/* * Copyright 2000-2010 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 org.napile.idea.thermit.dom; import java.util.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.napile.idea.thermit.ThermitSupport; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.xml.XmlFile; import com.intellij.util.containers.HashMap; import com.intellij.util.xml.DomElement; /** * @author Eugene Zhuravlev * Date: Apr 22, 2010 */ public abstract class PropertyProviderFinder extends AntDomRecursiveVisitor { protected static <K, V> void cacheResult(@Nullable final DomElement context, final Key<Map<K, V>> cacheKind, K key, V value) { if(context != null) { Map<K, V> cachemap = context.getUserData(cacheKind); if(cachemap == null) { context.putUserData(cacheKind, cachemap = Collections.synchronizedMap(new HashMap<K, V>())); } cachemap.put(key, value); } } @Nullable protected static <K, V> V getCachedResult(@Nullable final DomElement context, final Key<Map<K, V>> cacheKind, K key) { if(context != null) { final Map<K, V> cached = context.getUserData(cacheKind); if(cached != null) { return cached.get(key); } } return null; } public static enum Stage { RESOLVE_MAP_BUILDING_STAGE, TARGETS_WALKUP_STAGE } private Stage myStage = Stage.RESOLVE_MAP_BUILDING_STAGE; private Stack<String> myCurrentTargetEffectiveName = new Stack<String>(); private final AntDomElement myContextElement; private boolean myStopped; private TargetsNameContext myNameContext = new TargetsNameContext(); private Map<String, AntDomTarget> myTargetsResolveMap = new HashMap<String, AntDomTarget>(); // target effective name -> thermit target private Map<String, List<String>> myDependenciesMap = new HashMap<String, List<String>>(); // target effective name -> dependencies effective names private Set<String> myProcessedTargets = new HashSet<String>(); private Set<AntDomProject> myVisitedProjects = new HashSet<AntDomProject>(); protected PropertyProviderFinder(DomElement contextElement) { myContextElement = contextElement != null ? contextElement.getParentOfType(AntDomElement.class, false) : null; } public void execute(AntDomProject startProject, String initialTargetName) { myStage = Stage.RESOLVE_MAP_BUILDING_STAGE; startProject.accept(this); stageCompleted(Stage.RESOLVE_MAP_BUILDING_STAGE, Stage.TARGETS_WALKUP_STAGE); if(!myStopped) { myStage = Stage.TARGETS_WALKUP_STAGE; final AntDomTarget target = initialTargetName != null ? getTargetByName(initialTargetName) : null; if(target != null) { processTarget(initialTargetName, target); } List<String> unprocessed = null; for(String s : myTargetsResolveMap.keySet()) { if(!myProcessedTargets.contains(s)) { if(unprocessed == null) { unprocessed = new ArrayList<String>(); } unprocessed.add(s); } } if(unprocessed != null) { for(String targetName : unprocessed) { processTarget(targetName, myTargetsResolveMap.get(targetName)); } } } } private void processTarget(String targetEffectiveName, AntDomTarget target) { myCurrentTargetEffectiveName.push(targetEffectiveName); try { target.accept(this); } finally { myCurrentTargetEffectiveName.pop(); } } public void visitTarget(AntDomTarget target) { if(myStage == Stage.TARGETS_WALKUP_STAGE) { final String targetEffectiveName = myCurrentTargetEffectiveName.peek(); if(!myProcessedTargets.contains(targetEffectiveName)) { myProcessedTargets.add(targetEffectiveName); final List<String> depsList = myDependenciesMap.get(targetEffectiveName); if(depsList != null) { for(String dependencyName : depsList) { final AntDomTarget dependency = getTargetByName(dependencyName); if(dependency != null) { processTarget(dependencyName, dependency); } } } super.visitTarget(target); } } else if(myStage == Stage.RESOLVE_MAP_BUILDING_STAGE) { final String declaredTargetName = target.getName().getRawText(); String effectiveTargetName = null; final InclusionKind inclusionKind = myNameContext.getCurrentInclusionKind(); switch(inclusionKind) { case IMPORT: final String alias = myNameContext.getShortPrefix() + declaredTargetName; if(!myTargetsResolveMap.containsKey(declaredTargetName)) { effectiveTargetName = declaredTargetName; myTargetsResolveMap.put(alias, target); } else { effectiveTargetName = alias; } break; case INCLUDE: effectiveTargetName = myNameContext.getFQPrefix() + declaredTargetName; break; default: effectiveTargetName = declaredTargetName; break; } if(effectiveTargetName != null) { final AntDomTarget existingTarget = myTargetsResolveMap.get(effectiveTargetName); if(existingTarget != null && Comparing.equal(existingTarget.getAntProject(), target.getAntProject())) { duplicateTargetFound(existingTarget, target, effectiveTargetName); } else { myTargetsResolveMap.put(effectiveTargetName, target); final String dependsStr = target.getDependsList().getRawText(); Map<String, Pair<AntDomTarget, String>> depsMap = Collections.emptyMap(); if(dependsStr != null) { depsMap = new HashMap<String, Pair<AntDomTarget, String>>(); final StringTokenizer tokenizer = new StringTokenizer(dependsStr, ",", false); while(tokenizer.hasMoreTokens()) { final String token = tokenizer.nextToken().trim(); final String dependentTargetEffectiveName = myNameContext.calcTargetReferenceText(token); final AntDomTarget dependent = getTargetByName(dependentTargetEffectiveName); if(dependent != null) { depsMap.put(token, new Pair<AntDomTarget, String>(dependent, dependentTargetEffectiveName)); } addDependency(effectiveTargetName, dependentTargetEffectiveName); } } targetDefined(target, effectiveTargetName, depsMap); } } } } @Override public void visitAntDomElement(AntDomElement element) { if(myStopped) { return; } if(element.equals(myContextElement)) { stop(); } else { if(element instanceof PropertiesProvider) { propertyProviderFound(((PropertiesProvider) element)); } } if(!myStopped) { //super.visitAntDomElement(element); for(Iterator<AntDomElement> iterator = element.getAntChildrenIterator(); iterator.hasNext(); ) { AntDomElement child = iterator.next(); child.accept(this); if(myStage == Stage.TARGETS_WALKUP_STAGE) { if(myStopped) { break; } } } } } @Nullable protected AntDomTarget getTargetByName(String effectiveName) { return myTargetsResolveMap.get(effectiveName); } @NotNull public final Map<String, AntDomTarget> getDiscoveredTargets() { return Collections.unmodifiableMap(myTargetsResolveMap); } public AntDomElement getContextElement() { return myContextElement; } protected void stop() { myStopped = true; } /** * @param propertiesProvider * @return true if search should be continued and false in order to stop */ protected abstract void propertyProviderFound(PropertiesProvider propertiesProvider); public void visitInclude(AntDomInclude includeTag) { processFileInclusion(includeTag, InclusionKind.INCLUDE); } public void visitImport(AntDomImport importTag) { processFileInclusion(importTag, InclusionKind.IMPORT); } public void visitProject(AntDomProject project) { if(!myVisitedProjects.contains(project)) { myVisitedProjects.add(project); try { super.visitProject(project); } finally { myVisitedProjects.remove(project); } } } private void processFileInclusion(AntDomIncludingDirective directive, final InclusionKind kind) { if(directive.equals(myContextElement)) { stop(); } if(myStopped) { return; } final PsiFileSystemItem item = directive.getFile().getValue(); if(item instanceof PsiFile) { final AntDomProject slaveProject = item instanceof XmlFile ? ThermitSupport.getAntDomProjectForceAntFile((XmlFile) item) : null; if(slaveProject != null) { myNameContext.pushPrefix(directive, kind, slaveProject); try { slaveProject.accept(this); } finally { myNameContext.popPrefix(); } } } } private void addDependency(String effectiveTargetName, String dependentTargetEffectiveName) { List<String> list = myDependenciesMap.get(effectiveTargetName); if(list == null) { myDependenciesMap.put(effectiveTargetName, list = new ArrayList<String>()); } list.add(dependentTargetEffectiveName); } /** * @param target * @param taregetEffectiveName * @param dependenciesMap Map declared dependency reference->pair[tareget object, effective reference name] */ protected void targetDefined(AntDomTarget target, String taregetEffectiveName, Map<String, Pair<AntDomTarget, String>> dependenciesMap) { } /** * @param existingTarget * @param duplicatingTarget * @param taregetEffectiveName */ protected void duplicateTargetFound(AntDomTarget existingTarget, AntDomTarget duplicatingTarget, String taregetEffectiveName) { } protected void stageCompleted(Stage completedStage, Stage startingStage) { } private static enum InclusionKind { INCLUDE("included"), IMPORT("imported"), TOPLEVEL("toplevel"); private final String myDisplayName; private InclusionKind(String displayName) { myDisplayName = displayName; } public String toString() { return myDisplayName; } } private static class TargetsNameContext { private int myDefaultPrefixCounter = 0; private final LinkedList<Pair<String, InclusionKind>> myPrefixes = new LinkedList<Pair<String, InclusionKind>>(); private String myCurrentPrefix = null; public String calcTargetReferenceText(String targetReferenceText) { if(!myPrefixes.isEmpty()) { final InclusionKind kind = myPrefixes.getLast().getSecond(); switch(kind) { case IMPORT: return targetReferenceText; case INCLUDE: return getFQPrefix() + targetReferenceText; } } return targetReferenceText; } @NotNull public InclusionKind getCurrentInclusionKind() { if(myPrefixes.isEmpty()) { return InclusionKind.TOPLEVEL; } return myPrefixes.getLast().getSecond(); } @NotNull public String getFQPrefix() { if(myCurrentPrefix != null) { return myCurrentPrefix; } if(myPrefixes.isEmpty()) { return ""; } StringBuffer buf = new StringBuffer(); for(Pair<String, InclusionKind> prefix : myPrefixes) { buf.append(prefix.getFirst()); } return myCurrentPrefix = buf.toString(); } @NotNull public String getShortPrefix() { return myPrefixes.isEmpty() ? "" : myPrefixes.getLast().getFirst(); } public void pushPrefix(AntDomIncludingDirective directive, final InclusionKind kind, final @NotNull AntDomProject slaveProject) { final String separator = directive.getTargetPrefixSeparatorValue(); String prefix = directive.getTargetPrefix().getStringValue(); if(prefix == null) { prefix = slaveProject.getName().getRawText(); if(prefix == null) { prefix = "anonymous" + (myDefaultPrefixCounter++); } } pushPrefix(prefix.endsWith(separator) ? prefix : prefix + separator, kind); } public void pushPrefix(String prefix, InclusionKind kind) { myCurrentPrefix = null; myPrefixes.addLast(new Pair<String, InclusionKind>(prefix, kind)); } public void popPrefix() { myCurrentPrefix = null; myPrefixes.removeLast(); } } }