001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.server.plugins;
021    
022    import com.google.common.base.*;
023    import com.google.common.collect.Lists;
024    import org.apache.commons.io.FileUtils;
025    import org.apache.commons.lang.CharEncoding;
026    import org.apache.commons.lang.StringUtils;
027    
028    import javax.annotation.Nullable;
029    import java.io.File;
030    import java.io.IOException;
031    import java.net.URL;
032    import java.net.URLDecoder;
033    import java.util.Collection;
034    import java.util.Enumeration;
035    import java.util.jar.JarEntry;
036    import java.util.jar.JarFile;
037    
038    /**
039     * @since 3.0
040     */
041    public final class ClassLoaderUtils {
042    
043      private ClassLoaderUtils() {
044      }
045    
046      public static File copyResources(ClassLoader classLoader, String rootPath, File toDir) {
047        return copyResources(classLoader, rootPath, toDir, Functions.<String>identity());
048      }
049    
050      public static File copyResources(ClassLoader classLoader, String rootPath, File toDir, Function<String, String> relocationFunction) {
051        Collection<String> relativePaths = listFiles(classLoader, rootPath);
052        for (String relativePath : relativePaths) {
053          URL resource = classLoader.getResource(relativePath);
054          String filename = relocationFunction.apply(relativePath);
055          File toFile = new File(toDir, filename);
056          try {
057            FileUtils.copyURLToFile(resource, toFile);
058          } catch (IOException e) {
059            throw new IllegalStateException("Fail to extract " + relativePath + " to " + toFile.getAbsolutePath());
060          }
061        }
062    
063        return toDir;
064      }
065    
066      /**
067       * Finds files within a given directory and its subdirectories
068       *
069       * @param classLoader
070       * @param rootPath    the root directory, for example org/sonar/sqale
071       * @return a list of relative paths, for example {"org/sonar/sqale/foo/bar.txt}. Never null.
072       */
073      public static Collection<String> listFiles(ClassLoader classLoader, String rootPath) {
074        return listResources(classLoader, rootPath, new Predicate<String>() {
075          public boolean apply(@Nullable String path) {
076            return !StringUtils.endsWith(path, "/");
077          }
078        });
079      }
080    
081    
082      public static Collection<String> listResources(ClassLoader classLoader, String rootPath) {
083        return listResources(classLoader, rootPath, Predicates.<String>alwaysTrue());
084      }
085    
086      /**
087       * Finds directories and files within a given directory and its subdirectories.
088       *
089       * @param classLoader
090       * @param rootPath    the root directory, for example org/sonar/sqale, or a file in this root directory, for example org/sonar/sqale/index.txt
091       * @param
092       * @return a list of relative paths, for example {"org/sonar/sqale", "org/sonar/sqale/foo", "org/sonar/sqale/foo/bar.txt}. Never null.
093       */
094      public static Collection<String> listResources(ClassLoader classLoader, String rootPath, Predicate<String> predicate) {
095        try {
096          Collection<String> paths = Lists.newArrayList();
097          rootPath = StringUtils.removeStart(rootPath, "/");
098    
099          URL root = classLoader.getResource(rootPath);
100          if (root != null) {
101            checkJarFile(root);
102    
103            // Path of the root directory
104            // Examples :
105            // org/sonar/sqale/index.txt  -> rootDirectory is org/sonar/sqale
106            // org/sonar/sqale/  -> rootDirectory is org/sonar/sqale
107            // org/sonar/sqale  -> rootDirectory is org/sonar/sqale
108            String rootDirectory = rootPath;
109            if (StringUtils.substringAfterLast(rootPath, "/").indexOf('.') >= 0) {
110              rootDirectory = StringUtils.substringBeforeLast(rootPath, "/");
111            }
112            String jarPath = root.getPath().substring(5, root.getPath().indexOf("!")); //strip out only the JAR file
113            JarFile jar = new JarFile(URLDecoder.decode(jarPath, CharEncoding.UTF_8));
114            Enumeration<JarEntry> entries = jar.entries();
115            while (entries.hasMoreElements()) {
116              String name = entries.nextElement().getName();
117              if (name.startsWith(rootDirectory) && predicate.apply(name)) {
118                paths.add(name);
119              }
120            }
121          }
122          return paths;
123        } catch (Exception e) {
124          throw Throwables.propagate(e);
125        }
126      }
127    
128      private static void checkJarFile(URL root) {
129        if (!"jar".equals(root.getProtocol())) {
130          throw new IllegalStateException("Unsupported protocol: " + root.getProtocol());
131        }
132      }
133    }