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