001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.ivy.core.resolve;
019:
020: import java.util.ArrayList;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.LinkedHashSet;
026:
027: import org.apache.ivy.core.IvyContext;
028: import org.apache.ivy.core.module.descriptor.Configuration;
029: import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
030: import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
031: import org.apache.ivy.core.module.id.ModuleId;
032: import org.apache.ivy.core.module.id.ModuleRevisionId;
033: import org.apache.ivy.core.resolve.IvyNodeEviction.EvictionData;
034: import org.apache.ivy.plugins.conflict.ConflictManager;
035: import org.apache.ivy.util.Checks;
036:
037: /**
038: * A visit node is an object used to represent one visit from one parent on an {@link IvyNode} of
039: * the dependency graph. During dependency resolution, the {@link ResolveEngine} visits nodes of the
040: * depency graph following the dependencies, thus the same node can be visited several times, if it
041: * is requested from several module. In this case you will have one VisitNode per parent and per
042: * root module configuration. Thus VisitNode stores data specific to the visit:
043: * <ul>
044: * <li>parent</li>
045: * the node from which the visit is occuring
046: * <li>parentConf</li>
047: * the configuration of the parent in which this node is visited
048: * <li>rootModuleConf</li>
049: * the configuration of the root module which is currently resolved
050: * </ul>
051: */
052: public class VisitNode {
053: /**
054: * The node which is currently visited
055: */
056: private IvyNode node;
057:
058: /**
059: * Represents the current parent of the node during ivy visit of dependency graph.
060: */
061: private VisitNode parent = null;
062:
063: /**
064: * The root node of the current visit It is null until it is required, see getRoot
065: */
066: private VisitNode root = null;
067:
068: /**
069: * Direct path from root to this node. Note that the colleciton is ordered but is not a list
070: * implementation This collection is null until it is required, see getPath
071: */
072: private Collection path = null; // Collection(VisitNode)
073:
074: /**
075: * The configuration of the parent module in the current visit
076: */
077: private String parentConf = null;
078:
079: /**
080: * The configuration requested by the parent Note that this is the actual conf requested by the
081: * parent, not a configuration extended by the requested conf which actually trigger the node
082: * visit
083: */
084: private String requestedConf;
085:
086: /**
087: * The root configuration which is currently visited
088: */
089: private String rootModuleConf;
090:
091: /**
092: * Shared ResolveData instance, which can be used to get info on the current resolve process
093: */
094: private ResolveData data;
095:
096: /**
097: * Boolean.TRUE if a node with a same module id as the one visited has already been visited in
098: * the current path. null if not computed yet Boolean.FALSE otherwise
099: */
100: private Boolean isCircular;
101:
102: public VisitNode(ResolveData data, IvyNode node, VisitNode parent,
103: String rootModuleConf, String parentConf) {
104: Checks.checkNotNull(data, "data");
105: Checks.checkNotNull(node, "node");
106: Checks.checkNotNull(rootModuleConf, "rootModuleConf");
107:
108: this .data = data;
109: this .node = node;
110: this .parent = parent;
111: this .rootModuleConf = rootModuleConf;
112: this .parentConf = parentConf;
113:
114: this .data.register(this );
115: }
116:
117: public IvyNode getNode() {
118: return node;
119: }
120:
121: /**
122: * @return Returns the configuration requested by the parent
123: */
124: public String getRequestedConf() {
125: return requestedConf;
126: }
127:
128: public void setRequestedConf(String requestedConf) {
129: this .requestedConf = requestedConf;
130: }
131:
132: public VisitNode getParent() {
133: return parent;
134: }
135:
136: public VisitNode getRoot() {
137: if (root == null) {
138: root = computeRoot();
139: }
140: return root;
141: }
142:
143: public Collection getPath() {
144: if (path == null) {
145: path = computePath();
146: }
147: return path;
148: }
149:
150: private Collection computePath() {
151: if (parent != null) {
152: Collection p = new LinkedHashSet(parent.getPath());
153: p.add(this );
154: return p;
155: } else {
156: return Collections.singletonList(this );
157: }
158: }
159:
160: private VisitNode computeRoot() {
161: if (node.isRoot()) {
162: return this ;
163: } else if (parent != null) {
164: return parent.getRoot();
165: } else {
166: return null;
167: }
168: }
169:
170: public String getParentConf() {
171: return parentConf;
172: }
173:
174: public void setParentConf(String parentConf) {
175: this .parentConf = parentConf;
176: }
177:
178: public String getRootModuleConf() {
179: return rootModuleConf;
180: }
181:
182: public static VisitNode getRoot(VisitNode parent) {
183: VisitNode root = parent;
184: Collection path = new HashSet();
185: path.add(root);
186: while (root.getParent() != null && !root.getNode().isRoot()) {
187: if (path.contains(root.getParent())) {
188: return root;
189: }
190: root = root.getParent();
191: path.add(root);
192: }
193: return root;
194: }
195:
196: /**
197: * Returns true if the current dependency descriptor is transitive and the parent configuration
198: * is transitive. Otherwise returns false.
199: *
200: * @return true if current node is transitive and the parent configuration is transitive.
201: */
202: public boolean isTransitive() {
203: return (data.isTransitive()
204: && node.getDependencyDescriptor(getParentNode())
205: .isTransitive() && isParentConfTransitive());
206: }
207:
208: /**
209: * Checks if the current node's parent configuration is transitive.
210: *
211: * @param node
212: * current node
213: * @return true if the node's parent configuration is transitive
214: */
215: protected boolean isParentConfTransitive() {
216: String conf = getParent().getRequestedConf();
217: if (conf == null) {
218: return true;
219: }
220: Configuration parentConf = getParentNode().getConfiguration(
221: conf);
222: return parentConf.isTransitive();
223:
224: }
225:
226: /**
227: * Returns the 'real' node currently visited. 'Real' means that if we are visiting a node
228: * created originally with only a version constraint, and if this version constraint has been
229: * resolved to an existing node in the graph, we will return the existing node, and not the one
230: * originally used which is about to be discarded, since it's not possible to have in the graph
231: * two nodes for the same ModuleRevisionId
232: *
233: * @return the 'real' node currently visited.
234: */
235: public IvyNode getRealNode() {
236: IvyNode node = this .node.getRealNode();
237: if (node != null) {
238: return node;
239: } else {
240: return this .node;
241: }
242: }
243:
244: /**
245: * Ask to the current visited node to use a real node only, if one exist. See getRealNode for
246: * details about what a 'real' node is.
247: */
248: public void useRealNode() {
249: if (parent != null) { // use real node make sense only for non root module
250: IvyNode node = data.getNode(this .node.getId());
251: if (node != null && node != this .node) {
252: this .node = node;
253: }
254: }
255: }
256:
257: public boolean loadData(String conf, boolean shouldBePublic) {
258: boolean loaded = node.loadData(rootModuleConf, getParentNode(),
259: parentConf, conf, shouldBePublic);
260: if (loaded) {
261: useRealNode();
262:
263: // if the revision was a dynamic one (which has now be resolved)
264: // we now register this node on the resolved id
265: if (data.getSettings().getVersionMatcher().isDynamic(
266: getId())) {
267: data.register(node.getResolvedId(), this );
268: }
269: }
270:
271: return loaded;
272: }
273:
274: public Collection getDependencies(String conf) {
275: Collection deps = node.getDependencies(rootModuleConf, conf,
276: requestedConf);
277: Collection ret = new ArrayList(deps.size());
278: for (Iterator iter = deps.iterator(); iter.hasNext();) {
279: IvyNode depNode = (IvyNode) iter.next();
280: ret.add(traverseChild(conf, depNode));
281: }
282: return ret;
283: }
284:
285: /**
286: * Returns a VisitNode for the given node. The given node must be a representation of the same
287: * module (usually in another revision) as the one visited by this node. The given node must
288: * also have been already visited.
289: *
290: * @param node
291: * the node to visit
292: * @return a VisitNode for the given node
293: */
294: VisitNode gotoNode(IvyNode node) {
295: if (!getModuleId().equals(node.getModuleId())) {
296: throw new IllegalArgumentException(
297: "You can't use gotoNode for a node which does not represent the same Module "
298: + "as the one represented by this node.\nCurrent node module id="
299: + getModuleId() + " Given node module id="
300: + node.getModuleId());
301: }
302: VisitData visitData = data.getVisitData(node.getId());
303: if (visitData == null) {
304: throw new IllegalArgumentException(
305: "You can't use gotoNode with a node which has not been visited yet.\n"
306: + "Given node id=" + node.getId());
307: }
308: for (Iterator iter = visitData.getVisitNodes(rootModuleConf)
309: .iterator(); iter.hasNext();) {
310: VisitNode vnode = (VisitNode) iter.next();
311: if ((parent == null && vnode.getParent() == null)
312: || (parent != null && parent.getId().equals(
313: vnode.getParent().getId()))) {
314: return vnode;
315: }
316: }
317: // the node has not yet been visited from the current parent, we create a new visit node
318: return traverse(parent, parentConf, node);
319: }
320:
321: private VisitNode traverseChild(String parentConf, IvyNode child) {
322: VisitNode parent = this ;
323: return traverse(parent, parentConf, child);
324: }
325:
326: private VisitNode traverse(VisitNode parent, String parentConf,
327: IvyNode node) {
328: if (getPath().contains(node)) {
329: IvyContext.getContext().getCircularDependencyStrategy()
330: .handleCircularDependency(
331: toMrids(getPath(), node.getId()));
332: // we do not use the new parent, but the first one, to always be able to go up to the
333: // root
334: // parent = getVisitNode(depNode).getParent();
335: }
336: return new VisitNode(data, node, parent, rootModuleConf,
337: parentConf);
338: }
339:
340: private ModuleRevisionId[] toMrids(Collection path,
341: ModuleRevisionId last) {
342: ModuleRevisionId[] ret = new ModuleRevisionId[path.size() + 1];
343: int i = 0;
344: for (Iterator iter = path.iterator(); iter.hasNext(); i++) {
345: VisitNode node = (VisitNode) iter.next();
346: ret[i] = node.getNode().getId();
347: }
348: ret[ret.length - 1] = last;
349: return ret;
350: }
351:
352: public ModuleRevisionId getResolvedId() {
353: return node.getResolvedId();
354: }
355:
356: public void updateConfsToFetch(Collection confs) {
357: node.updateConfsToFetch(confs);
358: }
359:
360: public ModuleRevisionId getId() {
361: return node.getId();
362: }
363:
364: public boolean isEvicted() {
365: return node.isEvicted(rootModuleConf);
366: }
367:
368: public String[] getRealConfs(String conf) {
369: return node.getRealConfs(conf);
370: }
371:
372: public boolean hasProblem() {
373: return node.hasProblem();
374: }
375:
376: public Configuration getConfiguration(String conf) {
377: return node.getConfiguration(conf);
378: }
379:
380: public EvictionData getEvictedData() {
381: return node.getEvictedData(rootModuleConf);
382: }
383:
384: public DependencyDescriptor getDependencyDescriptor() {
385: return node.getDependencyDescriptor(getParentNode());
386: }
387:
388: private IvyNode getParentNode() {
389: return parent == null ? null : parent.getNode();
390: }
391:
392: /**
393: * Returns true if this node can already be found in the path
394: *
395: * @return
396: */
397: public boolean isCircular() {
398: if (isCircular == null) {
399: if (parent != null) {
400: isCircular = Boolean.FALSE; // asumme it's false, and see if it isn't by checking
401: // the parent path
402: for (Iterator iter = parent.getPath().iterator(); iter
403: .hasNext();) {
404: VisitNode ancestor = (VisitNode) iter.next();
405: if (getId().getModuleId().equals(
406: ancestor.getId().getModuleId())) {
407: isCircular = Boolean.TRUE;
408: break;
409: }
410: }
411: } else {
412: isCircular = Boolean.FALSE;
413: }
414: }
415: return isCircular.booleanValue();
416: }
417:
418: public String[] getConfsToFetch() {
419: return node.getConfsToFetch();
420: }
421:
422: public String[] getRequiredConfigurations(VisitNode in,
423: String inConf) {
424: return node.getRequiredConfigurations(in.getNode(), inConf);
425: }
426:
427: public ModuleId getModuleId() {
428: return node.getModuleId();
429: }
430:
431: public Collection getResolvedRevisions(ModuleId mid) {
432: return node.getResolvedRevisions(mid, rootModuleConf);
433: }
434:
435: public void markEvicted(EvictionData evictionData) {
436: node.markEvicted(evictionData);
437: }
438:
439: public String[] getRequiredConfigurations() {
440: return node.getRequiredConfigurations();
441: }
442:
443: /**
444: * Marks the current node as evicted by the the given selected IvyNodes, in the given parent and
445: * root module configuration, with the given {@link ConflictManager}
446: *
447: * @param parent
448: * the VisitNode in which eviction has been made
449: * @param conflictMgr
450: * the conflict manager responsible for the eviction
451: * @param selected
452: * a Collection of {@link IvyNode} which have been selected
453: */
454: public void markEvicted(VisitNode parent,
455: ConflictManager conflictMgr, Collection selected) {
456: node.markEvicted(rootModuleConf, parent.getNode(), conflictMgr,
457: selected);
458: }
459:
460: public ModuleDescriptor getDescriptor() {
461: return node.getDescriptor();
462: }
463:
464: public EvictionData getEvictionDataInRoot(String rootModuleConf,
465: VisitNode ancestor) {
466: return node.getEvictionDataInRoot(rootModuleConf, ancestor
467: .getNode());
468: }
469:
470: public Collection getEvictedRevisions(ModuleId moduleId) {
471: return node.getEvictedRevisions(moduleId, rootModuleConf);
472: }
473:
474: // public void setRootModuleConf(String rootModuleConf) {
475: // if (rootModuleConf != null && !rootModuleConf.equals(rootModuleConf)) {
476: // _confsToFetch.clear(); // we change of root module conf => we discard all confs to fetch
477: // }
478: // if (rootModuleConf != null && rootModuleConf.equals(rootModuleConf)) {
479: // _selectedDeps.put(new ModuleIdConf(_id.getModuleId(), rootModuleConf),
480: // Collections.singleton(this));
481: // }
482: // rootModuleConf = rootModuleConf;
483: // }
484:
485: public String toString() {
486: return node.toString();
487: }
488:
489: }
|