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.util.ArrayList;
017    import java.util.Collection;
018    import java.util.Collections;
019    import java.util.Iterator;
020    import java.util.LinkedHashMap;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.MissingResourceException;
024    import java.util.NoSuchElementException;
025    
026    import org.apache.commons.classscan.ClassPath;
027    import org.apache.commons.classscan.ClassPathElement;
028    import org.apache.commons.classscan.MetaClassLoader;
029    import org.apache.commons.classscan.MetaClassPathElement;
030    import org.apache.commons.classscan.model.MetaArray;
031    import org.apache.commons.classscan.model.MetaClass;
032    import org.apache.commons.classscan.model.MetaType;
033    import org.apache.commons.classscan.spi.model.SpiClassPathElement;
034    import org.apache.commons.classscan.spi.model.SpiMetaClassLoader;
035    import org.apache.commons.classscan.spi.model.SpiMetaClassPathElement;
036    import org.apache.commons.classscan.spi.model.SpiMetaRegistry;
037    import org.slf4j.Logger;
038    import org.slf4j.LoggerFactory;
039    
040    public class UrlMetaClassLoader implements SpiMetaClassLoader {
041    
042        private static final Logger logger = LoggerFactory.getLogger(UrlMetaClassLoader.class);
043    
044        private final SpiMetaClassLoader parent;
045        private Map<String, SpiMetaClassPathElement> locations;
046        
047            public UrlMetaClassLoader(SpiMetaRegistry registry, ClassLoader classLoader) {
048                    parent= classLoader!= null ?(SpiMetaClassLoader)registry.getMetaClassLoader(classLoader.getParent()) :null;
049                    // use LinkedHashMap to keep iteration over path elements in same order as definition
050                    locations = new LinkedHashMap<String, SpiMetaClassPathElement>();
051                    addLocations(registry, classLoader);
052                    resolve();
053                    locations = Collections.unmodifiableMap(locations);
054        }
055    
056            void addLocations(SpiMetaRegistry registry, ClassLoader classLoader) {
057            ClassPath classPath = registry.getClassPath(classLoader);
058                    for(ClassPathElement pathElement : classPath.getClassPathElements()) {
059                    addLocation(registry, pathElement);
060            }
061        }
062    
063        /**
064         * Add a location
065         * 
066         * @param pathElement
067         *            The URI of a jar or folder
068         */
069            private void addLocation(SpiMetaRegistry registry, ClassPathElement pathElement) {
070            String baseLocation= pathElement.getLocation();
071            if (!locations.containsKey(baseLocation)) {
072                SpiMetaClassPathElement mcl = registry.createMetaClassPathElement(pathElement);
073                if (mcl != null) {
074                    locations.put(baseLocation, mcl);       
075                }
076                    Collection<SpiClassPathElement> additionalUrls= ((SpiClassPathElement)pathElement).getAdditionalLocations(registry);
077                    if(additionalUrls!=null) {
078                            for(SpiClassPathElement additionalUrl : additionalUrls) {
079                                    addLocation(registry, additionalUrl);
080                            }
081                    }
082            }
083        }
084            
085        protected void addLocation(SpiMetaClassPathElement cpe) {
086            locations.put(cpe.getName(), cpe);
087            }
088    
089        // resolve string names into model entities instead of using resolution upon demand
090        // memory used by multiple string copies weighs more heavily than time spent resolving meta-class
091        // demand resolution would require clients to filter out classes which have un-resolvable dependencies
092        private void resolve() {
093            Iterator<SpiMetaClassPathElement> metaClassPathElements= locations.values().iterator();
094            while(metaClassPathElements.hasNext()) {
095                    SpiMetaClassPathElement metaClassPathElement= metaClassPathElements.next();
096                    if(!metaClassPathElement.resolve(this)) {
097                            metaClassPathElements.remove();
098                    }
099            }
100            }
101    
102        @Override
103        public MetaClassLoader getParent() {
104            return parent;
105        }
106    
107        @Override
108        public Collection<? extends MetaClassPathElement> getClassLocations() {
109            return locations.values();
110        }
111    
112        @Override
113        public MetaClassPathElement getClassLocation(String location) {
114            return locations.get(location);
115        }
116    
117        @Override
118        public Iterator<? extends MetaClass> getMetaClasses() {       
119            Collection<? extends MetaClassPathElement> classLocations = getClassLocations();
120            if(classLocations.isEmpty()) {
121                    List<? extends MetaClass> empty= Collections.emptyList();
122                    return empty.iterator();
123            }
124            
125                    final Iterator<? extends MetaClassPathElement> locations= classLocations.iterator();
126            
127            return new Iterator<MetaClass>() {
128                    {
129                            nextInnerIterator();
130                    }
131                    Iterator<? extends MetaClass> metaClasses;
132    
133                    private boolean nextInnerIterator() {
134                            if(!locations.hasNext()) {
135                                    return false;
136                            }
137                                    MetaClassPathElement mcpe= locations.next();
138                                    metaClasses= mcpe.getMetaClasses().iterator();
139                                    return true;
140                            }
141                    
142                            @Override
143                            public boolean hasNext() {
144                            while(!metaClasses.hasNext()) {                                 
145                                    if(!nextInnerIterator()) {
146                                            return false;
147                                    }
148                            }
149                            return true;
150                            }
151    
152                            @Override
153                            public MetaClass next() {
154                            while(!metaClasses.hasNext()) {                                 
155                                    if(!locations.hasNext()) {
156                                            throw new NoSuchElementException();
157                                    }
158                                    nextInnerIterator();
159                            }
160                            return metaClasses.next();
161                            }
162    
163                            @Override
164                            public void remove() {
165                                    throw new UnsupportedOperationException();
166                            }
167            };
168        }
169    
170        @Override
171        public MetaClass getMetaClass(String className) {
172            for(MetaClassPathElement location : locations.values()) {
173                    MetaClass mc = location.getMetaClass(className);
174                    if(mc != null) {
175                            return mc;
176                    }
177            }
178            return null;
179        }
180    
181        @Override
182        public MetaClass findMetaClass(String className) {
183            MetaClass mc = parent.findMetaClass(className);
184            if (mc != null) {
185                return mc;
186            }
187            return getMetaClass(className);
188        }
189    
190        @Override
191        public MetaClass resolveMetaClass(String className) {
192            MetaClass mc = parent.resolveMetaClass(className);
193            if (mc != null) {
194                return mc;
195            }
196            return resolveInPath(className);
197        }
198    
199            MetaClass resolveInPath(String className) {
200                    for(SpiMetaClassPathElement location : locations.values()) {
201                            MetaClass mc = location.resolveMetaClass(this, className);
202                    if(mc != null) {
203                            return mc;
204                    }
205            }
206            return null;
207            }
208    
209        /**
210         * Get metadata representing a type from its byte code signature
211         * 
212         * @param fieldDescriptor
213         *            The field descriptor as defined in section 4.3.2 of The Java
214         *            Virtual Machine Specification
215         * @return The type metadata
216         */
217        /*
218         * FieldType: BaseType ObjectType ArrayType
219         * 
220         * BaseType: // see PrimitiveClass
221         * 
222         * ObjectType: L <classname> ;
223         * 
224         * ArrayType: [ ComponentType
225         */
226        @Override
227            public MetaType resolveTypeForDescriptor(String fieldDescriptor) {
228            MetaType mt; 
229            char typeCode = fieldDescriptor.charAt(0);
230            switch (typeCode) {
231            case 'L':
232                            mt = resolveObjectType(fieldDescriptor);
233                            break;
234            case '[':
235                mt = resolveArrayType(fieldDescriptor.substring(1));
236                break;
237            default:
238                    mt = PrimitiveClass.valueOf(fieldDescriptor);
239                    break;
240            }
241            if(mt == null) {
242                    logger.info("Cannot resolve type "+fieldDescriptor);
243            }
244            return mt;
245        }
246    
247            private MetaType resolveObjectType(String fieldDescriptor) {
248                    String canonicalName = ClassNameHelper.internalToCanonicalName(fieldDescriptor);
249                    return resolveMetaClass(canonicalName);
250            }
251    
252        private MetaArray resolveArrayType(String fieldDescriptor) {
253            MetaType arrayType = resolveTypeForDescriptor(fieldDescriptor);
254            return new DefaultArrayType(arrayType);
255        }
256    
257        @Override
258        public Collection<? extends MetaClass> findAllImplementors(String interfaceToFind) {
259            MetaClass mc = findMetaClass(interfaceToFind);
260    
261            Collection<MetaClass> implementations = new ArrayList<MetaClass>();
262            MetaClassLoader classLoader = this;
263            do {            
264                Iterator<? extends MetaClass> metaClasses = classLoader.getMetaClasses(); 
265                while(metaClasses.hasNext()) {
266                    MetaClass potentialImplementation = metaClasses.next();
267                    try {
268                        if (!potentialImplementation.equals(mc) && mc.isAssignableFrom(potentialImplementation)) {
269                            implementations.add(potentialImplementation);
270                        }
271                    }
272                    catch (MissingResourceException mre) {
273                        continue;
274                    }
275                }
276                classLoader = classLoader.getParent();
277            }
278            while (classLoader != null);
279            return implementations;
280        }
281    }