1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  */
18 package org.apache.bcel.util;
19 
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.lang.ref.SoftReference;
23 import java.util.HashMap;
24 import java.util.Map;
25 
26 import org.apache.bcel.classfile.ClassParser;
27 import org.apache.bcel.classfile.JavaClass;
28 
29 /**
30  * 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
31  * specified in the given class path. By default, this is the value returned by ClassPath.getClassPath(). This repository holds onto classes with
32  * SoftReferences, and will reload as needed, in cases where memory sizes are important.<br>
33  *
34  * @see org.apache.bcel.Repository
35  */
36 public class MemorySensitiveClassPathRepository implements Repository {
37 
38     private ClassPath _path = null;
39     private final Map<String, SoftReference<JavaClass>> _loadedClasses = new HashMap<>(); // CLASSNAME X JAVACLASS
40 
MemorySensitiveClassPathRepository(final ClassPath path)41     public MemorySensitiveClassPathRepository(final ClassPath path) {
42         this._path = path;
43     }
44 
45     /**
46      * Store a new JavaClass instance into this Repository.
47      */
48     @Override
storeClass(final JavaClass clazz)49     public void storeClass(final JavaClass clazz) {
50         _loadedClasses.put(clazz.getClassName(), new SoftReference<>(clazz));
51         clazz.setRepository(this);
52     }
53 
54     /**
55      * Remove class from repository
56      */
57     @Override
removeClass(final JavaClass clazz)58     public void removeClass(final JavaClass clazz) {
59         _loadedClasses.remove(clazz.getClassName());
60     }
61 
62     /**
63      * Find an already defined (cached) JavaClass object by name.
64      */
65     @Override
findClass(final String className)66     public JavaClass findClass(final String className) {
67         final SoftReference<JavaClass> ref = _loadedClasses.get(className);
68         if (ref == null) {
69             return null;
70         }
71         return ref.get();
72     }
73 
74     /**
75      * Find a JavaClass object by name. If it is already in this Repository, the Repository version is returned. Otherwise, the Repository's classpath is
76      * searched for the class (and it is added to the Repository if found).
77      *
78      * @param className
79      *            the name of the class
80      * @return the JavaClass object
81      * @throws ClassNotFoundException
82      *             if the class is not in the Repository, and could not be found on the classpath
83      */
84     @Override
loadClass(String className)85     public JavaClass loadClass(String className) throws ClassNotFoundException {
86         if ((className == null) || className.isEmpty()) {
87             throw new IllegalArgumentException("Invalid class name " + className);
88         }
89         className = className.replace('/', '.'); // Just in case, canonical form
90         final JavaClass clazz = findClass(className);
91         if (clazz != null) {
92             return clazz;
93         }
94         try {
95             return loadClass(_path.getInputStream(className), className);
96         } catch (final IOException e) {
97             throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
98         }
99     }
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
loadClass(final Class<?> clazz)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 
loadClass(final InputStream is, final String className)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
getClassPath()160     public ClassPath getClassPath() {
161         return _path;
162     }
163 
164     /**
165      * Clear all entries from cache.
166      */
167     @Override
clear()168     public void clear() {
169         _loadedClasses.clear();
170     }
171 }
172