View Javadoc

1   /*
2    * $Id: ClasspathPackageProvider.java 757842 2009-03-24 15:22:40Z wesw $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.config;
23  
24  import com.opensymphony.xwork2.Action;
25  import com.opensymphony.xwork2.config.Configuration;
26  import com.opensymphony.xwork2.config.ConfigurationException;
27  import com.opensymphony.xwork2.config.PackageProvider;
28  import com.opensymphony.xwork2.config.entities.ActionConfig;
29  import com.opensymphony.xwork2.config.entities.PackageConfig;
30  import com.opensymphony.xwork2.config.entities.ResultConfig;
31  import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
32  import com.opensymphony.xwork2.inject.Inject;
33  import com.opensymphony.xwork2.util.ClassLoaderUtil;
34  import com.opensymphony.xwork2.util.ResolverUtil;
35  import com.opensymphony.xwork2.util.ResolverUtil.ClassTest;
36  import com.opensymphony.xwork2.util.logging.Logger;
37  import com.opensymphony.xwork2.util.logging.LoggerFactory;
38  
39  import javax.servlet.ServletContext;
40  import java.lang.annotation.Annotation;
41  import java.lang.reflect.Modifier;
42  import java.net.MalformedURLException;
43  import java.net.URL;
44  import java.util.*;
45  
46  import org.apache.commons.lang.xwork.StringUtils;
47  
48  /***
49   * ClasspathPackageProvider loads the configuration
50   * by scanning the classpath or selected packages for Action classes.
51   * <p>
52   * This provider is only invoked if one or more action packages are passed to the dispatcher,
53   * usually from the web.xml.
54   * Configurations are created for objects that either implement Action or have classnames that end with "Action".
55   */
56  public class ClasspathPackageProvider implements PackageProvider {
57  
58      /***
59       * The default page prefix (or "path").
60       * Some applications may place pages under "/WEB-INF" as an extreme security precaution.
61       */
62      protected static final String DEFAULT_PAGE_PREFIX = "struts.configuration.classpath.defaultPagePrefix";
63  
64      /***
65       * The default page prefix (none).
66       */
67      private String defaultPagePrefix = "";
68  
69      /***
70       * The default page extension,  to use in place of ".jsp".
71       */
72      protected static final String DEFAULT_PAGE_EXTENSION = "struts.configuration.classpath.defaultPageExtension";
73  
74      /***
75       * The defacto default page extension, usually associated with JavaServer Pages.
76       */
77      private String defaultPageExtension = ".jsp";
78  
79      /***
80       * A setting to indicate a custom default parent package,
81       * to use in place of "struts-default".
82       */
83      protected static final String DEFAULT_PARENT_PACKAGE = "struts.configuration.classpath.defaultParentPackage";
84  
85      /***
86       * A setting to disable action scanning.
87       */
88      protected static final String DISABLE_ACTION_SCANNING = "struts.configuration.classpath.disableActionScanning";
89  
90      /***
91       * Name of the framework's default configuration package,
92       * that application configuration packages automatically inherit.
93       */
94      private String defaultParentPackage = "struts-default";
95  
96      /***
97       * The default page prefix (or "path").
98       * Some applications may place pages under "/WEB-INF" as an extreme security precaution.
99       */
100     protected static final String FORCE_LOWER_CASE = "struts.configuration.classpath.forceLowerCase";
101 
102     /***
103      * Whether to use a lowercase letter as the initial letter of an action.
104      * If false, actions will retain the initial uppercase letter from the Action class.
105      * (<code>view.action</code> (true) versus <code>View.action</code> (false)).
106      */
107     private boolean forceLowerCase = true;
108 
109     protected static final String CLASS_SUFFIX = "struts.codebehind.classSuffix";
110     /***
111      * Default suffix that can be used to indicate POJO "Action" classes.
112      */
113     protected String classSuffix = "Action";
114 
115     protected static final String CHECK_IMPLEMENTS_ACTION = "struts.codebehind.checkImplementsAction";
116 
117     /***
118      * When testing a class, check that it implements Action
119      */
120     protected boolean checkImplementsAction = true;
121 
122     protected static final String CHECK_ANNOTATION = "struts.codebehind.checkAnnotation";
123 
124     /***
125      * When testing a class, check that it has an @Action annotation
126      */
127     protected boolean checkAnnotation = true;
128 
129     /***
130      * Helper class to scan class path for server pages.
131      */
132     private PageLocator pageLocator = new ClasspathPageLocator();
133 
134     /***
135      * Flag to indicate the packages have been loaded.
136      *
137      * @see #loadPackages
138      * @see #needsReload
139      */
140     private boolean initialized = false;
141 
142     private boolean disableActionScanning = false;
143 
144     private PackageLoader packageLoader;
145 
146     /***
147      * Logging instance for this class.
148      */
149     private static final Logger LOG = LoggerFactory.getLogger(ClasspathPackageProvider.class);
150 
151     /***
152      * The XWork Configuration for this application.
153      *
154      * @see #init
155      */
156     private Configuration configuration;
157 
158     private String actionPackages;
159 
160     private ServletContext servletContext;
161 
162     public ClasspathPackageProvider() {
163     }
164 
165     /***
166      * PageLocator defines a locate method that can be used to discover server pages.
167      */
168     public static interface PageLocator {
169         public URL locate(String path);
170     }
171 
172     /***
173      * ClasspathPathLocator searches the classpath for server pages.
174      */
175     public static class ClasspathPageLocator implements PageLocator {
176         public URL locate(String path) {
177             return ClassLoaderUtil.getResource(path, getClass());
178         }
179     }
180 
181     @Inject("actionPackages")
182     public void setActionPackages(String packages) {
183         this.actionPackages = packages;
184     }
185 
186     public void setServletContext(ServletContext ctx) {
187         this.servletContext = ctx;
188     }
189 
190     /***
191      * Disables action scanning.
192      *
193      * @param disableActionScanning True to disable
194      */
195     @Inject(value=DISABLE_ACTION_SCANNING, required=false)
196     public void setDisableActionScanning(String disableActionScanning) {
197         this.disableActionScanning = "true".equals(disableActionScanning);
198     }
199 
200     /***
201      * Check that the class implements Action
202      *
203      * @param checkImplementsAction True to check
204      */
205     @Inject(value=CHECK_IMPLEMENTS_ACTION, required=false)
206     public void setCheckImplementsAction(String checkImplementsAction) {
207         this.checkImplementsAction = "true".equals(checkImplementsAction);
208     }
209 
210     /***
211      * Check that the class has an @Action annotation
212      *
213      * @param checkImplementsAction True to check
214      */
215     @Inject(value=CHECK_ANNOTATION, required=false)
216     public void setCheckAnnotation(String checkAnnotation) {
217         this.checkAnnotation = "true".equals(checkAnnotation);
218     }
219 
220     /***
221      * Register a default parent package for the actions.
222      *
223      * @param defaultParentPackage the new defaultParentPackage
224      */
225     @Inject(value=DEFAULT_PARENT_PACKAGE, required=false)
226     public void setDefaultParentPackage(String defaultParentPackage) {
227         this.defaultParentPackage = defaultParentPackage;
228     }
229 
230     /***
231      * Register a default page extension to use when locating pages.
232      *
233      * @param defaultPageExtension the new defaultPageExtension
234      */
235     @Inject(value=DEFAULT_PAGE_EXTENSION, required=false)
236     public void setDefaultPageExtension(String defaultPageExtension) {
237         this.defaultPageExtension = defaultPageExtension;
238     }
239 
240     /***
241      * Reigster a default page prefix to use when locating pages.
242      *
243      * @param defaultPagePrefix the defaultPagePrefix to set
244      */
245     @Inject(value=DEFAULT_PAGE_PREFIX, required=false)
246     public void setDefaultPagePrefix(String defaultPagePrefix) {
247         this.defaultPagePrefix = defaultPagePrefix;
248     }
249 
250     /***
251      * Default suffix that can be used to indicate POJO "Action" classes.
252      *
253      * @param classSuffix the classSuffix to set
254      */
255     @Inject(value=CLASS_SUFFIX, required=false)
256     public void setClassSuffix(String classSuffix) {
257         this.classSuffix = classSuffix;
258     }
259 
260     /***
261      * Whether to use a lowercase letter as the initial letter of an action.
262      *
263      * @param force If false, actions will retain the initial uppercase letter from the Action class.
264      * (<code>view.action</code> (true) versus <code>View.action</code> (false)).
265      */
266     @Inject(value=FORCE_LOWER_CASE, required=false)
267     public void setForceLowerCase(String force) {
268         this.forceLowerCase = "true".equals(force);
269     }
270 
271     /***
272      * Register a PageLocation to use to scan for server pages.
273      *
274      * @param locator
275      */
276     public void setPageLocator(PageLocator locator) {
277         this.pageLocator = locator;
278     }
279 
280     /***
281      * Scan a list of packages for Action classes.
282      *
283      * This method loads classes that implement the Action interface
284      * or have a class name that ends with the letters "Action".
285      *
286      * @param pkgs A list of packages to load
287      * @see #processActionClass
288      */
289     protected void loadPackages(String[] pkgs) {
290 
291         packageLoader = new PackageLoader();
292         ResolverUtil<Class> resolver = new ResolverUtil<Class>();
293         resolver.find(createActionClassTest(), pkgs);
294 
295         Set<? extends Class<? extends Class>> actionClasses = resolver.getClasses();
296         for (Object obj : actionClasses) {
297            Class cls = (Class) obj;
298            if (!Modifier.isAbstract(cls.getModifiers())) {
299                processActionClass(cls, pkgs);
300            }
301         }
302 
303         for (PackageConfig config : packageLoader.createPackageConfigs()) {
304             configuration.addPackageConfig(config.getName(), config);
305         }
306     }
307 
308     protected ClassTest createActionClassTest() {
309         return new ClassTest() {
310             // Match Action implementations and classes ending with "Action"
311             public boolean matches(Class type) {
312                 // TODO: should also find annotated classes
313                 return ((checkImplementsAction && Action.class.isAssignableFrom(type)) ||
314                         type.getSimpleName().endsWith(getClassSuffix()) ||
315                         (checkAnnotation && type.getAnnotation(org.apache.struts2.config.Action.class) != null));
316             }
317 
318         };
319     }
320 
321     protected String getClassSuffix() {
322         return classSuffix;
323     }
324 
325     /***
326      * Create a default action mapping for a class instance.
327      *
328      * The namespace annotation is honored, if found, otherwise
329      * the Java package is converted into the namespace
330      * by changing the dots (".") to slashes ("/").
331      *
332      * @param cls Action or POJO instance to process
333      * @param pkgs List of packages that were scanned for Actions
334      */
335     protected void processActionClass(Class<?> cls, String[] pkgs) {
336         String name = cls.getName();
337         String actionPackage = cls.getPackage().getName();
338         String actionNamespace = null;
339         String actionName = null;
340 
341         org.apache.struts2.config.Action actionAnn =
342             (org.apache.struts2.config.Action) cls.getAnnotation(org.apache.struts2.config.Action.class);
343         if (actionAnn != null) {
344             actionName = actionAnn.name();
345             if (actionAnn.namespace().equals(org.apache.struts2.config.Action.DEFAULT_NAMESPACE)) {
346                 actionNamespace = "";
347             } else {
348                 actionNamespace = actionAnn.namespace();
349             }
350         } else {
351             for (String pkg : pkgs) {
352                 if (name.startsWith(pkg)) {
353                     if (LOG.isDebugEnabled()) {
354                         LOG.debug("ClasspathPackageProvider: Processing class "+name);
355                     }
356                     name = name.substring(pkg.length() + 1);
357 
358                     actionNamespace = "";
359                     actionName = name;
360                     int pos = name.lastIndexOf('.');
361                     if (pos > -1) {
362                         actionNamespace = "/" + name.substring(0, pos).replace('.','/');
363                         actionName = name.substring(pos+1);
364                     }
365                     break;
366                 }
367             }
368             // Truncate Action suffix if found
369             if (actionName.endsWith(getClassSuffix())) {
370                 actionName = actionName.substring(0, actionName.length() - getClassSuffix().length());
371             }
372 
373             // Force initial letter of action to lowercase, if desired
374             if ((forceLowerCase) && (actionName.length() > 1)) {
375                 int lowerPos = actionName.lastIndexOf('/') + 1;
376                 StringBuilder sb = new StringBuilder();
377                 sb.append(actionName.substring(0, lowerPos));
378                 sb.append(Character.toLowerCase(actionName.charAt(lowerPos)));
379                 sb.append(actionName.substring(lowerPos + 1));
380                 actionName = sb.toString();
381             }
382         }
383 
384         PackageConfig.Builder pkgConfig = loadPackageConfig(actionNamespace, actionPackage, cls);
385 
386         // In case the package changed due to namespace annotation processing
387         if (!actionPackage.equals(pkgConfig.getName())) {
388             actionPackage = pkgConfig.getName();
389         }
390 
391         List<PackageConfig> parents = findAllParentPackages(cls);
392         if (parents.size() > 0) {
393             pkgConfig.addParents(parents);
394 
395             // Try to guess the namespace from the first package
396             PackageConfig firstParent = parents.get(0);
397             if (StringUtils.isEmpty(pkgConfig.getNamespace()) && StringUtils.isNotEmpty(firstParent.getNamespace())) {
398                 pkgConfig.namespace(firstParent.getNamespace());
399             }
400         }
401 
402 
403         ResultTypeConfig defaultResultType = packageLoader.getDefaultResultType(pkgConfig);
404         ActionConfig actionConfig = new ActionConfig.Builder(actionPackage, actionName, cls.getName())
405                 .addResultConfigs(new ResultMap<String,ResultConfig>(cls, actionName, defaultResultType))
406                 .build();
407         pkgConfig.addActionConfig(actionName, actionConfig);
408     }
409 
410     /***
411      * Finds all parent packages by first looking at the ParentPackage annotation on the package, then the class
412      * @param cls The action class
413      * @return A list of unique packages to add
414      */
415     private List<PackageConfig> findAllParentPackages(Class<?> cls) {
416 
417         List<PackageConfig> parents = new ArrayList<PackageConfig>();
418         // Favor parent package annotations from the package
419         Set<String> parentNames = new LinkedHashSet<String>();
420         ParentPackage annotation = cls.getPackage().getAnnotation(ParentPackage.class);
421         if (annotation != null) {
422             parentNames.addAll(Arrays.asList(annotation.value()));
423         }
424         annotation = cls.getAnnotation(ParentPackage.class);
425         if (annotation != null) {
426             parentNames.addAll(Arrays.asList(annotation.value()));
427         }
428         if (parentNames.size() > 0) {
429             for (String parent : parentNames) {
430                 PackageConfig parentPkg = configuration.getPackageConfig(parent);
431                 if (parentPkg == null) {
432                     throw new ConfigurationException("ClasspathPackageProvider: Unable to locate parent package "+parent, annotation);
433                 }
434                 parents.add(parentPkg);
435             }
436         }
437         return parents;
438     }
439 
440     /***
441      * Finds or creates the package configuration for an Action class.
442      *
443      * The namespace annotation is honored, if found,
444      * and the namespace is checked for a parent configuration.
445      *
446      * @param actionNamespace The configuration namespace
447      * @param actionPackage The Java package containing our Action classes
448      * @param actionClass The Action class instance
449      * @return PackageConfig object for the Action class
450      */
451     protected PackageConfig.Builder loadPackageConfig(String actionNamespace, String actionPackage, Class actionClass) {
452         PackageConfig.Builder parent = null;
453 
454         // Check for the @Namespace annotation
455         if (actionClass != null) {
456             Namespace ns = (Namespace) actionClass.getAnnotation(Namespace.class);
457             if (ns != null) {
458                 parent = loadPackageConfig(actionNamespace, actionPackage, null);
459                 actionNamespace = ns.value();
460                 actionPackage = actionClass.getName();
461 
462             // See if the namespace has been overridden by the @Action annotation
463             } else {
464                 org.apache.struts2.config.Action actionAnn =
465                     (org.apache.struts2.config.Action) actionClass.getAnnotation(org.apache.struts2.config.Action.class);
466                 if (actionAnn != null && !actionAnn.DEFAULT_NAMESPACE.equals(actionAnn.namespace())) {
467                     // we pass null as the namespace in case the parent package hasn't been loaded yet
468                     parent = loadPackageConfig(null, actionPackage, null);
469                     actionPackage = actionClass.getName();
470                 }
471             }
472         }
473 
474 
475         PackageConfig.Builder pkgConfig = packageLoader.getPackage(actionPackage);
476         if (pkgConfig == null) {
477             pkgConfig = new PackageConfig.Builder(actionPackage);
478 
479             pkgConfig.namespace(actionNamespace);
480             if (parent == null) {
481                 PackageConfig cfg = configuration.getPackageConfig(defaultParentPackage);
482                 if (cfg != null) {
483                     pkgConfig.addParent(cfg);
484                 } else {
485                     throw new ConfigurationException("ClasspathPackageProvider: Unable to locate default parent package: " +
486                         defaultParentPackage);
487                 }
488             }
489 
490             packageLoader.registerPackage(pkgConfig);
491 
492         // if the parent package was first created by a child, ensure the namespace is correct
493         } else if (pkgConfig.getNamespace() == null) {
494             pkgConfig.namespace(actionNamespace);
495         }
496 
497         if (parent != null) {
498             packageLoader.registerChildToParent(pkgConfig, parent);
499         }
500 
501         if (LOG.isDebugEnabled()) {
502             LOG.debug("class:"+actionClass+" parent:"+parent+" current:"+(pkgConfig != null ? pkgConfig.getName() : ""));
503         }
504 
505         return pkgConfig;
506     }
507 
508     /***
509      * Default destructor. Override to provide behavior.
510      */
511     public void destroy() {
512 
513     }
514 
515     /***
516      * Register this application's configuration.
517      *
518      * @param config The configuration for this application.
519      */
520     public void init(Configuration config) {
521         this.configuration = config;
522     }
523 
524     /***
525      * Clears and loads the list of packages registered at construction.
526      *
527      * @throws ConfigurationException
528      */
529     public void loadPackages() throws ConfigurationException {
530         if (actionPackages != null && !disableActionScanning) {
531             String[] names = actionPackages.split("//s*[,]//s*");
532             // Initialize the classloader scanner with the configured packages
533             if (names.length > 0) {
534                 setPageLocator(new ServletContextPageLocator(servletContext));
535             }
536             loadPackages(names);
537         }
538         initialized = true;
539     }
540 
541     /***
542      * Indicates whether the packages have been initialized.
543      *
544      * @return True if the packages have been initialized
545      */
546     public boolean needsReload() {
547         return !initialized;
548     }
549 
550     /***
551      * Creates ResultConfig objects from result annotations,
552      * and if a result isn't found, creates it on the fly.
553      */
554     class ResultMap<K,V> extends HashMap<K,V> {
555         private Class actionClass;
556         private String actionName;
557         private ResultTypeConfig defaultResultType;
558 
559         public ResultMap(Class actionClass, String actionName, ResultTypeConfig defaultResultType) {
560             this.actionClass = actionClass;
561             this.actionName = actionName;
562             this.defaultResultType = defaultResultType;
563 
564             // check if any annotations are around
565             while (!actionClass.getName().equals(Object.class.getName())) {
566                 //noinspection unchecked
567                 Results results = (Results) actionClass.getAnnotation(Results.class);
568                 if (results != null) {
569                     // first check here...
570                     for (int i = 0; i < results.value().length; i++) {
571                         Result result = results.value()[i];
572                         ResultConfig config = createResultConfig(result);
573 						if (!containsKey((K)config.getName())) {
574                             put((K)config.getName(), (V)config);
575                         }
576                     }
577                 }
578 
579                 // what about a single Result annotation?
580                 Result result = (Result) actionClass.getAnnotation(Result.class);
581                 if (result != null) {
582                     ResultConfig config = createResultConfig(result);
583                     if (!containsKey((K)config.getName())) {
584                         put((K)config.getName(), (V)config);
585                     }
586                 }
587 
588                 actionClass = actionClass.getSuperclass();
589             }
590         }
591 
592         /***
593          * Extracts result name and value and calls {@link #createResultConfig}.
594          *
595          * @param result Result annotation reference representing result type to create
596          * @return New or cached ResultConfig object for result
597          */
598         protected ResultConfig createResultConfig(Result result) {
599             Class<? extends Object> cls = result.type();
600             if (cls == NullResult.class) {
601                 cls = null;
602             }
603             return createResultConfig(result.name(), cls, result.value(), createParameterMap(result.params()));
604         }
605 
606         protected Map<String, String> createParameterMap(String[] parms) {
607             Map<String, String> map = new HashMap<String, String>();
608             int subtract = parms.length % 2;
609             if(subtract != 0) {
610                 LOG.warn("Odd number of result parameters key/values specified.  The final one will be ignored.");
611             }
612             for (int i = 0; i < parms.length - subtract; i++) {
613                 String key = parms[i++];
614                 String value = parms[i];
615                 map.put(key, value);
616                 if(LOG.isDebugEnabled()) {
617                     LOG.debug("Adding parmeter["+key+":"+value+"] to result.");
618                 }
619             }
620             return map;
621         }
622 
623         /***
624          * Creates a default ResultConfig,
625          * using either the resultClass or the default ResultType for configuration package
626          * associated this ResultMap class.
627          *
628          * @param key The result type name
629          * @param resultClass The class for the result type
630          * @param location Path to the resource represented by this type
631          * @return A ResultConfig for key mapped to location
632          */
633         private ResultConfig createResultConfig(Object key, Class<? extends Object> resultClass,
634                                                 String location,
635                                                 Map<? extends Object,? extends Object > configParams) {
636             if (resultClass == null) {
637                 configParams = defaultResultType.getParams();
638                 String className = defaultResultType.getClassName();
639                 try {
640                     resultClass = ClassLoaderUtil.loadClass(className, getClass());
641                 } catch (ClassNotFoundException ex) {
642                     throw new ConfigurationException("ClasspathPackageProvider: Unable to locate result class "+className, actionClass);
643                 }
644             }
645 
646             String defaultParam;
647             try {
648                 defaultParam = (String) resultClass.getField("DEFAULT_PARAM").get(null);
649             } catch (Exception e) {
650                 // not sure why this happened, but let's just use a sensible choice
651                 defaultParam = "location";
652             }
653 
654             HashMap params = new HashMap();
655             if (configParams != null) {
656                 params.putAll(configParams);
657             }
658 
659             params.put(defaultParam, location);
660             return new ResultConfig.Builder((String) key, resultClass.getName()).addParams(params).build();
661         }
662     }
663 
664     /***
665      * Search classpath for a page.
666      */
667     private final class ServletContextPageLocator implements PageLocator {
668         private final ServletContext context;
669         private ClasspathPageLocator classpathPageLocator = new ClasspathPageLocator();
670 
671         private ServletContextPageLocator(ServletContext context) {
672             this.context = context;
673         }
674 
675         public URL locate(String path) {
676             URL url = null;
677             try {
678                 url = context.getResource(path);
679                 if (url == null) {
680                     url = classpathPageLocator.locate(path);
681                 }
682             } catch (MalformedURLException e) {
683                 if (LOG.isDebugEnabled()) {
684                     LOG.debug("Unable to resolve path "+path+" against the servlet context");
685                 }
686             }
687             return url;
688         }
689     }
690 
691     private static class PackageLoader {
692 
693         /***
694          * The package configurations for scanned Actions.
695          */
696         private Map<String,PackageConfig.Builder> packageConfigBuilders = new HashMap<String,PackageConfig.Builder>();
697 
698         private Map<PackageConfig.Builder,PackageConfig.Builder> childToParent = new HashMap<PackageConfig.Builder,PackageConfig.Builder>();
699 
700         public PackageConfig.Builder getPackage(String name) {
701             return packageConfigBuilders.get(name);
702         }
703 
704         public void registerChildToParent(PackageConfig.Builder child, PackageConfig.Builder parent) {
705             childToParent.put(child, parent);
706         }
707 
708         public void registerPackage(PackageConfig.Builder builder) {
709             packageConfigBuilders.put(builder.getName(), builder);
710         }
711 
712         public Collection<PackageConfig> createPackageConfigs() {
713             Map<String, PackageConfig> configs = new HashMap<String, PackageConfig>();
714 
715             Set<PackageConfig.Builder> builders;
716             while ((builders = findPackagesWithNoParents()).size() > 0) {
717                 for (PackageConfig.Builder parent : builders) {
718                     PackageConfig config = parent.build();
719                     configs.put(config.getName(), config);
720                     packageConfigBuilders.remove(config.getName());
721 
722                     for (Iterator<Map.Entry<PackageConfig.Builder,PackageConfig.Builder>> i = childToParent.entrySet().iterator(); i.hasNext(); ) {
723                         Map.Entry<PackageConfig.Builder,PackageConfig.Builder> entry = i.next();
724                         if (entry.getValue() == parent) {
725                             entry.getKey().addParent(config);
726                             i.remove();
727                         }
728                     }
729                 }
730             }
731             return configs.values();
732         }
733 
734         Set<PackageConfig.Builder> findPackagesWithNoParents() {
735             Set<PackageConfig.Builder> builders = new HashSet<PackageConfig.Builder>();
736             for (PackageConfig.Builder child : packageConfigBuilders.values()) {
737                 if (!childToParent.containsKey(child)) {
738                     builders.add(child);
739                 }
740             }
741             return builders;
742         }
743 
744         public ResultTypeConfig getDefaultResultType(PackageConfig.Builder pkgConfig) {
745             PackageConfig.Builder parent;
746             PackageConfig.Builder current = pkgConfig;
747 
748             while ((parent = childToParent.get(current)) != null) {
749                 current = parent;
750             }
751             return current.getResultType(current.getFullDefaultResultType());
752         }
753     }
754 }