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