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.lang.ref.SoftReference;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.bcel.classfile.ClassParser;
027import org.apache.bcel.classfile.JavaClass;
028
029/**
030 * 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
031 * specified in the given class path. By default, this is the value returned by ClassPath.getClassPath(). This repository holds onto classes with
032 * SoftReferences, and will reload as needed, in cases where memory sizes are important.<br>
033 *
034 * @see org.apache.bcel.Repository
035 */
036public class MemorySensitiveClassPathRepository implements Repository {
037
038    private ClassPath _path = null;
039    private final Map<String, SoftReference<JavaClass>> _loadedClasses = new HashMap<>(); // CLASSNAME X JAVACLASS
040
041    public MemorySensitiveClassPathRepository(final ClassPath path) {
042        this._path = path;
043    }
044
045    /**
046     * Store a new JavaClass instance into this Repository.
047     */
048    @Override
049    public void storeClass(final JavaClass clazz) {
050        _loadedClasses.put(clazz.getClassName(), new SoftReference<>(clazz));
051        clazz.setRepository(this);
052    }
053
054    /**
055     * Remove class from repository
056     */
057    @Override
058    public void removeClass(final JavaClass clazz) {
059        _loadedClasses.remove(clazz.getClassName());
060    }
061
062    /**
063     * Find an already defined (cached) JavaClass object by name.
064     */
065    @Override
066    public JavaClass findClass(final String className) {
067        final SoftReference<JavaClass> ref = _loadedClasses.get(className);
068        if (ref == null) {
069            return null;
070        }
071        return ref.get();
072    }
073
074    /**
075     * Find a JavaClass object by name. If it is already in this Repository, the Repository version is returned. Otherwise, the Repository's classpath is
076     * searched for the class (and it is added to the Repository if found).
077     *
078     * @param className
079     *            the name of the class
080     * @return the JavaClass object
081     * @throws ClassNotFoundException
082     *             if the class is not in the Repository, and could not be found on the classpath
083     */
084    @Override
085    public JavaClass loadClass(String className) throws ClassNotFoundException {
086        if ((className == null) || className.isEmpty()) {
087            throw new IllegalArgumentException("Invalid class name " + className);
088        }
089        className = className.replace('/', '.'); // Just in case, canonical form
090        final JavaClass clazz = findClass(className);
091        if (clazz != null) {
092            return clazz;
093        }
094        try {
095            return loadClass(_path.getInputStream(className), className);
096        } catch (final IOException e) {
097            throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
098        }
099    }
100
101    /**
102     * 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.
103     * Otherwise, getResourceAsStream() is called on the Class object to find the class's representation. If the representation is found, it is added to the
104     * Repository.
105     *
106     * @see Class
107     * @param clazz
108     *            the runtime Class object
109     * @return JavaClass object for given runtime class
110     * @throws ClassNotFoundException
111     *             if the class is not in the Repository, and its representation could not be found
112     */
113    @Override
114    public JavaClass loadClass(final Class<?> clazz) throws ClassNotFoundException {
115        final String className = clazz.getName();
116        final JavaClass repositoryClass = findClass(className);
117        if (repositoryClass != null) {
118            return repositoryClass;
119        }
120        String name = className;
121        final int i = name.lastIndexOf('.');
122        if (i > 0) {
123            name = name.substring(i + 1);
124        }
125        JavaClass cls = null;
126        try (InputStream clsStream = clazz.getResourceAsStream(name + ".class")) {
127            return cls = loadClass(clsStream, className);
128        } catch (final IOException e) {
129            return cls;
130        }
131
132    }
133
134    private JavaClass loadClass(final InputStream is, final String className) throws ClassNotFoundException {
135        try {
136            if (is != null) {
137                final ClassParser parser = new ClassParser(is, className);
138                final JavaClass clazz = parser.parse();
139                storeClass(clazz);
140                return clazz;
141            }
142        } catch (final IOException e) {
143            throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
144        } finally {
145            if (is != null) {
146                try {
147                    is.close();
148                } catch (final IOException e) {
149                    // ignored
150                }
151            }
152        }
153        throw new ClassNotFoundException("SyntheticRepository could not load " + className);
154    }
155
156    /**
157     * ClassPath associated with the Repository.
158     */
159    @Override
160    public ClassPath getClassPath() {
161        return _path;
162    }
163
164    /**
165     * Clear all entries from cache.
166     */
167    @Override
168    public void clear() {
169        _loadedClasses.clear();
170    }
171}