View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.properties.utils;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.net.URL;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.Enumeration;
25  import java.util.List;
26  import java.util.NoSuchElementException;
27  
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * A class loader that serves classes from the given directories.
33   */
34  public final class ProjectClassLoader extends AbstractProjectClassLoader
35  { // NOPMD
36    // ********************************* Fields *********************************
37  
38    // --- constants ------------------------------------------------------------
39  
40    // --- members --------------------------------------------------------------
41  
42    /**
43     * The list of root directories and Java archive files to search for classes.
44     */
45    private final List<File> rootDirectories;
46  
47    // ****************************** Initializer *******************************
48  
49    // ****************************** Constructors ******************************
50  
51    /**
52     * Default constructor.
53     *
54     * @param parent the parent class loader.
55     * @param rootDirectories the list of root directories to search for classes.
56     */
57    private ProjectClassLoader(final ClassLoader parent,
58        final List<File> rootDirectories)
59    {
60      super(parent);
61      this.rootDirectories = Collections.unmodifiableList(rootDirectories);
62    }
63  
64    // ****************************** Inner Classes *****************************
65  
66    // ********************************* Methods ********************************
67  
68    // --- init -----------------------------------------------------------------
69  
70    /**
71     * Checks the directories for accessibility as root directories. Any directory
72     * that is not accessible is dismissed and a debug message is logged.
73     *
74     * @param log the logger to use for logging debug messages.
75     * @param directoryNames the collection of directory names to analyze.
76     * @return the valid directories.
77     */
78    private static List<File> initDirectories(final Logger log,
79        final Collection<String> directoryNames)
80    {
81      final List<File> directories = new ArrayList<File>(directoryNames.size());
82      for (final String directoryName : directoryNames)
83      {
84        final File directory = new File(directoryName);
85        if (directory.canRead()
86            && (directory.isDirectory() || directory.isFile()
87                                           && isArchive(directoryName)))
88        {
89          directories.add(directory);
90        }
91        else
92        {
93          if (log.isDebugEnabled())
94          {
95            final String message =
96                "Cannot access '" + directoryName
97                    + "' as directory or Java archive."
98                    + " Ignoring as classpath root.";
99            log.debug(message);
100         }
101       }
102     }
103     return directories;
104   }
105 
106   /**
107    * Checks if the given root refers to an archive or not. Simply the file
108    * extension is checked. Accessibility et al. is not considered.
109    *
110    * @param root the root to check.
111    * @return <code>true</code> if root refers to an archive file,
112    *         <code>false</code> otherwise.
113    * @throws NullPointerException if <code>root</code> is <code>null</code>.
114    */
115   private static boolean isArchive(final String root)
116     throws NullPointerException
117   {
118     return root.endsWith(".jar") || root.endsWith(".zip");
119   }
120 
121   // --- create ---------------------------------------------------------------
122 
123   /**
124    * Convenience factory method using the
125    * <code>Thread.currentThread().getContextClassLoader()</code>.
126    *
127    * @param rootDirectories the list of root directories to search for classes.
128    * @return the requested instance.
129    */
130   public static ProjectClassLoader create(final List<File> rootDirectories)
131   {
132     return create(Thread.currentThread().getContextClassLoader(),
133         rootDirectories);
134   }
135 
136   /**
137    * Convenience factory method to specify the directories as {@link String}s
138    * and using the <code>Thread.currentThread().getContextClassLoader()</code>.
139    *
140    * @param rootDirectoryNames the list of root directory names to search for
141    *          classes.
142    * @return the requested instance.
143    */
144   public static ProjectClassLoader createFromNames(
145       final Collection<String> rootDirectoryNames)
146   {
147     return create(initDirectories(
148         LoggerFactory.getLogger(ProjectClassLoader.class), rootDirectoryNames));
149   }
150 
151   /**
152    * Convenience factory method to specify the directories as {@link String}s.
153    *
154    * @param parent the parent class loader.
155    * @param rootDirectoryNames the list of root directory names to search for
156    *          classes.
157    * @return the requested instance.
158    */
159   public static ProjectClassLoader createFromNames(final ClassLoader parent,
160       final Collection<String> rootDirectoryNames)
161   {
162     return new ProjectClassLoader(parent, (List<File>) initDirectories(
163         LoggerFactory.getLogger(ProjectClassLoader.class), rootDirectoryNames));
164   }
165 
166   /**
167    * Default factory method.
168    *
169    * @param parent the parent class loader.
170    * @param rootDirectories the list of root directories to search for classes.
171    * @return the requested instance.
172    */
173   public static ProjectClassLoader create(final ClassLoader parent,
174       final List<File> rootDirectories)
175   {
176     return new ProjectClassLoader(parent, rootDirectories);
177   }
178 
179   // --- get&set --------------------------------------------------------------
180 
181   // --- business -------------------------------------------------------------
182 
183   @Override
184   protected URL findResource(final String name)
185   {
186     for (File directoryOrArchive : rootDirectories)
187     {
188       try
189       {
190         if (directoryOrArchive.isDirectory())
191         {
192           final File resourceFile = new File(directoryOrArchive, name);
193           if (resourceFile.canRead())
194           {
195             return resourceFile.toURI().toURL();
196           }
197         }
198         else
199         {
200           String resourceName = name;
201           final int index = name.lastIndexOf('.');
202           if (index != -1)
203           {
204             resourceName = name.substring(0, index);
205           }
206           resourceName = resourceName.replace('/', '.');
207           return loadResourceFromLibrary(resourceName, name, directoryOrArchive);
208         }
209       }
210       catch (final IOException e)
211       {
212         // Ignore this and try the next location...
213       }
214     }
215     return super.findResource(name);
216   }
217 
218   @Override
219   protected Enumeration<URL> findResources(final String name)
220   {
221     return new Enumeration<URL>()
222     {
223       private URL element = findResource(name);
224 
225       public boolean hasMoreElements()
226       {
227         return this.element != null;
228       }
229 
230       public URL nextElement()
231       {
232         if (this.element != null)
233         {
234           final URL element = this.element;
235           this.element = null;
236           return element;
237         }
238         throw new NoSuchElementException();
239       }
240     };
241   }
242 
243   /**
244    * {@inheritDoc}
245    */
246   @Override
247   protected Class<?> findClass(final String name) throws ClassNotFoundException
248   {
249     for (File directoryOrArchive : rootDirectories)
250     {
251       try
252       {
253         final String fileName = name.replace('.', '/') + ".class";
254         if (directoryOrArchive.isDirectory())
255         {
256           final File classFile = new File(directoryOrArchive, fileName);
257           if (classFile.canRead())
258           {
259             return loadClassFile(name, classFile);
260           }
261         }
262         else
263         {
264           return loadClassFromLibrary(name, fileName, directoryOrArchive);
265         }
266       }
267       catch (final ClassNotFoundException e)
268       {
269         // Ignore this and try the next location...
270       }
271     }
272     return super.findClass(name);
273   }
274 
275   // --- object basics --------------------------------------------------------
276 
277 }