From 7acd97f63fdd4fad9fbbdbf7e941dda03b04e44a Mon Sep 17 00:00:00 2001 From: Zachary Heidepriem Date: Fri, 6 Apr 2018 12:28:36 -0700 Subject: Reorganized rootcomponentgenerator package and added demo. -Moved rootcomponentgenerator/annotations to dialer/inject to reduce the number of deps engineers need to include -Move rootcomponentgenerator/processor into rootcomponentgenerator/ since the other package is now gone -Added inject/demo and tests TEST=unit, tap Test: unit, tap PiperOrigin-RevId: 191916595 Change-Id: Ic155808c7435bdce198970caa92309edb9fadac8 --- java/com/android/dialer/inject/ContextModule.java | 1 + .../android/dialer/inject/DialerRootComponent.java | 44 ++++ java/com/android/dialer/inject/DialerVariant.java | 44 ++++ .../android/dialer/inject/IncludeInDialerRoot.java | 47 ++++ java/com/android/dialer/inject/InstallIn.java | 40 +++ .../inject/RootComponentGeneratorMetadata.java | 31 +++ .../dialer/inject/demo/DemoDaggerApplication.java | 55 +++++ .../com/android/dialer/inject/demo/DemoModule.java | 35 +++ .../com/android/dialer/inject/demo/DemoObject.java | 32 +++ .../dialer/inject/demo/DemoSubcomponent.java | 40 +++ .../ComponentGeneratingStep.java | 267 +++++++++++++++++++++ .../MetadataGeneratingStep.java | 92 +++++++ .../RootComponentGeneratingStep.java | 146 +++++++++++ .../RootComponentProcessor.java | 40 +++ .../rootcomponentgenerator/RootComponentUtils.java | 48 ++++ .../annotation/DialerRootComponent.java | 44 ---- .../annotation/DialerVariant.java | 42 ---- .../annotation/IncludeInDialerRoot.java | 47 ---- .../annotation/InstallIn.java | 40 --- .../annotation/RootComponentGeneratorMetadata.java | 31 --- .../processor/ComponentGeneratingStep.java | 267 --------------------- .../processor/MetadataGeneratingStep.java | 92 ------- .../processor/RootComponentGeneratingStep.java | 146 ----------- .../processor/RootComponentProcessor.java | 40 --- .../processor/RootComponentUtils.java | 48 ---- 25 files changed, 962 insertions(+), 797 deletions(-) create mode 100644 java/com/android/dialer/inject/DialerRootComponent.java create mode 100644 java/com/android/dialer/inject/DialerVariant.java create mode 100644 java/com/android/dialer/inject/IncludeInDialerRoot.java create mode 100644 java/com/android/dialer/inject/InstallIn.java create mode 100644 java/com/android/dialer/inject/RootComponentGeneratorMetadata.java create mode 100644 java/com/android/dialer/inject/demo/DemoDaggerApplication.java create mode 100644 java/com/android/dialer/inject/demo/DemoModule.java create mode 100644 java/com/android/dialer/inject/demo/DemoObject.java create mode 100644 java/com/android/dialer/inject/demo/DemoSubcomponent.java create mode 100644 java/com/android/dialer/rootcomponentgenerator/ComponentGeneratingStep.java create mode 100644 java/com/android/dialer/rootcomponentgenerator/MetadataGeneratingStep.java create mode 100644 java/com/android/dialer/rootcomponentgenerator/RootComponentGeneratingStep.java create mode 100644 java/com/android/dialer/rootcomponentgenerator/RootComponentProcessor.java create mode 100644 java/com/android/dialer/rootcomponentgenerator/RootComponentUtils.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/annotation/IncludeInDialerRoot.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java delete mode 100644 java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java (limited to 'java/com') diff --git a/java/com/android/dialer/inject/ContextModule.java b/java/com/android/dialer/inject/ContextModule.java index e3da08edc..828f97830 100644 --- a/java/com/android/dialer/inject/ContextModule.java +++ b/java/com/android/dialer/inject/ContextModule.java @@ -24,6 +24,7 @@ import dagger.Provides; /** Provides the singleton application context object. */ @Module +@InstallIn(variants = {DialerVariant.DIALER_DEMO, DialerVariant.DIALER_TEST}) public final class ContextModule { @NonNull private final Context context; diff --git a/java/com/android/dialer/inject/DialerRootComponent.java b/java/com/android/dialer/inject/DialerRootComponent.java new file mode 100644 index 000000000..11dbf962e --- /dev/null +++ b/java/com/android/dialer/inject/DialerRootComponent.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates the place with this annotation when a RootComponent is needed. + * + *

Usually users put this annotation on application class that is root of dependencies (the last + * thing to compile). The annotation processor will figure out what it needs to generate a variant + * root through dependencies. + * + *

Example: + * + *

+ * 
+ * @DialerRootComponent(variant = DialerVariant.DIALER_AOSP)
+ * public class RootDialerAosp {}
+ * 
+ * 
+ */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface DialerRootComponent { + DialerVariant variant(); +} diff --git a/java/com/android/dialer/inject/DialerVariant.java b/java/com/android/dialer/inject/DialerVariant.java new file mode 100644 index 000000000..2e5794ffb --- /dev/null +++ b/java/com/android/dialer/inject/DialerVariant.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject; + +/** Represents all dialer variants. */ +public enum DialerVariant { + // AOSP Dialer variants + DIALER_AOSP("DialerAosp"), + DIALER_AOSP_ESPRESSO("DialerAospEspresso"), + DIALER_ROBOLECTRIC("DialerRobolectric"), + + + + // TEST variant will be used in situations where we need create in-test application class which + // doesn't belong to any variants listed above + DIALER_TEST("DialerTest"), + // Just for sample code in inject/demo. + DIALER_DEMO("DialerDemo"); + + private final String variant; + + DialerVariant(String variant) { + this.variant = variant; + } + + @Override + public String toString() { + return variant; + } +} diff --git a/java/com/android/dialer/inject/IncludeInDialerRoot.java b/java/com/android/dialer/inject/IncludeInDialerRoot.java new file mode 100644 index 000000000..4e800559a --- /dev/null +++ b/java/com/android/dialer/inject/IncludeInDialerRoot.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotates a type that should be included in Dialer Root Component. Typically, annotated types are + * HasComponent interfaces. + * + *

An example: + * + *

+ * 
+ * {@literal @}dagger.Subcomponent
+ * public abstract class SimulatorComponent {
+ *   public static SimulatorComponent get(Context context) {
+ *      return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
+ *         .simulatorComponent();
+ *   }
+ *   {@literal @}IncludeInDialerRoot
+ *   public interface HasComponent {
+ *      SimulatorComponent simulatorComponent();
+ *  }
+ * }
+ * 
+ * 
+ */ +@Target(ElementType.TYPE) +public @interface IncludeInDialerRoot { + Class[] modules() default {}; +} diff --git a/java/com/android/dialer/inject/InstallIn.java b/java/com/android/dialer/inject/InstallIn.java new file mode 100644 index 000000000..a6f973b7b --- /dev/null +++ b/java/com/android/dialer/inject/InstallIn.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation for {@link dagger.Module dagger.Modules} which causes them to be installed in the + * specified variants. + * + *

It has a parameter for users to enter on which variants annotated module will be installed and + * also must be non-empty. Example: + * + *

+ * 
+ * @InstallIn(variants = {DialerVariant.DIALER_AOSP, DialerVariant.DIALER_TEST})
+ * public class Module1 {}
+ *
+ * 
+ * 
+ */ +@Target(ElementType.TYPE) +public @interface InstallIn { + DialerVariant[] variants(); +} diff --git a/java/com/android/dialer/inject/RootComponentGeneratorMetadata.java b/java/com/android/dialer/inject/RootComponentGeneratorMetadata.java new file mode 100644 index 000000000..51d134a95 --- /dev/null +++ b/java/com/android/dialer/inject/RootComponentGeneratorMetadata.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Only used by rootcomponent generator to store metadata for locating annotated + * (@DialerComponent, @InstallIn) class. + */ +@Target(ElementType.TYPE) +public @interface RootComponentGeneratorMetadata { + String tag(); + + Class annotatedClass(); +} diff --git a/java/com/android/dialer/inject/demo/DemoDaggerApplication.java b/java/com/android/dialer/inject/demo/DemoDaggerApplication.java new file mode 100644 index 000000000..0c13dbb48 --- /dev/null +++ b/java/com/android/dialer/inject/demo/DemoDaggerApplication.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject.demo; + +import android.app.Application; +import android.support.annotation.NonNull; +import com.android.dialer.inject.ContextModule; +import com.android.dialer.inject.DialerRootComponent; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.HasRootComponent; + +/** Demo dialer dagger application. */ +@DialerRootComponent(variant = DialerVariant.DIALER_DEMO) +public final class DemoDaggerApplication extends Application implements HasRootComponent { + + private volatile Object rootComponent; + + /** Returns a cached instance of application's root component. */ + @Override + @NonNull + public final Object component() { + Object result = rootComponent; + if (result == null) { + synchronized (this) { + result = rootComponent; + if (result == null) { + rootComponent = + result = DaggerDialerDemo.builder().contextModule(new ContextModule(this)).build(); + } + } + } + return result; + } + + @Override + public void onCreate() { + super.onCreate(); + + DemoSubcomponent.get(this).demoObject(); + } +} diff --git a/java/com/android/dialer/inject/demo/DemoModule.java b/java/com/android/dialer/inject/demo/DemoModule.java new file mode 100644 index 000000000..40cd6fea1 --- /dev/null +++ b/java/com/android/dialer/inject/demo/DemoModule.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject.demo; + +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.InstallIn; +import dagger.Module; +import dagger.Provides; + +/** Module for demo dagger application. */ +@Module +@InstallIn(variants = DialerVariant.DIALER_DEMO) +public final class DemoModule { + + private DemoModule() {} + + @Provides + static DemoObject provide() { + return new DemoObject("prod"); + } +} diff --git a/java/com/android/dialer/inject/demo/DemoObject.java b/java/com/android/dialer/inject/demo/DemoObject.java new file mode 100644 index 000000000..c6d48a14d --- /dev/null +++ b/java/com/android/dialer/inject/demo/DemoObject.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject.demo; + +/** Object used to demonstrate dagger bindings. */ +class DemoObject { + + private final String value; + + DemoObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/java/com/android/dialer/inject/demo/DemoSubcomponent.java b/java/com/android/dialer/inject/demo/DemoSubcomponent.java new file mode 100644 index 000000000..ff000aaa9 --- /dev/null +++ b/java/com/android/dialer/inject/demo/DemoSubcomponent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.inject.demo; + +import android.content.Context; +import com.android.dialer.inject.HasRootComponent; +import com.android.dialer.inject.IncludeInDialerRoot; +import dagger.Subcomponent; + +/** Subcomponent for the demo dagger application. */ +@Subcomponent +public abstract class DemoSubcomponent { + + abstract DemoObject demoObject(); + + public static DemoSubcomponent get(Context context) { + return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component()) + .demoSubcomponent(); + } + + /** Used to refer to the root application component. */ + @IncludeInDialerRoot + public interface HasComponent { + DemoSubcomponent demoSubcomponent(); + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/ComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/ComponentGeneratingStep.java new file mode 100644 index 000000000..1a40611c5 --- /dev/null +++ b/java/com/android/dialer/rootcomponentgenerator/ComponentGeneratingStep.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.rootcomponentgenerator; + +import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; +import static com.google.auto.common.MoreElements.getAnnotationMirror; +import static javax.lang.model.element.Modifier.ABSTRACT; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; +import static javax.lang.model.util.ElementFilter.typesIn; + +import com.android.dialer.inject.IncludeInDialerRoot; +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.MoreElements; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.SetMultimap; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import dagger.Subcomponent; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * Generates component for a type annotated with {@link IncludeInDialerRoot}. + * + *

Our components have boilerplate code like: + * + *

+ * + *

+ * 
+ *
+ * {@literal @}dagger.Subcomponent
+ * public abstract class GenXXXXComponent {
+ *   public static SimulatorComponent get(Context context) {
+ *      return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
+ *         .simulatorComponent();
+ *   }
+ *   {@literal @}IncludeInDialerRoot
+ *   public interface HasComponent {
+ *      SimulatorComponent simulatorComponent();
+ *  }
+ * }
+ * 
+ * 
+ */ +final class ComponentGeneratingStep implements ProcessingStep { + + private static final String DIALER_INJECT_PACKAGE = "com.android.dialer.inject"; + private static final String DIALER_HASROOTCOMPONENT_INTERFACE = "HasRootComponent"; + private static final ClassName ANDROID_CONTEXT_CLASS_NAME = + ClassName.get("android.content", "Context"); + private final ProcessingEnvironment processingEnv; + + public ComponentGeneratingStep(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + @Override + public Set> annotations() { + return ImmutableSet.of(IncludeInDialerRoot.class); + } + + @Override + public Set process( + SetMultimap, Element> elementsByAnnotation) { + for (TypeElement type : typesIn(elementsByAnnotation.get(IncludeInDialerRoot.class))) { + generateComponent(type); + } + return Collections.emptySet(); + } + + /** + * Generates component file for a componentElement. + * + *

The annotation processor will generate a new type file with some prefix, which contains + * public static XXX get(Context context) method and HasComponent interface. + * + * @param dialerComponentElement a component used by the annotation processor. + */ + private void generateComponent(TypeElement dialerComponentElement) { + TypeSpec.Builder componentClass = + dialerComponentElement.getKind().isClass() + ? cloneClass(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX) + : cloneInterface(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX); + componentClass.addAnnotation(makeDaggerSubcomponentAnnotation(dialerComponentElement)); + RootComponentUtils.writeJavaFile( + processingEnv, + ClassName.get(dialerComponentElement).packageName(), + dialerBoilerplateCode(componentClass, dialerComponentElement)); + } + + @SuppressWarnings("unchecked") + private AnnotationSpec makeDaggerSubcomponentAnnotation(TypeElement dialerComponentElement) { + + Optional componentMirror = + getAnnotationMirror(dialerComponentElement, IncludeInDialerRoot.class); + + AnnotationSpec.Builder subcomponentBuilder = AnnotationSpec.builder(Subcomponent.class); + for (AnnotationValue annotationValue : + (List) + getAnnotationValue(componentMirror.get(), "modules").getValue()) { + subcomponentBuilder.addMember( + "modules", "$T.class", ClassName.get((TypeMirror) annotationValue.getValue())); + } + return subcomponentBuilder.build(); + } + + private TypeSpec dialerBoilerplateCode( + TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { + return typeBuilder + .addType(hasComponentInterface(typeBuilder, dialerComponentElement)) + .addMethod(addGetComponentMethod(typeBuilder, dialerComponentElement)) + .build(); + } + + private TypeSpec hasComponentInterface( + TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { + return TypeSpec.interfaceBuilder("HasComponent") + .addModifiers(PUBLIC) + .addMethod( + MethodSpec.methodBuilder("make" + dialerComponentElement.getSimpleName()) + .addModifiers(PUBLIC, ABSTRACT) + .returns(getComponentClass(typeBuilder, dialerComponentElement)) + .build()) + .build(); + } + + private MethodSpec addGetComponentMethod( + TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { + ClassName hasComponenetInterface = + ClassName.get( + getPackageName(dialerComponentElement), + RootComponentUtils.GENERATED_COMPONENT_PREFIX + + dialerComponentElement.getSimpleName()) + .nestedClass("HasComponent"); + ClassName hasRootComponentInterface = + ClassName.get(DIALER_INJECT_PACKAGE, DIALER_HASROOTCOMPONENT_INTERFACE); + return MethodSpec.methodBuilder("get") + .addModifiers(PUBLIC, STATIC) + .addParameter(ParameterSpec.builder(ANDROID_CONTEXT_CLASS_NAME, "context").build()) + .addStatement( + "$1T hasRootComponent = ($1T) context.getApplicationContext()", + hasRootComponentInterface) + .addStatement( + "return (($T) (hasRootComponent.component())).make$T()", + hasComponenetInterface, + dialerComponentElement) + .returns(getComponentClass(typeBuilder, dialerComponentElement)) + .build(); + } + + private void addElement(TypeSpec.Builder builder, Element element) { + switch (element.getKind()) { + case INTERFACE: + builder.addType(cloneInterface(MoreElements.asType(element), "").build()); + break; + case CLASS: + builder.addType(cloneClass(MoreElements.asType(element), "").build()); + break; + case FIELD: + builder.addField(cloneField(MoreElements.asVariable(element)).build()); + break; + case METHOD: + builder.addMethod(cloneMethod(MoreElements.asExecutable(element))); + break; + case CONSTRUCTOR: + builder.addMethod(cloneConstructor(MoreElements.asExecutable(element))); + break; + default: + throw new RuntimeException( + String.format("Unexpected element %s met during class cloning phase!", element)); + } + } + + private MethodSpec cloneMethod(ExecutableElement element) { + return MethodSpec.methodBuilder(element.getSimpleName().toString()) + .addModifiers(element.getModifiers()) + .returns(TypeName.get(element.getReturnType())) + .addParameters(cloneParameters(element.getParameters())) + .build(); + } + + private MethodSpec cloneConstructor(ExecutableElement element) { + return MethodSpec.constructorBuilder() + .addModifiers(element.getModifiers()) + .addParameters(cloneParameters(element.getParameters())) + .build(); + } + + private List cloneParameters( + List variableElementsList) { + List list = new ArrayList<>(); + for (VariableElement variableElement : variableElementsList) { + ParameterSpec.Builder builder = + ParameterSpec.builder( + TypeName.get(variableElement.asType()), + variableElement.getSimpleName().toString()) + .addModifiers(variableElement.getModifiers()); + list.add(builder.build()); + } + return list; + } + + private TypeSpec.Builder cloneInterface(TypeElement element, String prefix) { + return cloneType(TypeSpec.interfaceBuilder(prefix + element.getSimpleName()), element); + } + + private TypeSpec.Builder cloneClass(TypeElement element, String prefix) { + return cloneType(TypeSpec.classBuilder(prefix + element.getSimpleName()), element); + } + + private FieldSpec.Builder cloneField(VariableElement element) { + FieldSpec.Builder builder = + FieldSpec.builder(TypeName.get(element.asType()), element.getSimpleName().toString()); + element.getModifiers().forEach(builder::addModifiers); + return builder; + } + + private TypeSpec.Builder cloneType(TypeSpec.Builder builder, TypeElement element) { + element.getModifiers().forEach(builder::addModifiers); + for (Element enclosedElement : element.getEnclosedElements()) { + addElement(builder, enclosedElement); + } + return builder; + } + + private ClassName getComponentClass( + TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { + return ClassName.get(getPackageName(dialerComponentElement), typeBuilder.build().name); + } + + private String getPackageName(TypeElement element) { + return ClassName.get(element).packageName(); + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/MetadataGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/MetadataGeneratingStep.java new file mode 100644 index 000000000..266574d76 --- /dev/null +++ b/java/com/android/dialer/rootcomponentgenerator/MetadataGeneratingStep.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.rootcomponentgenerator; + +import static javax.tools.Diagnostic.Kind.ERROR; + +import com.android.dialer.inject.IncludeInDialerRoot; +import com.android.dialer.inject.InstallIn; +import com.android.dialer.inject.RootComponentGeneratorMetadata; +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.SetMultimap; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.TypeSpec; +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +/** + * Genereates metadata for every type annotated by {@link InstallIn} and {@link + * IncludeInDialerRoot}. + * + *

The metadata has the information where the annotated types are and it is used by annotation + * processor when the processor tries to generate root component. + */ +final class MetadataGeneratingStep implements ProcessingStep { + + private final ProcessingEnvironment processingEnv; + + MetadataGeneratingStep(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + @Override + public Set> annotations() { + return ImmutableSet.of(IncludeInDialerRoot.class, InstallIn.class); + } + + @Override + public Set process( + SetMultimap, Element> elementsByAnnotation) { + + for (Element element : elementsByAnnotation.get(IncludeInDialerRoot.class)) { + generateMetadataFor(IncludeInDialerRoot.class, MoreElements.asType(element)); + } + for (Element element : elementsByAnnotation.get(InstallIn.class)) { + if (element.getAnnotation(InstallIn.class).variants().length == 0) { + processingEnv + .getMessager() + .printMessage( + ERROR, String.format("@InstallIn %s must have at least one variant", element)); + continue; + } + generateMetadataFor(InstallIn.class, MoreElements.asType(element)); + } + + return Collections.emptySet(); + } + + private void generateMetadataFor( + Class annotation, TypeElement annotatedElement) { + TypeSpec.Builder metadataClassBuilder = + TypeSpec.classBuilder( + annotatedElement.getQualifiedName().toString().replace('.', '_') + "Metadata"); + metadataClassBuilder.addAnnotation( + AnnotationSpec.builder(RootComponentGeneratorMetadata.class) + .addMember("tag", "$S", annotation.getSimpleName()) + .addMember("annotatedClass", "$T.class", annotatedElement.asType()) + .build()); + TypeSpec metadataClass = metadataClassBuilder.build(); + RootComponentUtils.writeJavaFile( + processingEnv, RootComponentUtils.METADATA_PACKAGE_NAME, metadataClass); + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/RootComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/RootComponentGeneratingStep.java new file mode 100644 index 000000000..61c326a3c --- /dev/null +++ b/java/com/android/dialer/rootcomponentgenerator/RootComponentGeneratingStep.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.rootcomponentgenerator; + +import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; +import static com.google.auto.common.MoreElements.getAnnotationMirror; +import static com.google.auto.common.MoreElements.isAnnotationPresent; + +import com.android.dialer.inject.DialerRootComponent; +import com.android.dialer.inject.DialerVariant; +import com.android.dialer.inject.IncludeInDialerRoot; +import com.android.dialer.inject.InstallIn; +import com.android.dialer.inject.RootComponentGeneratorMetadata; +import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; +import com.google.auto.common.MoreElements; +import com.google.common.base.Optional; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.SetMultimap; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeSpec; +import dagger.Component; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.ProcessingEnvironment; +import javax.inject.Singleton; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** Generates root component for a java type annotated with {@link DialerRootComponent}. */ +final class RootComponentGeneratingStep implements ProcessingStep { + + private final ProcessingEnvironment processingEnv; + + public RootComponentGeneratingStep(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + @Override + public Set> annotations() { + return ImmutableSet.of(DialerRootComponent.class); + } + + @Override + public Set process( + SetMultimap, Element> elementsByAnnotation) { + for (Element element : elementsByAnnotation.get(DialerRootComponent.class)) { + generateRootComponent(MoreElements.asType(element)); + } + return Collections.emptySet(); + } + + /** + * Generates a root component. + * + * @param rootElement the annotated type with {@link DialerRootComponent} used in annotation + * processor. + */ + private void generateRootComponent(TypeElement rootElement) { + DialerRootComponent dialerRootComponent = rootElement.getAnnotation(DialerRootComponent.class); + ListMultimap componentModuleMap = generateComponentModuleMap(); + List componentList = generateComponentList(); + DialerVariant dialerVariant = dialerRootComponent.variant(); + TypeSpec.Builder rootComponentClassBuilder = + TypeSpec.interfaceBuilder(dialerVariant.toString()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Singleton.class); + for (TypeElement componentWithSuperInterface : componentList) { + rootComponentClassBuilder.addSuperinterface(ClassName.get(componentWithSuperInterface)); + } + AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class); + for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) { + componentAnnotation.addMember("modules", "$T.class", annotatedElement.asType()); + } + rootComponentClassBuilder.addAnnotation(componentAnnotation.build()); + TypeSpec rootComponentClass = rootComponentClassBuilder.build(); + RootComponentUtils.writeJavaFile( + processingEnv, ClassName.get(rootElement).packageName(), rootComponentClass); + } + + private List generateComponentList() { + List list = new ArrayList<>(); + extractInfoFromMetadata(IncludeInDialerRoot.class, list::add); + return list; + } + + private ListMultimap generateComponentModuleMap() { + ListMultimap map = ArrayListMultimap.create(); + extractInfoFromMetadata( + InstallIn.class, + (annotatedElement) -> { + for (DialerVariant rootComponentName : + annotatedElement.getAnnotation(InstallIn.class).variants()) { + map.put(rootComponentName, annotatedElement); + } + }); + return map; + } + + private void extractInfoFromMetadata( + Class annotation, MetadataProcessor metadataProcessor) { + PackageElement cachePackage = + processingEnv.getElementUtils().getPackageElement(RootComponentUtils.METADATA_PACKAGE_NAME); + for (Element element : cachePackage.getEnclosedElements()) { + Optional metadataAnnotation = + getAnnotationMirror(element, RootComponentGeneratorMetadata.class); + if (isAnnotationPresent(element, RootComponentGeneratorMetadata.class) + && getAnnotationValue(metadataAnnotation.get(), "tag") + .getValue() + .equals(annotation.getSimpleName())) { + TypeMirror annotatedClass = + (TypeMirror) getAnnotationValue(metadataAnnotation.get(), "annotatedClass").getValue(); + TypeElement annotatedElement = + processingEnv.getElementUtils().getTypeElement(annotatedClass.toString()); + metadataProcessor.process(annotatedElement); + } + } + } + + private interface MetadataProcessor { + void process(TypeElement typeElement); + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/RootComponentProcessor.java b/java/com/android/dialer/rootcomponentgenerator/RootComponentProcessor.java new file mode 100644 index 000000000..76df5e312 --- /dev/null +++ b/java/com/android/dialer/rootcomponentgenerator/RootComponentProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.rootcomponentgenerator; + +import com.google.auto.common.BasicAnnotationProcessor; +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; +import javax.annotation.processing.Processor; +import javax.lang.model.SourceVersion; + +/** Processes annotations defined by RootComponentGenerator. */ +@AutoService(Processor.class) +public class RootComponentProcessor extends BasicAnnotationProcessor { + + @Override + protected Iterable initSteps() { + return ImmutableList.of( + new MetadataGeneratingStep(processingEnv), + new RootComponentGeneratingStep(processingEnv)); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/RootComponentUtils.java b/java/com/android/dialer/rootcomponentgenerator/RootComponentUtils.java new file mode 100644 index 000000000..cdcae9250 --- /dev/null +++ b/java/com/android/dialer/rootcomponentgenerator/RootComponentUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * 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 com.android.dialer.rootcomponentgenerator; + +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; + +/** + * Contains a basic method writing java file to a curtain package. ProcessingEnvironment is needed. + */ +public abstract class RootComponentUtils { + /** + * The place where the generator puts metadata files storing reference for {@link + * RootComponentGeneratingStep}. + */ + static final String METADATA_PACKAGE_NAME = "com.android.dialer.rootcomponentgenerator.metadata"; + + static final String GENERATED_COMPONENT_PREFIX = "Gen"; + + static void writeJavaFile( + ProcessingEnvironment processingEnv, String packageName, TypeSpec typeSpec) { + try { + JavaFile.builder(packageName, typeSpec) + .skipJavaLangImports(true) + .build() + .writeTo(processingEnv.getFiler()); + } catch (IOException e) { + System.out.println(e); + throw new RuntimeException(e); + } + } +} diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java deleted file mode 100644 index b6bf22efb..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerRootComponent.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates the place with this annotation when a RootComponent is needed. - * - *

Usually users put this annotation on application class that is root of dependencies (the last - * thing to compile). The annotation processor will figure out what it needs to generate a variant - * root through dependencies. - * - *

Example: - * - *

- * 
- * @DialerRootComponent(variant = DialerVariant.DIALER_AOSP)
- * public class RootDialerAosp {}
- * 
- * 
- */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface DialerRootComponent { - DialerVariant variant(); -} diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java b/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java deleted file mode 100644 index 0bb185296..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/annotation/DialerVariant.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.annotation; - -/** Represents all dialer variants. */ -public enum DialerVariant { - // AOSP Dialer variants - DIALER_AOSP("DialerAosp"), - DIALER_AOSP_ESPRESSO("DialerAospEspresso"), - DIALER_ROBOLECTRIC("DialerRobolectric"), - - - - // TEST variant will be used in situations where we need create in-test application class which - // doesn't belong to any variants listed above - DIALER_TEST("DialerTest"); - - private final String variant; - - DialerVariant(String variant) { - this.variant = variant; - } - - @Override - public String toString() { - return variant; - } -} diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/IncludeInDialerRoot.java b/java/com/android/dialer/rootcomponentgenerator/annotation/IncludeInDialerRoot.java deleted file mode 100644 index 4ce9ec3a3..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/annotation/IncludeInDialerRoot.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * Annotates a type that should be included in Dialer Root Component. Typically, annotated types are - * HasComponent interfaces. - * - *

An example: - * - *

- * 
- * {@literal @}dagger.Subcomponent
- * public abstract class SimulatorComponent {
- *   public static SimulatorComponent get(Context context) {
- *      return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
- *         .simulatorComponent();
- *   }
- *   {@literal @}IncludeInDialerRoot
- *   public interface HasComponent {
- *      SimulatorComponent simulatorComponent();
- *  }
- * }
- * 
- * 
- */ -@Target(ElementType.TYPE) -public @interface IncludeInDialerRoot { - Class[] modules() default {}; -} diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java b/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java deleted file mode 100644 index 01a7873b0..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/annotation/InstallIn.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * Annotation for {@link dagger.Module dagger.Modules} which causes them to be installed in the - * specified variants. - * - *

It has a parameter for users to enter on which variants annotated module will be installed and - * also must be non-empty. Example: - * - *

- * 
- * @InstallIn(variants = {DialerVariant.DIALER_AOSP, DialerVariant.DIALER_TEST})
- * public class Module1 {}
- *
- * 
- * 
- */ -@Target(ElementType.TYPE) -public @interface InstallIn { - DialerVariant[] variants(); -} diff --git a/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java b/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java deleted file mode 100644 index 070cc7350..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/annotation/RootComponentGeneratorMetadata.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * Only used by rootcomponent generator to store metadata for locating annotated - * (@DialerComponent, @InstallIn) class. - */ -@Target(ElementType.TYPE) -public @interface RootComponentGeneratorMetadata { - String tag(); - - Class annotatedClass(); -} diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java deleted file mode 100644 index 04d42ac59..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/processor/ComponentGeneratingStep.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.processor; - -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static com.google.auto.common.MoreElements.getAnnotationMirror; -import static javax.lang.model.element.Modifier.ABSTRACT; -import static javax.lang.model.element.Modifier.PUBLIC; -import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.typesIn; - -import com.android.dialer.rootcomponentgenerator.annotation.IncludeInDialerRoot; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; -import com.google.auto.common.MoreElements; -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.SetMultimap; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import dagger.Subcomponent; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * Generates component for a type annotated with {@link IncludeInDialerRoot}. - * - *

Our components have boilerplate code like: - * - *

- * - *

- * 
- *
- * {@literal @}dagger.Subcomponent
- * public abstract class GenXXXXComponent {
- *   public static SimulatorComponent get(Context context) {
- *      return ((HasComponent)((HasRootComponent) context.getApplicationContext()).component())
- *         .simulatorComponent();
- *   }
- *   {@literal @}IncludeInDialerRoot
- *   public interface HasComponent {
- *      SimulatorComponent simulatorComponent();
- *  }
- * }
- * 
- * 
- */ -final class ComponentGeneratingStep implements ProcessingStep { - - private static final String DIALER_INJECT_PACKAGE = "com.android.dialer.inject"; - private static final String DIALER_HASROOTCOMPONENT_INTERFACE = "HasRootComponent"; - private static final ClassName ANDROID_CONTEXT_CLASS_NAME = - ClassName.get("android.content", "Context"); - private final ProcessingEnvironment processingEnv; - - public ComponentGeneratingStep(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - } - - @Override - public Set> annotations() { - return ImmutableSet.of(IncludeInDialerRoot.class); - } - - @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { - for (TypeElement type : typesIn(elementsByAnnotation.get(IncludeInDialerRoot.class))) { - generateComponent(type); - } - return Collections.emptySet(); - } - - /** - * Generates component file for a componentElement. - * - *

The annotation processor will generate a new type file with some prefix, which contains - * public static XXX get(Context context) method and HasComponent interface. - * - * @param dialerComponentElement a component used by the annotation processor. - */ - private void generateComponent(TypeElement dialerComponentElement) { - TypeSpec.Builder componentClass = - dialerComponentElement.getKind().isClass() - ? cloneClass(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX) - : cloneInterface(dialerComponentElement, RootComponentUtils.GENERATED_COMPONENT_PREFIX); - componentClass.addAnnotation(makeDaggerSubcomponentAnnotation(dialerComponentElement)); - RootComponentUtils.writeJavaFile( - processingEnv, - ClassName.get(dialerComponentElement).packageName(), - dialerBoilerplateCode(componentClass, dialerComponentElement)); - } - - @SuppressWarnings("unchecked") - private AnnotationSpec makeDaggerSubcomponentAnnotation(TypeElement dialerComponentElement) { - - Optional componentMirror = - getAnnotationMirror(dialerComponentElement, IncludeInDialerRoot.class); - - AnnotationSpec.Builder subcomponentBuilder = AnnotationSpec.builder(Subcomponent.class); - for (AnnotationValue annotationValue : - (List) - getAnnotationValue(componentMirror.get(), "modules").getValue()) { - subcomponentBuilder.addMember( - "modules", "$T.class", ClassName.get((TypeMirror) annotationValue.getValue())); - } - return subcomponentBuilder.build(); - } - - private TypeSpec dialerBoilerplateCode( - TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { - return typeBuilder - .addType(hasComponentInterface(typeBuilder, dialerComponentElement)) - .addMethod(addGetComponentMethod(typeBuilder, dialerComponentElement)) - .build(); - } - - private TypeSpec hasComponentInterface( - TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { - return TypeSpec.interfaceBuilder("HasComponent") - .addModifiers(PUBLIC) - .addMethod( - MethodSpec.methodBuilder("make" + dialerComponentElement.getSimpleName()) - .addModifiers(PUBLIC, ABSTRACT) - .returns(getComponentClass(typeBuilder, dialerComponentElement)) - .build()) - .build(); - } - - private MethodSpec addGetComponentMethod( - TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { - ClassName hasComponenetInterface = - ClassName.get( - getPackageName(dialerComponentElement), - RootComponentUtils.GENERATED_COMPONENT_PREFIX - + dialerComponentElement.getSimpleName()) - .nestedClass("HasComponent"); - ClassName hasRootComponentInterface = - ClassName.get(DIALER_INJECT_PACKAGE, DIALER_HASROOTCOMPONENT_INTERFACE); - return MethodSpec.methodBuilder("get") - .addModifiers(PUBLIC, STATIC) - .addParameter(ParameterSpec.builder(ANDROID_CONTEXT_CLASS_NAME, "context").build()) - .addStatement( - "$1T hasRootComponent = ($1T) context.getApplicationContext()", - hasRootComponentInterface) - .addStatement( - "return (($T) (hasRootComponent.component())).make$T()", - hasComponenetInterface, - dialerComponentElement) - .returns(getComponentClass(typeBuilder, dialerComponentElement)) - .build(); - } - - private void addElement(TypeSpec.Builder builder, Element element) { - switch (element.getKind()) { - case INTERFACE: - builder.addType(cloneInterface(MoreElements.asType(element), "").build()); - break; - case CLASS: - builder.addType(cloneClass(MoreElements.asType(element), "").build()); - break; - case FIELD: - builder.addField(cloneField(MoreElements.asVariable(element)).build()); - break; - case METHOD: - builder.addMethod(cloneMethod(MoreElements.asExecutable(element))); - break; - case CONSTRUCTOR: - builder.addMethod(cloneConstructor(MoreElements.asExecutable(element))); - break; - default: - throw new RuntimeException( - String.format("Unexpected element %s met during class cloning phase!", element)); - } - } - - private MethodSpec cloneMethod(ExecutableElement element) { - return MethodSpec.methodBuilder(element.getSimpleName().toString()) - .addModifiers(element.getModifiers()) - .returns(TypeName.get(element.getReturnType())) - .addParameters(cloneParameters(element.getParameters())) - .build(); - } - - private MethodSpec cloneConstructor(ExecutableElement element) { - return MethodSpec.constructorBuilder() - .addModifiers(element.getModifiers()) - .addParameters(cloneParameters(element.getParameters())) - .build(); - } - - private List cloneParameters( - List variableElementsList) { - List list = new ArrayList<>(); - for (VariableElement variableElement : variableElementsList) { - ParameterSpec.Builder builder = - ParameterSpec.builder( - TypeName.get(variableElement.asType()), - variableElement.getSimpleName().toString()) - .addModifiers(variableElement.getModifiers()); - list.add(builder.build()); - } - return list; - } - - private TypeSpec.Builder cloneInterface(TypeElement element, String prefix) { - return cloneType(TypeSpec.interfaceBuilder(prefix + element.getSimpleName()), element); - } - - private TypeSpec.Builder cloneClass(TypeElement element, String prefix) { - return cloneType(TypeSpec.classBuilder(prefix + element.getSimpleName()), element); - } - - private FieldSpec.Builder cloneField(VariableElement element) { - FieldSpec.Builder builder = - FieldSpec.builder(TypeName.get(element.asType()), element.getSimpleName().toString()); - element.getModifiers().forEach(builder::addModifiers); - return builder; - } - - private TypeSpec.Builder cloneType(TypeSpec.Builder builder, TypeElement element) { - element.getModifiers().forEach(builder::addModifiers); - for (Element enclosedElement : element.getEnclosedElements()) { - addElement(builder, enclosedElement); - } - return builder; - } - - private ClassName getComponentClass( - TypeSpec.Builder typeBuilder, TypeElement dialerComponentElement) { - return ClassName.get(getPackageName(dialerComponentElement), typeBuilder.build().name); - } - - private String getPackageName(TypeElement element) { - return ClassName.get(element).packageName(); - } -} diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java deleted file mode 100644 index 3b46ed0d0..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/processor/MetadataGeneratingStep.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.processor; - -import static javax.tools.Diagnostic.Kind.ERROR; - -import com.android.dialer.rootcomponentgenerator.annotation.IncludeInDialerRoot; -import com.android.dialer.rootcomponentgenerator.annotation.InstallIn; -import com.android.dialer.rootcomponentgenerator.annotation.RootComponentGeneratorMetadata; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; -import com.google.auto.common.MoreElements; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.SetMultimap; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.TypeSpec; -import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; - -/** - * Genereates metadata for every type annotated by {@link InstallIn} and {@link - * IncludeInDialerRoot}. - * - *

The metadata has the information where the annotated types are and it is used by annotation - * processor when the processor tries to generate root component. - */ -final class MetadataGeneratingStep implements ProcessingStep { - - private final ProcessingEnvironment processingEnv; - - MetadataGeneratingStep(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - } - - @Override - public Set> annotations() { - return ImmutableSet.of(IncludeInDialerRoot.class, InstallIn.class); - } - - @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { - - for (Element element : elementsByAnnotation.get(IncludeInDialerRoot.class)) { - generateMetadataFor(IncludeInDialerRoot.class, MoreElements.asType(element)); - } - for (Element element : elementsByAnnotation.get(InstallIn.class)) { - if (element.getAnnotation(InstallIn.class).variants().length == 0) { - processingEnv - .getMessager() - .printMessage( - ERROR, String.format("@InstallIn %s must have at least one variant", element)); - continue; - } - generateMetadataFor(InstallIn.class, MoreElements.asType(element)); - } - - return Collections.emptySet(); - } - - private void generateMetadataFor( - Class annotation, TypeElement annotatedElement) { - TypeSpec.Builder metadataClassBuilder = - TypeSpec.classBuilder( - annotatedElement.getQualifiedName().toString().replace('.', '_') + "Metadata"); - metadataClassBuilder.addAnnotation( - AnnotationSpec.builder(RootComponentGeneratorMetadata.class) - .addMember("tag", "$S", annotation.getSimpleName()) - .addMember("annotatedClass", "$T.class", annotatedElement.asType()) - .build()); - TypeSpec metadataClass = metadataClassBuilder.build(); - RootComponentUtils.writeJavaFile( - processingEnv, RootComponentUtils.METADATA_PACKAGE_NAME, metadataClass); - } -} diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java deleted file mode 100644 index ad91a3cbe..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentGeneratingStep.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.processor; - -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static com.google.auto.common.MoreElements.getAnnotationMirror; -import static com.google.auto.common.MoreElements.isAnnotationPresent; - -import com.android.dialer.rootcomponentgenerator.annotation.DialerRootComponent; -import com.android.dialer.rootcomponentgenerator.annotation.DialerVariant; -import com.android.dialer.rootcomponentgenerator.annotation.IncludeInDialerRoot; -import com.android.dialer.rootcomponentgenerator.annotation.InstallIn; -import com.android.dialer.rootcomponentgenerator.annotation.RootComponentGeneratorMetadata; -import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep; -import com.google.auto.common.MoreElements; -import com.google.common.base.Optional; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.SetMultimap; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeSpec; -import dagger.Component; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; -import javax.inject.Singleton; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; - -/** Generates root component for a java type annotated with {@link DialerRootComponent}. */ -final class RootComponentGeneratingStep implements ProcessingStep { - - private final ProcessingEnvironment processingEnv; - - public RootComponentGeneratingStep(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - } - - @Override - public Set> annotations() { - return ImmutableSet.of(DialerRootComponent.class); - } - - @Override - public Set process( - SetMultimap, Element> elementsByAnnotation) { - for (Element element : elementsByAnnotation.get(DialerRootComponent.class)) { - generateRootComponent(MoreElements.asType(element)); - } - return Collections.emptySet(); - } - - /** - * Generates a root component. - * - * @param rootElement the annotated type with {@link DialerRootComponent} used in annotation - * processor. - */ - private void generateRootComponent(TypeElement rootElement) { - DialerRootComponent dialerRootComponent = rootElement.getAnnotation(DialerRootComponent.class); - ListMultimap componentModuleMap = generateComponentModuleMap(); - List componentList = generateComponentList(); - DialerVariant dialerVariant = dialerRootComponent.variant(); - TypeSpec.Builder rootComponentClassBuilder = - TypeSpec.interfaceBuilder(dialerVariant.toString()) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Singleton.class); - for (TypeElement componentWithSuperInterface : componentList) { - rootComponentClassBuilder.addSuperinterface(ClassName.get(componentWithSuperInterface)); - } - AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class); - for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) { - componentAnnotation.addMember("modules", "$T.class", annotatedElement.asType()); - } - rootComponentClassBuilder.addAnnotation(componentAnnotation.build()); - TypeSpec rootComponentClass = rootComponentClassBuilder.build(); - RootComponentUtils.writeJavaFile( - processingEnv, ClassName.get(rootElement).packageName(), rootComponentClass); - } - - private List generateComponentList() { - List list = new ArrayList<>(); - extractInfoFromMetadata(IncludeInDialerRoot.class, list::add); - return list; - } - - private ListMultimap generateComponentModuleMap() { - ListMultimap map = ArrayListMultimap.create(); - extractInfoFromMetadata( - InstallIn.class, - (annotatedElement) -> { - for (DialerVariant rootComponentName : - annotatedElement.getAnnotation(InstallIn.class).variants()) { - map.put(rootComponentName, annotatedElement); - } - }); - return map; - } - - private void extractInfoFromMetadata( - Class annotation, MetadataProcessor metadataProcessor) { - PackageElement cachePackage = - processingEnv.getElementUtils().getPackageElement(RootComponentUtils.METADATA_PACKAGE_NAME); - for (Element element : cachePackage.getEnclosedElements()) { - Optional metadataAnnotation = - getAnnotationMirror(element, RootComponentGeneratorMetadata.class); - if (isAnnotationPresent(element, RootComponentGeneratorMetadata.class) - && getAnnotationValue(metadataAnnotation.get(), "tag") - .getValue() - .equals(annotation.getSimpleName())) { - TypeMirror annotatedClass = - (TypeMirror) getAnnotationValue(metadataAnnotation.get(), "annotatedClass").getValue(); - TypeElement annotatedElement = - processingEnv.getElementUtils().getTypeElement(annotatedClass.toString()); - metadataProcessor.process(annotatedElement); - } - } - } - - private interface MetadataProcessor { - void process(TypeElement typeElement); - } -} diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java deleted file mode 100644 index 56caa9ea4..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentProcessor.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.processor; - -import com.google.auto.common.BasicAnnotationProcessor; -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableList; -import javax.annotation.processing.Processor; -import javax.lang.model.SourceVersion; - -/** Processes annotations defined by RootComponentGenerator. */ -@AutoService(Processor.class) -public class RootComponentProcessor extends BasicAnnotationProcessor { - - @Override - protected Iterable initSteps() { - return ImmutableList.of( - new MetadataGeneratingStep(processingEnv), - new RootComponentGeneratingStep(processingEnv)); - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } -} diff --git a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java b/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java deleted file mode 100644 index 889297154..000000000 --- a/java/com/android/dialer/rootcomponentgenerator/processor/RootComponentUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * 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 com.android.dialer.rootcomponentgenerator.processor; - -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeSpec; -import java.io.IOException; -import javax.annotation.processing.ProcessingEnvironment; - -/** - * Contains a basic method writing java file to a curtain package. ProcessingEnvironment is needed. - */ -public abstract class RootComponentUtils { - /** - * The place where the generator puts metadata files storing reference for {@link - * RootComponentGeneratingStep}. - */ - static final String METADATA_PACKAGE_NAME = "com.android.dialer.rootcomponentgenerator.metadata"; - - static final String GENERATED_COMPONENT_PREFIX = "Gen"; - - static void writeJavaFile( - ProcessingEnvironment processingEnv, String packageName, TypeSpec typeSpec) { - try { - JavaFile.builder(packageName, typeSpec) - .skipJavaLangImports(true) - .build() - .writeTo(processingEnv.getFiler()); - } catch (IOException e) { - System.out.println(e); - throw new RuntimeException(e); - } - } -} -- cgit v1.2.3