| /* |
| * 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.lang.ant.dom; |
| |
| import com.intellij.lang.ant.AntSupport; |
| 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; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @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 = cacheKind.get(context); |
| if (cachemap == null) { |
| cacheKind.set(context, 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) { |
| final Map<K, V> cached = cacheKind.get(context); |
| return cached != null? cached.get(key) : null; |
| } |
| |
| public enum Stage { |
| RESOLVE_MAP_BUILDING_STAGE, TARGETS_WALKUP_STAGE |
| } |
| private Stage myStage = Stage.RESOLVE_MAP_BUILDING_STAGE; |
| |
| private final Stack<String> myCurrentTargetEffectiveName = new Stack<String>(); |
| |
| private final AntDomElement myContextElement; |
| private boolean myStopped; |
| private final TargetsNameContext myNameContext = new TargetsNameContext(); |
| private final Map<String, AntDomTarget> myTargetsResolveMap = new HashMap<String, AntDomTarget>(); // target effective name -> ant target |
| private final Map<String, List<String>> myDependenciesMap = new HashMap<String, List<String>>(); // target effective name -> dependencies effective names |
| |
| private final Set<String> myProcessedTargets = new HashSet<String>(); |
| private final 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.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, Pair.create(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.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 ? AntSupport.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(Pair.create(prefix, kind)); |
| } |
| |
| public void popPrefix() { |
| myCurrentPrefix = null; |
| myPrefixes.removeLast(); |
| } |
| } |
| } |