001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019
020package icy.plugin.classloader;
021
022import java.io.IOException;
023import java.io.InputStream;
024import java.net.URL;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Enumeration;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Set;
031import java.util.logging.Level;
032import java.util.logging.Logger;
033
034/**
035 * Abstract class loader that can load classes from different resources
036 * 
037 * @author Kamran Zafar
038 * @author Stephane Dallongeville
039 */
040@SuppressWarnings("unchecked")
041public abstract class AbstractClassLoader extends ClassLoader
042{
043    protected final List<ProxyClassLoader> loaders = new ArrayList<ProxyClassLoader>();
044
045    private final ProxyClassLoader systemLoader = new SystemLoader();
046    private final ProxyClassLoader parentLoader = new ParentLoader();
047    private final ProxyClassLoader currentLoader = new CurrentLoader();
048    private final ProxyClassLoader threadLoader = new ThreadContextLoader();
049
050    /**
051     * Build a new instance of AbstractClassLoader.java.
052     * 
053     * @param parent
054     *        parent class loader
055     */
056    public AbstractClassLoader(ClassLoader parent)
057    {
058        super(parent);
059
060        addDefaultLoaders();
061    }
062
063    public void addLoader(ProxyClassLoader loader)
064    {
065        loaders.add(loader);
066
067        Collections.sort(loaders);
068    }
069
070    protected void addDefaultLoaders()
071    {
072        // always add this one
073        loaders.add(systemLoader);
074        loaders.add(parentLoader);
075        loaders.add(currentLoader);
076        loaders.add(threadLoader);
077
078        Collections.sort(loaders);
079    }
080
081    @Override
082    public Class loadClass(String className) throws ClassNotFoundException
083    {
084        return loadClass(className, true);
085    }
086
087    /**
088     * Overrides the loadClass method to load classes from other resources,
089     * JarClassLoader is the only subclass in this project that loads classes
090     * from jar files
091     * 
092     * @see java.lang.ClassLoader#loadClass(String, boolean)
093     */
094    @Override
095    public Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException
096    {
097        if (className == null || className.trim().equals(""))
098            return null;
099
100        final List<ProxyClassLoader> loadersDone = new ArrayList<ProxyClassLoader>();
101
102        for (ProxyClassLoader l : loaders)
103        {
104            // don't search in same loader
105            if (l.isEnabled() && !loadersDone.contains(l))
106            {
107                final Class clazz = l.loadClass(className, resolveIt);
108                if (clazz != null)
109                    return clazz;
110
111                // loader done
112                loadersDone.add(l);
113            }
114        }
115
116        throw new ClassNotFoundException(className);
117    }
118
119    /**
120     * Overrides the getResourceAsStream method to load non-class resources from
121     * other sources, JarClassLoader is the only subclass in this project that
122     * loads non-class resources from jar files
123     * 
124     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
125     */
126    @Override
127    public InputStream getResourceAsStream(String name)
128    {
129        if (name == null || name.trim().equals(""))
130            return null;
131
132        final List<ProxyClassLoader> loadersDone = new ArrayList<ProxyClassLoader>();
133
134        for (ProxyClassLoader l : loaders)
135        {
136            // don't search in same loader
137            if (l.isEnabled() && !loadersDone.contains(l))
138            {
139                final InputStream is = l.getResourceAsStream(name);
140                if (is != null)
141                    return is;
142
143                // loader done
144                loadersDone.add(l);
145            }
146        }
147
148        return null;
149    }
150
151    @Override
152    public URL getResource(String name)
153    {
154        if (name == null || name.trim().equals(""))
155            return null;
156
157        final List<ProxyClassLoader> loadersDone = new ArrayList<ProxyClassLoader>();
158
159        for (ProxyClassLoader l : loaders)
160        {
161            // don't search in same loader
162            if (l.isEnabled() && !loadersDone.contains(l))
163            {
164                final URL url = l.getResource(name);
165                if (url != null)
166                    return url;
167
168                // loader done
169                loadersDone.add(l);
170            }
171        }
172
173        return null;
174    }
175
176    @Override
177    public Enumeration<URL> getResources(String name) throws IOException
178    {
179        if (name == null || name.trim().equals(""))
180            return null;
181
182        final Set<URL> result = new HashSet<URL>();
183        final List<ProxyClassLoader> loadersDone = new ArrayList<ProxyClassLoader>();
184
185        for (ProxyClassLoader l : loaders)
186        {
187            // don't search in same loader
188            if (l.isEnabled() && !loadersDone.contains(l))
189            {
190                final Enumeration<URL> urls = l.getResources(name);
191
192                if (urls != null)
193                {
194                    // avoid duplicate using Set here
195                    while (urls.hasMoreElements())
196                        result.add(urls.nextElement());
197                }
198
199                // loader done
200                loadersDone.add(l);
201            }
202        }
203
204        return Collections.enumeration(result);
205    }
206
207    /**
208     * System class loader
209     */
210    class SystemLoader extends ProxyClassLoader
211    {
212        private final Logger logger = Logger.getLogger(SystemLoader.class.getName());
213
214        public SystemLoader()
215        {
216            super(10);
217
218            enabled = Configuration.isSystemLoaderEnabled();
219        }
220
221        @Override
222        public ClassLoader getLoader()
223        {
224            return getSystemClassLoader();
225        }
226
227        @Override
228        public Class loadClass(String className, boolean resolveIt)
229        {
230            Class result;
231
232            try
233            {
234                result = findSystemClass(className);
235            }
236            catch (ClassNotFoundException e)
237            {
238                return null;
239            }
240
241            if (logger.isLoggable(Level.FINEST))
242                logger.finest("Returning system class " + className);
243
244            return result;
245        }
246
247        @Override
248        public InputStream getResourceAsStream(String name)
249        {
250            InputStream is = getSystemResourceAsStream(name);
251
252            if (is != null)
253            {
254                if (logger.isLoggable(Level.FINEST))
255                    logger.finest("Returning system resource " + name);
256
257                return is;
258            }
259
260            return null;
261        }
262
263        @Override
264        public URL getResource(String name)
265        {
266            URL url = getSystemResource(name);
267
268            if (url != null)
269            {
270                if (logger.isLoggable(Level.FINEST))
271                    logger.finest("Returning system resource " + name);
272
273                return url;
274            }
275
276            return null;
277        }
278
279        @Override
280        public Enumeration<URL> getResources(String name) throws IOException
281        {
282            Enumeration<URL> urls = getSystemResources(name);
283
284            if ((urls != null) && urls.hasMoreElements())
285            {
286                if (logger.isLoggable(Level.FINEST))
287                    logger.finest("Returning system resources " + name);
288
289                return urls;
290            }
291
292            return null;
293        }
294    }
295
296    /**
297     * Parent class loader
298     */
299    class ParentLoader extends ProxyClassLoader
300    {
301        private final Logger logger = Logger.getLogger(ParentLoader.class.getName());
302
303        public ParentLoader()
304        {
305            super(30);
306
307            enabled = Configuration.isParentLoaderEnabled();
308        }
309
310        @Override
311        public ClassLoader getLoader()
312        {
313            return getParent();
314        }
315
316        @Override
317        public Class loadClass(String className, boolean resolveIt)
318        {
319            Class result;
320
321            try
322            {
323                result = getParent().loadClass(className);
324            }
325            catch (ClassNotFoundException e)
326            {
327                return null;
328            }
329
330            if (logger.isLoggable(Level.FINEST))
331                logger.finest("Returning class " + className + " loaded with parent classloader");
332
333            return result;
334        }
335
336        @Override
337        public InputStream getResourceAsStream(String name)
338        {
339            InputStream is = getParent().getResourceAsStream(name);
340
341            if (is != null)
342            {
343                if (logger.isLoggable(Level.FINEST))
344                    logger.finest("Returning resource " + name + " loaded with parent classloader");
345
346                return is;
347            }
348            return null;
349        }
350
351        @Override
352        public URL getResource(String name)
353        {
354            URL url = getParent().getResource(name);
355
356            if (url != null)
357            {
358                if (logger.isLoggable(Level.FINEST))
359                    logger.finest("Returning resource " + name + " loaded with parent classloader");
360
361                return url;
362            }
363
364            return null;
365        }
366
367        @Override
368        public Enumeration<URL> getResources(String name) throws IOException
369        {
370            Enumeration<URL> urls = getParent().getResources(name);
371
372            if ((urls != null) && urls.hasMoreElements())
373            {
374                if (logger.isLoggable(Level.FINEST))
375                    logger.finest("Returning resource " + name + " loaded with parent classloader");
376
377                return urls;
378            }
379
380            return null;
381        }
382    }
383
384    /**
385     * Current class loader
386     */
387    class CurrentLoader extends ProxyClassLoader
388    {
389        private final Logger logger = Logger.getLogger(CurrentLoader.class.getName());
390
391        public CurrentLoader()
392        {
393            super(20);
394
395            enabled = Configuration.isCurrentLoaderEnabled();
396        }
397
398        @Override
399        public ClassLoader getLoader()
400        {
401            return getClass().getClassLoader();
402        }
403
404        @Override
405        public Class loadClass(String className, boolean resolveIt)
406        {
407            Class result;
408
409            try
410            {
411                result = getClass().getClassLoader().loadClass(className);
412            }
413            catch (ClassNotFoundException e)
414            {
415                return null;
416            }
417
418            if (logger.isLoggable(Level.FINEST))
419                logger.finest("Returning class " + className + " loaded with current classloader");
420
421            return result;
422        }
423
424        @Override
425        public InputStream getResourceAsStream(String name)
426        {
427            InputStream is = getClass().getClassLoader().getResourceAsStream(name);
428
429            if (is != null)
430            {
431                if (logger.isLoggable(Level.FINEST))
432                    logger.finest("Returning resource " + name + " loaded with current classloader");
433
434                return is;
435            }
436
437            return null;
438        }
439
440        @Override
441        public URL getResource(String name)
442        {
443            URL url = getClass().getClassLoader().getResource(name);
444
445            if (url != null)
446            {
447                if (logger.isLoggable(Level.FINEST))
448                    logger.finest("Returning resource " + name + " loaded with current classloader");
449
450                return url;
451            }
452
453            return null;
454        }
455
456        @Override
457        public Enumeration<URL> getResources(String name) throws IOException
458        {
459            Enumeration<URL> urls = getClass().getClassLoader().getResources(name);
460
461            if ((urls != null) && (urls.hasMoreElements()))
462            {
463                if (logger.isLoggable(Level.FINEST))
464                    logger.finest("Returning resources " + name + " loaded with current classloader");
465
466                return urls;
467            }
468
469            return null;
470        }
471
472    }
473
474    /**
475     * Current class loader
476     */
477    class ThreadContextLoader extends ProxyClassLoader
478    {
479        private final Logger logger = Logger.getLogger(ThreadContextLoader.class.getName());
480
481        public ThreadContextLoader()
482        {
483            super(40);
484
485            enabled = Configuration.isThreadContextLoaderEnabled();
486        }
487
488        @Override
489        public ClassLoader getLoader()
490        {
491            return Thread.currentThread().getContextClassLoader();
492        }
493
494        @Override
495        public Class loadClass(String className, boolean resolveIt)
496        {
497            Class result;
498            try
499            {
500                result = Thread.currentThread().getContextClassLoader().loadClass(className);
501            }
502            catch (ClassNotFoundException e)
503            {
504                return null;
505            }
506
507            if (logger.isLoggable(Level.FINEST))
508                logger.finest("Returning class " + className + " loaded with thread context classloader");
509
510            return result;
511        }
512
513        @Override
514        public InputStream getResourceAsStream(String name)
515        {
516            InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
517
518            if (is != null)
519            {
520                if (logger.isLoggable(Level.FINEST))
521                    logger.finest("Returning resource " + name + " loaded with thread context classloader");
522
523                return is;
524            }
525
526            return null;
527        }
528
529        @Override
530        public URL getResource(String name)
531        {
532            URL url = Thread.currentThread().getContextClassLoader().getResource(name);
533
534            if (url != null)
535            {
536                if (logger.isLoggable(Level.FINEST))
537                    logger.finest("Returning resource " + name + " loaded with thread context classloader");
538
539                return url;
540            }
541
542            return null;
543        }
544
545        @Override
546        public Enumeration<URL> getResources(String name) throws IOException
547        {
548            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
549
550            if ((urls != null) && (urls.hasMoreElements()))
551            {
552                if (logger.isLoggable(Level.FINEST))
553                    logger.finest("Returning resources " + name + " loaded with thread context classloader");
554
555                return urls;
556            }
557
558            return null;
559        }
560    }
561
562    public ProxyClassLoader getSystemLoader()
563    {
564        return systemLoader;
565    }
566
567    public ProxyClassLoader getParentLoader()
568    {
569        return parentLoader;
570    }
571
572    public ProxyClassLoader getCurrentLoader()
573    {
574        return currentLoader;
575    }
576
577    public ProxyClassLoader getThreadLoader()
578    {
579        return threadLoader;
580    }
581}