001package net.gdface.utils;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.FilenameFilter;
006import java.io.IOException;
007import java.io.InputStream;
008import java.net.URI;
009import java.net.URL;
010import java.net.URLDecoder;
011import java.util.ArrayList;
012import java.util.Arrays;
013import java.util.Enumeration;
014import java.util.Iterator;
015import java.util.LinkedList;
016import java.util.List;
017import java.util.jar.JarEntry;
018import java.util.jar.JarFile;
019
020/**
021 * classpath resource 工具
022 * @author guyadong
023 *
024 */
025public class ClassResourceUtils {
026
027        /**
028         * 文件名过滤器接口
029         * @author guyadong
030         *
031         */
032        public static interface FileFilter{
033                /**
034                 * Tests if a specified file should be included in a file list.
035                 * @param filename
036                 * @return
037                 */
038                boolean accept(String filename);
039        }
040        /**
041         * 参见 {@link #getResourceFileList(Class, String)},<br>
042         * {@link IOException}封装为{@link RuntimeException},
043         * @param clazz Class to use when getting the System classloader
044         * @param path
045         * @return
046         */
047        public static List<String> getFilesUnchedked(Class<?> clazz, String path){
048                try {
049                        return getResourceFileList(ClassResourceUtils.class,path);
050                } catch (IOException e) {
051                        throw new RuntimeException(e);
052                }
053        }
054        /**
055         * 
056         * @param clazz Class to use when getting the System classloader
057         * @param path
058         * @param filter A filename filter
059         * @return
060         * @see #getFilesUnchedked(Class, String)
061         */
062        public static List<String> getFilesUnchecked(Class<?> clazz,String path, FileFilter filter){
063                List<String> list = getFilesUnchedked(clazz, path);
064                if(null != filter){
065                        for(Iterator<String> itor = list.iterator();itor.hasNext();){
066                                String file = itor.next();
067                                if(!filter.accept(file)){
068                                        itor.remove();
069                                }
070                        }
071                }
072                return list;
073        }
074        /**
075         * Returns true if resource exist.
076         * @param clazz Class to use when getting the System classloader
077         * @param resource
078         * @return
079         */
080        public static boolean resourceExist(Class<?> clazz, String resource){
081                return getResource(clazz,resource)!=null;
082        }
083
084        /**
085     * Return a context-relative path, beginning with a "/", that represents
086     * the canonical version of the specified path after ".." and "." elements
087     * are resolved out.  If the specified path attempts to go outside the
088     * boundaries of the current context (i.e. too many ".." path elements
089     * are present), return <code>null</code> instead.
090     *
091     * @param path Path to be normalized
092     * @return String normalized path
093     */
094    public static String normalizePath(String path)
095    {
096        // Normalize the slashes and add leading slash if necessary
097        String normalized = path;
098        if (normalized.indexOf('\\') >= 0)
099        {
100            normalized = normalized.replace('\\', '/');
101        }
102
103        if (!normalized.startsWith("/"))
104        {
105            normalized = "/" + normalized;
106        }
107
108        // Resolve occurrences of "//" in the normalized path
109        while (true)
110        {
111            int index = normalized.indexOf("//");
112            if (index < 0){
113                break;
114            }
115            normalized = normalized.substring(0, index) +
116            normalized.substring(index + 1);
117        }
118
119        // Resolve occurrences of "%20" in the normalized path
120        while (true)
121        {
122            int index = normalized.indexOf("%20");
123            if (index < 0){
124                break;
125            }
126            normalized =new StringBuilder()
127                .append(normalized.substring(0, index))
128                .append(" ")
129                .append(normalized.substring(index + 3)).toString();
130        }
131
132        // Resolve occurrences of "/./" in the normalized path
133        while (true)
134        {
135            int index = normalized.indexOf("/./");
136            if (index < 0){
137                break;
138            }
139            normalized = normalized.substring(0, index) +
140            normalized.substring(index + 2);
141        }
142
143        // Resolve occurrences of "/../" in the normalized path
144        while (true)
145        {
146            int index = normalized.indexOf("/../");
147            if (index < 0){
148                break;
149            }
150            if (index == 0){
151                // Trying to go outside our context
152                return (null);  
153            }
154            int index2 = normalized.lastIndexOf('/', index - 1);
155            normalized = normalized.substring(0, index2) +
156            normalized.substring(index + 3);
157        }
158
159        // Return the normalized path that we have completed
160        return (normalized);
161    }
162    /**
163     * 
164     * Return a normalized directory path, end with "/", but not start with one 
165     * @param path Path to be normalized
166     * @return Path to be normalized
167     * @see #normalizePath(String)
168     */
169    public static String normalizeDirPath(String path){
170        path = normalizePath(path);
171        if (path.startsWith("/"))
172        {
173            path = path.substring(1);
174        }
175        if(!path.endsWith("/")){
176                path += "/";
177        }
178        return path;
179    }
180    /**
181     * Returns an input stream for reading the specified resource.
182     * @param clazz Class to use when getting the System classloader (used if no Thread
183     * Context classloader available or fails to get resource).
184     * @param name name of the resource
185     * @return InputStream for the resource.
186     * @see #getResource(Class, String)
187     */
188    public static InputStream getResourceAsStream(Class<?> clazz, String name)
189    {
190        URL url = getResource(clazz,name);
191        try {
192            return url != null ? url.openStream() : null;
193        } catch (IOException e) {
194            return null;
195        }
196    }
197    /**
198     * Finds a resource with the given name.  Checks the Thread Context
199     * classloader, then uses the System classloader.  Should replace all
200     * calls to <code>Class.getResourceAsString</code> when the resource
201     * might come from a different classloader.  (e.g. a webapp).
202     * @param claz Class to use when getting the System classloader (used if no Thread
203     * Context classloader available or fails to get resource).
204     * @param path name of the resource
205     * @return  A <tt>URL</tt> object for reading the resource, or
206     *          <tt>null</tt> if the resource could not be found or the invoker
207     *          doesn't have adequate  privileges to get the resource.
208     */
209    public static URL getResource(Class<?> claz, String path)
210    {
211        URL result = null;
212        path = normalizePath(path);
213        /**
214         * remove leading slash so path will work with classes in a JAR file
215         */
216        path = path.substring(1);
217        ClassLoader classLoader = Thread.currentThread()
218                                    .getContextClassLoader();
219
220        if (classLoader == null)
221        {
222            classLoader = claz.getClassLoader();
223            result = classLoader.getResource( path );
224        }
225        else
226        {
227            result= classLoader.getResource( path );
228
229            /**
230            * for compatibility with texen / ant tasks, fall back to
231            * old method when resource is not found.
232            */
233
234            if (result == null)
235            {
236                classLoader = claz.getClassLoader();
237                if (classLoader != null){
238                    result = classLoader.getResource( path );
239                }
240            }
241        }
242        return result;
243
244    }
245    private static final FilenameFilter FILE_FILTER = new FilenameFilter(){
246                @Override
247                public boolean accept(File dir, String name) {
248                        return new File(dir,name).isFile();
249                }};
250        private static final String PROTOCOL_FILE = "file";
251        private static final String PROTOCOL_JAR = "jar";
252    /**
253     * List file names for a resource folder. Not recursive.
254     * This is basically a brute-force implementation.
255     * Works for regular files and also JARs.<br>
256     * refer to: <a href="http://www.uofr.net/~greg/java/get-resource-listing.html">Java: Listing the contents of a resource directory</a>
257     * @param clazz Any java class that lives in the same place as the resources you want.
258     * @param dirPath 
259     * @return Just the name of each member item, not the full paths.
260     * @throws IOException 
261     */
262    public static List<String> getResourceFileList(Class<?> clazz, String dirPath) throws IOException {
263        if(null == dirPath || dirPath.isEmpty()){
264                throw new IllegalArgumentException("path must not be null or empty");
265        }
266        dirPath = normalizeDirPath(dirPath);
267        URL dirURL = getResource(clazz,dirPath);
268        if(null == dirURL){
269                throw new FileNotFoundException(dirPath);
270        }
271        
272        if (PROTOCOL_FILE.equals(dirURL.getProtocol())) {
273          /* A file path: easy enough */
274          return new ArrayList<String>(Arrays.asList(new File(URI.create(dirURL.toString())).list(FILE_FILTER)));
275        } 
276   
277        if (PROTOCOL_JAR.equals(dirURL.getProtocol())) {
278                /* A JAR path */
279                //strip out only the JAR file
280                String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); 
281                JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
282                LinkedList<String> result = new LinkedList<String>();
283          try{
284                //gives ALL entries in jar
285                  Enumeration<JarEntry> entries = jar.entries(); 
286                  while(entries.hasMoreElements()) {
287                          JarEntry entry = entries.nextElement();
288                        // if it is a subdirectory, skip
289                          if(!entry.isDirectory()){
290                                  String name = entry.getName();
291                          if (name.startsWith(dirPath)) { 
292                                  //filter according to the path
293                                  String element = name.substring(dirPath.length());
294                                  int checkSubdir = element.indexOf("/");
295                                  if (checkSubdir < 0 ) {
296                                          result.add(element);
297                                  }
298                          }
299                          }                       
300                  }
301          }finally{
302                  jar.close();
303          }
304          return result;
305        }           
306        throw new UnsupportedOperationException("Cannot list files for URL "+dirURL);
307    }
308}