Coverage Report - org.kuali.rice.krms.framework.engine.TermResolutionEngineImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
TermResolutionEngineImpl
93%
125/134
86%
69/80
3.231
TermResolutionEngineImpl$1
85%
6/7
N/A
3.231
TermResolutionEngineImpl$InvalidResolutionPathException
0%
0/2
N/A
3.231
TermResolutionEngineImpl$TermResolverKey
94%
51/54
76%
29/38
3.231
TermResolutionEngineImpl$ToVisit
57%
16/28
33%
6/18
3.231
TermResolutionEngineImpl$Visited
100%
18/18
N/A
3.231
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
 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 org.kuali.rice.krms.framework.engine;
 17  
 
 18  
 import java.util.ArrayList;
 19  
 import java.util.Arrays;
 20  
 import java.util.Collection;
 21  
 import java.util.Collections;
 22  
 import java.util.HashMap;
 23  
 import java.util.HashSet;
 24  
 import java.util.Iterator;
 25  
 import java.util.LinkedList;
 26  
 import java.util.List;
 27  
 import java.util.Map;
 28  
 import java.util.Map.Entry;
 29  
 import java.util.PriorityQueue;
 30  
 import java.util.Set;
 31  
 
 32  
 import org.apache.commons.collections.CollectionUtils;
 33  
 import org.apache.commons.lang.ArrayUtils;
 34  
 import org.apache.commons.lang.StringUtils;
 35  
 import org.kuali.rice.krms.api.engine.Term;
 36  
 import org.kuali.rice.krms.api.engine.TermResolutionEngine;
 37  
 import org.kuali.rice.krms.api.engine.TermResolutionException;
 38  
 import org.kuali.rice.krms.api.engine.TermResolver;
 39  
 
 40  72
 public class TermResolutionEngineImpl implements TermResolutionEngine {
 41  1
         private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TermResolutionEngineImpl.class);
 42  
         
 43  72
         private final Map<String, List<TermResolver<?>>> termResolversByOutput = new HashMap<String, List<TermResolver<?>>>();
 44  72
         private final Map<TermResolverKey, TermResolver<?>> termResolversByKey = new HashMap<TermResolverKey, TermResolver<?>>(); 
 45  
         
 46  
         // should this use soft refs?  Will require some refactoring to check if the referenced object is around;
 47  72
         private final Map<Term, Object> termCache = new HashMap<Term, Object>();
 48  
 
 49  
         @Override
 50  
         public void addTermValue(Term term, Object value) {
 51  73
                 termCache.put(term, value);
 52  73
         }
 53  
         
 54  
         @Override
 55  
         public void addTermResolver(TermResolver<?> termResolver) {
 56  68
                 if (termResolver == null) throw new IllegalArgumentException("termResolver is reuqired");
 57  68
                 if (termResolver.getOutput() == null) throw new IllegalArgumentException("termResolver.getOutput() must not be null");
 58  
 
 59  68
                 List<TermResolver<?>> termResolvers = termResolversByOutput.get(termResolver.getOutput());
 60  68
                 if (termResolvers == null) {
 61  63
                         termResolvers = new LinkedList<TermResolver<?>>();
 62  63
                         termResolversByOutput.put(termResolver.getOutput(), termResolvers);
 63  
                 }
 64  68
                 termResolversByKey.put(new TermResolverKey(termResolver), termResolver);
 65  68
                 termResolvers.add(termResolver);
 66  68
         }
 67  
 
 68  
         @SuppressWarnings("unchecked")
 69  
         @Override
 70  
         public <T> T resolveTerm(Term term) throws TermResolutionException {
 71  49
                 LOG.debug("+--> resolveTerm(" + term + ")");
 72  49
                 if (termCache.containsKey(term)) return (T)termCache.get(term);
 73  
                 
 74  41
                 String termName = term.getName();
 75  
                 
 76  
                 // build plan w/ termName spec for correct TermResolver selection
 77  41
                 List<TermResolverKey> resolutionPlan = buildTermResolutionPlan(termName);
 78  
                 // TODO: could cache these plans somewhere, since future agendas will likely require the same plans
 79  
                 
 80  41
                 LOG.debug("resolutionPlan: " + (resolutionPlan == null ? "null" : StringUtils.join(resolutionPlan.iterator(), ", ")));
 81  
                 
 82  41
                 if (resolutionPlan != null) {
 83  38
                         LOG.debug("executing plan");
 84  38
                         for (TermResolverKey resolverKey : resolutionPlan) {
 85  50
                                 TermResolver<?> resolver = termResolversByKey.get(resolverKey);
 86  
                                 
 87  
                                 // build prereqs
 88  50
                                 Map<String, Object> resolvedPrereqs = new HashMap<String, Object>();
 89  
                                 
 90  
                                 // The plan order should guarantee these prereqs exist and are cached.
 91  50
                                 for (String prereq : resolver.getPrerequisites()) {
 92  25
                                         Object resolvedPrereq = termCache.get(new Term(prereq, null));
 93  25
                                         resolvedPrereqs.put(prereq, resolvedPrereq);
 94  25
                                 }
 95  
                                 
 96  50
                                 Map<String, String> providedParameters = Collections.emptyMap();
 97  
                                 // The destination Term (and only the dest Term) can be parameterized
 98  50
                                 if (termName.equals(resolver.getOutput())) {
 99  38
                                         providedParameters = term.getParameters();
 100  
                                         
 101  
                                         // throw a TermResolutionException if the params doen't match up
 102  38
                                         validateTermParameters(resolver, providedParameters);
 103  
                                 }
 104  
                                 
 105  49
                                 Object resolvedTerm = resolver.resolve(resolvedPrereqs, providedParameters);
 106  49
                                 if (termName.equals(resolver.getOutput())) {
 107  37
                                         termCache.put(term, resolvedTerm);
 108  
                                 } else {
 109  12
                                         if (!CollectionUtils.isEmpty(resolver.getParameterNames())) {
 110  
                                                 // Shouldn't happen due to checks in buildResolutionPlan
 111  0
                                                 throw new TermResolutionException("TermResolvers requiring parameters cannot be intermediates in the Term resolution plan", resolver, providedParameters);
 112  
                                         }
 113  12
                                         termCache.put(new Term(resolver.getOutput(), null), resolvedTerm);
 114  
                                 }
 115  49
                         }                
 116  
                 } else {
 117  3
                         throw new TermResolutionException("Unable to plan the resolution of " + term, null, null);
 118  
                 }
 119  37
                 return (T)termCache.get(term);
 120  
         }
 121  
 
 122  
         /**
 123  
          * This method checks that the required parameters (as returned by the {@link TermResolver} via 
 124  
          * {@link TermResolver#getParameterNames()}) are met in the {@link Map} of provided parameters.
 125  
          * 
 126  
          * @param resolver
 127  
          * @param providedParameters
 128  
          * @throws TermResolutionException
 129  
          */
 130  
         private void validateTermParameters(TermResolver<?> resolver,
 131  
                         Map<String, String> providedParameters)
 132  
                         throws TermResolutionException {
 133  
                 // validate that params match what the TermResolver requires
 134  38
                 if (!providedParameters.keySet().equals(resolver.getParameterNames())) {
 135  1
                         StringBuilder sb = new StringBuilder();
 136  
 
 137  1
                         boolean first = true;
 138  1
                         for (Entry<String,String> param : providedParameters.entrySet()) {
 139  0
                                 if (first) {
 140  0
                                         first = false;
 141  
                                 } else {
 142  0
                                         sb.append(",");
 143  
                                 }
 144  0
                                 sb.append(param.getKey());
 145  0
                                 sb.append("=");
 146  0
                                 sb.append(param.getValue());
 147  
                         }
 148  
 
 149  1
                         throw new TermResolutionException("provided parameters ("+ sb
 150  
                                         +") do not match requirements ("
 151  
                                         + StringUtils.join(resolver.getParameterNames(), ",") +")", resolver, providedParameters);
 152  
                 }
 153  37
         }
 154  
 
 155  
         protected List<TermResolverKey> buildTermResolutionPlan(String termName) {
 156  
                 // our result
 157  42
                 List<TermResolverKey> resolutionPlan = null;
 158  
 
 159  
                 // Holds the resolvers we've visited, along with the needed metadata for generating our final plan
 160  42
                 Map<TermResolverKey, Visited> visitedByKey = new HashMap<TermResolverKey, Visited>();
 161  
 
 162  
                 // this holds a least cost first list of nodes remaining to be explored
 163  42
                 PriorityQueue<ToVisit> toVisits = new PriorityQueue<ToVisit>(); // nice grammar there cowboy
 164  
 
 165  
                 // dummy resolver to be the root of this tree
 166  
                 // Do I really need this?  Yes, because there may be more than one resolver that resolves to the desired termName,
 167  
                 // so this destination unifies the trees of those candidate resolvers
 168  42
                 TermResolver destination = createDestination(termName); // problem is we can't get this one out of the registry
 169  42
                 TermResolverKey destinationKey = new TermResolverKey(destination);
 170  
 
 171  42
                 LOG.debug("Beginning resolution tree search for " + termName);
 172  
 
 173  
                 // seed our queue of resolvers to visit
 174  
                 // need to be aware of null parent for root ToVisit
 175  42
                 toVisits.add(new ToVisit(0, destination, null));
 176  
 
 177  
                 // there may not be a viable plan
 178  42
                 boolean plannedToDestination = false;
 179  
 
 180  
                 // We'll do a modified Dijkstra's shortest path algorithm, where at each leaf we see if we've planned out
 181  
                 // termName resolution all the way up to the root, our destination.  If so, we just reconstruct our plan.
 182  146
                 while (!plannedToDestination && toVisits.size() > 0) {
 183  
                         // visit least cost node remaining
 184  104
                         ToVisit visiting = toVisits.poll();
 185  
 
 186  104
                         LOG.debug("visiting " + visiting.getTermResolverKey());
 187  
 
 188  
                         // the resolver is the edge in our tree -- we don't get it directly from the termResolversByKey Map, because it could be our destination
 189  104
                         TermResolver resolver = getResolver(visiting.getTermResolverKey(), destination, destinationKey);
 190  104
                         TermResolver parent = getResolver(visiting.getParentKey(), destination, destinationKey);
 191  
 
 192  104
                         if (visitedByKey.containsKey(visiting.getTermResolverKey())) {
 193  1
                                 continue; // We've already visited this one
 194  
                         }
 195  
 
 196  103
                         Visited parentVisited = visitedByKey.get(visiting.getParentKey());
 197  
 
 198  103
                         if (resolver == null) throw new RuntimeException("Unable to get TermResolver by its key");
 199  103
                         Set<String> prereqs = resolver.getPrerequisites();
 200  
                         // keep track of any prereqs that we already have handy
 201  103
                         List<String> metPrereqs = new LinkedList<String>();
 202  
 
 203  
                         // see what prereqs we have already, and which we'll need to visit
 204  103
                         if (prereqs != null) for (String prereq : prereqs) {
 205  78
                                 if (!termCache.containsKey(new Term(prereq, null))) {
 206  
                                         // enqueue all resolvers in toVisits
 207  64
                                         List<TermResolver<?>> prereqResolvers = termResolversByOutput.get(prereq);
 208  64
                                         if (prereqResolvers != null) for (TermResolver prereqResolver : prereqResolvers) {
 209  
                                                 // Only TermResolvers that don't take paramaterized terms can be chained, so:
 210  
                                                 // if the TermResolver doesn't take parameters, or it resolves the output termName
 211  66
                                                 if (CollectionUtils.isEmpty(prereqResolver.getParameterNames()) ||
 212  
                                                                 termName.equals(prereqResolver.getOutput()))
 213  
                                                 {
 214  
                                                         // queue it up for visiting
 215  65
                                                         toVisits.add(new ToVisit(visiting.getCost() /* cost to get to this resolver */, prereqResolver, resolver));
 216  
                                                 }
 217  
                                         }
 218  64
                                 } else {
 219  14
                                         metPrereqs.add(prereq);
 220  
                                 }
 221  
                         }
 222  
 
 223  
                         // Build visited info
 224  103
                         Visited visited = buildVisited(resolver, parentVisited, metPrereqs);
 225  103
                         visitedByKey.put(visited.getResolverKey(), visited);
 226  
 
 227  103
                         plannedToDestination = isPlannedBackToDestination(visited, destinationKey, visitedByKey);
 228  103
                 }
 229  
 
 230  42
                 if (plannedToDestination) {
 231  
                         // build result from Visited tree.
 232  39
                         resolutionPlan = new LinkedList<TermResolverKey>();
 233  
 
 234  39
                         assembleLinearResolutionPlan(visitedByKey.get(destinationKey), visitedByKey, resolutionPlan);
 235  
                 }
 236  42
                 return resolutionPlan;
 237  
         }
 238  
 
 239  
         /**
 240  
          *  @return the Visited object for the resolver we just, er, well, visited.
 241  
          */
 242  
         private Visited buildVisited(TermResolver resolver, Visited parentVisited, Collection<String> metPrereqs) {
 243  103
                 Visited visited = null;
 244  
 
 245  103
                 List<TermResolverKey> pathTo = new ArrayList<TermResolverKey>(1 + (parentVisited == null ? 0 : parentVisited.pathTo.size()));
 246  103
                 if (parentVisited != null && parentVisited.getPathTo() != null) pathTo.addAll(parentVisited.getPathTo());
 247  103
                 if (parentVisited != null) pathTo.add(parentVisited.getResolverKey());
 248  103
                 TermResolverKey resolverKey = new TermResolverKey(resolver);
 249  
 
 250  103
                 visited = new Visited(resolverKey, pathTo, resolver.getPrerequisites(), resolver.getCost() + (parentVisited == null ? 0 : parentVisited.getCost()));
 251  103
                 for (String metPrereq : metPrereqs) { visited.addPlannedPrereq(metPrereq); }
 252  
 
 253  103
                 return visited;
 254  
         }
 255  
 
 256  
         /**
 257  
          * our dummy destination isn't allowed to pollute termResolversByKey, hence the ugly conditional encapsulated herein
 258  
          */
 259  
         private TermResolver getResolver(TermResolverKey resolverKey,
 260  
                         TermResolver destination, TermResolverKey destinationKey) {
 261  
                 TermResolver resolver;
 262  208
                 if (destinationKey.equals(resolverKey)) {
 263  86
                         resolver = destination;
 264  
                 } else {
 265  122
                         resolver = termResolversByKey.get(resolverKey);
 266  
                 }
 267  208
                 return resolver;
 268  
         }
 269  
 
 270  
         /**
 271  
          * @param visited
 272  
          * @param destinationKey
 273  
          * @param visitedByKey
 274  
          * @return
 275  
          */
 276  
         private boolean isPlannedBackToDestination(Visited visited,
 277  
                         TermResolverKey destinationKey,
 278  
                         Map<TermResolverKey, Visited> visitedByKey) {
 279  103
                 boolean plannedToDestination = false;
 280  103
                 if (visited.isFullyPlanned()) {
 281  42
                         LOG.debug("Leaf! this resolver's prereqs are all avialable.");
 282  
                         // no traversing further yet, instead we need to check how far up the tree is fully planned out
 283  
                         // step backwards toward the root of the tree and see if we're good all the way to our objective.
 284  
                         // if a node fully planned, move up the tree (towards the root) to see if its parent is fully planned, and so on.
 285  
 
 286  42
                         if (visited.getPathTo().size() > 0) {
 287  
                                 // reverse the path to
 288  42
                                 List<TermResolverKey> reversePathTo = new ArrayList<TermResolverKey>(visited.getPathTo());
 289  42
                                 Collections.reverse(reversePathTo);
 290  
 
 291  
                                 // we use this to propagate resolutions up the tree
 292  42
                                 Visited previousAncestor = visited;
 293  
 
 294  42
                                 for (TermResolverKey ancestorKey : reversePathTo) {
 295  
 
 296  51
                                         Visited ancestorVisited = visitedByKey.get(ancestorKey);
 297  51
                                         ancestorVisited.addPlannedPrereq(previousAncestor.getResolverKey());
 298  
 
 299  51
                                         LOG.debug("checking ancestor " + ancestorKey);
 300  
 
 301  51
                                         if (ancestorVisited.isFullyPlanned() && ancestorKey.equals(destinationKey)) {
 302  
                                                 // Woot! Job's done!
 303  39
                                                 plannedToDestination = true;
 304  39
                                                 break;
 305  12
                                         } else if (!ancestorVisited.isFullyPlanned()) { // if the ancestor isn't fully planned, we're done
 306  3
                                                 LOG.debug("Still have planning to do.");
 307  3
                                                 break;
 308  
                                         }
 309  
                                         // update previousAncestor reference for next iteration
 310  9
                                         previousAncestor = ancestorVisited;
 311  9
                                 }
 312  42
                         } else {
 313  
                                 // we're done already! do a jig?
 314  0
                                 LOG.debug("Trivial plan.");
 315  0
                                 plannedToDestination = true;
 316  
                         }
 317  
                 }
 318  103
                 return plannedToDestination;
 319  
         }
 320  
 
 321  
         private void assembleLinearResolutionPlan(Visited visited, Map<TermResolverKey, Visited> visitedByKey, List<TermResolverKey> plan) {
 322  
                 // DFS
 323  90
                 for (TermResolverKey prereqResolverKey : visited.getPrereqResolvers()) {
 324  51
                         Visited prereqVisited = visitedByKey.get(prereqResolverKey);
 325  51
                         assembleLinearResolutionPlan(prereqVisited, visitedByKey, plan);
 326  51
                         plan.add(prereqResolverKey);
 327  51
                 }
 328  90
         }
 329  
 
 330  
         /**
 331  
          * Create our dummy destination resolver
 332  
          * @param termName
 333  
          */
 334  
         private TermResolver<? extends Object> createDestination(final String termName) {
 335  42
                 TermResolver<?> destination = new TermResolver<Object>() {
 336  42
                         final String dest = "termResolutionEngineDestination";
 337  
                         @Override
 338  
                         public int getCost() {
 339  84
                                 return 0;
 340  
                         }
 341  
                         @Override
 342  
                         public String getOutput() {
 343  171
                                 return dest;
 344  
                         }
 345  
                         @Override
 346  
                         public Set<String> getPrerequisites() {
 347  255
                                 return Collections.<String>singleton(termName);
 348  
                         }
 349  
                         @Override
 350  
                         public Set<String> getParameterNames() {
 351  171
                                 return Collections.emptySet();
 352  
                         }
 353  
                         @Override
 354  
                         public Object resolve(Map<String, Object> resolvedPrereqs, Map<String, String> parameters) throws TermResolutionException {
 355  0
                                 return null;
 356  
                         }
 357  
                 };
 358  42
                 return destination;
 359  
         }
 360  
 
 361  
 
 362  21
         private static class ToVisit implements Comparable<ToVisit> {
 363  
 
 364  
                 private final int precost;
 365  
                 private final int addcost;
 366  
                 private final TermResolverKey resolverKey;
 367  
 
 368  
                 // the parent key is not being used for comparison purposes currently
 369  
                 private final TermResolverKey parentKey;
 370  
 
 371  
                 /**
 372  
                  * @param precost
 373  
                  * @param resolver
 374  
                  */
 375  
                 public ToVisit(int precost, TermResolver resolver, TermResolver parent) {
 376  107
                         super();
 377  107
                         this.precost = precost;
 378  107
                         this.addcost = resolver.getCost();
 379  107
                         this.resolverKey = new TermResolverKey(resolver);
 380  
 
 381  107
                         if (parent != null) {
 382  65
                                 this.parentKey = new TermResolverKey(parent);
 383  
                         } else {
 384  42
                                 this.parentKey = null;
 385  
                         }
 386  107
                 }
 387  
 
 388  
                 public int getCost() {
 389  133
                         return precost + addcost;
 390  
                 }
 391  
 
 392  
                 @Override
 393  
                 public boolean equals(Object obj) {
 394  0
                         if (this == obj) return true;
 395  0
                         if (obj == null) return false;
 396  0
                         if (getClass() != obj.getClass())
 397  0
                                 return false;
 398  0
                         ToVisit other = (ToVisit)obj;
 399  0
                         return this.compareTo(other) == 0;
 400  
                 }
 401  
 
 402  
                 /* (non-Javadoc)
 403  
                  * @see java.lang.Object#hashCode()
 404  
                  */
 405  
                 @Override
 406  
                 public int hashCode() {
 407  0
                         final int prime = 31;
 408  0
                         int result = 1;
 409  0
                         result = prime * result + getCost();
 410  0
                         result = prime * result + ((resolverKey == null) ? 0 : resolverKey.hashCode());
 411  0
                         return result;
 412  
                 }
 413  
 
 414  
                 /**
 415  
                  * {@inheritDoc Comparable}
 416  
                  */
 417  
                 @Override
 418  
                 public int compareTo(ToVisit o) {
 419  21
                         if (o == null) return 1;
 420  21
                         if (getCost() > o.getCost()) return 1;
 421  13
                         if (getCost() < o.getCost()) return -1;
 422  13
                         return resolverKey.compareTo(o.resolverKey);
 423  
                 }
 424  
 
 425  
                 public TermResolverKey getTermResolverKey() {
 426  312
                         return resolverKey;
 427  
                 }
 428  
 
 429  
                 public TermResolverKey getParentKey() {
 430  207
                         return parentKey;
 431  
                 }
 432  
 
 433  
                 @Override
 434  
                 public String toString() {
 435  0
                         return getClass().getSimpleName()+"("+getTermResolverKey()+")";
 436  
                 }
 437  
         }
 438  
 
 439  0
         protected static class TermResolverKey implements Comparable<TermResolverKey> {
 440  
                 private final List<String> data;
 441  
                 private final String [] params;
 442  
 
 443  
                 // just used for toArray call
 444  1
                 private static final String[] TERM_SPEC_TYPER = new String[0];
 445  1
                 private static final String[] STRING_TYPER = new String[0];
 446  
 
 447  
                 public TermResolverKey(TermResolver resolver) {
 448  385
                         this(resolver.getOutput(), resolver.getParameterNames(), resolver.getPrerequisites());
 449  385
                 }
 450  
 
 451  385
                 private TermResolverKey(String dest, Set<String> paramSet, Set<String> prereqs) {
 452  385
                         if (dest == null) throw new IllegalArgumentException("dest parameter must not be null");
 453  385
                         data = new ArrayList<String>(1 + ((prereqs == null) ? 0 : prereqs.size())); // size ArrayList perfectly
 454  385
                         data.add(dest);
 455  385
                         if (!CollectionUtils.isEmpty(paramSet)) {
 456  10
                                 params = paramSet.toArray(STRING_TYPER);
 457  10
                                 Arrays.sort(params);
 458  
                         } else {
 459  375
                                 params = STRING_TYPER; // just assigning a handy zero length String array
 460  
                         }
 461  385
                         if (prereqs != null) {
 462  
                                 // this is painful, but to be able to compare we need a defined order
 463  385
                                 String [] prereqsArray = prereqs.toArray(TERM_SPEC_TYPER);
 464  385
                                 Arrays.sort(prereqsArray);
 465  705
                                 for (String prereq : prereqsArray) {
 466  320
                                         data.add(prereq);
 467  
                                 }
 468  
                         }
 469  385
                 }
 470  
 
 471  
                 public String getOutput() {
 472  102
                         return data.get(0);
 473  
                 }
 474  
 
 475  
                 @Override
 476  
                 public int compareTo(TermResolverKey o) {
 477  458
                         if (o == null) return 1;
 478  
 
 479  458
                         Iterator<String> mDataIter = data.iterator();
 480  458
                         Iterator<String> oDataIter = o.data.iterator();
 481  
 
 482  1141
                         while (mDataIter.hasNext() && oDataIter.hasNext()) {
 483  785
                                 int itemCompareResult = mDataIter.next().compareTo(oDataIter.next());
 484  785
                                 if (itemCompareResult != 0) return itemCompareResult;
 485  683
                         }
 486  
 
 487  356
                         if (mDataIter.hasNext()) return 1;
 488  356
                         if (oDataIter.hasNext()) return -1;
 489  
 
 490  356
                         return 0;
 491  
                 }
 492  
 
 493  
                 /* (non-Javadoc)
 494  
                  * @see java.lang.Object#hashCode()
 495  
                  */
 496  
                 @Override
 497  
                 public int hashCode() {
 498  607
                         return data.hashCode();
 499  
                 }
 500  
 
 501  
                 /* (non-Javadoc)
 502  
                  * @see java.lang.Object#equals(java.lang.Object)
 503  
                  */
 504  
                 @Override
 505  
                 public boolean equals(Object obj) {
 506  487
                         if (this == obj)
 507  0
                                 return true;
 508  487
                         if (obj == null)
 509  42
                                 return false;
 510  445
                         if (getClass() != obj.getClass())
 511  0
                                 return false;
 512  445
                         TermResolverKey other = (TermResolverKey) obj;
 513  445
                         return this.compareTo(other) == 0;
 514  
                 }
 515  
 
 516  
                 @Override
 517  
                 public String toString() {
 518  206
                         StringBuilder sb = new StringBuilder();
 519  206
                         sb.append(getClass().getSimpleName());
 520  206
                         sb.append("(");
 521  206
                         Iterator<String> iter = data.iterator();
 522  206
                         sb.append(iter.next().toString());
 523  206
                         if (params.length != 0) {
 524  6
                                 sb.append("+");
 525  6
                                 ArrayUtils.toString(params);
 526  
                         }
 527  
 
 528  206
                         if (iter.hasNext()) sb.append(" <- ");
 529  206
                         boolean first = true;
 530  369
                         while (iter.hasNext()) {
 531  163
                                 if (first) first = false;
 532  15
                                 else sb.append(", ");
 533  163
                                 sb.append(iter.next().toString());
 534  
                         }
 535  
 
 536  
 
 537  206
                         return sb.toString();
 538  
                 }
 539  
         }
 540  
 
 541  
 
 542  61
         private static class Visited {
 543  
                 private TermResolverKey resolverKey;
 544  
                 private List<TermResolverKey> pathTo;
 545  
                 private Set<String> remainingPrereqs;
 546  
                 private Map<String, TermResolverKey> prereqResolvers;
 547  
                 private int cost;
 548  
 
 549  
                 /**
 550  
                  * @param resolverKey
 551  
                  * @param pathTo
 552  
                  * @param prereqs
 553  
                  * @param cost
 554  
                  */
 555  
                 public Visited(TermResolverKey resolverKey, List<TermResolverKey> pathTo, Set<String> prereqs, int cost) {
 556  103
                         super();
 557  103
                         this.resolverKey = resolverKey;
 558  103
                         this.pathTo = pathTo;
 559  103
                         this.remainingPrereqs = new HashSet<String>(prereqs);
 560  103
                         this.prereqResolvers = new HashMap<String, TermResolverKey>();
 561  103
                         this.cost = cost;
 562  103
                 }
 563  
 
 564  
                 /**
 565  
                  * @return the path from the goal node down to this resolver
 566  
                  */
 567  
                 public List<TermResolverKey> getPathTo() {
 568  206
                         return pathTo;
 569  
                 }
 570  
 
 571  
                 public TermResolverKey getResolverKey() {
 572  215
                         return resolverKey;
 573  
                 }
 574  
 
 575  
                 public Collection<TermResolverKey> getPrereqResolvers() {
 576  90
                         return prereqResolvers.values();
 577  
                 }
 578  
 
 579  
                 /**
 580  
                  * @return true if resolution of all the prerequisites has been planned
 581  
                  */
 582  
                 public boolean isFullyPlanned() {
 583  166
                         return remainingPrereqs.isEmpty();
 584  
                 }
 585  
 
 586  
                 public int getCost() {
 587  61
                         return cost;
 588  
                 }
 589  
 
 590  
                 public void addPlannedPrereq(TermResolverKey termResolverKey) {
 591  51
                         remainingPrereqs.remove(termResolverKey.getOutput());
 592  51
                         prereqResolvers.put(termResolverKey.getOutput(), termResolverKey);
 593  51
                 }
 594  
 
 595  
                 public void addPlannedPrereq(String termName) {
 596  14
                         remainingPrereqs.remove(termName);
 597  14
                 }
 598  
         }
 599  
 
 600  72
         private static class InvalidResolutionPathException extends Exception {
 601  
                 private static final long serialVersionUID = 1L;
 602  
 
 603  0
                 public InvalidResolutionPathException() {
 604  0
                 }
 605  
         }
 606  
 
 607  
 }
 608  
 
 609