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