diff options
Diffstat (limited to 'plugins/structuralsearch/source/com/intellij/structuralsearch')
109 files changed, 14739 insertions, 0 deletions
diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java new file mode 100644 index 000000000000..1f319b8f15e5 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/DocumentBasedReplaceHandler.java @@ -0,0 +1,55 @@ +package com.intellij.structuralsearch; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.RangeMarker; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementInfoImpl; +import com.intellij.util.containers.HashMap; + +import java.util.Map; + +/** + * @author Eugene.Kudelevsky + */ +public class DocumentBasedReplaceHandler extends StructuralReplaceHandler { + private final Project myProject; + private final Map<ReplacementInfo, RangeMarker> myRangeMarkers = new HashMap<ReplacementInfo, RangeMarker>(); + + DocumentBasedReplaceHandler(Project project) { + myProject = project; + } + + public void replace(ReplacementInfo info, ReplaceOptions options) { + if (info.getMatchesCount() == 0) return; + assert info instanceof ReplacementInfoImpl; + PsiElement element = info.getMatch(0); + if (element == null) return; + PsiFile file = element instanceof PsiFile ? (PsiFile)element : element.getContainingFile(); + assert file != null; + RangeMarker rangeMarker = myRangeMarkers.get(info); + Document document = rangeMarker.getDocument(); + document.replaceString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset(), info.getReplacement()); + PsiDocumentManager.getInstance(element.getProject()).commitDocument(document); + } + + @Override + public void prepare(ReplacementInfo info) { + assert info instanceof ReplacementInfoImpl; + MatchResult result = ((ReplacementInfoImpl)info).getMatchResult(); + PsiElement element = result.getMatch(); + PsiFile file = element instanceof PsiFile ? (PsiFile)element : element.getContainingFile(); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(file); + TextRange textRange = result.getMatchRef().getElement().getTextRange(); + assert textRange != null; + RangeMarker rangeMarker = document.createRangeMarker(textRange); + rangeMarker.setGreedyToLeft(true); + rangeMarker.setGreedyToRight(true); + myRangeMarkers.put(info, rangeMarker); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java new file mode 100644 index 000000000000..e73eddc19550 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MalformedPatternException.java @@ -0,0 +1,11 @@ +package com.intellij.structuralsearch; + +import org.jetbrains.annotations.NonNls; + +/** + * Class to indicate incorrect pattern + */ +public class MalformedPatternException extends RuntimeException { + public MalformedPatternException() {} + public MalformedPatternException(String msg) { super(msg); } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java new file mode 100644 index 000000000000..fb22f39990fa --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchOptions.java @@ -0,0 +1,334 @@ +package com.intellij.structuralsearch; + +import com.intellij.lang.Language; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.psi.search.SearchScope; +import org.jdom.Attribute; +import org.jdom.DataConversionException; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * match options + */ +public class MatchOptions implements JDOMExternalizable, Cloneable { + @NonNls private static final String TEXT_ATTRIBUTE_NAME = "text"; + + private boolean looseMatching; + private boolean distinct; + private boolean recursiveSearch; + private boolean caseSensitiveMatch; + private boolean resultIsContextMatch = false; + private FileType myFileType = StructuralSearchUtil.getDefaultFileType(); + private Language myDialect = null; + private int maxMatches = Integer.MAX_VALUE; + + private SearchScope scope; + private SearchScope downUpMatchScope; + private String searchCriteria = ""; + private Map<String,MatchVariableConstraint> variableConstraints; + + private String myPatternContext; + + @NonNls private static final String DISTINCT_ATTRIBUTE_NAME = "distinct"; + @NonNls private static final String RECURSIVE_ATTRIBUTE_NAME = "recursive"; + @NonNls private static final String CASESENSITIVE_ATTRIBUTE_NAME = "caseInsensitive"; + //private static final String SCOPE_ATTRIBUTE_NAME = "scope"; + @NonNls private static final String CONSTRAINT_TAG_NAME = "constraint"; + @NonNls private static final String FILE_TYPE_ATTR_NAME = "type"; + @NonNls private static final String DIALECT_ATTR_NAME = "dialect"; + @NonNls public static final String INSTANCE_MODIFIER_NAME = "Instance"; + @NonNls public static final String MODIFIER_ANNOTATION_NAME = "Modifier"; + + //private static final String UNDEFINED_SCOPE = "undefined"; + + public void addVariableConstraint(MatchVariableConstraint constraint) { + if (variableConstraints==null) { + variableConstraints = new LinkedHashMap<String,MatchVariableConstraint>(); + } + variableConstraints.put( constraint.getName(), constraint ); + } + + public boolean hasVariableConstraints() { + return variableConstraints!=null; + } + + public void clearVariableConstraints() { + variableConstraints=null; + } + + public MatchVariableConstraint getVariableConstraint(String name) { + if (variableConstraints!=null) { + return variableConstraints.get(name); + } + return null; + } + + public Iterator<String> getVariableConstraintNames() { + if (variableConstraints==null) return null; + return variableConstraints.keySet().iterator(); + } + + public void setCaseSensitiveMatch(boolean caseSensitiveMatch) { + this.caseSensitiveMatch = caseSensitiveMatch; + } + + public boolean isCaseSensitiveMatch() { + return caseSensitiveMatch; + } + + @SuppressWarnings({"HardCodedStringLiteral"}) + public String toString() { + StringBuffer result = new StringBuffer(); + + result.append("match options:\n"); + result.append("search pattern:\n"); + result.append(searchCriteria); + result.append("\nsearch scope:\n"); + + // @TODO print scope + //result.append((scopeHandler!=null)?scopeHandler.toString():"undefined scope"); + + result.append("\nrecursive:"); + result.append(recursiveSearch); + + result.append("\ndistinct:"); + result.append(distinct); + + result.append("\ncasesensitive:"); + result.append(caseSensitiveMatch); + + return result.toString(); + } + + public boolean isDistinct() { + return distinct; + } + + public void setDistinct(boolean distinct) { + this.distinct = distinct; + } + + public boolean isRecursiveSearch() { + return recursiveSearch; + } + + public void setRecursiveSearch(boolean recursiveSearch) { + this.recursiveSearch = recursiveSearch; + } + + public boolean isLooseMatching() { + return looseMatching; + } + + public void setLooseMatching(boolean looseMatching) { + this.looseMatching = looseMatching; + } + + public void setSearchPattern(String text) { + searchCriteria = text; + } + + public String getSearchPattern() { + return searchCriteria; + } + + public int getMaxMatchesCount() { + return maxMatches; + } + + public boolean isResultIsContextMatch() { + return resultIsContextMatch; + } + + public void setResultIsContextMatch(boolean resultIsContextMatch) { + this.resultIsContextMatch = resultIsContextMatch; + } + + public SearchScope getScope() { + return scope; + } + + public void setScope(SearchScope scope) { + this.scope = scope; + } + + public SearchScope getDownUpMatchScope() { + return downUpMatchScope; + } + + public void setDownUpMatchScope(final SearchScope downUpMatchScope) { + this.downUpMatchScope = downUpMatchScope; + } + + public void writeExternal(Element element) { + element.setAttribute(TEXT_ATTRIBUTE_NAME,getSearchPattern()); + element.setAttribute(RECURSIVE_ATTRIBUTE_NAME,String.valueOf(recursiveSearch)); + if (distinct) element.setAttribute(DISTINCT_ATTRIBUTE_NAME,String.valueOf(distinct)); + element.setAttribute(CASESENSITIVE_ATTRIBUTE_NAME,String.valueOf(caseSensitiveMatch)); + + //@TODO serialize scope! + + //if (myFileType != StdFileTypes.JAVA) { + element.setAttribute(FILE_TYPE_ATTR_NAME, myFileType.getName()); + //} + + if (myDialect != null) { + element.setAttribute(DIALECT_ATTR_NAME, myDialect.getID()); + } + + if (variableConstraints!=null) { + for (final MatchVariableConstraint matchVariableConstraint : variableConstraints.values()) { + if (matchVariableConstraint.isArtificial()) continue; + final Element infoElement = new Element(CONSTRAINT_TAG_NAME); + element.addContent(infoElement); + matchVariableConstraint.writeExternal(infoElement); + } + } + } + + public void readExternal(Element element) { + setSearchPattern(element.getAttribute(TEXT_ATTRIBUTE_NAME).getValue()); + + Attribute attr = element.getAttribute(RECURSIVE_ATTRIBUTE_NAME); + if (attr!=null) { + try { + recursiveSearch = attr.getBooleanValue(); + } catch(DataConversionException ignored) {} + } + + attr = element.getAttribute(DISTINCT_ATTRIBUTE_NAME); + if (attr!=null) { + try { + distinct = attr.getBooleanValue(); + } catch(DataConversionException ignored) {} + } + + attr = element.getAttribute(CASESENSITIVE_ATTRIBUTE_NAME); + if (attr!=null) { + try { + caseSensitiveMatch = attr.getBooleanValue(); + } catch(DataConversionException ignored) {} + } + + attr = element.getAttribute(FILE_TYPE_ATTR_NAME); + if (attr!=null) { + String value = attr.getValue(); + myFileType = getFileTypeByName(value); + } + + attr = element.getAttribute(DIALECT_ATTR_NAME); + if (attr != null) { + myDialect = Language.findLanguageByID(attr.getValue()); + } + + // @TODO deserialize scope + + List<Element> elements = element.getChildren(CONSTRAINT_TAG_NAME); + if (elements!=null && !elements.isEmpty()) { + for (final Element element1 : elements) { + final MatchVariableConstraint constraint = new MatchVariableConstraint(); + constraint.readExternal(element1); + addVariableConstraint(constraint); + } + } + } + + private static FileType getFileTypeByName(String value) { + if (value != null) { + for (FileType type : StructuralSearchUtil.getSuitableFileTypes()) { + if (value.equals(type.getName())) { + return type; + } + } + } + + return StructuralSearchUtil.getDefaultFileType(); + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MatchOptions)) return false; + + final MatchOptions matchOptions = (MatchOptions)o; + + if (caseSensitiveMatch != matchOptions.caseSensitiveMatch) return false; + if (distinct != matchOptions.distinct) return false; + //if (enableAutoIdentifySearchTarget != matchOptions.enableAutoIdentifySearchTarget) return false; + if (looseMatching != matchOptions.looseMatching) return false; + if (recursiveSearch != matchOptions.recursiveSearch) return false; + // @TODO support scope + + if (searchCriteria != null ? !searchCriteria.equals(matchOptions.searchCriteria) : matchOptions.searchCriteria != null) return false; + if (variableConstraints != null ? !variableConstraints.equals(matchOptions.variableConstraints) : matchOptions.variableConstraints != + null) { + return false; + } + if (myFileType != matchOptions.myFileType) { + return false; + } + + if (myDialect != null ? !myDialect.equals(matchOptions.myDialect) : matchOptions.myDialect != null) { + return false; + } + + if (myPatternContext != null ? !myPatternContext.equals(matchOptions.myPatternContext) : matchOptions.myPatternContext != null) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = (looseMatching ? 1 : 0); + result = 29 * result + (distinct ? 1 : 0); + result = 29 * result + (recursiveSearch ? 1 : 0); + result = 29 * result + (caseSensitiveMatch ? 1 : 0); + // @TODO support scope + result = 29 * result + (searchCriteria != null ? searchCriteria.hashCode() : 0); + result = 29 * result + (variableConstraints != null ? variableConstraints.hashCode() : 0); + if (myFileType != null) result = 29 * result + myFileType.hashCode(); + if (myDialect != null) result = 29 * result + myDialect.hashCode(); + return result; + } + + public void setFileType(FileType fileType) { + myFileType = fileType; + } + + public FileType getFileType() { + return myFileType; + } + + public Language getDialect() { + return myDialect; + } + + public void setDialect(Language dialect) { + myDialect = dialect; + } + + public String getPatternContext() { + return myPatternContext; + } + + public void setPatternContext(String patternContext) { + myPatternContext = patternContext; + } + + public MatchOptions clone() { + try { + return (MatchOptions) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java new file mode 100644 index 000000000000..c5196e67178d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResult.java @@ -0,0 +1,29 @@ +package com.intellij.structuralsearch; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; + +import java.util.List; + +/** + * Class describing the match result + */ +public abstract class MatchResult { + @NonNls public static final String LINE_MATCH = "line"; + @NonNls public static final String MULTI_LINE_MATCH = "context"; + + public abstract String getMatchImage(); + + public abstract SmartPsiPointer getMatchRef(); + public abstract PsiElement getMatch(); + public abstract int getStart(); + public abstract int getEnd(); + + public abstract String getName(); + + public abstract List<MatchResult> getAllSons(); + public abstract boolean hasSons(); + public abstract boolean isScopeMatch(); + public abstract boolean isMultipleMatch(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java new file mode 100644 index 000000000000..8a4fdf968a8c --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchResultSink.java @@ -0,0 +1,35 @@ +package com.intellij.structuralsearch; + +import com.intellij.psi.PsiFile; +import com.intellij.structuralsearch.MatchingProcess; +import com.intellij.openapi.progress.ProgressIndicator; + +/** + * Interface for consumers of match results + */ +public interface MatchResultSink { + /** + * Notifies sink about new match + * @param result + */ + void newMatch(MatchResult result); + + /** + * Notifies sink about starting the matching for given element + * @param element the current file + */ + void processFile(PsiFile element); + + /** + * Sets the reference to the matching process + * @param matchingProcess the matching process reference + */ + void setMatchingProcess(MatchingProcess matchingProcess); + + /** + * Notifies sink about end of matching. + */ + void matchingFinished(); + + ProgressIndicator getProgressIndicator(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java new file mode 100644 index 000000000000..db2aaa901c54 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchVariableConstraint.java @@ -0,0 +1,558 @@ +package com.intellij.structuralsearch; + +import org.jdom.Element; +import org.jdom.Attribute; +import org.jdom.DataConversionException; +import org.jetbrains.annotations.NonNls; + +/** + * @author Maxim.Mossienko + * Date: Mar 19, 2004 + * Time: 5:36:32 PM + */ +public class MatchVariableConstraint extends NamedScriptableDefinition { + private String regExp = ""; + private boolean invertRegExp; + private boolean withinHierarchy; + private boolean strictlyWithinHierarchy; + private boolean wholeWordsOnly; + private int minCount = 1; + private int maxCount = 1; + private boolean readAccess; + private boolean invertReadAccess; + private boolean writeAccess; + private boolean invertWriteAccess; + private boolean greedy = true; + private boolean reference; + private boolean invertReference; + private String nameOfReferenceVar = ""; + private boolean partOfSearchResults; + private String nameOfExprType = ""; + private boolean invertExprType; + private boolean exprTypeWithinHierarchy; + + private String nameOfFormalArgType = ""; + private boolean invertFormalType; + private boolean formalArgTypeWithinHierarchy; + + private String withinConstraint = ""; + private String containsConstraint = ""; + private boolean invertContainsConstraint; + private boolean invertWithinConstraint; + private final boolean artificial; + + @NonNls private static final String NAME_OF_REFEENCE_VAR = "nameOfReferenceVar"; + @NonNls private static final String NAME_OF_EXPRTYPE = "nameOfExprType"; + @NonNls private static final String NAME_OF_FORMALTYPE = "nameOfFormalType"; + @NonNls private static final String REGEXP = "regexp"; + @NonNls private static final String EXPRTYPE_WITHIN_HIERARCHY = "exprTypeWithinHierarchy"; + @NonNls private static final String FORMALTYPE_WITHIN_HIERARCHY = "formalTypeWithinHierarchy"; + + @NonNls private static final String WITHIN_HIERARCHY = "withinHierarchy"; + @NonNls private static final String MAX_OCCURS = "maxCount"; + @NonNls private static final String MIN_OCCURS = "minCount"; + + @NonNls private static final String NEGATE_NAME_CONDITION = "negateName"; + @NonNls private static final String NEGATE_EXPRTYPE_CONDITION = "negateExprType"; + @NonNls private static final String NEGATE_FORMALTYPE_CONDITION = "negateFormalType"; + @NonNls private static final String NEGATE_READ_CONDITION = "negateRead"; + @NonNls private static final String NEGATE_WRITE_CONDITION = "negateWrite"; + @NonNls private static final String NEGATE_CONTAINS_CONDITION = "negateContains"; + @NonNls private static final String NEGATE_WITHIN_CONDITION = "negateWithin"; + @NonNls private static final String WITHIN_CONDITION = "within"; + @NonNls private static final String CONTAINS_CONDITION = "contains"; + @NonNls private static final String READ = "readAccess"; + @NonNls private static final String WRITE = "writeAccess"; + @NonNls private static final String TARGET = "target"; + + @NonNls private static final String WHOLE_WORDS_ONLY = "wholeWordsOnly"; + @NonNls private static final String TRUE = Boolean.TRUE.toString(); + + public MatchVariableConstraint() { this(false); } + public MatchVariableConstraint(boolean _artificial) { artificial = _artificial; } + + public boolean isGreedy() { + return greedy; + } + + public void setGreedy(boolean greedy) { + this.greedy = greedy; + } + + public String getRegExp() { + return regExp; + } + + public void setRegExp(String regExp) { + this.regExp = regExp; + } + + public boolean isInvertRegExp() { + return invertRegExp; + } + + public void setInvertRegExp(boolean invertRegExp) { + this.invertRegExp = invertRegExp; + } + + public boolean isWithinHierarchy() { + return withinHierarchy; + } + + public void setWithinHierarchy(boolean withinHierarchy) { + this.withinHierarchy = withinHierarchy; + } + + public int getMinCount() { + return minCount; + } + + public void setMinCount(int minCount) { + this.minCount = minCount; + } + + public int getMaxCount() { + return maxCount; + } + + public void setMaxCount(int maxCount) { + this.maxCount = maxCount; + } + + public boolean isReadAccess() { + return readAccess; + } + + public void setReadAccess(boolean readAccess) { + this.readAccess = readAccess; + } + + public boolean isInvertReadAccess() { + return invertReadAccess; + } + + public void setInvertReadAccess(boolean invertReadAccess) { + this.invertReadAccess = invertReadAccess; + } + + public boolean isWriteAccess() { + return writeAccess; + } + + public void setWriteAccess(boolean writeAccess) { + this.writeAccess = writeAccess; + } + + public boolean isInvertWriteAccess() { + return invertWriteAccess; + } + + public void setInvertWriteAccess(boolean invertWriteAccess) { + this.invertWriteAccess = invertWriteAccess; + } + + public boolean isPartOfSearchResults() { + return partOfSearchResults; + } + + public void setPartOfSearchResults(boolean partOfSearchResults) { + this.partOfSearchResults = partOfSearchResults; + } + + public boolean isReference() { + return reference; + } + + public void setReference(boolean reference) { + this.reference = reference; + } + + public boolean isInvertReference() { + return invertReference; + } + + public void setInvertReference(boolean invertReference) { + this.invertReference = invertReference; + } + + public String getNameOfReferenceVar() { + return nameOfReferenceVar; + } + + public void setNameOfReferenceVar(String nameOfReferenceVar) { + this.nameOfReferenceVar = nameOfReferenceVar; + } + + public boolean isStrictlyWithinHierarchy() { + return strictlyWithinHierarchy; + } + + public void setStrictlyWithinHierarchy(boolean strictlyWithinHierarchy) { + this.strictlyWithinHierarchy = strictlyWithinHierarchy; + } + + public String getNameOfExprType() { + return nameOfExprType; + } + + public void setNameOfExprType(String nameOfExprType) { + this.nameOfExprType = nameOfExprType; + } + + public boolean isInvertExprType() { + return invertExprType; + } + + public void setInvertExprType(boolean invertExprType) { + this.invertExprType = invertExprType; + } + + public boolean isExprTypeWithinHierarchy() { + return exprTypeWithinHierarchy; + } + + public void setExprTypeWithinHierarchy(boolean exprTypeWithinHierarchy) { + this.exprTypeWithinHierarchy = exprTypeWithinHierarchy; + } + + public boolean isWholeWordsOnly() { + return wholeWordsOnly; + } + + public void setWholeWordsOnly(boolean wholeWordsOnly) { + this.wholeWordsOnly = wholeWordsOnly; + } + + public String getNameOfFormalArgType() { + return nameOfFormalArgType; + } + + public void setNameOfFormalArgType(String nameOfFormalArgType) { + this.nameOfFormalArgType = nameOfFormalArgType; + } + + public boolean isInvertFormalType() { + return invertFormalType; + } + + public void setInvertFormalType(boolean invertFormalType) { + this.invertFormalType = invertFormalType; + } + + public boolean isFormalArgTypeWithinHierarchy() { + return formalArgTypeWithinHierarchy; + } + + public void setFormalArgTypeWithinHierarchy(boolean formalArgTypeWithinHierarchy) { + this.formalArgTypeWithinHierarchy = formalArgTypeWithinHierarchy; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MatchVariableConstraint)) return false; + if (!(super.equals(o))) return false; + + final MatchVariableConstraint matchVariableConstraint = (MatchVariableConstraint)o; + + if (exprTypeWithinHierarchy != matchVariableConstraint.exprTypeWithinHierarchy) return false; + if (formalArgTypeWithinHierarchy != matchVariableConstraint.formalArgTypeWithinHierarchy) return false; + if (greedy != matchVariableConstraint.greedy) return false; + if (invertExprType != matchVariableConstraint.invertExprType) return false; + if (invertFormalType != matchVariableConstraint.invertFormalType) return false; + if (invertReadAccess != matchVariableConstraint.invertReadAccess) return false; + if (invertReference != matchVariableConstraint.invertReference) return false; + if (invertRegExp != matchVariableConstraint.invertRegExp) return false; + if (invertWriteAccess != matchVariableConstraint.invertWriteAccess) return false; + if (maxCount != matchVariableConstraint.maxCount) return false; + if (minCount != matchVariableConstraint.minCount) return false; + if (partOfSearchResults != matchVariableConstraint.partOfSearchResults) return false; + if (readAccess != matchVariableConstraint.readAccess) return false; + if (reference != matchVariableConstraint.reference) return false; + if (strictlyWithinHierarchy != matchVariableConstraint.strictlyWithinHierarchy) return false; + if (wholeWordsOnly != matchVariableConstraint.wholeWordsOnly) return false; + if (withinHierarchy != matchVariableConstraint.withinHierarchy) return false; + if (writeAccess != matchVariableConstraint.writeAccess) return false; + if (!nameOfExprType.equals(matchVariableConstraint.nameOfExprType)) return false; + if (!nameOfFormalArgType.equals(matchVariableConstraint.nameOfFormalArgType)) return false; + if (!nameOfReferenceVar.equals(matchVariableConstraint.nameOfReferenceVar)) return false; + if (!regExp.equals(matchVariableConstraint.regExp)) return false; + if (!withinConstraint.equals(matchVariableConstraint.withinConstraint)) return false; + if (!containsConstraint.equals(matchVariableConstraint.containsConstraint)) return false; + if (invertWithinConstraint != matchVariableConstraint.invertWithinConstraint) return false; + if (invertContainsConstraint != matchVariableConstraint.invertContainsConstraint) return false; + + return true; + } + + public int hashCode() { + int result; + result = super.hashCode(); + result = 29 * result + regExp.hashCode(); + result = 29 * result + (invertRegExp ? 1 : 0); + result = 29 * result + (withinHierarchy ? 1 : 0); + result = 29 * result + (strictlyWithinHierarchy ? 1 : 0); + result = 29 * result + (wholeWordsOnly ? 1 : 0); + result = 29 * result + minCount; + result = 29 * result + maxCount; + result = 29 * result + (readAccess ? 1 : 0); + result = 29 * result + (invertReadAccess ? 1 : 0); + result = 29 * result + (writeAccess ? 1 : 0); + result = 29 * result + (invertWriteAccess ? 1 : 0); + result = 29 * result + (greedy ? 1 : 0); + result = 29 * result + (reference ? 1 : 0); + result = 29 * result + (invertReference ? 1 : 0); + result = 29 * result + nameOfReferenceVar.hashCode(); + result = 29 * result + (partOfSearchResults ? 1 : 0); + result = 29 * result + nameOfExprType.hashCode(); + result = 29 * result + (invertExprType ? 1 : 0); + result = 29 * result + (exprTypeWithinHierarchy ? 1 : 0); + result = 29 * result + nameOfFormalArgType.hashCode(); + result = 29 * result + (invertFormalType ? 1 : 0); + result = 29 * result + (formalArgTypeWithinHierarchy ? 1 : 0); + result = 29 * result + withinConstraint.hashCode(); + result = 29 * result + containsConstraint.hashCode(); + + if (invertContainsConstraint) result = 29 * result + 1; + if (invertWithinConstraint) result = 29 * result + 1; + return result; + } + + public void readExternal(Element element) { + super.readExternal(element); + Attribute attribute; + + attribute = element.getAttribute(REGEXP); + if (attribute != null) { + regExp = attribute.getValue(); + } + + attribute = element.getAttribute(NAME_OF_EXPRTYPE); + if (attribute != null) { + nameOfExprType = attribute.getValue(); + } + + attribute = element.getAttribute(NAME_OF_FORMALTYPE); + if (attribute != null) { + nameOfFormalArgType = attribute.getValue(); + } + + attribute = element.getAttribute(NAME_OF_REFEENCE_VAR); + if (attribute != null) { + nameOfReferenceVar = attribute.getValue(); + } + + attribute = element.getAttribute(WITHIN_HIERARCHY); + if (attribute != null) { + try { + withinHierarchy = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(EXPRTYPE_WITHIN_HIERARCHY); + if (attribute != null) { + try { + exprTypeWithinHierarchy = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(FORMALTYPE_WITHIN_HIERARCHY); + if (attribute != null) { + try { + formalArgTypeWithinHierarchy = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_NAME_CONDITION); + if (attribute != null) { + try { + invertRegExp = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_EXPRTYPE_CONDITION); + if (attribute != null) { + try { + invertExprType = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_FORMALTYPE_CONDITION); + if (attribute != null) { + try { + invertFormalType = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_READ_CONDITION); + if (attribute != null) { + try { + invertReadAccess = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_WRITE_CONDITION); + if (attribute != null) { + try { + invertWriteAccess = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(READ); + if (attribute != null) { + try { + readAccess = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(WRITE); + if (attribute != null) { + try { + writeAccess = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(TARGET); + if (attribute != null) { + try { + partOfSearchResults = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(MIN_OCCURS); + if (attribute != null) { + try { + minCount = attribute.getIntValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(MAX_OCCURS); + if (attribute != null) { + try { + maxCount = attribute.getIntValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(WHOLE_WORDS_ONLY); + if (attribute != null) { + try { + wholeWordsOnly = attribute.getBooleanValue(); + } + catch (DataConversionException ex) { + } + } + + attribute = element.getAttribute(NEGATE_WITHIN_CONDITION); + if (attribute != null) { + try { + invertWithinConstraint = attribute.getBooleanValue(); + } catch (DataConversionException ex) {} + } + + attribute = element.getAttribute(NEGATE_CONTAINS_CONDITION); + if (attribute != null) { + try { + invertContainsConstraint = attribute.getBooleanValue(); + } catch (DataConversionException ex) {} + } + + attribute = element.getAttribute(CONTAINS_CONDITION); + if(attribute != null) containsConstraint = attribute.getValue(); + + attribute = element.getAttribute(WITHIN_CONDITION); + if(attribute != null) withinConstraint = attribute.getValue(); + } + + public void writeExternal(Element element) { + super.writeExternal(element); + + if (regExp.length() > 0) element.setAttribute(REGEXP,regExp); + if (nameOfExprType.length() > 0) element.setAttribute(NAME_OF_EXPRTYPE,nameOfExprType); + if (nameOfReferenceVar.length() > 0) element.setAttribute(NAME_OF_REFEENCE_VAR,nameOfReferenceVar); + if (nameOfFormalArgType.length() > 0) element.setAttribute(NAME_OF_FORMALTYPE,nameOfFormalArgType); + + + if (withinHierarchy) element.setAttribute(WITHIN_HIERARCHY,TRUE); + if (exprTypeWithinHierarchy) element.setAttribute(EXPRTYPE_WITHIN_HIERARCHY,TRUE); + if (formalArgTypeWithinHierarchy) element.setAttribute(FORMALTYPE_WITHIN_HIERARCHY,TRUE); + + if (minCount!=1) element.setAttribute(MIN_OCCURS,String.valueOf(minCount)); + if (maxCount!=1) element.setAttribute(MAX_OCCURS,String.valueOf(maxCount)); + if (partOfSearchResults) element.setAttribute(TARGET,TRUE); + if (readAccess) element.setAttribute(READ,TRUE); + if (writeAccess) element.setAttribute(WRITE,TRUE); + + if (invertRegExp) element.setAttribute(NEGATE_NAME_CONDITION,TRUE); + if (invertExprType) element.setAttribute(NEGATE_EXPRTYPE_CONDITION,TRUE); + if (invertFormalType) element.setAttribute(NEGATE_FORMALTYPE_CONDITION,TRUE); + if (invertReadAccess) element.setAttribute(NEGATE_READ_CONDITION,TRUE); + if (invertWriteAccess) element.setAttribute(NEGATE_WRITE_CONDITION,TRUE); + + if (wholeWordsOnly) element.setAttribute(WHOLE_WORDS_ONLY,TRUE); + if (invertContainsConstraint) element.setAttribute(NEGATE_CONTAINS_CONDITION,TRUE); + if (invertWithinConstraint) element.setAttribute(NEGATE_WITHIN_CONDITION,TRUE); + element.setAttribute(WITHIN_CONDITION, withinConstraint); + element.setAttribute(CONTAINS_CONDITION, containsConstraint); + } + + public String getWithinConstraint() { + return withinConstraint; + } + + public void setWithinConstraint(final String withinConstraint) { + this.withinConstraint = withinConstraint; + } + + public String getContainsConstraint() { + return containsConstraint; + } + + public void setContainsConstraint(final String containsConstraint) { + this.containsConstraint = containsConstraint; + } + + public boolean isInvertContainsConstraint() { + return invertContainsConstraint; + } + + public void setInvertContainsConstraint(final boolean invertContainsConstraint) { + this.invertContainsConstraint = invertContainsConstraint; + } + + public boolean isInvertWithinConstraint() { + return invertWithinConstraint; + } + + public void setInvertWithinConstraint(final boolean invertWithinConstraint) { + this.invertWithinConstraint = invertWithinConstraint; + } + + public boolean isArtificial() { + return artificial; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/Matcher.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/Matcher.java new file mode 100644 index 000000000000..594686e14d49 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/Matcher.java @@ -0,0 +1,84 @@ +package com.intellij.structuralsearch; + +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatcherImpl; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * This class makes program structure tree matching: + */ +public class Matcher extends MatcherImpl { + + public Matcher(Project project) { + super(project); + } + + public Matcher(final Project project, final MatchOptions matchOptions) { + super(project, matchOptions); + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + public void findMatches(MatchResultSink sink,MatchOptions options) throws + MalformedPatternException, UnsupportedPatternException + { + super.findMatches(sink,options); + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param source string for search + * @param pattern to be searched + * @return list of matches found + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + public List<MatchResult> testFindMatches(String source, + String pattern, + MatchOptions options, + boolean filePattern, + FileType sourceFileType, + String sourceExtension, + boolean physicalSourceFile) + throws MalformedPatternException, UnsupportedPatternException { + return super.testFindMatches(source, pattern, options, filePattern, sourceFileType, sourceExtension, physicalSourceFile); + } + + public List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) + throws MalformedPatternException, UnsupportedPatternException { + return super.testFindMatches(source, pattern, options, filePattern); + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param sink + * @param options + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + public void testFindMatches(MatchResultSink sink,MatchOptions options) + throws MalformedPatternException, UnsupportedPatternException { + + super.testFindMatches(sink,options); + } + + /** + * Tests if given element is matched by given pattern starting from target variable. If matching succeeds + * then not null match result is returned. + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + @Nullable + public MatchResult isMatchedByDownUp(PsiElement element,MatchOptions options) throws + MalformedPatternException, UnsupportedPatternException + { + return super.isMatchedByDownUp(element, options); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java new file mode 100644 index 000000000000..6ecb42d800c3 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/MatchingProcess.java @@ -0,0 +1,13 @@ +package com.intellij.structuralsearch; + +/** + * Interface of running matching process + */ +public interface MatchingProcess { + void stop(); + void pause(); + void resume(); + + boolean isSuspended(); + boolean isEnded(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java new file mode 100644 index 000000000000..8c954ab5a0ef --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/NamedScriptableDefinition.java @@ -0,0 +1,81 @@ +package com.intellij.structuralsearch; + +import com.intellij.openapi.util.JDOMExternalizable; +import org.jdom.Element; +import org.jdom.Attribute; +import org.jetbrains.annotations.NonNls; + +/** + * @author Maxim.Mossienko + * Date: 11.06.2009 + * Time: 12:55:39 + */ +public class NamedScriptableDefinition implements JDOMExternalizable, Cloneable { + @NonNls private static final String NAME = "name"; + @NonNls private static final String SCRIPT = "script"; + private String name; + private String scriptCodeConstraint = ""; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getScriptCodeConstraint() { + return scriptCodeConstraint; + } + + public void setScriptCodeConstraint(String scriptCodeConstraint) { + this.scriptCodeConstraint = scriptCodeConstraint; + } + + public Object clone() { + try { + return super.clone(); + } catch(CloneNotSupportedException ex) { + return null; + } + } + + public void readExternal(Element element) { + Attribute attribute = element.getAttribute(NAME); + if (attribute != null) { + name = attribute.getValue(); + } + + String s = element.getAttributeValue(SCRIPT); + if (s != null) { + setScriptCodeConstraint(s); + } + } + + public void writeExternal(Element element) { + element.setAttribute(NAME,name); + if (scriptCodeConstraint.length() > 0) element.setAttribute(SCRIPT,scriptCodeConstraint); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NamedScriptableDefinition)) return false; + + NamedScriptableDefinition that = (NamedScriptableDefinition)o; + + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (scriptCodeConstraint != null ? !scriptCodeConstraint.equals(that.scriptCodeConstraint) : that.scriptCodeConstraint != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (scriptCodeConstraint != null ? scriptCodeConstraint.hashCode() : 0); + return result; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java new file mode 100644 index 000000000000..6e14ce437025 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/PredefinedConfigurationUtil.java @@ -0,0 +1,34 @@ +package com.intellij.structuralsearch; + +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.SearchConfiguration; +import org.jetbrains.annotations.NonNls; + +public class PredefinedConfigurationUtil { + + public static Configuration createSearchTemplateInfo(String name, @NonNls String criteria, String category) { + return createSearchTemplateInfo(name, criteria, category, StdFileTypes.JAVA); + } + + public static Configuration createSearchTemplateInfo(String name, @NonNls String criteria, String category, FileType fileType) { + final SearchConfiguration config = new SearchConfiguration(); + config.setPredefined(true); + config.setName(name); + config.setCategory(category); + config.getMatchOptions().setSearchPattern(criteria); + config.getMatchOptions().setFileType(fileType); + MatcherImplUtil.transform( config.getMatchOptions() ); + + return config; + } + + public static Configuration createSearchTemplateInfoSimple(String name, @NonNls String criteria, String category) { + final Configuration info = createSearchTemplateInfo(name,criteria,category); + info.getMatchOptions().setRecursiveSearch(false); + + return info; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java new file mode 100644 index 000000000000..1010db48c1f8 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/ReplacementVariableDefinition.java @@ -0,0 +1,14 @@ +package com.intellij.structuralsearch; + +/** + * @author Maxim.Mossienko + * Date: Mar 19, 2004 + * Time: 5:36:32 PM + */ +public class ReplacementVariableDefinition extends NamedScriptableDefinition { + @Override + public boolean equals(Object o) { + if (!(o instanceof ReplacementVariableDefinition)) return false; + return super.equals(o); + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java new file mode 100644 index 000000000000..8ef278154b24 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/SSRBundle.java @@ -0,0 +1,32 @@ +package com.intellij.structuralsearch; + +import com.intellij.CommonBundle; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.PropertyKey; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.ResourceBundle; + +public class SSRBundle { + + public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) { + return CommonBundle.message(getBundle(), key, params); + } + + private static Reference<ResourceBundle> ourBundle; + @NonNls private static final String BUNDLE = "messages.SSRBundle"; + + private SSRBundle() { + } + + private static ResourceBundle getBundle() { + ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(ourBundle); + if (bundle == null) { + bundle = ResourceBundle.getBundle(BUNDLE); + ourBundle = new SoftReference<ResourceBundle>(bundle); + } + return bundle; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java new file mode 100644 index 000000000000..063f272cf4c4 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralReplaceHandler.java @@ -0,0 +1,15 @@ +package com.intellij.structuralsearch; + +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; + +/** + * @author Eugene.Kudelevsky + */ +public abstract class StructuralReplaceHandler { + public abstract void replace(final ReplacementInfo info, ReplaceOptions options); + + public void prepare + (ReplacementInfo info) { + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java new file mode 100644 index 000000000000..f925285054f0 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchException.java @@ -0,0 +1,13 @@ + +package com.intellij.structuralsearch; + +/** + * @author Bas Leijdekkers + */ +public class StructuralSearchException extends RuntimeException { + public StructuralSearchException() {} + + public StructuralSearchException(String message) { + super(message); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java new file mode 100644 index 000000000000..ff7b379a161f --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfile.java @@ -0,0 +1,281 @@ +package com.intellij.structuralsearch; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.lang.Language; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; +import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.impl.ParameterInfo; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementBuilder; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.SearchContext; +import com.intellij.structuralsearch.plugin.ui.UIUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.LocalTimeCounter; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; + +/** + * @author Eugene.Kudelevsky + */ +public abstract class StructuralSearchProfile { + public static final ExtensionPointName<StructuralSearchProfile> EP_NAME = + ExtensionPointName.create("com.intellij.structuralsearch.profile"); + + public abstract void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor); + + @NotNull + public abstract PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor); + + @NotNull + public abstract PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter); + + @NotNull + public abstract CompiledPattern createCompiledPattern(); + + public static String getTypeName(FileType fileType) { + return fileType.getName().toLowerCase(); + } + + public abstract boolean canProcess(@NotNull FileType fileType); + + public abstract boolean isMyLanguage(@NotNull Language language); + + public boolean isMyFile(PsiFile file, @NotNull Language language, Language... patternLanguages) { + if (isMyLanguage(language) && ArrayUtil.find(patternLanguages, language) >= 0) { + return true; + } + return false; + } + + @NotNull + public PsiElement[] createPatternTree(@NotNull String text, + @NotNull PatternTreeContext context, + @NotNull FileType fileType, + @Nullable Language language, + @Nullable String contextName, + @Nullable String extension, + @NotNull Project project, + boolean physical) { + final String ext = extension != null ? extension : fileType.getDefaultExtension(); + final String name = "__dummy." + ext; + final PsiFileFactory factory = PsiFileFactory.getInstance(project); + + final PsiFile file = language == null + ? factory.createFileFromText(name, fileType, text, LocalTimeCounter.currentTime(), physical, true) + : factory.createFileFromText(name, language, text, physical, true); + + return file != null ? file.getChildren() : PsiElement.EMPTY_ARRAY; + } + + @NotNull + public PsiElement[] createPatternTree(@NotNull String text, + @NotNull PatternTreeContext context, + @NotNull FileType fileType, + @NotNull Project project, + boolean physical) { + return createPatternTree(text, context, fileType, null, null, null, project, physical); + } + + @NotNull + public Editor createEditor(@NotNull SearchContext searchContext, + @NotNull FileType fileType, + Language dialect, + String text, + boolean useLastConfiguration) { + PsiFile codeFragment = createCodeFragment(searchContext.getProject(), text, null); + if (codeFragment == null) { + codeFragment = createFileFragment(searchContext, fileType, dialect, text); + } + + if (codeFragment != null) { + final Document doc = PsiDocumentManager.getInstance(searchContext.getProject()).getDocument(codeFragment); + assert doc != null : "code fragment element should be physical"; + DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(codeFragment, false); + return UIUtil.createEditor(doc, searchContext.getProject(), true, true, getTemplateContextType()); + } + + final EditorFactory factory = EditorFactory.getInstance(); + final Document document = factory.createDocument(text); + final EditorEx editor = (EditorEx)factory.createEditor(document, searchContext.getProject()); + editor.getSettings().setFoldingOutlineShown(false); + return editor; + } + + private static PsiFile createFileFragment(SearchContext searchContext, FileType fileType, Language dialect, String text) { + final String name = "__dummy." + fileType.getDefaultExtension(); + final PsiFileFactory factory = PsiFileFactory.getInstance(searchContext.getProject()); + + return dialect == null ? + factory.createFileFromText(name, fileType, text, LocalTimeCounter.currentTime(), true, true) : + factory.createFileFromText(name, dialect, text, true, true); + } + + @Nullable + public PsiCodeFragment createCodeFragment(Project project, String text, @Nullable PsiElement context) { + return null; + } + + @Nullable + public Class<? extends TemplateContextType> getTemplateContextTypeClass() { + return null; + } + + public final TemplateContextType getTemplateContextType() { + final Class<? extends TemplateContextType> clazz = getTemplateContextTypeClass(); + return clazz != null ? ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), clazz) : null; + } + + @Nullable + public FileType detectFileType(@NotNull PsiElement context) { + return null; + } + + @Nullable + public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) { + return null; + } + + public void checkSearchPattern(Project project, MatchOptions options) { + } + + public void checkReplacementPattern(Project project, ReplaceOptions options) { + String fileType = getTypeName(options.getMatchOptions().getFileType()); + throw new UnsupportedPatternException(SSRBundle.message("replacement.not.supported.for.filetype", fileType)); + } + + @NotNull + public Language getLanguage(PsiElement element) { + return element.getLanguage(); + } + + // only for nodes not filtered by lexical-nodes filter; they can be by default + public boolean canBeVarDelimeter(@NotNull PsiElement element) { + return false; + } + + public String getText(PsiElement match, int start, int end) { + final String matchText = match.getText(); + if (start==0 && end==-1) return matchText; + return matchText.substring(start, end == -1 ? matchText.length() : end); + } + + public Class getElementContextByPsi(PsiElement element) { + return element.getClass(); + } + + public String getTypedVarString(PsiElement element) { + if (element instanceof PsiNamedElement) { + return ((PsiNamedElement)element).getName(); + } + return element.getText(); + } + + public String getMeaningfulText(PsiElement element) { + return getTypedVarString(element); + } + + public PsiElement updateCurrentNode(PsiElement node) { + return node; + } + + public PsiElement extendMatchedByDownUp(PsiElement node) { + return node; + } + + public PsiElement extendMatchOnePsiFile(PsiElement file) { + return file; + } + + public LanguageFileType getDefaultFileType(@Nullable LanguageFileType fileType) { + return fileType; + } + + Configuration[] getPredefinedTemplates() { + return Configuration.EMPTY_ARRAY; + } + + public void provideAdditionalReplaceOptions(@NotNull PsiElement node, ReplaceOptions options, ReplacementBuilder builder) {} + + public int handleSubstitution(final ParameterInfo info, + MatchResult match, + StringBuilder result, + int offset, + HashMap<String, MatchResult> matchMap) { + return defaultHandleSubstitution(info, match, result, offset); + } + + public static int defaultHandleSubstitution(ParameterInfo info, MatchResult match, StringBuilder result, int offset) { + if (info.getName().equals(match.getName())) { + String replacementString = match.getMatchImage(); + boolean forceAddingNewLine = false; + if (match.getAllSons().size() > 0 && !match.isScopeMatch()) { + // compound matches + StringBuilder buf = new StringBuilder(); + + for (final MatchResult matchResult : match.getAllSons()) { + final PsiElement currentElement = matchResult.getMatch(); + + if (buf.length() > 0) { + if (info.isParameterContext()) { + buf.append(','); + } else { + buf.append(' '); + } + } + + buf.append(matchResult.getMatchImage()); + forceAddingNewLine = currentElement instanceof PsiComment; + } + replacementString = buf.toString(); + } else { + if (info.isStatementContext()) { + forceAddingNewLine = match.getMatch() instanceof PsiComment; + } + } + + offset = Replacer.insertSubstitution(result, offset, info, replacementString); + if (forceAddingNewLine && info.isStatementContext()) { + result.insert(info.getStartIndex() + offset + 1, '\n'); + offset ++; + } + } + return offset; + } + + public int processAdditionalOptions(ParameterInfo info, int offset, StringBuilder result, MatchResult r) { + return offset; + } + + public boolean isIdentifier(PsiElement element) { + return false; + } + + public Collection<String> getReservedWords() { + return Collections.emptySet(); + } + + public boolean isDocCommentOwner(PsiElement match) { + return false; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java new file mode 100644 index 000000000000..9e9496115809 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchProfileBase.java @@ -0,0 +1,674 @@ +package com.intellij.structuralsearch; + +import com.intellij.dupLocator.PsiElementRole; +import com.intellij.dupLocator.equivalence.EquivalenceDescriptor; +import com.intellij.dupLocator.equivalence.EquivalenceDescriptorProvider; +import com.intellij.dupLocator.equivalence.MultiChildDescriptor; +import com.intellij.dupLocator.equivalence.SingleChildDescriptor; +import com.intellij.dupLocator.iterators.FilteringNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.DuplocatorUtil; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageParserDefinitions; +import com.intellij.lang.ParserDefinition; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.LeafElement; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.TokenSet; +import com.intellij.structuralsearch.impl.matcher.*; +import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.*; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; +import com.intellij.util.ArrayUtil; +import com.intellij.util.LocalTimeCounter; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * @author Eugene.Kudelevsky + */ +public abstract class StructuralSearchProfileBase extends StructuralSearchProfile { + private static final String DELIMETER_CHARS = ",;.[]{}():"; + protected static final String PATTERN_PLACEHOLDER = "$$PATTERN_PLACEHOLDER$$"; + private PsiElementVisitor myLexicalNodesFilter; + + @Override + public void compile(PsiElement[] elements, @NotNull final GlobalCompilingVisitor globalVisitor) { + final PsiElement topElement = elements[0].getParent(); + final PsiElement element = elements.length > 1 ? topElement : elements[0]; + + element.accept(new MyCompilingVisitor(globalVisitor, topElement)); + + element.accept(new PsiRecursiveElementVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + if (DuplocatorUtil.isIgnoredNode(element)) { + return; + } + CompiledPattern pattern = globalVisitor.getContext().getPattern(); + MatchingHandler handler = pattern.getHandler(element); + + if (!(handler instanceof SubstitutionHandler) && + !(handler instanceof TopLevelMatchingHandler) && + !(handler instanceof LightTopLevelMatchingHandler)) { + pattern.setHandler(element, new SkippingHandler(handler)); + } + + // todo: simplify logic + + /* + place skipping handler under top-level handler, because when we skip top-level node we can get non top-level handler, so + depth matching won't be done!; + */ + if (handler instanceof LightTopLevelMatchingHandler) { + MatchingHandler delegate = ((LightTopLevelMatchingHandler)handler).getDelegate(); + if (!(delegate instanceof SubstitutionHandler)) { + pattern.setHandler(element, new LightTopLevelMatchingHandler(new SkippingHandler(delegate))); + } + } + } + }); + + + final Language baseLanguage = element.getContainingFile().getLanguage(); + + // todo: try to optimize it: too heavy strategy! + globalVisitor.getContext().getPattern().setStrategy(new MatchingStrategy() { + @Override + public boolean continueMatching(PsiElement start) { + Language language = start.getLanguage(); + + PsiFile file = start.getContainingFile(); + if (file != null) { + Language fileLanguage = file.getLanguage(); + if (fileLanguage.isKindOf(language)) { + // dialect + language = fileLanguage; + } + } + + return language == baseLanguage; + } + + @Override + public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) { + return DuplocatorUtil.shouldSkip(element, elementToMatchWith); + } + }); + } + + @NotNull + @Override + public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) { + return new MyMatchingVisitor(globalVisitor); + } + + @NotNull + @Override + public PsiElementVisitor getLexicalNodesFilter(@NotNull final LexicalNodesFilter filter) { + if (myLexicalNodesFilter == null) { + myLexicalNodesFilter = new PsiElementVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + if (DuplocatorUtil.isIgnoredNode(element)) { + filter.setResult(true); + } + } + }; + } + return myLexicalNodesFilter; + } + + public static boolean containsOnlyDelimeters(String s) { + for (int i = 0, n = s.length(); i < n; i++) { + if (DELIMETER_CHARS.indexOf(s.charAt(i)) < 0) { + return false; + } + } + return true; + } + + @NotNull + protected abstract String[] getVarPrefixes(); + + @NotNull + @Override + public CompiledPattern createCompiledPattern() { + return new CompiledPattern() { + + @Override + protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) { + return new MySubstitutionHandler(name, target, minOccurs, maxOccurs, greedy); + } + + @Override + public String[] getTypedVarPrefixes() { + return getVarPrefixes(); + } + + @Override + public boolean isTypedVar(String str) { + for (String prefix : getVarPrefixes()) { + if (str.startsWith(prefix)) { + return true; + } + } + return false; + } + + @Override + public String getTypedVarString(PsiElement element) { + final PsiElement initialElement = element; + PsiElement child = SkippingHandler.getOnlyNonWhitespaceChild(element); + + while (child != element && child != null && !(child instanceof LeafElement)) { + element = child; + child = SkippingHandler.getOnlyNonWhitespaceChild(element); + } + return child instanceof LeafElement ? element.getText() : initialElement.getText(); + } + }; + } + + @Override + public boolean canProcess(@NotNull FileType fileType) { + return fileType instanceof LanguageFileType && + isMyLanguage(((LanguageFileType)fileType).getLanguage()); + } + + @Override + public boolean isMyLanguage(@NotNull Language language) { + return language.isKindOf(getFileType().getLanguage()); + } + + @NotNull + protected abstract LanguageFileType getFileType(); + + @NotNull + @Override + public PsiElement[] createPatternTree(@NotNull String text, + @NotNull PatternTreeContext context, + @NotNull FileType fileType, + @Nullable Language language, + @Nullable String contextName, + @Nullable String extension, + @NotNull Project project, + boolean physical) { + if (context == PatternTreeContext.Block) { + final String strContext = getContext(text, language, contextName); + return strContext != null ? + parsePattern(project, strContext, text, fileType, language, extension, physical) : + PsiElement.EMPTY_ARRAY; + } + return super.createPatternTree(text, context, fileType, language, contextName, extension, project, physical); + } + + @Override + public void checkReplacementPattern(Project project, ReplaceOptions options) { + final CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, options.getMatchOptions()); + if (compiledPattern == null) { + return; + } + + final NodeIterator it = compiledPattern.getNodes(); + if (!it.hasNext()) { + return; + } + + final PsiElement root = it.current().getParent(); + + if (!checkOptionalChildren(root) || + !checkErrorElements(root)) { + throw new UnsupportedPatternException(": Partial and expression patterns are not supported"); + } + } + + private static boolean checkErrorElements(PsiElement element) { + final boolean[] result = {true}; + final int endOffset = element.getTextRange().getEndOffset(); + + element.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (element instanceof PsiErrorElement && element.getTextRange().getEndOffset() == endOffset) { + result[0] = false; + } + } + }); + + return result[0]; + } + + private static boolean checkOptionalChildren(PsiElement root) { + final boolean[] result = {true}; + + root.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (element instanceof LeafElement) { + return; + } + + final EquivalenceDescriptorProvider provider = EquivalenceDescriptorProvider.getInstance(element); + if (provider == null) { + return; + } + + final EquivalenceDescriptor descriptor = provider.buildDescriptor(element); + if (descriptor == null) { + return; + } + + for (SingleChildDescriptor childDescriptor : descriptor.getSingleChildDescriptors()) { + if (childDescriptor.getType() == SingleChildDescriptor.MyType.OPTIONALLY_IN_PATTERN && + childDescriptor.getElement() == null) { + result[0] = false; + } + } + + for (MultiChildDescriptor childDescriptor : descriptor.getMultiChildDescriptors()) { + if (childDescriptor.getType() == MultiChildDescriptor.MyType.OPTIONALLY_IN_PATTERN) { + PsiElement[] elements = childDescriptor.getElements(); + if (elements == null || elements.length == 0) { + result[0] = false; + } + } + } + } + }); + return result[0]; + } + + @Override + public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) { + return new DocumentBasedReplaceHandler(context.getProject()); + } + + @NotNull + public String[] getContextNames() { + return ArrayUtil.EMPTY_STRING_ARRAY; + } + + @Nullable + protected String getContext(@NotNull String pattern, @Nullable Language language, @Nullable String contextName) { + return PATTERN_PLACEHOLDER; + } + + private static boolean canBePatternVariable(PsiElement element) { + // can be leaf element! (ex. var a = 1 <-> var $a$ = 1) + if (element instanceof LeafElement) { + return true; + } + + while (!(element instanceof LeafElement) && element != null) { + element = SkippingHandler.getOnlyNonWhitespaceChild(element); + } + return element != null; + } + + private static boolean isLiteral(PsiElement element) { + if (element == null) return false; + final ASTNode astNode = element.getNode(); + if (astNode == null) { + return false; + } + final IElementType elementType = astNode.getElementType(); + final ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage()); + if (parserDefinition != null) { + final TokenSet literals = parserDefinition.getStringLiteralElements(); + return literals.contains(elementType); + } + return false; + } + + private static boolean canBePatternVariableValue(PsiElement element) { + // can be leaf element! (ex. var a = 1 <-> var $a$ = 1) + return !containsOnlyDelimeters(element.getText()); + } + + @Override + public boolean canBeVarDelimeter(@NotNull PsiElement element) { + final ASTNode node = element.getNode(); + return node != null && getVariableDelimiters().contains(node.getElementType()); + } + + protected TokenSet getVariableDelimiters() { + return TokenSet.EMPTY; + } + + public static PsiElement[] parsePattern(Project project, + String context, + String pattern, + FileType fileType, + Language language, + String extension, + boolean physical) { + int offset = context.indexOf(PATTERN_PLACEHOLDER); + + final int patternLength = pattern.length(); + final String patternInContext = context.replace(PATTERN_PLACEHOLDER, pattern); + + final String ext = extension != null ? extension : fileType.getDefaultExtension(); + final String name = "__dummy." + ext; + final PsiFileFactory factory = PsiFileFactory.getInstance(project); + + final PsiFile file = language == null + ? factory.createFileFromText(name, fileType, patternInContext, LocalTimeCounter.currentTime(), physical, true) + : factory.createFileFromText(name, language, patternInContext, physical, true); + if (file == null) { + return PsiElement.EMPTY_ARRAY; + } + + final List<PsiElement> result = new ArrayList<PsiElement>(); + + PsiElement element = file.findElementAt(offset); + if (element == null) { + return PsiElement.EMPTY_ARRAY; + } + + PsiElement topElement = element; + element = element.getParent(); + + while (element != null) { + if (element.getTextRange().getStartOffset() == offset && element.getTextLength() <= patternLength) { + topElement = element; + } + element = element.getParent(); + } + + if (topElement instanceof PsiFile) { + return topElement.getChildren(); + } + + final int endOffset = offset + patternLength; + result.add(topElement); + topElement = topElement.getNextSibling(); + + while (topElement != null && topElement.getTextRange().getEndOffset() <= endOffset) { + result.add(topElement); + topElement = topElement.getNextSibling(); + } + + return result.toArray(new PsiElement[result.size()]); + } + + // todo: support expression patterns + // todo: support {statement;} = statement; (node has only non-lexical child) + + private static class MyCompilingVisitor extends PsiRecursiveElementVisitor { + private final GlobalCompilingVisitor myGlobalVisitor; + private final PsiElement myTopElement; + + private Pattern[] mySubstitutionPatterns; + + private MyCompilingVisitor(GlobalCompilingVisitor globalVisitor, PsiElement topElement) { + myGlobalVisitor = globalVisitor; + myTopElement = topElement; + } + + @Override + public void visitElement(PsiElement element) { + doVisitElement(element); + + if (isLiteral(element)) { + visitLiteral(element); + } + } + + private void doVisitElement(PsiElement element) { + CompiledPattern pattern = myGlobalVisitor.getContext().getPattern(); + + if (myGlobalVisitor.getCodeBlockLevel() == 0) { + initTopLevelElement(element); + return; + } + + if (canBePatternVariable(element) && pattern.isRealTypedVar(element)) { + myGlobalVisitor.handle(element); + final MatchingHandler handler = pattern.getHandler(element); + handler.setFilter(new NodeFilter() { + public boolean accepts(PsiElement other) { + return canBePatternVariableValue(other); + } + }); + + super.visitElement(element); + + return; + } + + super.visitElement(element); + + if (myGlobalVisitor.getContext().getSearchHelper().doOptimizing() && element instanceof LeafElement) { + ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(element.getLanguage()); + if (parserDefinition != null) { + String text = element.getText(); + + // todo: support variables inside comments + boolean flag = true; + if (StringUtil.isJavaIdentifier(text) && flag) { + myGlobalVisitor.processTokenizedName(text, true, GlobalCompilingVisitor.OccurenceKind.CODE); + } + } + } + } + + private void visitLiteral(PsiElement literal) { + String value = literal.getText(); + + if (value.length() > 2 && + (value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') || + (value.charAt(0) == '\'' && value.charAt(value.length() - 1) == '\'')) { + + if (mySubstitutionPatterns == null) { + final String[] prefixes = myGlobalVisitor.getContext().getPattern().getTypedVarPrefixes(); + mySubstitutionPatterns = createPatterns(prefixes); + } + + for (Pattern substitutionPattern : mySubstitutionPatterns) { + @Nullable MatchingHandler handler = + myGlobalVisitor.processPatternStringWithFragments(value, GlobalCompilingVisitor.OccurenceKind.LITERAL, substitutionPattern); + + if (handler != null) { + literal.putUserData(CompiledPattern.HANDLER_KEY, handler); + break; + } + } + } + } + + private static Pattern[] createPatterns(String[] prefixes) { + final Pattern[] patterns = new Pattern[prefixes.length]; + + for (int i = 0; i < prefixes.length; i++) { + final String s = StructuralSearchUtil.shieldSpecialChars(prefixes[0]); + patterns[i] = Pattern.compile("\\b(" + s + "\\w+)\\b"); + } + return patterns; + } + + private void initTopLevelElement(PsiElement element) { + CompiledPattern pattern = myGlobalVisitor.getContext().getPattern(); + + PsiElement newElement = SkippingHandler.skipNodeIfNeccessary(element); + + if (element != newElement && newElement != null) { + // way to support partial matching (ex. if ($condition$) ) + newElement.accept(this); + pattern.setHandler(element, new LightTopLevelMatchingHandler(pattern.getHandler(element))); + } + else { + myGlobalVisitor.setCodeBlockLevel(myGlobalVisitor.getCodeBlockLevel() + 1); + + for (PsiElement el = element.getFirstChild(); el != null; el = el.getNextSibling()) { + if (GlobalCompilingVisitor.getFilter().accepts(el)) { + if (el instanceof PsiWhiteSpace) { + myGlobalVisitor.addLexicalNode(el); + } + } + else { + el.accept(this); + + MatchingHandler matchingHandler = pattern.getHandler(el); + pattern.setHandler(el, element == myTopElement ? new TopLevelMatchingHandler(matchingHandler) : + new LightTopLevelMatchingHandler(matchingHandler)); + + /* + do not assign light-top-level handlers through skipping, because it is incorrect; + src: if (...) { st1; st2; } + pattern: if (...) {$a$;} + + $a$ will have top-level handler, so matching will be considered as correct, although "st2;" is left! + */ + } + } + + myGlobalVisitor.setCodeBlockLevel(myGlobalVisitor.getCodeBlockLevel() - 1); + pattern.setHandler(element, new TopLevelMatchingHandler(pattern.getHandler(element))); + } + } + } + + private static class MyMatchingVisitor extends PsiElementVisitor { + private final GlobalMatchingVisitor myGlobalVisitor; + + private MyMatchingVisitor(GlobalMatchingVisitor globalVisitor) { + myGlobalVisitor = globalVisitor; + } + + private boolean shouldIgnoreVarNode(PsiElement element) { + MatchingHandler handler = myGlobalVisitor.getMatchContext().getPattern().getHandlerSimple(element); + if (handler instanceof DelegatingHandler) { + handler = ((DelegatingHandler)handler).getDelegate(); + } + return handler instanceof MySubstitutionHandler && ((MySubstitutionHandler)handler).myExceptedNodes.contains(element); + } + + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + final EquivalenceDescriptorProvider descriptorProvider = EquivalenceDescriptorProvider.getInstance(element); + + if (descriptorProvider != null) { + final EquivalenceDescriptor descriptor1 = descriptorProvider.buildDescriptor(element); + final EquivalenceDescriptor descriptor2 = descriptorProvider.buildDescriptor(myGlobalVisitor.getElement()); + + if (descriptor1 != null && descriptor2 != null) { + final boolean result = DuplocatorUtil + .match(descriptor1, descriptor2, myGlobalVisitor, Collections.<PsiElementRole>emptySet(), null); + myGlobalVisitor.setResult(result); + return; + } + } + + if (isLiteral(element)) { + visitLiteral(element); + return; + } + + if (canBePatternVariable(element) && + myGlobalVisitor.getMatchContext().getPattern().isRealTypedVar(element) && + !shouldIgnoreVarNode(element)) { + + PsiElement matchedElement = myGlobalVisitor.getElement(); + PsiElement newElement = SkippingHandler.skipNodeIfNeccessary(matchedElement); + while (newElement != matchedElement) { + matchedElement = newElement; + newElement = SkippingHandler.skipNodeIfNeccessary(matchedElement); + } + + myGlobalVisitor.setResult(myGlobalVisitor.handleTypedElement(element, matchedElement)); + } + else if (element instanceof LeafElement) { + myGlobalVisitor.setResult(element.getText().equals(myGlobalVisitor.getElement().getText())); + } + else if (element.getFirstChild() == null && element.getTextLength() == 0) { + myGlobalVisitor.setResult(true); + } + else { + PsiElement patternChild = element.getFirstChild(); + PsiElement matchedChild = myGlobalVisitor.getElement().getFirstChild(); + + FilteringNodeIterator patternIterator = new SsrFilteringNodeIterator(patternChild); + FilteringNodeIterator matchedIterator = new SsrFilteringNodeIterator(matchedChild); + + boolean matched = myGlobalVisitor.matchSequentially(patternIterator, matchedIterator); + myGlobalVisitor.setResult(matched); + } + } + + public void visitLiteral(PsiElement literal) { + final PsiElement l2 = myGlobalVisitor.getElement(); + + MatchingHandler handler = (MatchingHandler)literal.getUserData(CompiledPattern.HANDLER_KEY); + + if (handler instanceof SubstitutionHandler) { + int offset = 0; + int length = l2.getTextLength(); + final String text = l2.getText(); + + if (length > 2 && + (text.charAt(0) == '"' && text.charAt(length - 1) == '"') || + (text.charAt(0) == '\'' && text.charAt(length - 1) == '\'')) { + length--; + offset++; + } + myGlobalVisitor.setResult(((SubstitutionHandler)handler).handle(l2, offset, length, myGlobalVisitor.getMatchContext())); + } + else if (handler != null) { + myGlobalVisitor.setResult(handler.match(literal, l2, myGlobalVisitor.getMatchContext())); + } + else { + myGlobalVisitor.setResult(literal.textMatches(l2)); + } + } + } + + private static class MySubstitutionHandler extends SubstitutionHandler { + final Set<PsiElement> myExceptedNodes; + + public MySubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) { + super(name, target, minOccurs, maxOccurs, greedy); + myExceptedNodes = new HashSet<PsiElement>(); + } + + @Override + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + if (doMatchSequentially(nodes, nodes2, context)) { + return true; + } + final PsiElement current = nodes.current(); + if (current != null) { + myExceptedNodes.add(current); + } + final boolean result = doMatchSequentiallyBySimpleHandler(nodes, nodes2, context); + myExceptedNodes.remove(current); + return result; + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java new file mode 100644 index 000000000000..b37c10143636 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/StructuralSearchUtil.java @@ -0,0 +1,169 @@ +package com.intellij.structuralsearch; + +import com.intellij.lang.Language; +import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.fileTypes.impl.AbstractFileType; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchUtils; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.tokenindex.LanguageTokenizer; +import com.intellij.tokenindex.Tokenizer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +public class StructuralSearchUtil { + private static LanguageFileType ourDefaultFileType = null; + + public static boolean ourUseUniversalMatchingAlgorithm = false; + private static StructuralSearchProfile[] ourNewStyleProfiles; + private static List<Configuration> ourPredefinedConfigurations = null; + + private StructuralSearchUtil() {} + + @Nullable + public static StructuralSearchProfile getProfileByPsiElement(@NotNull PsiElement element) { + return getProfileByLanguage(element.getLanguage()); + } + + @Contract("null -> false") + public static boolean isIdentifier(PsiElement element) { + final StructuralSearchProfile profile = getProfileByPsiElement(element); + return profile != null && profile.isIdentifier(element); + } + + private static StructuralSearchProfile[] getNewStyleProfiles() { + if (ourNewStyleProfiles == null) { + final List<StructuralSearchProfile> list = new ArrayList<StructuralSearchProfile>(); + + for (StructuralSearchProfile profile : StructuralSearchProfile.EP_NAME.getExtensions()) { + if (profile instanceof StructuralSearchProfileBase) { + list.add(profile); + } + } + list.add(new XmlStructuralSearchProfile()); + ourNewStyleProfiles = list.toArray(new StructuralSearchProfile[list.size()]); + } + return ourNewStyleProfiles; + } + + private static StructuralSearchProfile[] getProfiles() { + return ourUseUniversalMatchingAlgorithm + ? getNewStyleProfiles() + : StructuralSearchProfile.EP_NAME.getExtensions(); + } + + public static FileType getDefaultFileType() { + if (ourDefaultFileType == null) { + for (StructuralSearchProfile profile : getProfiles()) { + ourDefaultFileType = profile.getDefaultFileType(ourDefaultFileType); + } + if (ourDefaultFileType == null) { + ourDefaultFileType = StdFileTypes.XML; + } + } + assert isValidFileType(ourDefaultFileType) : "file type not valid for structural search: " + ourDefaultFileType.getName(); + return ourDefaultFileType; + } + + @Nullable + public static StructuralSearchProfile getProfileByLanguage(@NotNull Language language) { + + for (StructuralSearchProfile profile : getProfiles()) { + if (profile.isMyLanguage(language)) { + return profile; + } + } + return null; + } + + @Nullable + public static Tokenizer getTokenizerForLanguage(@NotNull Language language) { + return LanguageTokenizer.INSTANCE.forLanguage(language); + } + + public static boolean isTypedVariable(@NotNull final String name) { + return name.charAt(0)=='$' && name.charAt(name.length()-1)=='$'; + } + + @Nullable + public static StructuralSearchProfile getProfileByFileType(FileType fileType) { + + for (StructuralSearchProfile profile : getProfiles()) { + if (profile.canProcess(fileType)) { + return profile; + } + } + + return null; + } + + @NotNull + public static FileType[] getSuitableFileTypes() { + Set<FileType> allFileTypes = new HashSet<FileType>(); + Collections.addAll(allFileTypes, FileTypeManager.getInstance().getRegisteredFileTypes()); + for (Language language : Language.getRegisteredLanguages()) { + FileType fileType = language.getAssociatedFileType(); + if (fileType != null) { + allFileTypes.add(fileType); + } + } + + List<FileType> result = new ArrayList<FileType>(); + for (FileType fileType : allFileTypes) { + if (isValidFileType(fileType)) { + result.add(fileType); + } + } + + return result.toArray(new FileType[result.size()]); + } + + private static boolean isValidFileType(FileType fileType) { + return fileType != StdFileTypes.GUI_DESIGNER_FORM && + fileType != StdFileTypes.IDEA_MODULE && + fileType != StdFileTypes.IDEA_PROJECT && + fileType != StdFileTypes.IDEA_WORKSPACE && + fileType != FileTypes.ARCHIVE && + fileType != FileTypes.UNKNOWN && + fileType != FileTypes.PLAIN_TEXT && + !(fileType instanceof AbstractFileType) && + !fileType.isBinary() && + !fileType.isReadOnly(); + } + + public static String shieldSpecialChars(String word) { + final StringBuilder buf = new StringBuilder(word.length()); + + for (int i = 0; i < word.length(); ++i) { + if (MatchUtils.SPECIAL_CHARS.indexOf(word.charAt(i)) != -1) { + buf.append("\\"); + } + buf.append(word.charAt(i)); + } + + return buf.toString(); + } + + public static List<Configuration> getPredefinedTemplates() { + if (ourPredefinedConfigurations == null) { + final List<Configuration> result = new ArrayList<Configuration>(); + for (StructuralSearchProfile profile : getProfiles()) { + Collections.addAll(result, profile.getPredefinedTemplates()); + } + Collections.sort(result); + ourPredefinedConfigurations = Collections.unmodifiableList(result); + } + return ourPredefinedConfigurations; + } + + public static boolean isDocCommentOwner(PsiElement match) { + final StructuralSearchProfile profile = getProfileByPsiElement(match); + return profile != null && profile.isDocCommentOwner(match); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java new file mode 100644 index 000000000000..1236264118c2 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/UnsupportedPatternException.java @@ -0,0 +1,11 @@ +package com.intellij.structuralsearch; + +/** + * Exception about encountering yet unsupported pattern event. + */ +public class UnsupportedPatternException extends RuntimeException { + + public UnsupportedPatternException(String _pattern) { + super(_pattern); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java new file mode 100644 index 000000000000..5e0ca8bde957 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/XmlStructuralSearchProfile.java @@ -0,0 +1,229 @@ +package com.intellij.structuralsearch; + +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.codeInsight.template.XmlContextType; +import com.intellij.lang.Language; +import com.intellij.lang.StdLanguages; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; +import com.intellij.structuralsearch.impl.matcher.*; +import com.intellij.structuralsearch.impl.matcher.compiler.GlobalCompilingVisitor; +import com.intellij.structuralsearch.impl.matcher.compiler.XmlCompilingVisitor; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.filters.XmlLexicalNodesFilter; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacementContext; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.replace.impl.ReplacerUtil; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.LocalTimeCounter; +import com.intellij.xml.util.HtmlUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.structuralsearch.PredefinedConfigurationUtil.createSearchTemplateInfo; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlStructuralSearchProfile extends StructuralSearchProfile { + + private XmlLexicalNodesFilter myLexicalNodesFilter; + + public void compile(PsiElement[] elements, @NotNull GlobalCompilingVisitor globalVisitor) { + elements[0].getParent().accept(new XmlCompilingVisitor(globalVisitor)); + } + + @NotNull + public PsiElementVisitor createMatchingVisitor(@NotNull GlobalMatchingVisitor globalVisitor) { + return new XmlMatchingVisitor(globalVisitor); + } + + @NotNull + @Override + public PsiElementVisitor getLexicalNodesFilter(@NotNull LexicalNodesFilter filter) { + if (myLexicalNodesFilter == null) { + myLexicalNodesFilter = new XmlLexicalNodesFilter(filter); + } + return myLexicalNodesFilter; + } + + @NotNull + public CompiledPattern createCompiledPattern() { + return new XmlCompiledPattern(); + } + + @Override + public boolean canProcess(@NotNull FileType fileType) { + return fileType == StdFileTypes.XML || fileType == StdFileTypes.HTML || fileType == StdFileTypes.JSP || + fileType == StdFileTypes.JSPX || fileType == StdFileTypes.XHTML; + } + + public boolean isMyLanguage(@NotNull Language language) { + return language instanceof XMLLanguage; + } + + @NotNull + @Override + public PsiElement[] createPatternTree(@NotNull String text, + @NotNull PatternTreeContext context, + @NotNull FileType fileType, + @Nullable Language language, + String contextName, @Nullable String extension, + @NotNull Project project, + boolean physical) { + final String ext = extension != null ? extension : fileType.getDefaultExtension(); + String text1 = context == PatternTreeContext.File ? text : "<QQQ>" + text + "</QQQ>"; + final PsiFile fileFromText = PsiFileFactory.getInstance(project) + .createFileFromText("dummy." + ext, fileType, text1, LocalTimeCounter.currentTime(), physical, true); + + final XmlDocument document = HtmlUtil.getRealXmlDocument(((XmlFile)fileFromText).getDocument()); + if (context == PatternTreeContext.File) { + return new PsiElement[]{document}; + } + + return document.getRootTag().getValue().getChildren(); + } + + @Override + public Class<? extends TemplateContextType> getTemplateContextTypeClass() { + return XmlContextType.class; + } + + @NotNull + @Override + public FileType detectFileType(@NotNull PsiElement context) { + PsiFile file = context instanceof PsiFile ? (PsiFile)context : context.getContainingFile(); + Language contextLanguage = context instanceof PsiFile ? null : context.getLanguage(); + if (file.getLanguage() == StdLanguages.HTML || (file.getFileType() == StdFileTypes.JSP && contextLanguage == StdLanguages.HTML)) { + return StdFileTypes.HTML; + } + return StdFileTypes.XML; + } + + @Override + public void checkReplacementPattern(Project project, ReplaceOptions options) { + } + + @Override + public StructuralReplaceHandler getReplaceHandler(@NotNull ReplacementContext context) { + return new MyReplaceHandler(context); + } + + private static class MyReplaceHandler extends StructuralReplaceHandler { + private final ReplacementContext myContext; + + private MyReplaceHandler(ReplacementContext context) { + myContext = context; + } + + public void replace(ReplacementInfo info, ReplaceOptions options) { + PsiElement elementToReplace = info.getMatch(0); + assert elementToReplace != null; + PsiElement elementParent = elementToReplace.getParent(); + String replacementToMake = info.getReplacement(); + boolean listContext = elementToReplace.getParent() instanceof XmlTag; + + if (listContext) { + doReplaceInContext(info, elementToReplace, replacementToMake, elementParent, myContext); + } + + PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, myContext); + + if (statements.length > 0) { + PsiElement replacement = ReplacerUtil.copySpacesAndCommentsBefore(elementToReplace, statements, replacementToMake, elementParent); + + // preserve comments + Replacer.handleComments(elementToReplace, replacement, myContext); + elementToReplace.replace(replacement); + } + else { + final PsiElement nextSibling = elementToReplace.getNextSibling(); + elementToReplace.delete(); + assert nextSibling != null; + if (nextSibling.isValid()) { + if (nextSibling instanceof PsiWhiteSpace) { + nextSibling.delete(); + } + } + } + } + } + + private static void doReplaceInContext(ReplacementInfo info, + PsiElement elementToReplace, + String replacementToMake, + PsiElement elementParent, + ReplacementContext context) { + PsiElement[] statements = ReplacerUtil.createTreeForReplacement(replacementToMake, PatternTreeContext.Block, context); + + if (statements.length > 1) { + elementParent.addRangeBefore(statements[0], statements[statements.length - 1], elementToReplace); + } + else if (statements.length == 1) { + PsiElement replacement = statements[0]; + + Replacer.handleComments(elementToReplace, replacement, context); + + try { + elementParent.addBefore(replacement, elementToReplace); + } + catch (IncorrectOperationException e) { + elementToReplace.replace(replacement); + } + } + + final int matchSize = info.getMatchesCount(); + + for (int i = 0; i < matchSize; ++i) { + PsiElement element = info.getMatch(i); + + if (element == null) continue; + PsiElement firstToDelete = element; + PsiElement lastToDelete = element; + PsiElement prevSibling = element.getPrevSibling(); + PsiElement nextSibling = element.getNextSibling(); + + if (prevSibling instanceof PsiWhiteSpace) { + firstToDelete = prevSibling; + } + else if (prevSibling == null && nextSibling instanceof PsiWhiteSpace) { + lastToDelete = nextSibling; + } + if (nextSibling instanceof XmlText && i + 1 < matchSize) { + final PsiElement next = info.getMatch(i + 1); + if (next != null && next == nextSibling.getNextSibling()) { + lastToDelete = nextSibling; + } + } + element.getParent().deleteChildRange(firstToDelete, lastToDelete); + } + } + + @Override + Configuration[] getPredefinedTemplates() { + return XmlPredefinedConfigurations.createPredefinedTemplates(); + } + + private static class XmlPredefinedConfigurations { + private static final String HTML_XML = SSRBundle.message("xml_html.category"); + + private static Configuration[] createPredefinedTemplates() { + return new Configuration[]{ + createSearchTemplateInfo("xml tag", "<'a/>", HTML_XML, StdFileTypes.XML), + createSearchTemplateInfo("xml attribute", "<'_tag 'attribute=\"'_value\"/>", HTML_XML, StdFileTypes.XML), + createSearchTemplateInfo("xml attribute value", "<'_tag '_attribute=\"'value\"/>", HTML_XML, StdFileTypes.XML), + createSearchTemplateInfo("xml/html tag value", "<table>'_content*</table>", HTML_XML, StdFileTypes.HTML), + }; + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java new file mode 100644 index 000000000000..565f80ac01ce --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/CompiledPattern.java @@ -0,0 +1,182 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SimpleHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; + +/** + * Class to hold compiled pattern information + */ +public abstract class CompiledPattern { + public static final String ALL_CLASS_UNMATCHED_CONTENT_VAR_ARTIFICIAL_NAME = "__class_unmatched__"; + private SearchScope scope; + private NodeIterator nodes; + private MatchingStrategy strategy; + private PsiElement targetNode; + private int optionsHashStamp; + private int nodeCount; + + // @todo support this property during matching (how many nodes should be advanced) + // when match is not successful (or successful partially) + //private int advancement = 1; + + public abstract String[] getTypedVarPrefixes(); + public abstract boolean isTypedVar(String str); + + public void setTargetNode(final PsiElement element) { + targetNode = element; + } + + public PsiElement getTargetNode() { + return targetNode; + } + + public int getOptionsHashStamp() { + return optionsHashStamp; + } + + public void setOptionsHashStamp(final int optionsHashStamp) { + this.optionsHashStamp = optionsHashStamp; + } + + public static final Key<Object> HANDLER_KEY = Key.create("ss.handler"); + + public MatchingStrategy getStrategy() { + return strategy; + } + + public void setStrategy(MatchingStrategy strategy) { + this.strategy = strategy; + } + + public int getNodeCount() { + return nodeCount; + } + + public NodeIterator getNodes() { + return nodes; + } + + public void setNodes(List<PsiElement> elements) { + this.nodes = new ArrayBackedNodeIterator(PsiUtilCore.toPsiElementArray(elements)); + this.nodeCount = elements.size(); + } + + public boolean isTypedVar(final PsiElement element) { + return element!=null && isTypedVar( element.getText() ); + } + + public boolean isRealTypedVar(PsiElement element) { + if (element!=null && element.getTextLength()>0) { + String str = getTypedVarString(element); + if (str.length()==0) { + return false; + } + return isTypedVar( str ); + } else { + return false; + } + } + + public String getTypedVarString(PsiElement element) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile == null) { + return element.getText(); + } + return profile.getTypedVarString(element); + } + + private final HashMap<Object,MatchingHandler> handlers = new HashMap<Object,MatchingHandler>(); + + public MatchingHandler getHandlerSimple(PsiElement node) { + return handlers.get(node); + } + + private PsiElement last; + private MatchingHandler lastHandler; + + public MatchingHandler getHandler(PsiElement node) { + if (node==last) { + return lastHandler; + } + MatchingHandler handler = handlers.get(node); + + if (handler==null) { + handler = new SimpleHandler(); + setHandler(node,handler); + } + + last = node; + lastHandler = handler; + + return handler; + } + + public MatchingHandler getHandler(String name) { + return handlers.get(name); + } + + public void setHandler(PsiElement node, MatchingHandler handler) { + last = null; + handlers.put(node,handler); + } + + public SubstitutionHandler createSubstitutionHandler( + String name, String compiledName, boolean target,int minOccurs, int maxOccurs, boolean greedy) { + + SubstitutionHandler handler = (SubstitutionHandler) handlers.get(compiledName); + if (handler != null) return handler; + + handler = doCreateSubstitutionHandler(name, target, minOccurs, maxOccurs, greedy); + + handlers.put(compiledName,handler); + + return handler; + } + + protected SubstitutionHandler doCreateSubstitutionHandler(String name, boolean target, int minOccurs, int maxOccurs, boolean greedy) { + return new SubstitutionHandler(name, target, minOccurs, maxOccurs, greedy); + } + + public SearchScope getScope() { + return scope; + } + + public void setScope(SearchScope scope) { + this.scope = scope; + } + + public void clearHandlers() { + handlers.clear(); + last = null; + lastHandler = null; + } + + void clearHandlersState() { + for (final MatchingHandler h : handlers.values()) { + if (h != null) h.reset(); + } + } + + public boolean isToResetHandler(PsiElement element) { + return true; + } + + @Nullable + public String getAlternativeTextToMatch(PsiElement node, String previousText) { + return null; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java new file mode 100644 index 000000000000..e7327fad7e5e --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/DataProvider.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 07.01.2004 + * Time: 1:06:51 + * To change this template use Options | File Templates. + */ +public interface DataProvider { + Editor getEditor(); + Project getProject(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java new file mode 100644 index 000000000000..bfbe065e461c --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/GlobalMatchingVisitor.java @@ -0,0 +1,319 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.dupLocator.AbstractMatchingVisitor; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.lang.Language; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.DelegatingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import com.intellij.util.containers.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Visitor class to manage pattern matching + */ +@SuppressWarnings({"RefusedBequest"}) +public class GlobalMatchingVisitor extends AbstractMatchingVisitor { + private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor"); + + // the pattern element for visitor check + private PsiElement myElement; + + // the result of matching in visitor + private boolean myResult; + + // context of matching + private MatchContext matchContext; + + private MatchingHandler myLastHandler; + + private Map<Language, PsiElementVisitor> myLanguage2MatchingVisitor = new HashMap<Language, PsiElementVisitor>(1); + + public PsiElement getElement() { + return myElement; + } + + public boolean getResult() { + return myResult; + } + + public void setElement(PsiElement element) { + this.myElement = element; + } + + public void setResult(boolean result) { + this.myResult = result; + } + + public MatchContext getMatchContext() { + return matchContext; + } + + @Override + protected boolean doMatchInAnyOrder(NodeIterator elements, NodeIterator elements2) { + return matchContext.getPattern().getHandler(elements.current()).matchInAnyOrder( + elements, + elements2, + matchContext + ); + } + + @NotNull + @Override + protected NodeFilter getNodeFilter() { + return LexicalNodesFilter.getInstance(); + } + + public final boolean handleTypedElement(final PsiElement typedElement, final PsiElement match) { + MatchingHandler handler = matchContext.getPattern().getHandler(typedElement); + final MatchingHandler initialHandler = handler; + if (handler instanceof DelegatingHandler) { + handler = ((DelegatingHandler)handler).getDelegate(); + } + assert handler instanceof SubstitutionHandler : + handler != null ? handler.getClass() : "null" + ' ' + (initialHandler != null ? initialHandler.getClass() : "null"); + + return ((SubstitutionHandler)handler).handle(match, matchContext); + } + + /** + * Identifies the match between given element of program tree and pattern element + * + * @param el1 the pattern for matching + * @param el2 the tree element for matching + * @return true if equal and false otherwise + */ + public boolean match(final PsiElement el1, final PsiElement el2) { + // null + if (el1 == el2) return true; + if (el2 == null || el1 == null) { + // this a bug! + return false; + } + + // copy changed data to local stack + PsiElement prevElement = myElement; + myElement = el2; + + try { + /*if (el1 instanceof XmlElement) { + el1.accept(myXmlVisitor); + } + else { + el1.accept(myJavaVisitor); + }*/ + PsiElementVisitor visitor = getVisitorForElement(el1); + if (visitor != null) { + el1.accept(visitor); + } + } + catch (ClassCastException ex) { + myResult = false; + } + finally { + myElement = prevElement; + } + + return myResult; + } + + @Nullable + private PsiElementVisitor getVisitorForElement(PsiElement element) { + Language language = element.getLanguage(); + PsiElementVisitor visitor = myLanguage2MatchingVisitor.get(language); + if (visitor == null) { + visitor = createMatchingVisitor(language); + myLanguage2MatchingVisitor.put(language, visitor); + } + return visitor; + } + + @Nullable + private PsiElementVisitor createMatchingVisitor(Language language) { + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + if (profile == null) { + LOG.warn("there is no StructuralSearchProfile for language " + language.getID()); + return null; + } + else { + return profile.createMatchingVisitor(this); + } + } + + /** + * Matches tree segments starting with given elements to find equality + * + * @param nodes the pattern element for matching + * @param nodes2 the tree element for matching + * @return if they are equal and false otherwise + */ + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2) { + if (!nodes.hasNext()) { + return nodes.hasNext() == nodes2.hasNext(); + } + + myLastHandler = matchContext.getPattern().getHandler(nodes.current()); + return myLastHandler.matchSequentially( + nodes, + nodes2, + matchContext + ); + } + + public static boolean continueMatchingSequentially(final NodeIterator nodes, final NodeIterator nodes2, MatchContext matchContext) { + if (!nodes.hasNext()) { + return nodes.hasNext() == nodes2.hasNext(); + } + + return matchContext.getPattern().getHandler(nodes.current()).matchSequentially( + nodes, + nodes2, + matchContext + ); + } + + /** + * Descents the tree in depth finding matches + * + * @param elements the element for which the sons are looked for match + */ + public void matchContext(final NodeIterator elements) { + final CompiledPattern pattern = matchContext.getPattern(); + final NodeIterator patternNodes = pattern.getNodes().clone(); + final MatchResultImpl saveResult = matchContext.hasResult() ? matchContext.getResult() : null; + final List<PsiElement> saveMatchedNodes = matchContext.getMatchedNodes(); + + try { + matchContext.setResult(null); + matchContext.setMatchedNodes(null); + + if (!patternNodes.hasNext()) return; + final MatchingHandler firstMatchingHandler = pattern.getHandler(patternNodes.current()); + + for (; elements.hasNext(); elements.advance()) { + final PsiElement elementNode = elements.current(); + + boolean matched = firstMatchingHandler.matchSequentially(patternNodes, elements, matchContext); + + if (matched) { + MatchingHandler matchingHandler = matchContext.getPattern().getHandler(Configuration.CONTEXT_VAR_NAME); + if (matchingHandler != null) { + matched = ((SubstitutionHandler)matchingHandler).handle(elementNode, matchContext); + } + } + + final List<PsiElement> matchedNodes = matchContext.getMatchedNodes(); + + if (matched) { + dispatchMatched(matchedNodes, matchContext.getResult()); + } + + matchContext.setMatchedNodes(null); + matchContext.setResult(null); + + patternNodes.reset(); + if (matchedNodes != null && matchedNodes.size() > 0 && matched) { + elements.rewind(); + } + } + } + finally { + matchContext.setResult(saveResult); + matchContext.setMatchedNodes(saveMatchedNodes); + } + } + + private void dispatchMatched(final List<PsiElement> matchedNodes, MatchResultImpl result) { + if (!matchContext.getOptions().isResultIsContextMatch() && doDispatch(result, result)) return; + + // There is no substitutions so show the context + + processNoSubstitutionMatch(matchedNodes, result); + matchContext.getSink().newMatch(result); + } + + private boolean doDispatch(final MatchResultImpl result, MatchResultImpl context) { + boolean ret = false; + + for (MatchResult _r : result.getAllSons()) { + final MatchResultImpl r = (MatchResultImpl)_r; + + if ((r.isScopeMatch() && !r.isTarget()) || r.isMultipleMatch()) { + ret |= doDispatch(r, context); + } + else if (r.isTarget()) { + r.setContext(context); + matchContext.getSink().newMatch(r); + ret = true; + } + } + return ret; + } + + private static void processNoSubstitutionMatch(List<PsiElement> matchedNodes, MatchResultImpl result) { + boolean complexMatch = matchedNodes.size() > 1; + final PsiElement match = matchedNodes.get(0); + + if (!complexMatch) { + result.setMatchRef(new SmartPsiPointer(match)); + result.setMatchImage(match.getText()); + } + else { + MatchResultImpl sonresult; + + for (final PsiElement matchStatement : matchedNodes) { + result.getMatches().add( + sonresult = new MatchResultImpl( + MatchResult.LINE_MATCH, + matchStatement.getText(), + new SmartPsiPointer(matchStatement), + true + ) + ); + + sonresult.setParent(result); + } + + result.setMatchRef( + new SmartPsiPointer(match) + ); + result.setMatchImage( + match.getText() + ); + result.setName(MatchResult.MULTI_LINE_MATCH); + } + } + + public void setMatchContext(MatchContext matchContext) { + this.matchContext = matchContext; + } + + // Matches the sons of given elements to find equality + // @param el1 the pattern element for matching + // @param el2 the tree element for matching + // @return if they are equal and false otherwise + + @Override + protected boolean isLeftLooseMatching() { + return matchContext.getOptions().isLooseMatching(); + } + + @Override + protected boolean isRightLooseMatching() { + return false; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java new file mode 100644 index 000000000000..2036c83d56f4 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchConstraintsSink.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResultSink; +import com.intellij.structuralsearch.MatchingProcess; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.psi.PsiFile; +import com.intellij.openapi.progress.ProgressIndicator; + +import javax.swing.*; +import java.util.HashMap; + +/** + * Sink to detect + */ +class MatchConstraintsSink implements MatchResultSink { + private final MatchResultSink delegate; + private MatchingProcess process; + private final boolean distinct; + private final boolean caseSensitive; + private int matchCount; + private final int maxMatches; + private final HashMap<Object, MatchResult> matches = new HashMap<Object, MatchResult>(); + + MatchConstraintsSink(MatchResultSink _delegate, int _maxMatches,boolean distinct, boolean _caseSensitive) { + delegate = _delegate; + maxMatches = _maxMatches; + this.distinct = distinct; + caseSensitive = _caseSensitive; + } + + public void newMatch(MatchResult result) { + if (distinct) { + String matchImage = result.getMatchImage(); + + if (!caseSensitive) matchImage = matchImage.toLowerCase(); + + if (matches.get(matchImage)!=null) { + return; + } + + matches.put(matchImage,result); + } + else { + final PsiElement match = result.getMatch(); + if (matches.containsKey(match)) { + return; + } + matches.put(match, result); + } + + delegate.newMatch(result); + ++matchCount; + + if (matchCount==maxMatches) { + JOptionPane.showMessageDialog(null, SSRBundle.message("search.produced.too.many.results.message")); + process.stop(); + } + } + + /* Notifies sink about starting the matching for given element + * @param element the current file + * @param task process continuation reference + */ + public void processFile(PsiFile element) { + delegate.processFile(element); + } + + public void setMatchingProcess(MatchingProcess matchingProcess) { + process = matchingProcess; + delegate.setMatchingProcess(process); + } + + public void matchingFinished() { + matchCount = 0; + matches.clear(); + delegate.matchingFinished(); + } + + public ProgressIndicator getProgressIndicator() { + return delegate.getProgressIndicator(); + } + +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java new file mode 100644 index 000000000000..c4c8d21e3ab3 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchContext.java @@ -0,0 +1,130 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchResultSink; +import com.intellij.util.containers.Stack; + +import java.util.Collection; +import java.util.List; + +/** + * Global context of matching process + */ +public class MatchContext { + private MatchResultSink sink; + private final Stack<MatchResultImpl> previousResults = new Stack<MatchResultImpl>(); + private MatchResultImpl result; + private CompiledPattern pattern; + private MatchOptions options; + private GlobalMatchingVisitor matcher; + private boolean shouldRecursivelyMatch = true; + private boolean myWithAlternativePatternRoots = true; + + private List<PsiElement> myMatchedNodes; + + public List<PsiElement> getMatchedNodes() { + return myMatchedNodes; + } + + public void setMatchedNodes(final List<PsiElement> matchedNodes) { + myMatchedNodes = matchedNodes; + } + + public boolean isWithAlternativePatternRoots() { + return myWithAlternativePatternRoots; + } + + public void setWithAlternativePatternRoots(boolean withAlternativePatternRoots) { + myWithAlternativePatternRoots = withAlternativePatternRoots; + } + + public interface MatchedElementsListener { + void matchedElements(Collection<PsiElement> matchedElements); + void commitUnmatched(); + } + + private MatchedElementsListener myMatchedElementsListener; + + public void setMatcher(GlobalMatchingVisitor matcher) { + this.matcher = matcher; + } + + public GlobalMatchingVisitor getMatcher() { + return matcher; + } + + public MatchOptions getOptions() { + return options; + } + + public void setOptions(MatchOptions options) { + this.options = options; + } + + public MatchResultImpl getPreviousResult() { + return previousResults.isEmpty() ? null : previousResults.peek(); + } + + public MatchResultImpl getResult() { + if (result==null) result = new MatchResultImpl(); + return result; + } + + public void pushResult() { + previousResults.push(result); + result = null; + } + + public void popResult() { + result = previousResults.pop(); + } + + public void setResult(MatchResultImpl result) { + this.result = result; + if (result == null) { + pattern.clearHandlersState(); + } + } + + public boolean hasResult() { + return result!=null; + } + + public CompiledPattern getPattern() { + return pattern; + } + + public void setPattern(CompiledPattern pattern) { + this.pattern = pattern; + } + + public MatchResultSink getSink() { + return sink; + } + + public void setSink(MatchResultSink sink) { + this.sink = sink; + } + + void clear() { + result = null; + pattern = null; + } + + public boolean shouldRecursivelyMatch() { + return shouldRecursivelyMatch; + } + + public void setShouldRecursivelyMatch(boolean shouldRecursivelyMatch) { + this.shouldRecursivelyMatch = shouldRecursivelyMatch; + } + + public void setMatchedElementsListener(MatchedElementsListener _matchedElementsListener) { + myMatchedElementsListener = _matchedElementsListener; + } + + public MatchedElementsListener getMatchedElementsListener() { + return myMatchedElementsListener; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java new file mode 100644 index 000000000000..b75ffb5d0f5d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchPredicateProvider.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchVariableConstraint; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +import java.util.Set; + +public abstract class MatchPredicateProvider { + public static final ExtensionPointName<MatchPredicateProvider> EP_NAME = ExtensionPointName.create("com.intellij.structuralsearch.matchPredicateProvider"); + public abstract void collectPredicates(MatchVariableConstraint constraint, + String name, + MatchOptions options, + Set<MatchPredicate> predicates); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java new file mode 100644 index 000000000000..8761472d813a --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchResultImpl.java @@ -0,0 +1,207 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Class describing the match result + */ +public final class MatchResultImpl extends MatchResult { + private String name; + private SmartPsiPointer matchRef; + private int start; + private int end = -1; + private String matchImage; + private List<MatchResult> matches; + private MatchResult parent; + private boolean target; + + @NonNls public static final String DEFAULT_NAME2 = "end of context match"; + @NonNls public static final String DEFAULT_NAME = "start of context match"; + private boolean myScopeMatch; + private boolean myMultipleMatch; + @NonNls private static final String NULL = "null"; + private MatchResultImpl myContext; + + MatchResultImpl() { + } + + public MatchResultImpl(String name, String image, SmartPsiPointer ref, boolean target) { + this(name,image,ref,0,-1,target); + } + + public MatchResultImpl(String name, String image, SmartPsiPointer ref, int start, int end,boolean target) { + matchRef = ref; + this.name = name; + matchImage = image; + this.target = target; + this.start = start; + this.end = end; + } + + public String getMatchImage() { + if (matchImage==null) { + matchImage = NULL; + } + return matchImage; + } + + public void setParent(MatchResult parent) { + this.parent = parent; + } + + public SmartPsiPointer getMatchRef() { + return matchRef; + } + + public PsiElement getMatch() { + return matchRef.getElement(); + } + + public void setMatchRef(SmartPsiPointer matchStart) { + matchRef = matchStart; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List<MatchResult> getMatches() { + if (matches==null) matches = new ArrayList<MatchResult>(); + return matches; + } + + public List<MatchResult> getAllSons() { + return getMatches(); + } + + public boolean hasSons() { + return matches!=null; + } + + public boolean isScopeMatch() { + return myScopeMatch; + } + + public boolean isMultipleMatch() { + return myMultipleMatch; + } + + public void clear() { + if (matchRef != null) { + matchRef.clear(); + matchRef = null; + } + + if (matches != null) { + for (final MatchResult match : matches) { + ((MatchResultImpl)match).clear(); + } + matches = null; + } + + name = null; + matchImage = null; + } + + public void clearMatches() { + matches = null; + } + + public void setScopeMatch(final boolean scopeMatch) { + myScopeMatch = scopeMatch; + } + + public void setMultipleMatch(final boolean multipleMatch) { + myMultipleMatch = multipleMatch; + } + + public MatchResultImpl findSon(String name) { + if (matches!=null) { + // @todo this could be performance bottleneck, replace with hash lookup! + for (final MatchResult match : matches) { + final MatchResultImpl res = (MatchResultImpl)match; + + if (name.equals(res.getName())) { + return res; + } + } + } + return null; + } + + public MatchResultImpl removeSon(String typedVar) { + if (matches == null) return null; + + // @todo this could be performance bottleneck, replace with hash lookup! + for(Iterator<MatchResult> i=matches.iterator();i.hasNext();) { + final MatchResultImpl res = (MatchResultImpl)i.next(); + if (typedVar.equals(res.getName())) { + i.remove(); + return res; + } + } + + return null; + } + + public void addSon(MatchResultImpl result) { + getMatches().add(result); + } + + public void setMatchImage(String matchImage) { + this.matchImage = matchImage; + } + + public boolean isTarget() { + return target; + } + + public void setTarget(boolean target) { + this.target = target; + } + + public boolean isMatchImageNull() { + return matchImage==null; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getEnd() { + return end; + } + + public void setEnd(int end) { + this.end = end; + } + + public void setContext(final MatchResultImpl context) { + myContext = context; + } + + public MatchResultImpl getContext() { + return myContext; + } + + @Override + public String toString() { + return "MatchResultImpl{name='" + name + '\'' + ", matchImage='" + matchImage + '\'' + "}"; + } +} + diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java new file mode 100644 index 000000000000..38c31d16524f --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatchUtils.java @@ -0,0 +1,42 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.*; +import org.jetbrains.annotations.NonNls; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 24.12.2003 + * Time: 22:10:20 + * To change this template use Options | File Templates. + */ +public class MatchUtils { + public static final String SPECIAL_CHARS = "*(){}[]^$\\.-|"; + + public static final boolean compareWithNoDifferenceToPackage(final String typeImage,@NonNls final String typeImage2) { + if (typeImage == null || typeImage2 == null) return typeImage == typeImage2; + return typeImage2.endsWith(typeImage) && ( + typeImage.length() == typeImage2.length() || + typeImage2.charAt(typeImage2.length()-typeImage.length()-1)=='.' // package separator + ); + } + + public static PsiElement getReferencedElement(final PsiElement element) { + if (element instanceof PsiReference) { + return ((PsiReference)element).resolve(); + } + + /*if (element instanceof PsiTypeElement) { + PsiType type = ((PsiTypeElement)element).getType(); + + if (type instanceof PsiArrayType) { + type = ((PsiArrayType)type).getComponentType(); + } + if (type instanceof PsiClassType) { + return ((PsiClassType)type).resolve(); + } + return null; + }*/ + return element; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java new file mode 100644 index 000000000000..d667b068ee48 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImpl.java @@ -0,0 +1,737 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.lang.Language; +import com.intellij.lang.StdLanguages; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ContentIterator; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; +import com.intellij.psi.search.DelegatingGlobalSearchScope; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PairProcessor; +import com.intellij.util.SmartList; +import com.intellij.util.indexing.FileBasedIndex; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * This class makes program structure tree matching: + */ +public class MatcherImpl { + private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.impl.matcher.MatcherImpl"); + // project being worked on + private final Project project; + + // context of matching + private final MatchContext matchContext; + private boolean isTesting; + + // visitor to delegate the real work + private final GlobalMatchingVisitor visitor = new GlobalMatchingVisitor(); + private ProgressIndicator progress; + private final TaskScheduler scheduler = new TaskScheduler(); + + private int totalFilesToScan; + private int scannedFilesCount; + + public MatcherImpl(final Project project, final MatchOptions matchOptions) { + this.project = project; + matchContext = new MatchContext(); + matchContext.setMatcher(visitor); + + if (matchOptions != null) { + matchContext.setOptions(matchOptions); + cacheCompiledPattern(matchOptions, PatternCompiler.compilePattern(project,matchOptions)); + } + } + + static class LastMatchData { + CompiledPattern lastPattern; + MatchOptions lastOptions; + } + + private static SoftReference<LastMatchData> lastMatchData; + + protected MatcherImpl(Project project) { + this(project, null); + } + + public static void validate(Project project, MatchOptions options) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + synchronized(MatcherImpl.class) { + final LastMatchData data = new LastMatchData(); + data.lastPattern = PatternCompiler.compilePattern(project, options); + data.lastOptions = options; + lastMatchData = new SoftReference<LastMatchData>(data); + } + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType()); + profile.checkSearchPattern(project, options); + } + + public static class CompiledOptions { + public final List<Pair<MatchContext, Configuration>> matchContexts; + + public CompiledOptions(final List<Pair<MatchContext, Configuration>> matchContexts) { + this.matchContexts = matchContexts; + } + + public List<Pair<MatchContext, Configuration>> getMatchContexts() { + return matchContexts; + } + } + + public static boolean checkIfShouldAttemptToMatch(MatchContext context, NodeIterator matchedNodes) { + final CompiledPattern pattern = context.getPattern(); + final NodeIterator patternNodes = pattern.getNodes(); + try { + while (true) { + final PsiElement patternNode = patternNodes.current(); + if (patternNode == null) { + return true; + } + final PsiElement matchedNode = matchedNodes.current(); + if (matchedNode == null) { + return false; + } + final MatchingHandler matchingHandler = pattern.getHandler(patternNode); + if (matchingHandler == null || !matchingHandler.canMatch(patternNode, matchedNode)) { + return false; + } + matchedNodes.advance(); + patternNodes.advance(); + } + } finally { + patternNodes.reset(); + matchedNodes.reset(); + } + } + + public void processMatchesInElement(MatchContext context, Configuration configuration, + NodeIterator matchedNodes, + PairProcessor<MatchResult, Configuration> processor) { + try { + configureOptions(context, configuration, matchedNodes.current(), processor); + context.setShouldRecursivelyMatch(false); + visitor.matchContext(matchedNodes); + } finally { + matchedNodes.reset(); + } + } + + public void clearContext() { + matchContext.clear(); + } + + private void configureOptions(MatchContext context, + final Configuration configuration, + PsiElement psiFile, + final PairProcessor<MatchResult, Configuration> processor) { + if (psiFile == null) return; + LocalSearchScope scope = new LocalSearchScope(psiFile); + + matchContext.clear(); + matchContext.setMatcher(visitor); + + MatchOptions options = context.getOptions(); + matchContext.setOptions(options); + matchContext.setPattern(context.getPattern()); + matchContext.setShouldRecursivelyMatch(context.shouldRecursivelyMatch()); + visitor.setMatchContext(matchContext); + + matchContext.setSink( + new MatchConstraintsSink( + new MatchResultSink() { + public void newMatch(MatchResult result) { + processor.process(result, configuration); + } + + public void processFile(PsiFile element) { + } + + public void setMatchingProcess(MatchingProcess matchingProcess) { + } + + public void matchingFinished() { + } + + public ProgressIndicator getProgressIndicator() { + return null; + } + }, + options.getMaxMatchesCount(), + options.isDistinct(), + options.isCaseSensitiveMatch() + ) + ); + options.setScope(scope); + } + + public CompiledOptions precompileOptions(List<Configuration> configurations) { + List<Pair<MatchContext, Configuration>> contexts = new ArrayList<Pair<MatchContext, Configuration>>(); + + for (Configuration configuration : configurations) { + MatchContext matchContext = new MatchContext(); + matchContext.setMatcher(visitor); + MatchOptions matchOptions = configuration.getMatchOptions(); + matchContext.setOptions(matchOptions); + + try { + CompiledPattern compiledPattern = PatternCompiler.compilePattern(project, matchOptions); + matchContext.setPattern(compiledPattern); + contexts.add(Pair.create(matchContext, configuration)); + } + catch (UnsupportedPatternException ignored) {} + catch (MalformedPatternException ignored) {} + } + return new CompiledOptions(contexts); + } + + Project getProject() { + return project; + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected void findMatches(MatchResultSink sink, final MatchOptions options) throws MalformedPatternException, UnsupportedPatternException + { + CompiledPattern compiledPattern = prepareMatching(sink, options); + if (compiledPattern== null) { + return; + } + + matchContext.getSink().setMatchingProcess( scheduler ); + scheduler.init(); + progress = matchContext.getSink().getProgressIndicator(); + + if (/*TokenBasedSearcher.canProcess(compiledPattern)*/ false) { + //TokenBasedSearcher searcher = new TokenBasedSearcher(this); + //searcher.search(compiledPattern); + if (isTesting) { + matchContext.getSink().matchingFinished(); + return; + } + } + else { + if (isTesting) { + // testing mode; + final PsiElement[] elements = ((LocalSearchScope)options.getScope()).getScope(); + + PsiElement parent = elements[0].getParent(); + if (elements.length > 0 && matchContext.getPattern().getStrategy().continueMatching(parent != null ? parent : elements[0])) { + visitor.matchContext(new SsrFilteringNodeIterator(new ArrayBackedNodeIterator(elements))); + } + else { + for (PsiElement element : elements) { + match(element); + } + } + + matchContext.getSink().matchingFinished(); + return; + } + if (!findMatches(options, compiledPattern)) { + return; + } + } + + if (scheduler.getTaskQueueEndAction()==null) { + scheduler.setTaskQueueEndAction( + new Runnable() { + public void run() { + matchContext.getSink().matchingFinished(); + } + } + ); + } + + scheduler.executeNext(); + } + + private boolean findMatches(MatchOptions options, CompiledPattern compiledPattern) { + LanguageFileType languageFileType = (LanguageFileType)options.getFileType(); + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(languageFileType.getLanguage()); + assert profile != null; + PsiElement node = compiledPattern.getNodes().current(); + final Language ourPatternLanguage = node != null ? profile.getLanguage(node) : ((LanguageFileType)options.getFileType()).getLanguage(); + final Language ourPatternLanguage2 = ourPatternLanguage == StdLanguages.XML ? StdLanguages.XHTML:null; + SearchScope searchScope = compiledPattern.getScope(); + boolean ourOptimizedScope = searchScope != null; + if (!ourOptimizedScope) searchScope = options.getScope(); + + if (searchScope instanceof GlobalSearchScope) { + final GlobalSearchScope scope = (GlobalSearchScope)searchScope; + + final ContentIterator ci = new ContentIterator() { + public boolean processFile(final VirtualFile fileOrDir) { + if (!fileOrDir.isDirectory() && scope.contains(fileOrDir) && fileOrDir.getFileType() != FileTypes.UNKNOWN) { + ++totalFilesToScan; + scheduler.addOneTask(new MatchOneVirtualFile(fileOrDir, profile, ourPatternLanguage, ourPatternLanguage2)); + } + return true; + } + }; + + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + FileBasedIndex.getInstance().iterateIndexableFiles(ci, project, progress); + } + }); + progress.setText2(""); + } + else { + final PsiElement[] elementsToScan = ((LocalSearchScope)searchScope).getScope(); + totalFilesToScan = elementsToScan.length; + + for (int i = 0; i < elementsToScan.length; ++i) { + final PsiElement psiElement = elementsToScan[i]; + + if (psiElement == null) continue; + final Language language = psiElement.getLanguage(); + + PsiFile file = psiElement instanceof PsiFile ? (PsiFile)psiElement : psiElement.getContainingFile(); + + if (profile.isMyFile(file, language, ourPatternLanguage, ourPatternLanguage2)) { + scheduler.addOneTask(new MatchOnePsiFile(psiElement)); + } + if (ourOptimizedScope) elementsToScan[i] = null; // to prevent long PsiElement reference + } + } + return true; + } + + private CompiledPattern prepareMatching(final MatchResultSink sink, final MatchOptions options) { + CompiledPattern savedPattern = null; + + if (matchContext.getOptions() == options && matchContext.getPattern() != null && + matchContext.getOptions().hashCode() == matchContext.getPattern().getOptionsHashStamp()) { + savedPattern = matchContext.getPattern(); + } + + matchContext.clear(); + matchContext.setSink( + new MatchConstraintsSink( + sink, + options.getMaxMatchesCount(), + options.isDistinct(), + options.isCaseSensitiveMatch() + ) + ); + matchContext.setOptions(options); + matchContext.setMatcher(visitor); + visitor.setMatchContext(matchContext); + + CompiledPattern compiledPattern = savedPattern; + + if (compiledPattern == null) { + + synchronized(getClass()) { + final LastMatchData data = com.intellij.reference.SoftReference.dereference(lastMatchData); + if (data != null && options == data.lastOptions) { + compiledPattern = data.lastPattern; + } + lastMatchData = null; + } + + if (compiledPattern==null) { + compiledPattern = ApplicationManager.getApplication().runReadAction(new Computable<CompiledPattern>() { + @Override + public CompiledPattern compute() { + return PatternCompiler.compilePattern(project,options); + } + }); + } + } + + cacheCompiledPattern(options, compiledPattern); + return compiledPattern; + } + + private void cacheCompiledPattern(final MatchOptions options, final CompiledPattern compiledPattern) { + matchContext.setPattern(compiledPattern); + compiledPattern.setOptionsHashStamp(options.hashCode()); + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param sink match result destination + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected void testFindMatches(MatchResultSink sink, MatchOptions options) + throws MalformedPatternException, UnsupportedPatternException { + isTesting = true; + try { + findMatches(sink,options); + } finally { + isTesting = false; + } + } + + /** + * Finds the matches of given pattern starting from given tree element. + * @param source string for search + * @param pattern to be searched + * @return list of matches found + * @throws MalformedPatternException + * @throws UnsupportedPatternException + */ + protected List<MatchResult> testFindMatches(String source, + String pattern, + MatchOptions options, + boolean filePattern, + FileType sourceFileType, + String sourceExtension, + boolean physicalSourceFile) + throws MalformedPatternException, UnsupportedPatternException { + + CollectingMatchResultSink sink = new CollectingMatchResultSink(); + + try { + PsiElement[] elements = MatcherImplUtil.createSourceTreeFromText(source, + filePattern ? PatternTreeContext.File : PatternTreeContext.Block, + sourceFileType, + sourceExtension, + project, physicalSourceFile); + + options.setSearchPattern(pattern); + options.setScope(new LocalSearchScope(elements)); + testFindMatches(sink, options); + } + catch (IncorrectOperationException e) { + MalformedPatternException exception = new MalformedPatternException(); + exception.initCause(e); + throw exception; + } + + return sink.getMatches(); + } + + protected List<MatchResult> testFindMatches(String source, String pattern, MatchOptions options, boolean filePattern) { + return testFindMatches(source, pattern, options, filePattern, options.getFileType(), null, false); + } + + class TaskScheduler implements MatchingProcess { + private LinkedList<Runnable> tasks = new LinkedList<Runnable>(); + private boolean ended; + private Runnable taskQueueEndAction; + + private boolean suspended; + + public void stop() { + ended = true; + } + + public void pause() { + suspended = true; + } + + public void resume() { + if (!suspended) return; + suspended = false; + executeNext(); + } + + public boolean isSuspended() { + return suspended; + } + + public boolean isEnded() { + return ended; + } + + void setTaskQueueEndAction(Runnable taskQueueEndAction) { + this.taskQueueEndAction = taskQueueEndAction; + } + Runnable getTaskQueueEndAction () { + return taskQueueEndAction; + } + + void addOneTask(Runnable runnable) { + tasks.add(runnable); + } + + private void executeNext() { + while(!suspended && !ended) { + if (tasks.isEmpty()) { + ended = true; + break; + } + + final Runnable task = tasks.removeFirst(); + try { + task.run(); + } + catch (ProcessCanceledException e) { + ended = true; + clearSchedule(); + throw e; + } + catch (StructuralSearchException e) { + ended = true; + clearSchedule(); + throw e; + } + catch (Throwable th) { + LOG.error(th); + } + } + + if (ended) clearSchedule(); + } + + private void init() { + ended = false; + suspended = false; + PsiManager.getInstance(project).startBatchFilesProcessingMode(); + } + + private void clearSchedule() { + if (tasks != null) { + taskQueueEndAction.run(); + if (!project.isDisposed()) { + PsiManager.getInstance(project).finishBatchFilesProcessingMode(); + } + tasks = null; + } + } + + } + + private class MatchOnePsiFile extends MatchOneFile { + private PsiElement file; + + MatchOnePsiFile(PsiElement file) { + this.file = file; + } + + @Nullable + @Override + protected List<PsiElement> getPsiElementsToProcess() { + PsiElement file = this.file; + this.file = null; + return new SmartList<PsiElement>(file); + } + } + + private abstract class MatchOneFile implements Runnable { + public void run() { + List<PsiElement> files = getPsiElementsToProcess(); + + if (progress!=null) { + progress.setFraction((double)scannedFilesCount/totalFilesToScan); + } + + ++scannedFilesCount; + + if (files == null || files.size() == 0) return; + final PsiFile psiFile = files.get(0).getContainingFile(); + + if (psiFile!=null) { + final Runnable action = new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + if (project.isDisposed()) return; + final PsiDocumentManager manager = PsiDocumentManager.getInstance(project); + Document document = manager.getDocument(psiFile); + if (document != null) manager.commitDocument(document); + } + }); + } + }; + + if (ApplicationManager.getApplication().isDispatchThread()) { + action.run(); + } else { + ApplicationManager.getApplication().invokeAndWait( + action, + ModalityState.defaultModalityState() + ); + } + } + + if (project.isDisposed()) return; + + for(PsiElement file:files) { + if (file instanceof PsiFile) { + matchContext.getSink().processFile((PsiFile)file); + } + + final PsiElement finalFile = file; + ApplicationManager.getApplication().runReadAction( + new Runnable() { + public void run() { + PsiElement file = finalFile; + if (!file.isValid()) return; + file = StructuralSearchUtil.getProfileByLanguage(file.getLanguage()).extendMatchOnePsiFile(file); + match(file); + } + } + ); + } + } + + protected abstract @Nullable List<PsiElement> getPsiElementsToProcess(); + } + + // Initiates the matching process for given element + // @param element the current search tree element + public void match(PsiElement element) { + MatchingStrategy strategy = matchContext.getPattern().getStrategy(); + + if (strategy.continueMatching(element)) { + visitor.matchContext(new ArrayBackedNodeIterator(new PsiElement[] {element})); + return; + } + for(PsiElement el=element.getFirstChild();el!=null;el=el.getNextSibling()) { + match(el); + } + if (element instanceof PsiLanguageInjectionHost) { + InjectedLanguageUtil.enumerate(element, new PsiLanguageInjectionHost.InjectedPsiVisitor() { + @Override + public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { + match(injectedPsi); + } + }); + } + } + + @Nullable + protected MatchResult isMatchedByDownUp(PsiElement element, final MatchOptions options) { + final CollectingMatchResultSink sink = new CollectingMatchResultSink(); + CompiledPattern compiledPattern = prepareMatching(sink, options); + + if (compiledPattern== null) { + assert false; + return null; + } + + PsiElement targetNode = compiledPattern.getTargetNode(); + PsiElement elementToStartMatching = null; + + if (targetNode == null) { + targetNode = compiledPattern.getNodes().current(); + if (targetNode != null) { + compiledPattern.getNodes().advance(); + assert !compiledPattern.getNodes().hasNext(); + compiledPattern.getNodes().rewind(); + + while (element.getClass() != targetNode.getClass()) { + element = element.getParent(); + if (element == null) return null; + } + + elementToStartMatching = element; + } + } else { + targetNode = StructuralSearchUtil.getProfileByPsiElement(element).extendMatchedByDownUp(targetNode); + + MatchingHandler handler = null; + + while (element.getClass() == targetNode.getClass() || + compiledPattern.isTypedVar(targetNode) && compiledPattern.getHandler(targetNode).canMatch(targetNode, element) + ) { + handler = compiledPattern.getHandler(targetNode); + handler.setPinnedElement(element); + elementToStartMatching = element; + if (handler instanceof TopLevelMatchingHandler) break; + element = element.getParent(); + targetNode = targetNode.getParent(); + + if (options.isLooseMatching()) { + element = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(element); + targetNode = StructuralSearchUtil.getProfileByPsiElement(element).updateCurrentNode(targetNode); + } + } + + if (!(handler instanceof TopLevelMatchingHandler)) return null; + } + + assert targetNode != null : "Could not match down up when no target node"; + + match(elementToStartMatching); + matchContext.getSink().matchingFinished(); + final int matchCount = sink.getMatches().size(); + assert matchCount <= 1; + return matchCount > 0 ? sink.getMatches().get(0) : null; + } + + private class MatchOneVirtualFile extends MatchOneFile { + private final VirtualFile myFileOrDir; + private final StructuralSearchProfile myProfile; + private final Language myOurPatternLanguage; + private final Language myOurPatternLanguage2; + + public MatchOneVirtualFile(VirtualFile fileOrDir, + StructuralSearchProfile profile, + Language ourPatternLanguage, + Language ourPatternLanguage2) { + myFileOrDir = fileOrDir; + myProfile = profile; + myOurPatternLanguage = ourPatternLanguage; + myOurPatternLanguage2 = ourPatternLanguage2; + } + + @Nullable + @Override + protected List<PsiElement> getPsiElementsToProcess() { + return ApplicationManager.getApplication().runReadAction(new Computable<List<PsiElement>>() { + @Override + public List<PsiElement> compute() { + PsiFile file = PsiManager.getInstance(project).findFile(myFileOrDir); + if (file == null) { + return null; + } + + final FileViewProvider viewProvider = file.getViewProvider(); + List<PsiElement> elementsToProcess = new SmartList<PsiElement>(); + + for(Language lang: viewProvider.getLanguages()) { + if (myProfile.isMyFile(file, lang, myOurPatternLanguage, myOurPatternLanguage2)) { + elementsToProcess.add(viewProvider.getPsi(lang)); + } + } + + return elementsToProcess; + } + }); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java new file mode 100644 index 000000000000..ffc9ae78e876 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/MatcherImplUtil.java @@ -0,0 +1,67 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.lang.Language; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; +import com.intellij.util.IncorrectOperationException; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 19, 2004 + * Time: 6:56:25 PM + * To change this template use File | Settings | File Templates. + */ +public class MatcherImplUtil { + + public static void transform(MatchOptions options) { + if (options.hasVariableConstraints()) return; + PatternCompiler.transformOldPattern(options); + } + + public static PsiElement[] createTreeFromText(String text, PatternTreeContext context, FileType fileType, Project project) + throws IncorrectOperationException { + return createTreeFromText(text, context, fileType, null, null, project, false); + } + + public static PsiElement[] createSourceTreeFromText(String text, + PatternTreeContext context, + FileType fileType, + String extension, + Project project, + boolean physical) { + if (fileType instanceof LanguageFileType) { + Language language = ((LanguageFileType)fileType).getLanguage(); + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + if (profile != null) { + return profile.createPatternTree(text, context, fileType, null, null, extension, project, physical); + } + } + return PsiElement.EMPTY_ARRAY; + } + + public static PsiElement[] createTreeFromText(String text, + PatternTreeContext context, + FileType fileType, + Language language, + String contextName, + Project project, + boolean physical) throws IncorrectOperationException { + if (language == null && fileType instanceof LanguageFileType) { + language = ((LanguageFileType)fileType).getLanguage(); + } + if (language != null) { + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + if (profile != null) { + return profile.createPatternTree(text, context, fileType, language, contextName, null, project, physical); + } + } + return PsiElement.EMPTY_ARRAY; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java new file mode 100644 index 000000000000..7d93c9ae82e0 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/PatternTreeContext.java @@ -0,0 +1,8 @@ +package com.intellij.structuralsearch.impl.matcher; + +/** +* @author Eugene.Kudelevsky +*/ +public enum PatternTreeContext { + File, Block, Class +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java new file mode 100644 index 000000000000..0c9f4945e3a5 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlCompiledPattern.java @@ -0,0 +1,22 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlCompiledPattern extends CompiledPattern { + private static final String XML_TYPED_VAR_PREFIX = "__"; + + public XmlCompiledPattern() { + setStrategy(XmlMatchingStrategy.getInstance()); + } + + public String[] getTypedVarPrefixes() { + return new String[] {XML_TYPED_VAR_PREFIX}; + } + + public boolean isTypedVar(final String str) { + return str.startsWith(XML_TYPED_VAR_PREFIX); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java new file mode 100644 index 000000000000..d2e1e210534b --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/XmlMatchingVisitor.java @@ -0,0 +1,130 @@ +package com.intellij.structuralsearch.impl.matcher; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.psi.xml.*; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; + +import java.util.ArrayList; +import java.util.List; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlMatchingVisitor extends XmlElementVisitor { + private final GlobalMatchingVisitor myMatchingVisitor; + private final boolean myCaseSensitive; + + public XmlMatchingVisitor(GlobalMatchingVisitor matchingVisitor) { + myMatchingVisitor = matchingVisitor; + myCaseSensitive = myMatchingVisitor.getMatchContext().getOptions().isCaseSensitiveMatch(); + } + + @Override + public void visitElement(final PsiElement element) { + myMatchingVisitor.setResult(element.textMatches(element)); + } + + @Override public void visitXmlAttribute(XmlAttribute attribute) { + final XmlAttribute another = (XmlAttribute)myMatchingVisitor.getElement(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(attribute.getName()); + + myMatchingVisitor.setResult(matches(attribute.getName(), another.getName()) || isTypedVar); + if (myMatchingVisitor.getResult()) { + myMatchingVisitor.setResult(myMatchingVisitor.match(attribute.getValueElement(), another.getValueElement())); + } + + if (myMatchingVisitor.getResult() && isTypedVar) { + MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler(attribute.getName()); + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext())); + } + } + + @Override public void visitXmlAttributeValue(XmlAttributeValue value) { + final XmlAttributeValue another = (XmlAttributeValue)myMatchingVisitor.getElement(); + final String text = value.getValue(); + + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text); + MatchingHandler handler; + + if (isTypedVar && (handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( text )) instanceof SubstitutionHandler) { + String text2 = another.getText(); + int offset = text2.length() > 0 && ( text2.charAt(0) == '"' || text2.charAt(0) == '\'') ? 1:0; + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, offset, text2.length() - offset, + myMatchingVisitor.getMatchContext())); + } else { + myMatchingVisitor.setResult(matches(text, another.getValue())); + } + } + + @Override public void visitXmlTag(XmlTag tag) { + final XmlTag another = (XmlTag)myMatchingVisitor.getElement(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(tag.getName()); + + myMatchingVisitor.setResult((matches(tag.getName(), another.getName()) || isTypedVar) && + myMatchingVisitor.matchInAnyOrder(tag.getAttributes(), another.getAttributes())); + + if(myMatchingVisitor.getResult()) { + final XmlTagChild[] contentChildren = tag.getValue().getChildren(); + + if (contentChildren.length > 0) { + PsiElement[] patternNodes = contentChildren; + PsiElement[] matchedNodes = another.getValue().getChildren(); + + if (contentChildren.length != 1) { + patternNodes = expandXmlTexts(patternNodes); + matchedNodes = expandXmlTexts(matchedNodes); + } + + myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially( + new ArrayBackedNodeIterator(patternNodes), + new ArrayBackedNodeIterator(matchedNodes) + )); + } + } + + if (myMatchingVisitor.getResult() && isTypedVar) { + MatchingHandler handler = myMatchingVisitor.getMatchContext().getPattern().getHandler( tag.getName() ); + myMatchingVisitor.setResult(((SubstitutionHandler)handler).handle(another, myMatchingVisitor.getMatchContext())); + } + } + + private static PsiElement[] expandXmlTexts(PsiElement[] children) { + List<PsiElement> result = new ArrayList<PsiElement>(children.length); + for(PsiElement c:children) { + if (c instanceof XmlText) { + for(PsiElement p:c.getChildren()) { + if (!(p instanceof PsiWhiteSpace)) result.add(p); + } + } else if (!(c instanceof PsiWhiteSpace)) { + result.add(c); + } + } + return PsiUtilCore.toPsiElementArray(result); + } + + @Override public void visitXmlText(XmlText text) { + myMatchingVisitor.setResult(myMatchingVisitor.matchSequentially(text.getFirstChild(), myMatchingVisitor.getElement().getFirstChild())); + } + + @Override public void visitXmlToken(XmlToken token) { + if (token.getTokenType() == XmlTokenType.XML_DATA_CHARACTERS) { + String text = token.getText(); + final boolean isTypedVar = myMatchingVisitor.getMatchContext().getPattern().isTypedVar(text); + + if (isTypedVar) { + myMatchingVisitor.setResult(myMatchingVisitor.handleTypedElement(token, myMatchingVisitor.getElement())); + } else { + myMatchingVisitor.setResult(matches(text, myMatchingVisitor.getElement().getText())); + } + } + } + + private boolean matches(String a, String b) { + return myCaseSensitive ? a.equals(b) : a.equalsIgnoreCase(b); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java new file mode 100644 index 000000000000..09758a5e2191 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/CompileContext.java @@ -0,0 +1,71 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 17.11.2004 + * Time: 19:26:37 + * To change this template use File | Settings | File Templates. + */ +public class CompileContext { + private OptimizingSearchHelper searchHelper; + + private CompiledPattern pattern; + private MatchOptions options; + private Project project; + + public void clear() { + if (searchHelper!=null) searchHelper.clear(); + + project = null; + pattern = null; + options = null; + } + + public void init(final CompiledPattern _result, final MatchOptions _options, final Project _project, final boolean _findMatchingFiles) { + options = _options; + project = _project; + pattern = _result; + + searchHelper = ApplicationManager.getApplication().isUnitTestMode() ? + new TestModeOptimizingSearchHelper(this) : + new FindInFilesOptimizingSearchHelper(this, _findMatchingFiles, _project); + } + + public OptimizingSearchHelper getSearchHelper() { + return searchHelper; + } + + void setSearchHelper(OptimizingSearchHelper searchHelper) { + this.searchHelper = searchHelper; + } + + public CompiledPattern getPattern() { + return pattern; + } + + void setPattern(CompiledPattern pattern) { + this.pattern = pattern; + } + + MatchOptions getOptions() { + return options; + } + + void setOptions(MatchOptions options) { + this.options = options; + } + + Project getProject() { + return project; + } + + void setProject(Project project) { + this.project = project; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java new file mode 100644 index 000000000000..4b5ad7c64545 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/DeleteNodesAction.java @@ -0,0 +1,59 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiElement; + +import java.util.ArrayList; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 17.11.2004 + * Time: 19:24:40 + * To change this template use File | Settings | File Templates. + */ +class DeleteNodesAction implements Runnable { + private final ArrayList elements; + + DeleteNodesAction(ArrayList _elements) { + elements = _elements; + } + + private void delete(PsiElement first, PsiElement last) throws Exception { + if (last==first) { + first.delete(); + } else { + first.getParent().deleteChildRange(first,last); + } + } + public void run() { + try { + PsiElement first= null; + PsiElement last = null; + + for(int i = 0;i < elements.size(); ++i) { + final PsiElement el = (PsiElement)elements.get(i); + + if (!el.isValid()) continue; + + if (first==null) { + first = last = el; + } else if (last.getNextSibling()==el) { + last = el; + } else { + delete(first,last); + first = last = null; + --i; + continue; + } + } + + if (first!=null) { + delete(first,last); + } + } catch(Throwable ex) { + ex.printStackTrace(); + } finally { + elements.clear(); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java new file mode 100644 index 000000000000..ff7f06a36d5b --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/FindInFilesOptimizingSearchHelper.java @@ -0,0 +1,103 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.lang.Language; +import com.intellij.lang.LanguageNamesValidation; +import com.intellij.lang.refactoring.NamesValidator; +import com.intellij.lexer.Lexer; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.util.Processor; +import gnu.trove.THashMap; + +import java.util.Set; + +/** + * @author Maxim.Mossienko +*/ +class FindInFilesOptimizingSearchHelper extends OptimizingSearchHelperBase { + private PsiSearchHelper helper; + private THashMap<PsiFile,PsiFile> filesToScan; + private THashMap<PsiFile,PsiFile> filesToScan2; + private Lexer javaLexer; + + private final boolean findMatchingFiles; + + FindInFilesOptimizingSearchHelper(CompileContext _context, boolean _findMatchngFiles, Project project) { + super(_context); + findMatchingFiles = _findMatchngFiles; + + if (findMatchingFiles) { + helper = PsiSearchHelper.SERVICE.getInstance(project); + + if (filesToScan == null) { + filesToScan = new THashMap<PsiFile,PsiFile>(); + filesToScan2 = new THashMap<PsiFile,PsiFile>(); + } + } + } + + public boolean doOptimizing() { + return findMatchingFiles; + } + + public void clear() { + super.clear(); + + if (filesToScan != null) { + filesToScan.clear(); + filesToScan2.clear(); + + helper = null; + } + } + + protected void doAddSearchWordInCode(final String refname) { + final FileType fileType = context.getOptions().getFileType(); + final Language language = fileType instanceof LanguageFileType ? ((LanguageFileType)fileType).getLanguage() : Language.ANY; + final NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(language); + if (namesValidator.isKeyword(refname, context.getProject())) { + helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } else { + helper.processAllFilesWithWord(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } + } + + protected void doAddSearchWordInText(final String refname) { + helper.processAllFilesWithWordInText(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor(), true); + } + + protected void doAddSearchWordInComments(final String refname) { + helper.processAllFilesWithWordInComments(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor()); + } + + protected void doAddSearchWordInLiterals(final String refname) { + helper.processAllFilesWithWordInLiterals(refname, (GlobalSearchScope)context.getOptions().getScope(), new MyFileProcessor()); + } + + public void endTransaction() { + super.endTransaction(); + THashMap<PsiFile,PsiFile> map = filesToScan; + if (map.size() > 0) map.clear(); + filesToScan = filesToScan2; + filesToScan2 = map; + } + + public Set<PsiFile> getFilesSetToScan() { + return filesToScan.keySet(); + } + + private class MyFileProcessor implements Processor<PsiFile> { + public boolean process(PsiFile file) { + if (scanRequest == 0 || + filesToScan.get(file)!=null) { + filesToScan2.put(file,file); + } + return true; + } + } + +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java new file mode 100644 index 000000000000..130751adec67 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/GlobalCompilingVisitor.java @@ -0,0 +1,314 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.filters.CompositeFilter; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.LiteralWithSubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.intellij.structuralsearch.MatchOptions.INSTANCE_MODIFIER_NAME; +import static com.intellij.structuralsearch.MatchOptions.MODIFIER_ANNOTATION_NAME; + +/** + * @author maxim + */ +public class GlobalCompilingVisitor { + @NonNls private static final String SUBSTITUTION_PATTERN_STR = "\\b(__\\$_\\w+)\\b"; + private static final Pattern ourSubstitutionPattern = Pattern.compile(SUBSTITUTION_PATTERN_STR); + private static final Set<String> ourReservedWords = new HashSet<String>(Arrays.asList(MODIFIER_ANNOTATION_NAME, INSTANCE_MODIFIER_NAME)) { + { + for (StructuralSearchProfile profile : Extensions.getExtensions(StructuralSearchProfile.EP_NAME)) { + addAll(profile.getReservedWords()); + } + } + }; + private static final Pattern ourAlternativePattern = Pattern.compile("^\\((.+)\\)$"); + @NonNls private static final String WORD_SEARCH_PATTERN_STR = ".*?\\b(.+?)\\b.*?"; + private static final Pattern ourWordSearchPattern = Pattern.compile(WORD_SEARCH_PATTERN_STR); + private CompileContext context; + private final ArrayList<PsiElement> myLexicalNodes = new ArrayList<PsiElement>(); + + private int myCodeBlockLevel; + + private static final NodeFilter ourFilter = LexicalNodesFilter.getInstance(); + + public static NodeFilter getFilter() { + return ourFilter; + } + + public void setHandler(PsiElement element, MatchingHandler handler) { + MatchingHandler realHandler = context.getPattern().getHandlerSimple(element); + + if (realHandler instanceof SubstitutionHandler) { + ((SubstitutionHandler)realHandler).setMatchHandler(handler); + } + else { + // @todo care about composite handler in this case of simple handler! + context.getPattern().setHandler(element, handler); + } + } + + public final void handle(PsiElement element) { + + if ((!ourFilter.accepts(element) || + StructuralSearchUtil.isIdentifier(element)) && + context.getPattern().isRealTypedVar(element) && + context.getPattern().getHandlerSimple(element) == null + ) { + String name = context.getPattern().getTypedVarString(element); + // name is the same for named element (clazz,methods, etc) and token (name of ... itself) + // @todo need fix this + final SubstitutionHandler handler; + + context.getPattern().setHandler( + element, + handler = (SubstitutionHandler)context.getPattern().getHandler(name) + ); + + if (handler != null && context.getOptions().getVariableConstraint(handler.getName()).isPartOfSearchResults()) { + handler.setTarget(true); + context.getPattern().setTargetNode(element); + } + } + } + + public CompileContext getContext() { + return context; + } + + public int getCodeBlockLevel() { + return myCodeBlockLevel; + } + + public void setCodeBlockLevel(int codeBlockLevel) { + this.myCodeBlockLevel = codeBlockLevel; + } + + static void setFilter(MatchingHandler handler, NodeFilter filter) { + if (handler.getFilter() != null && + handler.getFilter().getClass() != filter.getClass() + ) { + // for constructor we will have the same handler for class and method and tokens itselfa + handler.setFilter( + new CompositeFilter( + filter, + handler.getFilter() + ) + ); + } + else { + handler.setFilter(filter); + } + } + + public ArrayList<PsiElement> getLexicalNodes() { + return myLexicalNodes; + } + + public void addLexicalNode(PsiElement node) { + myLexicalNodes.add(node); + } + + void compile(PsiElement[] elements, CompileContext context) { + myCodeBlockLevel = 0; + this.context = context; + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()); + assert profile != null; + profile.compile(elements, this); + + if (context.getPattern().getStrategy() == null) { + System.out.println(); + } + } + + @Nullable + public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind) { + return processPatternStringWithFragments(pattern, kind, ourSubstitutionPattern); + } + + @Nullable + public MatchingHandler processPatternStringWithFragments(String pattern, OccurenceKind kind, Pattern substitutionPattern) { + String content; + + if (kind == OccurenceKind.LITERAL) { + content = pattern.substring(1, pattern.length() - 1); + } + else if (kind == OccurenceKind.COMMENT) { + content = pattern; + } + else { + return null; + } + + @NonNls StringBuilder buf = new StringBuilder(content.length()); + Matcher matcher = substitutionPattern.matcher(content); + List<SubstitutionHandler> handlers = null; + int start = 0; + String word; + boolean hasLiteralContent = false; + + SubstitutionHandler handler = null; + while (matcher.find()) { + if (handlers == null) handlers = new ArrayList<SubstitutionHandler>(); + handler = (SubstitutionHandler)getContext().getPattern().getHandler(matcher.group(1)); + if (handler != null) handlers.add(handler); + + word = content.substring(start, matcher.start()); + + if (word.length() > 0) { + buf.append(StructuralSearchUtil.shieldSpecialChars(word)); + hasLiteralContent = true; + + processTokenizedName(word, false, kind); + } + + RegExpPredicate predicate = MatchingHandler.getSimpleRegExpPredicate(handler); + + if (predicate == null || !predicate.isWholeWords()) { + buf.append("(.*?)"); + } + else { + buf.append(".*?\\b(").append(predicate.getRegExp()).append(")\\b.*?"); + } + + if (isSuitablePredicate(predicate, handler)) { + processTokenizedName(predicate.getRegExp(), false, kind); + } + + start = matcher.end(); + } + + word = content.substring(start, content.length()); + + if (word.length() > 0) { + hasLiteralContent = true; + buf.append(StructuralSearchUtil.shieldSpecialChars(word)); + + processTokenizedName(word, false, kind); + } + + if (hasLiteralContent) { + if (kind == OccurenceKind.LITERAL) { + buf.insert(0, "[\"']"); + buf.append("[\"']"); + } + buf.append("$"); + } + + if (handlers != null) { + return hasLiteralContent ? new LiteralWithSubstitutionHandler(buf.toString(), handlers) : handler; + } + + return null; + } + + @Contract("null,_ -> false") + static boolean isSuitablePredicate(RegExpPredicate predicate, SubstitutionHandler handler) { + return predicate != null && handler.getMinOccurs() != 0 && predicate.couldBeOptimized(); + } + + public static void addFilesToSearchForGivenWord(String refname, + boolean endTransaction, + GlobalCompilingVisitor.OccurenceKind kind, + CompileContext compileContext) { + if (!compileContext.getSearchHelper().doOptimizing()) { + return; + } + if (ourReservedWords.contains(refname)) return; // skip our special annotations !!! + + boolean addedSomething = false; + + if (kind == GlobalCompilingVisitor.OccurenceKind.CODE) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInCode(refname); + } + else if (kind == GlobalCompilingVisitor.OccurenceKind.COMMENT) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInComments(refname); + } + else if (kind == GlobalCompilingVisitor.OccurenceKind.LITERAL) { + addedSomething = compileContext.getSearchHelper().addWordToSearchInLiterals(refname); + } + + if (addedSomething && endTransaction) { + compileContext.getSearchHelper().endTransaction(); + } + } + + public void processTokenizedName(String name, boolean skipComments, GlobalCompilingVisitor.OccurenceKind kind) { + WordTokenizer tokenizer = new WordTokenizer(name); + for (Iterator<String> i = tokenizer.iterator(); i.hasNext();) { + String nextToken = i.next(); + if (skipComments && + (nextToken.equals("/*") || nextToken.equals("/**") || nextToken.equals("*/") || nextToken.equals("*") || nextToken.equals("//")) + ) { + continue; + } + + Matcher matcher = ourAlternativePattern.matcher(nextToken); + if (matcher.matches()) { + StringTokenizer alternatives = new StringTokenizer(matcher.group(1), "|"); + while (alternatives.hasMoreTokens()) { + addFilesToSearchForGivenWord(alternatives.nextToken(), !alternatives.hasMoreTokens(), kind, getContext()); + } + } + else { + addFilesToSearchForGivenWord(nextToken, true, kind, getContext()); + } + } + } + + public enum OccurenceKind { + LITERAL, COMMENT, CODE + } + + private static class WordTokenizer { + private final List<String> myWords = new ArrayList<String>(); + + WordTokenizer(String text) { + final StringTokenizer tokenizer = new StringTokenizer(text); + Matcher matcher = null; + + while (tokenizer.hasMoreTokens()) { + String nextToken = tokenizer.nextToken(); + if (matcher == null) { + matcher = ourWordSearchPattern.matcher(nextToken); + } + else { + matcher.reset(nextToken); + } + + nextToken = (matcher.matches()) ? matcher.group(1) : nextToken; + int lastWordStart = 0; + int i; + for (i = 0; i < nextToken.length(); ++i) { + if (!Character.isJavaIdentifierStart(nextToken.charAt(i))) { + if (i != lastWordStart) { + myWords.add(nextToken.substring(lastWordStart, i)); + } + lastWordStart = i + 1; + } + } + + if (i != lastWordStart) { + myWords.add(nextToken.substring(lastWordStart, i)); + } + } + } + + Iterator<String> iterator() { + return myWords.iterator(); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java new file mode 100644 index 000000000000..8d6a08b6ef95 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelper.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiFile; + +import java.util.Set; + +/** + * @author Maxim.Mossienko +*/ +public interface OptimizingSearchHelper { + boolean doOptimizing(); + void clear(); + + boolean addWordToSearchInCode(final String refname); + + boolean addWordToSearchInText(final String refname); + + boolean addWordToSearchInComments(final String refname); + + boolean addWordToSearchInLiterals(final String refname); + + void endTransaction(); + + boolean isScannedSomething(); + + Set<PsiFile> getFilesSetToScan(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java new file mode 100644 index 000000000000..2d6f2bc41b37 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/OptimizingSearchHelperBase.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import gnu.trove.THashSet; + +/** + * @author Maxim.Mossienko +*/ +abstract class OptimizingSearchHelperBase implements OptimizingSearchHelper { + private final THashSet<String> scanned; + private final THashSet<String> scannedText; + private final THashSet<String> scannedComments; + private final THashSet<String> scannedLiterals; + protected int scanRequest; + + + protected final CompileContext context; + + OptimizingSearchHelperBase(CompileContext _context) { + context = _context; + + scanRequest = 0; + scanned = new THashSet<String>(); + scannedText = new THashSet<String>(); + scannedComments = new THashSet<String>(); + scannedLiterals = new THashSet<String>(); + } + + public void clear() { + scanned.clear(); + scannedComments.clear(); + scannedLiterals.clear(); + } + + public boolean addWordToSearchInCode(final String refname) { + if (!scanned.contains(refname)) { + doAddSearchWordInCode(refname); + scanned.add( refname ); + return true; + } + + return false; + } + + public boolean addWordToSearchInText(final String refname) { + if (!scannedText.contains(refname)) { + doAddSearchWordInText(refname); + scannedText.add(refname); + return true; + } + return false; + } + + protected abstract void doAddSearchWordInCode(final String refname); + protected abstract void doAddSearchWordInText(final String refname); + + protected abstract void doAddSearchWordInComments(final String refname); + protected abstract void doAddSearchWordInLiterals(final String refname); + + public void endTransaction() { + scanRequest++; + } + + public boolean addWordToSearchInComments(final String refname) { + if (!scannedComments.contains(refname)) { + doAddSearchWordInComments(refname); + + scannedComments.add( refname ); + return true; + } + return false; + } + + public boolean addWordToSearchInLiterals(final String refname) { + if (!scannedLiterals.contains(refname)) { + doAddSearchWordInLiterals(refname); + scannedLiterals.add( refname ); + return true; + } + return false; + } + + public boolean isScannedSomething() { + return scanned.size() > 0 || scannedComments.size() > 0 || scannedLiterals.size() > 0; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java new file mode 100644 index 000000000000..e290ebb70793 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/PatternCompiler.java @@ -0,0 +1,512 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiErrorElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiRecursiveElementWalkingVisitor; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.tree.LeafElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; +import com.intellij.structuralsearch.impl.matcher.MatchPredicateProvider; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.predicates.*; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.util.IncorrectOperationException; +import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Compiles the handlers for usability + */ +public class PatternCompiler { + private static CompileContext lastTestingContext; + + public static void transformOldPattern(MatchOptions options) { + StringToConstraintsTransformer.transformOldPattern(options); + } + + public static CompiledPattern compilePattern(final Project project, final MatchOptions options) throws MalformedPatternException, + UnsupportedOperationException { + FileType fileType = options.getFileType(); + assert fileType instanceof LanguageFileType; + Language language = ((LanguageFileType)fileType).getLanguage(); + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByLanguage(language); + assert profile != null; + CompiledPattern result = profile.createCompiledPattern(); + + final String[] prefixes = result.getTypedVarPrefixes(); + assert prefixes.length > 0; + + final CompileContext context = new CompileContext(); + if (ApplicationManager.getApplication().isUnitTestMode()) lastTestingContext = context; + + /*CompiledPattern result = options.getFileType() == StdFileTypes.JAVA ? + new JavaCompiledPattern() : + new XmlCompiledPattern();*/ + + try { + context.init(result, options, project, options.getScope() instanceof GlobalSearchScope); + + List<PsiElement> elements = compileByAllPrefixes(project, options, result, context, prefixes); + + context.getPattern().setNodes(elements); + + if (context.getSearchHelper().doOptimizing() && context.getSearchHelper().isScannedSomething()) { + final Set<PsiFile> set = context.getSearchHelper().getFilesSetToScan(); + final List<PsiFile> filesToScan = new ArrayList<PsiFile>(set.size()); + final GlobalSearchScope scope = (GlobalSearchScope)options.getScope(); + + for (final PsiFile file : set) { + if (!scope.contains(file.getVirtualFile())) { + continue; + } + + if (file instanceof PsiFileImpl) { + ((PsiFileImpl)file).clearCaches(); + } + filesToScan.add(file); + } + + if (filesToScan.size() == 0) { + throw new MalformedPatternException(SSRBundle.message("ssr.will.not.find.anything")); + } + result.setScope( + new LocalSearchScope(PsiUtilCore.toPsiElementArray(filesToScan)) + ); + } + } finally { + context.clear(); + } + + return result; + } + + public static String getLastFindPlan() { + return ((TestModeOptimizingSearchHelper)lastTestingContext.getSearchHelper()).getSearchPlan(); + } + + @NotNull + private static List<PsiElement> compileByAllPrefixes(Project project, + MatchOptions options, + CompiledPattern pattern, + CompileContext context, + String[] applicablePrefixes) { + if (applicablePrefixes.length == 0) { + return Collections.emptyList(); + } + + List<PsiElement> elements = doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiFile file = elements.get(0).getContainingFile(); + if (file == null) { + return elements; + } + + final PsiElement last = elements.get(elements.size() - 1); + final Pattern[] patterns = new Pattern[applicablePrefixes.length]; + + for (int i = 0; i < applicablePrefixes.length; i++) { + String s = StructuralSearchUtil.shieldSpecialChars(applicablePrefixes[i]); + patterns[i] = Pattern.compile(s + "\\w+\\b"); + } + + final int[] varEndOffsets = findAllTypedVarOffsets(file, patterns); + + final int patternEndOffset = last.getTextRange().getEndOffset(); + if (elements.size() == 0 || + checkErrorElements(file, patternEndOffset, patternEndOffset, varEndOffsets, true) != Boolean.TRUE) { + return elements; + } + + final int varCount = varEndOffsets.length; + final String[] prefixSequence = new String[varCount]; + + for (int i = 0; i < varCount; i++) { + prefixSequence[i] = applicablePrefixes[0]; + } + + final List<PsiElement> finalElements = + compileByPrefixes(project, options, pattern, context, applicablePrefixes, patterns, prefixSequence, 0); + return finalElements != null + ? finalElements + : doCompile(project, options, pattern, new ConstantPrefixProvider(applicablePrefixes[0]), context); + } + + @Nullable + private static List<PsiElement> compileByPrefixes(Project project, + MatchOptions options, + CompiledPattern pattern, + CompileContext context, + String[] applicablePrefixes, + Pattern[] substitutionPatterns, + String[] prefixSequence, + int index) { + if (index >= prefixSequence.length) { + final List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiElement parent = elements.get(0).getParent(); + final PsiElement last = elements.get(elements.size() - 1); + final int[] varEndOffsets = findAllTypedVarOffsets(parent.getContainingFile(), substitutionPatterns); + final int patternEndOffset = last.getTextRange().getEndOffset(); + return checkErrorElements(parent, patternEndOffset, patternEndOffset, varEndOffsets, false) != Boolean.TRUE + ? elements + : null; + } + + String[] alternativeVariant = null; + + for (String applicablePrefix : applicablePrefixes) { + prefixSequence[index] = applicablePrefix; + + List<PsiElement> elements = doCompile(project, options, pattern, new ArrayPrefixProvider(prefixSequence), context); + if (elements.isEmpty()) { + return elements; + } + + final PsiFile file = elements.get(0).getContainingFile(); + if (file == null) { + return elements; + } + + final int[] varEndOffsets = findAllTypedVarOffsets(file, substitutionPatterns); + final int offset = varEndOffsets[index]; + + final int patternEndOffset = elements.get(elements.size() - 1).getTextRange().getEndOffset(); + final Boolean result = checkErrorElements(file, offset, patternEndOffset, varEndOffsets, false); + + if (result == Boolean.TRUE) { + continue; + } + + if (result == Boolean.FALSE || (result == null && alternativeVariant == null)) { + final List<PsiElement> finalElements = + compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, prefixSequence, index + 1); + if (finalElements != null) { + if (result == Boolean.FALSE) { + return finalElements; + } + alternativeVariant = new String[prefixSequence.length]; + System.arraycopy(prefixSequence, 0, alternativeVariant, 0, prefixSequence.length); + } + } + } + + return alternativeVariant != null ? + compileByPrefixes(project, options, pattern, context, applicablePrefixes, substitutionPatterns, alternativeVariant, index + 1) : + null; + } + + @NotNull + private static int[] findAllTypedVarOffsets(final PsiFile file, final Pattern[] substitutionPatterns) { + final TIntHashSet result = new TIntHashSet(); + + file.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (element instanceof LeafElement) { + final String text = element.getText(); + + for (Pattern pattern : substitutionPatterns) { + final Matcher matcher = pattern.matcher(text); + + while (matcher.find()) { + result.add(element.getTextRange().getStartOffset() + matcher.end()); + } + } + } + } + }); + + final int[] resultArray = result.toArray(); + Arrays.sort(resultArray); + return resultArray; + } + + + /** + * False: there are no error elements before offset, except patternEndOffset + * Null: there are only error elements located exactly after template variables or at the end of the pattern + * True: otherwise + */ + @Nullable + private static Boolean checkErrorElements(PsiElement element, + final int offset, + final int patternEndOffset, + final int[] varEndOffsets, + final boolean strict) { + final TIntArrayList errorOffsets = new TIntArrayList(); + final boolean[] containsErrorTail = {false}; + final TIntHashSet varEndOffsetsSet = new TIntHashSet(varEndOffsets); + + element.accept(new PsiRecursiveElementWalkingVisitor() { + @Override + public void visitElement(PsiElement element) { + super.visitElement(element); + + if (!(element instanceof PsiErrorElement)) { + return; + } + + final int startOffset = element.getTextRange().getStartOffset(); + + if ((strict || !varEndOffsetsSet.contains(startOffset)) && startOffset != patternEndOffset) { + errorOffsets.add(startOffset); + } + + if (startOffset == offset) { + containsErrorTail[0] = true; + } + } + }); + + for (int i = 0; i < errorOffsets.size(); i++) { + final int errorOffset = errorOffsets.get(i); + if (errorOffset <= offset) { + return true; + } + } + return containsErrorTail[0] ? null : false; + } + + private interface PrefixProvider { + String getPrefix(int varIndex); + } + + private static class ConstantPrefixProvider implements PrefixProvider { + private final String myPrefix; + + private ConstantPrefixProvider(String prefix) { + myPrefix = prefix; + } + + @Override + public String getPrefix(int varIndex) { + return myPrefix; + } + } + + private static class ArrayPrefixProvider implements PrefixProvider { + private final String[] myPrefixes; + + private ArrayPrefixProvider(String[] prefixes) { + myPrefixes = prefixes; + } + + @Override + public String getPrefix(int varIndex) { + try { + return myPrefixes[varIndex]; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + } + + private static List<PsiElement> doCompile(Project project, + MatchOptions options, + CompiledPattern result, + PrefixProvider prefixProvider, + CompileContext context) { + result.clearHandlers(); + context.init(result, options, project, options.getScope() instanceof GlobalSearchScope); + + final StringBuilder buf = new StringBuilder(); + + Template template = TemplateManager.getInstance(project).createTemplate("","",options.getSearchPattern()); + + int segmentsCount = template.getSegmentsCount(); + String text = template.getTemplateText(); + buf.setLength(0); + int prevOffset = 0; + + for(int i=0;i<segmentsCount;++i) { + final int offset = template.getSegmentOffset(i); + final String name = template.getSegmentName(i); + + final String prefix = prefixProvider.getPrefix(i); + if (prefix == null) { + throw new MalformedPatternException(); + } + + buf.append(text.substring(prevOffset,offset)); + buf.append(prefix); + buf.append(name); + + MatchVariableConstraint constraint = options.getVariableConstraint(name); + if (constraint==null) { + // we do not edited the constraints + constraint = new MatchVariableConstraint(); + constraint.setName( name ); + options.addVariableConstraint(constraint); + } + + SubstitutionHandler handler = result.createSubstitutionHandler( + name, + prefix + name, + constraint.isPartOfSearchResults(), + constraint.getMinCount(), + constraint.getMaxCount(), + constraint.isGreedy() + ); + + if(constraint.isWithinHierarchy()) { + handler.setSubtype(true); + } + + if(constraint.isStrictlyWithinHierarchy()) { + handler.setStrictSubtype(true); + } + + MatchPredicate predicate; + + if (!StringUtil.isEmptyOrSpaces(constraint.getRegExp())) { + predicate = new RegExpPredicate( + constraint.getRegExp(), + options.isCaseSensitiveMatch(), + name, + constraint.isWholeWordsOnly(), + constraint.isPartOfSearchResults() + ); + if (constraint.isInvertRegExp()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + if (constraint.isReference()) { + predicate = new ReferencePredicate( constraint.getNameOfReferenceVar() ); + + if (constraint.isInvertReference()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + Set<MatchPredicate> predicates = new LinkedHashSet<MatchPredicate>(); + for (MatchPredicateProvider matchPredicateProvider : Extensions.getExtensions(MatchPredicateProvider.EP_NAME)) { + matchPredicateProvider.collectPredicates(constraint, name, options, predicates); + } + for (MatchPredicate matchPredicate : predicates) { + addPredicate(handler, matchPredicate); + } + + addScriptConstraint(name, constraint, handler); + + if (!StringUtil.isEmptyOrSpaces(constraint.getContainsConstraint())) { + predicate = new ContainsPredicate(name, constraint.getContainsConstraint()); + if (constraint.isInvertContainsConstraint()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) { + assert false; + } + + prevOffset = offset; + } + + MatchVariableConstraint constraint = options.getVariableConstraint(Configuration.CONTEXT_VAR_NAME); + if (constraint != null) { + SubstitutionHandler handler = result.createSubstitutionHandler( + Configuration.CONTEXT_VAR_NAME, + Configuration.CONTEXT_VAR_NAME, + constraint.isPartOfSearchResults(), + constraint.getMinCount(), + constraint.getMaxCount(), + constraint.isGreedy() + ); + + if (!StringUtil.isEmptyOrSpaces(constraint.getWithinConstraint())) { + MatchPredicate predicate = new WithinPredicate(Configuration.CONTEXT_VAR_NAME, constraint.getWithinConstraint(), project); + if (constraint.isInvertWithinConstraint()) { + predicate = new NotPredicate(predicate); + } + addPredicate(handler,predicate); + } + + addScriptConstraint(Configuration.CONTEXT_VAR_NAME, constraint, handler); + } + + buf.append(text.substring(prevOffset,text.length())); + + PsiElement[] matchStatements; + + try { + final String pattern = buf.toString(); + matchStatements = MatcherImplUtil.createTreeFromText(pattern, PatternTreeContext.Block, options.getFileType(), + options.getDialect(), options.getPatternContext(), project, false); + if (matchStatements.length==0) throw new MalformedPatternException(pattern); + } catch (IncorrectOperationException e) { + throw new MalformedPatternException(e.getMessage()); + } + + NodeFilter filter = LexicalNodesFilter.getInstance(); + + GlobalCompilingVisitor compilingVisitor = new GlobalCompilingVisitor(); + compilingVisitor.compile(matchStatements,context); + ArrayList<PsiElement> elements = new ArrayList<PsiElement>(); + + for (PsiElement matchStatement : matchStatements) { + if (!filter.accepts(matchStatement)) { + elements.add(matchStatement); + } + } + + new DeleteNodesAction(compilingVisitor.getLexicalNodes()).run(); + return elements; + } + + private static void addScriptConstraint(String name, MatchVariableConstraint constraint, SubstitutionHandler handler) { + MatchPredicate predicate; + if (constraint.getScriptCodeConstraint()!= null && constraint.getScriptCodeConstraint().length() > 2) { + final String script = StringUtil.stripQuotesAroundValue(constraint.getScriptCodeConstraint()); + final String s = ScriptSupport.checkValidScript(script); + if (s != null) throw new MalformedPatternException("Script constraint for " + constraint.getName() + " has problem "+s); + predicate = new ScriptPredicate(name, script); + addPredicate(handler,predicate); + } + } + + static void addPredicate(SubstitutionHandler handler, MatchPredicate predicate) { + if (handler.getPredicate()==null) { + handler.setPredicate(predicate); + } else { + handler.setPredicate(new BinaryPredicate(handler.getPredicate(), predicate, false)); + } + } + +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java new file mode 100644 index 000000000000..0d684503a14d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/StringToConstraintsTransformer.java @@ -0,0 +1,436 @@ +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(); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java new file mode 100644 index 000000000000..e341b57da8ce --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/TestModeOptimizingSearchHelper.java @@ -0,0 +1,70 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiFile; + +import java.util.Collections; +import java.util.Set; + +/** + * @author Maxim.Mossienko + */ +public class TestModeOptimizingSearchHelper extends OptimizingSearchHelperBase { + private static String lastString; + private final StringBuilder builder = new StringBuilder(); + private int lastLength; + + TestModeOptimizingSearchHelper(CompileContext _context) { + super(_context); + } + + public boolean doOptimizing() { + return true; + } + + public void clear() { + lastString = builder.toString(); + builder.setLength(0); + lastLength = 0; + } + + protected void doAddSearchWordInCode(final String refname) { + append(refname, "reserved in code:"); + } + + @Override + protected void doAddSearchWordInText(String refname) { + append(refname, "in text:"); + } + + private void append(final String refname, final String str) { + if (builder.length() == lastLength) builder.append("["); + else builder.append("|"); + builder.append(str).append(refname); + } + + protected void doAddSearchWordInComments(final String refname) { + append(refname, "in comments:"); + } + + protected void doAddSearchWordInLiterals(final String refname) { + append(refname, "in literals:"); + } + + public void endTransaction() { + super.endTransaction(); + builder.append("]"); + lastLength = builder.length(); + } + + public boolean isScannedSomething() { + return false; + } + + public Set<PsiFile> getFilesSetToScan() { + return Collections.emptySet(); + } + + public String getSearchPlan() { + return lastString; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java new file mode 100644 index 000000000000..99917ad23240 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/compiler/XmlCompilingVisitor.java @@ -0,0 +1,53 @@ +package com.intellij.structuralsearch.impl.matcher.compiler; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlRecursiveElementVisitor; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; +import com.intellij.psi.xml.XmlToken; +import com.intellij.structuralsearch.impl.matcher.filters.TagValueFilter; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.TopLevelMatchingHandler; +import com.intellij.structuralsearch.impl.matcher.handlers.XmlTextHandler; +import com.intellij.structuralsearch.impl.matcher.strategies.XmlMatchingStrategy; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlCompilingVisitor extends XmlRecursiveElementVisitor { + private final GlobalCompilingVisitor myCompilingVisitor; + + public XmlCompilingVisitor(GlobalCompilingVisitor compilingVisitor) { + this.myCompilingVisitor = compilingVisitor; + } + + @Override public void visitElement(PsiElement element) { + myCompilingVisitor.handle(element); + super.visitElement(element); + } + + @Override public void visitXmlToken(XmlToken token) { + super.visitXmlToken(token); + + if (token.getParent() instanceof XmlText && myCompilingVisitor.getContext().getPattern().isRealTypedVar(token)) { + final MatchingHandler handler = myCompilingVisitor.getContext().getPattern().getHandler(token); + handler.setFilter(TagValueFilter.getInstance()); + + final XmlTextHandler parentHandler = new XmlTextHandler(); + myCompilingVisitor.getContext().getPattern().setHandler(token.getParent(), parentHandler); + parentHandler.setFilter(TagValueFilter.getInstance()); + } + } + + @Override public void visitXmlTag(XmlTag xmlTag) { + myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() + 1); + super.visitXmlTag(xmlTag); + myCompilingVisitor.setCodeBlockLevel(myCompilingVisitor.getCodeBlockLevel() - 1); + + if (myCompilingVisitor.getCodeBlockLevel() == 1) { + myCompilingVisitor.getContext().getPattern().setStrategy(XmlMatchingStrategy.getInstance()); + myCompilingVisitor.getContext().getPattern() + .setHandler(xmlTag, new TopLevelMatchingHandler(myCompilingVisitor.getContext().getPattern().getHandler(xmlTag))); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java new file mode 100644 index 000000000000..db962ed8edf1 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/CompositeFilter.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 28.12.2003 + * Time: 0:13:19 + * To change this template use Options | File Templates. + */ +public class CompositeFilter implements NodeFilter { + private final NodeFilter first; + private final NodeFilter second; + protected boolean result; + + public boolean accepts(PsiElement element) { + return first.accepts(element) || + second.accepts(element); + } + + public CompositeFilter(NodeFilter _first, NodeFilter _second) { + first = _first; + second = _second; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java new file mode 100644 index 000000000000..267f37699945 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/DefaultFilter.java @@ -0,0 +1,16 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafElement; + +/** + * Default searching filter + */ +public class DefaultFilter { + public static boolean accepts(PsiElement element, PsiElement element2) { + if (element instanceof LeafElement && element2 instanceof LeafElement) { + return ((LeafElement)element).getElementType() == ((LeafElement)element2).getElementType(); + } + return element.getClass()==element2.getClass(); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java new file mode 100644 index 000000000000..3cef402b09d3 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/LexicalNodesFilter.java @@ -0,0 +1,51 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; + +/** + * Filter for lexical nodes + */ +public final class LexicalNodesFilter implements NodeFilter { + private boolean careKeyWords; + private boolean result; + + private LexicalNodesFilter() {} + + public static NodeFilter getInstance() { + return NodeFilterHolder.instance; + } + + public boolean getResult() { + return result; + } + + public void setResult(boolean result) { + this.result = result; + } + + private static class NodeFilterHolder { + private static final NodeFilter instance = new LexicalNodesFilter(); + } + + public boolean isCareKeyWords() { + return careKeyWords; + } + + public void setCareKeyWords(boolean careKeyWords) { + this.careKeyWords = careKeyWords; + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile != null) { + element.accept(profile.getLexicalNodesFilter(this)); + } + } + return result; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java new file mode 100644 index 000000000000..97ed9627e3fc --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/TagValueFilter.java @@ -0,0 +1,43 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlTag; +import com.intellij.psi.xml.XmlText; + +/** + * Created by IntelliJ IDEA. + * User: maxim.mossienko + * Date: Oct 12, 2005 + * Time: 4:44:19 PM + * To change this template use File | Settings | File Templates. + */ +public class TagValueFilter extends XmlElementVisitor implements NodeFilter { + private boolean result; + + @Override public void visitXmlText(XmlText text) { + result = true; + } + + @Override public void visitXmlTag(XmlTag tag) { + result = true; + } + + private static class NodeFilterHolder { + private static final NodeFilter instance = new TagValueFilter(); + } + + public static NodeFilter getInstance() { + return NodeFilterHolder.instance; + } + + private TagValueFilter() { + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) element.accept(this); + return result; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java new file mode 100644 index 000000000000..89c295ce1d80 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/filters/XmlLexicalNodesFilter.java @@ -0,0 +1,19 @@ +package com.intellij.structuralsearch.impl.matcher.filters; + +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlText; + +/** +* @author Eugene.Kudelevsky +*/ +public class XmlLexicalNodesFilter extends XmlElementVisitor { + private final LexicalNodesFilter myLexicalNodesFilter; + + public XmlLexicalNodesFilter(LexicalNodesFilter lexicalNodesFilter) { + this.myLexicalNodesFilter = lexicalNodesFilter; + } + + @Override public void visitXmlText(XmlText text) { + myLexicalNodesFilter.setResult(true); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java new file mode 100644 index 000000000000..c95a85623753 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/DelegatingHandler.java @@ -0,0 +1,8 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +/** + * @author Eugene.Kudelevsky + */ +public interface DelegatingHandler { + MatchingHandler getDelegate(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java new file mode 100644 index 000000000000..34941cfa0257 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LightTopLevelMatchingHandler.java @@ -0,0 +1,46 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.dupLocator.iterators.NodeIterator; +import org.jetbrains.annotations.NotNull; + +public final class LightTopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler { + private final MatchingHandler myDelegate; + + public LightTopLevelMatchingHandler(@NotNull MatchingHandler delegate) { + myDelegate = delegate; + } + + public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) { + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return myDelegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return myDelegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(final PsiElement patternNode, + final PsiElement matchedNode, final int start, final int end, final MatchContext context) { + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return true; + } + + @Override + public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) { + return myDelegate.shouldAdvanceTheMatchFor(patternElement, matchedElement); + } + + public MatchingHandler getDelegate() { + return myDelegate; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java new file mode 100644 index 000000000000..78967106755f --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/LiteralWithSubstitutionHandler.java @@ -0,0 +1,49 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Jun 30, 2004 + * Time: 5:07:33 PM + * To change this template use File | Settings | File Templates. + */ +public class LiteralWithSubstitutionHandler extends MatchingHandler { + private final String matchExpression; + private Matcher matcher; + private final List<SubstitutionHandler> handlers; + + public LiteralWithSubstitutionHandler(String _matchedExpression,List<SubstitutionHandler> _handlers) { + matchExpression = _matchedExpression; + handlers = _handlers; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + final String text = RegExpPredicate.getMeaningfulText(matchedNode); + int offset = matchedNode.getText().indexOf(text); + if (matcher==null) { + matcher = Pattern.compile(matchExpression).matcher(text); + } else { + matcher.reset(text); + } + + while (matcher.find()) { + for (int i = 0; i < handlers.size(); ++i) { + SubstitutionHandler handler = handlers.get(i); + + if (!handler.handle(matchedNode,offset + matcher.start(i+1), offset + matcher.end(i+1),context)) { + return false; + } + } + return true; + } + return false; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java new file mode 100644 index 000000000000..cba96681ab71 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchPredicate.java @@ -0,0 +1,27 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public abstract class MatchPredicate { + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) { + return match(patternNode,matchedNode,context); + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public abstract boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java new file mode 100644 index 000000000000..c53b08412b6b --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/MatchingHandler.java @@ -0,0 +1,271 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.*; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.impl.matcher.filters.DefaultFilter; +import com.intellij.structuralsearch.impl.matcher.predicates.BinaryPredicate; +import com.intellij.structuralsearch.impl.matcher.predicates.NotPredicate; +import com.intellij.structuralsearch.impl.matcher.predicates.RegExpPredicate; +import com.intellij.structuralsearch.impl.matcher.strategies.MatchingStrategy; + +import java.util.HashSet; +import java.util.Set; + +/** + * Root of handlers for pattern node matching. Handles simplest type of the match. + */ +public abstract class MatchingHandler extends MatchPredicate { + protected NodeFilter filter; + private PsiElement pinnedElement; + + public void setFilter(NodeFilter filter) { + this.filter = filter; + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successful and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, int start, int end, MatchContext context) { + return match(patternNode,matchedNode,context); + } + + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successful and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) { + if (patternNode == null) { + return matchedNode == null; + } + + return canMatch(patternNode, matchedNode); + } + + public boolean canMatch(final PsiElement patternNode, final PsiElement matchedNode) { + if (filter!=null) { + return filter.accepts(matchedNode); + } else { + return DefaultFilter.accepts(patternNode, matchedNode); + } + } + + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + PsiElement patternElement; + MatchingHandler handler; + MatchingStrategy strategy = context.getPattern().getStrategy(); + + skipIfNecessary(nodes, nodes2, strategy); + skipIfNecessary(nodes2, nodes, strategy); + + if (nodes2.hasNext() && + (handler = context.getPattern().getHandler(nodes.current())).match(patternElement = nodes.current(),nodes2.current(),context)) { + + nodes.advance(); + + if (shouldAdvanceTheMatchFor(patternElement, nodes2.current())) { + nodes2.advance(); + skipIfNecessary(nodes, nodes2, strategy); + } + skipIfNecessary(nodes2, nodes, strategy); + + if (nodes.hasNext()) { + final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current()); + + if (nextHandler.matchSequentially(nodes,nodes2,context)) { + // match was found! + return true; + } else { + // rewind, we was not able to match descendants + nodes.rewind(); + nodes2.rewind(); + } + } else { + // match was found + return handler.isMatchSequentiallySucceeded(nodes2); + } + } + return false; + } + + private static void skipIfNecessary(NodeIterator nodes, NodeIterator nodes2, MatchingStrategy strategy) { + while (strategy.shouldSkip(nodes2.current(), nodes.current())) { + nodes2.advance(); + } + } + + protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return !nodes2.hasNext(); + } + + private static MatchPredicate findRegExpPredicate(MatchPredicate start) { + if (start==null) return null; + if (start instanceof RegExpPredicate) return start; + + if(start instanceof BinaryPredicate) { + BinaryPredicate binary = (BinaryPredicate)start; + final MatchPredicate result = findRegExpPredicate(binary.getFirst()); + if (result!=null) return result; + + return findRegExpPredicate(binary.getSecond()); + } else if (start instanceof NotPredicate) { + return null; + } + return null; + } + + public static RegExpPredicate getSimpleRegExpPredicate(SubstitutionHandler handler) { + if (handler == null) return null; + return (RegExpPredicate)findRegExpPredicate(handler.getPredicate()); + } + + static class ClearStateVisitor extends PsiRecursiveElementWalkingVisitor { + private CompiledPattern pattern; + + ClearStateVisitor() { + super(true); + } + + @Override public void visitElement(PsiElement element) { + // We do not reset certain handlers because they are also bound to higher level nodes + // e.g. Identifier handler in name is also bound to PsiMethod + if (pattern.isToResetHandler(element)) { + MatchingHandler handler = pattern.getHandlerSimple(element); + if (handler instanceof SubstitutionHandler) { + handler.reset(); + } + } + super.visitElement(element); + } + + synchronized void clearState(CompiledPattern _pattern, PsiElement el) { + pattern = _pattern; + el.acceptChildren(this); + pattern = null; + } + } + + protected static ClearStateVisitor clearingVisitor = new ClearStateVisitor(); + + public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) { + final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null; + context.setResult(null); + + try { + + if (patternNodes.hasNext() && !matchedNodes.hasNext()) { + return validateSatisfactionOfHandlers(patternNodes, context); + } + + Set<PsiElement> matchedElements = null; + + for(; patternNodes.hasNext(); patternNodes.advance()) { + final PsiElement patternNode = patternNodes.current(); + final CompiledPattern pattern = context.getPattern(); + final MatchingHandler handler = pattern.getHandler(patternNode); + + final PsiElement startMatching = matchedNodes.current(); + do { + final PsiElement element = handler.getPinnedNode(null); + final PsiElement matchedNode = element != null ? element : matchedNodes.current(); + + if (element == null) matchedNodes.advance(); + if (!matchedNodes.hasNext()) matchedNodes.reset(); + + if (matchedElements == null || !matchedElements.contains(matchedNode)) { + + if (handler.match(patternNode, matchedNode, context)) { + if (matchedElements == null) matchedElements = new HashSet<PsiElement>(); + matchedElements.add(matchedNode); + if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) { + break; + } + } else if (element != null) { + return false; + } + + // clear state of dependent objects + clearingVisitor.clearState(pattern, patternNode); + } + + // passed of elements and does not found the match + if (startMatching == matchedNodes.current()) { + final boolean result = validateSatisfactionOfHandlers(patternNodes,context); + if (result && context.getMatchedElementsListener() != null) { + context.getMatchedElementsListener().matchedElements(matchedElements); + } + return result; + } + } while(true); + + if (!handler.shouldAdvanceThePatternFor(patternNode, null)) { + patternNodes.rewind(); + } + } + + final boolean result = validateSatisfactionOfHandlers(patternNodes, context); + if (result && context.getMatchedElementsListener() != null) { + context.getMatchedElementsListener().matchedElements(matchedElements); + } + return result; + } finally { + if (saveResult!=null) { + if (context.hasResult()) { + saveResult.getMatches().addAll(context.getResult().getMatches()); + } + context.setResult(saveResult); + } + } + } + + protected static boolean validateSatisfactionOfHandlers(NodeIterator nodes, MatchContext context) { + while(nodes.hasNext()) { + final PsiElement element = nodes.current(); + final MatchingHandler handler = context.getPattern().getHandler( element ); + + if (handler instanceof SubstitutionHandler) { + if (!((SubstitutionHandler)handler).validate( + context, StructuralSearchUtil.getProfileByPsiElement(element).getElementContextByPsi(element))) { + return false; + } + } else { + return false; + } + nodes.advance(); + } + return true; + } + + public NodeFilter getFilter() { + return filter; + } + + public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public void reset() { + //pinnedElement = null; + } + + public PsiElement getPinnedNode(PsiElement context) { + return pinnedElement; + } + + public void setPinnedElement(final PsiElement pinnedElement) { + this.pinnedElement = pinnedElement; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java new file mode 100644 index 000000000000..3dffe792e308 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SimpleHandler.java @@ -0,0 +1,20 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class SimpleHandler extends MatchingHandler { + /** + * Matches given handler node against given value. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement patternNode,PsiElement matchedNode, MatchContext context) { + if (!super.match(patternNode,matchedNode,context)) return false; + return context.getMatcher().match(patternNode,matchedNode); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java new file mode 100644 index 000000000000..389c4a7916c2 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SkippingHandler.java @@ -0,0 +1,108 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.equivalence.EquivalenceDescriptor; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.DuplocatorUtil; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Eugene.Kudelevsky + */ +public class SkippingHandler extends MatchingHandler implements DelegatingHandler { + + private final MatchingHandler myDelegate; + + public SkippingHandler(@NotNull MatchingHandler delegate) { + myDelegate = delegate; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, final MatchContext matchContext) { + if (patternNode == null || matchedNode == null || matchedNode.getClass() == patternNode.getClass()) { + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + /*if (patternNode != null && matchedNode != null && patternNode.getClass() == matchedNode.getClass()) { + //return myDelegate.match(patternNode, matchedNode, matchContext); + }*/ + PsiElement newPatternNode = skipNodeIfNeccessary(patternNode); + matchedNode = skipNodeIfNeccessary(matchedNode); + + if (newPatternNode != patternNode) { + return matchContext.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, matchContext); + } + + return myDelegate.match(patternNode, matchedNode, matchContext); + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return myDelegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return myDelegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(PsiElement patternNode, + PsiElement matchedNode, + final int start, + final int end, + final MatchContext context) { + if (patternNode == null || matchedNode == null || patternNode.getClass() == matchedNode.getClass()) { + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + PsiElement newPatternNode = skipNodeIfNeccessary(patternNode); + matchedNode = skipNodeIfNeccessary(matchedNode); + + if (newPatternNode != patternNode) { + return context.getPattern().getHandler(newPatternNode).match(newPatternNode, matchedNode, start, end, context); + } + + return myDelegate.match(patternNode, matchedNode, start, end, context); + } + + protected boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return myDelegate.isMatchSequentiallySucceeded(nodes2); + } + + @Override + public boolean shouldAdvanceTheMatchFor(PsiElement patternElement, PsiElement matchedElement) { + return true; + } + + public MatchingHandler getDelegate() { + return myDelegate; + } + + @Nullable + public static PsiElement getOnlyNonWhitespaceChild(PsiElement element) { + PsiElement onlyChild = null; + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + if (DuplocatorUtil.isIgnoredNode(element) || child.getTextLength() == 0) { + continue; + } + if (onlyChild != null) { + return null; + } + onlyChild = child; + } + return onlyChild; + } + + @Nullable + public static PsiElement skipNodeIfNeccessary(PsiElement element) { + return skipNodeIfNeccessary(element, null, null); + } + + @Nullable + public static PsiElement skipNodeIfNeccessary(PsiElement element, EquivalenceDescriptor descriptor, NodeFilter filter) { + return DuplocatorUtil.skipNodeIfNeccessary(element, descriptor, filter != null ? filter : LexicalNodesFilter.getInstance()); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java new file mode 100644 index 000000000000..0c84e3bb2436 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SubstitutionHandler.java @@ -0,0 +1,557 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.FilteringNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.CompiledPattern; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Matching handler that manages substitutions matching + */ +public class SubstitutionHandler extends MatchingHandler { + private final String name; + private final int maxOccurs; + private final int minOccurs; + private final boolean greedy; + private boolean target; + private MatchPredicate predicate; + private MatchingHandler matchHandler; + private boolean subtype; + private boolean strictSubtype; + // matchedOccurs + 1 = number of item being matched + private int matchedOccurs; + private int totalMatchedOccurs = -1; + private MatchResultImpl myNestedResult; + + private static final NodeFilter VARS_DELIM_FILTER = new NodeFilter() { + @Override + public boolean accepts(PsiElement element) { + if (element == null) { + return false; + } + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile == null) { + return false; + } + + return profile.canBeVarDelimeter(element); + } + }; + + public SubstitutionHandler(final String name, final boolean target, int minOccurs, + int maxOccurs, boolean greedy) { + this.name = name; + this.maxOccurs = maxOccurs; + this.minOccurs = minOccurs; + this.target = target; + this.greedy = greedy; + } + + public SubstitutionHandler(final SubstitutionHandler substitutionHandler) { + this(substitutionHandler.getName(),substitutionHandler.isTarget(), substitutionHandler.getMinOccurs(), + substitutionHandler.getMaxOccurs(), substitutionHandler.greedy); + } + + public boolean isSubtype() { + return subtype; + } + + public boolean isStrictSubtype() { + return strictSubtype; + } + + public void setStrictSubtype(boolean strictSubtype) { + this.strictSubtype = strictSubtype; + } + + public void setSubtype(boolean subtype) { + this.subtype = subtype; + } + + public void setPredicate(MatchPredicate handler) { + predicate = handler; + } + + // Matcher + + public MatchPredicate getPredicate() { + return predicate; + } + + private static boolean validateOneMatch(final PsiElement match, int start, int end, final MatchResultImpl result, final MatchContext matchContext) { + final boolean matchresult; + + if (match!=null) { + if (start==0 && end==-1 && result.getStart()==0 && result.getEnd()==-1) { + matchresult = matchContext.getMatcher().match(match,result.getMatchRef().getElement()); + } else { + matchresult = StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end).equals( + result.getMatchImage() + ); + } + } else { + matchresult = result.isMatchImageNull(); + } + + return matchresult; + } + + public boolean validate(final PsiElement match, int start, int end, MatchContext context) { + if (predicate!=null) { + if(!predicate.match(null,match,start,end,context)) return false; + } + + if (maxOccurs==0) { + totalMatchedOccurs++; + return false; + } + + MatchResultImpl result = context.getResult().findSon(name); + + if (result == null && context.getPreviousResult() != null) { + result = context.getPreviousResult().findSon(name); + } + + if (result!=null) { + if (minOccurs == 1 && maxOccurs == 1) { + // check if they are the same + return validateOneMatch(match, start, end, result,context); + } else if (maxOccurs > 1 && totalMatchedOccurs!=-1) { + final int size = result.getAllSons().size(); + if (matchedOccurs >= size) { + return false; + } + result = size == 0 ?result:(MatchResultImpl)result.getAllSons().get(matchedOccurs); + // check if they are the same + return validateOneMatch(match, start, end, result, context); + } + } + + return true; + } + + public boolean match(final PsiElement node, final PsiElement match, MatchContext context) { + if (!super.match(node,match,context)) return false; + + return matchHandler == null ? + context.getMatcher().match(node, match): + matchHandler.match(node,match,context); + } + + public boolean handle(final PsiElement match, MatchContext context) { + return handle(match,0,-1,context); + } + + public void addResult(PsiElement match,int start, int end,MatchContext context) { + if (totalMatchedOccurs == -1) { + final MatchResultImpl matchResult = context.getResult(); + final MatchResultImpl substitution = matchResult.findSon(name); + + if (substitution == null) { + matchResult.addSon( createMatch(match,start,end) ); + } else if (maxOccurs > 1) { + final MatchResultImpl result = createMatch(match,start,end); + + if (!substitution.isMultipleMatch()) { + // adding intermediate node to contain all multiple matches + MatchResultImpl sonresult = new MatchResultImpl( + substitution.getName(), + substitution.getMatchImage(), + substitution.getMatchRef(), + substitution.getStart(), + substitution.getEnd(), + target + ); + + sonresult.setParent(substitution); + substitution.setMatchRef( + new SmartPsiPointer(match == null ? null : match) + ); + + substitution.setMultipleMatch(true); + + if (substitution.isScopeMatch()) { + substitution.setScopeMatch(false); + sonresult.setScopeMatch(true); + for(MatchResult r:substitution.getAllSons()) sonresult.addSon((MatchResultImpl)r); + substitution.clearMatches(); + } + + substitution.addSon( sonresult); + } + + result.setParent(substitution); + substitution.addSon( result ); + } + } + } + + public boolean handle(final PsiElement match, int start, int end, MatchContext context) { + if (!validate(match,start,end,context)) { + myNestedResult = null; + + //if (maxOccurs==1 && minOccurs==1) { + // if (context.hasResult()) context.getResult().removeSon(name); + //} + // @todo we may fail fast the match by throwing an exception + + return false; + } + + if (!Configuration.CONTEXT_VAR_NAME.equals(name)) addResult(match, start, end, context); + + return true; + } + + private MatchResultImpl createMatch(final PsiElement match, int start, int end) { + final String image = match == null ? null : StructuralSearchUtil.getProfileByPsiElement(match).getText(match, start, end); + final SmartPsiPointer ref = new SmartPsiPointer(match); + + final MatchResultImpl result = myNestedResult == null ? new MatchResultImpl( + name, + image, + ref, + start, + end, + target + ) : myNestedResult; + + if (myNestedResult != null) { + myNestedResult.setName( name ); + myNestedResult.setMatchImage( image ); + myNestedResult.setMatchRef( ref ); + myNestedResult.setStart( start ); + myNestedResult.setEnd( end ); + myNestedResult.setTarget( target ); + myNestedResult = null; + } + + return result; + } + + boolean validate(MatchContext context, Class elementContext) { + MatchResult substitution = context.getResult().findSon(name); + + if (minOccurs >= 1 && + ( substitution == null || + StructuralSearchUtil.getProfileByFileType(context.getOptions().getFileType()).getElementContextByPsi(substitution.getMatchRef().getElement()) != elementContext + ) + ) { + return false; + } else if (maxOccurs <= 1 && + substitution!=null && substitution.hasSons() + ) { + return false; + } else if (maxOccurs==0 && totalMatchedOccurs!=-1) { + return false; + } + return true; + } + + public int getMinOccurs() { + return minOccurs; + } + + public int getMaxOccurs() { + return maxOccurs; + } + + private void removeLastResults(int numberOfResults, MatchContext context) { + if (numberOfResults == 0) return; + final MatchResultImpl substitution = context.getResult().findSon(name); + + if (substitution!=null) { + final List<PsiElement> matchedNodes = context.getMatchedNodes(); + + if (substitution.hasSons()) { + final List<MatchResult> sons = substitution.getMatches(); + + while(numberOfResults > 0) { + --numberOfResults; + final MatchResult matchResult = sons.remove(sons.size() - 1); + if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch()); + } + + if (sons.isEmpty()) { + context.getResult().removeSon(name); + } + } else { + final MatchResultImpl matchResult = context.getResult().removeSon(name); + if (matchedNodes != null) matchedNodes.remove(matchResult.getMatch()); + } + } + } + + public boolean matchInAnyOrder(NodeIterator patternNodes, NodeIterator matchedNodes, final MatchContext context) { + final MatchResultImpl saveResult = context.hasResult() ? context.getResult() : null; + context.setResult(null); + + try { + + if (patternNodes.hasNext() && !matchedNodes.hasNext()) { + return validateSatisfactionOfHandlers(patternNodes, context); + } + + Set<PsiElement> matchedElements = null; + + for(; patternNodes.hasNext(); patternNodes.advance()) { + int matchedOccurs = 0; + final PsiElement patternNode = patternNodes.current(); + final CompiledPattern pattern = context.getPattern(); + final MatchingHandler handler = pattern.getHandler(patternNode); + + final PsiElement startMatching = matchedNodes.current(); + do { + final PsiElement element = handler.getPinnedNode(null); + final PsiElement matchedNode = (element != null) ? element : matchedNodes.current(); + + if (element == null) matchedNodes.advance(); + if (!matchedNodes.hasNext()) matchedNodes.reset(); + + if (matchedOccurs <= maxOccurs && + (matchedElements == null || !matchedElements.contains(matchedNode))) { + + if (handler.match(patternNode, matchedNode, context)) { + ++matchedOccurs; + if (matchedElements == null) matchedElements = new HashSet<PsiElement>(); + matchedElements.add(matchedNode); + if (handler.shouldAdvanceThePatternFor(patternNode, matchedNode)) { + break; + } + } else if (element != null) { + return false; + } + + // clear state of dependent objects + clearingVisitor.clearState(pattern, patternNode); + } + + // passed of elements and does not found the match + if (startMatching == matchedNodes.current()) { + final boolean result = validateSatisfactionOfHandlers(patternNodes, context) && + matchedOccurs >= minOccurs && matchedOccurs <= maxOccurs; + if (result && context.getMatchedElementsListener() != null) { + context.getMatchedElementsListener().matchedElements(matchedElements); + } + return result; + } + } while(true); + + if (!handler.shouldAdvanceThePatternFor(patternNode, null)) { + patternNodes.rewind(); + } + } + + final boolean result = validateSatisfactionOfHandlers(patternNodes, context); + if (result && context.getMatchedElementsListener() != null) { + context.getMatchedElementsListener().matchedElements(matchedElements); + } + return result; + } finally { + if (saveResult!=null) { + if (context.hasResult()) { + saveResult.getMatches().addAll(context.getResult().getMatches()); + } + context.setResult(saveResult); + } + } + } + + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + return doMatchSequentially(nodes, nodes2, context); + } + + protected boolean doMatchSequentiallyBySimpleHandler(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + final boolean oldValue = context.shouldRecursivelyMatch(); + context.setShouldRecursivelyMatch(false); + final boolean result = super.matchSequentially(nodes, nodes2, context); + context.setShouldRecursivelyMatch(oldValue); + return result; + } + + protected boolean doMatchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + final int previousMatchedOccurs = matchedOccurs; + + FilteringNodeIterator fNodes2 = new FilteringNodeIterator(nodes2, VARS_DELIM_FILTER); + + try { + MatchingHandler handler = context.getPattern().getHandler(nodes.current()); + matchedOccurs = 0; + + boolean flag = false; + + while(fNodes2.hasNext() && matchedOccurs < minOccurs) { + if (handler.match(nodes.current(), nodes2.current(), context)) { + ++matchedOccurs; + } else { + break; + } + fNodes2.advance(); + flag = true; + } + + if (matchedOccurs!=minOccurs) { + // failed even for min occurs + removeLastResults(matchedOccurs, context); + fNodes2.rewind(matchedOccurs); + return false; + } + + if (greedy) { + // go greedily to maxOccurs + + while(fNodes2.hasNext() && matchedOccurs < maxOccurs) { + if (handler.match(nodes.current(), nodes2.current(), context)) { + ++matchedOccurs; + } else { + // no more matches could take! + break; + } + fNodes2.advance(); + flag = true; + } + + if (flag) { + fNodes2.rewind(); + nodes2.advance(); + } + + nodes.advance(); + + if (nodes.hasNext()) { + final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current()); + + while(matchedOccurs >= minOccurs) { + if (nextHandler.matchSequentially(nodes, nodes2, context)) { + totalMatchedOccurs = matchedOccurs; + // match found + return true; + } + + if (matchedOccurs > 0) { + nodes2.rewind(); + removeLastResults(1,context); + } + --matchedOccurs; + } + + if (matchedOccurs > 0) { + removeLastResults(matchedOccurs, context); + } + nodes.rewind(); + return false; + } else { + // match found + if (handler.isMatchSequentiallySucceeded(nodes2)) { + return checkSameOccurrencesConstraint(); + } + removeLastResults(matchedOccurs, context); + return false; + } + } else { + nodes.advance(); + + if (flag) { + fNodes2.rewind(); + nodes2.advance(); + } + + if (nodes.hasNext()) { + final MatchingHandler nextHandler = context.getPattern().getHandler(nodes.current()); + + flag = false; + + while(nodes2.hasNext() && matchedOccurs <= maxOccurs) { + if (nextHandler.matchSequentially(nodes, nodes2, context)) { + return checkSameOccurrencesConstraint(); + } + + if (flag) { + nodes2.rewind(); + fNodes2.advance(); + } + + if (handler.match(nodes.current(), nodes2.current(), context)) { + matchedOccurs++; + } else { + nodes.rewind(); + removeLastResults(matchedOccurs,context); + return false; + } + nodes2.advance(); + flag = true; + } + + nodes.rewind(); + removeLastResults(matchedOccurs,context); + return false; + } else { + return checkSameOccurrencesConstraint(); + } + } + } finally { + matchedOccurs = previousMatchedOccurs; + } + } + + private boolean checkSameOccurrencesConstraint() { + if (totalMatchedOccurs == -1) { + totalMatchedOccurs = matchedOccurs; + return true; + } + else { + return totalMatchedOccurs == matchedOccurs; + } + } + + public void setTarget(boolean target) { + this.target = target; + } + + public MatchingHandler getMatchHandler() { + return matchHandler; + } + + public void setMatchHandler(MatchingHandler matchHandler) { + this.matchHandler = matchHandler; + } + + public boolean isTarget() { + return target; + } + + public String getName() { + return name; + } + + public void reset() { + super.reset(); + totalMatchedOccurs = -1; + } + + public boolean shouldAdvanceThePatternFor(PsiElement patternElement, PsiElement matchedElement) { + if(maxOccurs > 1) return false; + return super.shouldAdvanceThePatternFor(patternElement,matchedElement); + } + + public void setNestedResult(final MatchResultImpl nestedResult) { + myNestedResult = nestedResult; + } + + public MatchResultImpl getNestedResult() { + return myNestedResult; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java new file mode 100644 index 000000000000..4d1f9a852940 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/SymbolHandler.java @@ -0,0 +1,21 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Search handler for symbol search + */ +public class SymbolHandler extends MatchingHandler { + private final SubstitutionHandler handler; + + public SymbolHandler(SubstitutionHandler handler) { + this.handler = handler; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + // there is no need to do filtering since this is delegate of Substituion handler + + return handler.handle(matchedNode,context); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java new file mode 100644 index 000000000000..6711bcb1fc7f --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TopLevelMatchingHandler.java @@ -0,0 +1,85 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.iterators.SiblingNodeIterator; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public final class TopLevelMatchingHandler extends MatchingHandler implements DelegatingHandler { + private final MatchingHandler delegate; + + public TopLevelMatchingHandler(@NotNull MatchingHandler _delegate) { + delegate = _delegate; + setFilter(_delegate.getFilter()); + } + + public boolean match(final PsiElement patternNode, final PsiElement matchedNode, final MatchContext matchContext) { + final boolean matched = delegate.match(patternNode, matchedNode, matchContext); + + if (matched) { + List<PsiElement> matchedNodes = matchContext.getMatchedNodes(); + if (matchedNodes == null) { + matchedNodes = new ArrayList<PsiElement>(); + matchContext.setMatchedNodes(matchedNodes); + } + + PsiElement elementToAdd = matchedNode; + + if (patternNode instanceof PsiComment && StructuralSearchUtil.isDocCommentOwner(matchedNode)) { + // psicomment and psidoccomment are placed inside the psimember next to them so + // simple topdown matching should do additional "dances" to cover this case. + elementToAdd = matchedNode.getFirstChild(); + assert elementToAdd instanceof PsiComment; + } + + matchedNodes.add(elementToAdd); + } + + if ((!matched || matchContext.getOptions().isRecursiveSearch()) && + matchContext.getPattern().getStrategy().continueMatching(matchedNode) && + matchContext.shouldRecursivelyMatch() + ) { + matchContext.getMatcher().matchContext( + new SsrFilteringNodeIterator( + new SiblingNodeIterator(matchedNode.getFirstChild()) + ) + ); + } + return matched; + } + + @Override + public boolean canMatch(PsiElement patternNode, PsiElement matchedNode) { + return delegate.canMatch(patternNode, matchedNode); + } + + @Override + public boolean matchSequentially(final NodeIterator nodes, final NodeIterator nodes2, final MatchContext context) { + return delegate.matchSequentially(nodes, nodes2, context); + } + + public boolean match(final PsiElement patternNode, + final PsiElement matchedNode, final int start, final int end, final MatchContext context) { + return match(patternNode, matchedNode, context); + } + + public boolean isMatchSequentiallySucceeded(final NodeIterator nodes2) { + return true; + } + + @Override + public boolean shouldAdvanceTheMatchFor(final PsiElement patternElement, final PsiElement matchedElement) { + return delegate.shouldAdvanceTheMatchFor(patternElement, matchedElement); + } + + public MatchingHandler getDelegate() { + return delegate; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java new file mode 100644 index 000000000000..08e335614d5a --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/TypedSymbolHandler.java @@ -0,0 +1,21 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiDeclarationStatement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Search handler for typed symbol ('T<a*>) + */ +public class TypedSymbolHandler extends MatchingHandler { + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + if (!super.match(patternNode,matchedNode,context)) { + return false; + } + + return context.getMatcher().match( + patternNode.getFirstChild(), + matchedNode + ); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java new file mode 100644 index 000000000000..cc71c02ee403 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/handlers/XmlTextHandler.java @@ -0,0 +1,23 @@ +package com.intellij.structuralsearch.impl.matcher.handlers; + +import com.intellij.dupLocator.iterators.ArrayBackedNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.GlobalMatchingVisitor; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class XmlTextHandler extends MatchingHandler { + public boolean matchSequentially(NodeIterator nodes, NodeIterator nodes2, MatchContext context) { + final PsiElement psiElement = nodes.current(); + + return GlobalMatchingVisitor.continueMatchingSequentially( + new SsrFilteringNodeIterator( new ArrayBackedNodeIterator(psiElement.getChildren()) ), + nodes2, + context + ); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java new file mode 100644 index 000000000000..a2b14ae83e17 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/iterators/SsrFilteringNodeIterator.java @@ -0,0 +1,20 @@ +package com.intellij.structuralsearch.impl.matcher.iterators; + +import com.intellij.dupLocator.iterators.FilteringNodeIterator; +import com.intellij.dupLocator.iterators.NodeIterator; +import com.intellij.dupLocator.iterators.SiblingNodeIterator; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; + +/** + * @author Eugene.Kudelevsky + */ +public class SsrFilteringNodeIterator extends FilteringNodeIterator { + public SsrFilteringNodeIterator(final NodeIterator iterator) { + super(iterator, LexicalNodesFilter.getInstance()); + } + + public SsrFilteringNodeIterator(final PsiElement element) { + this(new SiblingNodeIterator(element)); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java new file mode 100644 index 000000000000..916fc1e15639 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/AbstractStringBasedPredicate.java @@ -0,0 +1,22 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +/** + * @author Maxim.Mossienko + */ +public class AbstractStringBasedPredicate extends MatchPredicate { + protected final String myName; + protected final String myWithin; + + public AbstractStringBasedPredicate(String name, String within) { + myName = name; + myWithin = within; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return match(patternNode, matchedNode, 0, -1, context); + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java new file mode 100644 index 000000000000..b7d5432964d4 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/BinaryPredicate.java @@ -0,0 +1,38 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * Binary predicate + */ +public final class BinaryPredicate extends MatchPredicate { + private final MatchPredicate first; + private final MatchPredicate second; + private final boolean or; + + public BinaryPredicate(MatchPredicate first, MatchPredicate second, boolean or) { + this.first = first; + this.second = second; + this.or = or; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + if (or) { + return first.match(patternNode,matchedNode,context) || + second.match(patternNode,matchedNode,context); + } else { + return first.match(patternNode,matchedNode,context) && + second.match(patternNode,matchedNode,context); + } + } + + public MatchPredicate getFirst() { + return first; + } + + public MatchPredicate getSecond() { + return second; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java new file mode 100644 index 000000000000..2ed894fe6b64 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ContainsPredicate.java @@ -0,0 +1,18 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * @author Maxim.Mossienko + */ +public class ContainsPredicate extends AbstractStringBasedPredicate { + + public ContainsPredicate(String name, String within) { + super(name, within); + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + return false; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java new file mode 100644 index 000000000000..3d28a635e3af --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/NotPredicate.java @@ -0,0 +1,24 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; + +/** + * Negates predicate + */ +public final class NotPredicate extends MatchPredicate { + private final MatchPredicate handler; + + public NotPredicate(final MatchPredicate _handler) { + handler = _handler; + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return !handler.match(patternNode,matchedNode,context); + } + + public MatchPredicate getHandler() { + return handler; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java new file mode 100644 index 000000000000..17570ed8f5df --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ReferencePredicate.java @@ -0,0 +1,31 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.*; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.handlers.SubstitutionHandler; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchUtils; + +/** + * Handles finding method + */ +public final class ReferencePredicate extends SubstitutionHandler { + public ReferencePredicate(String _name) { + super(_name, true, 1, 1, true); + } + + public boolean match(PsiElement node, PsiElement match, MatchContext context) { + if (StructuralSearchUtil.isIdentifier(match)) { + // since we pickup tokens + match = match.getParent(); + } + + PsiElement result = MatchUtils.getReferencedElement(match); + if (result == null) { + result = match; + //return false; + } + + return handle(result,context); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java new file mode 100644 index 000000000000..f7b8ecedcf3d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/RegExpPredicate.java @@ -0,0 +1,175 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.*; +import com.intellij.structuralsearch.MalformedPatternException; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.impl.matcher.MatchUtils; +import com.intellij.structuralsearch.impl.matcher.handlers.MatchPredicate; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * Root of handlers for pattern node matching. Handles simpliest type of the match. + */ +public final class RegExpPredicate extends MatchPredicate { + private Pattern pattern; + private final String baseHandlerName; + private boolean simpleString; + private final boolean couldBeOptimized; + private final String regexp; + private final boolean caseSensitive; + private boolean multiline; + private final boolean wholeWords; + private final boolean target; + private NodeTextGenerator myNodeTextGenerator; + + public interface NodeTextGenerator { + String getText(PsiElement element); + } + + public RegExpPredicate(final String regexp, final boolean caseSensitive, final String _baseHandlerName, boolean _wholeWords, boolean _target) { + couldBeOptimized = containsRegExp(regexp); + if (!_wholeWords) { + simpleString = couldBeOptimized; + } + + this.regexp = regexp; + this.caseSensitive = caseSensitive; + this.wholeWords = _wholeWords; + baseHandlerName = _baseHandlerName; + + if (!simpleString) { + compilePattern(); + } + target = _target; + } + + private static boolean containsRegExp(final String regexp) { + for(int i=0;i<regexp.length();++i) { + if(MatchUtils.SPECIAL_CHARS.indexOf(regexp.charAt(i))!=-1) { + return false; + } + } + + return true; + } + + private void compilePattern() { + try { + @NonNls String realRegexp = regexp; + if (wholeWords) { + realRegexp = ".*?\\b(?:" + realRegexp + ")\\b.*?"; + } + + pattern = Pattern.compile( + realRegexp, + (caseSensitive ? 0: Pattern.CASE_INSENSITIVE) | (multiline ? Pattern.DOTALL:0) + ); + } catch(PatternSyntaxException ex) { + throw new MalformedPatternException(SSRBundle.message("error.incorrect.regexp.constraint", regexp, baseHandlerName)); + } + } + + public boolean couldBeOptimized() { + return couldBeOptimized; + } + + public String getRegExp() { + return regexp; + } + + /** + * Attempts to match given handler node against given node. + * @param matchedNode for matching + * @param context of the matching + * @return true if matching was successfull and false otherwise + */ + public boolean match(PsiElement node,PsiElement matchedNode, int start, int end, MatchContext context) { + if (matchedNode==null) return false; + String text; + + text = myNodeTextGenerator != null ? myNodeTextGenerator.getText(matchedNode) : getMeaningfulText(matchedNode); + + boolean result = doMatch(text, start, end, context, matchedNode); + + if (!result) { + + if(StructuralSearchUtil.isIdentifier(matchedNode)) { + matchedNode = matchedNode.getParent(); + } + + String alternativeText = context.getPattern().getAlternativeTextToMatch(matchedNode, text); + if (alternativeText != null) { + result = doMatch(alternativeText, start, end, context, matchedNode); + } + } + + return result; + } + + public static String getMeaningfulText(PsiElement matchedNode) { + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(matchedNode); + return profile != null ? profile.getMeaningfulText(matchedNode) : matchedNode.getText(); + } + + public boolean match(PsiElement patternNode, PsiElement matchedNode, MatchContext context) { + return match(patternNode,matchedNode,0,-1,context); + } + + boolean doMatch(String text, MatchContext context, PsiElement matchedElement) { + return doMatch(text,0,-1,context, matchedElement); + } + + boolean doMatch(String text, int from, int end, MatchContext context,PsiElement matchedElement) { + if (from > 0 || end != -1) { + text = text.substring(from, end == -1 || end >= text.length() ? text.length():end); + } + + if (simpleString) { + return (caseSensitive)?text.equals(regexp):text.equalsIgnoreCase(regexp); + } + + if(!multiline && text.contains("\n")) setMultiline(true); + final Matcher matcher = pattern.matcher(text); + + if (matcher.matches()) { + for (int i=1;i<=matcher.groupCount();++i) { + context.getResult().addSon( + new MatchResultImpl( + baseHandlerName + "_" + i, + matcher.group(i), + new SmartPsiPointer(matchedElement), + matcher.start(i), + matcher.end(i), + target + ) + ); + } + return true; + } else { + return false; + } + } + + + public void setNodeTextGenerator(final NodeTextGenerator nodeTextGenerator) { + myNodeTextGenerator = nodeTextGenerator; + } + + public void setMultiline(boolean b) { + multiline = b; + compilePattern(); + } + + public boolean isWholeWords() { + return wholeWords; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java new file mode 100644 index 000000000000..9310eb9e70a0 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptPredicate.java @@ -0,0 +1,28 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.impl.matcher.MatchContext; + +/** + * @author Maxim.Mossienko + */ +public class ScriptPredicate extends AbstractStringBasedPredicate { + private final ScriptSupport scriptSupport; + + public ScriptPredicate(String name, String within) { + super(name, within); + scriptSupport = new ScriptSupport(within, name); + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + if (match == null) return false; + + return Boolean.TRUE.equals( + Boolean.valueOf(scriptSupport.evaluate( + context.hasResult() ? context.getResult() : null, + match + )) + ); + } + +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java new file mode 100644 index 000000000000..e4136ede2831 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/ScriptSupport.java @@ -0,0 +1,91 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchException; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import groovy.lang.Binding; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.GroovyShell; +import groovy.lang.Script; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.ErrorCollector; +import org.codehaus.groovy.control.MultipleCompilationErrorsException; +import org.codehaus.groovy.control.messages.Message; +import org.codehaus.groovy.control.messages.SyntaxErrorMessage; +import org.codehaus.groovy.syntax.SyntaxException; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author Maxim.Mossienko + * Date: 11.06.2009 + * Time: 16:25:12 + */ +public class ScriptSupport { + private final Script script; + + public ScriptSupport(String text, String name) { + File scriptFile = new File(text); + GroovyShell shell = new GroovyShell(); + try { + script = scriptFile.exists() ? shell.parse(scriptFile):shell.parse(text, name); + } catch (Exception ex) { + Logger.getInstance(getClass().getName()).error(ex); + throw new RuntimeException(ex); + } + } + + public String evaluate(MatchResultImpl result, PsiElement context) { + try { + Binding binding = new Binding(); + + if (result != null) { + for(MatchResult r:result.getMatches()) { + binding.setVariable(r.getName(),r.getMatchRef().getElement()); + } + } + + if (context == null) { + context = result.getMatchRef().getElement(); + } + if (StructuralSearchUtil.isIdentifier(context)) context = context.getParent(); + binding.setVariable("__context__", context); + script.setBinding(binding); + + Object o = script.run(); + return String.valueOf(o); + } catch (GroovyRuntimeException ex) { + throw new StructuralSearchException(SSRBundle.message("groovy.script.error", ex.getMessage())); + } + } + + public static String checkValidScript(String scriptText) { + try { + final File scriptFile = new File(scriptText); + final GroovyShell shell = new GroovyShell(); + final Script script = scriptFile.exists() ? shell.parse(scriptFile) : shell.parse(scriptText); + return null; + } catch (IOException e) { + return e.getMessage(); + } catch (MultipleCompilationErrorsException e) { + final ErrorCollector errorCollector = e.getErrorCollector(); + final List<Message> errors = errorCollector.getErrors(); + for (Message error : errors) { + if (error instanceof SyntaxErrorMessage) { + final SyntaxErrorMessage errorMessage = (SyntaxErrorMessage)error; + final SyntaxException cause = errorMessage.getCause(); + return cause.getMessage(); + } + } + return e.getMessage(); + } catch (CompilationFailedException ex) { + return ex.getLocalizedMessage(); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java new file mode 100644 index 000000000000..3904eb397bce --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/predicates/WithinPredicate.java @@ -0,0 +1,38 @@ +package com.intellij.structuralsearch.impl.matcher.predicates; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.Matcher; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.compiler.PatternCompiler; + +/** + * @author Maxim.Mossienko + */ +public class WithinPredicate extends AbstractStringBasedPredicate { + private final MatchOptions myMatchOptions; + private Matcher matcher; + + public WithinPredicate(String name, String within, Project project) { + super(name, within); + myMatchOptions = new MatchOptions(); + + myMatchOptions.setLooseMatching(true); + final String unquoted = StringUtil.stripQuotesAroundValue(within); + if (!unquoted.equals(within)) { + myMatchOptions.setSearchPattern(unquoted); + PatternCompiler.transformOldPattern(myMatchOptions); + matcher = new Matcher(project, myMatchOptions); + } else { + assert false; + } + } + + public boolean match(PsiElement node, PsiElement match, int start, int end, MatchContext context) { + final MatchResult result = matcher.isMatchedByDownUp(match, myMatchOptions); + return result != null; + } +}
\ No newline at end of file diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java new file mode 100644 index 000000000000..8f5b5bc2fc20 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/MatchingStrategy.java @@ -0,0 +1,13 @@ +package com.intellij.structuralsearch.impl.matcher.strategies; + +import com.intellij.psi.PsiElement; + + +/** + * CommonStrategy of metching process + */ +public interface MatchingStrategy { + boolean continueMatching(PsiElement start); + + boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java new file mode 100644 index 000000000000..b2ca87a176e8 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/impl/matcher/strategies/XmlMatchingStrategy.java @@ -0,0 +1,42 @@ +package com.intellij.structuralsearch.impl.matcher.strategies; + +import com.intellij.dupLocator.util.NodeFilter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.xml.XmlTag; + +/** + * Base filtering strategy to find statements + */ +public class XmlMatchingStrategy extends XmlElementVisitor implements MatchingStrategy,NodeFilter { + protected boolean result; + + @Override public void visitXmlTag(final XmlTag element) { + result = true; + } + + public boolean continueMatching(final PsiElement start) { + return accepts(start); + } + + @Override + public boolean shouldSkip(PsiElement element, PsiElement elementToMatchWith) { + return false; + } + + protected XmlMatchingStrategy() {} + + private static class XmlMatchingStrategyHolder { + private static final XmlMatchingStrategy instance = new XmlMatchingStrategy(); + } + + public static MatchingStrategy getInstance() { + return XmlMatchingStrategyHolder.instance; + } + + public boolean accepts(PsiElement element) { + result = false; + if (element!=null) element.accept(this); + return result; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java new file mode 100644 index 000000000000..7a38ddb2ea65 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspection.java @@ -0,0 +1,186 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.structuralsearch.inspection.highlightTemplate; + +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInspection.*; +import com.intellij.dupLocator.iterators.CountingNodeIterator; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.notification.Notifications; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.InvalidDataException; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.WriteExternalException; +import com.intellij.profile.codeInspection.InspectionProfileManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.Matcher; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchException; +import com.intellij.structuralsearch.impl.matcher.MatchContext; +import com.intellij.structuralsearch.impl.matcher.MatcherImpl; +import com.intellij.structuralsearch.impl.matcher.filters.LexicalNodesFilter; +import com.intellij.structuralsearch.impl.matcher.iterators.SsrFilteringNodeIterator; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.ConfigurationManager; +import com.intellij.structuralsearch.plugin.ui.SearchContext; +import com.intellij.util.PairProcessor; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import javax.swing.*; +import java.util.*; + +/** + * @author cdr + */ +public class SSBasedInspection extends LocalInspectionTool { + static final String SHORT_NAME = "SSBasedInspection"; + private List<Configuration> myConfigurations = new ArrayList<Configuration>(); + private Set<String> myProblemsReported = new HashSet<String>(1); + + public void writeSettings(@NotNull Element node) throws WriteExternalException { + ConfigurationManager.writeConfigurations(node, myConfigurations, Collections.<Configuration>emptyList()); + } + + public void readSettings(@NotNull Element node) throws InvalidDataException { + myProblemsReported.clear(); + myConfigurations.clear(); + ConfigurationManager.readConfigurations(node, myConfigurations, new ArrayList<Configuration>()); + } + + @NotNull + public String getGroupDisplayName() { + return GENERAL_GROUP_NAME; + } + + @NotNull + public String getDisplayName() { + return SSRBundle.message("SSRInspection.display.name"); + } + + @NotNull + @NonNls + public String getShortName() { + return SHORT_NAME; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) { + final MatcherImpl.CompiledOptions compiledOptions = + SSBasedInspectionCompiledPatternsCache.getCompiledOptions(holder.getProject()); + + if (compiledOptions == null) return super.buildVisitor(holder, isOnTheFly); + + return new PsiElementVisitor() { + final List<Pair<MatchContext,Configuration>> contexts = compiledOptions.getMatchContexts(); + final Matcher matcher = new Matcher(holder.getManager().getProject()); + final PairProcessor<MatchResult, Configuration> processor = new PairProcessor<MatchResult, Configuration>() { + public boolean process(MatchResult matchResult, Configuration configuration) { + PsiElement element = matchResult.getMatch(); + String name = configuration.getName(); + LocalQuickFix fix = createQuickFix(holder.getManager().getProject(), matchResult, configuration); + holder.registerProblem( + holder.getManager().createProblemDescriptor(element, name, fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, isOnTheFly) + ); + return true; + } + }; + + @Override + public void visitElement(PsiElement element) { + if (LexicalNodesFilter.getInstance().accepts(element)) return; + final SsrFilteringNodeIterator matchedNodes = new SsrFilteringNodeIterator(element); + for (Pair<MatchContext, Configuration> pair : contexts) { + Configuration configuration = pair.second; + MatchContext context = pair.first; + + if (MatcherImpl.checkIfShouldAttemptToMatch(context, matchedNodes)) { + final int nodeCount = context.getPattern().getNodeCount(); + try { + matcher.processMatchesInElement(context, configuration, new CountingNodeIterator(nodeCount, matchedNodes), processor); + } + catch (StructuralSearchException e) { + if (myProblemsReported.add(configuration.getName())) { // don't overwhelm the user with messages + Notifications.Bus.notify(new Notification(SSRBundle.message("structural.search.title"), + SSRBundle.message("template.problem", configuration.getName()), + e.getMessage(), + NotificationType.ERROR), element.getProject()); + } + } + matchedNodes.reset(); + } + } + } + }; + } + + private static LocalQuickFix createQuickFix(final Project project, final MatchResult matchResult, final Configuration configuration) { + if (!(configuration instanceof ReplaceConfiguration)) return null; + ReplaceConfiguration replaceConfiguration = (ReplaceConfiguration)configuration; + final Replacer replacer = new Replacer(project, replaceConfiguration.getOptions()); + final ReplacementInfo replacementInfo = replacer.buildReplacement(matchResult); + + return new LocalQuickFix() { + @NotNull + public String getName() { + return SSRBundle.message("SSRInspection.replace.with", replacementInfo.getReplacement()); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getPsiElement(); + if (element != null && FileModificationService.getInstance().preparePsiElementsForWrite(element)) { + replacer.replace(replacementInfo); + } + } + + @NotNull + public String getFamilyName() { + return SSRBundle.message("SSRInspection.family.name"); + } + }; + } + + @Nullable + public JComponent createOptionsPanel() { + return new SSBasedInspectionOptions(myConfigurations){ + public void configurationsChanged(final SearchContext searchContext) { + super.configurationsChanged(searchContext); + SSBasedInspectionCompiledPatternsCache.precompileConfigurations(searchContext.getProject(), SSBasedInspection.this); + InspectionProfileManager.getInstance().fireProfileChanged(null); + } + }.getComponent(); + } + + @TestOnly + public void setConfigurations(final List<Configuration> configurations, final Project project) { + myConfigurations = configurations; + SSBasedInspectionCompiledPatternsCache.setCompiledOptions(project, configurations); + } + + public List<Configuration> getConfigurations() { + return myConfigurations; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java new file mode 100644 index 000000000000..2d6335fe10ce --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionCompiledPatternsCache.java @@ -0,0 +1,78 @@ +package com.intellij.structuralsearch.inspection.highlightTemplate; + +import com.intellij.codeInspection.InspectionProfile; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import com.intellij.openapi.util.Key; +import com.intellij.profile.codeInspection.InspectionProjectProfileManager; +import com.intellij.structuralsearch.Matcher; +import com.intellij.structuralsearch.impl.matcher.MatcherImpl; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.Collections; +import java.util.List; + +/** + * @author Eugene.Kudelevsky + */ +public class SSBasedInspectionCompiledPatternsCache implements StartupActivity { + private static final Key<MatcherImpl.CompiledOptions> COMPILED_OPTIONS_KEY = Key.create("SSR_INSPECTION_COMPILED_OPTIONS_KEY"); + + @Override + public void runActivity(@NotNull final Project project) { + precompileConfigurations(project, null); + } + + static void precompileConfigurations(final Project project, @Nullable final SSBasedInspection ssBasedInspection) { + if (project.isDisposed()) { + return; + } + final MatcherImpl.CompiledOptions currentCompiledOptions = getCompiledOptions(project); + + final SSBasedInspection inspection = ssBasedInspection != null ? ssBasedInspection : getInspection(project); + if (inspection == null) { + return; + } + + List<Configuration> configurations = inspection.getConfigurations(); + if (configurations == null) { + configurations = Collections.emptyList(); + } + + if ((currentCompiledOptions == null || currentCompiledOptions.getMatchContexts().isEmpty()) && + configurations.isEmpty()) { + return; + } + + final Matcher matcher = new Matcher(project); + final MatcherImpl.CompiledOptions compiledOptions = matcher.precompileOptions(configurations); + + if (compiledOptions != null) { + project.putUserData(COMPILED_OPTIONS_KEY, compiledOptions); + } + } + + @Nullable + private static SSBasedInspection getInspection(@NotNull Project project) { + final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); + final InspectionToolWrapper entry = profile.getInspectionTool(SSBasedInspection.SHORT_NAME, project); + + return entry == null ? null : (SSBasedInspection)entry.getTool(); + } + + @Nullable + static MatcherImpl.CompiledOptions getCompiledOptions(@NotNull Project project) { + return project.getUserData(COMPILED_OPTIONS_KEY); + } + + @TestOnly + static void setCompiledOptions(@NotNull Project project, @NotNull List<Configuration> configurations) { + final Matcher matcher = new Matcher(project); + project.putUserData(COMPILED_OPTIONS_KEY, + matcher.precompileOptions(configurations)); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java new file mode 100644 index 000000000000..8dd7211eefb4 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/inspection/highlightTemplate/SSBasedInspectionOptions.java @@ -0,0 +1,256 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.structuralsearch.inspection.highlightTemplate; + +import com.intellij.ide.DataManager; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog; +import com.intellij.structuralsearch.plugin.ui.*; +import com.intellij.ui.AnActionButton; +import com.intellij.ui.AnActionButtonRunnable; +import com.intellij.ui.DoubleClickListener; +import com.intellij.ui.ToolbarDecorator; +import com.intellij.ui.components.JBList; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.Iterator; +import java.util.List; + +/** + * @author cdr + */ +public class SSBasedInspectionOptions { + private JBList myTemplatesList; + // for externalization + private final List<Configuration> myConfigurations; + + public SSBasedInspectionOptions(final List<Configuration> configurations) { + myConfigurations = configurations; + myTemplatesList = new JBList(new MyListModel()); + myTemplatesList.setCellRenderer(new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel component = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + Configuration configuration = myConfigurations.get(index); + component.setText(configuration.getName()); + return component; + } + }); + } + + private static void copyConfiguration(final Configuration configuration, final Configuration newConfiguration) { + @NonNls Element temp = new Element("temp"); + configuration.writeExternal(temp); + newConfiguration.readExternal(temp); + } + + interface SearchDialogFactory { + SearchDialog createDialog(SearchContext searchContext); + } + + private void addTemplate(SearchDialogFactory searchDialogFactory) { + SearchDialog dialog = createDialog(searchDialogFactory); + dialog.show(); + if (!dialog.isOK()) return; + Configuration configuration = dialog.getConfiguration(); + + if (configuration.getName() == null || configuration.getName().equals(SearchDialog.USER_DEFINED)) { + String name = dialog.showSaveTemplateAsDialog(); + + if (name != null) { + name = ConfigurationManager.findAppropriateName(myConfigurations, name, dialog.getProject()); + } + if (name == null) return; + configuration.setName(name); + } + myConfigurations.add(configuration); + + configurationsChanged(dialog.getSearchContext()); + } + + private static SearchDialog createDialog(final SearchDialogFactory searchDialogFactory) { + SearchContext searchContext = createSearchContext(); + return searchDialogFactory.createDialog(searchContext); + } + + private static SearchContext createSearchContext() { + AnActionEvent event = new AnActionEvent(null, DataManager.getInstance().getDataContext(), + "", new DefaultActionGroup().getTemplatePresentation(), ActionManager.getInstance(), 0); + return SearchContext.buildFromDataContext(event.getDataContext()); + } + + public void configurationsChanged(final SearchContext searchContext) { + ((MyListModel)myTemplatesList.getModel()).fireContentsChanged(); + } + + public JPanel getComponent() { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JLabel(SSRBundle.message("SSRInspection.selected.templates"))); + panel.add( + ToolbarDecorator.createDecorator(myTemplatesList) + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final AnAction[] children = new AnAction[]{ + new AnAction(SSRBundle.message("SSRInspection.add.search.template.button")) { + @Override + public void actionPerformed(AnActionEvent e) { + addTemplate(new SearchDialogFactory() { + public SearchDialog createDialog(SearchContext searchContext) { + return new SearchDialog(searchContext, false, false); + } + }); + } + }, + new AnAction(SSRBundle.message("SSRInspection.add.replace.template.button")) { + @Override + public void actionPerformed(AnActionEvent e) { + addTemplate(new SearchDialogFactory() { + public SearchDialog createDialog(SearchContext searchContext) { + return new ReplaceDialog(searchContext, false, false); + } + }); + } + } + }; + JBPopupFactory.getInstance().createActionGroupPopup(null, new DefaultActionGroup(children), + DataManager.getInstance() + .getDataContext(button.getContextComponent()), + JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true) + .show(button.getPreferredPopupPoint()); + } + }).setEditAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + performEditAction(); + } + }).setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + Object[] selected = myTemplatesList.getSelectedValues(); + for (Object o : selected) { + Configuration configuration = (Configuration)o; + Iterator<Configuration> iterator = myConfigurations.iterator(); + while (iterator.hasNext()) { + Configuration configuration1 = iterator.next(); + if (configuration1.getName().equals(configuration.getName())) { + iterator.remove(); + } + } + } + configurationsChanged(createSearchContext()); + } + }).setMoveUpAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + performMoveUpDown(false); + } + }).setMoveDownAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + performMoveUpDown(true); + } + }).createPanel() + ); + new DoubleClickListener() { + @Override + protected boolean onDoubleClick(MouseEvent e) { + performEditAction(); + return true; + } + }.installOn(myTemplatesList); + return panel; + } + + private void performMoveUpDown(boolean down) { + final int[] indices = myTemplatesList.getSelectedIndices(); + if (indices.length == 0) return; + final int delta = down ? 1 : -1; + myTemplatesList.removeSelectionInterval(0, myConfigurations.size() - 1); + for (int i = down ? indices[indices.length - 1] : 0; + down ? i >= 0 : i < indices.length; + i -= delta) { + final int index = indices[i]; + final Configuration temp = myConfigurations.get(index); + myConfigurations.set(index, myConfigurations.get(index + delta)); + myConfigurations.set(index + delta, temp); + myTemplatesList.addSelectionInterval(index + delta, index + delta); + } + final int index = down ? myTemplatesList.getMaxSelectionIndex() : myTemplatesList.getMinSelectionIndex(); + final Rectangle cellBounds = myTemplatesList.getCellBounds(index, index); + if (cellBounds != null) { + myTemplatesList.scrollRectToVisible(cellBounds); + } + } + + private void performEditAction() { + final Configuration configuration = (Configuration)myTemplatesList.getSelectedValue(); + if (configuration == null) return; + + SearchDialog dialog = createDialog(new SearchDialogFactory() { + public SearchDialog createDialog(SearchContext searchContext) { + if (configuration instanceof SearchConfiguration) { + return new SearchDialog(searchContext, false, false) { + public Configuration createConfiguration() { + SearchConfiguration newConfiguration = new SearchConfiguration(); + copyConfiguration(configuration, newConfiguration); + return newConfiguration; + } + }; + } + else { + return new ReplaceDialog(searchContext, false, false) { + public Configuration createConfiguration() { + ReplaceConfiguration newConfiguration = new ReplaceConfiguration(); + copyConfiguration(configuration, newConfiguration); + return newConfiguration; + } + }; + } + } + }); + dialog.setValuesFromConfig(configuration); + dialog.setUseLastConfiguration(true); + dialog.show(); + if (!dialog.isOK()) return; + Configuration newConfiguration = dialog.getConfiguration(); + copyConfiguration(newConfiguration, configuration); + configurationsChanged(dialog.getSearchContext()); + } + + private class MyListModel extends AbstractListModel { + public int getSize() { + return myConfigurations.size(); + } + + public Object getElementAt(int index) { + return index < myConfigurations.size() ? myConfigurations.get(index) : null; + } + + public void fireContentsChanged() { + fireContentsChanged(myTemplatesList, -1, -1); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java new file mode 100644 index 000000000000..33e7ac5b4d4d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralReplaceAction.java @@ -0,0 +1,55 @@ +package com.intellij.structuralsearch.plugin; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.Project; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.SearchContext; + +/** + * Search and replace structural java code patterns action. + */ +public class StructuralReplaceAction extends AnAction { + + public StructuralReplaceAction() { + super(SSRBundle.message("structuralreplace.action")); + } + + /** Handles IDEA action event + * @param event the event of action + */ + public void actionPerformed(AnActionEvent event) { + triggerAction(null, SearchContext.buildFromDataContext(event.getDataContext())); + } + + public static void triggerAction(Configuration config, SearchContext searchContext) { + ReplaceDialog replaceDialog = new ReplaceDialog(searchContext); + + if (config!=null) { + replaceDialog.setUseLastConfiguration(true); + replaceDialog.setValuesFromConfig(config); + } + + replaceDialog.show(); + } + + /** Updates the state of the action + * @param event the action event + */ + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final DataContext context = event.getDataContext(); + final Project project = CommonDataKeys.PROJECT.getData(context); + final StructuralSearchPlugin plugin = (project == null)? null:StructuralSearchPlugin.getInstance( project ); + + if (plugin== null || plugin.isSearchInProgress() || plugin.isReplaceInProgress() || plugin.isDialogVisible()) { + presentation.setEnabled( false ); + } else { + presentation.setEnabled( true ); + } + + super.update(event); + } +} + diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java new file mode 100644 index 000000000000..116f885b4679 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchAction.java @@ -0,0 +1,54 @@ +package com.intellij.structuralsearch.plugin; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.project.Project; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.SearchContext; +import com.intellij.structuralsearch.plugin.ui.SearchDialog; + +public class StructuralSearchAction extends AnAction { + + public StructuralSearchAction() { + super(SSRBundle.message("structuralsearch.action")); + } + + /** Handles IDEA action event + * @param event the event of action + */ + public void actionPerformed(AnActionEvent event) { + triggerAction(null, SearchContext.buildFromDataContext(event.getDataContext())); + } + + public static void triggerAction(Configuration config, SearchContext searchContext) { + //StructuralSearchPlugin.getInstance(searchContext.getProject()); + final SearchDialog searchDialog = new SearchDialog(searchContext); + + if (config!=null) { + searchDialog.setUseLastConfiguration(true); + searchDialog.setValuesFromConfig(config); + } + + searchDialog.show(); + } + + /** Updates the state of the action + * @param event the action event + */ + public void update(AnActionEvent event) { + final Presentation presentation = event.getPresentation(); + final DataContext context = event.getDataContext(); + final Project project = CommonDataKeys.PROJECT.getData(context); + final StructuralSearchPlugin plugin = project==null ? null:StructuralSearchPlugin.getInstance( project ); + + if (plugin == null || plugin.isSearchInProgress() || plugin.isDialogVisible()) { + presentation.setEnabled( false ); + } else { + presentation.setEnabled( true ); + } + + super.update(event); + } + +} + diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java new file mode 100644 index 000000000000..23de5efb9cde --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/StructuralSearchPlugin.java @@ -0,0 +1,110 @@ +package com.intellij.structuralsearch.plugin; + +import com.intellij.openapi.components.ProjectComponent; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.structuralsearch.plugin.ui.ConfigurationManager; +import com.intellij.structuralsearch.plugin.ui.ExistingTemplatesComponent; +import org.jetbrains.annotations.NotNull; + +/** + * Structural search plugin main class. + */ +public final class StructuralSearchPlugin implements ProjectComponent, JDOMExternalizable { + private boolean searchInProgress; + private boolean replaceInProgress; + private boolean myDialogVisible; + private final ConfigurationManager myConfigurationManager = new ConfigurationManager(); + private ExistingTemplatesComponent myExistingTemplatesComponent; + + public boolean isSearchInProgress() { + return searchInProgress; + } + + public void setSearchInProgress(boolean searchInProgress) { + this.searchInProgress = searchInProgress; + } + + public boolean isReplaceInProgress() { + return replaceInProgress; + } + + public void setReplaceInProgress(boolean replaceInProgress) { + this.replaceInProgress = replaceInProgress; + } + + public boolean isDialogVisible() { + return myDialogVisible; + } + + public void setDialogVisible(boolean dialogVisible) { + myDialogVisible = dialogVisible; + } + + /** + * Method is called after plugin is already created and configured. Plugin can start to communicate with + * other plugins only in this method. + */ + public void initComponent() { + } + + /** + * This method is called on plugin disposal. + */ + public void disposeComponent() { + } + + /** + * Returns the name of component + * + * @return String representing component name. Use PluginName.ComponentName notation + * to avoid conflicts. + */ + @NotNull + public String getComponentName() { + return "StructuralSearchPlugin"; + } + + // Simple logging facility + + // Logs given string to IDEA logger + + private static class LoggerHolder { + private static final Logger logger = Logger.getInstance("Structural search"); + } + + public static void debug(String str) { + LoggerHolder.logger.info(str); + } + + public void readExternal(org.jdom.Element element) { + myConfigurationManager.loadConfigurations(element); + } + + public void writeExternal(org.jdom.Element element) { + myConfigurationManager.saveConfigurations(element); + } + + public void projectOpened() { + } + + public void projectClosed() { + } + + public static StructuralSearchPlugin getInstance(Project project) { + return project.getComponent(StructuralSearchPlugin.class); + } + + public ConfigurationManager getConfigurationManager() { + return myConfigurationManager; + } + + public ExistingTemplatesComponent getExistingTemplatesComponent() { + return myExistingTemplatesComponent; + } + + public void setExistingTemplatesComponent(ExistingTemplatesComponent existingTemplatesComponent) { + myExistingTemplatesComponent = existingTemplatesComponent; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java new file mode 100644 index 000000000000..d18d51acd93c --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplaceOptions.java @@ -0,0 +1,177 @@ +package com.intellij.structuralsearch.plugin.replace; + +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.UserDataHolder; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.ReplacementVariableDefinition; +import gnu.trove.THashMap; +import org.jdom.Attribute; +import org.jdom.DataConversionException; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author Maxim.Mossienko + * Date: Mar 5, 2004 + * Time: 7:51:38 PM + */ +public class ReplaceOptions implements JDOMExternalizable, Cloneable, UserDataHolder { + private Map<String, ReplacementVariableDefinition> variableDefs; + private String replacement = ""; + private boolean toShortenFQN; + private boolean myToReformatAccordingToStyle; + private MatchOptions matchOptions = new MatchOptions(); + + @NonNls private static final String REFORMAT_ATTR_NAME = "reformatAccordingToStyle"; + @NonNls private static final String REPLACEMENT_ATTR_NAME = "replacement"; + @NonNls private static final String SHORTEN_FQN_ATTR_NAME = "shortenFQN"; + + private THashMap myUserMap = null; + @NonNls private static final String VARIABLE_DEFINITION_TAG_NAME = "variableDefinition"; + + public String getReplacement() { + return replacement; + } + + public void setReplacement(String replacement) { + this.replacement = replacement; + } + + public boolean isToShortenFQN() { + return toShortenFQN; + } + + public void setToShortenFQN(boolean shortedFQN) { + this.toShortenFQN = shortedFQN; + } + + public boolean isToReformatAccordingToStyle() { + return myToReformatAccordingToStyle; + } + + public MatchOptions getMatchOptions() { + return matchOptions; + } + + public void setMatchOptions(MatchOptions matchOptions) { + this.matchOptions = matchOptions; + } + + public void setToReformatAccordingToStyle(boolean reformatAccordingToStyle) { + myToReformatAccordingToStyle = reformatAccordingToStyle; + } + + public void readExternal(Element element) { + matchOptions.readExternal(element); + + Attribute attribute = element.getAttribute(REFORMAT_ATTR_NAME); + try { + myToReformatAccordingToStyle = attribute.getBooleanValue(); + } catch(DataConversionException ex) { + } + + attribute = element.getAttribute(SHORTEN_FQN_ATTR_NAME); + try { + toShortenFQN = attribute.getBooleanValue(); + } catch(DataConversionException ex) {} + + replacement = element.getAttributeValue(REPLACEMENT_ATTR_NAME); + + List<Element> elements = element.getChildren(VARIABLE_DEFINITION_TAG_NAME); + + if (elements!=null && elements.size() > 0) { + for (final Element element1 : elements) { + final ReplacementVariableDefinition variableDefinition = new ReplacementVariableDefinition(); + variableDefinition.readExternal(element1); + addVariableDefinition(variableDefinition); + } + } + } + + public void writeExternal(Element element) { + matchOptions.writeExternal(element); + + element.setAttribute(REFORMAT_ATTR_NAME,String.valueOf(myToReformatAccordingToStyle)); + element.setAttribute(SHORTEN_FQN_ATTR_NAME,String.valueOf(toShortenFQN)); + element.setAttribute(REPLACEMENT_ATTR_NAME,replacement); + + if (variableDefs!=null) { + for (final ReplacementVariableDefinition variableDefinition : variableDefs.values()) { + final Element infoElement = new Element(VARIABLE_DEFINITION_TAG_NAME); + element.addContent(infoElement); + variableDefinition.writeExternal(infoElement); + } + } + } + + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ReplaceOptions)) return false; + + final ReplaceOptions replaceOptions = (ReplaceOptions)o; + + if (myToReformatAccordingToStyle != replaceOptions.myToReformatAccordingToStyle) return false; + if (toShortenFQN != replaceOptions.toShortenFQN) return false; + if (matchOptions != null ? !matchOptions.equals(replaceOptions.matchOptions) : replaceOptions.matchOptions != null) return false; + if (replacement != null ? !replacement.equals(replaceOptions.replacement) : replaceOptions.replacement != null) return false; + if (variableDefs != null ? !variableDefs.equals(replaceOptions.variableDefs) : replaceOptions.variableDefs != null) { + return false; + } + + return true; + } + + public int hashCode() { + int result; + result = (replacement != null ? replacement.hashCode() : 0); + result = 29 * result + (toShortenFQN ? 1 : 0); + result = 29 * result + (myToReformatAccordingToStyle ? 1 : 0); + result = 29 * result + (matchOptions != null ? matchOptions.hashCode() : 0); + result = 29 * result + (variableDefs != null ? variableDefs.hashCode() : 0); + return result; + } + + public ReplaceOptions clone() { + try { + ReplaceOptions replaceOptions = (ReplaceOptions) super.clone(); + replaceOptions.matchOptions = matchOptions.clone(); + return replaceOptions; + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + public <T> T getUserData(@NotNull Key<T> key) { + if (myUserMap==null) return null; + return (T)myUserMap.get(key); + } + + public <T> void putUserData(@NotNull Key<T> key, T value) { + if (myUserMap==null) myUserMap = new THashMap(1); + myUserMap.put(key,value); + } + + public ReplacementVariableDefinition getVariableDefinition(String name) { + return variableDefs != null ? variableDefs.get(name): null; + } + + public void addVariableDefinition(ReplacementVariableDefinition definition) { + if (variableDefs==null) { + variableDefs = new LinkedHashMap<String, ReplacementVariableDefinition>(); + } + variableDefs.put( definition.getName(), definition ); + } + + public Collection<ReplacementVariableDefinition> getReplacementVariableDefinitions() { + return variableDefs != null ? variableDefs.values() : Collections.<ReplacementVariableDefinition>emptyList(); + } + + public void clearVariableDefinitions() { + variableDefs = null; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java new file mode 100644 index 000000000000..6b74197c19dd --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ReplacementInfo.java @@ -0,0 +1,22 @@ +package com.intellij.structuralsearch.plugin.replace; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Nullable; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 03.12.2004 + * Time: 21:33:53 + * To change this template use File | Settings | File Templates. + */ +public abstract class ReplacementInfo { + public abstract String getReplacement(); + + public abstract void setReplacement(String replacement); + + @Nullable + public abstract PsiElement getMatch(int index); + + public abstract int getMatchesCount(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java new file mode 100644 index 000000000000..43ce859b17dd --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ParameterInfo.java @@ -0,0 +1,123 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.psi.PsiElement; + +public final class ParameterInfo { + private String name; + private int startIndex; + private boolean parameterContext; + private boolean methodParameterContext; + private boolean statementContext; + private boolean variableInitialContext; + private int afterDelimiterPos; + private boolean hasCommaBefore; + private int beforeDelimiterPos; + private boolean hasCommaAfter; + private boolean scopeParameterization; + private boolean replacementVariable; + private PsiElement myElement; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getStartIndex() { + return startIndex; + } + + public void setStartIndex(int startIndex) { + this.startIndex = startIndex; + } + + public boolean isParameterContext() { + return parameterContext; + } + + public void setParameterContext(boolean parameterContext) { + this.parameterContext = parameterContext; + } + + public boolean isMethodParameterContext() { + return methodParameterContext; + } + + public void setMethodParameterContext(boolean methodParameterContext) { + this.methodParameterContext = methodParameterContext; + } + + public boolean isStatementContext() { + return statementContext; + } + + public void setStatementContext(boolean statementContext) { + this.statementContext = statementContext; + } + + public boolean isVariableInitialContext() { + return variableInitialContext; + } + + public void setVariableInitialContext(boolean variableInitialContext) { + this.variableInitialContext = variableInitialContext; + } + + public int getAfterDelimiterPos() { + return afterDelimiterPos; + } + + public void setAfterDelimiterPos(int afterDelimiterPos) { + this.afterDelimiterPos = afterDelimiterPos; + } + + public boolean isHasCommaBefore() { + return hasCommaBefore; + } + + public void setHasCommaBefore(boolean hasCommaBefore) { + this.hasCommaBefore = hasCommaBefore; + } + + public int getBeforeDelimiterPos() { + return beforeDelimiterPos; + } + + public void setBeforeDelimiterPos(int beforeDelimiterPos) { + this.beforeDelimiterPos = beforeDelimiterPos; + } + + public boolean isHasCommaAfter() { + return hasCommaAfter; + } + + public void setHasCommaAfter(boolean hasCommaAfter) { + this.hasCommaAfter = hasCommaAfter; + } + + public boolean isScopeParameterization() { + return scopeParameterization; + } + + public void setScopeParameterization(boolean scopeParameterization) { + this.scopeParameterization = scopeParameterization; + } + + public boolean isReplacementVariable() { + return replacementVariable; + } + + public void setReplacementVariable(boolean replacementVariable) { + this.replacementVariable = replacementVariable; + } + + public PsiElement getElement() { + return myElement; + } + + public void setElement(PsiElement element) { + myElement = element; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java new file mode 100644 index 000000000000..81933d9c9a91 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementBuilder.java @@ -0,0 +1,217 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.structuralsearch.MalformedPatternException; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.util.IncorrectOperationException; + +import java.util.*; + +/** + * @author maxim + * Date: 24.02.2004 + * Time: 15:34:57 + */ +public final class ReplacementBuilder { + private String replacement; + private List<ParameterInfo> parameterizations; + private HashMap<String,MatchResult> matchMap; + private final Map<String, ScriptSupport> replacementVarsMap; + private final ReplaceOptions options; + //private Map<TextRange,ParameterInfo> scopedParameterizations; + + ReplacementBuilder(final Project project,final ReplaceOptions options) { + replacementVarsMap = new HashMap<String, ScriptSupport>(); + this.options = options; + String _replacement = options.getReplacement(); + FileType fileType = options.getMatchOptions().getFileType(); + + final Template template = TemplateManager.getInstance(project).createTemplate("","",_replacement); + + final int segmentsCount = template.getSegmentsCount(); + replacement = template.getTemplateText(); + + for(int i=0;i<segmentsCount;++i) { + final int offset = template.getSegmentOffset(i); + final String name = template.getSegmentName(i); + + final ParameterInfo info = new ParameterInfo(); + info.setStartIndex(offset); + info.setName(name); + info.setReplacementVariable(options.getVariableDefinition(name) != null); + + // find delimiter + int pos; + for(pos = offset-1; pos >=0 && pos < replacement.length() && Character.isWhitespace(replacement.charAt(pos));) { + --pos; + } + + if (pos >= 0) { + if (replacement.charAt(pos) == ',') { + info.setHasCommaBefore(true); + } + info.setBeforeDelimiterPos(pos); + } + + for(pos = offset; pos < replacement.length() && Character.isWhitespace(replacement.charAt(pos));) { + ++pos; + } + + if (pos < replacement.length()) { + final char ch = replacement.charAt(pos); + + if (ch == ';') { + info.setStatementContext(true); + } + else if (ch == ',' || ch == ')') { + info.setParameterContext(true); + info.setHasCommaAfter(ch == ','); + } + info.setAfterDelimiterPos(pos); + } + + if (parameterizations==null) { + parameterizations = new ArrayList<ParameterInfo>(); + } + + parameterizations.add(info); + } + + final StructuralSearchProfile profile = parameterizations != null ? StructuralSearchUtil.getProfileByFileType(fileType) : null; + if (profile != null) { + try { + final PsiElement[] elements = MatcherImplUtil.createTreeFromText( + _replacement, + PatternTreeContext.Block, + fileType, + options.getMatchOptions().getDialect(), + options.getMatchOptions().getPatternContext(), + project, + false + ); + if (elements.length > 0) { + final PsiElement patternNode = elements[0].getParent(); + profile.provideAdditionalReplaceOptions(patternNode, options, this); + } + } catch (IncorrectOperationException e) { + throw new MalformedPatternException(); + } + } + } + + private static void fill(MatchResult r,Map<String,MatchResult> m) { + if (r.getName()!=null) { + if (m.get(r.getName()) == null) { + m.put(r.getName(), r); + } + } + + if (!r.isScopeMatch() || !r.isMultipleMatch()) { + for (final MatchResult matchResult : r.getAllSons()) { + fill(matchResult, m); + } + } else if (r.hasSons()) { + final List<MatchResult> allSons = r.getAllSons(); + if (allSons.size() > 0) { + fill(allSons.get(0),m); + } + } + } + + String process(MatchResult match, ReplacementInfoImpl replacementInfo, FileType type) { + if (parameterizations==null) { + return replacement; + } + + final StringBuilder result = new StringBuilder(replacement); + matchMap = new HashMap<String,MatchResult>(); + fill(match, matchMap); + + int offset = 0; + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(type); + + for (final ParameterInfo info : parameterizations) { + MatchResult r = matchMap.get(info.getName()); + if (info.isReplacementVariable()) { + offset = Replacer.insertSubstitution(result, offset, info, generateReplacement(info, match)); + } + else if (r != null) { + offset = profile != null ? profile.handleSubstitution(info, r, result, offset, matchMap) : StructuralSearchProfile.defaultHandleSubstitution(info, r, result, offset); + } + else { + if (info.isHasCommaBefore()) { + result.delete(info.getBeforeDelimiterPos() + offset, info.getBeforeDelimiterPos() + 1 + offset); + --offset; + } + else if (info.isHasCommaAfter()) { + result.delete(info.getAfterDelimiterPos() + offset, info.getAfterDelimiterPos() + 1 + offset); + --offset; + } + else if (info.isVariableInitialContext()) { + //if (info.afterDelimiterPos > 0) { + result.delete(info.getBeforeDelimiterPos() + offset, info.getAfterDelimiterPos() + offset - 1); + offset -= (info.getAfterDelimiterPos() - info.getBeforeDelimiterPos() - 1); + //} + } else if (profile != null) { + offset = profile.processAdditionalOptions(info, offset, result, r); + } + offset = Replacer.insertSubstitution(result, offset, info, ""); + } + } + + replacementInfo.variableMap = (HashMap<String, MatchResult>)matchMap.clone(); + matchMap.clear(); + return result.toString(); + } + + private String generateReplacement(ParameterInfo info, MatchResult match) { + ScriptSupport scriptSupport = replacementVarsMap.get(info.getName()); + + if (scriptSupport == null) { + String constraint = options.getVariableDefinition(info.getName()).getScriptCodeConstraint(); + scriptSupport = new ScriptSupport(StringUtil.stripQuotesAroundValue(constraint), info.getName()); + replacementVarsMap.put(info.getName(), scriptSupport); + } + return scriptSupport.evaluate((MatchResultImpl)match, null); + } + + public ParameterInfo findParameterization(String name) { + if (parameterizations==null) return null; + + for (final ParameterInfo info : parameterizations) { + + if (info.getName().equals(name)) { + return info; + } + } + + return null; + } + + public void clear() { + replacement = null; + + if (parameterizations!=null) { + parameterizations.clear(); + parameterizations = null; + } + } + + public void addParametrization(ParameterInfo e) { + assert parameterizations != null; + parameterizations.add(e); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java new file mode 100644 index 000000000000..c77d0b7ce1f1 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementContext.java @@ -0,0 +1,57 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiNamedElement; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: 27.09.2005 + * Time: 14:27:20 + * To change this template use File | Settings | File Templates. + */ +public class ReplacementContext { + ReplacementInfoImpl replacementInfo; + ReplaceOptions options; + Project project; + + public ReplaceOptions getOptions() { + return options; + } + + public Project getProject() { + return project; + } + + ReplacementContext(ReplaceOptions _options, Project _project) { + options = _options; + project = _project; + } + + public Map<String, String> getNewName2PatternNameMap() { + Map<String, String> newNameToSearchPatternNameMap = new HashMap<String, String>(1); + final Map<String, MatchResult> variableMap = replacementInfo.getVariableMap(); + + if (variableMap != null) { + for (String s : variableMap.keySet()) { + final MatchResult matchResult = replacementInfo.getVariableMap().get(s); + PsiElement match = matchResult.getMatchRef() != null ? matchResult.getMatch() : null; + if (StructuralSearchUtil.isIdentifier(match)) match = match.getParent(); + + if (match instanceof PsiNamedElement) { + final String name = ((PsiNamedElement)match).getName(); + + newNameToSearchPatternNameMap.put(name, s); + } + } + } + return newNameToSearchPatternNameMap; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java new file mode 100644 index 000000000000..318e88f27c24 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacementInfoImpl.java @@ -0,0 +1,52 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 03.12.2004 + * Time: 21:33:53 + * To change this template use File | Settings | File Templates. + */ +public class ReplacementInfoImpl extends ReplacementInfo { + List<SmartPsiElementPointer> matchesPtrList; + String result; + MatchResult matchResult; + Map<String,MatchResult> variableMap; + Map<PsiElement,String> elementToVariableNameMap; + + public String getReplacement() { + return result; + } + + public void setReplacement(String replacement) { + result = replacement; + } + + @Nullable + @Override + public PsiElement getMatch(int index) { + return matchesPtrList.get(index).getElement(); + } + + @Override + public int getMatchesCount() { + return matchesPtrList.size(); + } + + public Map<String, MatchResult> getVariableMap() { + return variableMap; + } + + public MatchResult getMatchResult() { + return matchResult; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java new file mode 100644 index 000000000000..9980e95c77d7 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/Replacer.java @@ -0,0 +1,424 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.codeInsight.template.Template; +import com.intellij.codeInsight.template.TemplateManager; +import com.intellij.lang.Language; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; +import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.util.CollectingMatchResultSink; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Maxim.Mossienko + * Date: Mar 4, 2004 + * Time: 9:19:34 PM + */ +public class Replacer { + private final Project project; + private ReplacementBuilder replacementBuilder; + private ReplaceOptions options; + private ReplacementContext context; + private StructuralReplaceHandler replaceHandler; + + public Replacer(Project project, ReplaceOptions options) { + this.project = project; + this.options = options; + } + + public static String stripTypedVariableDecoration(final String type) { + return type.substring(1,type.length()-1); + } + + public static int insertSubstitution(StringBuilder result, int offset, final ParameterInfo info, String image) { + if (image.length() > 0) result.insert(offset+ info.getStartIndex(),image); + offset += image.length(); + return offset; + } + + public String testReplace(String in, String what, String by, ReplaceOptions options) throws IncorrectOperationException { + return testReplace(in, what, by, options,false); + } + + public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern) { + FileType type = options.getMatchOptions().getFileType(); + return testReplace(in, what, by, options, filePattern, false, type, null); + } + + public String testReplace(String in, String what, String by, ReplaceOptions options, boolean filePattern, boolean createPhysicalFile, + FileType sourceFileType, Language sourceDialect) { + this.options = options; + this.options.getMatchOptions().setSearchPattern(what); + this.options.setReplacement(by); + replacementBuilder=null; + context = null; + replaceHandler = null; + + this.options.getMatchOptions().clearVariableConstraints(); + MatcherImplUtil.transform(this.options.getMatchOptions()); + + checkSupportedReplacementPattern(project, options); + + Matcher matcher = new Matcher(project); + try { + PsiElement firstElement, lastElement, parent; + + if (options.getMatchOptions().getScope() == null) { + PsiElement[] elements = MatcherImplUtil.createTreeFromText( + in, + filePattern ? PatternTreeContext.File : PatternTreeContext.Block, + sourceFileType, sourceDialect, null, + project, + createPhysicalFile + ); + + firstElement = elements[0]; + lastElement = elements[elements.length-1]; + parent = firstElement.getParent(); + + this.options.getMatchOptions().setScope( + new LocalSearchScope(parent) + ); + } else { + parent = ((LocalSearchScope)options.getMatchOptions().getScope()).getScope()[0]; + firstElement = parent.getFirstChild(); + lastElement = parent.getLastChild(); + } + + this.options.getMatchOptions().setResultIsContextMatch(true); + CollectingMatchResultSink sink = new CollectingMatchResultSink(); + matcher.testFindMatches(sink, this.options.getMatchOptions()); + + final List<ReplacementInfo> resultPtrList = new ArrayList<ReplacementInfo>(); + + for (final MatchResult result : sink.getMatches()) { + resultPtrList.add(buildReplacement(result)); + } + + sink.getMatches().clear(); + + int startOffset = firstElement.getTextRange().getStartOffset(); + int endOffset = filePattern ?0: parent.getTextLength() - (lastElement.getTextRange().getEndOffset()); + + // get nodes from text may contain + PsiElement prevSibling = firstElement.getPrevSibling(); + if (prevSibling instanceof PsiWhiteSpace) { + startOffset -= prevSibling.getTextLength() - 1; + } + + PsiElement nextSibling = lastElement.getNextSibling(); + if (nextSibling instanceof PsiWhiteSpace) { + endOffset -= nextSibling.getTextLength() - 1; + } + + replaceAll(resultPtrList); + + String result = parent.getText(); + result = result.substring(startOffset); + result = result.substring(0,result.length() - endOffset); + + return result; + } + catch (Exception e) { + throw new IncorrectOperationException(e); + } + finally { + options.getMatchOptions().setScope(null); + } + } + + public void replaceAll(final List<ReplacementInfo> resultPtrList) { + PsiElement lastAffectedElement = null; + PsiElement currentAffectedElement; + + for (ReplacementInfo info : resultPtrList) { + PsiElement element = info.getMatch(0); + initContextAndHandler(element); + if (replaceHandler != null) { + replaceHandler.prepare(info); + } + } + + for (final ReplacementInfo aResultPtrList : resultPtrList) { + currentAffectedElement = doReplace(aResultPtrList); + + if (currentAffectedElement != lastAffectedElement) { + if (lastAffectedElement != null) reformatAndShortenRefs(lastAffectedElement); + lastAffectedElement = currentAffectedElement; + } + } + + reformatAndShortenRefs(lastAffectedElement); + } + + public void replace(ReplacementInfo info) { + PsiElement element = info.getMatch(0); + initContextAndHandler(element); + + if (replaceHandler != null) { + replaceHandler.prepare(info); + } + reformatAndShortenRefs(doReplace(info)); + } + + @Nullable + private PsiElement doReplace(final ReplacementInfo info) { + final ReplacementInfoImpl replacementInfo = (ReplacementInfoImpl)info; + final PsiElement element = replacementInfo.matchesPtrList.get(0).getElement(); + + if (element==null || !element.isWritable() || !element.isValid()) return null; + + final PsiElement elementParent = element.getParent(); + + //noinspection HardCodedStringLiteral + CommandProcessor.getInstance().executeCommand( + project, + new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction( + new Runnable() { + public void run() { + doReplace(element, replacementInfo); + } + } + ); + PsiDocumentManager.getInstance(project).commitAllDocuments(); + } + }, + "ssreplace", + "test" + ); + + if (!elementParent.isValid() || !elementParent.isWritable()) { + return null; + } + + return elementParent; + } + + private void reformatAndShortenRefs(final PsiElement elementParent) { + if (elementParent == null) return; + final Runnable action = new Runnable() { + public void run() { + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(PsiManager.getInstance(project).getProject()); + final PsiFile containingFile = elementParent.getContainingFile(); + + if (containingFile != null && options.isToReformatAccordingToStyle()) { + if (containingFile.getVirtualFile() != null) { + PsiDocumentManager.getInstance(project) + .commitDocument(FileDocumentManager.getInstance().getDocument(containingFile.getVirtualFile())); + } + + final int parentOffset = elementParent.getTextRange().getStartOffset(); + + codeStyleManager.reformatRange(containingFile, parentOffset, parentOffset + elementParent.getTextLength(), true); + } + } + }; + + CommandProcessor.getInstance().executeCommand( + project, + new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(action); + } + }, + "reformat and shorten refs after ssr", + "test" + ); + } + + private void doReplace(final PsiElement elementToReplace, + final ReplacementInfoImpl info) { + CodeStyleManager.getInstance(project).performActionWithFormatterDisabled(new Runnable() { + public void run() { + initContextAndHandler(elementToReplace); + + context.replacementInfo = info; + + if (replaceHandler != null) { + replaceHandler.replace(info, options); + } + } + } + ); + } + + private void initContextAndHandler(PsiElement psiContext) { + if (context == null) { + context = new ReplacementContext(options, project); + } + if (replaceHandler == null) { + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(psiContext); + if (profile != null) { + replaceHandler = profile.getReplaceHandler(this.context); + } + } + } + + public static void handleComments(final PsiElement el, final PsiElement replacement, ReplacementContext context) throws IncorrectOperationException { + ReplacementInfoImpl replacementInfo = context.replacementInfo; + if (replacementInfo.elementToVariableNameMap == null) { + replacementInfo.elementToVariableNameMap = new HashMap<PsiElement, String>(1); + Map<String, MatchResult> variableMap = replacementInfo.variableMap; + if (variableMap != null) { + for(String name:variableMap.keySet()) { + fill(name,replacementInfo.variableMap.get(name),replacementInfo.elementToVariableNameMap); + } + } + } + + PsiElement lastChild = el.getLastChild(); + if (lastChild instanceof PsiComment && + replacementInfo.elementToVariableNameMap.get(lastChild) == null && + !(replacement.getLastChild() instanceof PsiComment) + ) { + PsiElement firstElementAfterStatementEnd = lastChild; + for(PsiElement curElement=firstElementAfterStatementEnd.getPrevSibling();curElement!=null;curElement = curElement.getPrevSibling()) { + if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break; + firstElementAfterStatementEnd = curElement; + } + replacement.addRangeAfter(firstElementAfterStatementEnd,lastChild,replacement.getLastChild()); + } + + final PsiElement firstChild = el.getFirstChild(); + if (firstChild instanceof PsiComment && + !(firstChild instanceof PsiDocCommentBase) && + replacementInfo.elementToVariableNameMap.get(firstChild) == null + ) { + PsiElement lastElementBeforeStatementStart = firstChild; + + for(PsiElement curElement=lastElementBeforeStatementStart.getNextSibling();curElement!=null;curElement = curElement.getNextSibling()) { + if (!(curElement instanceof PsiWhiteSpace) && !(curElement instanceof PsiComment)) break; + lastElementBeforeStatementStart = curElement; + } + replacement.addRangeBefore(firstChild,lastElementBeforeStatementStart,replacement.getFirstChild()); + } + } + + private static void fill(final String name, final MatchResult matchResult, final Map<PsiElement, String> elementToVariableNameMap) { + boolean b = matchResult.isMultipleMatch() || matchResult.isScopeMatch(); + if(matchResult.hasSons() && b) { + for(MatchResult r:matchResult.getAllSons()) { + fill(name, r, elementToVariableNameMap); + } + } else if (!b && matchResult.getMatchRef() != null) { + elementToVariableNameMap.put(matchResult.getMatch(),name); + } + } + + public static void checkSupportedReplacementPattern(Project project, ReplaceOptions options) throws UnsupportedPatternException { + try { + String search = options.getMatchOptions().getSearchPattern(); + String replacement = options.getReplacement(); + FileType fileType = options.getMatchOptions().getFileType(); + Template template = TemplateManager.getInstance(project).createTemplate("","",search); + Template template2 = TemplateManager.getInstance(project).createTemplate("","",replacement); + + int segmentCount = template2.getSegmentsCount(); + for(int i=0;i<segmentCount;++i) { + final String replacementSegmentName = template2.getSegmentName(i); + final int segmentCount2 = template.getSegmentsCount(); + int j; + + for(j=0;j<segmentCount2;++j) { + final String searchSegmentName = template.getSegmentName(j); + + if (replacementSegmentName.equals(searchSegmentName)) break; + + // Reference to + if (replacementSegmentName.startsWith(searchSegmentName) && + replacementSegmentName.charAt(searchSegmentName.length())=='_' + ) { + try { + Integer.parseInt(replacementSegmentName.substring(searchSegmentName.length()+1)); + break; + } catch(NumberFormatException ex) {} + } + } + + if (j==segmentCount2) { + ReplacementVariableDefinition definition = options.getVariableDefinition(replacementSegmentName); + + if (definition == null || definition.getScriptCodeConstraint().length() <= 2 /*empty quotes*/) { + throw new UnsupportedPatternException( + SSRBundle.message("replacement.variable.is.not.defined.message", replacementSegmentName) + ); + } else { + String message = ScriptSupport.checkValidScript(StringUtil.stripQuotesAroundValue(definition.getScriptCodeConstraint())); + if (message != null) { + throw new UnsupportedPatternException( + SSRBundle.message("replacement.variable.is.not.valid", replacementSegmentName, message) + ); + } + } + } + } + + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); + + profile.checkReplacementPattern(project, options); + + } catch(IncorrectOperationException ex) { + throw new UnsupportedPatternException(SSRBundle.message("incorrect.pattern.message")); + } + } + + public ReplacementInfo buildReplacement(MatchResult result) { + List<SmartPsiElementPointer> l = new ArrayList<SmartPsiElementPointer>(); + SmartPointerManager manager = SmartPointerManager.getInstance(project); + + if (MatchResult.MULTI_LINE_MATCH.equals(result.getName())) { + for(Iterator<MatchResult> i=result.getAllSons().iterator();i.hasNext();) { + final MatchResult r = i.next(); + + if (MatchResult.LINE_MATCH.equals(r.getName())) { + PsiElement element = r.getMatchRef().getElement(); + + if (element instanceof PsiDocCommentBase) { // doc comment is not collapsed when created in block + if (i.hasNext()) { + MatchResult matchResult = i.next(); + + if (MatchResult.LINE_MATCH.equals(matchResult.getName()) && + StructuralSearchUtil.isDocCommentOwner(matchResult.getMatch())) { + element = matchResult.getMatch(); + } else { + l.add( manager.createSmartPsiElementPointer(element) ); + element = matchResult.getMatch(); + } + } + } + l.add( manager.createSmartPsiElementPointer(element) ); + } + } + } else { + l.add( manager.createSmartPsiElementPointer(result.getMatchRef().getElement())); + } + + ReplacementInfoImpl replacementInfo = new ReplacementInfoImpl(); + + replacementInfo.matchesPtrList = l; + if (replacementBuilder==null) { + replacementBuilder = new ReplacementBuilder(project,options); + } + replacementInfo.result = replacementBuilder.process(result, replacementInfo, options.getMatchOptions().getFileType()); + replacementInfo.matchResult = result; + + return replacementInfo; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java new file mode 100644 index 000000000000..365ec74bc01e --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/impl/ReplacerUtil.java @@ -0,0 +1,49 @@ +package com.intellij.structuralsearch.plugin.replace.impl; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.PsiComment; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.impl.matcher.MatcherImplUtil; +import com.intellij.structuralsearch.impl.matcher.PatternTreeContext; + +/** + * @author Eugene.Kudelevsky + */ +public class ReplacerUtil { + private ReplacerUtil() { + } + + public static PsiElement[] createTreeForReplacement(String replacement, PatternTreeContext treeContext, ReplacementContext context) { + FileType fileType = context.getOptions().getMatchOptions().getFileType(); + return MatcherImplUtil.createTreeFromText(replacement, treeContext, fileType, context.getProject()); + } + + public static PsiElement copySpacesAndCommentsBefore(PsiElement elementToReplace, + PsiElement[] patternElements, + String replacementToMake, + PsiElement elementParent) { + int i = 0; + while (true) { // if it goes out of bounds then deep error happens + if (!(patternElements[i] instanceof PsiComment || patternElements[i] instanceof PsiWhiteSpace)) { + break; + } + ++i; + if (patternElements.length == i) { + break; + } + } + + if (patternElements.length == i) { + Logger logger = Logger.getInstance(StructuralSearchProfile.class.getName()); + logger.error("Unexpected replacement structure:" + replacementToMake); + } + + if (i != 0) { + elementParent.addRangeBefore(patternElements[0], patternElements[i - 1], elementToReplace); + } + return patternElements[i]; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java new file mode 100644 index 000000000000..32b99d76456d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceCommand.java @@ -0,0 +1,48 @@ +package com.intellij.structuralsearch.plugin.replace.ui; + +import com.intellij.structuralsearch.plugin.ui.SearchCommand; +import com.intellij.structuralsearch.plugin.StructuralSearchPlugin; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.openapi.project.Project; +import com.intellij.usages.Usage; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 31, 2004 + * Time: 3:54:03 PM + * To change this template use File | Settings | File Templates. + */ +public class ReplaceCommand extends SearchCommand { + private final ReplaceOptions options; + + public ReplaceCommand(Project project, ReplaceUsageViewContext context) { + super( project, context ); + options = ((ReplaceConfiguration)context.getConfiguration()).getOptions(); + + } + + protected void findStarted() { + super.findStarted(); + + StructuralSearchPlugin.getInstance(project).setReplaceInProgress(true); + } + + protected void findEnded() { + StructuralSearchPlugin.getInstance(project).setReplaceInProgress( false ); + + super.findEnded(); + } + + protected void foundUsage(MatchResult result, Usage usage) { + super.foundUsage(result, usage); + + final ReplaceUsageViewContext replaceUsageViewContext = ((ReplaceUsageViewContext)context); + replaceUsageViewContext.addReplaceUsage(usage,replaceUsageViewContext.getReplacer().buildReplacement(result)); + } + + public ReplaceOptions getOptions() { + return options; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java new file mode 100644 index 000000000000..fa9beaa36a23 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceConfiguration.java @@ -0,0 +1,46 @@ +package com.intellij.structuralsearch.plugin.replace.ui; + +import org.jdom.Element; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.MatchOptions; + +/** + * @author Maxim.Mossienko + * Date: Apr 14, 2004 + * Time: 4:41:37 PM + */ +public class ReplaceConfiguration extends Configuration { + private final ReplaceOptions options = new ReplaceOptions(); + public static final String REPLACEMENT_VARIABLE_SUFFIX = "$replacement"; + + public ReplaceOptions getOptions() { + return options; + } + + public MatchOptions getMatchOptions() { + return options.getMatchOptions(); + } + + public void readExternal(Element element) { + super.readExternal(element); + options.readExternal(element); + } + + public void writeExternal(Element element) { + super.writeExternal(element); + options.writeExternal(element); + } + + public boolean equals(Object configuration) { + if (!super.equals(configuration)) return false; + if (configuration instanceof ReplaceConfiguration) { + return options.equals(((ReplaceConfiguration)configuration).options); + } + return false; + } + + public int hashCode() { + return options.hashCode(); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java new file mode 100644 index 000000000000..2ae0b18469fb --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceDialog.java @@ -0,0 +1,206 @@ +package com.intellij.structuralsearch.plugin.replace.ui; + +import com.intellij.codeInsight.template.impl.Variable; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.ui.Splitter; +import com.intellij.structuralsearch.MalformedPatternException; +import com.intellij.structuralsearch.ReplacementVariableDefinition; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.UnsupportedPatternException; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.ui.*; +import com.intellij.util.containers.hash.LinkedHashMap; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +// Class to show the user the request for search + +@SuppressWarnings({"RefusedBequest"}) +public class ReplaceDialog extends SearchDialog { + private Editor replaceCriteriaEdit; + private JCheckBox shortenFQN; + private JCheckBox formatAccordingToStyle; + + private String mySavedEditorText; + + protected String getDefaultTitle() { + return SSRBundle.message("structural.replace.title"); + } + + protected boolean isChanged(Configuration configuration) { + if (super.isChanged(configuration)) return true; + + String replacement; + + if (configuration instanceof ReplaceConfiguration) { + replacement = ((ReplaceConfiguration)configuration).getOptions().getReplacement(); + } + else { + replacement = configuration.getMatchOptions().getSearchPattern(); + } + + if (replacement == null) return false; + + return !replaceCriteriaEdit.getDocument().getText().equals(replacement); + } + + protected JComponent createEditorContent() { + JPanel result = new JPanel(new BorderLayout()); + Splitter p; + + result.add(BorderLayout.CENTER, p = new Splitter(true, 0.5f)); + p.setFirstComponent(super.createEditorContent()); + + replaceCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : ""); + JPanel replace = new JPanel(new BorderLayout()); + replace.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("replacement.template.label"))); + replace.add(BorderLayout.CENTER, replaceCriteriaEdit.getComponent()); + replaceCriteriaEdit.getComponent().setMinimumSize(new Dimension(150, 100)); + + p.setSecondComponent(replace); + + return result; + } + + protected int getRowsCount() { + return super.getRowsCount() + 1; + } + + protected String getDimensionServiceKey() { + return "#com.intellij.structuralsearch.plugin.replace.ui.ReplaceDialog"; + } + + protected void buildOptions(JPanel searchOptions) { + super.buildOptions(searchOptions); + searchOptions + .add(UIUtil.createOptionLine(shortenFQN = new JCheckBox(SSRBundle.message("shorten.fully.qualified.names.checkbox"), true))); + + searchOptions + .add(UIUtil.createOptionLine(formatAccordingToStyle = new JCheckBox(SSRBundle.message("format.according.to.style.checkbox"), true))); + + } + + protected UsageViewContext createUsageViewContext(Configuration configuration) { + return new ReplaceUsageViewContext(searchContext, configuration); + } + + public ReplaceDialog(SearchContext searchContext) { + super(searchContext); + } + + public ReplaceDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) { + super(searchContext, showScope, runFindActionOnClose); + } + + + public Configuration createConfiguration() { + ReplaceConfiguration configuration = new ReplaceConfiguration(); + configuration.setName(USER_DEFINED); + return configuration; + } + + protected void disposeEditorContent() { + mySavedEditorText = replaceCriteriaEdit.getDocument().getText(); + EditorFactory.getInstance().releaseEditor(replaceCriteriaEdit); + super.disposeEditorContent(); + } + + public void setValuesFromConfig(Configuration configuration) { + //replaceCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration); + + if (configuration instanceof ReplaceConfiguration) { + final ReplaceConfiguration config = (ReplaceConfiguration)configuration; + final ReplaceOptions options = config.getOptions(); + super.setValuesFromConfig(config); + + UIUtil.setContent(replaceCriteriaEdit, config.getOptions().getReplacement(), 0, replaceCriteriaEdit.getDocument().getTextLength(), + searchContext.getProject()); + + shortenFQN.setSelected(options.isToShortenFQN()); + formatAccordingToStyle.setSelected(options.isToReformatAccordingToStyle()); + + ReplaceOptions newReplaceOptions = ((ReplaceConfiguration)model.getConfig()).getOptions(); + newReplaceOptions.clearVariableDefinitions(); + + for (ReplacementVariableDefinition def : options.getReplacementVariableDefinitions()) { + newReplaceOptions.addVariableDefinition((ReplacementVariableDefinition)def.clone()); + } + } + else { + super.setValuesFromConfig(configuration); + + UIUtil.setContent(replaceCriteriaEdit, configuration.getMatchOptions().getSearchPattern(), 0, + replaceCriteriaEdit.getDocument().getTextLength(), searchContext.getProject()); + } + } + + protected void setValuesToConfig(Configuration config) { + super.setValuesToConfig(config); + + final ReplaceConfiguration replaceConfiguration = (ReplaceConfiguration)config; + final ReplaceOptions options = replaceConfiguration.getOptions(); + + options.setMatchOptions(replaceConfiguration.getMatchOptions()); + options.setReplacement(replaceCriteriaEdit.getDocument().getText()); + options.setToShortenFQN(shortenFQN.isSelected()); + options.setToReformatAccordingToStyle(formatAccordingToStyle.isSelected()); + } + + protected boolean isRecursiveSearchEnabled() { + return false; + } + + protected java.util.List<Variable> getVariablesFromListeners() { + ArrayList<Variable> vars = getVarsFrom(replaceCriteriaEdit); + List<Variable> searchVars = super.getVariablesFromListeners(); + Map<String, Variable> varsMap = new LinkedHashMap<String, Variable>(searchVars.size()); + + for(Variable var:searchVars) varsMap.put(var.getName(), var); + for(Variable var:vars) { + if (!varsMap.containsKey(var.getName())) { + String newVarName = var.getName() + ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX; + varsMap.put(newVarName, new Variable(newVarName, null, null, false, false)); + } + } + return new ArrayList<Variable>(varsMap.values()); + } + + protected boolean isValid() { + if (!super.isValid()) return false; + + try { + Replacer.checkSupportedReplacementPattern(searchContext.getProject(), ((ReplaceConfiguration)model.getConfig()).getOptions()); + } + catch (UnsupportedPatternException ex) { + reportMessage("unsupported.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage()); + return false; + } + catch (MalformedPatternException ex) { + reportMessage("malformed.replacement.pattern.message", replaceCriteriaEdit, ex.getMessage()); + return false; + } + + return true; + } + + public void show() { + replaceCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, model.getConfig()); + + super.show(); + } + + protected boolean isReplaceDialog() { + return true; + } + + protected void addOrReplaceSelection(final String selection) { + super.addOrReplaceSelection(selection); + addOrReplaceSelectionForEditor(selection, replaceCriteriaEdit); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java new file mode 100644 index 000000000000..6a0d352d3284 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplaceUsageViewContext.java @@ -0,0 +1,180 @@ +package com.intellij.structuralsearch.plugin.replace.ui; + +import com.intellij.history.LocalHistory; +import com.intellij.history.LocalHistoryAction; +import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ReplacementInfo; +import com.intellij.structuralsearch.plugin.replace.impl.Replacer; +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.plugin.ui.SearchCommand; +import com.intellij.structuralsearch.plugin.ui.SearchContext; +import com.intellij.structuralsearch.plugin.ui.UsageViewContext; +import com.intellij.usageView.UsageInfo; +import com.intellij.usages.Usage; +import com.intellij.usages.UsageInfo2UsageAdapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 9, 2005 + * Time: 4:37:08 PM + * To change this template use File | Settings | File Templates. + */ +class ReplaceUsageViewContext extends UsageViewContext { + private HashMap<Usage,ReplacementInfo> usage2ReplacementInfo; + private Replacer replacer; + + ReplaceUsageViewContext(final SearchContext context, final Configuration configuration) { + super(context,configuration); + } + + protected SearchCommand createCommand() { + ReplaceCommand command = new ReplaceCommand(mySearchContext.getProject(), this); + + usage2ReplacementInfo = new HashMap<Usage, ReplacementInfo>(); + replacer = new Replacer(mySearchContext.getProject(), ((ReplaceConfiguration)myConfiguration).getOptions()); + + return command; + } + + protected String _getPresentableText() { + return SSRBundle.message("replaceusageview.text", + getConfiguration().getMatchOptions().getSearchPattern(), + ((ReplaceConfiguration)getConfiguration()).getOptions().getReplacement() + ); + } + + public Replacer getReplacer() { + return replacer; + } + + public void addReplaceUsage(final Usage usage, final ReplacementInfo replacementInfo) { + usage2ReplacementInfo.put(usage,replacementInfo); + } + + private boolean isValid(UsageInfo2UsageAdapter info) { + final UsageInfo usageInfo = info.getUsageInfo(); + return !isExcluded(info) && usageInfo.getElement() != null && usageInfo.getElement().isValid(); + } + + @Override + protected void configureActions() { + final Runnable replaceRunnable = new Runnable() { + public void run() { + LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title")); + + doReplace(); + getUsageView().close(); + + labelAction.finish(); + } + }; + + //noinspection HardCodedStringLiteral + getUsageView().addPerformOperationAction(replaceRunnable, "Replace All", null, SSRBundle.message("do.replace.all.button")); + + final Runnable replaceSelected = new Runnable() { + public void run() { + final Set<Usage> infos = getUsageView().getSelectedUsages(); + if (infos == null || infos.isEmpty()) return; + + LocalHistoryAction labelAction = LocalHistory.getInstance().startAction(SSRBundle.message("structural.replace.title")); + + for (final Usage info : infos) { + final UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info; + + if (isValid(usage)) { + replaceOne(usage, false); + } + } + + labelAction.finish(); + + if (getUsageView().getUsagesCount() > 0) { + for (Usage usage : getUsageView().getSortedUsages()) { + if (!isExcluded(usage)) { + getUsageView().selectUsages(new Usage[]{usage}); + return; + } + } + } + } + }; + + getUsageView().addButtonToLowerPane(replaceSelected, SSRBundle.message("replace.selected.button")); + + final Runnable previewReplacement = new Runnable() { + public void run() { + Set<Usage> selection = getUsageView().getSelectedUsages(); + + if (selection != null && !selection.isEmpty()) { + UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)selection.iterator().next(); + + if (isValid(usage)) { + replaceOne(usage, true); + } + } + } + }; + + getUsageView().addButtonToLowerPane(previewReplacement, SSRBundle.message("preview.replacement.button")); + + super.configureActions(); + } + + private static void ensureFileWritable(final UsageInfo2UsageAdapter usage) { + final VirtualFile file = usage.getFile(); + + if (file != null && !file.isWritable()) { + ReadonlyStatusHandler.getInstance(usage.getElement().getProject()).ensureFilesWritable(file); + } + } + + private void replaceOne(UsageInfo2UsageAdapter info, boolean doConfirm) { + ReplacementInfo replacementInfo = usage2ReplacementInfo.get(info); + boolean approved; + + if (doConfirm) { + ReplacementPreviewDialog wrapper = + new ReplacementPreviewDialog(mySearchContext.getProject(), info.getUsageInfo(), replacementInfo.getReplacement()); + + wrapper.show(); + approved = wrapper.isOK(); + } + else { + approved = true; + } + + if (approved) { + ensureFileWritable(info); + getUsageView().removeUsage(info); + getReplacer().replace(replacementInfo); + + if (getUsageView().getUsagesCount() == 0) { + getUsageView().close(); + } + } + } + + private void doReplace() { + List<Usage> infos = getUsageView().getSortedUsages(); + List<ReplacementInfo> results = new ArrayList<ReplacementInfo>(infos.size()); + + for (final Usage info : infos) { + UsageInfo2UsageAdapter usage = (UsageInfo2UsageAdapter)info; + + if (isValid(usage)) { + results.add(usage2ReplacementInfo.get(usage)); + } + } + + getReplacer().replaceAll(results); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java new file mode 100644 index 000000000000..5bec5a920b45 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/replace/ui/ReplacementPreviewDialog.java @@ -0,0 +1,132 @@ +package com.intellij.structuralsearch.plugin.replace.ui; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.markup.HighlighterLayer; +import com.intellij.openapi.editor.markup.HighlighterTargetArea; +import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Segment; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchProfile; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.plugin.ui.UIUtil; +import com.intellij.usageView.UsageInfo; + +import javax.swing.*; +import java.awt.*; + +/** + * Navigates through the search results + */ +public final class ReplacementPreviewDialog extends DialogWrapper { + private final FileType myFileType; + private Editor replacement; + + private final Project project; + private RangeHighlighter hilighter; + private Editor editor; + + + public ReplacementPreviewDialog(final Project project, UsageInfo info, String replacementString) { + super(project,true); + + setTitle(SSRBundle.message("structural.replace.preview.dialog.title")); + setOKButtonText(SSRBundle.message("replace.preview.oktext")); + this.project = project; + final PsiElement element = info.getElement(); + final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(element); + myFileType = virtualFile != null ? virtualFile.getFileType() : FileTypes.PLAIN_TEXT; + init(); + + Segment range = info.getSegment(); + hilight(virtualFile, range.getStartOffset(), range.getEndOffset()); + UIUtil.setContent(replacement, replacementString,0,-1,project); + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(element); + if (profile != null) { + UIUtil.updateHighlighter(replacement, profile); + } + } + + private void hilight(VirtualFile file,int start, int end) { + removeHilighter(); + + editor = FileEditorManager.getInstance(project).openTextEditor( + new OpenFileDescriptor(project, file), + false + ); + hilighter = editor.getMarkupModel().addRangeHighlighter( + start, + end, + HighlighterLayer.SELECTION - 100, + EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES), + HighlighterTargetArea.EXACT_RANGE + ); + } + + private void removeHilighter() { + if (hilighter!=null && hilighter.isValid()) { + hilighter.dispose(); + hilighter = null; + editor = null; + } + } + + protected String getDimensionServiceKey() { + return "#com.intellij.strucuturalsearch.plugin.replace.ReplacementPreviewDialog"; + } + + protected JComponent createCenterPanel() { + JComponent centerPanel = new JPanel( new BorderLayout() ); + + PsiFile file = null; + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(myFileType); + if (profile != null) { + file = profile.createCodeFragment(project, "", null); + } + + if (file != null) { + final Document document = PsiDocumentManager.getInstance(project).getDocument(file); + replacement = UIUtil.createEditor(document, project, true, null); + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file,false); + } else { + final EditorFactory factory = EditorFactory.getInstance(); + final Document document = factory.createDocument(""); + replacement = factory.createEditor(document, project, myFileType, false); + } + + centerPanel.add(BorderLayout.NORTH,new JLabel(SSRBundle.message("replacement.code")) ); + centerPanel.add(BorderLayout.CENTER,replacement.getComponent() ); + centerPanel.setMaximumSize(new Dimension(640,480)); + + return centerPanel; + } + + public void dispose() { + final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(replacement.getDocument()); + if (file != null) { + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(file, true); + } + + EditorFactory.getInstance().releaseEditor(replacement); + removeHilighter(); + + super.dispose(); + } +} + diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java new file mode 100644 index 000000000000..440d33164f1d --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/Configuration.java @@ -0,0 +1,87 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.structuralsearch.MatchOptions; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Apr 14, 2004 + * Time: 5:29:37 PM + * To change this template use File | Settings | File Templates. + */ +public abstract class Configuration implements JDOMExternalizable, Comparable<Configuration> { + public static final Configuration[] EMPTY_ARRAY = {}; + @NonNls protected static final String NAME_ATTRIBUTE_NAME = "name"; + private String name = ""; + private String category = null; + private boolean predefined; + + private static ConfigurationCreator configurationCreator; + + public String getName() { + return name; + } + + public void setName(String value) { + name = value; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public void readExternal(Element element) { + name = element.getAttributeValue(NAME_ATTRIBUTE_NAME); + } + + public void writeExternal(Element element) { + element.setAttribute(NAME_ATTRIBUTE_NAME,name); + } + + public boolean isPredefined() { + return predefined; + } + + public void setPredefined(boolean predefined) { + this.predefined = predefined; + } + + public abstract MatchOptions getMatchOptions(); + + @Override + public int compareTo(Configuration other) { + int result = StringUtil.naturalCompare(getCategory(), other.getCategory()); + return result != 0 ? result : StringUtil.naturalCompare(getName(), other.getName()); + } + + public boolean equals(Object configuration) { + if (!(configuration instanceof Configuration)) return false; + Configuration other = (Configuration)configuration; + if (category != null ? !category.equals(other.category) : other.category != null) { + return false; + } + return name.equals(other.name); + } + + public int hashCode() { + return getMatchOptions().hashCode(); + } + + public static void setActiveCreator(ConfigurationCreator creator) { + configurationCreator = creator; + } + + public static ConfigurationCreator getConfigurationCreator() { + return configurationCreator; + } + + @NonNls public static final String CONTEXT_VAR_NAME = "__context__"; +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java new file mode 100644 index 000000000000..74332fd72e74 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationCreator.java @@ -0,0 +1,12 @@ +package com.intellij.structuralsearch.plugin.ui; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Apr 21, 2004 + * Time: 7:46:16 PM + * To change this template use File | Settings | File Templates. + */ +public interface ConfigurationCreator { + Configuration createConfiguration(); +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java new file mode 100644 index 000000000000..151e78d6863f --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ConfigurationManager.java @@ -0,0 +1,179 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.NonEmptyInputValidator; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import org.jdom.Element; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Created by IntelliJ IDEA. + * User: maxim + * Date: 10.02.2004 + * Time: 14:29:45 + * To change this template use File | Settings | File Templates. + */ +public class ConfigurationManager { + @NonNls static final String SEARCH_TAG_NAME = "searchConfiguration"; + @NonNls static final String REPLACE_TAG_NAME = "replaceConfiguration"; + @NonNls private static final String SAVE_HISTORY_ATTR_NAME = "history"; + + private List<Configuration> configurations; + private LinkedList<Configuration> historyConfigurations; + + public void addHistoryConfigurationToFront(Configuration configuration) { + if (historyConfigurations == null) historyConfigurations = new LinkedList<Configuration>(); + + if (historyConfigurations.indexOf(configuration) == -1) { + historyConfigurations.addFirst(configuration); + } + } + + public void removeHistoryConfiguration(Configuration configuration) { + if (historyConfigurations != null) { + historyConfigurations.remove(configuration); + } + } + + public void addConfiguration(Configuration configuration) { + if (configurations == null) configurations = new ArrayList<Configuration>(); + + if (configurations.indexOf(configuration) == -1) { + configurations.add(configuration); + } + } + + public void removeConfiguration(Configuration configuration) { + if (configurations != null) { + configurations.remove(configuration); + } + } + + public void saveConfigurations(Element element) { + writeConfigurations(element, configurations, historyConfigurations); + } + + public static void writeConfigurations(final Element element, + final Collection<Configuration> configurations, + final Collection<Configuration> historyConfigurations) { + if (configurations != null) { + for (final Configuration configuration : configurations) { + saveConfiguration(element, configuration); + } + } + + if (historyConfigurations != null) { + for (final Configuration historyConfiguration : historyConfigurations) { + final Element infoElement = saveConfiguration(element, historyConfiguration); + infoElement.setAttribute(SAVE_HISTORY_ATTR_NAME, "1"); + } + } + } + + public static Element saveConfiguration(Element element, final Configuration config) { + Element infoElement = new Element(config instanceof SearchConfiguration ? SEARCH_TAG_NAME : REPLACE_TAG_NAME); + element.addContent(infoElement); + config.writeExternal(infoElement); + + return infoElement; + } + + public void loadConfigurations(Element element) { + if (configurations != null) return; + ArrayList<Configuration> configurations = new ArrayList<Configuration>(); + ArrayList<Configuration> historyConfigurations = new ArrayList<Configuration>(); + readConfigurations(element, configurations, historyConfigurations); + for (Configuration configuration : historyConfigurations) { + addHistoryConfigurationToFront(configuration); + } + for (Configuration configuration : configurations) { + addConfiguration(configuration); + } + if (this.historyConfigurations != null) { + Collections.reverse(this.historyConfigurations); + } + } + + public static void readConfigurations(final Element element, @NotNull Collection<Configuration> configurations, @NotNull Collection<Configuration> historyConfigurations) { + final List<Element> patterns = element.getChildren(); + + if (patterns != null && patterns.size() > 0) { + for (final Element pattern : patterns) { + final Configuration config = readConfiguration(pattern); + if (config == null) continue; + + if (pattern.getAttribute(SAVE_HISTORY_ATTR_NAME) != null) { + historyConfigurations.add(config); + } + else { + configurations.add(config); + } + } + } + } + + public static Configuration readConfiguration(final Element childElement) { + String s = childElement.getName(); + final Configuration config = + s.equals(SEARCH_TAG_NAME) ? new SearchConfiguration() : s.equals(REPLACE_TAG_NAME) ? new ReplaceConfiguration():null; + if (config != null) config.readExternal(childElement); + return config; + } + + public Collection<Configuration> getConfigurations() { + return configurations; + } + + public static Configuration findConfigurationByName(final Collection<Configuration> configurations, final String name) { + for(Configuration config:configurations) { + if (config.getName().equals(name)) return config; + } + + return null; + } + + public Collection<Configuration> getHistoryConfigurations() { + return historyConfigurations; + } + + public static @Nullable String findAppropriateName(@NotNull final Collection<Configuration> configurations, @NotNull String _name, + @NotNull final Project project) { + Configuration config; + String name = _name; + + while ((config = findConfigurationByName(configurations, name)) != null) { + int i = Messages.showYesNoDialog( + project, + SSRBundle.message("overwrite.message"), + SSRBundle.message("overwrite.title"), + AllIcons.General.QuestionDialog + ); + + if (i == Messages.YES) { + configurations.remove(config); + break; + } + name = showSaveTemplateAsDialog(name, project); + if (name == null) break; + } + return name; + } + + public static @Nullable String showSaveTemplateAsDialog(@NotNull String initial, @NotNull Project project) { + return Messages.showInputDialog( + project, + SSRBundle.message("template.name.button"), + SSRBundle.message("save.template.description.button"), + AllIcons.General.QuestionDialog, + initial, + new NonEmptyInputValidator() + ); + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java new file mode 100644 index 000000000000..83d93ba0d05c --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/DialogBase.java @@ -0,0 +1,152 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.openapi.MnemonicHelper; +import com.intellij.CommonBundle; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.InputEvent; +import java.awt.*; + +import org.jetbrains.annotations.NonNls; + +/** + * Base dialog class + */ +public abstract class DialogBase extends JDialog { + private JButton ok; + private JButton cancel; + + private Action okAction; + private Action cancelAction; + private static Rectangle virtualBounds; + + class OkAction extends AbstractAction { + OkAction() { + putValue(NAME, CommonBundle.getOkButtonText()); + } + public void actionPerformed(ActionEvent e) { + doOKAction(); + } + } + + class CancelAction extends AbstractAction { + CancelAction() { + putValue(NAME,CommonBundle.getCancelButtonText()); + } + + public void actionPerformed(ActionEvent e) { + doCancelAction(); + } + } + + protected DialogBase() { + this(null); + } + + protected DialogBase(Frame frame) { + this(frame,true); + } + + protected DialogBase(Frame frame,boolean modal) { + super(frame,modal); + + new MnemonicHelper().register(getContentPane()); + + okAction = new OkAction(); + cancelAction = new CancelAction(); + + ok = createJButtonForAction(okAction); + cancel = createJButtonForAction(cancelAction); + + if (virtualBounds == null) { + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] gs = ge.getScreenDevices(); + virtualBounds = new Rectangle(); + + for (int j = 0; j < gs.length; j++) { + GraphicsDevice gd = gs[j]; + GraphicsConfiguration[] gc = gd.getConfigurations(); + + for (int i=0; i < gc.length; i++) { + virtualBounds = virtualBounds.union(gc[i].getBounds()); + } + } + } + + @NonNls String cancelCommandName = "close"; + KeyStroke escKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0); + ok.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escKeyStroke, cancelCommandName); + ok.getActionMap().put(cancelCommandName, cancelAction); + + @NonNls String startCommandName = "start"; + KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,InputEvent.CTRL_MASK); + ok.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(enterKeyStroke, startCommandName); + ok.getActionMap().put(startCommandName, okAction); + } + + protected JButton getCancelButton() { + return cancel; + } + + protected JButton getOkButton() { + return ok; + } + + protected abstract JComponent createCenterPanel(); + + protected JComponent createSouthPanel() { + JPanel p = new JPanel( null ); + p.setLayout( new BoxLayout(p,BoxLayout.X_AXIS) ); + p.add(Box.createHorizontalGlue()); + p.add(getOkButton()); + p.add(getCancelButton()); + return p; + } + + public void init() { + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(BorderLayout.CENTER,createCenterPanel()); + getContentPane().add(BorderLayout.SOUTH,createSouthPanel()); + pack(); + + Dimension dim = getPreferredSize(); + setLocation( + (int)(virtualBounds.getWidth()/2 - dim.getWidth()/2), + (int)(virtualBounds.getHeight()/2 - dim.getHeight()/2) + ); + } + + public void show() { + pack(); + super.show(); + } + + protected void doCancelAction() { + setVisible(false); + } + + protected void doOKAction() { + setVisible(false); + } + + protected void setOKActionEnabled(boolean b) { + okAction.setEnabled(b); + } + + protected void setOKButtonText(String text) { + okAction.putValue(Action.NAME,text); + } + + protected void setCancelButtonText(String text) { + cancelAction.putValue(Action.NAME,text); + } + + protected JButton createJButtonForAction(Action _action) { + JButton jb = new JButton( (String)_action.getValue(Action.NAME) ); + jb.setAction(_action); + + return jb; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java new file mode 100644 index 000000000000..368240ff50be --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/EditVarConstraintsDialog.java @@ -0,0 +1,635 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.template.impl.Variable; +import com.intellij.find.impl.RegExHelpPopup; +import com.intellij.ide.highlighter.HighlighterFactory; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.colors.ex.DefaultColorSchemesManager; +import com.intellij.openapi.editor.event.DocumentAdapter; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypes; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.help.HelpManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComponentWithBrowseButton; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.structuralsearch.MatchVariableConstraint; +import com.intellij.structuralsearch.NamedScriptableDefinition; +import com.intellij.structuralsearch.ReplacementVariableDefinition; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.impl.matcher.predicates.ScriptSupport; +import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import com.intellij.ui.ComboboxWithBrowseButton; +import com.intellij.ui.EditorTextField; +import com.intellij.ui.components.labels.LinkLabel; +import com.intellij.ui.components.labels.LinkListener; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.BadLocationException; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * @author Maxim.Mossienko + * Date: Mar 25, 2004 + * Time: 1:52:18 PM + */ +class EditVarConstraintsDialog extends DialogWrapper { + private static final Logger LOG = Logger.getInstance("#com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog"); + + private JTextField maxoccurs; + private JCheckBox applyWithinTypeHierarchy; + private JCheckBox notRegexp; + private EditorTextField regexp; + private JTextField minoccurs; + private JPanel mainForm; + private JCheckBox notWrite; + private JCheckBox notRead; + private JCheckBox write; + private JCheckBox read; + private JList parameterList; + private JCheckBox partOfSearchResults; + private JCheckBox notExprType; + private EditorTextField regexprForExprType; + private final SearchModel model; + private JCheckBox exprTypeWithinHierarchy; + + private final List<Variable> variables; + private Variable current; + private JCheckBox wholeWordsOnly; + private JCheckBox formalArgTypeWithinHierarchy; + private JCheckBox invertFormalArgType; + private EditorTextField formalArgType; + private ComponentWithBrowseButton<EditorTextField> customScriptCode; + private JCheckBox maxoccursUnlimited; + + private ComboboxWithBrowseButton withinCombo; + private JPanel containedInConstraints; + private JCheckBox invertWithinIn; + private JPanel expressionConstraints; + private JPanel occurencePanel; + private JPanel textConstraintsPanel; + private JLabel myRegExHelpLabel; + + private static Project myProject; + + EditVarConstraintsDialog(final Project project,SearchModel _model,List<Variable> _variables, boolean replaceContext, FileType fileType) { + super(project, false); + + variables = _variables; + model = _model; + + setTitle(SSRBundle.message("editvarcontraints.edit.variables")); + + regexp.getDocument().addDocumentListener(new MyDocumentListener(notRegexp, applyWithinTypeHierarchy, wholeWordsOnly)); + read.addChangeListener(new MyChangeListener(notRead, false)); + write.addChangeListener(new MyChangeListener(notWrite, false)); + regexprForExprType.getDocument().addDocumentListener(new MyDocumentListener(exprTypeWithinHierarchy, notExprType)); + formalArgType.getDocument().addDocumentListener(new MyDocumentListener(formalArgTypeWithinHierarchy, invertFormalArgType)); + + partOfSearchResults.setEnabled(!replaceContext); // todo: this doesn't do anything + containedInConstraints.setVisible(false); + withinCombo.getComboBox().setEditable(true); + + withinCombo.getButton().addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final SelectTemplateDialog dialog = new SelectTemplateDialog(project, false, false); + dialog.show(); + if (dialog.getExitCode() == OK_EXIT_CODE) { + final Configuration[] selectedConfigurations = dialog.getSelectedConfigurations(); + if (selectedConfigurations.length == 1) { + withinCombo.getComboBox().getEditor().setItem(selectedConfigurations[0].getMatchOptions().getSearchPattern()); // TODO: + } + } + } + }); + + boolean hasContextVar = false; + for(Variable var:variables) { + if (Configuration.CONTEXT_VAR_NAME.equals(var.getName())) { + hasContextVar = true; break; + } + } + + if (!hasContextVar) { + variables.add(new Variable(Configuration.CONTEXT_VAR_NAME, "", "", true)); + } + + if (fileType == StdFileTypes.JAVA) { + + formalArgTypeWithinHierarchy.setEnabled(true); + invertFormalArgType.setEnabled(true); + formalArgType.setEnabled(true); + + exprTypeWithinHierarchy.setEnabled(true); + notExprType.setEnabled(true); + regexprForExprType.setEnabled(true); + + read.setEnabled(true); + notRead.setEnabled(false); + write.setEnabled(true); + notWrite.setEnabled(false); + + applyWithinTypeHierarchy.setEnabled(true); + } else { + formalArgTypeWithinHierarchy.setEnabled(false); + invertFormalArgType.setEnabled(false); + formalArgType.setEnabled(false); + + exprTypeWithinHierarchy.setEnabled(false); + notExprType.setEnabled(false); + regexprForExprType.setEnabled(false); + + read.setEnabled(false); + notRead.setEnabled(false); + write.setEnabled(false); + notWrite.setEnabled(false); + + applyWithinTypeHierarchy.setEnabled(false); + } + + parameterList.setModel( + new AbstractListModel() { + public Object getElementAt(int index) { + return variables.get(index); + } + + public int getSize() { + return variables.size(); + } + } + ); + + parameterList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); + + parameterList.getSelectionModel().addListSelectionListener( + new ListSelectionListener() { + boolean rollingBackSelection; + + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) return; + if (rollingBackSelection) { + rollingBackSelection=false; + return; + } + final Variable var = variables.get(parameterList.getSelectedIndex()); + if (validateParameters()) { + if (current!=null) copyValuesFromUI(current); + ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { copyValuesToUI(var); }}); + current = var; + } else { + rollingBackSelection = true; + parameterList.setSelectedIndex(e.getFirstIndex()==parameterList.getSelectedIndex()?e.getLastIndex():e.getFirstIndex()); + } + } + } + ); + + parameterList.setCellRenderer( + new DefaultListCellRenderer() { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + String name = ((Variable)value).getName(); + if (Configuration.CONTEXT_VAR_NAME.equals(name)) name = SSRBundle.message("complete.match.variable.name"); + if (isReplacementVariable(name)) { + name = stripReplacementVarDecoration(name); + } + return super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus); + } + } + ); + + maxoccursUnlimited.addChangeListener(new MyChangeListener(maxoccurs, true)); + + customScriptCode.getButton().addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent e) { + final EditScriptDialog dialog = new EditScriptDialog(project, customScriptCode.getChildComponent().getText()); + dialog.show(); + if (dialog.getExitCode() == OK_EXIT_CODE) { + customScriptCode.getChildComponent().setText(dialog.getScriptText()); + } + } + }); + init(); + + if (variables.size() > 0) parameterList.setSelectedIndex(0); + } + + private static String stripReplacementVarDecoration(String name) { + name = name.substring(0, name.length() - ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX.length()); + return name; + } + + private static boolean isReplacementVariable(String name) { + return name.endsWith(ReplaceConfiguration.REPLACEMENT_VARIABLE_SUFFIX); + } + + private boolean validateParameters() { + return validateRegExp(regexp) && validateRegExp(regexprForExprType) && + validateIntOccurence(minoccurs) && + validateScript(customScriptCode.getChildComponent()) && + (maxoccursUnlimited.isSelected() || validateIntOccurence(maxoccurs)); + } + + protected JComponent createCenterPanel() { + return mainForm; + } + + protected void doOKAction() { + if(validateParameters()) { + if (current!=null) copyValuesFromUI(current); + super.doOKAction(); + } + } + + void copyValuesFromUI(Variable var) { + String varName = var.getName(); + Configuration configuration = model.getConfig(); + + if (isReplacementVariable(varName)) { + saveScriptInfo(getOrAddReplacementVariableDefinition(varName, configuration)); + return; + } + + MatchVariableConstraint varInfo = getOrAddVariableConstraint(varName, configuration); + + varInfo.setInvertReadAccess(notRead.isSelected()); + varInfo.setReadAccess(read.isSelected()); + varInfo.setInvertWriteAccess(notWrite.isSelected()); + varInfo.setWriteAccess(write.isSelected()); + varInfo.setRegExp(regexp.getDocument().getText()); + varInfo.setInvertRegExp(notRegexp.isSelected()); + + int minCount = Integer.parseInt( minoccurs.getText() ); + varInfo.setMinCount(minCount); + + int maxCount; + if (maxoccursUnlimited.isSelected()) maxCount = Integer.MAX_VALUE; + else maxCount = Integer.parseInt( maxoccurs.getText() ); + + varInfo.setMaxCount(maxCount); + varInfo.setWithinHierarchy(applyWithinTypeHierarchy.isSelected()); + varInfo.setInvertRegExp(notRegexp.isSelected()); + + varInfo.setPartOfSearchResults(partOfSearchResults.isEnabled() && partOfSearchResults.isSelected()); + + varInfo.setInvertExprType(notExprType.isSelected()); + varInfo.setNameOfExprType(regexprForExprType.getDocument().getText()); + varInfo.setExprTypeWithinHierarchy(exprTypeWithinHierarchy.isSelected()); + varInfo.setWholeWordsOnly(wholeWordsOnly.isSelected()); + varInfo.setInvertFormalType(invertFormalArgType.isSelected()); + varInfo.setFormalArgTypeWithinHierarchy(formalArgTypeWithinHierarchy.isSelected()); + varInfo.setNameOfFormalArgType(formalArgType.getDocument().getText()); + saveScriptInfo(varInfo); + + final String withinConstraint = (String)withinCombo.getComboBox().getEditor().getItem(); + varInfo.setWithinConstraint(withinConstraint.length() > 0 ? "\"" + withinConstraint +"\"":""); + varInfo.setInvertWithinConstraint(invertWithinIn.isSelected()); + } + + private static MatchVariableConstraint getOrAddVariableConstraint(String varName, Configuration configuration) { + MatchVariableConstraint varInfo = configuration.getMatchOptions().getVariableConstraint(varName); + + if (varInfo == null) { + varInfo = new MatchVariableConstraint(); + varInfo.setName(varName); + configuration.getMatchOptions().addVariableConstraint(varInfo); + } + return varInfo; + } + + private static ReplacementVariableDefinition getOrAddReplacementVariableDefinition(String varName, Configuration configuration) { + ReplaceOptions replaceOptions = ((ReplaceConfiguration)configuration).getOptions(); + String realVariableName = stripReplacementVarDecoration(varName); + ReplacementVariableDefinition variableDefinition = replaceOptions.getVariableDefinition(realVariableName); + + if (variableDefinition == null) { + variableDefinition = new ReplacementVariableDefinition(); + variableDefinition.setName(realVariableName); + replaceOptions.addVariableDefinition(variableDefinition); + } + return variableDefinition; + } + + private void saveScriptInfo(NamedScriptableDefinition varInfo) { + varInfo.setScriptCodeConstraint("\"" + customScriptCode.getChildComponent().getText() + "\""); + } + + private void copyValuesToUI(Variable var) { + Configuration configuration = model.getConfig(); + String varName = var.getName(); + + if (isReplacementVariable(varName)) { + ReplacementVariableDefinition definition = ((ReplaceConfiguration)configuration).getOptions().getVariableDefinition( + stripReplacementVarDecoration(varName) + ); + + restoreScriptCode(definition); + setSearchConstraintsVisible(false); + return; + } else { + setSearchConstraintsVisible(true); + } + + MatchVariableConstraint varInfo = configuration.getMatchOptions().getVariableConstraint(varName); + + if (varInfo == null) { + notRead.setSelected(false); + notRegexp.setSelected(false); + read.setSelected(false); + notWrite.setSelected(false); + write.setSelected(false); + regexp.getDocument().setText(""); + + minoccurs.setText("1"); + maxoccurs.setText("1"); + maxoccursUnlimited.setSelected(false); + applyWithinTypeHierarchy.setSelected(false); + partOfSearchResults.setSelected(false); + + regexprForExprType.getDocument().setText(""); + notExprType.setSelected(false); + exprTypeWithinHierarchy.setSelected(false); + wholeWordsOnly.setSelected(false); + + invertFormalArgType.setSelected(false); + formalArgTypeWithinHierarchy.setSelected(false); + formalArgType.getDocument().setText(""); + customScriptCode.getChildComponent().setText(""); + + withinCombo.getComboBox().getEditor().setItem(""); + invertWithinIn.setSelected(false); + } else { + notRead.setSelected(varInfo.isInvertReadAccess()); + read.setSelected(varInfo.isReadAccess()); + notWrite.setSelected(varInfo.isInvertWriteAccess()); + write.setSelected(varInfo.isWriteAccess()); + + applyWithinTypeHierarchy.setSelected(varInfo.isWithinHierarchy()); + regexp.getDocument().setText(varInfo.getRegExp()); + //doProcessing(applyWithinTypeHierarchy,regexp); + + notRegexp.setSelected(varInfo.isInvertRegExp()); + minoccurs.setText(Integer.toString(varInfo.getMinCount())); + + if(varInfo.getMaxCount() == Integer.MAX_VALUE) { + maxoccursUnlimited.setSelected(true); + maxoccurs.setText(""); + } else { + maxoccursUnlimited.setSelected(false); + maxoccurs.setText(Integer.toString(varInfo.getMaxCount())); + } + + partOfSearchResults.setSelected( partOfSearchResults.isEnabled() && varInfo.isPartOfSearchResults() ); + + exprTypeWithinHierarchy.setSelected(varInfo.isExprTypeWithinHierarchy()); + regexprForExprType.getDocument().setText(varInfo.getNameOfExprType()); + + notExprType.setSelected( varInfo.isInvertExprType() ); + wholeWordsOnly.setSelected( varInfo.isWholeWordsOnly() ); + + invertFormalArgType.setSelected( varInfo.isInvertFormalType() ); + formalArgTypeWithinHierarchy.setSelected(varInfo.isFormalArgTypeWithinHierarchy()); + formalArgType.getDocument().setText(varInfo.getNameOfFormalArgType()); + restoreScriptCode(varInfo); + + withinCombo.getComboBox().getEditor().setItem(StringUtil.stripQuotesAroundValue(varInfo.getWithinConstraint())); + invertWithinIn.setSelected(varInfo.isInvertWithinConstraint()); + } + + boolean isExprContext = true; + final boolean contextVar = Configuration.CONTEXT_VAR_NAME.equals(var.getName()); + if (contextVar) isExprContext = false; + containedInConstraints.setVisible(contextVar); + expressionConstraints.setVisible(isExprContext); + partOfSearchResults.setEnabled(!contextVar); //? + + occurencePanel.setVisible(!contextVar); + } + + private void setSearchConstraintsVisible(boolean b) { + textConstraintsPanel.setVisible(b); + occurencePanel.setVisible(b); + expressionConstraints.setVisible(b); + partOfSearchResults.setVisible(b); + containedInConstraints.setVisible(b); + pack(); + } + + private void restoreScriptCode(NamedScriptableDefinition varInfo) { + customScriptCode.getChildComponent().setText( + varInfo != null ? StringUtil.stripQuotesAroundValue(varInfo.getScriptCodeConstraint()) : ""); + } + + protected String getDimensionServiceKey() { + return "#com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog"; + } + + private static boolean validateRegExp(EditorTextField field) { + try { + final String s = field.getDocument().getText(); + if (s.length() > 0) { + Pattern.compile(s); + } + } catch(PatternSyntaxException ex) { + Messages.showErrorDialog(SSRBundle.message("invalid.regular.expression"), SSRBundle.message("invalid.regular.expression")); + field.requestFocus(); + return false; + } + return true; + } + + private static boolean validateScript(EditorTextField field) { + final String text = field.getText(); + + if (text.length() > 0) { + final String message = ScriptSupport.checkValidScript(text); + + if (message != null) { + Messages.showErrorDialog(message, SSRBundle.message("invalid.groovy.script")); + field.requestFocus(); + return false; + } + } + return true; + } + + private static boolean validateIntOccurence(JTextField field) { + try { + int a = Integer.parseInt(field.getText()); + if (a==-1) throw new NumberFormatException(); + } catch(NumberFormatException ex) { + Messages.showErrorDialog(SSRBundle.message("invalid.occurence.count"), SSRBundle.message("invalid.occurence.count")); + field.requestFocus(); + return false; + } + return true; + } + + @NotNull + protected Action[] createActions() { + return new Action[]{getOKAction(), getCancelAction(), getHelpAction()}; + } + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp("reference.dialogs.search.replace.structural.editvariable"); + } + + private void createUIComponents() { + regexp = createRegexComponent(); + regexprForExprType = createRegexComponent(); + formalArgType = createRegexComponent(); + customScriptCode = new ComponentWithBrowseButton<EditorTextField>(createScriptComponent(), null); + + myRegExHelpLabel = new LinkLabel(SSRBundle.message("regular.expression.help.label"), null, new LinkListener() { + public void linkSelected(LinkLabel aSource, Object aLinkData) { + try { + final JBPopup helpPopup = RegExHelpPopup.createRegExHelpPopup(); + helpPopup.showInCenterOf(mainForm); + } + catch (BadLocationException e) { + LOG.info(e); + } + } + }); + + myRegExHelpLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); + } + + private static EditorTextField createRegexComponent() { + @NonNls final String fileName = "1.regexp"; + final FileType fileType = getFileType(fileName); + final Document doc = createDocument(fileName, fileType, ""); + return new EditorTextField(doc, myProject, fileType); + } + + private static EditorTextField createScriptComponent() { + @NonNls final String fileName = "1.groovy"; + final FileType fileType = getFileType(fileName); + final Document doc = createDocument(fileName, fileType, ""); + return new EditorTextField(doc, myProject, fileType); + } + + private static Document createDocument(final String fileName, final FileType fileType, String text) { + final PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText(fileName, fileType, text, -1, true); + + return PsiDocumentManager.getInstance(myProject).getDocument(file); + } + + private static FileType getFileType(final String fileName) { + FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName); + if (fileType == FileTypes.UNKNOWN) fileType = FileTypes.PLAIN_TEXT; + return fileType; + } + + public static void setProject(final Project project) { + myProject = project; + } + + private static class MyChangeListener implements ChangeListener { + private final JComponent component; + private final boolean inverted; + + MyChangeListener(JComponent _component, boolean _inverted) { + component = _component; + inverted = _inverted; + } + + public void stateChanged(ChangeEvent e) { + final JCheckBox jCheckBox = (JCheckBox)e.getSource(); + component.setEnabled(inverted ^ jCheckBox.isSelected()); + } + } + + private static class MyDocumentListener extends DocumentAdapter { + private final JComponent[] components; + + private MyDocumentListener(JComponent... _components) { + components = _components; + } + + @Override + public void documentChanged(DocumentEvent e) { + final boolean enable = e.getDocument().getTextLength() > 0; + for (JComponent component : components) { + component.setEnabled(enable); + } + } + } + + private static Editor createEditor(final Project project, final String text, final String fileName) { + final FileType fileType = getFileType(fileName); + final Document doc = createDocument(fileName, fileType, text); + final Editor editor = EditorFactory.getInstance().createEditor(doc, project); + + ((EditorEx)editor).setEmbeddedIntoDialogWrapper(true); + final EditorSettings settings = editor.getSettings(); + settings.setLineNumbersShown(false); + settings.setFoldingOutlineShown(false); + settings.setRightMarginShown(false); + settings.setLineMarkerAreaShown(false); + settings.setIndentGuidesShown(false); + ((EditorEx)editor).setHighlighter(HighlighterFactory.createHighlighter(fileType, DefaultColorSchemesManager.getInstance().getAllSchemes()[0], project)); + + return editor; + } + + private static class EditScriptDialog extends DialogWrapper { + private final Editor editor; + + public EditScriptDialog(final Project project, String text) { + super(project, true); + setTitle(SSRBundle.message("edit.groovy.script.constraint.title")); + editor = createEditor(project, text, "1.groovy"); + init(); + } + + @Override + protected String getDimensionServiceKey() { + return getClass().getName(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return editor.getContentComponent(); + } + + protected JComponent createCenterPanel() { + return editor.getComponent(); + } + + String getScriptText() { + return editor.getDocument().getText(); + } + + @Override + protected void dispose() { + EditorFactory.getInstance().releaseEditor(editor); + super.dispose(); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java new file mode 100644 index 000000000000..3dd79e16175c --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/ExistingTemplatesComponent.java @@ -0,0 +1,333 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.StructuralSearchUtil; +import com.intellij.structuralsearch.plugin.StructuralSearchPlugin; +import com.intellij.ui.*; +import com.intellij.ui.components.JBList; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.tree.TreeUtil; + +import javax.swing.*; +import javax.swing.tree.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Apr 2, 2004 + * Time: 1:27:54 PM + * To change this template use File | Settings | File Templates. + */ +public class ExistingTemplatesComponent { + private final Tree patternTree; + private final DefaultTreeModel patternTreeModel; + private final DefaultMutableTreeNode userTemplatesNode; + private final JComponent panel; + private final DefaultListModel historyModel; + private final JList historyList; + private final JComponent historyPanel; + private DialogWrapper owner; + private final Project project; + + private ExistingTemplatesComponent(Project project) { + + this.project = project; + final DefaultMutableTreeNode root; + patternTreeModel = new DefaultTreeModel( + root = new DefaultMutableTreeNode(null) + ); + + DefaultMutableTreeNode parent = null; + String lastCategory = null; + LinkedList<Object> nodesToExpand = new LinkedList<Object>(); + + final List<Configuration> predefined = StructuralSearchUtil.getPredefinedTemplates(); + for (final Configuration info : predefined) { + final DefaultMutableTreeNode node = new DefaultMutableTreeNode(info); + + if (lastCategory == null || !lastCategory.equals(info.getCategory())) { + if (info.getCategory().length() > 0) { + root.add(parent = new DefaultMutableTreeNode(info.getCategory())); + nodesToExpand.add(parent); + lastCategory = info.getCategory(); + } + else { + root.add(node); + continue; + } + } + + parent.add(node); + } + + parent = new DefaultMutableTreeNode(SSRBundle.message("user.defined.category")); + userTemplatesNode = parent; + root.add(parent); + nodesToExpand.add(parent); + + final ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(this.project).getConfigurationManager(); + if (configurationManager.getConfigurations() != null) { + for (final Configuration config : configurationManager.getConfigurations()) { + parent.add(new DefaultMutableTreeNode(config)); + } + } + + patternTree = createTree(patternTreeModel); + + for (final Object aNodesToExpand : nodesToExpand) { + patternTree.expandPath( + new TreePath(new Object[]{root, aNodesToExpand}) + ); + } + + panel = ToolbarDecorator.createDecorator(patternTree) + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + addSelectedTreeNodeAndClose(); + } + }).setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + Object selection = patternTree.getLastSelectedPathComponent(); + + if (selection instanceof DefaultMutableTreeNode) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)selection; + + if (node.getUserObject() instanceof Configuration) { + Configuration configuration = (Configuration)node.getUserObject(); + patternTreeModel.removeNodeFromParent(node); + configurationManager.removeConfiguration(configuration); + } + } + } + }).createPanel(); + + new JPanel(new BorderLayout()); + + configureSelectTemplateAction(patternTree); + + historyModel = new DefaultListModel(); + historyPanel = new JPanel(new BorderLayout()); + historyPanel.add( + BorderLayout.NORTH, + new JLabel(SSRBundle.message("used.templates")) + ); + Component view = historyList = new JBList(historyModel); + historyPanel.add( + BorderLayout.CENTER, + ScrollPaneFactory.createScrollPane(view) + ); + + historyList.setCellRenderer( + new ListCellRenderer() + ); + + historyList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + new ListSpeedSearch(historyList); + + if (configurationManager.getHistoryConfigurations() != null) { + for (final Configuration configuration : configurationManager.getHistoryConfigurations()) { + historyModel.addElement(configuration); + } + + historyList.setSelectedIndex(0); + } + + configureSelectTemplateAction(historyList); + } + + private void configureSelectTemplateAction(JComponent component) { + component.addKeyListener( + new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + owner.close(DialogWrapper.OK_EXIT_CODE); + } + } + } + ); + + new DoubleClickListener() { + @Override + protected boolean onDoubleClick(MouseEvent event) { + owner.close(DialogWrapper.OK_EXIT_CODE); + return true; + } + }.installOn(component); + } + + private void addSelectedTreeNodeAndClose() { + addConfigurationToUserTemplates( + Configuration.getConfigurationCreator().createConfiguration() + ); + owner.close(DialogWrapper.OK_EXIT_CODE); + } + + private static Tree createTree(TreeModel treeModel) { + final Tree tree = new Tree(treeModel); + + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + tree.setDragEnabled(false); + tree.setEditable(false); + tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + + tree.setCellRenderer(new TreeCellRenderer()); + + new TreeSpeedSearch( + tree, + new Convertor<TreePath, String>() { + public String convert(TreePath object) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode)object.getLastPathComponent(); + Object displayValue = node.getUserObject(); + + if (displayValue instanceof Configuration) { + displayValue = ((Configuration)displayValue).getName(); + } + else { + displayValue = ""; + } + return displayValue.toString(); + } + } + ); + + return tree; + } + + public JTree getPatternTree() { + return patternTree; + } + + public JComponent getTemplatesPanel() { + return panel; + } + + public static ExistingTemplatesComponent getInstance(Project project) { + StructuralSearchPlugin plugin = StructuralSearchPlugin.getInstance(project); + + if (plugin.getExistingTemplatesComponent() == null) { + plugin.setExistingTemplatesComponent(new ExistingTemplatesComponent(project)); + } + + return plugin.getExistingTemplatesComponent(); + } + + static class ListCellRenderer extends DefaultListCellRenderer { + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value instanceof Configuration) { + value = ((Configuration)value).getName(); + } + + Component comp = super.getListCellRendererComponent( + list, + value, + index, + isSelected, + cellHasFocus + ); + + return comp; + } + } + + static class TreeCellRenderer extends DefaultTreeCellRenderer { + TreeCellRenderer() { + setOpenIcon(null); + setLeafIcon(null); + setClosedIcon(null); + } + + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean sel, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value; + Object displayValue = treeNode.getUserObject(); + + if (displayValue instanceof Configuration) { + displayValue = ((Configuration)displayValue).getName(); + } + + Component comp = super.getTreeCellRendererComponent( + tree, + displayValue, + sel, + expanded, + leaf, + row, + hasFocus + ); + + return comp; + } + } + + void addConfigurationToHistory(Configuration configuration) { + //configuration.setName( configuration.getName() +" "+new Date()); + historyModel.insertElementAt(configuration, 0); + ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager(); + configurationManager.addHistoryConfigurationToFront(configuration); + historyList.setSelectedIndex(0); + + if (historyModel.getSize() > 25) { + configurationManager.removeHistoryConfiguration( + (Configuration)historyModel.getElementAt(25) + ); + // we add by one! + historyModel.removeElementAt(25); + } + } + + private void insertNode(Configuration configuration, DefaultMutableTreeNode parent, int index) { + DefaultMutableTreeNode node; + patternTreeModel.insertNodeInto( + node = new DefaultMutableTreeNode( + configuration + ), + parent, + index + ); + + TreeUtil.selectPath( + patternTree, + new TreePath(new Object[]{patternTreeModel.getRoot(), parent, node}) + ); + } + + void addConfigurationToUserTemplates(Configuration configuration) { + insertNode(configuration, userTemplatesNode, userTemplatesNode.getChildCount()); + ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager(); + configurationManager.addConfiguration(configuration); + } + + boolean isConfigurationFromHistory(Configuration config) { + return historyModel.indexOf(config) != -1; + } + + public JList getHistoryList() { + return historyList; + } + + public JComponent getHistoryPanel() { + return historyPanel; + } + + public void setOwner(DialogWrapper owner) { + this.owner = owner; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java new file mode 100644 index 000000000000..7c51fa85af3b --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchCommand.java @@ -0,0 +1,145 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.notification.NotificationGroup; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNameIdentifierOwner; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.MatchResultImpl; +import com.intellij.structuralsearch.plugin.StructuralSearchPlugin; +import com.intellij.structuralsearch.plugin.ui.actions.DoSearchAction; +import com.intellij.usageView.UsageInfo; +import com.intellij.usages.Usage; +import com.intellij.usages.UsageInfo2UsageAdapter; +import com.intellij.util.Alarm; +import com.intellij.util.ObjectUtils; +import com.intellij.util.Processor; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 15, 2004 + * Time: 4:49:07 PM + * To change this template use File | Settings | File Templates. + */ +public class SearchCommand { + protected UsageViewContext context; + private MatchingProcess process; + protected Project project; + + public SearchCommand(Project _project, UsageViewContext _context) { + project = _project; + context = _context; + } + + public void findUsages(final Processor<Usage> processor) { + final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); + + try { + DoSearchAction.execute( + project, + new MatchResultSink() { + int count; + + public void setMatchingProcess(MatchingProcess _process) { + process = _process; + findStarted(); + } + + public void processFile(PsiFile element) { + final VirtualFile virtualFile = element.getVirtualFile(); + if (virtualFile != null) + progress.setText(SSRBundle.message("looking.in.progress.message", virtualFile.getPresentableName())); + } + + public void matchingFinished() { + findEnded(); + progress.setText(SSRBundle.message("found.progress.message", count)); + } + + public ProgressIndicator getProgressIndicator() { + return progress; + } + + public void newMatch(MatchResult result) { + UsageInfo info; + + if (MatchResult.MULTI_LINE_MATCH.equals(result.getName())) { + int start = -1; + int end = -1; + PsiElement parent = result.getMatchRef().getElement().getParent(); + + for (final MatchResult matchResult : ((MatchResultImpl)result).getMatches()) { + PsiElement el = matchResult.getMatchRef().getElement(); + final int elementStart = el.getTextRange().getStartOffset(); + + if (start == -1 || start > elementStart) { + start = elementStart; + } + final int newend = elementStart + el.getTextLength(); + + if (newend > end) { + end = newend; + } + } + + final int parentStart = parent.getTextRange().getStartOffset(); + int startOffset = start - parentStart; + info = new UsageInfo(parent, startOffset, end - parentStart); + } + else { + PsiElement element = result.getMatch(); + if (element instanceof PsiNameIdentifierOwner) { + element = ObjectUtils.notNull(((PsiNameIdentifierOwner)element).getNameIdentifier(), element); + } + info = new UsageInfo(element, result.getStart(), result.getEnd() == -1 ? element.getTextLength() : result.getEnd()); + } + + Usage usage = new UsageInfo2UsageAdapter(info); + processor.process(usage); + foundUsage(result, usage); + ++count; + } + }, + context.getConfiguration() + ); + } + catch (final StructuralSearchException e) { + final Alarm alarm = new Alarm(); + alarm.addRequest( + new Runnable() { + @Override + public void run() { + NotificationGroup.toolWindowGroup("Structural Search", ToolWindowId.FIND, true) + .createNotification(SSRBundle.message("problem", e.getMessage()), MessageType.ERROR).notify(project); + } + }, + 100, ModalityState.NON_MODAL + ); + } + } + + public void stopAsyncSearch() { + if (process!=null) process.stop(); + } + + protected void findStarted() { + StructuralSearchPlugin.getInstance(project).setSearchInProgress(true); + } + + protected void findEnded() { + if (!project.isDisposed()) { + StructuralSearchPlugin.getInstance(project).setSearchInProgress(false); + } + } + + protected void foundUsage(MatchResult result, Usage usage) { + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java new file mode 100644 index 000000000000..afb2f9451bbe --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchConfiguration.java @@ -0,0 +1,39 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.openapi.actionSystem.AnAction; +import org.jdom.Element; +import org.jdom.Attribute; +import org.jdom.DataConversionException; + +/** + * Configuration of the search + */ +public class SearchConfiguration extends Configuration { + private MatchOptions matchOptions; + + public SearchConfiguration() { + matchOptions = new MatchOptions(); + } + + public MatchOptions getMatchOptions() { + return matchOptions; + } + + public void setMatchOptions(MatchOptions matchOptions) { + this.matchOptions = matchOptions; + } + + public void readExternal(Element element) { + super.readExternal(element); + + matchOptions.readExternal(element); + } + + public void writeExternal(Element element) { + super.writeExternal(element); + + matchOptions.writeExternal(element); + } + +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java new file mode 100644 index 000000000000..62371e7e29d1 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchContext.java @@ -0,0 +1,59 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.structuralsearch.impl.matcher.DataProvider; + +/** + * Context of the search to be done + */ +public final class SearchContext implements DataProvider, Cloneable { + private final PsiFile file; + private final Project project; + + private SearchContext(Project project, PsiFile file) { + this.project = project; + this.file = file; + } + + public PsiFile getFile() { + return file; + } + + public Project getProject() { + return project; + } + + public static SearchContext buildFromDataContext(DataContext context) { + Project project = CommonDataKeys.PROJECT.getData(context); + if (project == null) { + project = ProjectManager.getInstance().getDefaultProject(); + } + + PsiFile file = CommonDataKeys.PSI_FILE.getData(context); + final VirtualFile vFile = CommonDataKeys.VIRTUAL_FILE.getData(context); + if (vFile != null && (file == null || !vFile.equals(file.getContainingFile().getVirtualFile()))) { + file = PsiManager.getInstance(project).findFile(vFile); + } + return new SearchContext(project, file); + } + + public Editor getEditor() { + return FileEditorManager.getInstance(project).getSelectedTextEditor(); + } + + protected Object clone() { + try { + return super.clone(); + } catch(CloneNotSupportedException ex) { + return null; + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java new file mode 100644 index 000000000000..5255472d4ce6 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchDialog.java @@ -0,0 +1,1000 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.codeInsight.template.impl.Variable; +import com.intellij.find.FindBundle; +import com.intellij.find.FindProgressIndicator; +import com.intellij.find.FindSettings; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.util.scopeChooser.ScopeChooserCombo; +import com.intellij.lang.Language; +import com.intellij.lang.LanguageUtil; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.LanguageFileType; +import com.intellij.openapi.fileTypes.impl.FileTypeRenderer; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.SearchScope; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.impl.matcher.MatcherImpl; +import com.intellij.structuralsearch.plugin.StructuralSearchPlugin; +import com.intellij.ui.ComboboxSpeedSearch; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.ListCellRendererWrapper; +import com.intellij.ui.TitledSeparator; +import com.intellij.usages.*; +import com.intellij.util.Alarm; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.*; +import java.util.List; + +/** + * Class to show the user the request for search + */ +@SuppressWarnings({"RefusedBequest", "AssignmentToStaticFieldFromInstanceMethod"}) +public class SearchDialog extends DialogWrapper implements ConfigurationCreator { + protected SearchContext searchContext; + + // text for search + protected Editor searchCriteriaEdit; + + // options of search scope + private ScopeChooserCombo myScopeChooserCombo; + + private JCheckBox recursiveMatching; + private JCheckBox caseSensitiveMatch; + + private JComboBox fileTypes; + private JComboBox contexts; + private JComboBox dialects; + private JLabel status; + private JLabel statusText; + + protected SearchModel model; + private JCheckBox openInNewTab; + private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); + + public static final String USER_DEFINED = SSRBundle.message("new.template.defaultname"); + protected final ExistingTemplatesComponent existingTemplatesComponent; + + private boolean useLastConfiguration; + + private static boolean ourOpenInNewTab; + + @NonNls private FileType ourFtSearchVariant = StructuralSearchUtil.getDefaultFileType(); + private static Language ourDialect = null; + private static String ourContext = null; + + private final boolean myShowScopePanel; + private final boolean myRunFindActionOnClose; + private boolean myDoingOkAction; + + private String mySavedEditorText; + private JPanel myContentPanel; + private JComponent myEditorPanel; + + public SearchDialog(SearchContext searchContext) { + this(searchContext, true, true); + } + + public SearchDialog(SearchContext searchContext, boolean showScope, boolean runFindActionOnClose) { + super(searchContext.getProject(), true); + + if (showScope) setModal(false); + myShowScopePanel = showScope; + myRunFindActionOnClose = runFindActionOnClose; + this.searchContext = (SearchContext)searchContext.clone(); + setTitle(getDefaultTitle()); + + if (runFindActionOnClose) { + setOKButtonText(FindBundle.message("find.dialog.find.button")); + } + + existingTemplatesComponent = ExistingTemplatesComponent.getInstance(this.searchContext.getProject()); + model = new SearchModel(createConfiguration()); + + init(); + } + + protected UsageViewContext createUsageViewContext(Configuration configuration) { + return new UsageViewContext(searchContext, configuration); + } + + public void setUseLastConfiguration(boolean useLastConfiguration) { + this.useLastConfiguration = useLastConfiguration; + } + + protected boolean isChanged(Configuration configuration) { + return configuration.getMatchOptions().getSearchPattern() != null && + !searchCriteriaEdit.getDocument().getText().equals(configuration.getMatchOptions().getSearchPattern()); + } + + public void setSearchPattern(final Configuration config) { + model.setShadowConfig(config); + setValuesFromConfig(config); + initiateValidation(); + } + + protected Editor createEditor(final SearchContext searchContext, String text) { + Editor editor = null; + + if (fileTypes != null) { + final FileType fileType = (FileType)fileTypes.getSelectedItem(); + final Language dialect = (Language)dialects.getSelectedItem(); + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); + if (profile != null) { + editor = profile.createEditor(searchContext, fileType, dialect, text, useLastConfiguration); + } + } + + if (editor == null) { + final EditorFactory factory = EditorFactory.getInstance(); + final Document document = factory.createDocument(""); + editor = factory.createEditor(document, searchContext.getProject()); + editor.getSettings().setFoldingOutlineShown(false); + } + + editor.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void beforeDocumentChange(final DocumentEvent event) { + } + + @Override + public void documentChanged(final DocumentEvent event) { + initiateValidation(); + } + }); + + return editor; + } + + private void initiateValidation() { + myAlarm.cancelAllRequests(); + myAlarm.addRequest(new Runnable() { + + @Override + public void run() { + try { + new WriteAction(){ + @Override + protected void run(Result result) throws Throwable { + if (!isValid()) { + getOKAction().setEnabled(false); + } + else { + getOKAction().setEnabled(true); + reportMessage(null, null); + } + } + }.execute(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + }, 500); + } + + protected void buildOptions(JPanel searchOptions) { + recursiveMatching = new JCheckBox(SSRBundle.message("recursive.matching.checkbox"), true); + if (isRecursiveSearchEnabled()) { + searchOptions.add(UIUtil.createOptionLine(recursiveMatching)); + } + + caseSensitiveMatch = new JCheckBox(FindBundle.message("find.options.case.sensitive"), true); + searchOptions.add(UIUtil.createOptionLine(caseSensitiveMatch)); + + final List<FileType> types = new ArrayList<FileType>(); + + for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) { + if (StructuralSearchUtil.getProfileByFileType(fileType) != null) { + types.add(fileType); + } + } + Collections.sort(types, new Comparator<FileType>() { + @Override + public int compare(FileType o1, FileType o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }); + + final DefaultComboBoxModel comboBoxModel = new DefaultComboBoxModel(types.toArray(new FileType[types.size()])); + comboBoxModel.setSelectedItem(ourFtSearchVariant); + fileTypes = new ComboBox(comboBoxModel); + fileTypes.setRenderer(new FileTypeRenderer()); + new ComboboxSpeedSearch(fileTypes) { + @Override + protected String getElementText(Object element) { + return ((FileType)element).getName(); + } + }; + fileTypes.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + updateDialectsAndContexts(); + updateEditor(); + } + }); + + contexts = new JComboBox(new DefaultComboBoxModel()); + contexts.setPreferredSize(new Dimension(60, -1)); + + dialects = new JComboBox(new DefaultComboBoxModel()); + dialects.setRenderer(new ListCellRendererWrapper() { + @Override + public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) { + if (value == null) { + setText("None"); + } + else if (value instanceof Language) { + setText(((Language)value).getDisplayName()); + } + } + }); + dialects.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + updateEditor(); + } + }); + new ComboboxSpeedSearch(dialects); + dialects.setPreferredSize(new Dimension(120, -1)); + + final JLabel jLabel = new JLabel(SSRBundle.message("search.dialog.file.type.label")); + final JLabel jLabel2 = new JLabel(SSRBundle.message("search.dialog.context.label")); + final JLabel jLabel3 = new JLabel(SSRBundle.message("search.dialog.file.dialect.label")); + searchOptions.add( + UIUtil.createOptionLine( + new JComponent[]{ + jLabel, + fileTypes, + (JComponent)Box.createHorizontalStrut(8), + jLabel2, + contexts, + (JComponent)Box.createHorizontalStrut(8), + jLabel3, + dialects, + } + ) + ); + + jLabel.setLabelFor(fileTypes); + jLabel2.setLabelFor(contexts); + jLabel3.setLabelFor(dialects); + + detectFileTypeAndDialect(); + + fileTypes.setSelectedItem(ourFtSearchVariant); + fileTypes.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) initiateValidation(); + } + }); + + dialects.setSelectedItem(ourDialect); + contexts.setSelectedItem(ourContext); + + updateDialectsAndContexts(); + } + + private void updateEditor() { + if (myContentPanel != null) { + if (myEditorPanel != null) { + myContentPanel.remove(myEditorPanel); + } + disposeEditorContent(); + myEditorPanel = createEditorContent(); + myContentPanel.add(myEditorPanel, BorderLayout.CENTER); + myContentPanel.revalidate(); + } + } + + private void updateDialectsAndContexts() { + final FileType fileType = (FileType)fileTypes.getSelectedItem(); + if (fileType instanceof LanguageFileType) { + Language language = ((LanguageFileType)fileType).getLanguage(); + Language[] languageDialects = LanguageUtil.getLanguageDialects(language); + Arrays.sort(languageDialects, new Comparator<Language>() { + @Override + public int compare(Language o1, Language o2) { + return o1.getDisplayName().compareTo(o2.getDisplayName()); + } + }); + Language[] variants = new Language[languageDialects.length + 1]; + variants[0] = null; + System.arraycopy(languageDialects, 0, variants, 1, languageDialects.length); + dialects.setModel(new DefaultComboBoxModel(variants)); + dialects.setEnabled(variants.length > 1); + } + + final StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(fileType); + + if (profile instanceof StructuralSearchProfileBase) { + final String[] contextNames = ((StructuralSearchProfileBase)profile).getContextNames(); + if (contextNames.length > 0) { + contexts.setModel(new DefaultComboBoxModel(contextNames)); + contexts.setSelectedItem(contextNames[0]); + contexts.setEnabled(true); + return; + } + } + contexts.setSelectedItem(null); + contexts.setEnabled(false); + } + + private void detectFileTypeAndDialect() { + final PsiFile file = searchContext.getFile(); + if (file != null) { + PsiElement context = null; + + if (searchContext.getEditor() != null) { + context = file.findElementAt(searchContext.getEditor().getCaretModel().getOffset()); + if (context != null) { + context = context.getParent(); + } + } + if (context == null) { + context = file; + } + + FileType detectedFileType = null; + + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByPsiElement(context); + if (profile != null) { + FileType fileType = profile.detectFileType(context); + if (fileType != null) { + detectedFileType = fileType; + } + } + + if (detectedFileType == null) { + for (FileType fileType : StructuralSearchUtil.getSuitableFileTypes()) { + if (fileType instanceof LanguageFileType && ((LanguageFileType)fileType).getLanguage().equals(context.getLanguage())) { + detectedFileType = fileType; + break; + } + } + } + + ourFtSearchVariant = detectedFileType != null ? + detectedFileType : + StructuralSearchUtil.getDefaultFileType(); + + // todo: detect dialect + + /*if (file.getLanguage() == StdLanguages.HTML || + (file.getFileType() == StdFileTypes.JSP && + contextLanguage == StdLanguages.HTML + ) + ) { + ourFileType = "html"; + } + else if (file.getLanguage() == StdLanguages.XHTML || + (file.getFileType() == StdFileTypes.JSPX && + contextLanguage == StdLanguages.HTML + )) { + ourFileType = "xml"; + } + else { + ourFileType = DEFAULT_TYPE_NAME; + }*/ + } + } + + protected boolean isRecursiveSearchEnabled() { + return true; + } + + public void setValuesFromConfig(Configuration configuration) { + //searchCriteriaEdit.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration); + + setDialogTitle(configuration); + final MatchOptions matchOptions = configuration.getMatchOptions(); + + UIUtil.setContent( + searchCriteriaEdit, + matchOptions.getSearchPattern(), + 0, + searchCriteriaEdit.getDocument().getTextLength(), + searchContext.getProject() + ); + + model.getConfig().getMatchOptions().setSearchPattern( + matchOptions.getSearchPattern() + ); + + recursiveMatching.setSelected( + isRecursiveSearchEnabled() && matchOptions.isRecursiveSearch() + ); + + caseSensitiveMatch.setSelected( + matchOptions.isCaseSensitiveMatch() + ); + + model.getConfig().getMatchOptions().clearVariableConstraints(); + if (matchOptions.hasVariableConstraints()) { + for (Iterator<String> i = matchOptions.getVariableConstraintNames(); i.hasNext(); ) { + final MatchVariableConstraint constraint = (MatchVariableConstraint)matchOptions.getVariableConstraint(i.next()).clone(); + model.getConfig().getMatchOptions().addVariableConstraint(constraint); + } + } + + MatchOptions options = configuration.getMatchOptions(); + StructuralSearchProfile profile = StructuralSearchUtil.getProfileByFileType(options.getFileType()); + assert profile != null; + fileTypes.setSelectedItem(options.getFileType()); + dialects.setSelectedItem(options.getDialect()); + if (options.getPatternContext() != null) { + contexts.setSelectedItem(options.getPatternContext()); + } + } + + private void setDialogTitle(final Configuration configuration) { + setTitle(getDefaultTitle() + " - " + configuration.getName()); + } + + @Override + public Configuration createConfiguration() { + SearchConfiguration configuration = new SearchConfiguration(); + configuration.setName(USER_DEFINED); + return configuration; + } + + protected void addOrReplaceSelection(final String selection) { + addOrReplaceSelectionForEditor(selection, searchCriteriaEdit); + } + + protected final void addOrReplaceSelectionForEditor(final String selection, Editor editor) { + final Project project = searchContext.getProject(); + UIUtil.setContent(editor, selection, 0, -1, project); + final Document document = editor.getDocument(); + editor.getSelectionModel().setSelection(0, document.getTextLength()); + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + documentManager.commitDocument(document); + final PsiFile file = documentManager.getPsiFile(document); + if (file == null) return; + + new WriteCommandAction(project, file) { + @Override protected void run(@NotNull Result result) throws Throwable { + CodeStyleManager.getInstance(project).adjustLineIndent(file, new TextRange(0, document.getTextLength())); + } + }.execute(); + } + + protected void runAction(final Configuration config, final SearchContext searchContext) { + createUsageView(searchContext, config); + } + + protected void createUsageView(final SearchContext searchContext, final Configuration config) { + UsageViewManager manager = UsageViewManager.getInstance(searchContext.getProject()); + + final UsageViewContext context = createUsageViewContext(config); + final UsageViewPresentation presentation = new UsageViewPresentation(); + presentation.setOpenInNewTab(openInNewTab.isSelected()); + presentation.setScopeText(config.getMatchOptions().getScope().getDisplayName()); + context.configure(presentation); + + final FindUsagesProcessPresentation processPresentation = new FindUsagesProcessPresentation(presentation); + processPresentation.setShowNotFoundMessage(true); + processPresentation.setShowPanelIfOnlyOneUsage(true); + + processPresentation.setProgressIndicatorFactory( + new Factory<ProgressIndicator>() { + @Override + public ProgressIndicator create() { + return new FindProgressIndicator(searchContext.getProject(), presentation.getScopeText()) { + @Override + public void cancel() { + context.getCommand().stopAsyncSearch(); + super.cancel(); + } + }; + } + } + ); + + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + + manager.searchAndShowUsages( + new UsageTarget[]{ + context.getTarget() + }, + new Factory<UsageSearcher>() { + @Override + public UsageSearcher create() { + return new UsageSearcher() { + @Override + public void generate(@NotNull final Processor<Usage> processor) { + context.getCommand().findUsages(processor); + } + }; + } + }, + processPresentation, + presentation, + new UsageViewManager.UsageViewStateListener() { + @Override + public void usageViewCreated(@NotNull UsageView usageView) { + context.setUsageView(usageView); + context.configureActions(); + } + + @Override + public void findingUsagesFinished(final UsageView usageView) { + } + } + ); + } + + protected String getDefaultTitle() { + return SSRBundle.message("structural.search.title"); + } + + protected JComponent createEditorContent() { + JPanel result = new JPanel(new BorderLayout()); + + result.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("search.template"))); + searchCriteriaEdit = createEditor(searchContext, mySavedEditorText != null ? mySavedEditorText : ""); + result.add(BorderLayout.CENTER, searchCriteriaEdit.getComponent()); + result.setMinimumSize(new Dimension(150, 100)); + + return result; + } + + protected int getRowsCount() { + return 4; + } + + @Override + protected JComponent createCenterPanel() { + myContentPanel = new JPanel(new BorderLayout()); + myEditorPanel = createEditorContent(); + myContentPanel.add(BorderLayout.CENTER, myEditorPanel); + myContentPanel.add(BorderLayout.SOUTH, Box.createVerticalStrut(8)); + JComponent centerPanel = new JPanel(new BorderLayout()); + { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(BorderLayout.CENTER, myContentPanel); + panel.add(BorderLayout.SOUTH, createTemplateManagementButtons()); + centerPanel.add(BorderLayout.CENTER, panel); + } + + JPanel optionsContent = new JPanel(new BorderLayout()); + centerPanel.add(BorderLayout.SOUTH, optionsContent); + + JPanel searchOptions = new JPanel(); + searchOptions.setLayout(new GridLayout(getRowsCount(), 1, 0, 0)); + searchOptions.setBorder(IdeBorderFactory.createTitledBorder(SSRBundle.message("ssdialog.options.group.border"), + true)); + + myScopeChooserCombo = new ScopeChooserCombo( + searchContext.getProject(), + true, + false, + FindSettings.getInstance().getDefaultScopeName() + ); + Disposer.register(myDisposable, myScopeChooserCombo); + JPanel allOptions = new JPanel(new BorderLayout()); + if (myShowScopePanel) { + JPanel scopePanel = new JPanel(new GridBagLayout()); + + TitledSeparator separator = new TitledSeparator(SSRBundle.message("search.dialog.scope.label"), myScopeChooserCombo.getComboBox()); + scopePanel.add(separator, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(5, 0, 0, 0), 0, 0)); + + scopePanel.add(myScopeChooserCombo, new GridBagConstraints(0, 1, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, + new Insets(0, 10, 0, 0), 0, 0)); + + allOptions.add( + scopePanel, + BorderLayout.SOUTH + ); + + myScopeChooserCombo.getComboBox().addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + initiateValidation(); + } + }); + } + + buildOptions(searchOptions); + + allOptions.add(searchOptions, BorderLayout.CENTER); + optionsContent.add(allOptions, BorderLayout.CENTER); + + if (myRunFindActionOnClose) { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); + openInNewTab = new JCheckBox(FindBundle.message("find.open.in.new.tab.checkbox")); + openInNewTab.setSelected(ourOpenInNewTab); + ToolWindow findWindow = ToolWindowManager.getInstance(searchContext.getProject()).getToolWindow(ToolWindowId.FIND); + openInNewTab.setEnabled(findWindow != null && findWindow.isAvailable()); + panel.add(openInNewTab, BorderLayout.EAST); + + optionsContent.add(BorderLayout.SOUTH, panel); + } + + updateEditor(); + return centerPanel; + } + + + @Override + protected JComponent createSouthPanel() { + final JPanel statusPanel = new JPanel(new BorderLayout(5, 0)); + statusPanel.add(super.createSouthPanel(), BorderLayout.NORTH); + statusPanel.add(statusText = new JLabel(SSRBundle.message("status.message")), BorderLayout.WEST); + statusPanel.add(status = new JLabel(), BorderLayout.CENTER); + return statusPanel; + } + + private JPanel createTemplateManagementButtons() { + JPanel panel = new JPanel(null); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + panel.add(Box.createHorizontalGlue()); + + panel.add( + createJButtonForAction(new AbstractAction() { + { + putValue(NAME, SSRBundle.message("save.template.text.button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + String name = showSaveTemplateAsDialog(); + + if (name != null) { + final Project project = searchContext.getProject(); + final ConfigurationManager configurationManager = StructuralSearchPlugin.getInstance(project).getConfigurationManager(); + final Collection<Configuration> configurations = configurationManager.getConfigurations(); + + if (configurations != null) { + name = ConfigurationManager.findAppropriateName(configurations, name, project); + if (name == null) return; + } + + model.getConfig().setName(name); + setValuesToConfig(model.getConfig()); + setDialogTitle(model.getConfig()); + + if (model.getShadowConfig() == null || + model.getShadowConfig().isPredefined()) { + existingTemplatesComponent.addConfigurationToUserTemplates(model.getConfig()); + } + else { // ??? + setValuesToConfig(model.getShadowConfig()); + model.getShadowConfig().setName(name); + } + } + } + }) + ); + + panel.add( + Box.createHorizontalStrut(8) + ); + + panel.add( + createJButtonForAction( + new AbstractAction() { + { + putValue(NAME, SSRBundle.message("edit.variables.button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + EditVarConstraintsDialog.setProject(searchContext.getProject()); + new EditVarConstraintsDialog( + searchContext.getProject(), + model, getVariablesFromListeners(), + isReplaceDialog(), + (FileType)fileTypes.getSelectedItem() + ).show(); + initiateValidation(); + EditVarConstraintsDialog.setProject(null); + } + } + ) + ); + + panel.add( + Box.createHorizontalStrut(8) + ); + + panel.add( + createJButtonForAction( + new AbstractAction() { + { + putValue(NAME, SSRBundle.message("history.button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), true, isReplaceDialog()); + dialog.show(); + + if (!dialog.isOK()) { + return; + } + Configuration[] configurations = dialog.getSelectedConfigurations(); + if (configurations.length == 1) { + setSearchPattern(configurations[0]); + } + } + } + ) + ); + + panel.add( + Box.createHorizontalStrut(8) + ); + + panel.add( + createJButtonForAction( + new AbstractAction() { + { + putValue(NAME, SSRBundle.message("copy.existing.template.button")); + } + + @Override + public void actionPerformed(ActionEvent e) { + SelectTemplateDialog dialog = new SelectTemplateDialog(searchContext.getProject(), false, isReplaceDialog()); + dialog.show(); + + if (!dialog.isOK()) { + return; + } + Configuration[] configurations = dialog.getSelectedConfigurations(); + if (configurations.length == 1) { + setSearchPattern(configurations[0]); + } + } + } + ) + ); + + return panel; + } + + protected List<Variable> getVariablesFromListeners() { + return getVarsFrom(searchCriteriaEdit); + } + + protected static ArrayList<Variable> getVarsFrom(Editor searchCriteriaEdit) { + SubstitutionShortInfoHandler handler = searchCriteriaEdit.getUserData(UIUtil.LISTENER_KEY); + return new ArrayList<Variable>(handler.getVariables()); + } + + public final Project getProject() { + return searchContext.getProject(); + } + + public String showSaveTemplateAsDialog() { + return ConfigurationManager.showSaveTemplateAsDialog( + model.getShadowConfig() != null ? model.getShadowConfig().getName() : SSRBundle.message("user.defined.category"), + searchContext.getProject() + ); + } + + protected boolean isReplaceDialog() { + return false; + } + + @Override + public void show() { + StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(true); + Configuration.setActiveCreator(this); + searchCriteriaEdit.putUserData( + SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, + model.getConfig() + ); + + if (!useLastConfiguration) { + final Editor editor = FileEditorManager.getInstance(searchContext.getProject()).getSelectedTextEditor(); + boolean setSomeText = false; + + if (editor != null) { + final SelectionModel selectionModel = editor.getSelectionModel(); + + if (selectionModel.hasSelection()) { + addOrReplaceSelection(selectionModel.getSelectedText()); + existingTemplatesComponent.getPatternTree().setSelectionPath(null); + existingTemplatesComponent.getHistoryList().setSelectedIndex(-1); + setSomeText = true; + } + } + + if (!setSomeText) { + int selection = existingTemplatesComponent.getHistoryList().getSelectedIndex(); + if (selection != -1) { + setValuesFromConfig( + (Configuration)existingTemplatesComponent.getHistoryList().getSelectedValue() + ); + } + } + } + + initiateValidation(); + + super.show(); + } + + @Override + public JComponent getPreferredFocusedComponent() { + return searchCriteriaEdit.getContentComponent(); + } + + // Performs ok action + @Override + protected void doOKAction() { + SearchScope selectedScope = getSelectedScope(); + if (selectedScope == null) return; + + myDoingOkAction = true; + boolean result = isValid(); + myDoingOkAction = false; + if (!result) return; + + myAlarm.cancelAllRequests(); + super.doOKAction(); + if (!myRunFindActionOnClose) return; + + FindSettings.getInstance().setDefaultScopeName(selectedScope.getDisplayName()); + ourOpenInNewTab = openInNewTab.isSelected(); + + try { + if (model.getShadowConfig() != null) { + if (model.getShadowConfig().isPredefined()) { + model.getConfig().setName( + model.getShadowConfig().getName() + ); + } //else { + // // user template, save it + // setValuesToConfig(model.getShadowConfig()); + //} + } + existingTemplatesComponent.addConfigurationToHistory(model.getConfig()); + + runAction(model.getConfig(), searchContext); + } + catch (MalformedPatternException ex) { + reportMessage("this.pattern.is.malformed.message", searchCriteriaEdit, ex.getMessage()); + } + } + + public Configuration getConfiguration() { + return model.getConfig(); + } + + private SearchScope getSelectedScope() { + return myScopeChooserCombo.getSelectedScope(); + } + + protected boolean isValid() { + setValuesToConfig(model.getConfig()); + boolean result = true; + + try { + MatcherImpl.validate(searchContext.getProject(), model.getConfig().getMatchOptions()); + } + catch (MalformedPatternException ex) { + if (myRunFindActionOnClose) { + reportMessage( + "this.pattern.is.malformed.message", + searchCriteriaEdit, + ex.getMessage() != null ? ex.getMessage() : "" + ); + result = false; + } + } + catch (UnsupportedPatternException ex) { + reportMessage("this.pattern.is.unsupported.message", searchCriteriaEdit, ex.getMessage()); + result = false; + } + + //getOKAction().setEnabled(result); + return result; + } + + protected void reportMessage(@NonNls String messageId, Editor editor, Object... params) { + final String message = messageId != null ? SSRBundle.message(messageId, params) : ""; + status.setText(message); + status.setToolTipText(message); + status.revalidate(); + statusText.setLabelFor(editor != null ? editor.getContentComponent() : null); + } + + protected void setValuesToConfig(Configuration config) { + + MatchOptions options = config.getMatchOptions(); + + boolean searchWithinHierarchy = IdeBundle.message("scope.class.hierarchy").equals(myScopeChooserCombo.getSelectedScopeName()); + // We need to reset search within hierarchy scope during online validation since the scope works with user participation + options.setScope( + searchWithinHierarchy && !myDoingOkAction ? GlobalSearchScope.projectScope(getProject()) : myScopeChooserCombo.getSelectedScope()); + options.setLooseMatching(true); + options.setRecursiveSearch(isRecursiveSearchEnabled() && recursiveMatching.isSelected()); + + ourFtSearchVariant = (FileType)fileTypes.getSelectedItem(); + ourDialect = (Language)dialects.getSelectedItem(); + ourContext = (String)contexts.getSelectedItem(); + FileType fileType = ourFtSearchVariant; + options.setFileType(fileType); + options.setDialect(ourDialect); + options.setPatternContext(ourContext); + + options.setSearchPattern(searchCriteriaEdit.getDocument().getText()); + options.setCaseSensitiveMatch(caseSensitiveMatch.isSelected()); + } + + @Override + protected String getDimensionServiceKey() { + return "#com.intellij.structuralsearch.plugin.ui.SearchDialog"; + } + + @Override + public void dispose() { + Configuration.setActiveCreator(null); + disposeEditorContent(); + + myAlarm.cancelAllRequests(); + + super.dispose(); + StructuralSearchPlugin.getInstance(getProject()).setDialogVisible(false); + } + + protected void disposeEditorContent() { + mySavedEditorText = searchCriteriaEdit.getDocument().getText(); + + // this will remove from myExcludedSet + final PsiFile file = PsiDocumentManager.getInstance(searchContext.getProject()).getPsiFile(searchCriteriaEdit.getDocument()); + if (file != null) { + DaemonCodeAnalyzer.getInstance(searchContext.getProject()).setHighlightingEnabled(file, true); + } + + EditorFactory.getInstance().releaseEditor(searchCriteriaEdit); + } + + @Override + protected String getHelpId() { + return "find.structuredSearch"; + } + + public SearchContext getSearchContext() { + return searchContext; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java new file mode 100644 index 000000000000..812ca6b0dc9b --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SearchModel.java @@ -0,0 +1,29 @@ +package com.intellij.structuralsearch.plugin.ui; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 25, 2004 + * Time: 1:33:10 PM + * To change this template use File | Settings | File Templates. + */ +public class SearchModel { + private final Configuration config; + private Configuration shadowConfig; + + public SearchModel(Configuration config) { + this.config = config; + } + + public Configuration getConfig() { + return config; + } + + public void setShadowConfig(Configuration shadowConfig) { + this.shadowConfig = shadowConfig; + } + + public Configuration getShadowConfig() { + return shadowConfig; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java new file mode 100644 index 000000000000..b74715ecf07a --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SelectTemplateDialog.java @@ -0,0 +1,294 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Splitter; +import com.intellij.structuralsearch.MatchOptions; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Apr 23, 2004 + * Time: 5:03:52 PM + * To change this template use File | Settings | File Templates. + */ +public class SelectTemplateDialog extends DialogWrapper { + private final boolean showHistory; + private Editor searchPatternEditor; + private Editor replacePatternEditor; + private final boolean replace; + private final Project project; + private final ExistingTemplatesComponent existingTemplatesComponent; + + private MySelectionListener selectionListener; + private CardLayout myCardLayout; + private JPanel myPreviewPanel; + @NonNls private static final String PREVIEW_CARD = "Preview"; + @NonNls private static final String SELECT_TEMPLATE_CARD = "SelectCard"; + + public SelectTemplateDialog(Project project, boolean showHistory, boolean replace) { + super(project, false); + + this.project = project; + this.showHistory = showHistory; + this.replace = replace; + existingTemplatesComponent = ExistingTemplatesComponent.getInstance(this.project); + + setTitle(SSRBundle.message(this.showHistory ? "used.templates.history.dialog.title" : "existing.templates.dialog.title")); + init(); + + if (this.showHistory) { + final int selection = existingTemplatesComponent.getHistoryList().getSelectedIndex(); + if (selection != -1) { + setPatternFromList(selection); + } + } + else { + final TreePath selection = existingTemplatesComponent.getPatternTree().getSelectionPath(); + if (selection != null) { + setPatternFromNode((DefaultMutableTreeNode)selection.getLastPathComponent()); + } + else { + showPatternPreviewFromConfiguration(null); + } + } + + setupListeners(); + } + + class MySelectionListener implements TreeSelectionListener, ListSelectionListener { + public void valueChanged(TreeSelectionEvent e) { + if (e.getNewLeadSelectionPath() != null) { + setPatternFromNode( + (DefaultMutableTreeNode)e.getNewLeadSelectionPath().getLastPathComponent() + ); + } + } + + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting() || e.getLastIndex() == -1) return; + int selectionIndex = existingTemplatesComponent.getHistoryList().getSelectedIndex(); + if (selectionIndex != -1) { + setPatternFromList(selectionIndex); + } + } + } + + private void setPatternFromList(int index) { + showPatternPreviewFromConfiguration( + (Configuration)existingTemplatesComponent.getHistoryList().getModel().getElementAt(index) + ); + } + + protected JComponent createCenterPanel() { + final JPanel centerPanel = new JPanel(new BorderLayout()); + Splitter splitter; + + centerPanel.add(BorderLayout.CENTER, splitter = new Splitter(false, 0.3f)); + centerPanel.add(splitter); + + splitter.setFirstComponent( + showHistory ? + existingTemplatesComponent.getHistoryPanel() : + existingTemplatesComponent.getTemplatesPanel() + ); + final JPanel panel; + splitter.setSecondComponent( + panel = new JPanel(new BorderLayout()) + ); + + searchPatternEditor = UIUtil.createEditor( + EditorFactory.getInstance().createDocument(""), + project, + false, + true, + ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), TemplateContextType.class) + ); + + JComponent centerComponent; + + if (replace) { + replacePatternEditor = UIUtil.createEditor( + EditorFactory.getInstance().createDocument(""), + project, + false, + true, + ContainerUtil.findInstance(TemplateContextType.EP_NAME.getExtensions(), TemplateContextType.class) + ); + centerComponent = new Splitter(true); + ((Splitter)centerComponent).setFirstComponent(searchPatternEditor.getComponent()); + ((Splitter)centerComponent).setSecondComponent(replacePatternEditor.getComponent()); + } + else { + centerComponent = searchPatternEditor.getComponent(); + } + + myCardLayout = new CardLayout(); + myPreviewPanel = new JPanel(myCardLayout); + myPreviewPanel.add(centerComponent, PREVIEW_CARD); + JPanel selectPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gb = new GridBagConstraints(0,0,0,0,0,0,GridBagConstraints.CENTER,GridBagConstraints.NONE, new Insets(0,0,0,0),0,0); + selectPanel.add(new JLabel(SSRBundle.message("selecttemplate.template.label.please.select.template")), gb); + myPreviewPanel.add(selectPanel, SELECT_TEMPLATE_CARD); + + panel.add(BorderLayout.CENTER, myPreviewPanel); + + panel.add(BorderLayout.NORTH, new JLabel(SSRBundle.message("selecttemplate.template.preview"))); + return centerPanel; + } + + public void dispose() { + EditorFactory.getInstance().releaseEditor(searchPatternEditor); + if (replacePatternEditor != null) EditorFactory.getInstance().releaseEditor(replacePatternEditor); + removeListeners(); + super.dispose(); + } + + public JComponent getPreferredFocusedComponent() { + return showHistory ? + existingTemplatesComponent.getHistoryList() : + existingTemplatesComponent.getPatternTree(); + } + + protected String getDimensionServiceKey() { + return "#com.intellij.structuralsearch.plugin.ui.SelectTemplateDialog"; + } + + private void setupListeners() { + existingTemplatesComponent.setOwner(this); + selectionListener = new MySelectionListener(); + + if (showHistory) { + existingTemplatesComponent.getHistoryList().getSelectionModel().addListSelectionListener( + selectionListener + ); + } + else { + existingTemplatesComponent.getPatternTree().getSelectionModel().addTreeSelectionListener( + selectionListener + ); + } + } + + private void removeListeners() { + existingTemplatesComponent.setOwner(null); + if (showHistory) { + existingTemplatesComponent.getHistoryList().getSelectionModel().removeListSelectionListener( + selectionListener + ); + } + else { + existingTemplatesComponent.getPatternTree().getSelectionModel().removeTreeSelectionListener(selectionListener); + } + } + + private void setPatternFromNode(DefaultMutableTreeNode node) { + if (node == null) return; + final Object userObject = node.getUserObject(); + final Configuration configuration; + + // root could be without search template + if (userObject instanceof Configuration) { + configuration = (Configuration)userObject; + } + else { + configuration = null; + } + + showPatternPreviewFromConfiguration(configuration); + } + + private void showPatternPreviewFromConfiguration(@Nullable final Configuration configuration) { + if (configuration == null) { + myCardLayout.show(myPreviewPanel, SELECT_TEMPLATE_CARD); + return; + } + else { + myCardLayout.show(myPreviewPanel, PREVIEW_CARD); + } + final MatchOptions matchOptions = configuration.getMatchOptions(); + + UIUtil.setContent( + searchPatternEditor, + matchOptions.getSearchPattern(), + 0, + searchPatternEditor.getDocument().getTextLength(), + project + ); + + searchPatternEditor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration); + + if (replace) { + String replacement; + + if (configuration instanceof ReplaceConfiguration) { + replacement = ((ReplaceConfiguration)configuration).getOptions().getReplacement(); + } + else { + replacement = configuration.getMatchOptions().getSearchPattern(); + } + + UIUtil.setContent( + replacePatternEditor, + replacement, + 0, + replacePatternEditor.getDocument().getTextLength(), + project + ); + + replacePatternEditor.putUserData(SubstitutionShortInfoHandler.CURRENT_CONFIGURATION_KEY, configuration); + } + } + + @NotNull public Configuration[] getSelectedConfigurations() { + if (showHistory) { + Object[] selectedValues = existingTemplatesComponent.getHistoryList().getSelectedValues(); + if (selectedValues == null) { + return new Configuration[0]; + } + Collection<Configuration> configurations = new ArrayList<Configuration>(); + for (Object selectedValue : selectedValues) { + if (selectedValue instanceof Configuration) { + configurations.add((Configuration)selectedValue); + } + } + return configurations.toArray(new Configuration[configurations.size()]); + } + else { + TreePath[] paths = existingTemplatesComponent.getPatternTree().getSelectionModel().getSelectionPaths(); + if (paths == null) { + return new Configuration[0]; + } + Collection<Configuration> configurations = new ArrayList<Configuration>(); + for (TreePath path : paths) { + + DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); + final Object userObject = node.getUserObject(); + if (userObject instanceof Configuration) { + configurations.add((Configuration)userObject); + } + } + return configurations.toArray(new Configuration[configurations.size()]); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java new file mode 100644 index 000000000000..32f1302336f3 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/SubstitutionShortInfoHandler.java @@ -0,0 +1,114 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.hint.TooltipGroup; +import com.intellij.codeInsight.template.impl.TemplateImplUtil; +import com.intellij.codeInsight.template.impl.Variable; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.editor.event.*; +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Apr 23, 2004 + * Time: 5:20:56 PM + * To change this template use File | Settings | File Templates. + */ +public class SubstitutionShortInfoHandler implements DocumentListener, EditorMouseMotionListener, CaretListener { + private static final TooltipGroup SS_INFO_TOOLTIP_GROUP = new TooltipGroup("SS_INFO_TOOLTIP_GROUP", 0); + + private long modificationTimeStamp; + private final ArrayList<Variable> variables = new ArrayList<Variable>(); + private final Editor editor; + public static final Key<Configuration> CURRENT_CONFIGURATION_KEY = Key.create("SS.CurrentConfiguration"); + + SubstitutionShortInfoHandler(@NotNull Editor _editor) { + editor = _editor; + } + + public void beforeDocumentChange(DocumentEvent event) { + } + + public void documentChanged(DocumentEvent event) { + } + + public void mouseMoved(EditorMouseEvent e) { + LogicalPosition position = editor.xyToLogicalPosition( e.getMouseEvent().getPoint() ); + + handleInputFocusMovement(position); + } + + private void handleInputFocusMovement(LogicalPosition position) { + checkModelValidity(); + String text = ""; + final int offset = editor.logicalPositionToOffset(position); + final int length = editor.getDocument().getTextLength(); + final CharSequence elements = editor.getDocument().getCharsSequence(); + + int start = offset-1; + int end = -1; + while(start >=0 && Character.isJavaIdentifierPart(elements.charAt(start)) && elements.charAt(start)!='$') start--; + + if (start >=0 && elements.charAt(start)=='$') { + end = offset; + + while(end < length && Character.isJavaIdentifierPart(elements.charAt(end)) && elements.charAt(end)!='$') end++; + if (end < length && elements.charAt(end)=='$') { + String varname = elements.subSequence(start + 1, end).toString(); + Variable foundVar = null; + + for(Iterator<Variable> i=variables.iterator();i.hasNext();) { + final Variable var = i.next(); + + if (var.getName().equals(varname)) { + foundVar = var; + break; + } + } + + if (foundVar!=null) { + text = UIUtil.getShortParamString(editor.getUserData(CURRENT_CONFIGURATION_KEY),varname); + } + } + } + + if (text.length() > 0) { + UIUtil.showTooltip(editor, start, end, text, SS_INFO_TOOLTIP_GROUP); + } + } + + private void checkModelValidity() { + Document document = editor.getDocument(); + if (modificationTimeStamp != document.getModificationStamp()) { + variables.clear(); + variables.addAll(TemplateImplUtil.parseVariables(document.getCharsSequence()).values()); + modificationTimeStamp = document.getModificationStamp(); + } + } + + public void mouseDragged(EditorMouseEvent e) { + } + + public void caretPositionChanged(CaretEvent e) { + handleInputFocusMovement(e.getNewPosition()); + } + + @Override + public void caretAdded(CaretEvent e) { + } + + @Override + public void caretRemoved(CaretEvent e) { + } + + public ArrayList<Variable> getVariables() { + checkModelValidity(); + return variables; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java new file mode 100644 index 000000000000..aa3ca82725b0 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UIUtil.java @@ -0,0 +1,241 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.codeInsight.hint.TooltipController; +import com.intellij.codeInsight.hint.TooltipGroup; +import com.intellij.codeInsight.template.TemplateContextType; +import com.intellij.codeInsight.template.impl.TemplateContext; +import com.intellij.codeInsight.template.impl.TemplateEditorUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.EditorSettings; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.structuralsearch.*; +import com.intellij.structuralsearch.plugin.StructuralReplaceAction; +import com.intellij.structuralsearch.plugin.StructuralSearchAction; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceConfiguration; +import com.intellij.structuralsearch.plugin.util.SmartPsiPointer; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +/** + * @author Maxim.Mossienko + * Date: Apr 21, 2004 + * Time: 7:50:48 PM + */ +public class UIUtil { + static Key<SubstitutionShortInfoHandler> LISTENER_KEY = Key.create("sslistener.key"); + private static final String MODIFY_EDITOR_CONTENT = SSRBundle.message("modify.editor.content.command.name"); + @NonNls private static final String SS_GROUP = "structuralsearchgroup"; + + @NotNull + public static Editor createEditor(Document doc, final Project project, boolean editable, @Nullable TemplateContextType contextType) { + return createEditor(doc, project, editable, false, contextType); + } + + @NotNull + public static Editor createEditor(@NotNull Document doc, + final Project project, + boolean editable, + boolean addToolTipForVariableHandler, + @Nullable TemplateContextType contextType) { + final Editor editor = + editable ? EditorFactory.getInstance().createEditor(doc, project) : EditorFactory.getInstance().createViewer(doc, project); + + EditorSettings editorSettings = editor.getSettings(); + editorSettings.setVirtualSpace(false); + editorSettings.setLineMarkerAreaShown(false); + editorSettings.setIndentGuidesShown(false); + editorSettings.setLineNumbersShown(false); + editorSettings.setFoldingOutlineShown(false); + + EditorColorsScheme scheme = editor.getColorsScheme(); + scheme.setColor(EditorColors.CARET_ROW_COLOR, null); + if (!editable) { + final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme(); + Color c = globalScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR); + + if (c == null) { + c = globalScheme.getDefaultBackground(); + } + + ((EditorEx)editor).setBackgroundColor(c); + } + else { + ((EditorEx)editor).setEmbeddedIntoDialogWrapper(true); + } + + if (contextType != null) { + TemplateContext context = new TemplateContext(); + context.setEnabled(contextType, true); + TemplateEditorUtil.setHighlighter(editor, context); + } + + if (addToolTipForVariableHandler) { + SubstitutionShortInfoHandler handler = new SubstitutionShortInfoHandler(editor); + editor.addEditorMouseMotionListener(handler); + editor.getDocument().addDocumentListener(handler); + editor.getCaretModel().addCaretListener(handler); + editor.putUserData(LISTENER_KEY, handler); + } + + return editor; + } + + public static JComponent createOptionLine(JComponent[] options) { + JPanel tmp = new JPanel(); + + tmp.setLayout(new BoxLayout(tmp, BoxLayout.X_AXIS)); + for (int i = 0; i < options.length; i++) { + if (i != 0) { + tmp.add(Box.createHorizontalStrut(com.intellij.util.ui.UIUtil.DEFAULT_HGAP)); + } + tmp.add(options[i]); + } + tmp.add(Box.createHorizontalGlue()); + + return tmp; + } + + public static JComponent createOptionLine(JComponent option) { + return createOptionLine(new JComponent[]{option}); + } + + public static void setContent(final Editor editor, String val, final int from, final int end, final Project project) { + final String value = val != null ? val : ""; + + CommandProcessor.getInstance().executeCommand(project, new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + editor.getDocument().replaceString(from, (end == -1) ? editor.getDocument().getTextLength() : end, value); + } + }); + } + }, MODIFY_EDITOR_CONTENT, SS_GROUP); + } + + static String getShortParamString(Configuration config, String varname) { + if (config == null) return ""; + final MatchOptions options = config.getMatchOptions(); + + + final MatchVariableConstraint constraint = options == null ? null : options.getVariableConstraint(varname); + NamedScriptableDefinition namedScriptableDefinition = constraint; + + final ReplacementVariableDefinition replacementVariableDefinition = + config instanceof ReplaceConfiguration ? ((ReplaceConfiguration)config).getOptions().getVariableDefinition(varname) : null; + if (replacementVariableDefinition != null) namedScriptableDefinition = replacementVariableDefinition; + + if (constraint == null && replacementVariableDefinition == null) { + return SSRBundle.message("no.constraints.specified.tooltip.message"); + } + + final StringBuilder buf = new StringBuilder(); + + if (constraint != null) { + if (constraint.isPartOfSearchResults()) { + append(buf, SSRBundle.message("target.tooltip.message")); + } + if (constraint.getRegExp() != null && constraint.getRegExp().length() > 0) { + append(buf, SSRBundle.message("text.tooltip.message", constraint.isInvertRegExp() ? SSRBundle.message("not.tooltip.message") : "", + constraint.getRegExp(), + constraint.isWithinHierarchy() || constraint.isStrictlyWithinHierarchy() ? + SSRBundle.message("within.hierarchy.tooltip.message") : "")); + } + + if (constraint.getNameOfExprType() != null && constraint.getNameOfExprType().length() > 0) { + append(buf, SSRBundle.message("exprtype.tooltip.message", + constraint.isInvertExprType() ? SSRBundle.message("not.tooltip.message") : "", + constraint.getNameOfExprType(), + constraint.isExprTypeWithinHierarchy() ? SSRBundle.message("within.hierarchy.tooltip.message") : "")); + } + + if (constraint.getMinCount() == constraint.getMaxCount()) { + append(buf, SSRBundle.message("occurs.tooltip.message", constraint.getMinCount())); + } + else { + append(buf, SSRBundle.message("min.occurs.tooltip.message", constraint.getMinCount(), + constraint.getMaxCount() == Integer.MAX_VALUE ? + StringUtil.decapitalize(SSRBundle.message("editvarcontraints.unlimited")) : + constraint.getMaxCount())); + } + } + + final String script = namedScriptableDefinition.getScriptCodeConstraint(); + if (script != null && script.length() > 2) { + final String str = SSRBundle.message("script.tooltip.message", StringUtil.stripQuotesAroundValue(script)); + append(buf, str); + } + + return buf.toString(); + } + + private static void append(final StringBuilder buf, final String str) { + if (buf.length() > 0) buf.append(", "); + buf.append(str); + } + + public static void navigate(PsiElement result) { + FileEditorManager.getInstance(result.getProject()).openTextEditor( + new OpenFileDescriptor(result.getProject(), result.getContainingFile().getVirtualFile(), result.getTextOffset()), true); + } + + public static void navigate(MatchResult result) { + final SmartPsiPointer ref = result.getMatchRef(); + + FileEditorManager.getInstance(ref.getProject()) + .openTextEditor(new OpenFileDescriptor(ref.getProject(), ref.getFile(), ref.getOffset()), true); + } + + public static void invokeAction(Configuration config, SearchContext context) { + if (config instanceof SearchConfiguration) { + StructuralSearchAction.triggerAction(config, context); + } + else { + StructuralReplaceAction.triggerAction(config, context); + } + } + + static void showTooltip(@NotNull Editor editor, final int start, int end, @NotNull String text, @NotNull TooltipGroup group) { + Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); + Point top = editor.logicalPositionToXY(editor.offsetToLogicalPosition(start)); + final int documentLength = editor.getDocument().getTextLength(); + if (end >= documentLength) end = documentLength; + Point bottom = editor.logicalPositionToXY(editor.offsetToLogicalPosition(end)); + + Point bestPoint = new Point(top.x, bottom.y + editor.getLineHeight()); + + if (!visibleArea.contains(bestPoint)) { + int defaultOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(new Point(0, 0))); + bestPoint = editor.logicalPositionToXY(editor.offsetToLogicalPosition(defaultOffset)); + } + + Point p = SwingUtilities.convertPoint(editor.getContentComponent(), bestPoint, editor.getComponent().getRootPane().getLayeredPane()); + TooltipController.getInstance().showTooltip(editor, p, text, false, group); + } + + public static void updateHighlighter(Editor editor, StructuralSearchProfile profile) { + final TemplateContextType contextType = profile.getTemplateContextType(); + if (contextType != null) { + TemplateContext context = new TemplateContext(); + context.setEnabled(contextType, true); + TemplateEditorUtil.setHighlighter(editor, context); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java new file mode 100644 index 000000000000..a55a16d4c379 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/UsageViewContext.java @@ -0,0 +1,183 @@ +package com.intellij.structuralsearch.plugin.ui; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.KeyboardShortcut; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.structuralsearch.SSRBundle; +import com.intellij.structuralsearch.plugin.replace.ui.ReplaceCommand; +import com.intellij.usages.*; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.Set; + +/** + * Created by IntelliJ IDEA. + * User: Maxim.Mossienko + * Date: Mar 9, 2005 + * Time: 2:47:49 PM + * To change this template use File | Settings | File Templates. + */ +public class UsageViewContext { + protected final SearchContext mySearchContext; + private UsageView myUsageView; + protected final Configuration myConfiguration; + private Set<Usage> myExcludedSet; + private SearchCommand myCommand; + + protected UsageViewContext(SearchContext _searchContext,Configuration _configuration) { + myConfiguration = _configuration; + mySearchContext = _searchContext; + } + + public boolean isExcluded(Usage usage) { + if (myExcludedSet == null) myExcludedSet = myUsageView.getExcludedUsages(); + return myExcludedSet.contains(usage); + } + + public UsageView getUsageView() { + return myUsageView; + } + + public void setUsageView(final UsageView usageView) { + myUsageView = usageView; + } + + public Configuration getConfiguration() { + return myConfiguration; + } + + public SearchCommand getCommand() { + if (myCommand == null) myCommand = createCommand(); + return myCommand; + } + + protected SearchCommand createCommand() { + return new SearchCommand(mySearchContext.getProject(), this); + } + + protected String _getPresentableText() { + return myConfiguration.getMatchOptions().getSearchPattern(); + } + + public UsageTarget getTarget() { + return new MyUsageTarget(_getPresentableText()); + } + + public void configure(@NotNull UsageViewPresentation presentation) { + String s = _getPresentableText(); + if (s.length() > 15) s = s.substring(0,15) + "..."; + final String usagesString = SSRBundle.message("occurrences.of", s); + presentation.setUsagesString(usagesString); + presentation.setTabText(StringUtil.capitalize(usagesString)); + presentation.setUsagesWord(SSRBundle.message("occurrence")); + presentation.setCodeUsagesString(SSRBundle.message("found.occurrences")); + } + + protected void configureActions() {} + + private class MyUsageTarget implements ConfigurableUsageTarget,ItemPresentation { + private final String myPresentableText; + + MyUsageTarget(String str) { + myPresentableText = str; + } + + @Override + public String getPresentableText() { + return myPresentableText; + } + + @Override + public String getLocationString() { + //noinspection HardCodedStringLiteral + return "Do Not Know Where"; + } + + @Override + public Icon getIcon(boolean open) { + return null; + } + + @Override + public void findUsages() { + throw new UnsupportedOperationException(); + } + + @Override + public void findUsagesInEditor(@NotNull FileEditor editor) { + throw new UnsupportedOperationException(); + } + + @Override + public void highlightUsages(@NotNull PsiFile file, @NotNull Editor editor, boolean clearHighlights) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public VirtualFile[] getFiles() { + return null; + } + + @Override + public void update() { + } + + @Override + public String getName() { + //noinspection HardCodedStringLiteral + return "my name"; + } + + @Override + public ItemPresentation getPresentation() { + return this; + } + + @Override + public void navigate(boolean requestFocus) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canNavigate() { + return false; + } + + @Override + public boolean canNavigateToSource() { + return false; + } + + @Override + public void showSettings() { + UIUtil.invokeAction(myConfiguration, mySearchContext); + } + + @Override + public KeyboardShortcut getShortcut() { + return ActionManager.getInstance().getKeyboardShortcut(getCommand() instanceof ReplaceCommand ? "StructuralSearchPlugin.StructuralReplaceAction":"StructuralSearchPlugin.StructuralSearchAction"); + } + + @NotNull + @Override + public String getLongDescriptiveName() { + return _getPresentableText(); + } + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form new file mode 100644 index 000000000000..20094b213e64 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/VarConstraints.form @@ -0,0 +1,350 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.structuralsearch.plugin.ui.EditVarConstraintsDialog"> + <grid id="53af4" binding="mainForm" layout-manager="GridLayoutManager" row-count="8" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="24" y="82" width="889" height="681"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <grid id="33d30" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="0" column="0" row-span="8" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithoutIndent"/> + </clientProperties> + <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.variables.border"/> + <children> + <grid id="d6a7" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="e85ba" class="com.intellij.ui.components.JBList" binding="parameterList"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="2" anchor="0" fill="3" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="50"/> + </grid> + </constraints> + <properties/> + </component> + </children> + </grid> + </children> + </grid> + <grid id="4cae8" binding="textConstraintsPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/> + </clientProperties> + <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.text.constraints.border"/> + <children> + <component id="cc210" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression"/> + </properties> + </component> + <component id="3b03c" class="com.intellij.ui.EditorTextField" binding="regexp" custom-create="true"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="f7d70" class="javax.swing.JCheckBox" binding="applyWithinTypeHierarchy"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/> + </properties> + </component> + <component id="b766f" class="javax.swing.JCheckBox" binding="notRegexp"> + <constraints> + <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + <component id="51cbf" class="javax.swing.JCheckBox" binding="wholeWordsOnly"> + <constraints> + <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.whole.words.only"/> + </properties> + </component> + </children> + </grid> + <grid id="bb81f" binding="expressionConstraints" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/> + </clientProperties> + <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.expression.constraints.border"/> + <children> + <component id="2bdb4" class="javax.swing.JCheckBox" binding="read"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.value.is.read"/> + </properties> + </component> + <component id="aba7e" class="javax.swing.JCheckBox" binding="write"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.value.is.written"/> + </properties> + </component> + <component id="5698c" class="javax.swing.JCheckBox" binding="notRead"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + <component id="79e72" class="javax.swing.JCheckBox" binding="notWrite"> + <constraints> + <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + <component id="e9391" class="com.intellij.ui.EditorTextField" binding="regexprForExprType" custom-create="true"> + <constraints> + <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="59512" class="javax.swing.JCheckBox" binding="exprTypeWithinHierarchy"> + <constraints> + <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/> + </properties> + </component> + <component id="17342" class="javax.swing.JCheckBox" binding="notExprType"> + <constraints> + <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + <component id="4d6d0" class="javax.swing.JLabel"> + <constraints> + <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression.for.java.expression.type"/> + </properties> + </component> + <component id="a97fa" class="javax.swing.JLabel"> + <constraints> + <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.text.regular.expression.for.formal.argument.type.of.the.method"/> + </properties> + </component> + <component id="d02c2" class="com.intellij.ui.EditorTextField" binding="formalArgType" custom-create="true"> + <constraints> + <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="bc6d0" class="javax.swing.JCheckBox" binding="invertFormalArgType"> + <constraints> + <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + <component id="2b0ea" class="javax.swing.JCheckBox" binding="formalArgTypeWithinHierarchy"> + <constraints> + <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.apply.constraint.within.type.hierarchy"/> + </properties> + </component> + </children> + </grid> + <component id="9a3a8" class="javax.swing.JCheckBox" binding="partOfSearchResults"> + <constraints> + <grid row="6" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.this.variable.is.target.of.the.search"/> + </properties> + </component> + <vspacer id="d06dd"> + <constraints> + <grid row="7" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/> + </constraints> + </vspacer> + <grid id="2a918" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <grid id="964fd" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/> + </clientProperties> + <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.script.constraints.border"/> + <children> + <component id="cdf9" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="script.option.text"/> + </properties> + </component> + <component id="e7633" class="com.intellij.openapi.ui.ComponentWithBrowseButton" binding="customScriptCode" custom-create="true"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + </children> + </grid> + </children> + </grid> + <grid id="d8664" binding="occurencePanel" layout-manager="GridLayoutManager" row-count="2" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/> + </clientProperties> + <border type="none" title-resource-bundle="messages/SSRBundle" title-key="var.constraints.occurrences.count.border"/> + <children> + <component id="e4b4a" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.minimum.count"/> + </properties> + </component> + <component id="f6a1a" class="javax.swing.JTextField" binding="minoccurs"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="50" height="-1"/> + <maximum-size width="50" height="-1"/> + </grid> + </constraints> + <properties> + <text value="1"/> + </properties> + </component> + <component id="881e7" class="javax.swing.JLabel"> + <constraints> + <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.maximum.count"/> + </properties> + </component> + <component id="42" class="javax.swing.JTextField" binding="maxoccurs"> + <constraints> + <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="50" height="-1"/> + </grid> + </constraints> + <properties> + <columns value="0"/> + <text value="1"/> + </properties> + </component> + <component id="7c33b" class="javax.swing.JCheckBox" binding="maxoccursUnlimited"> + <constraints> + <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.unlimited"/> + </properties> + </component> + </children> + </grid> + <grid id="463bd" binding="containedInConstraints" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <clientProperties> + <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/> + </clientProperties> + <border type="none" title="Contained in constraints"/> + <children> + <component id="7003f" class="com.intellij.ui.ComboboxWithBrowseButton" binding="withinCombo"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + </component> + <component id="896c2" class="javax.swing.JCheckBox" binding="invertWithinIn"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/SSRBundle" key="editvarcontraints.invert.condition"/> + </properties> + </component> + </children> + </grid> + <component id="ce461" class="javax.swing.JLabel" binding="myRegExHelpLabel" custom-create="true"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + </component> + </children> + </grid> +</form> diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/actions/DoSearchAction.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/actions/DoSearchAction.java new file mode 100644 index 000000000000..ed0da3a3f391 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/ui/actions/DoSearchAction.java @@ -0,0 +1,24 @@ +package com.intellij.structuralsearch.plugin.ui.actions; + +import com.intellij.structuralsearch.plugin.ui.Configuration; +import com.intellij.structuralsearch.*; +import com.intellij.openapi.project.Project; + +/** + * Does the search action + */ +public class DoSearchAction { + public static void execute(final Project project, MatchResultSink sink, + final Configuration configuration) { + final MatchOptions options = configuration.getMatchOptions(); + + final Matcher matcher = new Matcher(project); + try { + matcher.findMatches(sink, options); + } + finally { + sink.matchingFinished(); + } + } + +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java new file mode 100644 index 000000000000..4caff77bdc06 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/CollectingMatchResultSink.java @@ -0,0 +1,40 @@ +package com.intellij.structuralsearch.plugin.util; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.psi.PsiFile; +import com.intellij.structuralsearch.MatchResult; +import com.intellij.structuralsearch.MatchResultSink; +import com.intellij.structuralsearch.MatchingProcess; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedList; +import java.util.List; + +public class CollectingMatchResultSink implements MatchResultSink { + private final List<MatchResult> matches = new LinkedList<MatchResult>(); + + public void newMatch(MatchResult result) { + matches.add(result); + } + + /* Notifies sink about starting the matching for given element + * @param element the current file + */ + public void processFile(PsiFile element) { + } + + public void matchingFinished() { + } + + public ProgressIndicator getProgressIndicator() { + return null; + } + + public void setMatchingProcess(MatchingProcess process) { + } + + @NotNull + public List<MatchResult> getMatches() { + return matches; + } +} diff --git a/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java new file mode 100644 index 000000000000..0fee846c77b0 --- /dev/null +++ b/plugins/structuralsearch/source/com/intellij/structuralsearch/plugin/util/SmartPsiPointer.java @@ -0,0 +1,56 @@ +package com.intellij.structuralsearch.plugin.util; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.SmartPointerManager; +import com.intellij.psi.SmartPsiElementPointer; + +/** + * Reference to element have been matched + */ +public class SmartPsiPointer { + private SmartPsiElementPointer pointer; + + public SmartPsiPointer(PsiElement element) { + pointer = element != null ? SmartPointerManager.getInstance(element.getProject()).createSmartPsiElementPointer(element):null; + } + + public VirtualFile getFile() { + return pointer != null ? pointer.getVirtualFile():null; + } + + public int getOffset() { + return pointer != null ? pointer.getElement().getTextRange().getStartOffset():-1; + } + + public int getLength() { + return pointer != null ? pointer.getElement().getTextRange().getEndOffset():0; + } + + public PsiElement getElement() { + return pointer != null ? pointer.getElement():null; + } + + public void clear() { + pointer = null; + } + + public Project getProject() { + return pointer != null ? pointer.getElement().getProject():null; + } + + public boolean equals(Object o) { + if (o instanceof SmartPsiPointer) { + final SmartPsiPointer ref = ((SmartPsiPointer)o); + return ref.getFile().equals(getFile()) && + ref.getOffset() == getOffset() && + ref.getLength() == getLength(); + } + return false; + } + + public int hashCode() { + return pointer != null ? getElement().hashCode():0; + } +} |