import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.ModifierKind;
import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
* parent Schema Node for Extended Subtype
*
*/
- private void resolveExtendedSubtypeAsUnion(final GeneratedTOBuilder parentUnionGenTOBuilder, final TypeDefinition<?> unionSubtype, final List<String> regularExpressions, final SchemaNode parentNode) {
+ private void resolveExtendedSubtypeAsUnion(final GeneratedTOBuilder parentUnionGenTOBuilder,
+ final TypeDefinition<?> unionSubtype, final List<String> regularExpressions, final SchemaNode parentNode) {
final String unionTypeName = unionSubtype.getQName().getLocalName();
final Type genTO = findGenTO(unionTypeName, unionSubtype);
if (genTO != null) {
final List<String> regExps = new ArrayList<>(patternConstraints.size());
for (final PatternConstraint patternConstraint : patternConstraints) {
- final String regEx = patternConstraint.getJavaPatternString();
+ String regEx = patternConstraint.getJavaPatternString();
+
+ // The pattern can be inverted
+ final Optional<ModifierKind> optModifier = patternConstraint.getModifier();
+ if (optModifier.isPresent()) {
+ regEx = applyModifier(optModifier.get(), regEx);
+ }
+
final String modifiedRegEx = StringEscapeUtils.escapeJava(regEx);
regExps.add(modifiedRegEx);
}
return regExps;
}
+ private static String applyModifier(final ModifierKind modifier, final String pattern) {
+ switch (modifier) {
+ case INVERT_MATCH:
+ return BindingMapping.negatePatternString(pattern);
+ default:
+ LOG.warn("Ignoring unhandled modifier {}", modifier);
+ return pattern;
+ }
+ }
+
/**
*
* Adds to the <code>genTOBuilder</code> the constant which contains regular
public static final String RPC_INPUT_SUFFIX = "Input";
public static final String RPC_OUTPUT_SUFFIX = "Output";
+ private static final String NEGATED_PATTERN_PREFIX = "^(?!";
+ private static final String NEGATED_PATTERN_SUFFIX = ").*$";
+
private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
private BindingMapping() {
}
}
+ /**
+ * Create a {@link Pattern} expression which performs inverted match to the specified pattern. The input pattern
+ * is expected to be a valid regular expression passing {@link Pattern#compile(String)} and to have both start and
+ * end of string anchors as the first and last characters.
+ *
+ * @param pattern Pattern regular expression to negate
+ * @return Negated regular expression
+ * @throws IllegalArgumentException if the pattern does not conform to expected structure
+ * @throws NullPointerException if pattern is null
+ */
+ public static String negatePatternString(final String pattern) {
+ checkArgument(pattern.charAt(0) == '^' && pattern.charAt(pattern.length() - 1) == '$',
+ "Pattern '%s' does not have expected format", pattern);
+
+ /*
+ * Converting the expression into a negation is tricky. For example, when we have:
+ *
+ * pattern "a|b" { modifier invert-match; }
+ *
+ * this gets escaped into either "^a|b$" or "^(?:a|b)$". Either format can occur, as the non-capturing group
+ * strictly needed only in some cases. From that we want to arrive at:
+ * "^(?!(?:a|b)$).*$".
+ *
+ * ^^^ original expression
+ * ^^^^^^^^ tail of a grouped expression (without head anchor)
+ * ^^^^ ^^^^ inversion of match
+ *
+ * Inversion works by explicitly anchoring at the start of the string and then:
+ * - specifying a negative lookahead until the end of string
+ * - matching any string
+ * - anchoring at the end of the string
+ */
+ final boolean hasGroup = pattern.startsWith("^(?:") && pattern.endsWith(")$");
+ final int len = pattern.length();
+ final StringBuilder sb = new StringBuilder(len + (hasGroup ? 7 : 11)).append(NEGATED_PATTERN_PREFIX);
+
+ if (hasGroup) {
+ sb.append(pattern, 1, len);
+ } else {
+ sb.append("(?:").append(pattern, 1, len - 1).append(")$");
+ }
+ return sb.append(NEGATED_PATTERN_SUFFIX).toString();
+ }
+
+ /**
+ * Check if the specified {@link Pattern} is the result of {@link #negatePatternString(String)}. This method
+ * assumes the pattern was not hand-coded but rather was automatically-generated, such that its non-automated
+ * parts come from XSD regular expressions. If this constraint is violated, this method may result false positives.
+ *
+ * @param pattern Pattern to check
+ * @return True if this pattern is a negation.
+ * @throws NullPointerException if pattern is null
+ * @throws IllegalArgumentException if the pattern does not conform to expected structure
+ */
+ public static boolean isNegatedPattern(final Pattern pattern) {
+ return isNegatedPattern(pattern.toString());
+ }
+
+ private static boolean isNegatedPattern(final String pattern) {
+ return pattern.startsWith(NEGATED_PATTERN_PREFIX) && pattern.endsWith(NEGATED_PATTERN_SUFFIX);
+ }
+
/**
* Returns the {@link String} {@code s} with an {@link Character#isUpperCase(char) upper case} first character. This
* function is null-safe.
import org.opendaylight.yangtools.yang.model.api.Status;
import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.ModifierKind;
import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Auxiliary util class for {@link TypeProviderImpl} class
*/
@Beta
final class TypeGenHelper {
+ private static final Logger LOG = LoggerFactory.getLogger(TypeGenHelper.class);
private TypeGenHelper() {
throw new UnsupportedOperationException("Util class");
final List<String> regExps = new ArrayList<>(patternConstraints.size());
for (final PatternConstraint patternConstraint : patternConstraints) {
- final String regEx = patternConstraint.getJavaPatternString();
+ String regEx = patternConstraint.getJavaPatternString();
+
+ // The pattern can be inverted
+ final Optional<ModifierKind> optModifier = patternConstraint.getModifier();
+ if (optModifier.isPresent()) {
+ regEx = applyModifier(optModifier.get(), regEx);
+ }
+
final String modifiedRegEx = StringEscapeUtils.escapeJava(regEx);
regExps.add(modifiedRegEx);
}
return regExps;
}
+ private static String applyModifier(final ModifierKind modifier, final String pattern) {
+ switch (modifier) {
+ case INVERT_MATCH:
+ return BindingMapping.negatePatternString(pattern);
+ default:
+ LOG.warn("Ignoring unhandled modifier {}", modifier);
+ return pattern;
+ }
+ }
+
/**
* Finds out for each type definition how many immersion (depth) is
* necessary to get to the base type. Every type definition is inserted to
*/
public static final String PACKAGE_PREFIX = "org.opendaylight.mdsal.gen.javav2";
- private static final Pattern COLON_SLASH_SLASH = Pattern.compile("://", Pattern.LITERAL);
- private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
public static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
public static final String PATTERN_CONSTANT_NAME = "PATTERN_CONSTANTS";
public static final String RPC_INPUT_SUFFIX = "Input";
public static final String RPC_OUTPUT_SUFFIX = "Output";
+ private static final Pattern COLON_SLASH_SLASH = Pattern.compile("://", Pattern.LITERAL);
+ private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
+ private static final String NEGATED_PATTERN_PREFIX = "^(?!";
+ private static final String NEGATED_PATTERN_SUFFIX = ").*$";
+
private BindingMapping() {
throw new UnsupportedOperationException("Utility class");
}
return packageNameBuilder.toString();
}
+ /**
+ * Create a {@link Pattern} expression which performs inverted match to the specified pattern. The input pattern
+ * is expected to be a valid regular expression passing {@link Pattern#compile(String)} and to have both start and
+ * end of string anchors as the first and last characters.
+ *
+ * @param pattern Pattern regular expression to negate
+ * @return Negated regular expression
+ * @throws IllegalArgumentException if the pattern does not conform to expected structure
+ * @throws NullPointerException if pattern is null
+ */
+ public static String negatePatternString(final String pattern) {
+ checkArgument(pattern.charAt(0) == '^' && pattern.charAt(pattern.length() - 1) == '$',
+ "Pattern '%s' does not have expected format", pattern);
+
+ /*
+ * Converting the expression into a negation is tricky. For example, when we have:
+ *
+ * pattern "a|b" { modifier invert-match; }
+ *
+ * this gets escaped into either "^a|b$" or "^(?:a|b)$". Either format can occur, as the non-capturing group
+ * strictly needed only in some cases. From that we want to arrive at:
+ * "^(?!(?:a|b)$).*$".
+ *
+ * ^^^ original expression
+ * ^^^^^^^^ tail of a grouped expression (without head anchor)
+ * ^^^^ ^^^^ inversion of match
+ *
+ * Inversion works by explicitly anchoring at the start of the string and then:
+ * - specifying a negative lookahead until the end of string
+ * - matching any string
+ * - anchoring at the end of the string
+ */
+ final boolean hasGroup = pattern.startsWith("^(?:") && pattern.endsWith(")$");
+ final int len = pattern.length();
+ final StringBuilder sb = new StringBuilder(len + (hasGroup ? 7 : 11)).append(NEGATED_PATTERN_PREFIX);
+
+ if (hasGroup) {
+ sb.append(pattern, 1, len);
+ } else {
+ sb.append("(?:").append(pattern, 1, len - 1).append(")$");
+ }
+ return sb.append(NEGATED_PATTERN_SUFFIX).toString();
+ }
+
+ /**
+ * Check if the specified {@link Pattern} is the result of {@link #negatePatternString(String)}. This method
+ * assumes the pattern was not hand-coded but rather was automatically-generated, such that its non-automated
+ * parts come from XSD regular expressions. If this constraint is violated, this method may result false positives.
+ *
+ * @param pattern Pattern to check
+ * @return True if this pattern is a negation.
+ * @throws NullPointerException if pattern is null
+ * @throws IllegalArgumentException if the pattern does not conform to expected structure
+ */
+ public static boolean isNegatedPattern(final Pattern pattern) {
+ return isNegatedPattern(pattern.toString());
+ }
+
+ private static boolean isNegatedPattern(final String pattern) {
+ return pattern.startsWith(NEGATED_PATTERN_PREFIX) && pattern.endsWith(NEGATED_PATTERN_SUFFIX);
+ }
+
//TODO: further implementation of static util methods...
}