View Javadoc

1   /*
2    * Copyright 2011-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.util.lang.classpath;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.net.URISyntaxException;
21  import java.net.URL;
22  import java.net.URLDecoder;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Enumeration;
26  import java.util.List;
27  import java.util.jar.JarEntry;
28  import java.util.jar.JarFile;
29  
30  import org.apache.commons.lang.StringUtils;
31  
32  import de.smartics.util.lang.Arg;
33  
34  /**
35   * Lists the content of a directory on the class path.
36   * <p>
37   * Currently only the protocols
38   * </p>
39   * <ol>
40   * <li>file system (<code>file</code>) and</li>
41   * <li>JAR files (<code>jar</code>)</li>
42   * </ol>
43   * <p>
44   * are supported.
45   * </p>
46   */
47  public class ClassPathDirectoryListing
48  {
49    // ********************************* Fields *********************************
50  
51    // --- constants ------------------------------------------------------------
52  
53    // --- members --------------------------------------------------------------
54  
55    /**
56     * The context to load the directory listings.
57     */
58    private final ClassPathContext context;
59  
60    // ****************************** Initializer *******************************
61  
62    // ****************************** Constructors ******************************
63  
64    /**
65     * Default constructor.
66     *
67     * @param classLoader the class loader to load the directory listings.
68     * @throws NullPointerException if {@code classLoader} is <code>null</code>.
69     */
70    public ClassPathDirectoryListing(final ClassLoader classLoader)
71      throws NullPointerException
72    {
73      this(new ClassPathContext(classLoader, null));
74    }
75  
76    /**
77     * Default constructor.
78     *
79     * @param context the context to load the directory listings.
80     * @throws NullPointerException if {@code classLoader} is <code>null</code>.
81     */
82    public ClassPathDirectoryListing(final ClassPathContext context)
83      throws NullPointerException
84    {
85      Arg.checkNotNull("context", context);
86      this.context = context;
87    }
88  
89    // ****************************** Inner Classes *****************************
90  
91    // ********************************* Methods ********************************
92  
93    // --- init -----------------------------------------------------------------
94  
95    // --- get&set --------------------------------------------------------------
96  
97    /**
98     * Returns the context to load the directory listings.
99     *
100    * @return the context to load the directory listings.
101    */
102   public final ClassPathContext getClassPathContext()
103   {
104     return context;
105   }
106 
107   // --- business -------------------------------------------------------------
108 
109   /**
110    * Lists the contents of the resource path.
111    *
112    * @param resourcePath the path to the resource whose contents is to be
113    *          listed. The empty string returns the contents of the class
114    *          loader's root directory (which is usually the parent class
115    *          loader's root).
116    * @return the contents of the resource as names. The list may be empty, but
117    *         is never <code>null</code>.
118    * @throws NullPointerException if {@code resourcePath} is <code>null</code>.
119    * @throws IllegalArgumentException if resource path cannot be resolved to
120    *           determine the contents. Either the protocol is unknown or there
121    *           is a problem to access the resource's content physically.
122    * @impl Sub classes may override this method to add additional protocol
123    *       handlers. Call this method, if the derived handler does not handle
124    *       the protocol.
125    */
126   public List<String> list(final String resourcePath)
127     throws NullPointerException, IllegalArgumentException
128   {
129     Arg.checkNotNull("resourcePath", resourcePath);
130 
131     final URL resourcePathUrl = context.getResource(resourcePath);
132     if (resourcePathUrl == null)
133     {
134       throw new IllegalArgumentException("Cannot find resource '"
135                                          + resourcePath + "' on class path.");
136     }
137 
138     final String protocol = resourcePathUrl.getProtocol();
139     if ("file".equals(protocol))
140     {
141       return handleFile(resourcePath, resourcePathUrl);
142     }
143     else if ("jar".equals(protocol))
144     {
145       return handleJar(resourcePath, resourcePathUrl);
146     }
147 
148     throw new IllegalArgumentException(
149         "Protocol '" + protocol + "' is not supported to resolve resource '"
150             + resourcePath + "'.");
151   }
152 
153   private List<String> handleFile(final String resourcePath,
154       final URL resourcePathUrl) throws IllegalArgumentException
155   {
156     try
157     {
158       final String[] contentsArray = new File(resourcePathUrl.toURI()).list();
159       return Arrays.asList(contentsArray);
160     }
161     catch (final URISyntaxException e)
162     {
163       throw new IllegalArgumentException(
164           "Cannot read URL derived from resource '" + resourcePath
165               + "' on the class path.", e);
166     }
167   }
168 
169   private List<String> handleJar(final String resourcePath,
170       final URL resourcePathUrl) throws IllegalArgumentException
171   {
172     try
173     {
174       final List<String> contents = new ArrayList<String>();
175 
176       final int separatorIndex = resourcePathUrl.getPath().indexOf('!');
177       final String jarPath =
178           resourcePathUrl.getPath().substring(5, separatorIndex);
179       final JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
180       traverseJarEntries(resourcePath, contents, jar);
181 
182       return contents;
183     }
184     catch (final IOException e)
185     {
186       throw new IllegalArgumentException("Read from JAR '" + resourcePath
187                                          + "'.", e);
188     }
189   }
190 
191   private void traverseJarEntries(final String resourcePath,
192       final List<String> contents, final JarFile jar)
193   {
194     final int resourcePathLength = resourcePath.length();
195 
196     final Enumeration<JarEntry> entries = jar.entries();
197     while (entries.hasMoreElements())
198     {
199       final String name = entries.nextElement().getName();
200       if (name.startsWith(resourcePath))
201       {
202         final String entry = name.substring(resourcePathLength);
203         final String normalized = normalize(entry);
204 
205         if (normalized != null && !contents.contains(normalized))
206         {
207           contents.add(normalized);
208         }
209       }
210     }
211   }
212 
213   private static String normalize(final String entry)
214   {
215     if (StringUtils.isBlank(entry))
216     {
217       return null;
218     }
219 
220     String normalized = entry;
221     if (normalized.charAt(0) == '/')
222     {
223       if (entry.length() == 1)
224       {
225         return null;
226       }
227       normalized = normalized.substring(1);
228     }
229 
230     final int subDirIndex = normalized.indexOf('/');
231     if (subDirIndex != -1)
232     {
233       normalized = normalized.substring(0, subDirIndex);
234     }
235 
236     return normalized;
237   }
238 
239   // --- object basics --------------------------------------------------------
240 
241 }