001    /*
002     * Copyright 2005-2006 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.FileNotFoundException;
023    import java.net.URI;
024    import java.net.URISyntaxException;
025    import java.util.ArrayList;
026    import java.util.Properties;
027    import java.util.Hashtable;
028    
029    import net.dpml.library.info.LibraryDecoder;
030    import net.dpml.library.info.ImportDirective;
031    import net.dpml.library.info.LibraryDirective;
032    import net.dpml.library.info.ModuleDirective;
033    import net.dpml.library.info.ResourceDirective;
034    import net.dpml.library.info.Scope;
035    import net.dpml.library.Library;
036    import net.dpml.library.Module;
037    import net.dpml.library.ModuleNotFoundException;
038    import net.dpml.library.Resource;
039    import net.dpml.library.ResourceNotFoundException;
040    
041    import net.dpml.transit.Artifact;
042    
043    import net.dpml.util.Logger;
044    
045    /**
046     * Utility class used for construction of a module model from an XML source.
047     *
048     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
049     * @version 1.1.2
050     */
051    public final class DefaultLibrary extends DefaultDictionary implements Library
052    {
053        private static final LibraryDecoder LIBRARY_DECODER = new LibraryDecoder();
054        
055        private final LibraryDirective m_directive;
056        private final DefaultModule m_module;
057        private final File m_root;
058        private final Logger m_logger;
059        private final Hashtable m_anonymous = new Hashtable();
060        
061        private static LibraryDirective buildLibraryDirective( File source ) throws Exception
062        {
063            return LIBRARY_DECODER.build( source );
064        }
065        
066       /**
067        * Creation of a new library.  The definition of the indexwill 
068        * be resolved by search up the file system for a file named index.xml.
069        * @param logger the assigned logging channel
070        * @exception Exception if an error occurs during defintion loading
071        */
072        public DefaultLibrary( Logger logger ) throws Exception
073        {
074            this( logger, resolveLibrarySource() );
075        }
076        
077       /**
078        * Creation of a new library.
079        * @param logger the assigned logging channel
080        * @param source the index source defintion
081        * @exception Exception if an error occurs during defintion loading
082        */
083        public DefaultLibrary( Logger logger, File source ) throws Exception
084        {   
085            super( null, buildLibraryDirective( source ) );
086            
087            if( null == logger )
088            {
089                throw new NullPointerException( "logger" );
090            }
091            
092            m_logger = logger;
093            m_directive = (LibraryDirective) super.getAbstractDirective();
094            m_root = source.getParentFile().getCanonicalFile();
095            m_module = new DefaultModule( this, m_directive );
096            
097            getLogger().debug( "loaded root module: " + m_root );
098            System.setProperty( "dpml.library.basedir", m_root.toString() );
099            
100            // handle expansion of import directives 
101            
102            ImportDirective[] imports = m_directive.getImportDirectives();
103            ModuleDirective[] importModuleDirectives = new ModuleDirective[ imports.length ];
104            for( int i=0; i<imports.length; i++ )
105            {
106                ImportDirective include = imports[i];
107                ImportDirective.Mode mode = include.getMode();
108                if( ImportDirective.Mode.FILE.equals( mode ) )
109                {
110                    throw new UnsupportedOperationException( "file" );
111                }
112                else
113                {
114                    String path = include.getValue();
115                    URI uri = new URI( path );
116                    getLogger().debug( "loading external import: " + uri );
117                    ResourceDirective resource = LIBRARY_DECODER.buildResource( uri );
118                    if( resource instanceof ModuleDirective )
119                    {
120                        ModuleDirective moduleDirective = (ModuleDirective) resource;
121                        importModuleDirectives[i] = moduleDirective;
122                    }
123                    else
124                    {
125                        final String error = 
126                          "Not yet equipped to import resource of the type [" 
127                          + resource.getClass().getName() 
128                          + ".";
129                        throw new IllegalArgumentException( error );
130                    } 
131                }
132            }
133            
134            for( int i=0; i<importModuleDirectives.length; i++ )
135            {
136                ModuleDirective importModuleDirective = importModuleDirectives[i];
137                m_module.addResource( importModuleDirective );
138            }
139            
140            // create the top-level modules
141            
142            ArrayList primaryDirectives = new ArrayList();
143            ResourceDirective[] directives = m_directive.getResourceDirectives();
144            for( int i=0; i<directives.length; i++ )
145            {
146                ResourceDirective directive = directives[i];
147                //if( directive instanceof ModuleDirective )
148                //{
149                    //ModuleDirective md = (ModuleDirective) directive;
150                    primaryDirectives.add( directive );
151                //}
152                //else
153                //{
154                //    final String error = 
155                //      "No support in place for non-module top-level resources.";
156                //    throw new IllegalArgumentException( error );
157                //}
158            }
159            ResourceDirective[] values = (ResourceDirective[]) primaryDirectives.toArray( new ResourceDirective[0] );
160            for( int i=0; i<values.length; i++ )
161            {
162                ResourceDirective d = values[i];
163                m_module.addResource( d );
164            }
165        }
166        
167        //----------------------------------------------------------------------------
168        // Library
169        //----------------------------------------------------------------------------
170        
171       /**
172        * Utility operation to sort a collection of resources.
173        * @param resources the resources to sort
174        * @return the sorted resource array
175        */
176        public Resource[] sort( Resource[] resources )
177        {
178            DefaultResource[] array = new DefaultResource[ resources.length ];
179            for( int i=0; i<resources.length; i++ )
180            {
181                array[i] = (DefaultResource) resources[i];
182            }
183            return m_module.sortDefaultResources( array, Scope.TEST );
184        }
185        
186       /**
187        * Return an array of the top-level modules within the library.
188        * @return module array
189        */
190        public Module[] getModules()
191        {
192            return m_module.getModules();
193        }
194        
195       /**
196        * Return a array of all modules in the library.
197        * @return module array
198        */
199        public Module[] getAllModules()
200        {
201            return m_module.getAllModules();
202        }
203        
204       /**
205        * Return a named module.
206        * @param ref the fully qualified module name
207        * @return the module
208        * @exception ModuleNotFoundException if the module cannot be found
209        */
210        public Module getModule( String ref ) throws ModuleNotFoundException
211        {
212            return m_module.getModule( ref );
213        }
214    
215       /**
216        * Recursively lookup a resource using a fully qualified reference.
217        * @param ref the fully qualified resource name
218        * @return the resource instance
219        * @exception ResourceNotFoundException if the resource cannot be found
220        */
221        public Resource getResource( String ref ) throws ResourceNotFoundException
222        {
223            return m_module.getResource( ref );
224        }
225        
226       /**
227        * <p>Select a set of resource matching a supplied a resource selection 
228        * constraint.  The constraint may contain the wildcards '**' and '*'.
229        * @param criteria the selection criteria
230        * @param sort if true the returned array will be sorted relative to dependencies
231        *   otherwise the array will be sorted alphanumerically with respect to the resource
232        *   path
233        * @return an array of resources matching the selction criteria
234        */
235        public Resource[] select( String criteria, boolean sort )
236        {
237            return m_module.select( criteria, false, sort );
238        }
239        
240       /**
241        * <p>Select a set of resource matching a supplied a resource selection 
242        * constraint.  The constraint may contain the wildcards '**' and '*'.
243        * @param criteria the selection criteria
244        * @param local if true restrict selection to local projects
245        * @param sort if true the returned array will be sorted relative to dependencies
246        *   otherwise the array will be sorted alphanumerically with respect to the resource
247        *   path
248        * @return an array of resources matching the selction criteria
249        */
250        public Resource[] select( String criteria, boolean local, boolean sort )
251        {
252            return m_module.select( criteria, local, sort );
253        }
254        
255       /**
256        * Select all local projects with a basedir equal to or deeper than the supplied 
257        * directory.
258        * @param base the reference basedir
259        * @return an array of projects within or lower than the supplied basedir
260        */
261        public Resource[] select( File base )
262        {
263            return select( base, true );
264        }
265    
266       /**
267        * Select all local projects relative to the supplied basedir.
268        * @param base the reference basedir
269        * @param self if true and the basedir resolves to a project then include the project
270        *   otherwise the project will be expluded from selection
271        * @return an array of projects relative to the basedir
272        */
273        public Resource[] select( final File base, boolean self )
274        {
275            String root = base.toString();
276            ArrayList list = new ArrayList();
277            DefaultResource[] resources = m_module.selectDefaultResources( true, "**/*" );
278            for( int i=0; i<resources.length; i++ )
279            {
280                DefaultResource resource = resources[i];
281                File basedir = resource.getBaseDir();
282                if( null != basedir )
283                {
284                    String path = basedir.toString();
285                    if( path.startsWith( root ) )
286                    {
287                        if( path.equals( root ) )
288                        {
289                            if( self )
290                            {
291                                list.add( resource );
292                            }
293                        }
294                        else
295                        {
296                            list.add( resource );
297                        }
298                    }
299                }
300                else
301                {
302                    final String error = 
303                      "Local project list returned a resource with a null basedir ["
304                      + resource.getResourcePath() 
305                      + "].";
306                    throw new IllegalStateException( error );
307                }
308            }
309            DefaultResource[] selection = (DefaultResource[]) list.toArray( new DefaultResource[0] );
310            return m_module.sortDefaultResources( selection );
311        }
312        
313       /**
314        * Locate a resource relative to a base directory.
315        * @param base the base directory
316        * @return a resource with a matching basedir
317        * @exception ResourceNotFoundException if resource match  relative to the supplied base
318        */
319        public Resource locate( File base ) throws ResourceNotFoundException
320        {
321            return m_module.locate( base );
322        }
323        
324        //----------------------------------------------------------------------------
325        // Dictionary
326        //----------------------------------------------------------------------------
327        
328       /**
329        * Return the property names associated with the dictionary.
330        * @return the array of property names
331        */
332        public String[] getPropertyNames()
333        {
334            return m_module.getPropertyNames();
335        }
336        
337       /**
338        * Return a property value.
339        * @param key the property key
340        * @return the property value
341        */
342        public String getProperty( String key )
343        {
344            return m_module.getProperty( key );
345        }
346        
347       /**
348        * Return a property value.
349        * @param key the property key
350        * @param value the default value
351        * @return the property value
352        */
353        public String getProperty( String key, String value )
354        {
355            return m_module.getProperty( key, value );
356        }
357        //----------------------------------------------------------------------------
358        // internals
359        //----------------------------------------------------------------------------
360        
361       /**
362        * Construct a new locally referenced anonymouse resource.
363        * @param include the dependency include
364        * @return the resource
365        * @exception IllegalArgumentException if the include mode is not URN mode
366        * @exception URISyntaxException if the include urn is invaid
367        */
368        DefaultResource getAnonymousResource( String urn, Properties properties ) 
369        throws URISyntaxException
370        {
371            if( m_anonymous.containsKey( urn ) )
372            {
373                return (DefaultResource) m_anonymous.get( urn );
374            }
375            
376            Artifact artifact = Artifact.createArtifact( urn );
377            String scheme = artifact.getScheme();
378            String group = artifact.getGroup();
379            String name = artifact.getName();
380            String version = artifact.getVersion();
381            String type = artifact.getType();
382            
383            ResourceDirective resourceDirective = 
384              ResourceDirective.createAnonymousResource( scheme, name, version, type, properties );
385    
386            ModuleDirective enclosing = null;
387            String[] elements = group.split( "/", -1 );
388            for( int i = ( elements.length-1 ); i>-1; i-- )
389            {
390                String elem = elements[i];
391                if( i == ( elements.length-1 ) )
392                {
393                    enclosing = new ModuleDirective( elem, version, resourceDirective );
394                }
395                else
396                {
397                    enclosing = new ModuleDirective( elem, version, enclosing );
398                }
399            }
400            try
401            {
402                DefaultModule root = new DefaultModule( this, m_directive );
403                root.addResource( enclosing );
404                DefaultResource resource =  root.getDefaultResource( group + "/" + name );
405                m_anonymous.put( urn, resource );
406                return resource;
407            }
408            catch( Exception e )
409            {
410                final String error = 
411                  "Internal error while creating an ANONYMOUS resource: "
412                  + urn
413                  + "].";
414                throw new RuntimeException( error, e );
415            }
416        }
417        
418        File getRootDirectory()
419        {
420            return m_root;
421        }
422        
423       /**
424        * Return the array of top-level modules.
425        * @return the top-level module array
426        */
427        DefaultModule[] getDefaultModules()
428        {
429            return m_module.getDefaultModules();
430        }
431        
432       /**
433        * Recursively lookup a resource using a fully qualified reference.
434        * @param ref the fully qualified resource name
435        * @return the resource instance
436        */
437        DefaultResource getDefaultResource( String ref )
438        {
439            return m_module.getDefaultResource( ref );
440        }
441        
442       /**
443        * Return a named module.
444        * @param ref the fully qualified resource name
445        * @return the module
446        */
447        DefaultModule getDefaultModule( String ref )
448        {
449            return m_module.getDefaultModule( ref );
450        }
451        
452        //----------------------------------------------------------------------------
453        // selection
454        //----------------------------------------------------------------------------
455        
456        DefaultResource[] selectDefaultResources( String spec )
457        {
458            return m_module.selectDefaultResources( spec );
459        }
460        
461        //----------------------------------------------------------------------------
462        // other internals
463        //----------------------------------------------------------------------------
464        
465        private Logger getLogger()
466        {
467            return m_logger;
468        }
469    
470        //----------------------------------------------------------------------------
471        // static utilities
472        //----------------------------------------------------------------------------
473        
474        private static File resolveLibrarySource() throws FileNotFoundException
475        {
476            String path = System.getProperty( "user.dir" );
477            File dir = new File( path );
478            return resolveLibrarySource( dir );
479        }
480        
481        private static File resolveLibrarySource( File dir ) throws FileNotFoundException
482        {
483            if( dir.isFile() )
484            {
485                throw new IllegalArgumentException( "not-a-directory" );
486            }
487            else
488            {
489                File file = new File( dir, INDEX_FILENAME );
490                if( file.isFile() && file.exists() )
491                {
492                    return file;
493                }
494                else
495                {
496                    File parent = dir.getParentFile();
497                    if( parent != null )
498                    {
499                        return resolveLibrarySource( parent );
500                    }
501                }
502            }
503            throw new FileNotFoundException( "index.xml" );
504        }
505    }