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 ContainedClassClasspathUrlLocator implements Locator<URL> {
036
037        private final List<String> paths;
038        
039        private final ClassLoader[] classLoaders;
040
041        public ContainedClassClasspathUrlLocator(Class<?> clazz, ClassLoader... classLoaders) {
042                this.paths = new ArrayList<String>(1);
043                paths.add(asNormalizedName(clazz));
044                
045                this.classLoaders = ClassLoaderUtils.getClassLoaders(classLoaders);
046        }
047        
048        public ContainedClassClasspathUrlLocator(List<Class<?>> classes, ClassLoader... classLoaders) {
049        this.paths = new ArrayList<String>(classes.size());
050        for (Class<?> next : classes) {
051            paths.add(asNormalizedName(next));
052        }
053        
054        this.classLoaders = ClassLoaderUtils.getClassLoaders(classLoaders);
055        }
056        
057        @Override
058        public List<URL> locate() {
059
060        List<URL> list = new ArrayList<URL>();
061        
062        for (ClassLoader classLoader : classLoaders) {
063            try {
064                for (String nextPath : paths) {
065                    for (URL nextResourceMatchedUrl : new IterableEnumeration<URL>(classLoader.getResources(nextPath))) {
066        
067                        String deploymentArchiveRoot = determineClasspathRootForResource(nextPath, nextResourceMatchedUrl);
068                        
069                        File fp = new File(deploymentArchiveRoot);
070                        
071                        if (!fp.exists()) {
072                            throw new FileAccessException("File unexpectedly does not exist: " + fp);
073                        }
074                        
075                        try {
076                            list.add(fp.toURI().toURL());
077                        } catch (MalformedURLException e) {
078                                throw new FileAccessException("Filepath unexpectedly malformed: " + fp.getPath(), e);
079                        }
080                    }
081                }
082            } catch (IOException e) {
083                throw new FileAccessException("Problem resolving deployment archives: " + e.getMessage(), e);
084            }
085        }
086                
087        return list;
088    }
089        
090    private String determineClasspathRootForResource(String nextResource, URL nextResourceMatchedUrl) {
091
092        String nextResourceMatchedPathName = nextResourceMatchedUrl.getFile();
093        try {
094            nextResourceMatchedPathName = URLDecoder.decode(nextResourceMatchedPathName, "UTF-8");
095        } catch (UnsupportedEncodingException e) {
096            // Never thrown for UTF-8
097            throw new FileAccessException("Exception thrown for Encoding when not expected: " + e.getMessage(), e);
098        }
099
100        // Reformat file urls to remove file: prefix
101        if (nextResourceMatchedPathName.startsWith("file:")) {
102            nextResourceMatchedPathName = nextResourceMatchedPathName.substring(5);
103        }
104
105        // Chomp archive name if an archive
106        if (nextResourceMatchedPathName.indexOf('!') > 0) {
107            nextResourceMatchedPathName = nextResourceMatchedPathName.substring(0, nextResourceMatchedPathName.indexOf('!'));
108        } else {
109            File indicatedResource = new File(nextResourceMatchedPathName);
110
111            // Traverse to classpath root relative to the original matching resource
112            int pathDepth = nextResource.replaceAll("[^/]", "").length();
113            for (int i = 0; i < pathDepth; i++) {
114                indicatedResource = indicatedResource.getParentFile();
115            }
116            nextResourceMatchedPathName = indicatedResource.getParent();
117        }
118
119        return nextResourceMatchedPathName;
120    }
121    
122    
123    private static String asNormalizedName(Class<?> clazz) {
124     
125        String result = clazz.getName().replace(".", "/") + ".class";
126        return result;
127    }
128}