/*
* 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);
}
}