001/*
002 *  Copyright 2012 Chris Pheby
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.jadira.scanner.file.locator;
017
018import java.io.File;
019import java.io.IOException;
020import java.io.UnsupportedEncodingException;
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.net.URLDecoder;
024import java.util.ArrayList;
025import java.util.List;
026
027import org.jadira.scanner.core.api.Locator;
028import org.jadira.scanner.core.exception.FileAccessException;
029import org.jadira.scanner.core.utils.lang.IterableEnumeration;
030import org.jadira.scanner.core.utils.reflection.ClassLoaderUtils;
031
032/**
033 * Matches any Jars with a marker file indicated
034 */
035public class PackagesClasspathUrlLocator implements Locator<URL> {
036
037        private List<String> paths;
038
039        private final ClassLoader[] classLoaders;
040           
041        public PackagesClasspathUrlLocator(String markerFilePath, ClassLoader... classLoaders) {
042                this.paths = new ArrayList<String>(1);
043                paths.add(asNormalizedName(markerFilePath));
044                this.classLoaders = ClassLoaderUtils.getClassLoaders(classLoaders);
045        }
046        
047    public PackagesClasspathUrlLocator(List<String> markerFilePaths, ClassLoader... classLoaders) {
048                this.paths = new ArrayList<String>(markerFilePaths.size());
049                for (String next : markerFilePaths) {
050                    paths.add(asNormalizedName(next));
051                }
052                this.classLoaders = ClassLoaderUtils.getClassLoaders(classLoaders);
053        }
054        
055        @Override
056        public List<URL> locate() {
057
058        List<URL> list = new ArrayList<URL>();
059        
060        for (ClassLoader classLoader : classLoaders) {
061            try {
062                for (String nextPath : paths) {             
063                    for (URL nextResourceMatchedUrl : new IterableEnumeration<URL>(classLoader.getResources(nextPath))) {
064        
065                        String deploymentArchiveRoot = determineClasspathRootForResource(nextPath, nextResourceMatchedUrl);
066                        
067                        File fp = new File(deploymentArchiveRoot);
068                        
069                        if (!fp.exists()) {
070                            throw new FileAccessException("File unexpectedly does not exist: " + fp);
071                        }
072                        
073                        try {
074                            list.add(fp.toURI().toURL());
075                        } catch (MalformedURLException e) {
076                                throw new FileAccessException("Filepath unexpectedly malformed: " + fp.getPath(), e);
077                        }
078                    }
079                }
080            } catch (IOException e) {
081                throw new FileAccessException("Problem resolving deployment archives: " + e.getMessage(), e);
082            }
083        }
084                
085        return list;
086    }
087        
088    private String determineClasspathRootForResource(String nextResource, URL nextResourceMatchedUrl) {
089
090        String nextResourceMatchedPathName = nextResourceMatchedUrl.getFile();
091        try {
092            nextResourceMatchedPathName = URLDecoder.decode(nextResourceMatchedPathName, "UTF-8");
093        } catch (UnsupportedEncodingException e) {
094            // Never thrown for UTF-8
095            throw new FileAccessException("Exception thrown for Encoding when not expected: " + e.getMessage(), e);
096        }
097
098        // Reformat file urls to remove file: prefix
099        if (nextResourceMatchedPathName.startsWith("file:")) {
100            nextResourceMatchedPathName = nextResourceMatchedPathName.substring(5);
101        }
102
103        // Chomp archive name if an archive
104        if (nextResourceMatchedPathName.indexOf('!') > 0) {
105            nextResourceMatchedPathName = nextResourceMatchedPathName.substring(0, nextResourceMatchedPathName.indexOf('!'));
106        } else {
107            File indicatedResource = new File(nextResourceMatchedPathName);
108
109            // Traverse to classpath root relative to the original matching resource
110            int pathDepth = nextResource.replaceAll("[^/]", "").length();
111            for (int i = 0; i < pathDepth; i++) {
112                indicatedResource = indicatedResource.getParentFile();
113            }
114            nextResourceMatchedPathName = indicatedResource.getParent();
115        }
116
117        return nextResourceMatchedPathName;
118    }
119    
120    private static String asNormalizedName(String name) {
121     
122        String result = name;        
123        if (result != null) {
124            result = result.replace(".", "/");
125            result = result.replace("\\", "/");
126            if (result.startsWith("/")) {
127                result = result.substring(1);
128            }
129        }
130        return result;
131    }
132}