View Javadoc

1   /*
2    * Copyright 2007-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.maven.exceptions.runtime;
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.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import de.smartics.maven.exceptions.util.FileNameUtils;
32  
33  /**
34   * A class loader that serves classes from the given directories.
35   */
36  public class ProjectClassLoader extends AbstractProjectClassLoader
37  {
38    // ********************************* Fields *********************************
39  
40    // --- constants ------------------------------------------------------------
41  
42    // --- members --------------------------------------------------------------
43  
44    /**
45     * The list of root directories and Java archive files to search for classes.
46     */
47    private final List<File> rootDirectories;
48  
49    // ****************************** Initializer *******************************
50  
51    // ****************************** Constructors ******************************
52  
53    /**
54     * Convenience constructor using the {@link
55     * Thread.currentThread().getContextClassLoader()}.
56     *
57     * @param rootDirectories the list of root directories to search for classes.
58     */
59    public ProjectClassLoader(final List<File> rootDirectories)
60    {
61      this(Thread.currentThread().getContextClassLoader(), rootDirectories);
62    }
63  
64    /**
65     * Convenience constructor to specify the directories as {@link String}s and
66     * using the {@link Thread.currentThread().getContextClassLoader()}.
67     *
68     * @param rootDirectoryNames the list of root directory names to search for
69     *          classes.
70     */
71    public ProjectClassLoader(final Collection<String> rootDirectoryNames)
72    {
73      this(initDirectories(LogFactory.getLog(ProjectClassLoader.class),
74          rootDirectoryNames));
75    }
76  
77    @Override
78    protected URL findResource(final String name)
79    {
80      for (File directoryOrArchive : rootDirectories)
81      {
82        try
83        {
84          if (directoryOrArchive.isDirectory())
85          {
86            final File resourceFile = new File(directoryOrArchive, name);
87            if (resourceFile.canRead())
88            {
89              return resourceFile.toURI().toURL();
90            }
91          }
92          else
93          {
94            String resourceName = name;
95            final int index = name.lastIndexOf('.');
96            if (index != -1)
97            {
98              resourceName = name.substring(0, index);
99            }
100           resourceName = resourceName.replace('/', '.');
101           return loadResourceFromLibrary(resourceName, name, directoryOrArchive);
102         }
103       }
104       catch (final IOException e)
105       {
106         // Ignore this and try the next location...
107       }
108     }
109     return super.findResource(name);
110   }
111 
112   @Override
113   protected Enumeration<URL> findResources(final String name)
114   {
115     return new Enumeration<URL>()
116     {
117       private URL element = findResource(name);
118 
119       public boolean hasMoreElements()
120       {
121         return this.element != null;
122       }
123 
124       public URL nextElement()
125       {
126         if (this.element != null)
127         {
128           final URL element = this.element;
129           this.element = null;
130           return element;
131         }
132         throw new NoSuchElementException();
133       }
134     };
135   }
136 
137   /**
138    * Convenience constructor to specify the directories as {@link String}s.
139    *
140    * @param parent the parent class loader.
141    * @param rootDirectoryNames the list of root directory names to search for
142    *          classes.
143    */
144   public ProjectClassLoader(final ClassLoader parent,
145       final Collection<String> rootDirectoryNames)
146   {
147     this(parent, initDirectories(LogFactory.getLog(ProjectClassLoader.class),
148         rootDirectoryNames));
149   }
150 
151   /**
152    * Default constructor.
153    *
154    * @param parent the parent class loader.
155    * @param rootDirectories the list of root directories to search for classes.
156    */
157   public ProjectClassLoader(final ClassLoader parent,
158       final List<File> rootDirectories)
159   {
160     super(parent);
161     this.rootDirectories = Collections.unmodifiableList(rootDirectories);
162   }
163 
164   // ****************************** Inner Classes *****************************
165 
166   // ********************************* Methods ********************************
167 
168   // --- init -----------------------------------------------------------------
169 
170   /**
171    * Checks the directories for accessibility as root directories. Any directory
172    * that is not accessible is dismissed and a debug message is logged.
173    *
174    * @param log the logger to use for logging debug messages.
175    * @param directoryNames the collection of directory names to analyze.
176    * @return the valid directories.
177    */
178   private static List<File> initDirectories(final Log log,
179       final Collection<String> directoryNames)
180   {
181     final List<File> directories = new ArrayList<File>(directoryNames.size());
182     for (final String directoryName : directoryNames)
183     {
184       final File directory = new File(directoryName);
185       if (directory.canRead()
186           && (directory.isDirectory() || directory.isFile()
187                                          && FileNameUtils
188                                              .isArchive(directoryName)))
189       {
190         directories.add(directory);
191       }
192       else
193       {
194         if (log.isDebugEnabled())
195         {
196           final String message =
197               "Cannot access '" + directoryName
198                   + "' as directory or Java archive."
199                   + " Ignoring as classpath root.";
200           log.debug(message);
201         }
202       }
203     }
204     return directories;
205   }
206 
207   // --- get&set --------------------------------------------------------------
208 
209   // --- business -------------------------------------------------------------
210 
211   /**
212    * {@inheritDoc}
213    */
214   @Override
215   protected Class<?> findClass(final String name) throws ClassNotFoundException
216   {
217     for (File directoryOrArchive : rootDirectories)
218     {
219       try
220       {
221         final String fileName = name.replace('.', '/') + ".class";
222         if (directoryOrArchive.isDirectory())
223         {
224           final File classFile = new File(directoryOrArchive, fileName);
225           if (classFile.canRead())
226           {
227             return loadClassFile(name, classFile);
228           }
229         }
230         else
231         {
232           return loadClassFromLibrary(name, fileName, directoryOrArchive);
233         }
234       }
235       catch (final ClassNotFoundException e)
236       {
237         // Ignore this and try the next location...
238       }
239     }
240     return super.findClass(name);
241   }
242 
243   // --- object basics --------------------------------------------------------
244 
245 }