blob: 0d684503a14d5655fec863069e8c5553e59bb397 [file] [log] [blame]
package com.intellij.structuralsearch.impl.matcher.compiler;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.structuralsearch.*;
import com.intellij.structuralsearch.plugin.ui.Configuration;
import org.jetbrains.annotations.NonNls;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Created by IntelliJ IDEA.
* User: maxim
* Date: 17.11.2004
* Time: 19:29:05
* To change this template use File | Settings | File Templates.
*/
class StringToConstraintsTransformer {
@NonNls private static final String P_STR = "(\\w+)\\('(\\w+)\\)";
private static final Pattern p = Pattern.compile(P_STR);
@NonNls private static final String P2_STR = "(\\w+)";
private static final Pattern p2 = Pattern.compile(P2_STR);
@NonNls private static final String P3_STR = "(\\w+)\\(( ?(?:[\\\"\\*<>!\\.\\?\\:\\$\\\\\\(\\)\\[\\]\\w\\|\\+ =]*|(?:\\\"[^\\\"]*\\\")) ?)\\)";
private static final Pattern p3 = Pattern.compile(P3_STR);
@NonNls private static final String REF = "ref";
@NonNls private static final String READ = "read";
@NonNls private static final String WRITE = "write";
@NonNls private static final String REGEX = "regex";
@NonNls private static final String REGEXW = "regexw";
@NonNls private static final String EXPRTYPE = "exprtype";
@NonNls private static final String FORMAL = "formal";
@NonNls private static final String SCRIPT = "script";
@NonNls private static final String CONTAINS = "contains";
@NonNls private static final String WITHIN = "within";
static void transformOldPattern(MatchOptions options) {
final String pattern = options.getSearchPattern();
final StringBuilder buf = new StringBuilder();
StringBuilder miscBuffer = null;
int anonymousTypedVarsCount = 0;
for(int index=0;index < pattern.length();++index) {
char ch = pattern.charAt(index);
if (ch=='\'') {
// doubling '
final int length = pattern.length();
if (index + 1 < length &&
pattern.charAt(index + 1)=='\''
) {
// ignore next '
index++;
} else if (index + 2 < length &&
pattern.charAt(index + 2)=='\''
) {
// eat simple character
buf.append(ch);
buf.append(pattern.charAt(++index));
ch = pattern.charAt(++index);
} else if (index + 3 < length &&
pattern.charAt(index + 1)=='\\' &&
pattern.charAt(index + 3)=='\''
) {
// eat simple escape character
buf.append(ch);
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
ch = pattern.charAt(++index);
} else if (index + 7 < length &&
pattern.charAt(index + 1)=='\\' &&
pattern.charAt(index + 2)=='u' &&
pattern.charAt(index + 7)=='\'') {
// eat simple escape character
buf.append(ch);
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
buf.append(pattern.charAt(++index));
ch = pattern.charAt(++index);
} else {
// typed variable
buf.append("$");
if (miscBuffer == null) miscBuffer = new StringBuilder();
else miscBuffer.setLength(0);
// eat the name of typed var
for(++index; index< length && Character.isJavaIdentifierPart(ch = pattern.charAt(index)); ++index) {
miscBuffer.append(ch);
buf.append(ch);
}
boolean anonymous = false;
if (miscBuffer.length() == 0) throw new MalformedPatternException(SSRBundle.message("error.expected.character"));
if (miscBuffer.charAt(0)=='_') {
anonymous = true;
if(miscBuffer.length() == 1) {
// anonymous var, make it unique for the case of constraints
anonymousTypedVarsCount++;
miscBuffer.append(anonymousTypedVarsCount);
buf.append(anonymousTypedVarsCount);
} else {
buf.deleteCharAt(buf.length()-miscBuffer.length());
miscBuffer.deleteCharAt(0);
}
}
buf.append("$");
String typedVar = miscBuffer.toString();
int minOccurs = 1;
int maxOccurs = 1;
boolean greedy = true;
MatchVariableConstraint constraint = options.getVariableConstraint(typedVar);
boolean constraintCreated = false;
if (constraint==null) {
constraint = new MatchVariableConstraint();
constraint.setName( typedVar );
constraintCreated = true;
}
// Check the number of occurrences for typed variable
if (index < length) {
char possibleQuantifier = pattern.charAt(index);
if (possibleQuantifier=='+') {
maxOccurs = Integer.MAX_VALUE;
++index;
} else if (possibleQuantifier=='?') {
minOccurs = 0;
++index;
} else if (possibleQuantifier=='*') {
minOccurs = 0;
maxOccurs = Integer.MAX_VALUE;
++index;
} else if (possibleQuantifier=='{') {
++index;
minOccurs = 0;
while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
minOccurs *= 10;
minOccurs += (ch - '0');
if (minOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
++index;
}
if (ch==',') {
++index;
maxOccurs = 0;
while (index < length && (ch = pattern.charAt(index)) >= '0' && ch <= '9') {
maxOccurs *= 10;
maxOccurs += (ch - '0');
if (maxOccurs < 0) throw new MalformedPatternException(SSRBundle.message("error.overflow"));
++index;
}
} else {
maxOccurs = Integer.MAX_VALUE;
}
if (ch != '}') {
if (maxOccurs == Integer.MAX_VALUE) throw new MalformedPatternException(SSRBundle.message("error.expected.brace1"));
else throw new MalformedPatternException(SSRBundle.message("error.expected.brace2"));
}
++index;
}
if (index < length) {
ch = pattern.charAt(index);
if (ch=='?') {
greedy = false;
++index;
}
}
}
if (constraintCreated) {
constraint.setMinCount(minOccurs);
constraint.setMaxCount(maxOccurs);
constraint.setGreedy(greedy);
constraint.setPartOfSearchResults(!anonymous);
}
if (index < length && pattern.charAt(index) == ':') {
++index;
if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ':'));
ch = pattern.charAt(index);
if (ch == ':') {
// double colon instead of condition
buf.append(ch);
}
else {
index = eatTypedVarCondition(index, pattern, miscBuffer, constraint);
}
}
if (constraintCreated) {
if (constraint.getWithinConstraint().length() > 0) {
constraint.setName(Configuration.CONTEXT_VAR_NAME);
}
options.addVariableConstraint(constraint);
}
if (index == length) break;
// fall through to append white space
ch = pattern.charAt(index);
}
}
buf.append(ch);
}
options.setSearchPattern( buf.toString() );
}
private static int eatTypedVarCondition(int index,
String pattern,
StringBuilder miscBuffer,
MatchVariableConstraint constraint) {
final int length = pattern.length();
char ch = pattern.charAt(index);
if (ch == '+' || ch == '*') {
// this is type axis navigation relation
switch(ch) {
case '+':
constraint.setStrictlyWithinHierarchy(true);
break;
case '*':
constraint.setWithinHierarchy(true);
break;
}
++index;
if (index >= length) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", ch));
ch = pattern.charAt(index);
}
if (ch == '[') {
// eat complete condition
miscBuffer.setLength(0);
for(++index; index < length && ((ch = pattern.charAt(index))!=']' || pattern.charAt(index-1)=='\\'); ++index) {
miscBuffer.append(ch);
}
if (ch != ']') throw new MalformedPatternException(SSRBundle.message("error.expected.condition.or.bracket"));
++index;
parseCondition(constraint, miscBuffer.toString());
} else {
// eat reg exp constraint
miscBuffer.setLength(0);
index = handleRegExp(index, pattern, miscBuffer, constraint);
}
return index;
}
private static int handleRegExp(int index,
String pattern,
StringBuilder miscBuffer,
MatchVariableConstraint constraint) {
char c = pattern.charAt(index - 1);
final int length = pattern.length();
for(char ch; index < length && !Character.isWhitespace(ch = pattern.charAt(index)); ++index) {
miscBuffer.append(ch);
}
if (miscBuffer.length() == 0)
if (c == ':') throw new MalformedPatternException(SSRBundle.message("error.expected.condition", c));
else return index;
String regexp = miscBuffer.toString();
if (constraint.getRegExp()!=null &&
constraint.getRegExp().length() > 0 &&
!constraint.getRegExp().equals(regexp)) {
throw new MalformedPatternException(SSRBundle.message("error.two.different.type.constraints"));
} else {
try {
Pattern.compile(regexp);
} catch (PatternSyntaxException e) {
throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
}
constraint.setRegExp(regexp);
}
return index;
}
private static void parseCondition(MatchVariableConstraint constraint, String condition) {
if (condition.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "["));
final List<String> tokens = StringUtil.split(condition, "&&", true, false);
for (String token : tokens) {
token = token.trim();
if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "&&"));
boolean hasNot = false;
boolean consumed = false;
if (StringUtil.startsWithChar(token, '!')) {
token = token.substring(1);
if (token.isEmpty()) throw new MalformedPatternException(SSRBundle.message("error.expected.condition", "!"));
hasNot = true;
}
Matcher m = p.matcher(token);
if (m.matches()) {
String option = m.group(1);
if (option.equalsIgnoreCase(REF)) {
String name = m.group(2);
constraint.setReference(true);
constraint.setInvertReference(hasNot);
constraint.setNameOfReferenceVar(name);
consumed = true;
}
} else {
m = p2.matcher(token);
if (m.matches()) {
String option = m.group(1);
if (option.equalsIgnoreCase(READ)) {
constraint.setReadAccess(true);
constraint.setInvertReadAccess(hasNot);
consumed = true;
} else if (option.equalsIgnoreCase(WRITE)) {
constraint.setWriteAccess(true);
constraint.setInvertWriteAccess(hasNot);
consumed = true;
}
} else {
m = p3.matcher(token);
if (m.matches()) {
String option = m.group(1);
if (option.equalsIgnoreCase(REGEX) || option.equalsIgnoreCase(REGEXW)) {
String typePattern = getSingleParameter(m, SSRBundle.message("reg.exp.should.be.delimited.with.spaces.error.message"));
if (typePattern.isEmpty()) throw new MalformedPatternException(SSRBundle.message("no.reg.exp.specified.error.message"));
if (StringUtil.startsWithChar(typePattern, '*')) {
typePattern = typePattern.substring(1);
constraint.setWithinHierarchy(true);
}
try {
Pattern.compile(typePattern);
} catch (PatternSyntaxException e) {
throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
}
constraint.setRegExp( typePattern );
constraint.setInvertRegExp( hasNot );
consumed = true;
if (option.equalsIgnoreCase(REGEXW)) {
constraint.setWholeWordsOnly(true);
}
} else if (option.equalsIgnoreCase(EXPRTYPE)) {
String exprTypePattern = getSingleParameter(m, SSRBundle.message(
"reg.exp.in.expr.type.should.be.delimited.with.spaces.error.message"));
if (StringUtil.startsWithChar(exprTypePattern, '*')) {
exprTypePattern = exprTypePattern.substring(1);
constraint.setExprTypeWithinHierarchy(true);
}
try {
Pattern.compile(exprTypePattern);
} catch (PatternSyntaxException e) {
throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
}
constraint.setNameOfExprType( exprTypePattern );
constraint.setInvertExprType( hasNot );
consumed = true;
} else if (option.equalsIgnoreCase(FORMAL)) {
String exprTypePattern = getSingleParameter(m, SSRBundle.message(
"reg.exp.in.formal.arg.type.should.be.delimited.with.spaces.error.message"));
if (StringUtil.startsWithChar(exprTypePattern, '*')) {
exprTypePattern = exprTypePattern.substring(1);
constraint.setFormalArgTypeWithinHierarchy(true);
}
try {
Pattern.compile(exprTypePattern);
} catch (PatternSyntaxException e) {
throw new MalformedPatternException(SSRBundle.message("invalid.regular.expression"));
}
constraint.setNameOfFormalArgType( exprTypePattern );
constraint.setInvertFormalType( hasNot );
consumed = true;
} else if (option.equalsIgnoreCase(SCRIPT)) {
String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
constraint.setScriptCodeConstraint( script );
consumed = true;
} else if (option.equalsIgnoreCase(CONTAINS)) {
if (hasNot) constraint.setInvertContainsConstraint(true);
String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
constraint.setContainsConstraint(script );
consumed = true;
} else if (option.equalsIgnoreCase(WITHIN)) {
if (hasNot) constraint.setInvertWithinConstraint(true);
String script = getSingleParameter(m, SSRBundle.message("script.should.be.delimited.with.spaces.error.message"));
constraint.setWithinConstraint(script);
consumed = true;
}
}
}
}
if (!consumed) {
throw new UnsupportedPatternException(
SSRBundle.message("option.is.not.recognized.error.message", token)
);
}
}
}
private static String getSingleParameter(Matcher m, String errorMessage) {
final String value = m.group(2);
if (value.isEmpty()) return value;
if (value.charAt(0)!=' ' || value.charAt(value.length()-1)!=' ') {
throw new MalformedPatternException(errorMessage);
}
return value.trim();
}
}