/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.idea.rendering; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.res2.ResourceItem; import com.android.ide.common.res2.ResourceRepository; import com.android.ide.common.resources.TestResourceRepository; import com.android.resources.ResourceType; import com.android.util.Pair; import com.google.common.collect.ListMultimap; import org.jetbrains.android.AndroidTestCase; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Map; public class AarResourceClassGeneratorTest extends AndroidTestCase { public void test() throws Exception { final ResourceRepository repository = TestResourceRepository.createRes2(false, new Object[]{ "layout/layout1.xml", "<!--contents doesn't matter-->", "layout-land/layout1.xml", "<!--contents doesn't matter-->", "values/styles.xml", "" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <style name=\"MyTheme\" parent=\"android:Theme.Light\">\n" + " <item name=\"android:textColor\">#999999</item>\n" + " <item name=\"foo\">?android:colorForeground</item>\n" + " </style>\n" + " <declare-styleable name=\"GridLayout_Layout\">\n" + " <attr name=\"android:layout_width\" />\n" + " <attr name=\"android:layout_height\" />\n" + " <attr name=\"layout_columnSpan\" format=\"integer\" min=\"1\" />\n" + " <attr name=\"layout_gravity\">\n" + " <flag name=\"top\" value=\"0x30\" />\n" + " <flag name=\"bottom\" value=\"0x50\" />\n" + " <flag name=\"center_vertical\" value=\"0x10\" />\n" + " </attr>\n" + " </declare-styleable>\n" + "</resources>\n", "values/strings.xml", "" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <item type=\"id\" name=\"action_bar_refresh\" />\n" + " <item type=\"dimen\" name=\"dialog_min_width_major\">45%</item>\n" + " <string name=\"show_all_apps\">All</string>\n" + " <string name=\"menu_wallpaper\">Wallpaper</string>\n" + "</resources>\n", "values-es/strings.xml", "" + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <string name=\"show_all_apps\">Todo</string>\n" + "</resources>\n",}); LocalResourceRepository resources = new LocalResourceRepository("test") { @NonNull @Override protected Map<ResourceType, ListMultimap<String, ResourceItem>> getMap() { return repository.getItems(); } @Nullable @Override protected ListMultimap<String, ResourceItem> getMap(ResourceType type, boolean create) { return repository.getItems().get(type); } }; AppResourceRepository appResources = new AppResourceRepository(myFacet, Collections.singletonList(resources), Collections.singletonList(resources)); AarResourceClassGenerator generator = AarResourceClassGenerator.create(appResources, appResources); assertNotNull(generator); String name = "my.test.pkg.R"; Class<?> clz = generateClass(generator, name); assertNotNull(clz); assertEquals(name, clz.getName()); assertTrue(Modifier.isPublic(clz.getModifiers())); assertTrue(Modifier.isFinal(clz.getModifiers())); assertFalse(Modifier.isInterface(clz.getModifiers())); Object r = clz.newInstance(); assertNotNull(r); name = "my.test.pkg.R$string"; clz = generateClass(generator, name); assertNotNull(clz); assertEquals(name, clz.getName()); assertTrue(Modifier.isPublic(clz.getModifiers())); assertTrue(Modifier.isFinal(clz.getModifiers())); assertFalse(Modifier.isInterface(clz.getModifiers())); try { clz.getField("nonexistent"); fail("Shouldn't find nonexistent fields"); } catch (NoSuchFieldException e) { // pass } Field field1 = clz.getField("menu_wallpaper"); Object value1 = field1.get(null); assertEquals(Integer.TYPE, field1.getType()); assertNotNull(value1); assertEquals(2, clz.getFields().length); Field field2 = clz.getField("show_all_apps"); assertNotNull(field2); assertEquals(Integer.TYPE, field2.getType()); assertTrue(Modifier.isPublic(field2.getModifiers())); assertTrue(Modifier.isFinal(field2.getModifiers())); assertTrue(Modifier.isStatic(field2.getModifiers())); assertFalse(Modifier.isSynchronized(field2.getModifiers())); assertFalse(Modifier.isTransient(field2.getModifiers())); assertFalse(Modifier.isStrict(field2.getModifiers())); assertFalse(Modifier.isVolatile(field2.getModifiers())); r = clz.newInstance(); assertNotNull(r); Class<?> enclosingClass = clz.getEnclosingClass(); assertNotNull(enclosingClass); // Make sure the id's match what we've dynamically allocated in the resource repository @SuppressWarnings("deprecation") Pair<ResourceType,String> pair = appResources.resolveResourceId((Integer)clz.getField("menu_wallpaper").get(null)); assertNotNull(pair); assertEquals(ResourceType.STRING, pair.getFirst()); assertEquals("menu_wallpaper", pair.getSecond()); assertEquals(clz.getField("menu_wallpaper").get(null), appResources.getResourceId(ResourceType.STRING, "menu_wallpaper")); assertEquals(clz.getField("show_all_apps").get(null), appResources.getResourceId(ResourceType.STRING, "show_all_apps")); // Test attr class! name = "my.test.pkg.R$attr"; clz = generateClass(generator, name); assertNotNull(clz); assertEquals(name, clz.getName()); assertTrue(Modifier.isPublic(clz.getModifiers())); assertTrue(Modifier.isFinal(clz.getModifiers())); assertFalse(Modifier.isInterface(clz.getModifiers())); assertEquals(2, clz.getFields().length); field1 = clz.getField("layout_gravity"); assertNotNull(field1); Object gravityValue = field1.get(null); Object layoutColumnSpanValue = clz.getField("layout_columnSpan").get(null); // Test styleable class! name = "my.test.pkg.R$styleable"; clz = generateClass(generator, name); assertNotNull(clz); r = clz.newInstance(); assertEquals(name, clz.getName()); assertTrue(Modifier.isPublic(clz.getModifiers())); assertTrue(Modifier.isFinal(clz.getModifiers())); assertFalse(Modifier.isInterface(clz.getModifiers())); try { clz.getField("nonexistent"); fail("Shouldn't find nonexistent fields"); } catch (NoSuchFieldException e) { // pass } field1 = clz.getField("GridLayout_Layout"); value1 = field1.get(null); assertEquals("[I", field1.getType().getName()); assertNotNull(value1); assertEquals(5, clz.getFields().length); field2 = clz.getField("GridLayout_Layout_android_layout_height"); assertNotNull(field2); assertNotNull(clz.getField("GridLayout_Layout_android_layout_width")); assertNotNull(clz.getField("GridLayout_Layout_layout_columnSpan")); assertEquals(Integer.TYPE, field2.getType()); assertTrue(Modifier.isPublic(field2.getModifiers())); assertTrue(Modifier.isFinal(field2.getModifiers())); assertTrue(Modifier.isStatic(field2.getModifiers())); assertFalse(Modifier.isSynchronized(field2.getModifiers())); assertFalse(Modifier.isTransient(field2.getModifiers())); assertFalse(Modifier.isStrict(field2.getModifiers())); assertFalse(Modifier.isVolatile(field2.getModifiers())); int[] indices = (int[])clz.getField("GridLayout_Layout").get(r); Object layoutColumnSpanIndex = clz.getField("GridLayout_Layout_layout_columnSpan").get(null); assertTrue(layoutColumnSpanIndex instanceof Integer); int id = indices[(Integer)layoutColumnSpanIndex]; assertEquals(id, layoutColumnSpanValue); Object gravityIndex = clz.getField("GridLayout_Layout_layout_gravity").get(null); assertTrue(gravityIndex instanceof Integer); id = indices[(Integer)gravityIndex]; assertEquals(id, gravityValue); // The exact source order of attributes must be matched such that array indexing of the styleable arrays // reaches the right elements. For this reason, we use a LinkedHashMap in DeclareStyleableResourceValue. // Without this, using the v7 GridLayout widget and putting app:layout_gravity="left" on a child will // give value conversion errors. assertEquals(2, layoutColumnSpanIndex); assertEquals(3, gravityIndex); name = "my.test.pkg.R$id"; clz = generateClass(generator, name); assertNotNull(clz); r = clz.newInstance(); assertNotNull(r); assertEquals(name, clz.getName()); // TODO: Flag and enum values should also be created as id's by the ValueResourceParser //assertNotNull(clz.getField("top")); //assertNotNull(clz.getField("bottom")); //assertNotNull(clz.getField("center_vertical")); } @Nullable protected static Class<?> generateClass(final AarResourceClassGenerator generator, String name) throws ClassNotFoundException { ClassLoader classLoader = new ClassLoader(AarResourceClassGeneratorTest.class.getClassLoader()) { @Override public Class<?> loadClass(String s) throws ClassNotFoundException { if (!s.startsWith("java")) { // Don't try to load super class final byte[] data = generator.generate(s); if (data != null) { return defineClass(null, data, 0, data.length); } } return super.loadClass(s); } }; return classLoader.loadClass(name); } }