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 */
018 package org.apache.bcel.util;
019
020 import java.io.ByteArrayInputStream;
021 import java.util.Hashtable;
022 import org.apache.bcel.Constants;
023 import org.apache.bcel.classfile.ClassParser;
024 import org.apache.bcel.classfile.ConstantClass;
025 import org.apache.bcel.classfile.ConstantPool;
026 import org.apache.bcel.classfile.ConstantUtf8;
027 import org.apache.bcel.classfile.JavaClass;
028 import org.apache.bcel.classfile.Utility;
029
030 /**
031 * <p>Drop in replacement for the standard class loader of the JVM. You can use it
032 * in conjunction with the JavaWrapper to dynamically modify/create classes
033 * as they're requested.</p>
034 *
035 * <p>This class loader recognizes special requests in a distinct
036 * format, i.e., when the name of the requested class contains with
037 * "$$BCEL$$" it calls the createClass() method with that name
038 * (everything bevor the $$BCEL$$ is considered to be the package
039 * name. You can subclass the class loader and override that
040 * method. "Normal" classes class can be modified by overriding the
041 * modifyClass() method which is called just before defineClass().</p>
042 *
043 * <p>There may be a number of packages where you have to use the
044 * default class loader (which may also be faster). You can define the
045 * set of packages where to use the system class loader in the
046 * constructor. The default value contains "java.", "sun.",
047 * "javax."</p>
048 *
049 * @version $Id: ClassLoader.java 1152072 2011-07-29 01:54:05Z dbrosius $
050 * @author <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
051 * @see JavaWrapper
052 * @see ClassPath
053 */
054 public class ClassLoader extends java.lang.ClassLoader {
055
056 public static final String[] DEFAULT_IGNORED_PACKAGES = {
057 "java.", "javax.", "sun."
058 };
059 private Hashtable<String, Class<?>> classes = new Hashtable<String, Class<?>>(); // Hashtable is synchronized thus thread-safe
060 private String[] ignored_packages;
061 private Repository repository = SyntheticRepository.getInstance();
062
063
064 /** Ignored packages are by default ( "java.", "sun.",
065 * "javax."), i.e. loaded by system class loader
066 */
067 public ClassLoader() {
068 this(DEFAULT_IGNORED_PACKAGES);
069 }
070
071
072 /** @param deferTo delegate class loader to use for ignored packages
073 */
074 public ClassLoader(java.lang.ClassLoader deferTo) {
075 super(deferTo);
076 this.ignored_packages = DEFAULT_IGNORED_PACKAGES;
077 this.repository = new ClassLoaderRepository(deferTo);
078 }
079
080
081 /** @param ignored_packages classes contained in these packages will be loaded
082 * with the system class loader
083 */
084 public ClassLoader(String[] ignored_packages) {
085 this.ignored_packages = ignored_packages;
086 }
087
088
089 /** @param ignored_packages classes contained in these packages will be loaded
090 * with the system class loader
091 * @param deferTo delegate class loader to use for ignored packages
092 */
093 public ClassLoader(java.lang.ClassLoader deferTo, String[] ignored_packages) {
094 this(ignored_packages);
095 this.repository = new ClassLoaderRepository(deferTo);
096 }
097
098 @Override
099 protected Class<?> loadClass( String class_name, boolean resolve ) throws ClassNotFoundException {
100 Class<?> cl = null;
101 /* First try: lookup hash table.
102 */
103 if ((cl = classes.get(class_name)) == null) {
104 /* Second try: Load system class using system class loader. You better
105 * don't mess around with them.
106 */
107 for (int i = 0; i < ignored_packages.length; i++) {
108 if (class_name.startsWith(ignored_packages[i])) {
109 cl = getParent().loadClass(class_name);
110 break;
111 }
112 }
113 if (cl == null) {
114 JavaClass clazz = null;
115 /* Third try: Special request?
116 */
117 if (class_name.indexOf("$$BCEL$$") >= 0) {
118 clazz = createClass(class_name);
119 } else { // Fourth try: Load classes via repository
120 if ((clazz = repository.loadClass(class_name)) != null) {
121 clazz = modifyClass(clazz);
122 } else {
123 throw new ClassNotFoundException(class_name);
124 }
125 }
126 if (clazz != null) {
127 byte[] bytes = clazz.getBytes();
128 cl = defineClass(class_name, bytes, 0, bytes.length);
129 } else {
130 cl = Class.forName(class_name);
131 }
132 }
133 if (resolve) {
134 resolveClass(cl);
135 }
136 }
137 classes.put(class_name, cl);
138 return cl;
139 }
140
141
142 /** Override this method if you want to alter a class before it gets actually
143 * loaded. Does nothing by default.
144 */
145 protected JavaClass modifyClass( JavaClass clazz ) {
146 return clazz;
147 }
148
149
150 /**
151 * Override this method to create you own classes on the fly. The
152 * name contains the special token $$BCEL$$. Everything before that
153 * token is consddered to be a package name. You can encode you own
154 * arguments into the subsequent string. You must regard however not
155 * to use any "illegal" characters, i.e., characters that may not
156 * appear in a Java class name too<br>
157 *
158 * The default implementation interprets the string as a encoded compressed
159 * Java class, unpacks and decodes it with the Utility.decode() method, and
160 * parses the resulting byte array and returns the resulting JavaClass object.
161 *
162 * @param class_name compressed byte code with "$$BCEL$$" in it
163 */
164 protected JavaClass createClass( String class_name ) {
165 int index = class_name.indexOf("$$BCEL$$");
166 String real_name = class_name.substring(index + 8);
167 JavaClass clazz = null;
168 try {
169 byte[] bytes = Utility.decode(real_name, true);
170 ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
171 clazz = parser.parse();
172 } catch (Throwable e) {
173 e.printStackTrace();
174 return null;
175 }
176 // Adapt the class name to the passed value
177 ConstantPool cp = clazz.getConstantPool();
178 ConstantClass cl = (ConstantClass) cp.getConstant(clazz.getClassNameIndex(),
179 Constants.CONSTANT_Class);
180 ConstantUtf8 name = (ConstantUtf8) cp.getConstant(cl.getNameIndex(),
181 Constants.CONSTANT_Utf8);
182 name.setBytes(class_name.replace('.', '/'));
183 return clazz;
184 }
185 }