    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}