Package org.cojen.classfile

Source Code of org.cojen.classfile.RuntimeClassFile$LoaderAndName

/*
*  Copyright 2009-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.classfile;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Principal;
import java.security.ProtectionDomain;

import java.security.cert.Certificate;

import org.cojen.util.Cache;
import org.cojen.util.KeyFactory;
import org.cojen.util.WeakKeyCache;
import org.cojen.util.WeakValueCache;

/**
* Allows classes to be defined and loaded at runtime. A random number is
* appended to class names to prevent name collisions and to discourage
* referencing them persistently outside the runtime environment. This behavior
* can be disabled by constructing with {@code explicit} set to true.
*
* <p>Debugging can be enabled via the java command-line option
* "-Dorg.cojen.classfile.RuntimeClassFile.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
*/
public class RuntimeClassFile extends ClassFile {
    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();

    private static Cache<Object, Loader> cLoaders = new WeakValueCache<Object, Loader>(11);

    private final Loader mLoader;

    public RuntimeClassFile() {
        this(null, null, null, null, false, null);
    }

    /**
     * @param className fully qualified class name; pass null to use default
     */
    public RuntimeClassFile(String className) {
        this(className, null, null, null, false, null);
    }

    /**
     * @param className fully qualified class name; pass null to use default
     * @param superClassName fully qualified super class name; pass null to use Object.
     */
    public RuntimeClassFile(String className, String superClassName) {
        this(className, superClassName, null, null, false, null);
    }

   /**
     * @param className fully qualified class name; pass null to use default
     * @param superClassName fully qualified super class name; pass null to use Object.
     * @param parentLoader parent class loader; pass null to use default
     */
    public RuntimeClassFile(String className, String superClassName, ClassLoader parentLoader) {
        this(className, superClassName, parentLoader, null, false, null);
    }

    /**
     * @param className fully qualified class name; pass null to use default
     * @param superClassName fully qualified super class name; pass null to use Object.
     * @param parentLoader parent class loader; pass null to use default
     * @param domain to define class in; pass null to use default
     */
    public RuntimeClassFile(String className, String superClassName,
                            ClassLoader parentLoader, ProtectionDomain domain)
    {
        this(className, superClassName, parentLoader, domain, false, null);
    }

    /**
     * @param className fully qualified class name; pass null to use default
     * @param superClassName fully qualified super class name; pass null to use Object.
     * @param parentLoader parent class loader; pass null to use default
     * @param domain to define class in; pass null to use default
     * @param explicit pass true to prevent name mangling
     */
    public RuntimeClassFile(String className, String superClassName,
                            ClassLoader parentLoader, ProtectionDomain domain,
                            boolean explicit)
    {
        this(className, superClassName, parentLoader, domain, explicit, null);
    }

    // Magic constructor to select name and loader before calling super class constructor.
    private RuntimeClassFile(String className, String superClassName,
                             ClassLoader parentLoader, ProtectionDomain domain,
                             boolean explicit, LoaderAndName loaderAndName)
    {
        super((loaderAndName = loaderAndName
               (className, parentLoader, domain, explicit)).mClassName, superClassName);
        mLoader = loaderAndName.mLoader;
    }

    /**
     * Finishes the class definition.
     */
    public Class defineClass() {
        byte[] bytes;
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            writeTo(bout);
            bytes = bout.toByteArray();
        } catch (IOException e) {
            InternalError ie = new InternalError(e.toString());
            ie.initCause(e);
            throw ie;
        }

        if (DEBUG) {
            File file = new File(getClassName().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("RuntimeClassFile writing to " + file);
                OutputStream out = new FileOutputStream(file);
                out.write(bytes);
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return mLoader.define(getClassName(), bytes);
    }

    /**
     * Returns the ClassLoader used by defined classes.
     */
    public ClassLoader getClassLoader() {
        return mLoader;
    }

    private static LoaderAndName loaderAndName(String className,
                                               ClassLoader parentLoader,
                                               ProtectionDomain domain,
                                               boolean explicit)
    {
        if (className == null) {
            if (explicit) {
                throw new IllegalArgumentException("Explicit class name not provided");
            }
            className = RuntimeClassFile.class.getName();
        }

        if (parentLoader == null) {
            parentLoader = RuntimeClassFile.class.getClassLoader();
            if (parentLoader == null) {
                parentLoader = ClassLoader.getSystemClassLoader();
            }
        }

        final Object loaderKey = createLoaderKey(className, parentLoader, domain);

        Loader loader = cLoaders.get(loaderKey);
        if (loader == null) {
            loader = parentLoader == null ? new Loader(domain) : new Loader(parentLoader, domain);
            cLoaders.put(loaderKey, loader);
        }

        if (explicit) {
            return new LoaderAndName(loader, className);
        }

        for (int tryCount = 0; tryCount < 1000; tryCount++) {
            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: case 2: case 3: case 4:
                id &= 0xffffL;
                break;
            default:
                id &= 0xffffffffL;
                break;
            }

            String mangled = className + '$' + id;

            if (loader.reserveName(mangled, false)) {
                return new LoaderAndName(loader, mangled);
            }
        }

        throw new InternalError("Unable to create unique class name");
    }

    private static Object createLoaderKey(String className, ClassLoader parentLoader,
                                          ProtectionDomain domain)
    {
        String packageName;
        {
            int index = className.lastIndexOf('.');
            if (index < 0) {
                packageName = "";
            } else {
                packageName = className.substring(0, index);
            }
        }

        // ProtectionDomain doesn't have an equals method, so break it apart
        // and add the elements to the composite key.

        Object domainKey = null;
        Object csKey = null;
        Object permsKey = null;
        Object principalsKey = null;

        if (domain != null) {
            domainKey = "";
            csKey = domain.getCodeSource();

            PermissionCollection pc = domain.getPermissions();
            if (pc != null) {
                List<Permission> permList = Collections.list(pc.elements());
                if (permList.size() == 1) {
                    permsKey = permList.get(0);
                } else if (permList.size() > 1) {
                    permsKey = new HashSet<Permission>(permList);
                }
            }

            Principal[] principals = domain.getPrincipals();
            if (principals != null && principals.length > 0) {
                if (principals.length == 1) {
                    principalsKey = principals[0];
                } else {
                    Set<Principal> principalSet = new HashSet<Principal>(principals.length);
                    for (Principal principal : principals) {
                        principalSet.add(principal);
                    }
                    principalsKey = principalSet;
                }
            }
        }

        return KeyFactory.createKey(new Object[] {
            parentLoader, packageName, domainKey, csKey, permsKey, principalsKey
        });
    }

    private static final class Loader extends ClassLoader {
        private final Cache<String, Boolean> mReservedNames = new WeakKeyCache<String, Boolean>(17);
        private final ProtectionDomain mDomain;

        Loader(ClassLoader parent, ProtectionDomain domain) {
            super(parent);
            mDomain = prepareDomain(domain, this);
        }

        Loader(ProtectionDomain domain) {
            super();
            mDomain = prepareDomain(domain, this);
        }

        private static ProtectionDomain prepareDomain(ProtectionDomain domain,
                                                      ClassLoader loader)
        {
            if (domain == null) {
                return null;
            }

            return new ProtectionDomain(domain.getCodeSource(),
                                        domain.getPermissions(),
                                        loader,
                                        domain.getPrincipals());
        }

        // Prevent name collisions while multiple threads are defining classes
        // by reserving the name.
        boolean reserveName(String name, boolean explicit) {
            synchronized (mReservedNames) {
                if (mReservedNames.put(name, Boolean.TRUE) != null && !explicit) {
                    return false;
                }
            }

            // If explicit and name has already been reserved, don't
            // immediately return false. This allows the class to be defined if
            // an earlier RuntimeClassFile instance was abandoned. A duplicate
            // class definition can still be attempted later, which is
            // converted to an IllegalStateException by the define method.

            try {
                loadClass(name);
            } catch (ClassNotFoundException e) {
                return true;
            } catch (LinkageError e) {
                // Class by same name exists, but it is broken.
            }

            return false;
        }

        Class define(String name, byte[] b) {
            try {
                Class clazz;
                if (mDomain == null) {
                    clazz = defineClass(name, b, 0, b.length);
                } else {
                    clazz = defineClass(name, b, 0, b.length, mDomain);
                }
                resolveClass(clazz);
                return clazz;
            } catch (LinkageError e) {
                // Replace duplicate name definition with a better exception.
                try {
                    loadClass(name);
                    throw new IllegalStateException("Class already defined: " + name);
                } catch (ClassNotFoundException e2) {
                }
                throw e;
            } finally {
                mReservedNames.remove(name);
            }
        }
    }

    private static final class LoaderAndName {
        final Loader mLoader;
        final String mClassName;

        LoaderAndName(Loader loader, String className) {
            mLoader = loader;
            mClassName = className;
        }
    }
}
TOP

Related Classes of org.cojen.classfile.RuntimeClassFile$LoaderAndName

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.