Java tutorial
/* * Copyright 2016 Oleg Khalidov * * 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.brooth.jeta.apt.processors; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeSpec; import org.brooth.jeta.apt.MetacodeUtils; import org.brooth.jeta.apt.ProcessingContext; import org.brooth.jeta.apt.ProcessingException; import org.brooth.jeta.apt.RoundContext; import org.brooth.jeta.inject.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * @author Oleg Khalidov (brooth@gmail.com) */ public class MetaModuleProcessor extends AbstractProcessor { private Set<? extends Element> allMetaEntities; @Nullable private String defaultScopeStr; public MetaModuleProcessor() { super(Module.class); } @Override public void init(ProcessingContext processingContext) { super.init(processingContext); defaultScopeStr = processingContext.processingProperties().getProperty("inject.scope.default", null); } @Override public boolean process(TypeSpec.Builder builder, RoundContext context) { if (allMetaEntities != null) throw new ProcessingException("Multiple modules defined"); TypeElement moduleElement = (TypeElement) context.elements().iterator().next(); final Module annotation = moduleElement.getAnnotation(Module.class); List<String> scopesStr = getScopesClasses(annotation); List<TypeElement> scopes = Lists.transform(scopesStr, new Function<String, TypeElement>() { @Override public TypeElement apply(String input) { TypeElement scopeElement = processingContext.processingEnv().getElementUtils() .getTypeElement(input); Scope scopeAnnotation = scopeElement.getAnnotation(Scope.class); if (scopeAnnotation == null) throw new ProcessingException(input + " is not a scope class. Put @Scope on it"); return scopeElement; } }); allMetaEntities = context.roundEnv().getElementsAnnotatedWith(MetaEntity.class); List<AnnotationSpec> scopeAnnotationBuilders = new ArrayList<>(scopes.size()); AnnotationSpec.Builder moduleConfigAnnotationBuilder = AnnotationSpec.builder(ModuleConfig.class); for (TypeElement scopeElement : scopes) { String scopeClassStr = scopeElement.getQualifiedName().toString(); TypeElement metaScopeElement = processingContext.processingEnv().getElementUtils() .getTypeElement(MetacodeUtils.toMetacodeName(scopeClassStr)); ScopeConfig metaScopeConfigAnnotation = metaScopeElement != null ? metaScopeElement.getAnnotation(ScopeConfig.class) : null; if (metaScopeConfigAnnotation != null) { String scopeModuleStr = getModuleClass(metaScopeConfigAnnotation); if (!moduleElement.getQualifiedName().toString().equals(scopeModuleStr)) { processingContext.logger().debug(scopeClassStr + " belongs to ext module " + scopeModuleStr); scopeAnnotationBuilders.add(AnnotationSpec.builder(ModuleConfig.ScopeConfig.class) .addMember("\nscope", scopeClassStr + ".class") .addMember("\nmodule", scopeModuleStr + ".class\n").build()); continue; } } Set<? extends Element> scopeEntities = getScopeEntities(scopeElement); Iterable<ClassName> entities = Iterables.transform(scopeEntities, new Function<Element, ClassName>() { @Override public ClassName apply(Element input) { MetaEntity metaEntityAnnotation = input.getAnnotation(MetaEntity.class); try { String ofTypeStr = getOfClass(metaEntityAnnotation); if (isVoid(ofTypeStr)) return ClassName.get((TypeElement) input); if (ofTypeStr.equals(getExtClass(metaEntityAnnotation))) return ClassName.get(Void.class); return ClassName.bestGuess(ofTypeStr); } catch (Exception e) { throw new ProcessingException( "Failed to get meta entity info of element " + input.toString(), e); } } }); scopeAnnotationBuilders.add(AnnotationSpec.builder(ModuleConfig.ScopeConfig.class) .addMember("\nscope", scopeClassStr + ".class") .addMember("\nentities", !entities.iterator().hasNext() ? "{}" : "{\n" + Joiner.on(".class,\n").join(entities) + ".class\n}", entities) .build()); } builder.addAnnotation(moduleConfigAnnotationBuilder.addMember("scopes", "{\n" + Joiner.on(",\n").join(scopeAnnotationBuilders) + "}", scopeAnnotationBuilders).build()); return false; } @Nonnull protected String getModuleClass(final ScopeConfig scopeConfig) { return MetacodeUtils.extractClassName(new Runnable() { @Override public void run() { scopeConfig.module(); } }); } @Nonnull private String getOfClass(final MetaEntity annotation) { return MetacodeUtils.extractClassName(new Runnable() { public void run() { annotation.of(); } }); } @Nonnull private String getExtClass(final MetaEntity annotation) { return MetacodeUtils.extractClassName(new Runnable() { public void run() { annotation.ext(); } }); } private List<String> getScopesClasses(final Module annotation) { return MetacodeUtils.extractClassesNames(new Runnable() { @Override public void run() { annotation.scopes(); } }); } private Set<? extends Element> getScopeEntities(TypeElement scopeElement) { final String scopeClassStr = scopeElement.getQualifiedName().toString(); final boolean isDefaultScope = defaultScopeStr != null && defaultScopeStr.equals(scopeClassStr); return Sets.filter(allMetaEntities, new Predicate<Element>() { public boolean apply(Element input) { final MetaEntity a = input.getAnnotation(MetaEntity.class); String scope = MetacodeUtils.extractClassName(new Runnable() { public void run() { a.scope(); } }); if (scopeClassStr.equals(scope)) return true; if (isVoid(scope)) { if (defaultScopeStr == null) throw new ProcessingException("Scope undefined for '" + input.getSimpleName().toString() + "'. " + "You need to set the scope via @MetaEntity(scope) or define default one as 'inject.scope.default' property"); if (isDefaultScope) return true; } return false; } }); } private boolean isVoid(String str) { return str.equals(Void.class.getCanonicalName()); } @Override public boolean ignoreUpToDate() { return true; } }