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

com.hazelcast.org.codehaus.janino.Compiler Maven / Gradle / Ivy

There is a newer version: 5.5.0
Show newest version

/*
 * Janino - An embedded Java[TM] compiler
 *
 * Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *       following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *       following disclaimer in the documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
 *       products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.hazelcast.org.codehaus.janino;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.hazelcast.org.codehaus.commons.compiler.AbstractCompiler;
import com.hazelcast.org.codehaus.commons.compiler.CompileException;
import com.hazelcast.org.codehaus.commons.compiler.ICompiler;
import com.hazelcast.org.codehaus.commons.compiler.InternalCompilerException;
import com.hazelcast.org.codehaus.commons.compiler.Location;
import com.hazelcast.org.codehaus.commons.compiler.WarningHandler;
import com.hazelcast.org.codehaus.commons.compiler.java9.java.lang.module.ModuleFinder;
import com.hazelcast.org.codehaus.commons.compiler.java9.java.lang.module.ModuleReference;
import com.hazelcast.org.codehaus.commons.compiler.util.Benchmark;
import com.hazelcast.org.codehaus.commons.compiler.util.StringPattern;
import com.hazelcast.org.codehaus.commons.compiler.util.StringUtil;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.DirectoryResourceFinder;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.FileResource;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.FileResourceCreator;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.JarDirectoriesResourceFinder;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.MultiResourceFinder;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.PathResourceFinder;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.Resource;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.ResourceCreator;
import com.hazelcast.org.codehaus.commons.compiler.util.resource.ResourceFinder;
import com.hazelcast.org.codehaus.commons.nullanalysis.Nullable;
import com.hazelcast.org.codehaus.janino.util.ClassFile;

/**
 * JANINO implementation of {@link ICompiler}.
 */
public
class Compiler extends AbstractCompiler {

    private static final Logger LOGGER = Logger.getLogger(Compiler.class.getName());

    private EnumSet options = EnumSet.noneOf(JaninoOption.class);

    @Nullable private IClassLoader iClassLoader;

    private Benchmark benchmark = new Benchmark(false);

    // Compile time state:

    private final List parsedCompilationUnits = new ArrayList();

    /**
     * Initializes a new compiler.
     */
    public
    Compiler() {}

    /** @deprecated Use {@link #Compiler()} and the various configuration setters instead */
    @Deprecated public
    Compiler(ResourceFinder sourceFinder, IClassLoader parentIClassLoader) {
        this();
        this.setSourceFinder(sourceFinder);
        this.setIClassLoader(parentIClassLoader);
    }

    /** @deprecated Use {@link #Compiler()} and the various configuration setters instead */
    @Deprecated public
    Compiler(
        final File[]           sourcePath,
        final File[]           classPath,
        @Nullable final File[] extDirs,
        @Nullable final File[] bootClassPath,
        @Nullable final File   destinationDirectory,
        @Nullable final String characterEncoding,
        boolean                verbose,
        boolean                debugSource,
        boolean                debugLines,
        boolean                debugVars,
        StringPattern[]        warningHandlePatterns,
        boolean                rebuild
    ) {
        this.setSourcePath(sourcePath);
        this.setClassPath(classPath);
        this.setExtensionDirectories((File[]) Compiler.nullToEmptyArray(extDirs, File.class));
        this.setBootClassPath((File[]) Compiler.nullToEmptyArray(bootClassPath, File.class));
        this.setDestinationDirectory(destinationDirectory, rebuild);
        this.setCharacterEncoding(characterEncoding);
        this.setVerbose(verbose);
        this.setDebugSource(debugSource);
        this.setDebugLines(debugLines);
        this.setDebugVars(debugVars);
        this.setClassFileFinder(
            rebuild
            ? ResourceFinder.EMPTY_RESOURCE_FINDER
            : destinationDirectory == null // Compiler.NO_DESTINATION_DIRECTORY
            ? ICompiler.FIND_NEXT_TO_SOURCE_FILE
            : new DirectoryResourceFinder(destinationDirectory)
        );
        this.setVerbose(verbose);
        this.setDebugSource(debugSource);
        this.setDebugLines(debugLines);
        this.setDebugVars(debugVars);
        this.setCharacterEncoding(characterEncoding);
        this.setWarningHandler(
            new FilterWarningHandler(
                warningHandlePatterns,
                new WarningHandler() {

                    @Override public void
                    handleWarning(@Nullable String handle, String message, @Nullable Location location) {

                        StringBuilder sb = new StringBuilder();

                        if (location != null) sb.append(location).append(": ");

                        if (handle == null) {
                            sb.append("Warning: ");
                        } else {
                            sb.append("Warning ").append(handle).append(": ");
                        }

                        sb.append(message);

                        System.err.println(sb.toString());
                    }
                }
            )
        );

        this.benchmark.report("*** JANINO - an embedded compiler for the Java(TM) programming language");
        this.benchmark.report("*** For more information visit http://janino.codehaus.org");
        this.benchmark.report("Source path",             sourcePath);
        this.benchmark.report("Class path",              classPath);
        this.benchmark.report("Ext dirs",                extDirs);
        this.benchmark.report("Boot class path",         bootClassPath);
        this.benchmark.report("Destination directory",   destinationDirectory);
        this.benchmark.report("Character encoding",      characterEncoding);
        this.benchmark.report("Verbose",                 new Boolean(verbose));
        this.benchmark.report("Debug source",            new Boolean(debugSource));
        this.benchmark.report("Debug lines",             new Boolean(debugSource));
        this.benchmark.report("Debug vars",              new Boolean(debugSource));
        this.benchmark.report("Warning handle patterns", warningHandlePatterns);
        this.benchmark.report("Rebuild",                 new Boolean(rebuild));
    }

    @SuppressWarnings("unchecked") private static  T[]
    nullToEmptyArray(@Nullable T[] a, Class elementType) {
        return a != null ? a : (T[]) Array.newInstance(elementType, 0);
    }

    /**
     * The default value for the warningHandlerPatterns parameter of {@link Compiler#Compiler(File[], File[],
     * File[], File[], File, String, boolean, boolean, boolean, boolean, StringPattern[], boolean)}.
     */
    public static final StringPattern[] DEFAULT_WARNING_HANDLE_PATTERNS = StringPattern.PATTERNS_NONE;

    /**
     * @return A reference to the currently effective compilation options; changes to it take
     *         effect immediately
     */
    public EnumSet
    options() { return this.options; }

    /**
     * Sets the options for all future compilations.
     */
    public Compiler
    options(EnumSet options) {
        this.options = options;
        return this;
    }

    @Override public void
    compile(Resource[] sourceResources) throws CompileException, IOException {

        this.benchmark.beginReporting();
        try {

            final IClassLoader
            iClassLoader = new CompilerIClassLoader(this.sourceFinder, this.classFileFinder, this.getIClassLoader());

            // Initialize compile time fields.
            this.parsedCompilationUnits.clear();

            // Parse all source files.
            for (Resource sourceResource : sourceResources) {
                Compiler.LOGGER.log(Level.FINE, "Compiling \"{0}\"", sourceResource);

                UnitCompiler uc = new UnitCompiler(
                    this.parseAbstractCompilationUnit(
                        sourceResource.getFileName(),                   // fileName
                        new BufferedInputStream(sourceResource.open()), // inputStream
                        this.sourceCharset                              // charset
                    ),
                    iClassLoader
                );
                uc.setTargetVersion(this.targetVersion);
                uc.setCompileErrorHandler(this.compileErrorHandler);
                uc.setWarningHandler(this.warningHandler);
                uc.options(this.options);

                this.parsedCompilationUnits.add(uc);
            }

            // Compile all parsed compilation units. The vector of parsed CUs may grow while they are being compiled,
            // but eventually all CUs will be compiled.
            for (int i = 0; i < this.parsedCompilationUnits.size(); ++i) {
                UnitCompiler unitCompiler = (UnitCompiler) this.parsedCompilationUnits.get(i);

                File sourceFile;
                {
                    Java.AbstractCompilationUnit acu = unitCompiler.getAbstractCompilationUnit();
                    if (acu.fileName == null) throw new InternalCompilerException();
                    sourceFile = new File(acu.fileName);
                }

                unitCompiler.setTargetVersion(this.targetVersion);
                unitCompiler.setCompileErrorHandler(this.compileErrorHandler);
                unitCompiler.setWarningHandler(this.warningHandler);

                this.benchmark.beginReporting("Compiling compilation unit \"" + sourceFile + "\"");
                ClassFile[] classFiles;
                try {

                    // Compile the compilation unit.
                    classFiles = unitCompiler.compileUnit(this.debugSource, this.debugLines, this.debugVars);
                } finally {
                    this.benchmark.endReporting();
                }

                // Store the compiled classes and interfaces into class files.
                this.benchmark.beginReporting(
                    "Storing "
                    + classFiles.length
                    + " class file(s) resulting from compilation unit \""
                    + sourceFile
                    + "\""
                );
                try {
                    for (ClassFile classFile : classFiles) this.storeClassFile(classFile, sourceFile);
                } finally {
                    this.benchmark.endReporting();
                }
            }
        } finally {
            this.benchmark.endReporting("Compiled " + this.parsedCompilationUnits.size() + " compilation unit(s)");
        }
    }

    /**
     * Reads one compilation unit from a file and parses it.
     * 

* The inputStream is closed before the method returns. *

* * @return the parsed compilation unit */ private Java.AbstractCompilationUnit parseAbstractCompilationUnit( String fileName, InputStream inputStream, Charset charset ) throws CompileException, IOException { try { Scanner scanner = new Scanner(fileName, new InputStreamReader(inputStream, charset)); Parser parser = new Parser(scanner); parser.setSourceVersion(this.sourceVersion); parser.setWarningHandler(this.warningHandler); this.benchmark.beginReporting("Parsing \"" + fileName + "\""); try { return parser.parseAbstractCompilationUnit(); } finally { this.benchmark.endReporting(); } } finally { inputStream.close(); } } /** * Constructs the name of a file that could store the byte code of the class with the given name. *

* If destinationDirectory is non-{@code null}, the returned path is the * destinationDirectory plus the package of the class (with dots replaced with file separators) plus * the class name plus ".class". Example: "destdir/pkg1/pkg2/Outer$Inner.class" *

*

* If destinationDirectory is null, the returned path is the directory of the sourceFile * plus the class name plus ".class". Example: "srcdir/Outer$Inner.class" *

* * @param className E.g. {@code "pkg1.pkg2.Outer$Inner"} * @param sourceFile E.g. {@code "srcdir/Outer.java"} * @param destinationDirectory E.g. {@code "destdir"} */ public static File getClassFile(String className, File sourceFile, @Nullable File destinationDirectory) { if (destinationDirectory != null) { return new File(destinationDirectory, ClassFile.getClassFileResourceName(className)); } else { int idx = className.lastIndexOf('.'); return new File( sourceFile.getParentFile(), ClassFile.getClassFileResourceName(className.substring(idx + 1)) ); } } /** * Stores the byte code of this {@link ClassFile} in the file system. Directories are created as necessary. * * @param classFile * @param sourceFile Required to compute class file path if no destination directory given */ public void storeClassFile(ClassFile classFile, final File sourceFile) throws IOException { String classFileResourceName = ClassFile.getClassFileResourceName(classFile.getThisClassName()); // Determine where to create the class file. ResourceCreator rc; if (this.classFileCreator != ICompiler.CREATE_NEXT_TO_SOURCE_FILE) { rc = this.classFileCreator; assert rc != null; } else { // If the JAVAC option "-d" is given, place the class file next // to the source file, irrespective of the package name. rc = new FileResourceCreator() { @Override protected File getFile(String resourceName) { return new File( sourceFile.getParentFile(), resourceName.substring(resourceName.lastIndexOf('/') + 1) ); } }; } OutputStream os = rc.createResource(classFileResourceName); try { classFile.store(os); } catch (IOException ioe) { try { os.close(); } catch (IOException e) {} os = null; if (!rc.deleteResource(classFileResourceName)) { IOException ioe2 = new IOException( "Could not delete incompletely written class file \"" + classFileResourceName + "\"" ); ioe2.initCause(ioe); throw ioe2; // SUPPRESS CHECKSTYLE AvoidHidingCause } throw ioe; } finally { if (os != null) try { os.close(); } catch (IOException e) {} } } /** * Explicitly sets the {@link IClassLoader} that will be used to load "auxiliary classes". If this method is used, * then {@link #setBootClassPath(File[])}, {@link #setExtensionDirectories(File[])} and {@link * #setClassPath(File[])} have no more effect. */ public void setIClassLoader(IClassLoader iClassLoader) { this.iClassLoader = iClassLoader; } @Override public void setVerbose(boolean verbose) { this.benchmark = new Benchmark(verbose); } /** * @return Loads "auxiliary classes", either through the {@link IClassLoader} that was explicitly set with * {@link #setIClassLoader(IClassLoader)}, or otherwise from {@link #setBootClassPath(File[])}, {@link * #setExtensionDirectories(File[])} and {@link #setClassPath */ private IClassLoader getIClassLoader() { if (this.iClassLoader != null) return this.iClassLoader; ResourceFinder classPathResourceFinder; File[] bcp = this.bootClassPath; if (bcp == null) { String sbcp = System.getProperty("sun.boot.class.path"); if (sbcp != null) { this.bootClassPath = (bcp = StringUtil.parsePath(sbcp)); } } if (bcp != null) { // JVM 1.0-1.8; BOOTCLASSPATH supported: classPathResourceFinder = new MultiResourceFinder(Arrays.asList( new PathResourceFinder(bcp), new JarDirectoriesResourceFinder(this.extensionDirectories), new PathResourceFinder(this.classPath) )); } else { // JVM 9+: "Modules" replace the BOOTCLASSPATH: URL r = ClassLoader.getSystemClassLoader().getResource("java/lang/Object.class"); assert r != null; assert "jrt".equalsIgnoreCase(r.getProtocol()) : r.toString(); ResourceFinder rf = new ResourceFinder() { @Override @Nullable public Resource findResource(final String resourceName) { try { final Set mrs = ModuleFinder.ofSystem().findAll(); for (final ModuleReference mr : mrs) { final URI moduleContentLocation = (URI) mr.location().get(); final URL classFileUrl = new URL(moduleContentLocation + "/" + resourceName); // SUPPRESS CHECKSTYLE LineLength final URLConnection uc = classFileUrl.openConnection(); try { uc.connect(); return new Resource() { @Override public InputStream open() throws IOException { try { return uc.getInputStream(); } catch (IOException ioe) { throw new IOException(moduleContentLocation + ", " + resourceName, ioe); } } @Override public String getFileName() { return resourceName; } @Override public long lastModified() { return uc.getLastModified(); } }; } catch (IOException ioe) { ; } } return null; } catch (Exception e) { throw new AssertionError(e); } } }; classPathResourceFinder = new MultiResourceFinder(Arrays.asList( rf, new JarDirectoriesResourceFinder(this.extensionDirectories), new PathResourceFinder(this.classPath) )); } return (this.iClassLoader = new ResourceFinderIClassLoader(classPathResourceFinder, null)); } /** * A specialized {@link IClassLoader} that loads {@link IClass}es from the following sources: *
    *
  1. An already-parsed compilation unit *
  2. A class file in the output directory (if existent and younger than source file) *
  3. A source file in any of the source path directories *
  4. The parent class loader *
*

* Notice that the {@link CompilerIClassLoader} is an inner class of {@link Compiler} and heavily uses {@link * Compiler}'s members. *

*/ private class CompilerIClassLoader extends IClassLoader { private final ResourceFinder sourceFinder; @Nullable private final ResourceFinder classFileFinder; /** * @param sourceFinder Where to look for more source files * @param classFileFinder Where to look for previously generated .class resources, or {@link * #FIND_NEXT_TO_SOURCE_FILE} * @param parentIClassLoader {@link IClassLoader} through which {@link IClass}es are to be loaded */ CompilerIClassLoader( ResourceFinder sourceFinder, @Nullable ResourceFinder classFileFinder, IClassLoader parentIClassLoader ) { super(parentIClassLoader); this.sourceFinder = sourceFinder; this.classFileFinder = classFileFinder; super.postConstruct(); } /** * @param type field descriptor of the {@link IClass} to load, e.g. {@code * "Lpkg1/pkg2/Outer$Inner;"} * @return {@code null} if a the type could not be found * @throws ClassNotFoundException An exception was raised while loading the {@link IClass} */ @Override @Nullable protected IClass findIClass(final String type) throws ClassNotFoundException { Compiler.LOGGER.entering(null, "findIClass", type); // Determine the class name. String className = Descriptor.toClassName(type); // E.g. "pkg1.pkg2.Outer$Inner" Compiler.LOGGER.log(Level.FINE, "className={0}", className); // Do not attempt to load classes from package "java". if (className.startsWith("java.")) return null; // Determine the name of the top-level class. E.g. "pkg.$Outer$Inner" => "pkg.$Outer". for ( int idx = className.indexOf('$', className.lastIndexOf('.') + 2);; idx = className.indexOf('$', idx + 2) ) { String topLevelClassName = idx == -1 ? className : className.substring(0, idx); // Check the already-parsed compilation units. for (int i = 0; i < Compiler.this.parsedCompilationUnits.size(); ++i) { UnitCompiler uc = (UnitCompiler) Compiler.this.parsedCompilationUnits.get(i); IClass res = uc.findClass(topLevelClassName); if (res != null) { if (!className.equals(topLevelClassName)) { res = uc.findClass(className); if (res == null) return null; } this.defineIClass(res); return res; } } if (idx == -1) break; } // Search source path for uncompiled class. final Resource sourceResource = this.sourceFinder.findResource(ClassFile.getSourceResourceName(className)); if (sourceResource == null) return null; // Find an existing class file. ResourceFinder cff = this.classFileFinder; Resource classFileResource; if (cff != ICompiler.FIND_NEXT_TO_SOURCE_FILE) { assert cff != null; classFileResource = cff.findResource( ClassFile.getClassFileResourceName(className) ); } else { if (!(sourceResource instanceof FileResource)) return null; File classFile = new File( ((FileResource) sourceResource).getFile().getParentFile(), ClassFile.getClassFileResourceName(className.substring(className.lastIndexOf('.') + 1)) ); classFileResource = classFile.exists() ? new FileResource(classFile) : null; } // Compare source modification time against class file modification time. if (classFileResource != null && sourceResource.lastModified() <= classFileResource.lastModified()) { // The class file is up-to-date; load it. return this.defineIClassFromClassFileResource(classFileResource); } else { // Source file not yet compiled or younger than class file. return this.defineIClassFromSourceResource(sourceResource, className); } } /** * Parses the compilation unit stored in the given sourceResource, remembers it in {@code * Compiler.this.parsedCompilationUnits} (it may declare other classes that are needed later), finds the * declaration of the type with the given className, and defines it in the {@link IClassLoader}. *

* Notice that the compilation unit is not compiled here! *

*/ private IClass defineIClassFromSourceResource(Resource sourceResource, String className) throws ClassNotFoundException { // Parse the source file. UnitCompiler uc; try { Java.AbstractCompilationUnit acu = Compiler.this.parseAbstractCompilationUnit( sourceResource.getFileName(), // fileName new BufferedInputStream(sourceResource.open()), // inputStream Compiler.this.sourceCharset // charset ); uc = new UnitCompiler(acu, this).options(Compiler.this.options); } catch (IOException ex) { throw new ClassNotFoundException("Parsing compilation unit \"" + sourceResource + "\"", ex); } catch (CompileException ex) { throw new ClassNotFoundException("Parsing compilation unit \"" + sourceResource + "\"", ex); } // Remember compilation unit for later compilation. Compiler.this.parsedCompilationUnits.add(uc); // Define the class. IClass res = uc.findClass(className); if (res == null) { // This is a really complicated case: We may find a source file on the source // path that seemingly contains the declaration of the class we are looking // for, but doesn't. This is possible if the underlying file system has // case-insensitive file names and/or file names that are limited in length // (e.g. DOS 8.3). throw new ClassNotFoundException("\"" + sourceResource + "\" does not declare \"" + className + "\""); } this.defineIClass(res); return res; } /** * Opens the given classFileResource, reads its contents, defines it in the {@link IClassLoader}, * and resolves it (this step may involve loading more classes). */ private IClass defineIClassFromClassFileResource(Resource classFileResource) throws ClassNotFoundException { Compiler.this.benchmark.beginReporting("Loading class file \"" + classFileResource.getFileName() + "\""); try { InputStream is = null; ClassFile cf; try { is = classFileResource.open(); cf = new ClassFile(new BufferedInputStream(is)); } catch (IOException ex) { throw new ClassNotFoundException("Opening class file resource \"" + classFileResource + "\"", ex); } finally { if (is != null) try { is.close(); } catch (IOException e) {} } ClassFileIClass result = new ClassFileIClass( cf, // classFile CompilerIClassLoader.this // iClassLoader ); // Important: We must FIRST call "defineIClass()" so that the // new IClass is known to the IClassLoader, and THEN // "resolveAllClasses()", because otherwise endless recursion could // occur. this.defineIClass(result); result.resolveAllClasses(); return result; } finally { Compiler.this.benchmark.endReporting(); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy