• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.base;
18 
19 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
20 import static com.squareup.javapoet.MethodSpec.methodBuilder;
21 import static com.squareup.javapoet.TypeSpec.classBuilder;
22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
24 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
25 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
26 import static javax.lang.model.element.Modifier.PRIVATE;
27 
28 import androidx.room.compiler.processing.XConstructorElement;
29 import androidx.room.compiler.processing.XElement;
30 import androidx.room.compiler.processing.XExecutableParameterElement;
31 import androidx.room.compiler.processing.XProcessingEnv;
32 import androidx.room.compiler.processing.XType;
33 import androidx.room.compiler.processing.XTypeElement;
34 import com.google.common.collect.ImmutableList;
35 import com.google.common.collect.ImmutableSet;
36 import com.squareup.javapoet.ClassName;
37 import com.squareup.javapoet.CodeBlock;
38 import com.squareup.javapoet.FieldSpec;
39 import com.squareup.javapoet.MethodSpec;
40 import com.squareup.javapoet.TypeName;
41 import com.squareup.javapoet.TypeSpec;
42 import dagger.internal.codegen.javapoet.CodeBlocks;
43 import dagger.internal.codegen.javapoet.TypeNames;
44 import java.util.Optional;
45 import javax.lang.model.element.Modifier;
46 
47 /**
48  * A source file generator that only writes the relevant code necessary for Bazel to create a
49  * correct header (ABI) jar.
50  */
51 public final class SourceFileHjarGenerator<T> extends SourceFileGenerator<T> {
wrap( SourceFileGenerator<T> delegate, XProcessingEnv processingEnv)52   public static <T> SourceFileGenerator<T> wrap(
53       SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
54     return new SourceFileHjarGenerator<>(delegate, processingEnv);
55   }
56 
57   private final SourceFileGenerator<T> delegate;
58   private final XProcessingEnv processingEnv;
59 
SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv)60   private SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) {
61     super(delegate);
62     this.delegate = delegate;
63     this.processingEnv = processingEnv;
64   }
65 
66   @Override
originatingElement(T input)67   public XElement originatingElement(T input) {
68     return delegate.originatingElement(input);
69   }
70 
71   @Override
topLevelTypes(T input)72   public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) {
73     String packageName = closestEnclosingTypeElement(originatingElement(input)).getPackageName();
74     return delegate.topLevelTypes(input).stream()
75         .map(completeType -> skeletonType(packageName, completeType.build()))
76         .collect(toImmutableList());
77   }
78 
skeletonType(String packageName, TypeSpec completeType)79   private TypeSpec.Builder skeletonType(String packageName, TypeSpec completeType) {
80     TypeSpec.Builder skeleton =
81         classBuilder(completeType.name)
82             .addSuperinterfaces(completeType.superinterfaces)
83             .addTypeVariables(completeType.typeVariables)
84             .addModifiers(completeType.modifiers.toArray(new Modifier[0]))
85             .addAnnotations(completeType.annotations);
86 
87     if (!completeType.superclass.equals(ClassName.OBJECT)) {
88       skeleton.superclass(completeType.superclass);
89     }
90 
91     completeType.methodSpecs.stream()
92         .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor())
93         .map(completeMethod -> skeletonMethod(packageName, completeType, completeMethod))
94         .forEach(skeleton::addMethod);
95 
96     completeType.fieldSpecs.stream()
97         .filter(field -> !field.modifiers.contains(PRIVATE))
98         .map(this::skeletonField)
99         .forEach(skeleton::addField);
100 
101     completeType.typeSpecs.stream()
102         .map(type -> skeletonType(packageName, type).build())
103         .forEach(skeleton::addType);
104 
105     completeType.alwaysQualifiedNames
106         .forEach(skeleton::alwaysQualify);
107 
108     return skeleton;
109   }
110 
skeletonMethod( String packageName, TypeSpec completeType, MethodSpec completeMethod)111   private MethodSpec skeletonMethod(
112       String packageName, TypeSpec completeType, MethodSpec completeMethod) {
113     MethodSpec.Builder skeleton =
114         completeMethod.isConstructor()
115             ? constructorBuilder()
116             : methodBuilder(completeMethod.name).returns(completeMethod.returnType);
117 
118     if (completeMethod.isConstructor()) {
119       getRequiredSuperCall(packageName, completeType)
120           .ifPresent(superCall -> skeleton.addStatement("$L", superCall));
121     } else if (!completeMethod.returnType.equals(TypeName.VOID)) {
122       skeleton.addStatement("return $L", getDefaultValueCodeBlock(completeMethod.returnType));
123     }
124 
125     return skeleton
126         .addModifiers(completeMethod.modifiers)
127         .addTypeVariables(completeMethod.typeVariables)
128         .addParameters(completeMethod.parameters)
129         .addExceptions(completeMethod.exceptions)
130         .varargs(completeMethod.varargs)
131         .addAnnotations(completeMethod.annotations)
132         .build();
133   }
134 
getRequiredSuperCall(String packageName, TypeSpec completeType)135   private Optional<CodeBlock> getRequiredSuperCall(String packageName, TypeSpec completeType) {
136     if (completeType.superclass.equals(TypeName.OBJECT)) {
137       return Optional.empty();
138     }
139 
140     ClassName rawSuperClass = (ClassName) TypeNames.rawTypeName(completeType.superclass);
141     XTypeElement superTypeElement =
142         processingEnv.requireTypeElement(rawSuperClass.canonicalName());
143 
144     ImmutableSet<XConstructorElement> accessibleConstructors =
145         superTypeElement.getConstructors().stream()
146             .filter(
147                 constructor ->
148                     // isElementAccessibleFrom doesn't take protected into account so check manually
149                     constructor.isProtected()
150                         || isElementAccessibleFrom(constructor, packageName))
151             .collect(toImmutableSet());
152 
153     // If there's an accessible default constructor we don't need to call super() manually.
154     if (accessibleConstructors.isEmpty()
155             || accessibleConstructors.stream()
156                 .anyMatch(constructor -> constructor.getParameters().isEmpty())) {
157       return Optional.empty();
158     }
159 
160     return Optional.of(
161         CodeBlock.of(
162             "super($L)",
163             CodeBlocks.makeParametersCodeBlock(
164                 // We just choose the first constructor (it doesn't really matter since we're just
165                 // trying to ensure the constructor body compiles).
166                 accessibleConstructors.stream().findFirst().get().getParameters().stream()
167                     .map(XExecutableParameterElement::getType)
168                     .map(XType::getTypeName)
169                     .map(SourceFileHjarGenerator::getDefaultValueCodeBlock)
170                     .collect(toImmutableList()))));
171   }
172 
173   /**
174    * Returns a {@link CodeBlock} containing the default value for the given {@code typeName}.
175    *
176    * <p>See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html.
177    */
getDefaultValueCodeBlock(TypeName typeName)178   private static CodeBlock getDefaultValueCodeBlock(TypeName typeName) {
179     if (typeName.isPrimitive()) {
180       if (typeName.equals(TypeName.BOOLEAN)) {
181         return CodeBlock.of("false");
182       } else if (typeName.equals(TypeName.CHAR)) {
183         return CodeBlock.of("'\u0000'");
184       } else if (typeName.equals(TypeName.BYTE)) {
185         return CodeBlock.of("0");
186       } else if (typeName.equals(TypeName.SHORT)) {
187         return CodeBlock.of("0");
188       } else if (typeName.equals(TypeName.INT)) {
189         return CodeBlock.of("0");
190       } else if (typeName.equals(TypeName.LONG)) {
191         return CodeBlock.of("0L");
192       } else if (typeName.equals(TypeName.FLOAT)) {
193         return CodeBlock.of("0.0f");
194       } else if (typeName.equals(TypeName.DOUBLE)) {
195         return CodeBlock.of("0.0d");
196       } else {
197         throw new AssertionError("Unexpected type: " + typeName);
198       }
199     }
200     return CodeBlock.of("null");
201   }
202 
skeletonField(FieldSpec completeField)203   private FieldSpec skeletonField(FieldSpec completeField) {
204     return FieldSpec.builder(
205             completeField.type,
206             completeField.name,
207             completeField.modifiers.toArray(new Modifier[0]))
208         .addAnnotations(completeField.annotations)
209         .build();
210   }
211 }
212