/*
* 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();
}
}
}