blob: 7421e57d9fd587ecd2805d16d0737b9f4bb2957b [file] [log] [blame]
/*
* 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();
}
}
}