1c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam/* 2c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Copyright (C) 2017 The Android Open Source Project 3c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 4c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License"); 5c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * you may not use this file except in compliance with the License. 6c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * You may obtain a copy of the License at 7c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 8c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * http://www.apache.org/licenses/LICENSE-2.0 9c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 10c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Unless required by applicable law or agreed to in writing, software 11c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS, 12c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * See the License for the specific language governing permissions and 14c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * limitations under the License. 15c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 16c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 17c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lampackage com.android.setupwizardlib.items; 18c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 19c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.content.Context; 20c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.support.annotation.NonNull; 21c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.support.annotation.Nullable; 22c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.util.AttributeSet; 23c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport android.view.InflateException; 24c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 25c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport java.lang.reflect.Constructor; 26c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lamimport java.util.HashMap; 27c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 28c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam/** 29c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * An XML inflater that creates items by reading the tag as a class name, and constructs said class 30c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * by invoking the 2-argument constructor {@code Constructor(Context, AttributeSet)} via reflection. 31c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 32c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * <p>Optionally a "default package" can be specified so that for unqualified tag names (i.e. names 33c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * that do not contain "."), the default package will be prefixed onto the tag. 34c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 35c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param <T> The class where all instances (including child elements) belong to. If parent and 36c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * child elements belong to different class hierarchies, it's OK to set this to {@link Object}. 37c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 38c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lampublic abstract class ReflectionInflater<T> extends SimpleInflater<T> { 39c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 40c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /* static section */ 41c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 429ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam private static final Class<?>[] CONSTRUCTOR_SIGNATURE = 439ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam new Class<?>[] {Context.class, AttributeSet.class}; 44c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 45c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam private static final HashMap<String, Constructor<?>> sConstructorMap = new HashMap<>(); 46c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 47c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /* non-static section */ 48c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 49c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam // Array used to contain the constructor arguments (Context, AttributeSet), to avoid allocating 50c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam // a new array for creation of every item. 51c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam private final Object[] mTempConstructorArgs = new Object[2]; 52c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 53c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @Nullable 54c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam private String mDefaultPackage; 55c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 56c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @NonNull 57c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam private final Context mContext; 58c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 59c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /** 60c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Create a new inflater instance associated with a particular Context. 61c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 62c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param context The context used to resolve resource IDs. This context is also passed to the 63c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * constructor of the items created as the first argument. 64c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 65c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam protected ReflectionInflater(@NonNull Context context) { 66c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam super(context.getResources()); 67c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mContext = context; 68c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 69c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 70c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @NonNull 71c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam public Context getContext() { 72c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam return mContext; 73c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 74c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 75c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /** 76c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Instantiate the class by name. This attempts to instantiate class of the given {@code name} 77c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * found in this inflater's ClassLoader. 78c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 79c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param tagName The full name of the class to be instantiated. 80c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param attrs The XML attributes supplied for this instance. 81c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 82c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @return The newly instantiated item. 83c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 84c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @NonNull 85c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam public final T createItem(String tagName, String prefix, AttributeSet attrs) { 865ed761c1df4f5165a14218b64579e14af29d68a6Maurice Lam String qualifiedName = tagName; 875ed761c1df4f5165a14218b64579e14af29d68a6Maurice Lam if (prefix != null && qualifiedName.indexOf('.') == -1) { 885ed761c1df4f5165a14218b64579e14af29d68a6Maurice Lam qualifiedName = prefix.concat(qualifiedName); 895ed761c1df4f5165a14218b64579e14af29d68a6Maurice Lam } 909ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T 919ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam Constructor<? extends T> constructor = 929ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam (Constructor<? extends T>) sConstructorMap.get(qualifiedName); 93c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 94c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam try { 95c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam if (constructor == null) { 969ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam // Class not found in the cache, see if it's real, and try to add it 979ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam @SuppressWarnings("unchecked") // qualifiedName should correspond to a subclass of T 989ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam Class<? extends T> clazz = 999ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam (Class<? extends T>) mContext.getClassLoader().loadClass(qualifiedName); 100c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam constructor = clazz.getConstructor(CONSTRUCTOR_SIGNATURE); 101c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam constructor.setAccessible(true); 102c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam sConstructorMap.put(tagName, constructor); 103c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 104c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 105c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mTempConstructorArgs[0] = mContext; 106c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mTempConstructorArgs[1] = attrs; 1079ac3d54040b4dae6addc224fd8a69bf62ee84a83Maurice Lam final T item = constructor.newInstance(mTempConstructorArgs); 108c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mTempConstructorArgs[0] = null; 109c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mTempConstructorArgs[1] = null; 110c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam return item; 111c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } catch (Exception e) { 112c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam throw new InflateException(attrs.getPositionDescription() 113c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam + ": Error inflating class " + qualifiedName, e); 114c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 115c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 116c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 117c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @Override 118c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam protected T onCreateItem(String tagName, AttributeSet attrs) { 119c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam return createItem(tagName, mDefaultPackage, attrs); 120c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 121c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 122c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /** 123c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Sets the default package that will be searched for classes to construct for tag names that 124c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * have no explicit package. 125c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 126c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @param defaultPackage The default package. This will be prepended to the tag name, so it 127c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * should end with a period. 128c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 129c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam public void setDefaultPackage(@Nullable String defaultPackage) { 130c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam mDefaultPackage = defaultPackage; 131c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 132c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam 133c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam /** 134c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * Returns the default package, or null if it is not set. 135c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * 136c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @see #setDefaultPackage(String) 137c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam * @return The default package. 138c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam */ 139c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam @Nullable 140c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam public String getDefaultPackage() { 141c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam return mDefaultPackage; 142c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam } 143c64c94744da6d3d139c24be7dd62cbb3ceae0eb5Maurice Lam} 144