View Javadoc
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  
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
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
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
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
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
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 }