com.google.devtools.j2objc.jdt.AnnotationPreProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.j2objc.jdt.AnnotationPreProcessor.java

Source

/*
 * 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 com.google.devtools.j2objc.jdt;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.j2objc.Options;
import com.google.devtools.j2objc.file.RegularInputFile;
import com.google.devtools.j2objc.gen.GenerationUnit;
import com.google.devtools.j2objc.pipeline.ProcessingContext;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.FileUtil;
import com.google.devtools.j2objc.util.PathClassLoader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.annotation.processing.Processor;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import org.eclipse.jdt.internal.compiler.AbstractAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchAnnotationProcessorManager;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchFilerImpl;
import org.eclipse.jdt.internal.compiler.apt.dispatch.BatchProcessingEnvImpl;
import org.eclipse.jdt.internal.compiler.batch.Main;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;

/**
 * Preprocesses all input with annotation processors, if any,
 * and adds any .java files generated by those annotation processors
 * to the given GenerationBatch.
 */
class AnnotationPreProcessor {

    private File tmpDirectory;
    private final List<ProcessingContext> generatedInputs = Lists.newArrayList();
    private final Options options;

    public AnnotationPreProcessor(Options options) {
        this.options = options;
    }

    public File getTemporaryDirectory() {
        return tmpDirectory;
    }

    /**
     * Process the given input files, given in the same format as J2ObjC command line args.
     *
     * @return the list of processor-generated sources
     */
    public List<ProcessingContext> process(Iterable<String> fileArgs, List<ProcessingContext> inputs) {
        assert tmpDirectory == null; // Shouldn't run an instance more than once.

        if (!hasAnnotationProcessors()) {
            return generatedInputs;
        }

        try {
            tmpDirectory = FileUtil.createTempDir("annotations");
        } catch (IOException e) {
            ErrorUtil.error("failed creating temporary directory: " + e);
            return generatedInputs;
        }
        String tmpDirPath = tmpDirectory.getAbsolutePath();
        List<String> compileArgs = Lists.newArrayList();
        Joiner pathJoiner = Joiner.on(":");
        List<String> sourcePath = options.fileUtil().getSourcePathEntries();
        sourcePath.add(tmpDirPath);
        compileArgs.add("-sourcepath");
        compileArgs.add(pathJoiner.join(sourcePath));
        compileArgs.add("-classpath");
        List<String> classPath = options.fileUtil().getClassPathEntries();
        compileArgs.add(pathJoiner.join(classPath));
        compileArgs.add("-encoding");
        compileArgs.add(options.fileUtil().getCharset().name());
        compileArgs.add("-source");
        compileArgs.add(options.getSourceVersion().flag());
        compileArgs.add("-s");
        compileArgs.add(tmpDirPath);
        compileArgs.add("-d");
        compileArgs.add(tmpDirPath);
        List<String> processorPath = options.getProcessorPathEntries();
        if (!processorPath.isEmpty()) {
            compileArgs.add("-processorpath");
            compileArgs.add(pathJoiner.join(processorPath));
        }
        String processorClasses = options.getProcessors();
        if (processorClasses != null) {
            compileArgs.add("-processor");
            compileArgs.add(processorClasses);
        }
        if (options.isVerbose()) {
            compileArgs.add("-XprintProcessorInfo");
            compileArgs.add("-XprintRounds");
        }
        for (String fileArg : fileArgs) {
            compileArgs.add(fileArg);
        }
        Map<String, String> batchOptions = Maps.newHashMap();
        batchOptions.put(CompilerOptions.OPTION_Process_Annotations, CompilerOptions.ENABLED);
        batchOptions.put(CompilerOptions.OPTION_GenerateClassFiles, CompilerOptions.DISABLED);
        AnnotationCompiler batchCompiler = new AnnotationCompiler(compileArgs, batchOptions,
                new PrintWriter(System.out), new PrintWriter(System.err), inputs, options);
        if (!batchCompiler.compile(compileArgs.toArray(new String[0]))) {
            // Any compilation errors will already by displayed.
            ErrorUtil.error("failed batch processing sources");
        }
        if (!options.getHeaderMap().includeGeneratedSources() && tmpDirectory != null) {
            collectGeneratedInputs(tmpDirectory, "", inputs);
        }
        return generatedInputs;
    }

    private void collectGeneratedInputs(File dir, String currentRelativePath, List<ProcessingContext> inputs) {
        assert dir.exists() && dir.isDirectory();
        for (File f : dir.listFiles()) {
            String relativeName = currentRelativePath + File.separatorChar + f.getName();
            if (f.isDirectory()) {
                collectGeneratedInputs(f, relativeName, inputs);
            } else {
                if (f.getName().endsWith(".java")) {
                    inputs.add(
                            ProcessingContext.fromFile(new RegularInputFile(f.getPath(), relativeName), options));
                }
            }
        }
    }

    /**
     * Check whether any javax.annotation.processing.Processor services are defined on
     * the declared classpath. This is checked here to avoid batch compiling sources
     * in case any might have annotations that should be processed.
     */
    private boolean hasAnnotationProcessors() {
        PathClassLoader loader = new PathClassLoader(options.fileUtil().getClassPathEntries());
        loader.addPaths(options.getProcessorPathEntries());
        ServiceLoader<Processor> serviceLoader = ServiceLoader.load(Processor.class, loader);
        Iterator<Processor> iterator = serviceLoader.iterator();
        return iterator.hasNext();
    }

    // Custom javax.annotation.processing.Filer implementation, used to filter new files
    // created by annotation processors.
    private static class AnnotationProcessorFiler extends BatchFilerImpl {
        private final List<ProcessingContext> inputs;
        private final Options options;

        AnnotationProcessorFiler(BaseAnnotationProcessorManager dispatchManager, BatchProcessingEnvImpl env,
                List<ProcessingContext> inputs, Options options) {
            super(dispatchManager, env);
            this.inputs = inputs;
            this.options = options;
        }

        @Override
        public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements)
                throws IOException {
            if (!options.getHeaderMap().includeGeneratedSources()) {
                return super.createSourceFile(name, originatingElements);
            }
            String referenceFile = null;
            TypeElement outerType = null;
            for (Element e : originatingElements) {
                while (e instanceof TypeElement) {
                    outerType = (TypeElement) e;
                    e = outerType.getEnclosingElement();
                }
                if (outerType instanceof ReferenceBinding) {
                    referenceFile = new String(((ReferenceBinding) outerType).getFileName());
                    break;
                }
            }
            if (referenceFile == null && outerType != null) {
                referenceFile = outerType.getQualifiedName().toString().replace('.', '/') + ".java";
            }
            JavaFileObject newSourceFile = super.createSourceFile(name, originatingElements);
            ProcessingContext generatedSource = null;
            if (referenceFile != null) {
                for (ProcessingContext context : inputs) {
                    if (context.getFile().getUnitName().endsWith(referenceFile)) {
                        GenerationUnit unit = context.getGenerationUnit();
                        generatedSource = new ProcessingContext(
                                new RegularInputFile(newSourceFile.toUri().getPath()), unit);
                        break;
                    }
                }
            }
            if (generatedSource == null) {
                String relativePath = name.toString().replace('.', '/') + ".java";
                generatedSource = ProcessingContext
                        .fromFile(new RegularInputFile(newSourceFile.toUri().getPath(), relativePath), options);
            }
            inputs.add(generatedSource);
            return newSourceFile;
        }
    }

    // Override batch compiler classes to use custom Filer.

    private static class AnnotationCompiler extends org.eclipse.jdt.internal.compiler.batch.Main {
        private final String[] commandLine;
        private final PrintWriter out;
        private final PrintWriter err;
        private final List<ProcessingContext> inputs;
        private final Options options;

        AnnotationCompiler(List<String> compileArgs, Map<String, String> batchOptions, PrintWriter stdOut,
                PrintWriter stdErr, List<ProcessingContext> inputs, Options options) {
            super(stdOut, stdErr, false, batchOptions, null);
            commandLine = compileArgs.toArray(new String[0]);
            out = stdOut;
            err = stdErr;
            this.inputs = inputs;
            this.options = options;
        }

        @Override
        protected void initializeAnnotationProcessorManager() {
            AbstractAnnotationProcessorManager annotationManager = new BatchAnnotationProcessorManager() {
                @Override
                public void configure(Object batchCompiler, String[] commandLineArguments) {
                    super.configure(batchCompiler, commandLineArguments);
                    BatchProcessingEnvImpl processingEnv = new AnnotationProcessingEnv(this, (Main) batchCompiler,
                            commandLineArguments, inputs, options);
                    _processingEnv = processingEnv;
                }
            };
            annotationManager.configure(this, commandLine);
            annotationManager.setErr(this.err);
            annotationManager.setOut(this.out);
            batchCompiler.annotationProcessorManager = annotationManager;
        }
    }

    private static class AnnotationProcessingEnv extends BatchProcessingEnvImpl {
        private AnnotationProcessingEnv(BaseAnnotationProcessorManager dispatchManager, Main batchCompiler,
                String[] commandLineArguments, List<ProcessingContext> inputs, Options options) {
            super(dispatchManager, batchCompiler, commandLineArguments);
            _filer = new AnnotationProcessorFiler(_dispatchManager, this, inputs, options);
        }
    }
}