001    /*
002     * Licensed under the Apache License, Version 2.0 (the "License");
003     * you may not use this file except in compliance with the License.
004     * You may obtain a copy of the License at
005     *
006     *      http://www.apache.org/licenses/LICENSE-2.0
007     *
008     * Unless required by applicable law or agreed to in writing, software
009     * distributed under the License is distributed on an "AS IS" BASIS,
010     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011     * See the License for the specific language governing permissions and
012     * limitations under the License.
013     */
014    package org.apache.commons.classscan.builtin;
015    
016    import java.io.IOException;
017    import java.net.JarURLConnection;
018    import java.net.MalformedURLException;
019    import java.net.URI;
020    import java.net.URISyntaxException;
021    import java.net.URL;
022    import java.util.ArrayList;
023    import java.util.Collection;
024    import java.util.Enumeration;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.jar.Attributes;
028    import java.util.jar.JarEntry;
029    import java.util.jar.JarFile;
030    import java.util.jar.Manifest;
031    import java.util.regex.Pattern;
032    
033    import org.apache.commons.classscan.ClassFile;
034    import org.apache.commons.classscan.ResourceFile;
035    import org.apache.commons.classscan.spi.model.SpiClassPathElement;
036    import org.apache.commons.classscan.spi.model.SpiMetaRegistry;
037    import org.slf4j.Logger;
038    import org.slf4j.LoggerFactory;
039    
040    public class JarClassPathElement implements SpiClassPathElement {
041    
042        private static final Logger logger = LoggerFactory.getLogger(JarClassPathElement.class);
043    
044        final private URL location;
045        private URL[] classPathLocations;
046            
047            public JarClassPathElement(URL location) {
048                    this.location = location;
049            }
050    
051            @Override
052            public Iterator<ClassFile> iterator() {           
053                    final JarFile jarFile= getJarFile();            
054            final Enumeration<JarEntry> jarEntries = jarFile.entries();
055            
056                    return new ClassFileIterator() {                        
057                            ClassFile getNext() {
058                                    while(jarEntries.hasMoreElements()) {
059                                            JarClassFile jcf= new JarClassFile(jarFile, jarEntries.nextElement());
060                                            if(jcf.getClassName()!=null) {
061                                                    return jcf;
062                                            }
063                                    }
064                                    try {
065                                            jarFile.close();
066                                    } catch (IOException ex) {
067                                logger.warn("Failed to close " + location, ex);
068                                    }
069                                    return null;
070                            }
071                    };
072            }
073    
074            private JarFile getJarFile() {          
075            try {
076                JarURLConnection conn = (JarURLConnection) location.openConnection();
077                JarFile jf= conn.getJarFile();
078                extractClasspath(jf);
079                return jf;
080            }
081            catch (IOException ex) {
082                logger.warn("Failed to open " + location, ex);
083                return null;
084            }
085            }
086    
087            private void extractClasspath(JarFile jf) throws IOException {
088                    Manifest manifest = jf.getManifest();
089                    if(manifest == null) {
090                            return;
091                    }
092                    
093                    URI resolveLocation;
094                    try {
095                            resolveLocation = location.toURI();
096                    } catch (URISyntaxException e) {
097                    logger.warn("Could not create URI out of {}", location);
098                    return;
099                    }
100                    
101            Attributes attributes = manifest.getMainAttributes();
102            String classPath = attributes.getValue(Attributes.Name.CLASS_PATH);
103            if (classPath == null) {
104                return;
105            }
106            
107            List<URL> newLocations = new ArrayList<URL>();
108                    for (String pathElement : PATH_SPLITTER.split(classPath)) {
109                URI resolvedLocation = resolveLocation.resolve(pathElement);
110                try {
111                                    URL newLocation = resolvedLocation.toURL();
112                        newLocations.add(newLocation);
113                } catch (MalformedURLException e) {
114                    logger.warn("Could not resolve Class-Path element {} relative to {}", pathElement, resolveLocation);
115                            }
116            }
117            classPathLocations = newLocations.toArray(new URL[newLocations.size()]);
118            }
119    
120        private static Pattern PATH_SPLITTER = Pattern.compile("\\s+");
121        
122            @Override
123            public String getLocation() {
124                    return location.toExternalForm();
125            }
126    
127            @Override
128            public Collection<SpiClassPathElement> getAdditionalLocations(SpiMetaRegistry registry) {
129                    if(classPathLocations==null) {
130                            return null;
131                    }
132                    return UrlClassPath.getClassPathElements(registry, classPathLocations);
133            }
134    
135            @Override
136            public ResourceFile getResource(String fileName) {
137                    JarFile jarFile= getJarFile();
138                    JarEntry jarEntry = jarFile.getJarEntry(fileName);
139                    if(jarEntry==null) {
140                            try {
141                                    jarFile.close();
142                            } catch (IOException e) {
143                    logger.info("ignoring exception on close", e);
144                            }
145                            return null;
146                    }
147                    return new JarResourceFile(jarFile, jarEntry);
148            }
149    }