001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 *
017 */
018package org.apache.bcel.util;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.HashMap;
023import java.util.Map;
024
025import org.apache.bcel.classfile.ClassParser;
026import org.apache.bcel.classfile.JavaClass;
027
028/**
029 * This repository is used in situations where a Class is created outside the realm of a ClassLoader. Classes are loaded from the file systems using the paths
030 * specified in the given class path. By default, this is the value returned by ClassPath.getClassPath(). <br>
031 *
032 * @see org.apache.bcel.Repository
033 */
034public class ClassPathRepository implements Repository {
035
036    private ClassPath _path = null;
037    private final Map<String, JavaClass> _loadedClasses = new HashMap<>(); // CLASSNAME X JAVACLASS
038
039    public ClassPathRepository(final ClassPath path) {
040        _path = path;
041    }
042
043    /**
044     * Store a new JavaClass instance into this Repository.
045     */
046    @Override
047    public void storeClass(final JavaClass clazz) {
048        _loadedClasses.put(clazz.getClassName(), clazz);
049        clazz.setRepository(this);
050    }
051
052    /**
053     * Remove class from repository
054     */
055    @Override
056    public void removeClass(final JavaClass clazz) {
057        _loadedClasses.remove(clazz.getClassName());
058    }
059
060    /**
061     * Find an already defined (cached) JavaClass object by name.
062     */
063    @Override
064    public JavaClass findClass(final String className) {
065        return _loadedClasses.get(className);
066    }
067
068    /**
069     * Find a JavaClass object by name. If it is already in this Repository, the Repository version is returned. Otherwise, the Repository's classpath is
070     * searched for the class (and it is added to the Repository if found).
071     *
072     * @param className
073     *            the name of the class
074     * @return the JavaClass object
075     * @throws ClassNotFoundException
076     *             if the class is not in the Repository, and could not be found on the classpath
077     */
078    @Override
079    public JavaClass loadClass(String className) throws ClassNotFoundException {
080        if ((className == null) || className.isEmpty()) {
081            throw new IllegalArgumentException("Invalid class name " + className);
082        }
083        className = className.replace('/', '.'); // Just in case, canonical form
084        final JavaClass clazz = findClass(className);
085        if (clazz != null) {
086            return clazz;
087        }
088        try {
089            return loadClass(_path.getInputStream(className), className);
090        } catch (final IOException e) {
091            throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
092        }
093    }
094
095    /**
096     * Find the JavaClass object for a runtime Class object. If a class with the same name is already in this Repository, the Repository version is returned.
097     * Otherwise, getResourceAsStream() is called on the Class object to find the class's representation. If the representation is found, it is added to the
098     * Repository.
099     *
100     * @see Class
101     * @param clazz
102     *            the runtime Class object
103     * @return JavaClass object for given runtime class
104     * @throws ClassNotFoundException
105     *             if the class is not in the Repository, and its representation could not be found
106     */
107    @Override
108    public JavaClass loadClass(final Class<?> clazz) throws ClassNotFoundException {
109        final String className = clazz.getName();
110        final JavaClass repositoryClass = findClass(className);
111        if (repositoryClass != null) {
112            return repositoryClass;
113        }
114        String name = className;
115        final int i = name.lastIndexOf('.');
116        if (i > 0) {
117            name = name.substring(i + 1);
118        }
119        JavaClass cls = null;
120        try (InputStream clsStream = clazz.getResourceAsStream(name + ".class")) {
121            return cls = loadClass(clsStream, className);
122        } catch (final IOException e) {
123            return cls;
124        }
125    }
126
127    private JavaClass loadClass(final InputStream is, final String className) throws ClassNotFoundException {
128        try {
129            if (is != null) {
130                final ClassParser parser = new ClassParser(is, className);
131                final JavaClass clazz = parser.parse();
132                storeClass(clazz);
133                return clazz;
134            }
135        } catch (final IOException e) {
136            throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
137        } finally {
138            if (is != null) {
139                try {
140                    is.close();
141                } catch (final IOException e) {
142                    // ignored
143                }
144            }
145        }
146        throw new ClassNotFoundException("SyntheticRepository could not load " + className);
147    }
148
149    /**
150     * ClassPath associated with the Repository.
151     */
152    @Override
153    public ClassPath getClassPath() {
154        return _path;
155    }
156
157    /**
158     * Clear all entries from cache.
159     */
160    @Override
161    public void clear() {
162        _loadedClasses.clear();
163    }
164}