Java tutorial
/** * Copyright 2011 Nikolche Mihajlovski * * This file is part of JAnnocessor. * * JAnnocessor is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JAnnocessor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JAnnocessor. If not, see <http://www.gnu.org/licenses/>. */ package org.jannocessor.processor; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.FileObject; import javax.tools.JavaFileManager.Location; import javax.tools.StandardLocation; import org.apache.commons.io.IOUtils; import org.jannocessor.JannocessorException; import org.jannocessor.adapter.SourceHolder; import org.jannocessor.collection.Power; import org.jannocessor.context.Config; import org.jannocessor.context.Configuration; import org.jannocessor.engine.JannocessorEngine; import org.jannocessor.engine.impl.ProcessorModule; import org.jannocessor.inject.ImportsServiceModule; import org.jannocessor.processor.api.CodeMerger; import org.jannocessor.processor.api.FileInformation; import org.jannocessor.processor.api.RenderRegister; import org.jannocessor.processor.context.DefaultFileInformation; import org.jannocessor.processor.context.GeneratedCode; import org.jannocessor.processor.context.GeneratedFile; import org.jannocessor.processor.context.Problem; import org.jannocessor.processor.context.Problems; import org.jannocessor.processor.context.ProcessorsConfiguration; import org.jannocessor.service.configuration.ConfigurationServiceModule; import org.jannocessor.service.io.IOServiceModule; import org.jannocessor.service.render.TemplateServiceModule; import org.jannocessor.service.representation.RepresentationServiceModule; import org.jannocessor.service.splitter.SplitterServiceModule; import org.jannocessor.util.Jannocessor; import org.jannocessor.util.logging.JannocessorLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.inject.Guice; import com.google.inject.Injector; public abstract class JannocessorProcessorBase extends AbstractProcessor { protected Logger logger = LoggerFactory.getLogger("JANNOCESSOR"); protected boolean valid = true; protected Elements elementUtils; protected Types typeUtils; protected Filer filer; private Configuration options; protected Map<String, GeneratedFile> files = new HashMap<String, GeneratedFile>(); protected List<GeneratedCode> contents = new ArrayList<GeneratedCode>(); protected Problems problems = new Problems(); protected JannocessorEngine engine; protected RenderRegister renderRegister; private Messager messager; private List<String> globalErrors = new ArrayList<String>(); private List<String> globalWarnings = new ArrayList<String>(); private Injector injector; protected ProcessorsConfiguration processorsConfig; private String outputPath; public JannocessorProcessorBase() { logger.info("Instantiated Jannocessor"); } private Injector createInjector() { return Guice.createInjector(new ProcessorModule(options), new ConfigurationServiceModule(), new ImportsServiceModule(), new IOServiceModule(), new TemplateServiceModule(), new SplitterServiceModule(), new RepresentationServiceModule()); } protected void logException(JannocessorException e) { String msg = ""; Throwable error = e; while (error != null) { String cls = error.getClass().getSimpleName(); if (error instanceof JannocessorException) { msg += error.getMessage(); } else if (error.getMessage() != null) { msg += cls + " (" + error.getMessage() + ")"; } else { msg += cls; } error = error.getCause(); if (error != null) { msg += "\n"; } } addGlobalError(msg); logger.error(msg); } protected void addGlobalError(String msg) { globalErrors.add(msg); } protected void addGlobalWarning(String msg) { globalWarnings.add(msg); } @Override public synchronized void init(ProcessingEnvironment env) { // overwrite the class loader set by the Maven plugin Thread.currentThread().setContextClassLoader(JannocessorEngine.class.getClassLoader()); super.init(env); try { messager = env.getMessager(); JannocessorLogger.messager = messager; elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); options = new Config(env.getOptions()); injector = createInjector(); processOptions(); logger.info("Initializing services..."); engine = injector.getInstance(JannocessorEngine.class); engine.configure(engine.getTemplatesPath(), true); // recompileProcessors(); Class<?> hotConfig = Jannocessor.reloadClass("org.jannocessor.config.Processors", Power.emptyList(String.class)); processorsConfig = new ProcessorsConfiguration(hotConfig); showConfiguration(); logger.info("Initialization finished."); } catch (Exception e) { logger.error("Initialization failed!"); valid = false; throw new RuntimeException(e); } } private void showConfiguration() throws JannocessorException { logger.info("Templates path: {}", engine.getTemplatesPath()); } private void processOptions() throws JannocessorException { logger.info("Options:"); Set<Entry<String, String>> entries = options.getAllProperties().entrySet(); for (Entry<String, String> entry : entries) { logger.info("- Option: {} = '{}'", entry.getKey(), entry.getValue()); processOption(entry.getKey(), entry.getValue()); } } protected void processOption(String key, String value) { } @Override public Set<String> getSupportedAnnotationTypes() { if (valid) { logger.info("Specifying supported annotations..."); try { return retrieveSupportedAnnotations(); } catch (Exception e) { logger.error("Cannot specify supported annotations", e); throw new RuntimeException(e); } } else { return Collections.emptySet(); } } protected abstract Set<String> retrieveSupportedAnnotations() throws JannocessorException; @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { logger.info("Entering annotation processor..."); if (valid) { if (!env.processingOver()) { logger.info("Processing resources..."); try { processAnnotations(annotations, env); logger.info("Successfully finished processing."); } catch (JannocessorException e) { logException(e); } catch (Exception e) { throw new RuntimeException("Unexpected error occured", e); } processProblems(env); } else { logger.info("Last round, processing is done."); } return true; } else { logger.error("Canceled processing, due to error."); return false; } } private void processProblems(RoundEnvironment env) { for (Problem error : problems.getErrors()) { if (error.getElement() instanceof SourceHolder) { SourceHolder sourceHolder = (SourceHolder) error.getElement(); error(error.getMessage(), sourceHolder.retrieveSourceElement()); } else { throw new IllegalStateException("Expected source holder"); } } for (Problem warning : problems.getWarnings()) { if (warning.getElement() instanceof SourceHolder) { SourceHolder sourceHolder = (SourceHolder) warning.getElement(); warning(warning.getMessage(), sourceHolder.retrieveSourceElement()); } else { throw new IllegalStateException("Expected source holder"); } } Set<? extends Element> roots = env.getRootElements(); for (Element rootElement : roots) { for (String globalError : globalErrors) { error(globalError, rootElement); } for (String globalWarning : globalWarnings) { warning(globalWarning, rootElement); } } } protected void writeToFile(Location location, String pkg, String filename, String text, CodeMerger merger) throws JannocessorException { boolean mergeFile = merger != null; String info = fileInfo(location, pkg, filename); String operation = mergeFile ? "Merging" : "Writing"; logger.info("{} generated code ({} characters) to file: {}", new Object[] { operation, text.length(), info }); try { if (mergeFile) { FileInformation oldCode = readFile(location, pkg, filename); if (oldCode != null) { FileInformation newCode = new DefaultFileInformation(text, oldCode.getFilename(), new Date()); text = merger.mergeCode(oldCode, newCode); } else { logger.warn("Couldn't merge non-existing file: {}", info); } } } catch (Exception e) { throw new JannocessorException("Couldn't merge file: " + info, e); } Writer writer = null; try { FileObject fileRes = filer.createResource(location, pkg, filename); writer = fileRes.openWriter(); writer.write(text); } catch (IOException e) { throw new JannocessorException("Couldn't write to file: " + info, e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { throw new JannocessorException("Couldn't close file: " + info, e); } } } } private FileInformation readFile(Location location, String pkg, String filename) { try { FileObject file = filer.getResource(location, pkg, filename); InputStream inputStream = file.openInputStream(); String content = IOUtils.toString(inputStream); inputStream.close(); Date lastModified = new Date(file.getLastModified()); String name = new File(file.toUri()).getCanonicalPath(); return new DefaultFileInformation(content, name, lastModified); } catch (IOException e) { return null; } } protected abstract void processAnnotations(Set<? extends TypeElement> annotations, RoundEnvironment env) throws JannocessorException; protected String fileInfo(Location location, String pkg, String filename) { return String.format("%s/%s", pkg, filename); } protected void error(String msg, Element element) { messager.printMessage(Diagnostic.Kind.ERROR, msg, element); } protected void warning(String msg, Element element) { messager.printMessage(Diagnostic.Kind.WARNING, msg, element); } private String getPath(Location location) { String path; try { FileObject tempFile = filer.getResource(location, "", "jannocessor_temporary"); path = tempFile.toUri().getPath().replaceFirst("/jannocessor_temporary$", "").substring(1); } catch (Exception e) { try { FileObject tempFile = filer.createResource(location, "", "jannocessor_temporary"); path = tempFile.toUri().getPath().replaceFirst("/jannocessor_temporary$", "").substring(1); tempFile.delete(); } catch (Exception e2) { throw new RuntimeException("Cannot calculate path: " + location, e2); } } return path; } protected String getOutputPath() { if (outputPath == null) { outputPath = getPath(StandardLocation.SOURCE_OUTPUT); } return outputPath; } }