001package org.gbif.utils.file;
002
003import java.io.File;
004import java.io.FileOutputStream;
005import java.io.IOException;
006import java.io.OutputStream;
007import java.net.URISyntaxException;
008import java.net.URL;
009import java.net.URLDecoder;
010import java.util.Enumeration;
011import java.util.HashSet;
012import java.util.Set;
013import java.util.jar.JarEntry;
014import java.util.jar.JarFile;
015
016import com.google.common.io.Closer;
017import com.google.common.io.Files;
018import com.google.common.io.Resources;
019import org.apache.commons.lang3.StringUtils;
020import org.slf4j.Logger;
021import org.slf4j.LoggerFactory;
022
023/**
024 * Utils class dealing with classpath resources in addition to guavas {@link Resources}.
025 */
026public class ResourcesUtil {
027
028  private static final Logger LOG = LoggerFactory.getLogger(ResourcesUtil.class);
029
030  /**
031   * Static utils class.
032   */
033  private ResourcesUtil() {
034
035  }
036
037  /**
038   * Copies classpath resources to real files.
039   *
040   * @param folder                 to copy resource files into
041   * @param ignoreMissingResources if true ignores missing resources, throws IOException otherwise
042   * @param classpathPrefix        common prefix added to all classpath resources which is not used for the result file
043   *                               path.
044   * @param classpathResources     list of classpath resources to be copied into folder
045   */
046  public static void copy(File folder, String classpathPrefix, boolean ignoreMissingResources,
047    String... classpathResources) throws IOException {
048    for (String classpathResource : classpathResources) {
049      String res = classpathPrefix + classpathResource;
050      URL url = null;
051      try {
052        url = Resources.getResource(res);
053        if (url == null) {
054          throw new IllegalArgumentException("Classpath resource " + res + " not existing");
055        }
056      } catch (IllegalArgumentException e) {
057        if (ignoreMissingResources) {
058          LOG.debug("Resource {} not found", res);
059          continue;
060        }
061        throw new IOException(e);
062      }
063
064      Closer closer = Closer.create();
065      try {
066        File f = new File(folder, classpathResource);
067        Files.createParentDirs(f);
068        OutputStream out = closer.register(new FileOutputStream(f));
069        Resources.copy(url, out);
070      } catch (Throwable e) {
071        // must catch Throwable for closer to work, see https://code.google.com/p/guava-libraries/wiki/ClosingResourcesExplained
072        throw closer.rethrow(e);
073      } finally {
074        closer.close();
075      }
076    }
077  }
078
079    /**
080     * List directory contents for a resource folder. Not recursive.
081     * Works for regular files and also JARs.
082     *
083     * Based on code from Greg Briggs, slightly modified.
084     *
085     * @param clazz Any java class that lives in the same place as the resources you want.
086     * @param path Should end with "/", but not start with one.
087     * @return Just the name of each member item, not the full paths. Empty array in case folder cannot be found
088     * @throws IOException
089     */
090    public static String[] list(Class clazz, String path) throws IOException {
091        if (!path.endsWith("/")) {
092            path = path + "/";
093        }
094        URL dirURL = clazz.getClassLoader().getResource(path);
095        if (dirURL != null && dirURL.getProtocol().equals("file")) {
096        /* A file path: easy enough */
097            try {
098                return new File(dirURL.toURI()).list();
099            } catch (URISyntaxException e) {
100                throw new IOException("Bad URI. Cannot list files for path " + path + " in class " + clazz, e);
101            }
102        }
103
104        if (dirURL == null) {
105        /*
106         * In case of a jar file, we can't actually find a directory.
107         * Have to assume the same jar as clazz.
108         */
109            String me = clazz.getName().replace(".", "/")+".class";
110            dirURL = clazz.getClassLoader().getResource(me);
111        }
112
113        if (dirURL.getProtocol().equals("jar")) {
114        /* A JAR path */
115            String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); //strip out only the JAR file
116            JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
117            Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
118            Set<String> result = new HashSet<String>(); //avoid duplicates in case it is a subdirectory
119            while(entries.hasMoreElements()) {
120                String name = entries.nextElement().getName();
121                if (name.startsWith(path)) { //filter according to the path
122                    String entry = name.substring(path.length());
123                    if (!StringUtils.isBlank(entry)) {
124                        int checkSubdir = entry.indexOf("/");
125                        if (checkSubdir >= 0) {
126                            // if it is a subdirectory, we just return the directory name
127                            entry = entry.substring(0, checkSubdir);
128                        }
129                        result.add(entry);
130                    }
131                }
132            }
133            return result.toArray(new String[result.size()]);
134        }
135
136        return new String[]{};
137    }
138
139}