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