1 /* 2 * Copyright 2020 The Android Open Source Project 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 // @exportToFramework:skipFile() 17 package androidx.appsearch.app; 18 19 import androidx.annotation.AnyThread; 20 import androidx.annotation.RestrictTo; 21 import androidx.appsearch.exceptions.AppSearchException; 22 import androidx.core.util.Preconditions; 23 24 import org.jspecify.annotations.NonNull; 25 26 import java.util.HashMap; 27 import java.util.Map; 28 29 /** 30 * A registry which maintains instances of {@link DocumentClassFactory}. 31 * @exportToFramework:hide 32 */ 33 @AnyThread 34 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 35 public final class DocumentClassFactoryRegistry { 36 private static final String GEN_CLASS_PREFIX = "$$__AppSearch__"; 37 38 private static volatile DocumentClassFactoryRegistry sInstance = null; 39 40 private final Map<Class<?>, DocumentClassFactory<?>> mFactories = new HashMap<>(); 41 DocumentClassFactoryRegistry()42 private DocumentClassFactoryRegistry() {} 43 44 /** Returns the singleton instance of {@link DocumentClassFactoryRegistry}. */ getInstance()45 public static @NonNull DocumentClassFactoryRegistry getInstance() { 46 if (sInstance == null) { 47 synchronized (DocumentClassFactoryRegistry.class) { 48 if (sInstance == null) { 49 sInstance = new DocumentClassFactoryRegistry(); 50 } 51 } 52 } 53 return sInstance; 54 } 55 56 /** 57 * Gets the {@link DocumentClassFactory} instance that can convert to and from objects of type 58 * {@code T}. 59 * 60 * @throws AppSearchException if no factory for this document class could be found on the 61 * classpath 62 */ 63 @SuppressWarnings("unchecked") getOrCreateFactory(@onNull Class<T> documentClass)64 public <T> @NonNull DocumentClassFactory<T> getOrCreateFactory(@NonNull Class<T> documentClass) 65 throws AppSearchException { 66 Preconditions.checkNotNull(documentClass); 67 DocumentClassFactory<?> factory; 68 synchronized (this) { 69 factory = mFactories.get(documentClass); 70 } 71 if (factory == null) { 72 factory = loadFactoryByReflection(documentClass); 73 synchronized (this) { 74 DocumentClassFactory<?> racingFactory = mFactories.get(documentClass); 75 if (racingFactory == null) { 76 mFactories.put(documentClass, factory); 77 } else { 78 // Another thread beat us to it 79 factory = racingFactory; 80 } 81 } 82 } 83 return (DocumentClassFactory<T>) factory; 84 } 85 86 /** 87 * Gets the {@link DocumentClassFactory} instance that can convert to and from objects of type 88 * {@code T}. 89 * 90 * @throws AppSearchException if no factory for this document class could be found on the 91 * classpath 92 */ 93 @SuppressWarnings("unchecked") getOrCreateFactory(@onNull T documentClass)94 public <T> @NonNull DocumentClassFactory<T> getOrCreateFactory(@NonNull T documentClass) 95 throws AppSearchException { 96 Preconditions.checkNotNull(documentClass); 97 Class<?> clazz = documentClass.getClass(); 98 DocumentClassFactory<?> factory = getOrCreateFactory(clazz); 99 return (DocumentClassFactory<T>) factory; 100 } 101 loadFactoryByReflection(@onNull Class<?> documentClass)102 private DocumentClassFactory<?> loadFactoryByReflection(@NonNull Class<?> documentClass) 103 throws AppSearchException { 104 Package pkg = documentClass.getPackage(); 105 String simpleName = documentClass.getCanonicalName(); 106 if (simpleName == null) { 107 throw new AppSearchException( 108 AppSearchResult.RESULT_INTERNAL_ERROR, 109 "Failed to find simple name for document class \"" + documentClass 110 + "\". Perhaps it is anonymous?"); 111 } 112 113 // Creates factory class name under the package. 114 // For a class Foo annotated with @Document, we will generated a 115 // $$__AppSearch__Foo.class under the package. 116 // For an inner class Foo.Bar annotated with @Document, we will generated a 117 // $$__AppSearch__Foo$$__Bar.class under the package. 118 String packageName = ""; 119 if (pkg != null) { 120 packageName = pkg.getName() + "."; 121 simpleName = simpleName.substring(packageName.length()).replace(".", "$$__"); 122 } 123 String factoryClassName = packageName + GEN_CLASS_PREFIX + simpleName; 124 125 Class<?> factoryClass; 126 try { 127 factoryClass = Class.forName(factoryClassName); 128 } catch (ClassNotFoundException e) { 129 // If the current class or interface has only one parent interface/class, then try to 130 // look at the unique parent. 131 Class<?> superClass = documentClass.getSuperclass(); 132 Class<?>[] superInterfaces = documentClass.getInterfaces(); 133 if (superClass == Object.class) { 134 superClass = null; 135 } 136 int numParent = superInterfaces.length; 137 if (superClass != null) { 138 numParent += 1; 139 } 140 141 if (numParent == 1) { 142 if (superClass != null) { 143 return loadFactoryByReflection(superClass); 144 } else { 145 return loadFactoryByReflection(superInterfaces[0]); 146 } 147 } 148 149 String errorMessage = "Failed to find document class converter \"" + factoryClassName 150 + "\". Perhaps the annotation processor was not run or the class was " 151 + "proguarded out?"; 152 if (numParent > 1) { 153 errorMessage += " Or, this class may not have been annotated with @Document, and " 154 + "there is an ambiguity to determine a unique @Document annotated parent " 155 + "class/interface."; 156 } 157 158 throw new AppSearchException(AppSearchResult.RESULT_INTERNAL_ERROR, errorMessage, e); 159 } 160 Object instance; 161 try { 162 instance = factoryClass.getDeclaredConstructor().newInstance(); 163 } catch (Exception e) { 164 throw new AppSearchException( 165 AppSearchResult.RESULT_INTERNAL_ERROR, 166 "Failed to construct document class converter \"" + factoryClassName + "\"", 167 e); 168 } 169 return (DocumentClassFactory<?>) instance; 170 } 171 } 172