/* * Copyright 2015 Nokia Solutions and Networks * Licensed under the Apache License, Version 2.0, * see license.txt file for details. */ package org.rf.ide.core.testdata.model.table.keywords.names; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; /** * @author Michal Anglart */ public class EmbeddedKeywordNamesSupport { public static boolean hasEmbeddedArguments(final String definitionName) { return !findEmbeddedArgumentsRanges(definitionName).isEmpty(); } /** * @return number of characters in definition which matches with given prefix */ public static int startsWithIgnoreCase(final String definitionName, final String occurrenceNamePrefix) { return startsWith(definitionName.toLowerCase(), occurrenceNamePrefix.toLowerCase()); } private static int startsWith(final String definitionName, final String occurrenceNamePrefix) { if (definitionName.startsWith(occurrenceNamePrefix)) { return occurrenceNamePrefix.length(); } else if (definitionName.indexOf('$') == -1) { return -1; } final RangeSet<Integer> varRanges = findEmbeddedArgumentsRanges(definitionName); int i = definitionName.length(); while (i >= 0) { final String shortenedDefinition = definitionName.substring(0, i); final boolean matches = matchesIgnoreCase(shortenedDefinition, occurrenceNamePrefix); if (matches) { return i; } i--; if (varRanges.contains(i)) { final Range<Integer> range = varRanges.rangeContaining(i); i = range.lowerEndpoint(); } } return -1; } public static boolean matchesIgnoreCase(final String definitionName, final String occurrenceName) { if (definitionName.equalsIgnoreCase(occurrenceName)) { return true; } else if (definitionName.indexOf('$') == -1) { return false; } final String regex = "^" + substituteVariablesWithRegex(definitionName, true) + "$"; try { return Pattern.matches(regex, occurrenceName.toLowerCase()); } catch (final PatternSyntaxException e) { return false; } } private static String substituteVariablesWithRegex(final String definitionName, final boolean ignoreCase) { final StringBuilder wholeRegex = new StringBuilder(); final RangeSet<Integer> varRanges = findEmbeddedArgumentsRanges(definitionName); StringBuilder exactWordPatternRegex = new StringBuilder(); int i = 0; while (i < definitionName.length()) { if (varRanges.contains(i)) { if (exactWordPatternRegex.length() > 0) { final String exactWordPattern = exactWordPatternRegex.toString(); wholeRegex.append(Pattern.quote(ignoreCase ? exactWordPattern.toLowerCase() : exactWordPattern)); exactWordPatternRegex = new StringBuilder(); } final Range<Integer> varRange = varRanges.rangeContaining(i); final String internalRegex = getEmbeddedArgumentRegex(definitionName, varRange); wholeRegex.append(internalRegex); i = varRange.upperEndpoint() + 1; } else { exactWordPatternRegex.append(definitionName.charAt(i)); i++; } } if (exactWordPatternRegex.length() > 0) { final String exactWordPattern = exactWordPatternRegex.toString(); wholeRegex.append(Pattern.quote(ignoreCase ? exactWordPattern.toLowerCase() : exactWordPattern)); } return wholeRegex.toString(); } private static String getEmbeddedArgumentRegex(final String definitionName, final Range<Integer> varRange) { final String varContent = definitionName.substring(varRange.lowerEndpoint() + 2, varRange.upperEndpoint()); final String unescapedRegex = varContent.indexOf(':') != -1 ? varContent.substring(varContent.indexOf(':') + 1) : ".+"; boolean isEscaped = false; final StringBuilder escapedRegex = new StringBuilder(); for (int i = 0; i < unescapedRegex.length(); i++) { final char currentChar = unescapedRegex.charAt(i); if (!isEscaped && currentChar == '\\') { isEscaped = true; continue; } if (isEscaped && currentChar != '\\' && currentChar != '}') { escapedRegex.append('\\'); } escapedRegex.append(currentChar); isEscaped = false; } return escapedRegex.toString(); } @VisibleForTesting static RangeSet<Integer> findEmbeddedArgumentsRanges(final String definitionName) { final RangeSet<Integer> varRanges = TreeRangeSet.create(); if (definitionName.isEmpty()) { return varRanges; } int varStart = -1; int currentIndex = 0; int currentState = 0; while (currentIndex < definitionName.length()) { final char character = definitionName.charAt(currentIndex); if (currentState == KeywordDfaState.START_STATE) { currentState = character == '$' ? KeywordDfaState.VAR_DOLLAR_DETECTED : KeywordDfaState.START_STATE; } else if (currentState == KeywordDfaState.VAR_DOLLAR_DETECTED) { currentState = character == '{' ? KeywordDfaState.VAR_START_DETECTED : KeywordDfaState.START_STATE; } else if (currentState == KeywordDfaState.VAR_START_DETECTED) { varStart = currentIndex - 2; if (character == '}') { currentState = KeywordDfaState.START_STATE; } else if (character == '\\') { currentState = KeywordDfaState.IN_VAR_ESCAPING; } else { currentState = KeywordDfaState.IN_VAR_NO_ESCAPE_CURRENTLY; } } else if (currentState == KeywordDfaState.IN_VAR_ESCAPING) { currentState = KeywordDfaState.IN_VAR_NO_ESCAPE_CURRENTLY; } else if (currentState == KeywordDfaState.IN_VAR_NO_ESCAPE_CURRENTLY) { if (character == '}') { varRanges.add(Range.closed(varStart, currentIndex)); currentState = KeywordDfaState.START_STATE; } else if (character == '\\') { currentState = KeywordDfaState.IN_VAR_ESCAPING; } else { currentState = KeywordDfaState.IN_VAR_NO_ESCAPE_CURRENTLY; } } else { throw new IllegalStateException("Unrecognized state"); } currentIndex++; } return varRanges; } public static Function<String, String> removeRegexFunction() { return new Function<String, String>() { @Override public String apply(final String variable) { return removeRegex(variable); } }; } public static String removeRegex(final String variable) { return variable.indexOf(':') != -1 ? variable.substring(0, variable.indexOf(':')) + "}" : variable; } private interface KeywordDfaState { static final int START_STATE = 0; static final int VAR_DOLLAR_DETECTED = 1; static final int VAR_START_DETECTED = 2; static final int IN_VAR_ESCAPING = 3; static final int IN_VAR_NO_ESCAPE_CURRENTLY = 4; } }