1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * https://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.bcel.util;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.util.Hashtable;
24
25 import org.apache.bcel.Const;
26 import org.apache.bcel.classfile.ClassParser;
27 import org.apache.bcel.classfile.ConstantClass;
28 import org.apache.bcel.classfile.ConstantPool;
29 import org.apache.bcel.classfile.ConstantUtf8;
30 import org.apache.bcel.classfile.JavaClass;
31 import org.apache.bcel.classfile.Utility;
32
33 /**
34 * <p>
35 * Drop in replacement for the standard class loader of the JVM. You can use it in conjunction with the JavaWrapper to
36 * dynamically modify/create classes as they're requested.
37 * </p>
38 *
39 * <p>
40 * This class loader recognizes special requests in a distinct format, that is, when the name of the requested class
41 * contains with "$$BCEL$$" it calls the createClass() method with that name (everything bevor the $$BCEL$$ is
42 * considered to be the package name. You can subclass the class loader and override that method. "Normal" classes class
43 * can be modified by overriding the modifyClass() method which is called just before defineClass().
44 * </p>
45 *
46 * <p>
47 * There may be a number of packages where you have to use the default class loader (which may also be faster). You can
48 * define the set of packages where to use the system class loader in the constructor. The default value contains
49 * "java.", "sun.", "javax."
50 * </p>
51 *
52 * @see JavaWrapper
53 * @see ClassPath
54 * @deprecated 6.0 Do not use - does not work
55 */
56 @Deprecated
57 public class ClassLoader extends java.lang.ClassLoader {
58
59 private static final String BCEL_TOKEN = "$$BCEL$$";
60
61 /**
62 * Default packages that are ignored by the class loader.
63 */
64 public static final String[] DEFAULT_IGNORED_PACKAGES = {"java.", "javax.", "sun."};
65
66 private final Hashtable<String, Class<?>> classes = new Hashtable<>();
67 // Hashtable is synchronized thus thread-safe
68 private final String[] ignoredPackages;
69 private Repository repository = SyntheticRepository.getInstance();
70
71 /**
72 * Constructs a ClassLoader with default ignored packages.
73 * Ignored packages are by default ( "java.", "sun.", "javax."), for example loaded by system class loader.
74 */
75 public ClassLoader() {
76 this(DEFAULT_IGNORED_PACKAGES);
77 }
78
79 /**
80 * Constructs a ClassLoader with a delegate class loader.
81 *
82 * @param deferTo delegate class loader to use for ignored packages.
83 */
84 public ClassLoader(final java.lang.ClassLoader deferTo) {
85 super(deferTo);
86 this.ignoredPackages = DEFAULT_IGNORED_PACKAGES;
87 this.repository = new ClassLoaderRepository(deferTo);
88 }
89
90 /**
91 * Constructs a ClassLoader with a delegate class loader and ignored packages.
92 *
93 * @param deferTo delegate class loader to use for ignored packages.
94 * @param ignoredPackages classes contained in these packages will be loaded with the system class loader.
95 */
96 public ClassLoader(final java.lang.ClassLoader deferTo, final String[] ignoredPackages) {
97 this(ignoredPackages);
98 this.repository = new ClassLoaderRepository(deferTo);
99 }
100
101 /**
102 * Constructs a ClassLoader with specific ignored packages.
103 *
104 * @param ignoredPackages classes contained in these packages will be loaded with the system class loader.
105 */
106 public ClassLoader(final String[] ignoredPackages) {
107 this.ignoredPackages = ignoredPackages;
108 }
109
110 /**
111 * Override this method to create you own classes on the fly. The name contains the special token $$BCEL$$. Everything
112 * before that token is considered to be a package name. You can encode your own arguments into the subsequent string.
113 * You must ensure however not to use any "illegal" characters, that is, characters that may not appear in a Java class
114 * name too.
115 * <p>
116 * The default implementation interprets the string as a encoded compressed Java class, unpacks and decodes it with the
117 * Utility.decode() method, and parses the resulting byte array and returns the resulting JavaClass object.
118 * </p>
119 *
120 * @param className compressed byte code with "$$BCEL$$" in it.
121 * @return the created JavaClass.
122 */
123 protected JavaClass createClass(final String className) {
124 final int index = className.indexOf(BCEL_TOKEN);
125 final String realName = className.substring(index + BCEL_TOKEN.length());
126 JavaClass clazz = null;
127 try {
128 final byte[] bytes = Utility.decode(realName, true);
129 final ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
130 clazz = parser.parse();
131 } catch (final IOException e) {
132 e.printStackTrace();
133 return null;
134 }
135 // Adapt the class name to the passed value
136 final ConstantPool cp = clazz.getConstantPool();
137 final ConstantClass cl = cp.getConstant(clazz.getClassNameIndex(), Const.CONSTANT_Class, ConstantClass.class);
138 final ConstantUtf8 name = cp.getConstantUtf8(cl.getNameIndex());
139 name.setBytes(Utility.packageToPath(className));
140 return clazz;
141 }
142
143 @Override
144 protected Class<?> loadClass(final String className, final boolean resolve) throws ClassNotFoundException {
145 Class<?> cl = null;
146 /*
147 * First try: lookup hash table.
148 */
149 if ((cl = classes.get(className)) == null) {
150 /*
151 * Second try: Load system class using system class loader. You better don't mess around with them.
152 */
153 for (final String ignoredPackage : ignoredPackages) {
154 if (className.startsWith(ignoredPackage)) {
155 cl = getParent().loadClass(className);
156 break;
157 }
158 }
159 if (cl == null) {
160 JavaClass clazz = null;
161 /*
162 * Third try: Special request?
163 */
164 if (className.contains(BCEL_TOKEN)) {
165 clazz = createClass(className);
166 } else { // Fourth try: Load classes via repository
167 if ((clazz = repository.loadClass(className)) == null) {
168 throw new ClassNotFoundException(className);
169 }
170 clazz = modifyClass(clazz);
171 }
172 if (clazz != null) {
173 final byte[] bytes = clazz.getBytes();
174 cl = defineClass(className, bytes, 0, bytes.length);
175 } else {
176 cl = Class.forName(className);
177 }
178 }
179 if (resolve) {
180 resolveClass(cl);
181 }
182 }
183 classes.put(className, cl);
184 return cl;
185 }
186
187 /**
188 * Override this method if you want to alter a class before it gets actually loaded. Does nothing by default.
189 *
190 * @param clazz the class to modify.
191 * @return the modified class.
192 */
193 protected JavaClass modifyClass(final JavaClass clazz) {
194 return clazz;
195 }
196 }