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