001    /*
002     * Copyright (c) 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.lang;
020    
021    import java.io.IOException;
022    import java.util.ArrayList;
023    import java.net.URL;
024    import java.net.URI;
025    import java.net.URLClassLoader;
026    
027    import net.dpml.transit.Artifact;
028    
029    import net.dpml.util.Logger;
030    
031    /**
032     * A named classloader.
033     *
034     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
035     * @version 1.1.0
036     */
037    public class StandardClassLoader extends URLClassLoader
038    {
039        //--------------------------------------------------------------------
040        // static
041        //--------------------------------------------------------------------
042        
043       /**
044        * Internal utility class to build a classloader.  If the supplied url
045        * sequence is zero length the parent classloader is returned directly.
046        *
047        * @param logger the logging channel
048        * @param name the name identifying the classloader
049        * @param category the category that this classloader is handling
050        * @param parent the parent classloader
051        * @param uris the uris to assign as classloader content
052        * @return the classloader
053        * @exception IOException if an I/O error occurs
054        */
055        public static ClassLoader buildClassLoader( Logger logger, String name, Category category, ClassLoader parent, URI[] uris )
056          throws IOException
057        {
058            URL[] urls = toURLs( uris  );
059            if( 0 == urls.length )
060            {
061                return parent;
062            }
063            ArrayList list = new ArrayList();
064            for( int i=0; i < urls.length; i++ )
065            {
066                if( isaCandidate( parent, urls[i] ) )
067                {
068                    list.add( urls[i] );
069                }
070            }
071            URL[] qualified = (URL[]) list.toArray( new URL[0] );
072            if( qualified.length == 0 )
073            {
074                return parent;
075            }
076            else
077            {
078                ClassLoader loader =
079                   new StandardClassLoader( name, category, qualified, parent );
080                classloaderConstructed( logger, name, category, loader );
081                return loader;
082            }
083        }
084    
085       /**
086        * Convert a sequence of URIs to URLs.
087        * @param uris the uris to convert
088        * @return the corresponding urls
089        * @exception IOException of a transformation error occurs
090        */
091        public static URL[] toURLs( URI[] uris ) throws IOException
092        {
093            URL[] urls = new URL[ uris.length ];
094            for( int i=0; i < urls.length; i++ )
095            {
096                URI uri = uris[i];
097                if( Artifact.isRecognized( uri ) )
098                {
099                    urls[i] = Artifact.toURL( uri );
100                }
101                else
102                {
103                    urls[i] = uri.toURL();
104                }
105            }
106            return urls;
107        }
108    
109       /**
110        * Test if the supplied url is already present within the supplied classloader.
111        * @param classloader the classloader to validate against
112        * @param url to url to check for
113        * @return true if the url is not included in the classloader
114        */
115        private static boolean isaCandidate( ClassLoader classloader, URL url )
116        {
117            if( classloader instanceof URLClassLoader )
118            {
119                URL[] urls = ( (URLClassLoader) classloader ).getURLs();
120                for( int i=0; i < urls.length; i++ )
121                {
122                    if( urls[i].equals( url ) )
123                    {
124                        return false;
125                    }
126                }
127                ClassLoader parent = classloader.getParent();
128                if( parent == null )
129                {
130                    return true;
131                }
132                else
133                {
134                    return isaCandidate( parent, url );
135                }
136            }
137            else
138            {
139                return true;
140            }
141        }
142    
143        //--------------------------------------------------------------------
144        // state
145        //--------------------------------------------------------------------
146        
147        private final Category m_category;
148        private final String m_name;
149    
150        //--------------------------------------------------------------------
151        // constructor
152        //--------------------------------------------------------------------
153    
154       /**
155        * Creation of a new classloader.
156        * @param name a name identifying the plugin
157        * @param category the classloader category identifier
158        * @param urls an array of urls to add to the classloader
159        * @param parent the parent classloader
160        */
161        public StandardClassLoader( String name, Category category, URL[] urls, ClassLoader parent )
162        {
163            super( urls, parent );
164            m_category = category;
165            m_name = name;
166        }
167    
168        //--------------------------------------------------------------------
169        // StandardClassLoader
170        //--------------------------------------------------------------------
171    
172       /**
173        * Return the classloader category
174        * @return the classloader category
175        */
176        public Category getCategory()
177        {
178            return m_category;
179        }
180    
181       /**
182        * Return a string representation of the classloader.
183        * @return the string value
184        */
185        public String getAnnotations()
186        {
187            StringBuffer buffer = new StringBuffer();
188            ClassLoader parent = getParent();
189            if( parent instanceof URLClassLoader )
190            {
191                URLClassLoader urlClassLoader = (URLClassLoader) parent;
192                buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) ); 
193            }
194            buffer.append( " " );
195            URL[] urls = getURLs();
196            for( int i=0; i<urls.length; i++ )
197            {
198                String path = urls[i].toString();
199                if( !path.startsWith( "file:" ) )
200                {
201                    buffer.append( path );
202                    buffer.append( " " );
203                }
204            }
205            return buffer.toString().trim();
206        }
207        
208        private String getURLClassLoaderAnnotations( URLClassLoader classloader )
209        {
210            StringBuffer buffer = new StringBuffer();
211            ClassLoader parent = classloader.getParent();
212            if( ( null != parent ) && ( parent instanceof URLClassLoader ) )
213            {
214                URLClassLoader urlClassLoader = (URLClassLoader) parent;
215                buffer.append( getURLClassLoaderAnnotations( urlClassLoader ) );
216            }
217            if( ClassLoader.getSystemClassLoader() == classloader )
218            {
219                return "";
220            }
221            buffer.append( " " );
222            URL[] urls = classloader.getURLs();
223            for( int i=0; i<urls.length; i++ )
224            {
225                String path = urls[i].toString();
226                if( !path.startsWith( "file:" ) )
227                {
228                    buffer.append( path );
229                    buffer.append( " " );
230                }
231            }
232            return buffer.toString().trim();
233        }
234    
235       /**
236        * Return a string representing of the classloader.
237        * @param expanded if true return an expanded representation of the classloader
238        * @return the string representation 
239        */
240        public String toString( boolean expanded )
241        {
242            StringBuffer buffer = new StringBuffer();
243            listClasspath( buffer );
244            return buffer.toString();
245        }
246    
247        //--------------------------------------------------------------------
248        // Object
249        //--------------------------------------------------------------------
250    
251       /**
252        * Return a string representing of the classloader.
253        * @return the string representation 
254        */
255        public String toString()
256        {
257            final String label = 
258              getClass().getName() 
259              + "#" 
260              + System.identityHashCode( this );
261            return label;
262        }
263    
264       /**
265        * Internal operation to list the classloader classpath.
266        * @param buffer the buffer to list to
267        */
268        protected void listClasspath( StringBuffer buffer )
269        {
270            listClasspath( buffer, this );
271            buffer.append( "\n" );
272        }
273    
274       /**
275        * Internal operation to list a classloader classpath.
276        * @param buffer the buffer to list to
277        * @param classloader the classloader to list
278        */
279        protected void listClasspath( StringBuffer buffer, ClassLoader classloader )
280        {
281            String label = 
282              "\nClassLoader: " 
283              + classloader.getClass().getName() 
284              + " (" 
285              + System.identityHashCode( classloader ) 
286              + ")";
287    
288            if( classloader instanceof StandardClassLoader )
289            {
290                StandardClassLoader cl = (StandardClassLoader) classloader;
291                ClassLoader parent = cl.getParent();
292                if( null != parent )
293                {
294                    listClasspath( buffer, parent );
295                }
296                
297                if( null != m_name )
298                {
299                    label = label.concat( "\nLabel: " + cl.m_name + " " + cl.getCategory() );
300                }
301                else
302                {
303                    label = label.concat( "\nCategory: " + cl.getCategory() );
304                }
305                buffer.append( label );
306                buffer.append( "\n" );
307                appendEntries( buffer, cl );
308            }
309            else if( classloader instanceof URLClassLoader )
310            {
311                URLClassLoader cl = (URLClassLoader) classloader;
312                ClassLoader parent = cl.getParent();
313                if( null != parent )
314                {
315                    listClasspath( buffer, parent );
316                }
317                buffer.append( label );
318                appendEntries( buffer, cl );
319            }
320            else
321            {
322                buffer.append( label );
323                buffer.append( "]\n" );
324            }
325        }
326    
327        private static void appendEntries( StringBuffer buffer, URLClassLoader classloader )
328        {
329            URL[] urls = classloader.getURLs();
330            for( int i=0; i < urls.length; i++ )
331            {
332                buffer.append( "\n    " );
333                URL url = urls[i];
334                String spec = url.toString();
335                buffer.append( spec );
336            }
337            buffer.append( "\n" );
338        }
339    
340       /**
341        * Return a string representing a report fo the common classloader chain
342        * following by the primary annd seciondarty classloaders.
343        * @param primary the primary classloader
344        * @param secondary the secondary classloader
345        * @return the report
346        */
347        public static String toString( ClassLoader primary, ClassLoader secondary )
348        {
349            StringBuffer buffer = new StringBuffer();
350            ClassLoader anchor = getCommonParent( primary, secondary );
351            if( null != anchor )
352            {
353                buffer.append( "\n----------------------------------------------------------------" );
354                buffer.append( "\nCommon Classloader" );
355                buffer.append( "\n----------------------------------------------------------------" );
356                list( buffer, anchor );
357            }
358            if( null != primary )
359            {
360                buffer.append( "\n----------------------------------------------------------------" );
361                buffer.append( "\nPrimary Classloader" );
362                buffer.append( "\n----------------------------------------------------------------" );
363                list( buffer, primary, anchor );
364            }
365            if( null != secondary )
366            {
367                buffer.append( "\n----------------------------------------------------------------" );
368                buffer.append( "\nSecondary Classloader" );
369                buffer.append( "\n----------------------------------------------------------------" );
370                list( buffer, secondary, anchor );
371            }
372            buffer.append( "\n----------------------------------------------------------------" );
373            return buffer.toString();
374        }
375        
376        private static ClassLoader getCommonParent( ClassLoader primary, ClassLoader secondary )
377        {
378            ClassLoader[] primaryChain = getClassLoaderChain( primary );
379            ClassLoader[] secondaryChain = getClassLoaderChain( secondary );
380            return getCommonClassLoader( primaryChain, secondaryChain );
381        }
382        
383        private static ClassLoader[] getClassLoaderChain( ClassLoader classloader )
384        {
385            if( null == classloader )
386            {
387                return new ClassLoader[0];
388            }
389            else
390            {
391                ArrayList list = new ArrayList();
392                list.add( classloader );
393                ClassLoader parent = classloader.getParent();
394                while( null != parent )
395                {
396                    list.add( parent );
397                    parent = parent.getParent();
398                }
399                ArrayList result = new ArrayList();
400                int n = list.size() - 1;
401                for( int i=n; i>-1; i-- )
402                {
403                    result.add( list.get( i ) );
404                }
405                return (ClassLoader[]) result.toArray( new ClassLoader[0] );
406            }
407        }
408    
409        private static ClassLoader getCommonClassLoader( ClassLoader[] primary, ClassLoader[] secondary )
410        {
411            ClassLoader anchor = null;
412            for( int i=0; i<primary.length; i++ )
413            {
414                ClassLoader classloader = primary[i];
415                if( secondary.length > i )
416                {
417                    ClassLoader cl = secondary[i];
418                    if( classloader == cl )
419                    {
420                        anchor = cl;
421                    }
422                    else
423                    {
424                        return anchor;
425                    }
426                }
427                else
428                {
429                    return anchor;
430                }
431            }
432            return anchor;
433        }
434        
435        private static void list( StringBuffer buffer, ClassLoader classloader )
436        {
437            list( buffer, classloader, null );
438        }
439        
440        private static void list( StringBuffer buffer, ClassLoader classloader, ClassLoader anchor )
441        {
442            if( classloader == anchor )
443            {
444                return;
445            }
446            ClassLoader parent = classloader.getParent();
447            if( null != parent  )
448            {
449                list( buffer, parent, anchor );
450            }
451            String label = 
452              "\nClassLoader: " 
453              + classloader.getClass().getName() 
454              + " (" + System.identityHashCode( classloader ) + ")";
455            buffer.append( label );
456            if( classloader instanceof StandardClassLoader )
457            {
458                StandardClassLoader loader = (StandardClassLoader) classloader;
459                if( null != loader.m_name )
460                {
461                    buffer.append( "\nLabel: " + loader.m_name + " " + loader.m_category );
462                }
463                else
464                {
465                    buffer.append( "\nCategory: " + loader.m_category );
466                }
467            }
468            if( classloader instanceof URLClassLoader )
469            {
470                URLClassLoader urlcl = (URLClassLoader) classloader;
471                buffer.append( "\n" );
472                appendEntries( buffer, urlcl );
473            }
474        }
475        
476       /**
477        * Handle notification of the creation of a new classloader.
478        * @param logger the logging channel
479        * @param label the classloader label
480        * @param category the classloader category
481        * @param classloader the new classloader to report
482        */
483        private static void classloaderConstructed( Logger logger, String label, Category category, ClassLoader classloader )
484        {
485            if( logger.isTraceEnabled() )
486            {
487                int id = System.identityHashCode( classloader );
488                StringBuffer buffer = new StringBuffer();
489                buffer.append( "new " );
490                buffer.append( category.toString() );
491                buffer.append( " classloader for " + label );
492                 buffer.append( "\n           id: " + id );
493                ClassLoader parent = classloader.getParent();
494                if( null != parent )
495                {
496                    int pid = System.identityHashCode( parent );
497                    buffer.append( 
498                      "\n      extends: " 
499                      + pid );
500                }
501                if( classloader instanceof URLClassLoader )
502                {
503                    URLClassLoader loader = (URLClassLoader) classloader;
504                    URL[] urls = loader.getURLs();
505                    if( urls.length == 1 )
506                    {
507                        buffer.append( 
508                          "\n     contains: 1 entry" );
509                    }
510                    else
511                    {
512                        buffer.append( 
513                          "\n     contains: " 
514                          + urls.length 
515                          + " entries" );
516                    }
517                    for( int i=0; i < urls.length; i++ )
518                    {
519                        URL url = urls[i];
520                        buffer.append( 
521                          "\n         [" 
522                          + ( i+1 ) 
523                          + "] " 
524                          + url.toString() );
525                    }
526                }
527                logger.trace( buffer.toString() );
528            }
529        }
530    }