Package org.cojen.util

Source Code of org.cojen.util.ClassInjector$Loader

/*
*  Copyright 2004-2010 Brian S O'Neill
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/

package org.cojen.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.cojen.classfile.ClassFile;

/**
* ClassInjector allows transient classes to be loaded, where a transient class
* is defined as being dynamically created at runtime. Unless explicit, the
* name given to transient classes is randomly assigned to prevent name
* collisions and to discourage referencing the classname persistently outside
* the runtime environment.
* <p>
* Classes defined by ClassInjector may be unloaded, if no references to it
* exist. Once unloaded, they cannot be loaded again by name since the
* original bytecode was never preserved.
* <p>
* Debugging can be enabled via the java command-line option
* "-Dorg.cojen.util.ClassInjector.DEBUG=true". This causes all generated classes
* to be written to the temp directory, and a message is written to System.out
* indicating exactly where.
*
* @author Brian S O'Neill
* @deprecated use {@link org.cojen.classfile.RuntimeClassFile}
*/
@Deprecated
public class ClassInjector {
    private static final boolean DEBUG;

    static {
        DEBUG =
            Boolean.getBoolean("org.cojen.classfile.RuntimeClassFile.DEBUG") ||
            Boolean.getBoolean("org.cojen.util.ClassInjector.DEBUG") ||
            Boolean.getBoolean("cojen.util.ClassInjector.DEBUG");
    }

    private static final Random cRandom = new Random();

    // Weakly maps ClassLoaders to softly referenced internal ClassLoaders.
    private static Cache cLoaders = new WeakIdentityCache(5);

    /**
     * Create a ClassInjector for defining one class. The parent ClassLoader
     * used is the one which loaded the ClassInjector class.
     */
    public static ClassInjector create() {
        return create(null, null);
    }

    /**
     * Create a ClassInjector for defining one class. The prefix is optional,
     * which is used as the start of the auto-generated class name. If the
     * parent ClassLoader is not specified, it will default to the ClassLoader of
     * the ClassInjector class.
     * <p>
     * If the parent loader was used for loading injected classes, the new
     * class will be loaded by it. This allows auto-generated classes access to
     * package accessible members, as long as they are defined in the same
     * package.
     *
     * @param prefix optional class name prefix
     * @param parent optional parent ClassLoader
     */
    public static ClassInjector create(String prefix, ClassLoader parent) {
        return create(prefix, parent, false);
    }

    /**
     * Create a ClassInjector for defining one class with an explicit name. If
     * the parent ClassLoader is not specified, it will default to the
     * ClassLoader of the ClassInjector class.
     * <p>
     * If the parent loader was used for loading injected classes, the new
     * class will be loaded by it. This allows auto-generated classes access to
     * package accessible members, as long as they are defined in the same
     * package.
     *
     * @param name required class name
     * @param parent optional parent ClassLoader
     * @throws IllegalArgumentException if name is null
     */
    public static ClassInjector createExplicit(String name, ClassLoader parent) {
        if (name == null) {
            throw new IllegalArgumentException("Explicit class name not provided");
        }
        return create(name, parent, true);
    }

    private static ClassInjector create(String prefix, ClassLoader parent, boolean explicit) {
        if (prefix == null) {
            prefix = ClassInjector.class.getName();
        }
        if (parent == null) {
            parent = ClassInjector.class.getClassLoader();
            if (parent == null) {
                parent = ClassLoader.getSystemClassLoader();
            }
        }

        String name = explicit ? prefix : null;
        Loader loader;

        synchronized (cRandom) {
            getLoader: {
                if (parent instanceof Loader) {
                    // Use the same loader, allowing the new class access to
                    // same package protected members.
                    loader = (Loader) parent;
                    break getLoader;
                }
                SoftReference ref = (SoftReference) cLoaders.get(parent);
                if (ref != null) {
                    loader = (Loader) ref.get();
                    if (loader != null && loader.isValid()) {
                        break getLoader;
                    }
                    ref.clear();
                }
                loader = parent == null ? new Loader() : new Loader(parent);
                cLoaders.put(parent, new SoftReference(loader));
            }

            if (explicit) {
                reserveCheck: {
                    for (int i=0; i<2; i++) {
                        if (loader.reserveName(name)) {
                            try {
                                loader.loadClass(name);
                            } catch (ClassNotFoundException e) {
                                break reserveCheck;
                            }
                        }
                        if (i > 0) {
                            throw new IllegalStateException
                                ("Class name already reserved: " + name);
                        }
                        // Make a new loader and try again.
                        loader = parent == null ? new Loader() : new Loader(parent);
                    }

                    // Save new loader.
                    cLoaders.put(parent, new SoftReference(loader));
                }
            } else {
                for (int tryCount = 0; tryCount < 1000; tryCount++) {
                    name = null;
                   
                    long ID = cRandom.nextInt();
                   
                    // Use a small identifier if possible, making it easier to read
                    // stack traces and decompiled classes.
                    switch (tryCount) {
                    case 0:
                        ID &= 0xffL;
                        break;
                    case 1:
                        ID &= 0xffffL;
                        break;
                    default:
                        ID &= 0xffffffffL;
                        break;
                    }
                   
                    name = prefix + '$' + ID;
                   
                    if (!loader.reserveName(name)) {
                        continue;
                    }
                   
                    try {
                        loader.loadClass(name);
                    } catch (ClassNotFoundException e) {
                        break;
                    } catch (LinkageError e) {
                    }
                }
            }
        }

        if (name == null) {
            throw new InternalError("Unable to create unique class name");
        }

        return new ClassInjector(name, loader);
    }

    private final String mName;
    private final Loader mLoader;

    private ByteArrayOutputStream mData;
    private Class mClass;

    private ClassInjector(String name, Loader loader) {
        mName = name;
        mLoader = loader;
    }

    /**
     * Returns the name that must be given to the new class.
     */
    public String getClassName() {
        return mName;
    }

    /**
     * Open a stream to define the new class into.
     *
     * @throws IllegalStateException if new class has already been defined
     * or if a stream has already been opened
     */
    public OutputStream openStream() throws IllegalStateException {
        if (mClass != null) {
            throw new IllegalStateException("New class has already been defined");
        }
        ByteArrayOutputStream data = mData;
        if (data != null) {
            throw new IllegalStateException("Stream already opened");
        }
        mData = data = new ByteArrayOutputStream();
        return data;
    }

    /**
     * Define the new class from a ClassFile object.
     *
     * @return the newly created class
     * @throws IllegalStateException if new class has already been defined
     * or if a stream has already been opened
     */
    public Class defineClass(ClassFile cf) {
        try {
            cf.writeTo(openStream());
        } catch (IOException e) {
            throw new InternalError(e.toString());
        }
        return getNewClass();
    }

    /**
     * Returns the newly defined class.
     *
     * @throws IllegalStateException if class was never defined
     */
    public Class getNewClass() throws IllegalStateException, ClassFormatError {
        if (mClass != null) {
            return mClass;
        }
        ByteArrayOutputStream data = mData;
        if (data == null) {
            throw new IllegalStateException("Class not defined yet");
        }

        byte[] bytes = data.toByteArray();

        if (DEBUG) {
            File file = new File(mName.replace('.', '/') + ".class");
            try {
                File tempDir = new File(System.getProperty("java.io.tmpdir"));
                file = new File(tempDir, file.getPath());
            } catch (SecurityException e) {
            }
            try {
                file.getParentFile().mkdirs();
                System.out.println("ClassInjector writing to " + file);
                OutputStream out = new FileOutputStream(file);
                out.write(bytes);
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        mClass = mLoader.define(mName, bytes);
        mData = null;
        return mClass;
    }

    private static final class Loader extends ClassLoader {
        private Set mReservedNames = new HashSet();

        Loader(ClassLoader parent) {
            super(parent);
        }

        Loader() {
            super();
        }

        // Prevent name collisions while multiple threads are injecting classes
        // by reserving the name.
        synchronized boolean reserveName(String name) {
            return mReservedNames.add(name);
        }

        synchronized boolean isValid() {
            // Only use loader for 100 injections, to facilitate class
            // unloading.
            return mReservedNames.size() < 100;
        }

        Class define(String name, byte[] b) {
            Class clazz = defineClass(name, b, 0, b.length);
            resolveClass(clazz);
            return clazz;
        }
    }
}
TOP

Related Classes of org.cojen.util.ClassInjector$Loader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.