/*
* Copyright 2008-2014 the original author or authors
*
* 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.kaleidofoundry.core.plugin.processor;
import static javax.tools.StandardLocation.SOURCE_OUTPUT;
import static org.kaleidofoundry.core.i18n.InternalBundleHelper.PluginMessageBundle;
import static org.kaleidofoundry.core.plugin.PluginConstants.META_PLUGIN_FILE;
import static org.kaleidofoundry.core.plugin.PluginConstants.META_PLUGIN_IMPLEMENTATION_FILE;
import static org.kaleidofoundry.core.plugin.PluginConstants.META_PLUGIN_PATH;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import java.util.TreeSet;
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.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.AbstractElementVisitor6;
import javax.tools.Diagnostic.Kind;
import javax.tools.StandardLocation;
import org.kaleidofoundry.core.plugin.Declare;
import org.kaleidofoundry.core.plugin.PluginInspector;
import org.kaleidofoundry.core.plugin.PluginRegistryException;
/**
* Java 6 annotation processor, it is use to introspect (at compile time), specific kaleido plugin annotations :<br/>
* Use META-INF/services/javax.annotation.processing.Processor text file to enumerate your processor factory <br/>
* Handle annotation : <br/>
* <ul>
* <li>{@link Declare}</li>
* </ul>
* <br/>
* Processor will generate two resources files, use to registry plugin and plugin implementation at runtime. <br/>
* This two files are generated at compile time in :
* <ul>
* <li>META-INF/org.kaleidofoundry.core.plugin</li>
* <li>META-INF/org.kaleidofoundry.core.plugin.implementation</li>
* </ul>
* This files have to be include in your jar class resources.
*
* @see Declare
* @author jraduget
*/
@SupportedAnnotationTypes({ "org.kaleidofoundry.core.plugin.*" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
// @SupportedOptions( { "outputFile" })
public class PluginAnnotationProcessor extends AbstractProcessor {
protected ProcessingEnvironment environment;
/*
* (non-Javadoc)
* @see javax.annotation.processing.AbstractProcessor#init(javax.annotation.processing.ProcessingEnvironment)
*/
@Override
public void init(final ProcessingEnvironment environment) {
this.environment = environment;
}
/*
* (non-Javadoc)
* @see javax.annotation.processing.AbstractProcessor#process(java.util.Set, javax.annotation.processing.RoundEnvironment)
*/
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
final RegistryPluginVisitor registryVisitor = new RegistryPluginVisitor(environment);
boolean processedDeclarePlugin = false;
// scan and accept DeclarePlugin annotation
for (final Element element : roundEnvironment.getElementsAnnotatedWith(Declare.class)) {
element.accept(registryVisitor, null);
processedDeclarePlugin = true;
}
if (processedDeclarePlugin) {
try {
final PluginInspector pluginInspector = new PluginInspector();
// if all is right, create interface & class file
createOutputFile(registryVisitor.getPluginsSet(), SOURCE_OUTPUT, META_PLUGIN_PATH, META_PLUGIN_FILE,
PluginMessageBundle.getMessage("plugin.info.processor.load.createOutputFilePlugin", pluginInspector.getPluginMetaInfPath()));
createOutputFile(registryVisitor.getPluginsImplementationsSet(), SOURCE_OUTPUT, META_PLUGIN_PATH, META_PLUGIN_IMPLEMENTATION_FILE,
PluginMessageBundle.getMessage("plugin.info.processor.load.createOutputFilePluginImpl", pluginInspector.getPluginImplementationMetaInfPath()));
// load meta informations
pluginInspector.loadPluginMetaData();
pluginInspector.loadPluginImplementationMetaData();
// print load processing messages
for (final String message : pluginInspector.getEchoMessages()) {
environment.getMessager().printMessage(Kind.NOTE, message);
}
// coherence check
final Set<PluginRegistryException> pluginRegistryExceptions = pluginInspector.checkDeclarations();
if (!pluginRegistryExceptions.isEmpty()) {
// print coherence check errors messages
final StringBuilder errorMessage = new StringBuilder();
for (final PluginRegistryException pre : pluginRegistryExceptions) {
errorMessage.append("\t").append(pre.getMessage()).append("\n");
}
environment.getMessager().printMessage(Kind.ERROR, errorMessage.toString());
} else {
environment.getMessager().printMessage(Kind.NOTE, PluginMessageBundle.getMessage("plugin.info.processor.load.checkOk"));
}
} catch (final PluginRegistryException pre) {
// print registry error in console
environment.getMessager().printMessage(Kind.ERROR, pre.getMessage());
}
}
return processedDeclarePlugin;
}
/**
* Create output resource file, using {@link RegistryPluginVisitor} datas
*
* @param qualifierNames
* @param stdLocation
* @param packagename
* @param relativeOutputFilename
* @param messageNotif
*/
protected void createOutputFile(final Set<String> qualifierNames, final StandardLocation stdLocation, final String packagename,
final String relativeOutputFilename, final String messageNotif) {
final Filer filer = environment.getFiler();
final Messager messager = environment.getMessager();
Writer resourceWriter = null;
try {
// create resource file with env Filter
messager.printMessage(Kind.NOTE, messageNotif);
resourceWriter = filer.createResource(stdLocation, packagename, relativeOutputFilename).openWriter();
// for each annotation found on interface or class, write qualified name in text resource file
for (final String qualifier : qualifierNames) {
messager.printMessage(Kind.NOTE, "\t" + qualifier);
resourceWriter.append(qualifier).append("\n");
}
} catch (final IOException ioe) {
final String errorMessage = PluginMessageBundle.getMessage("plugin.error.processor.createOutputFilePlugin",
packagename + "/" + relativeOutputFilename, ioe.getMessage());
environment.getMessager().printMessage(Kind.ERROR, errorMessage);
throw new IllegalStateException(errorMessage);
} finally {
try {
if (resourceWriter != null) {
resourceWriter.close();
}
} catch (final IOException ioe) {
}
;
}
}
/**
* Annotation visitor for registering {@link Declare} use
*
* @author jraduget
*/
public static class RegistryPluginVisitor extends AbstractElementVisitor6<Void, Void> {
final Set<String> pluginsSet;
final Set<String> pluginsImplementationsSet;
final ProcessingEnvironment environment;
/**
* @param environment
*/
public RegistryPluginVisitor(final ProcessingEnvironment environment) {
this.environment = environment;
pluginsSet = new TreeSet<String>();
pluginsImplementationsSet = new TreeSet<String>();
}
/*
* (non-Javadoc)
* @see javax.lang.model.element.ElementVisitor#visitType(javax.lang.model.element.TypeElement, java.lang.Object)
*/
@Override
public Void visitType(final TypeElement e, final Void p) {
switch (e.getKind()) {
case CLASS: {
final Declare registerPluginAnnot = e.getAnnotation(Declare.class);
if (registerPluginAnnot != null) {
handleDeclarePluginImplementation(e, registerPluginAnnot);
}
break;
}
case INTERFACE: {
final Declare registerPluginAnnot = e.getAnnotation(Declare.class);
if (registerPluginAnnot != null) {
handleDeclarePlugin(e, registerPluginAnnot);
}
break;
}
default: {
break;
}
}
return null;
}
/*
* (non-Javadoc)
* @see javax.lang.model.element.ElementVisitor#visitExecutable(javax.lang.model.element.ExecutableElement, java.lang.Object)
*/
@Override
public Void visitExecutable(final ExecutableElement e, final Void p) {
return null;
}
/*
* (non-Javadoc)
* @see javax.lang.model.element.ElementVisitor#visitPackage(javax.lang.model.element.PackageElement, java.lang.Object)
*/
@Override
public Void visitPackage(final PackageElement e, final Void p) {
return null;
}
/*
* (non-Javadoc)
* @see javax.lang.model.element.ElementVisitor#visitTypeParameter(javax.lang.model.element.TypeParameterElement, java.lang.Object)
*/
@Override
public Void visitTypeParameter(final TypeParameterElement e, final Void p) {
return null;
}
/*
* (non-Javadoc)
* @see javax.lang.model.element.ElementVisitor#visitVariable(javax.lang.model.element.VariableElement, java.lang.Object)
*/
@Override
public Void visitVariable(final VariableElement e, final Void p) {
return null;
}
/**
* process handler for DeclarePlugin annotated interface
*
* @param interfaceDeclaration
* @param declarePluginAnnot
*/
void handleDeclarePlugin(final TypeElement interfaceDeclaration, final Declare declarePluginAnnot) {
if (declarePluginAnnot.enable()) {
pluginsSet.add(interfaceDeclaration.getQualifiedName().toString());
}
}
/**
* process handler for DeclarePluginImplementation annotated implementation class
*
* @param classDeclaration
* @param declarePluginImplementation
*/
void handleDeclarePluginImplementation(final TypeElement classDeclaration, final Declare declarePluginImplementation) {
if (declarePluginImplementation.enable()) {
pluginsImplementationsSet.add(classDeclaration.getQualifiedName().toString());
}
}
/**
* @return the pluginsSet
*/
Set<String> getPluginsSet() {
return pluginsSet;
}
/**
* @return the pluginsImplementationsSet
*/
Set<String> getPluginsImplementationsSet() {
return pluginsImplementationsSet;
}
}
}