1/*
2 * Copyright (C) 2014 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
17package com.android.layoutlib.bridge.intensive.setup;
18
19import com.android.SdkConstants;
20import com.android.ide.common.rendering.api.ActionBarCallback;
21import com.android.ide.common.rendering.api.AdapterBinding;
22import com.android.ide.common.rendering.api.ILayoutPullParser;
23import com.android.ide.common.rendering.api.IProjectCallback;
24import com.android.ide.common.rendering.api.ResourceReference;
25import com.android.ide.common.rendering.api.ResourceValue;
26import com.android.resources.ResourceType;
27import com.android.ide.common.resources.IntArrayWrapper;
28import com.android.util.Pair;
29import com.android.utils.ILogger;
30
31import java.io.ByteArrayOutputStream;
32import java.io.File;
33import java.io.IOException;
34import java.io.InputStream;
35import java.lang.reflect.Constructor;
36import java.lang.reflect.Field;
37import java.lang.reflect.Modifier;
38import java.util.Map;
39
40import com.google.android.collect.Maps;
41
42@SuppressWarnings("deprecation") // For Pair
43public class LayoutLibTestCallback extends ClassLoader implements IProjectCallback {
44
45    private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/";
46    private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication";
47
48    private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap();
49    private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap();
50    private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap();
51    private final Map<String, Class<?>> mClasses = Maps.newHashMap();
52    private final ILogger mLog;
53    private final ActionBarCallback mActionBarCallback = new ActionBarCallback();
54
55    public LayoutLibTestCallback(ILogger logger) {
56        mLog = logger;
57    }
58
59    public void initResources() throws ClassNotFoundException {
60        Class<?> rClass = loadClass(PACKAGE_NAME + ".R");
61        Class<?>[] nestedClasses = rClass.getDeclaredClasses();
62        for (Class<?> resClass : nestedClasses) {
63            final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName());
64
65            if (resType != null) {
66                final Map<String, Integer> resName2Id = Maps.newHashMap();
67                mResources.put(resType, resName2Id);
68
69                for (Field field : resClass.getDeclaredFields()) {
70                    final int modifiers = field.getModifiers();
71                    if (Modifier.isStatic(modifiers)) { // May not be final in library projects
72                        final Class<?> type = field.getType();
73                        try {
74                            if (type.isArray() && type.getComponentType() == int.class) {
75                                mStyleableValueToNameMap.put(
76                                        new IntArrayWrapper((int[]) field.get(null)),
77                                        field.getName());
78                            } else if (type == int.class) {
79                                final Integer value = (Integer) field.get(null);
80                                mProjectResources.put(value, Pair.of(resType, field.getName()));
81                                resName2Id.put(field.getName(), value);
82                            } else {
83                                mLog.error(null, "Unknown field type in R class: %1$s", type);
84                            }
85                        } catch (IllegalAccessException ignored) {
86                            mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R");
87                        }
88                    }
89                }
90            }
91        }
92    }
93
94    @Override
95    protected Class<?> findClass(String name) throws ClassNotFoundException {
96        Class<?> aClass = mClasses.get(name);
97        if (aClass != null) {
98            return aClass;
99        }
100        String pathName = PROJECT_CLASSES_LOCATION.concat(name.replace('.', '/')).concat(".class");
101        InputStream classInputStream = getClass().getResourceAsStream(pathName);
102        if (classInputStream == null) {
103            throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName);
104        }
105        byte[] data;
106        try {
107            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
108            int nRead;
109            data = new byte[16384];
110            while ((nRead = classInputStream.read(data, 0, data.length)) != -1) {
111                buffer.write(data, 0, nRead);
112            }
113            buffer.flush();
114            data = buffer.toByteArray();
115        } catch (IOException e) {
116            // Wrap the exception with ClassNotFoundException so that caller can deal with it.
117            throw new ClassNotFoundException("Unable to load class " + name, e);
118        }
119        aClass = defineClass(name, data, 0, data.length);
120        mClasses.put(name, aClass);
121        return aClass;
122    }
123
124    @Override
125    public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
126            throws Exception {
127        Class<?> viewClass = findClass(name);
128        Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature);
129        viewConstructor.setAccessible(true);
130        return viewConstructor.newInstance(constructorArgs);
131    }
132
133    @Override
134    public String getNamespace() {
135        return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S,
136                PACKAGE_NAME);
137    }
138
139    @Override
140    public Pair<ResourceType, String> resolveResourceId(int id) {
141        return mProjectResources.get(id);
142    }
143
144    @Override
145    public String resolveResourceId(int[] id) {
146        return mStyleableValueToNameMap.get(new IntArrayWrapper(id));
147    }
148
149    @Override
150    public Integer getResourceId(ResourceType type, String name) {
151        return mResources.get(type).get(name);
152    }
153
154    @Override
155    public ILayoutPullParser getParser(String layoutName) {
156        org.junit.Assert.fail("This method shouldn't be called by this version of LayoutLib.");
157        return null;
158    }
159
160    @Override
161    public ILayoutPullParser getParser(ResourceValue layoutResource) {
162        return new LayoutPullParser(new File(layoutResource.getValue()));
163    }
164
165    @Override
166    public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
167            ResourceReference itemRef, int fullPosition, int positionPerType,
168            int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
169            ViewAttribute viewAttribute, Object defaultValue) {
170        return null;
171    }
172
173    @Override
174    public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
175            Object viewObject) {
176        return null;
177    }
178
179    @Override
180    public ActionBarCallback getActionBarCallback() {
181        return mActionBarCallback;
182    }
183}
184