/**
* Copyright 2011-2015 John Ericksen
*
* 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.androidtransfuse;
import com.google.auto.service.AutoService;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import org.androidtransfuse.adapter.ASTAnnotation;
import org.androidtransfuse.adapter.ASTType;
import org.androidtransfuse.adapter.classes.ReloadableASTClassFactory;
import org.androidtransfuse.adapter.element.ASTElementConverterFactory;
import org.androidtransfuse.adapter.element.ReloadableASTElementFactory;
import org.androidtransfuse.annotations.*;
import org.androidtransfuse.bootstrap.Bootstrap;
import org.androidtransfuse.bootstrap.Bootstraps;
import org.androidtransfuse.config.EnterableScope;
import org.androidtransfuse.config.TransfuseAndroidModule;
import org.androidtransfuse.model.manifest.Manifest;
import org.androidtransfuse.model.r.RBuilder;
import org.androidtransfuse.model.r.RResource;
import org.androidtransfuse.model.r.RResourceComposite;
import org.androidtransfuse.plugins.PluginModule;
import org.androidtransfuse.processor.GenerateModuleProcessor;
import org.androidtransfuse.processor.TransfuseProcessor;
import org.androidtransfuse.scope.ScopeKey;
import org.androidtransfuse.util.Logger;
import org.androidtransfuse.util.ManifestLocator;
import org.androidtransfuse.util.ManifestSerializer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import java.io.File;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Set;
import static com.google.common.collect.Collections2.transform;
/**
* Transfuse Annotation processor. Kicks off the process of analyzing and generating code based on the compiled
* codebase.
*
* To use this class, you simply have to annotate your classes with the proper root components (Activity,
* Application, etc) and have this annotation processor on the classpath during a full compilation.
*
* This approach is compatible with Java 6 and above.
*
* See http://androidtransfuse.org for more details
*
* @author John Ericksen
*/
@SupportedAnnotations({
Activity.class,
Application.class,
BroadcastReceiver.class,
Service.class,
Fragment.class,
TransfuseModule.class,
Factory.class,
ImplementedBy.class,
Install.class})
@Bootstrap
@AutoService(Processor.class)
public class TransfuseAnnotationProcessor extends AnnotationProcessorBase {
@Inject
private ASTElementConverterFactory astElementConverterFactory;
@Inject
private ManifestSerializer manifestParser;
@Inject
private RBuilder rBuilder;
@Inject
private ReloadableASTElementFactory reloadableASTElementFactory;
@Inject
private ReloadableASTClassFactory reloadableASTClassFactory;
@Inject
private ManifestLocator manifestLocator;
@Inject
private Logger log;
@Inject
@ScopeReference(ConfigurationScope.class)
private EnterableScope configurationScope;
@Inject
private Provider<TransfuseProcessor> processorProvider;
@Inject
private Elements elements;
private boolean baseModuleConfiguration = false;
private int round = 0;
@Override
public void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Bootstraps.getInjector(TransfuseAnnotationProcessor.class)
.add(Singleton.class, ScopeKey.of(ProcessingEnvironment.class), processingEnv)
.inject(this);
}
@Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment roundEnvironment) {
log.debug("Annotation procesing started, round " + round++);
long start = System.currentTimeMillis();
//setup transfuse processor with manifest and R classes
File manifestFile = manifestLocator.findManifest();
Manifest manifest = manifestParser.readManifest(manifestFile);
RResourceComposite r = new RResourceComposite(
buildR(rBuilder, manifest.getApplicationPackage() + ".R"),
buildR(rBuilder, "android.R"));
configurationScope.enter();
configurationScope.seed(ScopeKey.of(File.class).annotatedBy("@javax.inject.Named(value=" + TransfuseAndroidModule.MANIFEST_FILE + ")"), manifestFile);
configurationScope.seed(ScopeKey.of(RResource.class), r);
configurationScope.seed(ScopeKey.of(Manifest.class).annotatedBy("@javax.inject.Named(value=" + TransfuseAndroidModule.ORIGINAL_MANIFEST + ")"), manifest);
TransfuseProcessor transfuseProcessor = processorProvider.get();
if (!baseModuleConfiguration) {
transfuseProcessor.submit(TransfuseModule.class, reloadableASTClassFactory.apply(PluginModule.class));
transfuseProcessor.submit(TransfuseModule.class, reloadableASTElementFactory.apply(elements.getTypeElement(APIModule.class.getName())));
baseModuleConfiguration = true;
}
transfuseProcessor.submit(Application.class, buildASTCollection(roundEnvironment, Application.class));
transfuseProcessor.submit(TransfuseModule.class, buildASTCollection(roundEnvironment, TransfuseModule.class));
transfuseProcessor.submit(ImplementedBy.class, buildASTCollection(roundEnvironment, ImplementedBy.class));
transfuseProcessor.submit(Factory.class, buildASTCollection(roundEnvironment, Factory.class));
transfuseProcessor.submit(Activity.class, buildASTCollection(roundEnvironment, Activity.class));
transfuseProcessor.submit(BroadcastReceiver.class, buildASTCollection(roundEnvironment, BroadcastReceiver.class));
transfuseProcessor.submit(Service.class, buildASTCollection(roundEnvironment, Service.class));
transfuseProcessor.submit(Fragment.class, buildASTCollection(roundEnvironment, Fragment.class));
transfuseProcessor.execute();
if (roundEnvironment.processingOver()) {
transfuseProcessor.logErrors();
}
log.debug("Took " + (System.currentTimeMillis() - start) + "ms to process");
configurationScope.exit();
return true;
}
private RResource buildR(RBuilder rBuilder, String className) {
TypeElement rTypeElement = elements.getTypeElement(className);
if (rTypeElement != null) {
Collection<ASTType> rInnerTypes = wrapASTCollection(ElementFilter.typesIn(rTypeElement.getEnclosedElements()));
return rBuilder.buildR(rInnerTypes);
}
return null;
}
private Collection<Provider<ASTType>> buildASTCollection(RoundEnvironment round, Class<? extends Annotation> annotation) {
ImmutableSet<? extends Element> components = ImmutableSet.<Element>builder()
.addAll(findInstalledComponents(round, annotation))
.addAll(round.getElementsAnnotatedWith(annotation))
.build();
return reloadableASTElementFactory.buildProviders(
FluentIterable.from(components)
.filter(new Predicate<Element>() {
public boolean apply(Element element) {
//we're only dealing with TypeElements
return element instanceof TypeElement;
}
})
.transform(new Function<Element, TypeElement>() {
public TypeElement apply(Element element) {
return (TypeElement) element;
}
})
.toList());
}
private Set<? extends Element> findInstalledComponents(RoundEnvironment round, final Class<? extends Annotation> annotation) {
Collection<ASTType> annotatedClasses =transform(round.getElementsAnnotatedWith(Install.class),
astElementConverterFactory.buildASTElementConverter(ASTType.class));
ImmutableSet.Builder<Element> builder = ImmutableSet.builder();
for (ASTType annotatedClass : annotatedClasses) {
ASTAnnotation installAstAnnotation = annotatedClass.getASTAnnotation(Install.class);
ASTType[] values = installAstAnnotation.getProperty("value", ASTType[].class);
for (ASTType value : values) {
if(value.isAnnotated(annotation)) {
builder.add(elements.getTypeElement(value.getName()));
}
}
}
return builder.build();
}
private Collection<ASTType> wrapASTCollection(Collection<? extends TypeElement> elementCollection) {
return transform(elementCollection,
astElementConverterFactory.buildASTElementConverter(ASTType.class)
);
}
@Override
public Set<String> getSupportedOptions() {
return ImmutableSet.of(
GenerateModuleProcessor.MANIFEST_PROCESSING_OPTION,
ManifestLocator.ANDROID_MANIFEST_FILE_OPTION);
}
}