/******************************************************************************* * Copyright (c) 2005, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.apt.dispatch; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import javax.annotation.processing.Processor; import javax.tools.StandardLocation; import org.eclipse.jdt.internal.compiler.batch.Main; import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; /** * Java 6 annotation processor manager used when compiling from the command line * or via the javax.tools.JavaCompiler interface. * @see org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeAnnotationProcessorManager */ public class BatchAnnotationProcessorManager extends BaseAnnotationProcessorManager { /** * Processors that have been set by calling CompilationTask.setProcessors(). */ private List<Processor> _setProcessors = null; private Iterator<Processor> _setProcessorIter = null; /** * Processors named with the -processor option on the command line. */ private List<String> _commandLineProcessors; private Iterator<String> _commandLineProcessorIter = null; private ServiceLoader<Processor> _serviceLoader = null; private Iterator<Processor> _serviceLoaderIter; private ClassLoader _procLoader; // Set this to true in order to trace processor discovery when -XprintProcessorInfo is specified private final static boolean VERBOSE_PROCESSOR_DISCOVERY = true; private boolean _printProcessorDiscovery = false; /** * Zero-arg constructor so this object can be easily created via reflection. * A BatchAnnotationProcessorManager cannot be used until its * {@link #configure(Object, String[])} method has been called. */ public BatchAnnotationProcessorManager() { } @Override public void configure(Object batchCompiler, String[] commandLineArguments) { if (null != _processingEnv) { throw new IllegalStateException( "Calling configure() more than once on an AnnotationProcessorManager is not supported"); //$NON-NLS-1$ } BatchProcessingEnvImpl processingEnv = new BatchProcessingEnvImpl(this, (Main) batchCompiler, commandLineArguments); _processingEnv = processingEnv; _procLoader = processingEnv.getFileManager().getClassLoader(StandardLocation.ANNOTATION_PROCESSOR_PATH); parseCommandLine(commandLineArguments); _round = 0; } /** * If a -processor option was specified in command line arguments, * parse it into a list of qualified classnames. * @param commandLineArguments contains one string for every space-delimited token on the command line */ private void parseCommandLine(String[] commandLineArguments) { List<String> commandLineProcessors = null; for (int i = 0; i < commandLineArguments.length; ++i) { String option = commandLineArguments[i]; if ("-XprintProcessorInfo".equals(option)) { //$NON-NLS-1$ _printProcessorInfo = true; _printProcessorDiscovery = VERBOSE_PROCESSOR_DISCOVERY; } else if ("-XprintRounds".equals(option)) { //$NON-NLS-1$ _printRounds = true; } else if ("-processor".equals(option)) { //$NON-NLS-1$ commandLineProcessors = new ArrayList<String>(); String procs = commandLineArguments[++i]; for (String proc : procs.split(",")) { //$NON-NLS-1$ commandLineProcessors.add(proc); } break; } } _commandLineProcessors = commandLineProcessors; if (null != _commandLineProcessors) { _commandLineProcessorIter = _commandLineProcessors.iterator(); } } @Override public ProcessorInfo discoverNextProcessor() { if (null != _setProcessors) { // If setProcessors() was called, use that list until it's empty and then stop. if (_setProcessorIter.hasNext()) { Processor p = _setProcessorIter.next(); p.init(_processingEnv); ProcessorInfo pi = new ProcessorInfo(p); _processors.add(pi); if (_printProcessorDiscovery && null != _out) { _out.println("API specified processor: " + pi); //$NON-NLS-1$ } return pi; } return null; } if (null != _commandLineProcessors) { // If there was a -processor option, iterate over processor names, // creating and initializing processors, until no more names are found, then stop. if (_commandLineProcessorIter.hasNext()) { String proc = _commandLineProcessorIter.next(); try { Class<?> clazz = _procLoader.loadClass(proc); Object o = clazz.newInstance(); Processor p = (Processor) o; p.init(_processingEnv); ProcessorInfo pi = new ProcessorInfo(p); _processors.add(pi); if (_printProcessorDiscovery && null != _out) { _out.println("Command line specified processor: " + pi); //$NON-NLS-1$ } return pi; } catch (Exception e) { // TODO: better error handling throw new AbortCompilation(null, e); } } return null; } // if no processors were explicitly specified with setProcessors() // or the command line, search the processor path with ServiceLoader. if (null == _serviceLoader ) { _serviceLoader = ServiceLoader.load(Processor.class, _procLoader); _serviceLoaderIter = _serviceLoader.iterator(); } try { if (_serviceLoaderIter.hasNext()) { Processor p = _serviceLoaderIter.next(); p.init(_processingEnv); ProcessorInfo pi = new ProcessorInfo(p); _processors.add(pi); if (_printProcessorDiscovery && null != _out) { StringBuilder sb = new StringBuilder(); sb.append("Discovered processor service "); //$NON-NLS-1$ sb.append(pi); sb.append("\n supporting "); //$NON-NLS-1$ sb.append(pi.getSupportedAnnotationTypesAsString()); sb.append("\n in "); //$NON-NLS-1$ sb.append(getProcessorLocation(p)); _out.println(sb.toString()); } return pi; } } catch (ServiceConfigurationError e) { // TODO: better error handling throw new AbortCompilation(null, e); } return null; } /** * Used only for debugging purposes. Generates output like "file:jar:D:/temp/jarfiles/myJar.jar!/". * Surely this code already exists in several hundred other places? * @return the location whence a processor class was loaded. */ private String getProcessorLocation(Processor p) { // Get the classname in a form that can be passed to ClassLoader.getResource(), // e.g., "pa/pb/pc/Outer$Inner.class" boolean isMember = false; Class<?> outerClass = p.getClass(); StringBuilder innerName = new StringBuilder(); while (outerClass.isMemberClass()) { innerName.insert(0, outerClass.getSimpleName()); innerName.insert(0, '$'); isMember = true; outerClass = outerClass.getEnclosingClass(); } String path = outerClass.getName(); path = path.replace('.', '/'); if (isMember) { path = path + innerName; } path = path + ".class"; //$NON-NLS-1$ // Find the URL for the class resource and strip off the resource name itself String location = _procLoader.getResource(path).toString(); if (location.endsWith(path)) { location = location.substring(0, location.length() - path.length()); } return location; } @Override public void reportProcessorException(Processor p, Exception e) { // TODO: if (verbose) report the processor throw new AbortCompilation(null, e); } @Override public void setProcessors(Object[] processors) { if (!_isFirstRound) { throw new IllegalStateException("setProcessors() cannot be called after processing has begun"); //$NON-NLS-1$ } // Cast all the processors here, rather than failing later. // But don't call init() until the processor is actually needed. _setProcessors = new ArrayList<Processor>(processors.length); for (Object o : processors) { Processor p = (Processor)o; _setProcessors.add(p); } _setProcessorIter = _setProcessors.iterator(); // processors set this way take precedence over anything on the command line _commandLineProcessors = null; _commandLineProcessorIter = null; } }