All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.joor.Compile Maven / Gradle / Ivy

Go to download

jOOR is a recursive acronym that stands for jOOR Object Oriented Reflection. It is a simple wrapper for the java.lang.reflect package.

There is a newer version: 0.9.15
Show newest version
/*
 * 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.joor;

/* [java-8] */

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.net.URI;
import java.security.SecureClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;


/**
 * A utility that simplifies in-memory compilation of new classes.
 *
 * @author Lukas Eder
 */
class Compile {

    static Class compile(String className, String content) {
        Lookup lookup = MethodHandles.lookup();

        try {
            return lookup.lookupClass().getClassLoader().loadClass(className);
        }
        catch (ClassNotFoundException ignore) {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

            try {
                ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));

                List files = new ArrayList();
                files.add(new CharSequenceJavaFileObject(className, content));

                compiler.getTask(null, fileManager, null, null, null, files).call();
                Class result = null;

                // This works if we have private-access to the interfaces in the class hierarchy
                if (Reflect.CACHED_LOOKUP_CONSTRUCTOR != null) {
                    ClassLoader cl = lookup.lookupClass().getClassLoader();
                    byte[] b = fileManager.o.getBytes();
                    result = Reflect.on(cl).call("defineClass", className, b, 0, b.length).get();
                }
                /* [java-9] */

                // Lookup.defineClass() has only been introduced in Java 9. It is
                // required to get private-access to interfaces in the class hierarchy
                else {

                    // This method is called by client code from two levels up the current stack frame
                    // We need a private-access lookup from the class in that stack frame in order to get
                    // private-access to any local interfaces at that location.
                    Class caller = StackWalker
                        .getInstance(RETAIN_CLASS_REFERENCE)
                        .walk(s -> s
                            .skip(2)
                            .findFirst()
                            .get()
                            .getDeclaringClass());

                    // If the compiled class is in the same package as the caller class, then
                    // we can use the private-access Lookup of the caller class
                    if (className.startsWith(caller.getPackageName() + ".")) {
                        result = MethodHandles
                            .privateLookupIn(caller, lookup)
                            .defineClass(fileManager.o.getBytes());
                    }

                    // Otherwise, use an arbitrary class loader. This approach doesn't allow for
                    // loading private-access interfaces in the compiled class's type hierarchy
                    else {
                        result = new ClassLoader() {
                            @Override
                            protected Class findClass(String name) throws ClassNotFoundException {
                                byte[] b = fileManager.o.getBytes();
                                return defineClass(className, b, 0, b.length);
                            }
                        }.loadClass(className);
                    }
                }
                /* [/java-9] */

                return result;
            }
            catch (Exception e) {
                throw new ReflectException("Error while compiling " + className, e);
            }
        }
    }

    static final class JavaFileObject extends SimpleJavaFileObject {
        final ByteArrayOutputStream os = new ByteArrayOutputStream();

        JavaFileObject(String name, JavaFileObject.Kind kind) {
            super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
        }

        byte[] getBytes() {
            return os.toByteArray();
        }

        @Override
        public OutputStream openOutputStream() {
            return os;
        }
    }

    static final class ClassFileManager extends ForwardingJavaFileManager {
        JavaFileObject o;

        ClassFileManager(StandardJavaFileManager standardManager) {
            super(standardManager);
        }

        @Override
        public JavaFileObject getJavaFileForOutput(
            JavaFileManager.Location location,
            String className,
            JavaFileObject.Kind kind,
            FileObject sibling
        ) {
            return o = new JavaFileObject(className, kind);
        }
    }

    static final class CharSequenceJavaFileObject extends SimpleJavaFileObject {
        final CharSequence content;

        public CharSequenceJavaFileObject(String className, CharSequence content) {
            super(URI.create("string:///" + className.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension), JavaFileObject.Kind.SOURCE);
            this.content = content;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return content;
        }
    }
}
/* [/java-8] */




© 2015 - 2024 Weber Informatics LLC | Privacy Policy