/plugins/ant/src/com/intellij/lang/ant/dom/PropertyProviderFinder.java

https://bitbucket.org/nbargnesi/idea · Java · 391 lines · 315 code · 42 blank · 34 comment · 73 complexity · 550c5e062f6509ab4b07660217b0b4c6 MD5 · raw file

  1. /*
  2. * Copyright 2000-2010 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.lang.ant.dom;
  17. import com.intellij.lang.ant.AntSupport;
  18. import com.intellij.openapi.util.Comparing;
  19. import com.intellij.openapi.util.Key;
  20. import com.intellij.openapi.util.Pair;
  21. import com.intellij.psi.PsiFile;
  22. import com.intellij.psi.PsiFileSystemItem;
  23. import com.intellij.psi.xml.XmlFile;
  24. import com.intellij.util.containers.HashMap;
  25. import com.intellij.util.xml.DomElement;
  26. import org.jetbrains.annotations.NotNull;
  27. import org.jetbrains.annotations.Nullable;
  28. import java.util.*;
  29. /**
  30. * @author Eugene Zhuravlev
  31. * Date: Apr 22, 2010
  32. */
  33. public abstract class PropertyProviderFinder extends AntDomRecursiveVisitor {
  34. protected static <K, V> void cacheResult(@Nullable final DomElement context,
  35. final Key<Map<K, V>> cacheKind,
  36. K key,
  37. V value) {
  38. if (context != null) {
  39. Map<K, V> cachemap = context.getUserData(cacheKind);
  40. if (cachemap == null) {
  41. context.putUserData(cacheKind, cachemap = Collections.synchronizedMap(new HashMap<K, V>()));
  42. }
  43. cachemap.put(key, value);
  44. }
  45. }
  46. @Nullable
  47. protected static <K, V> V getCachedResult(@Nullable final DomElement context,
  48. final Key<Map<K, V>> cacheKind,
  49. K key) {
  50. if (context != null) {
  51. final Map<K, V> cached = context.getUserData(cacheKind);
  52. if (cached != null) {
  53. return cached.get(key);
  54. }
  55. }
  56. return null;
  57. }
  58. public static enum Stage {
  59. RESOLVE_MAP_BUILDING_STAGE, TARGETS_WALKUP_STAGE
  60. }
  61. private Stage myStage = Stage.RESOLVE_MAP_BUILDING_STAGE;
  62. private Stack<String> myCurrentTargetEffectiveName = new Stack<String>();
  63. private final AntDomElement myContextElement;
  64. private boolean myStopped;
  65. private TargetsNameContext myNameContext = new TargetsNameContext();
  66. private Map<String, AntDomTarget> myTargetsResolveMap = new HashMap<String, AntDomTarget>(); // target effective name -> ant target
  67. private Map<String, List<String>> myDependenciesMap = new HashMap<String, List<String>>(); // target effective name -> dependencies effective names
  68. private Set<String> myProcessedTargets = new HashSet<String>();
  69. private Set<AntDomProject> myVisitedProjects = new HashSet<AntDomProject>();
  70. protected PropertyProviderFinder(DomElement contextElement) {
  71. myContextElement = contextElement != null? contextElement.getParentOfType(AntDomElement.class, false) : null;
  72. }
  73. public void execute(AntDomProject startProject, String initialTargetName) {
  74. myStage = Stage.RESOLVE_MAP_BUILDING_STAGE;
  75. startProject.accept(this);
  76. stageCompleted(Stage.RESOLVE_MAP_BUILDING_STAGE, Stage.TARGETS_WALKUP_STAGE);
  77. if (!myStopped) {
  78. myStage = Stage.TARGETS_WALKUP_STAGE;
  79. final AntDomTarget target = initialTargetName != null? getTargetByName(initialTargetName) : null;
  80. if (target != null) {
  81. processTarget(initialTargetName, target);
  82. }
  83. List<String> unprocessed = null;
  84. for (String s : myTargetsResolveMap.keySet()) {
  85. if (!myProcessedTargets.contains(s)) {
  86. if (unprocessed == null) {
  87. unprocessed = new ArrayList<String>();
  88. }
  89. unprocessed.add(s);
  90. }
  91. }
  92. if (unprocessed != null) {
  93. for (String targetName : unprocessed) {
  94. processTarget(targetName, myTargetsResolveMap.get(targetName));
  95. }
  96. }
  97. }
  98. }
  99. private void processTarget(String targetEffectiveName, AntDomTarget target) {
  100. myCurrentTargetEffectiveName.push(targetEffectiveName);
  101. try {
  102. target.accept(this);
  103. }
  104. finally {
  105. myCurrentTargetEffectiveName.pop();
  106. }
  107. }
  108. public void visitTarget(AntDomTarget target) {
  109. if (myStage == Stage.TARGETS_WALKUP_STAGE) {
  110. final String targetEffectiveName = myCurrentTargetEffectiveName.peek();
  111. if (!myProcessedTargets.contains(targetEffectiveName)) {
  112. myProcessedTargets.add(targetEffectiveName);
  113. final List<String> depsList = myDependenciesMap.get(targetEffectiveName);
  114. if (depsList != null) {
  115. for (String dependencyName : depsList) {
  116. final AntDomTarget dependency = getTargetByName(dependencyName);
  117. if (dependency != null) {
  118. processTarget(dependencyName, dependency);
  119. }
  120. }
  121. }
  122. super.visitTarget(target);
  123. }
  124. }
  125. else if (myStage == Stage.RESOLVE_MAP_BUILDING_STAGE){
  126. final String declaredTargetName = target.getName().getRawText();
  127. String effectiveTargetName = null;
  128. final InclusionKind inclusionKind = myNameContext.getCurrentInclusionKind();
  129. switch (inclusionKind) {
  130. case IMPORT:
  131. final String alias = myNameContext.getShortPrefix() + declaredTargetName;
  132. if (!myTargetsResolveMap.containsKey(declaredTargetName)) {
  133. effectiveTargetName = declaredTargetName;
  134. myTargetsResolveMap.put(alias, target);
  135. }
  136. else {
  137. effectiveTargetName = alias;
  138. }
  139. break;
  140. case INCLUDE:
  141. effectiveTargetName = myNameContext.getFQPrefix() + declaredTargetName;
  142. break;
  143. default:
  144. effectiveTargetName = declaredTargetName;
  145. break;
  146. }
  147. if (effectiveTargetName != null) {
  148. final AntDomTarget existingTarget = myTargetsResolveMap.get(effectiveTargetName);
  149. if (existingTarget != null && Comparing.equal(existingTarget.getAntProject(), target.getAntProject())) {
  150. duplicateTargetFound(existingTarget, target, effectiveTargetName);
  151. }
  152. else {
  153. myTargetsResolveMap.put(effectiveTargetName, target);
  154. final String dependsStr = target.getDependsList().getRawText();
  155. Map<String, Pair<AntDomTarget, String>> depsMap = Collections.emptyMap();
  156. if (dependsStr != null) {
  157. depsMap = new HashMap<String, Pair<AntDomTarget, String>>();
  158. final StringTokenizer tokenizer = new StringTokenizer(dependsStr, ",", false);
  159. while (tokenizer.hasMoreTokens()) {
  160. final String token = tokenizer.nextToken().trim();
  161. final String dependentTargetEffectiveName = myNameContext.calcTargetReferenceText(token);
  162. final AntDomTarget dependent = getTargetByName(dependentTargetEffectiveName);
  163. if (dependent != null) {
  164. depsMap.put(token, new Pair<AntDomTarget, String>(dependent, dependentTargetEffectiveName));
  165. }
  166. addDependency(effectiveTargetName, dependentTargetEffectiveName);
  167. }
  168. }
  169. targetDefined(target, effectiveTargetName, depsMap);
  170. }
  171. }
  172. }
  173. }
  174. @Override
  175. public void visitAntDomElement(AntDomElement element) {
  176. if (myStopped) {
  177. return;
  178. }
  179. if (element.equals(myContextElement)) {
  180. stop();
  181. }
  182. else {
  183. if (element instanceof PropertiesProvider) {
  184. propertyProviderFound(((PropertiesProvider)element));
  185. }
  186. }
  187. if (!myStopped) {
  188. //super.visitAntDomElement(element);
  189. for (Iterator<AntDomElement> iterator = element.getAntChildrenIterator(); iterator.hasNext();) {
  190. AntDomElement child = iterator.next();
  191. child.accept(this);
  192. if (myStage == Stage.TARGETS_WALKUP_STAGE) {
  193. if (myStopped) {
  194. break;
  195. }
  196. }
  197. }
  198. }
  199. }
  200. @Nullable
  201. protected AntDomTarget getTargetByName(String effectiveName) {
  202. return myTargetsResolveMap.get(effectiveName);
  203. }
  204. @NotNull
  205. public final Map<String, AntDomTarget> getDiscoveredTargets() {
  206. return Collections.unmodifiableMap(myTargetsResolveMap);
  207. }
  208. public AntDomElement getContextElement() {
  209. return myContextElement;
  210. }
  211. protected void stop() {
  212. myStopped = true;
  213. }
  214. /**
  215. * @param propertiesProvider
  216. * @return true if search should be continued and false in order to stop
  217. */
  218. protected abstract void propertyProviderFound(PropertiesProvider propertiesProvider);
  219. public void visitInclude(AntDomInclude includeTag) {
  220. processFileInclusion(includeTag, InclusionKind.INCLUDE);
  221. }
  222. public void visitImport(AntDomImport importTag) {
  223. processFileInclusion(importTag, InclusionKind.IMPORT);
  224. }
  225. public void visitProject(AntDomProject project) {
  226. if (!myVisitedProjects.contains(project)) {
  227. myVisitedProjects.add(project);
  228. try {
  229. super.visitProject(project);
  230. }
  231. finally {
  232. myVisitedProjects.remove(project);
  233. }
  234. }
  235. }
  236. private void processFileInclusion(AntDomIncludingDirective directive, final InclusionKind kind) {
  237. if (directive.equals(myContextElement)) {
  238. stop();
  239. }
  240. if (myStopped) {
  241. return;
  242. }
  243. final PsiFileSystemItem item = directive.getFile().getValue();
  244. if (item instanceof PsiFile) {
  245. final AntDomProject slaveProject = item instanceof XmlFile ? AntSupport.getAntDomProjectForceAntFile((XmlFile)item) : null;
  246. if (slaveProject != null) {
  247. myNameContext.pushPrefix(directive, kind, slaveProject);
  248. try {
  249. slaveProject.accept(this);
  250. }
  251. finally {
  252. myNameContext.popPrefix();
  253. }
  254. }
  255. }
  256. }
  257. private void addDependency(String effectiveTargetName, String dependentTargetEffectiveName) {
  258. List<String> list = myDependenciesMap.get(effectiveTargetName);
  259. if (list == null) {
  260. myDependenciesMap.put(effectiveTargetName, list = new ArrayList<String>());
  261. }
  262. list.add(dependentTargetEffectiveName);
  263. }
  264. /**
  265. * @param target
  266. * @param taregetEffectiveName
  267. * @param dependenciesMap Map declared dependency reference->pair[tareget object, effective reference name]
  268. */
  269. protected void targetDefined(AntDomTarget target, String taregetEffectiveName, Map<String, Pair<AntDomTarget, String>> dependenciesMap) {
  270. }
  271. /**
  272. * @param existingTarget
  273. * @param duplicatingTarget
  274. * @param taregetEffectiveName
  275. */
  276. protected void duplicateTargetFound(AntDomTarget existingTarget, AntDomTarget duplicatingTarget, String taregetEffectiveName) {
  277. }
  278. protected void stageCompleted(Stage completedStage, Stage startingStage) {
  279. }
  280. private static enum InclusionKind {
  281. INCLUDE("included"), IMPORT("imported"), TOPLEVEL("toplevel");
  282. private final String myDisplayName;
  283. private InclusionKind(String displayName) {
  284. myDisplayName = displayName;
  285. }
  286. public String toString() {
  287. return myDisplayName;
  288. }
  289. }
  290. private static class TargetsNameContext {
  291. private int myDefaultPrefixCounter = 0;
  292. private final LinkedList<Pair<String, InclusionKind>> myPrefixes = new LinkedList<Pair<String, InclusionKind>>();
  293. private String myCurrentPrefix = null;
  294. public String calcTargetReferenceText(String targetReferenceText) {
  295. if (!myPrefixes.isEmpty()) {
  296. final InclusionKind kind = myPrefixes.getLast().getSecond();
  297. switch (kind) {
  298. case IMPORT : return targetReferenceText;
  299. case INCLUDE : return getFQPrefix() + targetReferenceText;
  300. }
  301. }
  302. return targetReferenceText;
  303. }
  304. @NotNull
  305. public InclusionKind getCurrentInclusionKind() {
  306. if (myPrefixes.isEmpty()) {
  307. return InclusionKind.TOPLEVEL;
  308. }
  309. return myPrefixes.getLast().getSecond();
  310. }
  311. @NotNull
  312. public String getFQPrefix() {
  313. if (myCurrentPrefix != null) {
  314. return myCurrentPrefix;
  315. }
  316. if (myPrefixes.isEmpty()) {
  317. return "";
  318. }
  319. StringBuffer buf = new StringBuffer();
  320. for (Pair<String, InclusionKind> prefix : myPrefixes) {
  321. buf.append(prefix.getFirst());
  322. }
  323. return myCurrentPrefix = buf.toString();
  324. }
  325. @NotNull
  326. public String getShortPrefix() {
  327. return myPrefixes.isEmpty()? "" : myPrefixes.getLast().getFirst();
  328. }
  329. public void pushPrefix(AntDomIncludingDirective directive, final InclusionKind kind, final @NotNull AntDomProject slaveProject) {
  330. final String separator = directive.getTargetPrefixSeparatorValue();
  331. String prefix = directive.getTargetPrefix().getStringValue();
  332. if (prefix == null) {
  333. prefix = slaveProject.getName().getRawText();
  334. if (prefix == null) {
  335. prefix = "anonymous" + (myDefaultPrefixCounter++);
  336. }
  337. }
  338. pushPrefix(prefix.endsWith(separator) ? prefix : prefix + separator, kind);
  339. }
  340. public void pushPrefix(String prefix, InclusionKind kind) {
  341. myCurrentPrefix = null;
  342. myPrefixes.addLast(new Pair<String, InclusionKind>(prefix, kind));
  343. }
  344. public void popPrefix() {
  345. myCurrentPrefix = null;
  346. myPrefixes.removeLast();
  347. }
  348. }
  349. }