Remove redundant string operations
[mdsal.git] / binding2 / mdsal-binding2-util / src / main / java / org / opendaylight / mdsal / binding / javav2 / util / BindingMapping.java
1 /*
2  * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.mdsal.binding.javav2.util;
10
11 import static com.google.common.base.Preconditions.checkArgument;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.ImmutableSet;
15 import java.util.Optional;
16 import java.util.Set;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19 import org.opendaylight.yangtools.yang.common.Revision;
20 import org.opendaylight.yangtools.yang.model.api.Module;
21
22 /**
23  * Standard Util class that provides generated Java related functionality
24  */
25 @Beta
26 public final class BindingMapping {
27
28     public static final Set<String> JAVA_RESERVED_WORDS = ImmutableSet.of("abstract", "assert", "boolean", "break",
29             "byte", "case", "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum",
30             "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof",
31             "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return",
32             "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
33             "true", "try", "void", "volatile", "while");
34
35     public static final Set<String> WINDOWS_RESERVED_WORDS = ImmutableSet.of("CON", "PRN", "AUX", "CLOCK$", "NUL",
36             "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2",
37             "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9");
38
39     public static final String QNAME_STATIC_FIELD_NAME = "QNAME";
40
41     /**
42      * Package prefix for Binding v2 generated Java code structures
43      */
44     public static final String PACKAGE_PREFIX = "org.opendaylight.mdsal.gen.javav2";
45
46     public static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
47     public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
48     public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
49     public static final String MEMBER_PATTERN_LIST = "patterns";
50     public static final String MEMBER_REGEX_LIST = "regexes";
51     public static final String RPC_INPUT_SUFFIX = "Input";
52     public static final String RPC_OUTPUT_SUFFIX = "Output";
53
54     private static final Pattern COLON_SLASH_SLASH = Pattern.compile("://", Pattern.LITERAL);
55     private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
56     private static final String NEGATED_PATTERN_PREFIX = "^(?!";
57     private static final String NEGATED_PATTERN_SUFFIX = ").*$";
58
59     private BindingMapping() {
60         throw new UnsupportedOperationException("Utility class");
61     }
62
63     public static String getRootPackageName(final Module module) {
64         checkArgument(module != null, "Module must not be null");
65         checkArgument(module.getRevision() != null, "Revision must not be null");
66         checkArgument(module.getNamespace() != null, "Namespace must not be null");
67
68         final StringBuilder packageNameBuilder = new StringBuilder();
69         packageNameBuilder.append(PACKAGE_PREFIX);
70         packageNameBuilder.append('.');
71
72         String namespace = module.getNamespace().toString();
73         namespace = COLON_SLASH_SLASH.matcher(namespace).replaceAll(QUOTED_DOT);
74
75         final char[] chars = namespace.toCharArray();
76         for (int i = 0; i < chars.length; ++i) {
77             switch (chars[i]) {
78                 case '/':
79                 case ':':
80                 case '-':
81                 case '@':
82                 case '$':
83                 case '#':
84                 case '\'':
85                 case '*':
86                 case '+':
87                 case ',':
88                 case ';':
89                 case '=':
90                     chars[i] = '.';
91                     break;
92                 default:
93                     // no-op, any other character is kept as it is
94             }
95         }
96
97         packageNameBuilder.append(chars);
98         if (chars[chars.length - 1] != '.') {
99             packageNameBuilder.append('.');
100         }
101
102         //TODO: per yangtools dev, semantic version not used yet
103 //        final SemVer semVer = module.getSemanticVersion();
104 //        if (semVer != null) {
105 //            packageNameBuilder.append(semVer.toString());
106 //        } else {
107 //            packageNameBuilder.append("rev");
108 //            packageNameBuilder.append(PACKAGE_DATE_FORMAT.get().format(module.getRevision()));
109 //        }
110
111         final Optional<Revision> optRev = module.getRevision();
112         if (optRev.isPresent()) {
113             // Revision is in format 2017-10-26, we want the output to be 171026, which is a matter of picking the
114             // right characters.
115             final String rev = optRev.get().toString();
116             checkArgument(rev.length() == 10, "Unsupported revision %s", rev);
117             packageNameBuilder.append("rev").append(rev, 2, 4).append(rev, 5, 7).append(rev.substring(8));
118         } else {
119             // No-revision packages are special
120             packageNameBuilder.append("norev");
121         }
122         return packageNameBuilder.toString();
123     }
124
125     /**
126      * Create a {@link Pattern} expression which performs inverted match to the specified pattern. The input pattern
127      * is expected to be a valid regular expression passing {@link Pattern#compile(String)} and to have both start and
128      * end of string anchors as the first and last characters.
129      *
130      * @param pattern Pattern regular expression to negate
131      * @return Negated regular expression
132      * @throws IllegalArgumentException if the pattern does not conform to expected structure
133      * @throws NullPointerException if pattern is null
134      */
135     public static String negatePatternString(final String pattern) {
136         checkArgument(pattern.charAt(0) == '^' && pattern.charAt(pattern.length() - 1) == '$',
137                 "Pattern '%s' does not have expected format", pattern);
138
139         /*
140          * Converting the expression into a negation is tricky. For example, when we have:
141          *
142          *   pattern "a|b" { modifier invert-match; }
143          *
144          * this gets escaped into either "^a|b$" or "^(?:a|b)$". Either format can occur, as the non-capturing group
145          * strictly needed only in some cases. From that we want to arrive at:
146          *   "^(?!(?:a|b)$).*$".
147          *
148          *           ^^^         original expression
149          *        ^^^^^^^^       tail of a grouped expression (without head anchor)
150          *    ^^^^        ^^^^   inversion of match
151          *
152          * Inversion works by explicitly anchoring at the start of the string and then:
153          * - specifying a negative lookahead until the end of string
154          * - matching any string
155          * - anchoring at the end of the string
156          */
157         final boolean hasGroup = pattern.startsWith("^(?:") && pattern.endsWith(")$");
158         final int len = pattern.length();
159         final StringBuilder sb = new StringBuilder(len + (hasGroup ? 7 : 11)).append(NEGATED_PATTERN_PREFIX);
160
161         if (hasGroup) {
162             sb.append(pattern, 1, len);
163         } else {
164             sb.append("(?:").append(pattern, 1, len - 1).append(")$");
165         }
166         return sb.append(NEGATED_PATTERN_SUFFIX).toString();
167     }
168
169     /**
170      * Check if the specified {@link Pattern} is the result of {@link #negatePatternString(String)}. This method
171      * assumes the pattern was not hand-coded but rather was automatically-generated, such that its non-automated
172      * parts come from XSD regular expressions. If this constraint is violated, this method may result false positives.
173      *
174      * @param pattern Pattern to check
175      * @return True if this pattern is a negation.
176      * @throws NullPointerException if pattern is null
177      * @throws IllegalArgumentException if the pattern does not conform to expected structure
178      */
179     public static boolean isNegatedPattern(final Pattern pattern) {
180         return isNegatedPattern(pattern.toString());
181     }
182
183     private static boolean isNegatedPattern(final String pattern) {
184         return pattern.startsWith(NEGATED_PATTERN_PREFIX) && pattern.endsWith(NEGATED_PATTERN_SUFFIX);
185     }
186
187     //TODO: further implementation of static util methods...
188
189 }