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    }