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}