001    /*
002     * Copyright 2005 Stephen J. McConnell
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.library.impl;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.net.URI;
024    import java.net.URISyntaxException;
025    import java.text.SimpleDateFormat;
026    import java.util.Arrays;
027    import java.util.ArrayList;
028    import java.util.List;
029    import java.util.Date;
030    import java.util.TimeZone;
031    import java.util.Properties;
032    import java.util.Map;
033    import java.util.Hashtable;
034    
035    import net.dpml.lang.Category;
036    import net.dpml.lang.Version;
037    
038    import net.dpml.library.Info;
039    import net.dpml.library.Filter;
040    import net.dpml.library.Library;
041    import net.dpml.library.Module;
042    import net.dpml.library.Resource;
043    import net.dpml.library.Type;
044    import net.dpml.library.Data;
045    import net.dpml.library.ResourceNotFoundException;
046    import net.dpml.library.info.InfoDirective;
047    import net.dpml.library.info.TypeDirective;
048    import net.dpml.library.info.ResourceDirective;
049    import net.dpml.library.info.ResourceDirective.Classifier;
050    import net.dpml.library.info.IncludeDirective;
051    import net.dpml.library.info.DependencyDirective;
052    import net.dpml.library.info.AbstractDirective;
053    import net.dpml.library.info.ValidationException;
054    import net.dpml.library.info.FilterDirective;
055    import net.dpml.library.info.Scope;
056    
057    import net.dpml.transit.Artifact;
058    import net.dpml.transit.Transit;
059    
060    import net.dpml.util.Resolver;
061    
062    
063    /**
064     * Implementation of a resource.
065     *
066     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
067     * @version 1.1.2
068     */
069    public class DefaultResource extends DefaultDictionary implements Resource, Resolver, Comparable
070    {
071        private static final String LEGACY_DECIMAL_PREFIX_KEY = 
072          "project.version-prefix.enabled";
073        
074       /**
075        * Timestamp.
076        */
077        public static final String TIMESTAMP = getTimestamp();
078        
079       /**
080        * Constant SNAPSHOT symbol.
081        */
082        public static final String SNAPSHOT = "SNAPSHOT";
083        
084       /**
085        * Constant BOOTSTRAP symbol.
086        */
087        public static final String BOOTSTRAP = "BOOTSTRAP";
088        
089       /**
090        * Constant RELEASE symbol.
091        */
092        public static final String RELEASE = "RELEASE";
093        
094       /**
095        * Constant ANONYMOUS version symbol.
096        */
097        public static final String ANONYMOUS = "ANONYMOUS";
098        
099        private final DefaultLibrary m_library;
100        private final ResourceDirective m_directive;
101        private final DefaultModule m_parent;
102        private final Type[] m_types;
103        private final String[] m_typeNames;
104        private final String m_path;
105        private final File m_basedir;
106        private final Map m_filters = new Hashtable();
107        private final Data[] m_data;
108        
109       /**
110        * Creation of a new default resource.
111        * @param logger the assigned logging channel
112        * @param library the reference library
113        * @param directive the directive
114        */
115        DefaultResource( final DefaultLibrary library, final AbstractDirective directive )
116        {
117            super( null, directive );
118            
119            m_library = library;
120            m_directive = null;
121            m_parent = null;
122            m_types = new Type[0];
123            m_typeNames = new String[0];
124            m_path = "";
125            m_basedir = null;
126            m_data = new Data[0];
127        }
128        
129       /**
130        * Creation of a new default resource.
131        * @param logger the assigned logging channel
132        * @param library the reference library
133        * @param module the parent module
134        * @param directive the resource directive
135        */
136        DefaultResource( 
137          final DefaultLibrary library, final DefaultModule module, final ResourceDirective directive ) 
138        {
139            super( module, directive );
140            if( null == directive )
141            {
142                throw new NullPointerException( "directive" );
143            }
144            
145            m_library = library;
146            m_directive = directive;
147            m_parent = module;
148            
149            if( module.isRoot() )
150            {
151                m_path = directive.getName();
152            }
153            else
154            {
155                m_path = module.getResourcePath() + "/" + directive.getName();
156            }
157            
158            // setup produced types
159            
160            m_types = directive.getTypeDirectives();
161            m_typeNames = new String[ m_types.length ];
162            for( int i=0; i<m_types.length; i++ )
163            {
164                Type type = m_types[i];
165                m_typeNames[i] = type.getID();
166            }
167            
168            // setup production data
169            
170            m_data = new Data[0];
171            
172            // setup the resource basedir
173            
174            File anchor = getAnchor();
175            String filename = m_directive.getBasedir();
176            if( null != filename )
177            {
178                String spec = resolve( filename );
179                File file = new File( spec );
180                if( file.isAbsolute() )
181                {
182                    m_basedir = getCanonicalFile( file );
183                }
184                else
185                {
186                    File basedir = new File( anchor, spec );
187                    m_basedir = getCanonicalFile( basedir );
188                    setProperty( "basedir", m_basedir.toString() );
189                }
190            }
191            else
192            {
193                if( !m_directive.getClassifier().equals( Classifier.LOCAL ) )
194                {
195                    m_basedir = null;
196                }
197                else
198                {
199                    final String error = 
200                      "Missing base directory declaration in resource ["
201                      + m_path
202                      + "].";
203                    throw new ValidationException( error );
204                }
205            }
206            
207            // setup the default properties
208            
209            setProperty( "project.name", getName() );
210            if( null != m_parent )
211            {
212                setProperty( "project.group", m_parent.getResourcePath() );
213            }
214            else
215            {
216                setProperty( "project.group", "" );
217            }
218            String version = getVersion();
219            if( null != version )
220            {
221                setProperty( "project.version", version );
222            }
223            
224            // setup filters
225            
226            FilterDirective[] filters = directive.getFilterDirectives();
227            for( int i=0; i<filters.length; i++ )
228            {
229                FilterDirective filter = filters[i];
230                String token = filter.getToken();
231                m_filters.put( token, filter );
232            }
233        }
234        
235        //----------------------------------------------------------------------------
236        // Resource
237        //----------------------------------------------------------------------------
238        
239       /**
240        * Return a data directives.
241        * @return the associated production data
242        */
243        public Data[] getData()
244        {
245            return m_data;
246        }
247    
248       /**
249        * Return the singleton library.
250        * @return the library
251        */
252        public Library getLibrary()
253        {
254            return m_library;
255        }
256    
257       /**
258        * Return the name of the resource.
259        * @return the resource name
260        */
261        public String getName()
262        {
263            if( null != m_directive )
264            {
265                return m_directive.getName();
266            }
267            else
268            {
269                return null;
270            }
271        }
272        
273       /**
274        * Return the resource version.
275        * @return the version
276        */
277        public String getVersion()
278        {
279            String version = getStatutoryVersion();
280            if( null != version )
281            {
282                return version;
283            }
284            else
285            {
286                return getStandardVersion();
287            }
288        }
289        
290       /**
291        * Return the declard resource version.
292        * @return the statutory version
293        */
294        public String getStatutoryVersion()
295        {
296            if( null == m_directive )
297            {
298                return null;
299            }
300            else
301            {
302                String version = m_directive.getVersion();
303                if( null != version )
304                {
305                    return version;
306                }
307                else
308                {
309                    if( null != m_parent )
310                    {
311                        return m_parent.getStatutoryVersion();
312                    }
313                    else if( !m_directive.getClassifier().equals( Classifier.LOCAL ) )
314                    {
315                        return ANONYMOUS;
316                    }
317                    else
318                    {
319                        return null;
320                    }
321                }
322            }
323        }
324    
325       /**
326        * Return the decimal version.
327        *
328        * @return the version
329        */
330        public Version getDecimalVersion()
331        {
332            int major = getMajorVersion();
333            int minor = getMinorVersion();
334            int micro = getMicroVersion();
335            return new Version( major, minor, micro );
336        }
337        
338       /**
339        * Return the fully qualified path to the resource.
340        * @return the path
341        */
342        public String getResourcePath()
343        {
344            return m_path;
345        }
346        
347       /**
348        * Return the basedir for this resource.
349        * @return the base directory (possibly null)
350        */
351        public File getBaseDir()
352        {
353            return m_basedir;
354        }
355    
356       /**
357        * Return the resource classifier.
358        * @return the classifier (LOCAL, EXTERNAL or ANONYMOUS)
359        */
360        public Classifier getClassifier()
361        {
362            if( null != m_directive )
363            {
364                return m_directive.getClassifier();
365            }
366            else
367            {
368                return ResourceDirective.ANONYMOUS;
369            }
370        }
371        
372       /**
373        * Return the info block.
374        * @return the info block
375        */
376        public Info getInfo()
377        {
378            return m_directive.getInfoDirective();
379        }
380        
381       /**
382        * Return the expanded array of types associated with the resource.
383        * The returned array is a function of the types declared by a resource
384        * expanded relative to any types implied by processor dependencies.
385        * @return the type array
386        */
387        public Type[] getTypes()
388        {
389            return m_types;
390        }
391        
392       /**
393        * Test if this resource is associated with a type of the supplied name.
394        * @param type the type id
395        * @return TRUE if this resource produces an artifact of the supplied type
396        */
397        public boolean isa( final String type )
398        {
399            for( int i=0; i<m_types.length; i++ )
400            {
401                Type someType = m_types[i];
402                String name = someType.getID();
403                if( name.equals( type ) )
404                {
405                    return true;
406                }
407            }
408            return false;
409        }
410        
411       /**
412        * Return a resource type relative to a supplied type id.
413        * @param id the type name to retrieve
414        * @return the type instance
415        * @exception IllegalArgumentException if the id value does not match
416        * a type produced by the resource.
417        */
418        public Type getType( final String id ) throws IllegalArgumentException
419        {
420            for( int i=0; i<m_types.length; i++ )
421            {
422                Type type = m_types[i];
423                if( type.getID().equals( id ) )
424                {
425                    return type;
426                }
427            }
428            final String error = 
429              "Type name ["
430              + id
431              + "] not recognized with the scope of resource ["
432              + getResourcePath()
433              + "].";
434            throw new IllegalArgumentException( error );
435        }
436        
437       /**
438        * Construct an link artifact for the supplied type.
439        * @param id the resource type id
440        * @return the link artifact
441        */
442        public Artifact getLinkArtifact( final String id )
443        {
444            if( null == m_directive )
445            {
446                final String error = 
447                  "Method not supported on virtual root.";
448                throw new UnsupportedOperationException( error );
449            }
450            if( null == id )
451            {
452                throw new NullPointerException( "id" );
453            }
454            String group = getGroupName();
455            String name = getName();
456            Type type = getType( id );
457            Version version = type.getVersion();
458            if( null == version )
459            {
460                final String error = 
461                  "Resource does not declare production of an alias for the requested type."
462                  + "\nResource: " + this
463                  + "\nType: " + id;
464                throw new IllegalArgumentException( error );
465            }
466            try
467            {
468                String spec = "link:" + id;
469                if( null != group )
470                {
471                    spec = spec + ":" + group + "/" + name;
472                }
473                else
474                {
475                    spec = spec + ":" + name;
476                }
477                if( !Version.NULL_VERSION.equals( version ) )
478                {
479                    int major = version.getMajor();
480                    int minor = version.getMinor();
481                    spec = spec + "#"
482                      + major
483                      + "."
484                      + minor;
485                }
486                return Artifact.createArtifact( spec );
487            }
488            catch( Throwable e )
489            {
490                final String error = 
491                  "Failed to construct link artifact for resource ["
492                  + getResourcePath()
493                  + "].";
494                throw new RuntimeException( error, e );
495            }
496        }
497    
498       /**
499        * Construct an artifact for the supplied type.
500        * @param id the resource type identifier
501        * @return the artifact
502        */
503        public Artifact getArtifact( final String id )
504        {
505            if( null == m_directive )
506            {
507                final String error = 
508                  "Method not supported on virtual root.";
509                throw new UnsupportedOperationException( error );
510            }
511            if( null == id )
512            {
513                throw new NullPointerException( "id" );
514            }
515            
516            String group = getGroupName();
517            String name = getName();
518            String version = getVersion();
519            String scheme = m_directive.getScheme();
520            
521            try
522            {
523                return Artifact.createArtifact( scheme, group, name, version, id );
524            }
525            catch( Throwable e )
526            {
527                final String error = 
528                  "Failed to construct artifact for resource ["
529                  + getResourcePath()
530                  + "].";
531                throw new RuntimeException( error, e );
532            }
533        }
534        
535       /**
536        * Return the enclosing parent module.
537        * @return the enclosing module of null if this a top-level module.
538        */
539        public Module getParent()
540        {
541            return getDefaultParent();
542        }
543        
544       /**
545        * Return an array of filters associated with the resource.
546        * @return the array of filters
547        */
548        public Filter[] getFilters()
549        {
550            DefaultModule module = getDefaultParent();
551            if( null != module )
552            {
553                Map map = new Hashtable();
554                Filter[] filters = module.getFilters();
555                for( int i=0; i<filters.length; i++ )
556                {
557                    Filter filter = filters[i];
558                    String token = filter.getToken();
559                    map.put( token, filter );
560                }
561                Filter[] local = getLocalFilters();
562                for( int i=0; i<local.length; i++ )
563                {
564                    Filter filter = local[i];
565                    String token = filter.getToken();
566                    map.put( token, filter );
567                }
568                return (Filter[]) map.values().toArray( new Filter[0] );
569            }
570            else
571            {
572                return getLocalFilters();
573            }
574        }
575        
576        //----------------------------------------------------------------------------
577        // Resolver
578        //----------------------------------------------------------------------------
579    
580       /**
581        * Utility function supporting resolution of uris containing 'resource' or 
582        * 'alias' schemes.  If the supplied uri schem is 'resource' or 'alias' the 
583        * reference is resolved to a artifact type, group and name from which a 
584        * resource is resolved and the uri returned.  If the scheme is resource
585        * the usri of the resource is returned. If the scheme is 'alias' a 
586        * linkn alias is returned.  If the scheme is not 'resource' or 'alias' 
587        * the argument will be evaluated as a normal transit artifact uri 
588        * specification.
589        * 
590        * @param ref the uri argument
591        * @return the uri value
592        * @exception URISyntaxException if an error occurs during uri creation
593        */
594        public URI toURI( final String ref ) throws URISyntaxException
595        {
596            Artifact spec = Artifact.createArtifact( ref );
597            if( spec.isRecognized() )
598            {
599                return spec.toURI();
600            }
601            else if( ref.startsWith( "resource:" ) || ref.startsWith( "alias:" ) )
602            {
603                String type = spec.getType();
604                String group = spec.getGroup();
605                String name = spec.getName();
606                String path = group + "/" + name;
607                Library library = getLibrary();
608                try
609                {
610                    Resource resource = library.getResource( path );
611                    if( ref.startsWith( "resource:" ) )
612                    {
613                        Artifact artifact = resource.getArtifact( type );
614                        return artifact.toURI();
615                    }
616                    else
617                    {
618                        Artifact artifact = resource.getLinkArtifact( type );
619                        return artifact.toURI();
620                    }
621                }
622                catch( ResourceNotFoundException e )
623                {
624                    final String error = 
625                      "Unresolvable resource reference: " + path;
626                    IllegalArgumentException iae = new IllegalArgumentException( error );
627                    iae.initCause( e );
628                    throw iae;
629                }
630            }
631            else
632            {
633                return spec.toURI();
634            }
635        }
636        
637        //----------------------------------------------------------------------------
638        // implementation
639        //----------------------------------------------------------------------------
640    
641        Map getFilterMap()
642        {
643            return m_filters;
644        }
645        
646        Filter[] getLocalFilters()
647        {
648            return (Filter[]) getFilterMap().values().toArray( new Filter[0] );
649        }
650        
651       /**
652        * Return an array of resource that are providers to this resource.
653        * @param scope the operational scope
654        * @param expand if true include transitive dependencies
655        * @param sort if true the array will sorted relative to dependencies
656        * @return the resource providers
657        */
658        public Resource[] getProviders( final Scope scope, final boolean expand, final boolean sort )
659        {
660            return getDefaultProviders( scope, expand, sort );
661        }
662        
663       /**
664        * Return an array of resource that are providers to this resource. If
665        * the supplied scope is BUILD the returned resource array is equivalent
666        * <src>getProviders( Scope.BUILD, .. )</src>.  If the scope is RUNTIME
667        * the returned resource array includes BUILD and RUNTIME resources. If 
668        * the scope is TEST the returned array includes BUILD, RUNTIME and TEST
669        * resources.
670        * @param scope the scope of aggregation to be applied to the selection
671        * @param expand if TRUE include transitive dependencies
672        * @param sort if true the array will sorted relative to dependencies
673        * @return the resource providers
674        */
675        public Resource[] getAggregatedProviders( final Scope scope, final boolean expand, final boolean sort )
676        {
677            return getAggregatedDefaultProviders( scope, expand, sort, false );
678        }
679        
680       /**
681        * Return a sorted and filtered array of providers. Resources not declaring
682        * the "jar" type as a produced type are excluded from selection.  The 
683        * resource array will include transitive dependencies.  The method is 
684        * suitable for the construction of build and test phase classloaders.
685        *
686        * @param scope the aggregation scope
687        * @return the scoped resource chain
688        */
689        public Resource[] getClasspathProviders( final Scope scope )
690        {
691            DefaultResource[] result = getAggregatedDefaultProviders( scope, true, true, true );
692            List stack = new ArrayList();
693            for( int i=0; i<result.length; i++ )
694            {
695                DefaultResource resource = result[i];
696                if( resource.isa( "jar" ) )
697                {
698                    stack.add( resource );
699                }
700            }
701            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
702        }
703    
704       /**
705        * Return an array of runtime providers filtered relative to a supplied
706        * classloading category.  Resources not declaring the "jar" type as a 
707        * produced type are excluded from selection.  The resource array returned 
708        * from this operation is a sorted transitive sequence excluding all 
709        * resource references by any category higher than the supplied category.
710        * This method is typically used to construct information suitable for 
711        * the gerneration of plugin metadata.
712        *
713        * @param category the classloader category
714        * @return the category scoped resource chain
715        */
716        public Resource[] getClasspathProviders( final Category category )
717        {
718            DefaultResource[] resources = getClasspathDefaultProviders( category );
719            return sortDefaultResources( resources, Scope.RUNTIME );
720        }
721        
722       /**
723        * Return an array of resources that are consumers of this resource.
724        * @param expand if true the returned array includes consumers associated
725        *   through transitive dependency relationships, otherwise the array is 
726        *   limited to direct consumers
727        * @param sort if true the array is sorted relative to depenency relationships
728        * @return the array of consumer projects
729        */
730        public Resource[] getConsumers( final boolean expand, final boolean sort )
731        {
732            return getDefaultConsumers( expand, sort );
733        }
734        
735       /**
736        * Return the underlying resource defintion.
737        * @return the resource directive
738        */
739        public ResourceDirective getResourceDirective()
740        {
741            return m_directive;
742        }
743    
744       /**
745        * Return a filename using the layout strategy employed by the cache.
746        * @param id the artifact type
747        * @return the filename
748        */
749        public String getLayoutPath( final String id )
750        {
751            Artifact artifact = getArtifact( id );
752            return Transit.getInstance().getCacheLayout().resolveFilename( artifact );
753        }
754    
755       /**
756        * Return a directive suitable for publication as an external description.
757        * @param module the enclosing module
758        * @return the resource directive
759        */
760        ResourceDirective exportResource( final DefaultModule module )
761        {
762            if( null == m_directive )
763            {
764                final String error = 
765                  "Cannot export from the root module.";
766                throw new UnsupportedOperationException( error );
767            }
768            String name = getName();
769            String version = getVersion();
770            String basedir = null;
771            InfoDirective info = m_directive.getInfoDirective();
772            TypeDirective[] types = m_directive.getTypeDirectives();
773            TypeDirective[] exportedTypes = createExportedTypes( types );
774            DependencyDirective[] dependencies = createDeps( module );
775            Properties properties = getExportProperties();
776            return ResourceDirective.createResourceDirective( 
777              name, version, Classifier.EXTERNAL, basedir,
778              info, exportedTypes, dependencies, properties, null );
779        }
780        
781        TypeDirective[] createExportedTypes( final TypeDirective[] types )
782        {
783            TypeDirective[] export = new TypeDirective[ types.length ]; 
784            for( int i=0; i<export.length; i++ )
785            {
786                TypeDirective type = types[i];
787                String id = type.getID();
788                Version version = type.getVersion();
789                export[i] = new TypeDirective( id, version );
790            }
791            return export;
792        }
793        
794        private DependencyDirective[] createDeps( final DefaultModule module )
795        {
796            ArrayList list = new ArrayList();
797            createIncludeDirectives( module, list, Category.SYSTEM );
798            createIncludeDirectives( module, list, Category.PUBLIC );
799            createIncludeDirectives( module, list, Category.PROTECTED );
800            createIncludeDirectives( module, list, Category.PRIVATE );
801            if( list.size() == 0 )
802            {
803                return new DependencyDirective[0];
804            }
805            else
806            {
807                IncludeDirective[] includes = 
808                 (IncludeDirective[]) list.toArray( new IncludeDirective[0] );
809                DependencyDirective runtime = 
810                  new DependencyDirective( Scope.RUNTIME, includes );
811                return new DependencyDirective[]{runtime};
812            }
813        }
814        
815        boolean isaDescendant( final DefaultModule module )
816        {
817            if( module == this )
818            {
819                return true;
820            }
821            if( m_parent == null )
822            {
823                return false;
824            }
825            else
826            {
827                if( m_parent == module )
828                {
829                    return true;
830                }
831                else
832                {
833                    return m_parent.isaDescendant( module );
834                }
835            }
836        }
837    
838        private void createIncludeDirectives(
839          final DefaultModule module, final List list, final Category category )
840        {
841            DefaultResource[] providers = 
842              getDefaultProviders( Scope.RUNTIME, true, category );
843            for( int i=0; i<providers.length; i++ )
844            {
845                DefaultResource provider = providers[i];
846                if( provider.isaDescendant( module ) )
847                {
848                    // create a ref
849                    String path = provider.getResourcePath();
850                    IncludeDirective include = 
851                      new IncludeDirective( 
852                        IncludeDirective.REF,
853                        category,
854                        path,
855                        null );
856                    list.add( include );
857                }
858                else
859                {
860                    // create a urn
861                    
862                    Type[] types = provider.getTypes();
863                    for( int j=0; j<types.length; j++ )
864                    {
865                        Type type = types[j];
866                        String label = type.getID();
867                        Artifact artifact = provider.getArtifact( label );
868                        String urn = artifact.toString();
869                        IncludeDirective include = 
870                          new IncludeDirective( 
871                            IncludeDirective.URI,
872                            category,
873                            urn,
874                            null );
875                        list.add( include );
876                    }
877                }
878            }
879        }
880        
881        //----------------------------------------------------------------------------
882        // Object
883        //----------------------------------------------------------------------------
884        
885       /**
886        * Return a string representation of the resource in the form 'resource:[path]'.
887        * @return the string value
888        */
889        public String toString()
890        {
891            if( null != m_directive )
892            {
893                if( m_directive.isLocal() )
894                {
895                    return toString( "project" );
896                }
897            }
898            return toString( "resource" );
899        }
900        
901        String toString( final String type )
902        {
903            return type + ":" + getResourcePath() + "#" + getVersion();
904        }
905        
906       /**
907        * Compare this object with another.
908        * @param other the other object
909        * @return the comparitive index
910        */
911        public int compareTo( final Object other )
912        {
913            if( other instanceof DefaultResource )
914            {
915                DefaultResource resource = (DefaultResource) other;
916                return getResourcePath().compareTo( resource.m_path );
917            }
918            else
919            {
920                return -1;
921            }
922        }
923        
924        //----------------------------------------------------------------------------
925        // internals
926        //----------------------------------------------------------------------------
927        
928       /**
929        * Return the singlton library.
930        * @return the library
931        */
932        DefaultLibrary getDefaultLibrary()
933        {
934            return m_library;
935        }
936        
937        boolean isAnonymous()
938        {
939            if( null != m_directive )
940            {
941                return m_directive.isAnonymous();
942            }
943            return false;
944        }
945        
946        boolean isLocal()
947        {
948            if( null != m_directive )
949            {
950                return m_directive.isLocal();
951            }
952            return false;
953        }
954        
955        DefaultModule getDefaultParent()
956        {
957            if( null != m_parent )
958            {
959                if( m_parent.isRoot() )
960                {
961                    return null;
962                }
963            }
964            return m_parent;
965        }
966        
967        DefaultResource[] getAggregatedDefaultProviders( 
968          final Scope scope, final boolean expanded, final boolean sort, final boolean flag )
969        {
970            DefaultResource[] resources = 
971              getAggregatedDefaultProviders( scope, expanded, flag );
972            if( sort )
973            {
974                return sortDefaultResources( resources, scope );
975            }
976            else
977            {
978                Arrays.sort( resources );
979                return resources;
980            }
981        }
982        
983        DefaultResource[] getAggregatedDefaultProviders( 
984          final Scope scope, final boolean expanded, final boolean flag )
985        {
986            ArrayList list = new ArrayList();
987            if( !flag )
988            {
989                aggregateProviders( list, Scope.BUILD );
990            }
991            if( scope.isGreaterThan( Scope.BUILD ) )
992            {
993                aggregateProviders( list, Scope.RUNTIME );
994            }
995            if( scope.isGreaterThan( Scope.RUNTIME ) )
996            {
997                aggregateProviders( list, Scope.TEST );
998            }
999            DefaultResource[] result = (DefaultResource[]) list.toArray( new DefaultResource[0] );
1000            if( expanded )
1001            {
1002                List visited = new ArrayList();
1003                List stack = new ArrayList();
1004                for( int i=0; i<result.length; i++ )
1005                {
1006                    DefaultResource resource = result[i];
1007                    expandDefaultResource( visited, stack, scope, resource );
1008                }
1009                result = (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1010            }
1011            return result;
1012        }
1013    
1014        private void aggregateProviders( final List list, final Scope scope )
1015        {
1016            DefaultResource[] resources = getDefaultProviders( scope, false, null );
1017            for( int i=0; i<resources.length; i++ )
1018            {
1019                DefaultResource resource = resources[i];
1020                if( !list.contains( resource ) )
1021                {
1022                    list.add( resource );
1023                }
1024            }
1025        }
1026        
1027        DefaultResource[] getDefaultProviders( 
1028          final Scope scope, final boolean expanded, final boolean sort ) 
1029        {
1030            DefaultResource[] resources = getDefaultProviders( scope, expanded, null );
1031            if( sort )
1032            {
1033                return sortDefaultResources( resources, scope );
1034            }
1035            else
1036            {
1037                Arrays.sort( resources );
1038                return resources;
1039            }
1040        }
1041        
1042        DefaultResource[] getDefaultProviders( 
1043          final Scope scope, final boolean expand, final Category category )
1044        {
1045            ArrayList visited = new ArrayList();
1046            ArrayList stack = new ArrayList();
1047            DefaultResource[] providers = getLocalDefaultProviders( scope, category );
1048            for( int i=0; i<providers.length; i++ )
1049            {
1050                DefaultResource provider = providers[i];
1051                if( expand )
1052                {
1053                    expandDefaultResource( visited, stack, scope, provider );
1054                }
1055                else if( !stack.contains( provider ) )
1056                {
1057                    stack.add( provider );
1058                }
1059            }
1060            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1061        }
1062        
1063        DefaultResource[] getLocalDefaultProviders( final Scope scope, final Category category ) 
1064        {
1065            if( null == m_directive )
1066            {
1067                return new DefaultResource[0];
1068            }
1069            IncludeDirective[] includes = getLocalIncludes( scope, category );
1070            DefaultResource[] resources = new DefaultResource[ includes.length ];
1071            for( int i=0; i<includes.length; i++ )
1072            {
1073                IncludeDirective include = includes[i];
1074                if( include.getMode().equals( IncludeDirective.URI ) )
1075                {
1076                    try
1077                    {
1078                        String value = include.getValue();
1079                        String urn = resolve( value );
1080                        Properties properties = include.getProperties();
1081                        resources[i] = m_library.getAnonymousResource( urn, properties );
1082                    }
1083                    catch( URISyntaxException e )
1084                    {
1085                        final String error = 
1086                          "Invalid uri value: " + include.getValue();
1087                        throw new RuntimeException( error, e );
1088                    }
1089                    catch( InvalidNameException e )
1090                    {
1091                        final String error = 
1092                          "An anonomous dependency include reference to ["
1093                          + include
1094                          + "] within the resource ["
1095                          + getResourcePath()
1096                          + "] could not be resolved.";
1097                        throw new InvalidNameException( error, e );
1098                    }
1099                    catch( Exception e )
1100                    {
1101                        final String error = 
1102                          "Unexpected error during dynamic resource creation.";
1103                        throw new RuntimeException( error, e );
1104                    }
1105                }
1106                else
1107                {
1108                    String ref = getIncludeReference( include );
1109                    try
1110                    {
1111                        DefaultResource resource = m_library.getDefaultResource( ref );
1112                        resources[i] = resource;
1113                    }
1114                    catch( InvalidNameException e )
1115                    {
1116                        if( null == category )
1117                        {
1118                            final String error = 
1119                              "A dependency include ["
1120                              + ref
1121                              + "] within ["
1122                              + this
1123                              + "] referencing ["
1124                              + ref
1125                              + "] under the scope ["
1126                              + scope
1127                              + "] is unknown.";
1128                            throw new InvalidNameException( error );
1129                        }
1130                        else
1131                        {
1132                            final String error = 
1133                              "A dependency include within ["
1134                              + this
1135                              + "] referencing ["
1136                              + ref
1137                              + "] under the scope ["
1138                              + scope
1139                              + "] and category ["
1140                              + category
1141                              + "] is unknown.";
1142                            throw new InvalidNameException( error );
1143                        }
1144                    }
1145                }
1146            }
1147            return resources;
1148        }
1149        
1150        private IncludeDirective[] getLocalIncludes( final Scope scope, final Category category )
1151        {
1152            DependencyDirective dependency = m_directive.getDependencyDirective( scope );
1153            if( null == category )
1154            {
1155                return dependency.getIncludeDirectives();
1156            }
1157            else
1158            {
1159                return dependency.getIncludeDirectives( category );
1160            }
1161        }
1162        
1163        private void expandDefaultResource( 
1164          final List visited, final List stack, final Scope scope, final DefaultResource resource )
1165        {
1166            if( visited.contains( resource ) )
1167            {
1168                return;
1169            }
1170            else
1171            {
1172                visited.add( resource );
1173                boolean flag = !scope.equals( Scope.BUILD );
1174                DefaultResource[] providers = resource.getAggregatedDefaultProviders( scope, false, flag );
1175                for( int i=0; i<providers.length; i++ )
1176                {
1177                    DefaultResource provider = providers[i];
1178                    expandDefaultResource( visited, stack, scope, provider );
1179                }
1180                stack.add( resource );
1181            }
1182        }
1183        
1184        private String getIncludeReference( final IncludeDirective directive )
1185        {
1186            if( null == m_parent )
1187            {
1188                return directive.getValue();
1189            }
1190            else
1191            {
1192                if( IncludeDirective.REF.equals( directive.getMode() ) )
1193                {
1194                    return directive.getValue();
1195                }
1196                else
1197                {
1198                    String path = m_parent.getResourcePath();
1199                    if( "".equals( path ) )
1200                    {
1201                        return directive.getValue();
1202                    }
1203                    else
1204                    {
1205                        String key = directive.getValue();
1206                        return path + "/" + key;
1207                    }
1208                }
1209            }
1210        }
1211        
1212        //----------------------------------------------------------------------------
1213        // consumer concerns
1214        //----------------------------------------------------------------------------
1215        
1216        boolean isaConsumer( final DefaultResource resource )
1217        {
1218            DefaultResource[] resources = getAggregatedDefaultProviders( Scope.TEST, false, false );
1219            for( int i=0; i<resources.length; i++ )
1220            {
1221                DefaultResource provider = resources[i];
1222                if( resource.equals( provider ) )
1223                {
1224                    return true;
1225                }
1226            }
1227            return false;
1228        }
1229        
1230        DefaultResource[] getDefaultConsumers( final boolean expand, final boolean sort )
1231        {
1232            DefaultResource[] consumers = getDefaultConsumers( expand );
1233            if( sort )
1234            {
1235                return sortDefaultResources( consumers, Scope.TEST );
1236            }
1237            else
1238            {
1239                return consumers;
1240            }
1241        }
1242        
1243        DefaultResource[] getDefaultConsumers( final boolean expand )
1244        {
1245            if( !expand )
1246            {
1247                ArrayList list = new ArrayList();
1248                DefaultResource[] resources = m_library.selectDefaultResources( "**/*" );
1249                for( int i=0; i<resources.length; i++ )
1250                {
1251                    DefaultResource resource = resources[i];
1252                    if( !list.contains( resource ) && resource.isaConsumer( this ) )
1253                    {
1254                        list.add( resource );
1255                    }
1256                }
1257                return (DefaultResource[]) list.toArray( new DefaultResource[0] );
1258            }
1259            else
1260            {
1261                ArrayList visited = new ArrayList();
1262                ArrayList stack = new ArrayList();
1263                DefaultResource[] consumers = getDefaultConsumers( false );
1264                for( int i=0; i<consumers.length; i++ )
1265                {
1266                    DefaultResource consumer = consumers[i];
1267                    processConsumer( visited, stack, consumer );
1268                }
1269                return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1270            }
1271        }
1272        
1273        void processConsumer( final List visited, final List stack, final DefaultResource consumer )
1274        {
1275            if( visited.contains( consumer ) )
1276            {
1277                return;
1278            }
1279            visited.add( consumer );
1280            stack.add( consumer );
1281            DefaultResource[] resources = consumer.getDefaultConsumers( false, false );
1282            for( int i=0; i<resources.length; i++ )
1283            {
1284                DefaultResource resource = resources[i];
1285                processConsumer( visited, stack, resource );
1286            }
1287        }
1288        
1289        //----------------------------------------------------------------------------
1290        // classpath stuff
1291        //----------------------------------------------------------------------------
1292        
1293       /**
1294        * Construct an array of resources based on the RUNTIME scoped dependencies
1295        * associated with the supplied category.  The implementation builds a list
1296        * of all preceeding categories as a basis for filtering the returned list ensuring
1297        * no duplicate references are returned.
1298        * @param category the runtime classloader category
1299        * @return the array of resources the define a classloader for the category
1300        */
1301        private DefaultResource[] getClasspathDefaultProviders( final Category category )
1302        {
1303            ArrayList list = new ArrayList();
1304            for( int i=0; i<category.getValue(); i++ )
1305            {   
1306                Category c = Category.parse( i );
1307                DefaultResource[] collection = 
1308                  getDefaultProviders( Scope.RUNTIME, true, c );
1309                for( int j=0; j<collection.length; j++ )
1310                {
1311                    list.add( collection[j] );
1312                }
1313            }
1314            DefaultResource[] resources = 
1315              getDefaultProviders( Scope.RUNTIME, true, category );
1316            ArrayList stack = new ArrayList();
1317            for( int i=0; i<resources.length; i++ )
1318            {
1319                DefaultResource resource = resources[i];
1320                if( resource.isa( "jar" ) && !list.contains( resource ) )
1321                {
1322                    stack.add( resource );
1323                }
1324            }
1325            
1326            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1327        }
1328        
1329        //----------------------------------------------------------------------------
1330        // sorting relative to dependencies
1331        //----------------------------------------------------------------------------
1332        
1333        DefaultResource[] sortDefaultResources( final DefaultResource[] resources )
1334        {
1335            return sortDefaultResources( resources, Scope.TEST );
1336        }
1337        
1338        DefaultResource[] sortDefaultResources( final DefaultResource[] resources, final Scope scope )
1339        {
1340            ArrayList visited = new ArrayList();
1341            ArrayList stack = new ArrayList();
1342            for( int i=0; i<resources.length; i++ )
1343            {
1344                DefaultResource resource = resources[i];
1345                resource.sortDefaultResource( visited, stack, scope, resources );
1346            }
1347            return (DefaultResource[]) stack.toArray( new DefaultResource[0] );
1348        }
1349        
1350        void sortDefaultResource( 
1351          final List visited, final List stack, final Scope scope, final DefaultResource[] resources )
1352        {
1353            if( visited.contains( this ) )
1354            {
1355                return;
1356            }
1357            else
1358            {
1359                visited.add( this );
1360                DefaultResource[] providers = 
1361                  getAggregatedDefaultProviders( scope, false, false );
1362                for( int i=0; i<providers.length; i++ )
1363                {
1364                    DefaultResource provider = providers[i];
1365                    if( isaMember( resources, provider ) )
1366                    {
1367                        provider.sortDefaultResource( visited, stack, scope, resources );
1368                    }
1369                }
1370                if( !stack.contains( this ) )
1371                {
1372                    stack.add( this );
1373                }
1374            }
1375        }
1376        
1377        boolean isaMember( final DefaultResource[] resources, final DefaultResource resource )
1378        {
1379            for( int i=0; i<resources.length; i++ )
1380            {
1381                DefaultResource r = resources[i];
1382                if( resource == r )
1383                {
1384                    return true;
1385                }
1386            }
1387            return false;
1388        }
1389        
1390        //----------------------------------------------------------------------------
1391        // version utilities
1392        //----------------------------------------------------------------------------
1393    
1394        private String getStandardVersion()
1395        {
1396            String signature = getBuildSignature();
1397            if( BOOTSTRAP.equals( signature ) )
1398            {
1399                return BOOTSTRAP;
1400            }
1401            
1402            if( isDecimal() )
1403            {
1404                Version decimal = getDecimalVersion();
1405                String spec = decimal.toString();
1406                if( null == signature )
1407                {
1408                    return spec;
1409                }
1410                else
1411                {
1412                    return spec + "-" + signature;
1413                }
1414            }
1415            else
1416            {
1417                if( null == signature )
1418                {
1419                    return SNAPSHOT;
1420                }
1421                else
1422                {
1423                    return signature;
1424                }
1425            }
1426        }
1427        
1428        private boolean isDecimal()
1429        {
1430            boolean isDecimal = Boolean.getBoolean( DECIMAL_VERSIONING_KEY );
1431            isDecimal = getBooleanProperty( DECIMAL_VERSIONING_KEY, isDecimal );
1432            return getBooleanProperty( LEGACY_DECIMAL_PREFIX_KEY, isDecimal );
1433        }
1434        
1435        private String getBuildSignature()
1436        {
1437            String system = System.getProperty( "build.signature", null );
1438            return getProperty( "build.signature", system );
1439        }
1440        
1441        /*
1442        private String getBuildSignature()
1443        {
1444            String system = System.getProperty( "build.signature", null );
1445            String value = getProperty( "build.signature", system );
1446            if( null == value )
1447            {
1448                return SNAPSHOT;
1449            }
1450            else if( value.equals( "project.timestamp" ) )
1451            {
1452                return TIMESTAMP;
1453            }
1454            else
1455            {
1456                return value;
1457            }
1458        }
1459        */
1460        
1461        private int getMajorVersion()
1462        {
1463            return getIntegerProperty( "project.major.version", 0 );
1464        }
1465        
1466        private int getMinorVersion()
1467        {
1468            return getIntegerProperty( "project.minor.version", 0 );
1469        }
1470        
1471        private int getMicroVersion()
1472        {
1473            return getIntegerProperty( "project.micro.version", 0 );
1474        }
1475        
1476       /**
1477        * Return the UTC YYMMDD.HHMMSSS signature of a date.
1478        * @return the UTC date-stamp signature
1479        */
1480        public static String getTimestamp()
1481        {
1482            return getTimestamp( new Date() );
1483        }
1484        
1485       /**
1486        * Return the UTC YYMMDD.HHMMSSS signature of a date.
1487        * @param date the date
1488        * @return the UTC date-stamp signature
1489        */
1490        public static String getTimestamp( final Date date )
1491        {
1492            final SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
1493            sdf.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
1494            return sdf.format( date );
1495        }
1496        
1497        //----------------------------------------------------------------------------
1498        // other utilities
1499        //----------------------------------------------------------------------------
1500    
1501        private File getAnchor()
1502        {
1503            if( null != m_parent )
1504            {
1505                File anchor = m_parent.getBaseDir();
1506                if( null != anchor )
1507                {
1508                    return anchor;
1509                }
1510            }
1511            return m_library.getRootDirectory();
1512        }
1513        
1514        File getCanonicalFile( final File file )
1515        {
1516            try
1517            {
1518                return file.getCanonicalFile();
1519            }
1520            catch( IOException e )
1521            {
1522                final String error = 
1523                  "internal error while attempting to convert the file ["
1524                  + file
1525                  + "] to its canonical representation.";
1526                throw new RuntimeException( error, e );
1527            }
1528        }
1529        
1530        private String getGroupName()
1531        {
1532            if( m_parent.isRoot() )
1533            {
1534                return null;
1535            }
1536            else
1537            {
1538                return m_parent.getResourcePath();
1539            }
1540        }
1541    }