1 /* 2 * Copyright (C) 2024 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.writing; 18 19 import static dagger.internal.codegen.base.MapKeyAccessibility.isMapKeyAccessibleFrom; 20 import static javax.lang.model.element.Modifier.FINAL; 21 import static javax.lang.model.element.Modifier.PRIVATE; 22 import static javax.lang.model.element.Modifier.STATIC; 23 24 import androidx.room.compiler.processing.XAnnotation; 25 import com.google.common.base.Preconditions; 26 import com.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.CodeBlock; 28 import com.squareup.javapoet.FieldSpec; 29 import com.squareup.javapoet.TypeSpec; 30 import dagger.internal.codegen.base.UniqueNameSet; 31 import dagger.internal.codegen.javapoet.TypeNames; 32 import dagger.internal.codegen.model.Key; 33 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation; 34 import java.util.HashMap; 35 import java.util.Map; 36 37 /** Keeps track of all providers for DaggerMap keys. */ 38 public final class LazyClassKeyProviders { 39 public static final String MAP_KEY_PROVIDER_NAME = "LazyClassKeyProvider"; 40 private final ClassName mapKeyProviderType; 41 private final Map<Key, FieldSpec> entries = new HashMap<>(); 42 private final Map<Key, FieldSpec> keepClassNamesFields = new HashMap<>(); 43 private final UniqueNameSet uniqueFieldNames = new UniqueNameSet(); 44 private final ShardImplementation shardImplementation; 45 private boolean providerAdded = false; 46 LazyClassKeyProviders(ShardImplementation shardImplementation)47 LazyClassKeyProviders(ShardImplementation shardImplementation) { 48 String name = shardImplementation.getUniqueClassName(MAP_KEY_PROVIDER_NAME); 49 mapKeyProviderType = shardImplementation.name().nestedClass(name); 50 this.shardImplementation = shardImplementation; 51 } 52 53 /** Returns a reference to a field in LazyClassKeyProvider that corresponds to this binding. */ getMapKeyExpression(Key key)54 CodeBlock getMapKeyExpression(Key key) { 55 // This is for avoid generating empty LazyClassKeyProvider in codegen tests 56 if (!providerAdded) { 57 shardImplementation.addTypeSupplier(this::build); 58 providerAdded = true; 59 } 60 if (!entries.containsKey(key)) { 61 addField(key); 62 } 63 return CodeBlock.of("$T.$N", mapKeyProviderType, entries.get(key)); 64 } 65 addField(Key key)66 private void addField(Key key) { 67 Preconditions.checkArgument( 68 key.multibindingContributionIdentifier().isPresent() 69 && key.multibindingContributionIdentifier() 70 .get() 71 .bindingMethod() 72 .xprocessing() 73 .hasAnnotation(TypeNames.LAZY_CLASS_KEY)); 74 XAnnotation lazyClassKeyAnnotation = 75 key.multibindingContributionIdentifier() 76 .get() 77 .bindingMethod() 78 .xprocessing() 79 .getAnnotation(TypeNames.LAZY_CLASS_KEY); 80 ClassName lazyClassKey = 81 lazyClassKeyAnnotation.getAsType("value").getTypeElement().getClassName(); 82 entries.put( 83 key, 84 FieldSpec.builder( 85 TypeNames.STRING, 86 uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_'))) 87 // TODO(b/217435141): Leave the field as non-final. We will apply @IdentifierNameString 88 // on the field, which doesn't work well with static final fields. 89 .addModifiers(STATIC) 90 .initializer("$S", lazyClassKey.reflectionName()) 91 .build()); 92 // To be able to apply -includedescriptorclasses rule to keep the class names referenced by 93 // LazyClassKey, we need to generate fields that uses those classes as type in 94 // LazyClassKeyProvider. For types that are not accessible from the generated component, we 95 // generate fields in the proxy class. 96 // Note: the generated field should not be initialized to avoid class loading. 97 if (isMapKeyAccessibleFrom(lazyClassKeyAnnotation, shardImplementation.name().packageName())) { 98 keepClassNamesFields.put( 99 key, 100 FieldSpec.builder( 101 lazyClassKey, 102 uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_'))) 103 .addAnnotation(TypeNames.KEEP_FIELD_TYPE) 104 .build()); 105 } 106 } 107 build()108 private TypeSpec build() { 109 TypeSpec.Builder builder = 110 TypeSpec.classBuilder(mapKeyProviderType) 111 .addAnnotation(TypeNames.IDENTIFIER_NAME_STRING) 112 .addModifiers(PRIVATE, STATIC, FINAL) 113 .addFields(entries.values()) 114 .addFields(keepClassNamesFields.values()); 115 return builder.build(); 116 } 117 } 118