View Javadoc

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