Home | History | Annotate | Download | only in platform
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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 package com.android.ide.common.resources.platform;
     17 
     18 import static com.android.SdkConstants.ANDROID_URI;
     19 import static com.android.SdkConstants.DOT_XML;
     20 
     21 import com.android.SdkConstants;
     22 import com.android.annotations.NonNull;
     23 import com.android.ide.common.api.IAttributeInfo.Format;
     24 import com.android.ide.common.resources.ResourceItem;
     25 import com.android.ide.common.resources.ResourceRepository;
     26 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
     27 import com.android.ide.eclipse.mock.Mocks;
     28 import com.android.io.IAbstractFolder;
     29 import com.android.io.IAbstractResource;
     30 import com.android.resources.ResourceType;
     31 import com.android.utils.StdLogger;
     32 import com.google.common.base.Charsets;
     33 import com.google.common.collect.ArrayListMultimap;
     34 import com.google.common.collect.Multimap;
     35 import com.google.common.collect.Sets;
     36 import com.google.common.io.Files;
     37 
     38 import junit.framework.TestCase;
     39 
     40 import org.w3c.dom.Attr;
     41 import org.w3c.dom.Document;
     42 import org.w3c.dom.Element;
     43 import org.w3c.dom.NamedNodeMap;
     44 
     45 import java.io.File;
     46 import java.io.IOException;
     47 import java.util.Collection;
     48 import java.util.EnumSet;
     49 import java.util.Map;
     50 import java.util.Set;
     51 
     52 @SuppressWarnings("javadoc")
     53 public class AttributeInfoTest extends TestCase {
     54     public void testSimple() throws Exception {
     55         AttributeInfo info = new AttributeInfo("test", EnumSet.noneOf(Format.class));
     56         assertTrue(info.isValid("", null, null));
     57         assertTrue(info.isValid("a b c", null, null));
     58         assertTrue(info.isValid("@android foo bar", null, null));
     59     }
     60 
     61     public void testIsValidString() throws Exception {
     62         AttributeInfo info = new AttributeInfo("test", Format.STRING_SET);
     63         assertTrue(info.isValid("", null, null));
     64         assertTrue(info.isValid("a b c", null, null));
     65         assertTrue(info.isValid("@android foo bar", null, null));
     66     }
     67 
     68     public void testIsValidBoolean() throws Exception {
     69         AttributeInfo info = new AttributeInfo("test", Format.BOOLEAN_SET);
     70         assertTrue(info.isValid("true", null, null));
     71         assertTrue(info.isValid("false", null, null));
     72         assertFalse(info.isValid("", null, null));
     73         assertTrue(info.isValid("TRUE", null, null));
     74         assertTrue(info.isValid("True", null, null));
     75         assertTrue(info.isValid("FALSE", null, null));
     76         assertTrue(info.isValid("False", null, null));
     77     }
     78 
     79     public void testIsValidInteger() throws Exception {
     80         AttributeInfo info = new AttributeInfo("test", Format.INTEGER_SET);
     81         assertTrue(info.isValid("0", null, null));
     82         assertTrue(info.isValid("1", null, null));
     83         assertTrue(info.isValid("10", null, null));
     84         assertTrue(info.isValid("-10", null, null));
     85         assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null));
     86 
     87         assertFalse(info.isValid("", null, null));
     88         assertFalse(info.isValid("a", null, null));
     89         assertFalse(info.isValid("a1", null, null));
     90         assertFalse(info.isValid("1a", null, null));
     91         assertFalse(info.isValid("1.0", null, null));
     92     }
     93 
     94     public void testIsValidFloat() throws Exception {
     95         AttributeInfo info = new AttributeInfo("test", Format.FLOAT_SET);
     96         assertTrue(info.isValid("0", null, null));
     97         assertTrue(info.isValid("1", null, null));
     98         assertTrue(info.isValid("10", null, null));
     99         assertTrue(info.isValid("-10", null, null));
    100         assertTrue(info.isValid("-10.1234", null, null));
    101         assertTrue(info.isValid(".1", null, null));
    102         assertTrue(info.isValid("-.1", null, null));
    103         assertTrue(info.isValid("1.5e22", null, null));
    104         assertTrue(info.isValid(Integer.toString(Integer.MAX_VALUE), null, null));
    105 
    106         assertFalse(info.isValid("", null, null));
    107         assertFalse(info.isValid(".", null, null));
    108         assertFalse(info.isValid("-.", null, null));
    109         assertFalse(info.isValid("a", null, null));
    110         assertFalse(info.isValid("a1", null, null));
    111         assertFalse(info.isValid("1a", null, null));
    112     }
    113 
    114     public void testIsValidDimension() throws Exception {
    115         AttributeInfo info = new AttributeInfo("test", Format.DIMENSION_SET);
    116         assertTrue(info.isValid("0dp", null, null));
    117         assertTrue(info.isValid("1dp", null, null));
    118         assertTrue(info.isValid("10dip", null, null));
    119         assertTrue(info.isValid("-10px", null, null));
    120         assertTrue(info.isValid("-10.1234mm", null, null));
    121         assertTrue(info.isValid("14sp", null, null));
    122         assertTrue(info.isValid("72pt", null, null));
    123 
    124         assertFalse(info.isValid("", null, null));
    125         assertFalse(info.isValid("5", null, null));
    126         assertFalse(info.isValid("50ps", null, null));
    127         // Since we allow resources even when not specified in format, don't assert
    128         // this:
    129         //assertFalse(info.isValid("@dimen/foo"));
    130     }
    131 
    132     public void testIsValidColor() throws Exception {
    133         AttributeInfo info = new AttributeInfo("test", Format.COLOR_SET);
    134         assertTrue(info.isValid("#fff", null, null));
    135         assertTrue(info.isValid("#ffffff", null, null));
    136         assertTrue(info.isValid("#12345678", null, null));
    137         assertTrue(info.isValid("#abcdef00", null, null));
    138 
    139         assertFalse(info.isValid("", null, null));
    140         assertFalse(info.isValid("#fffffffff", null, null));
    141         assertFalse(info.isValid("red", null, null));
    142         assertFalse(info.isValid("rgb(1,2,3)", null, null));
    143     }
    144 
    145     public void testIsValidFraction() throws Exception {
    146         AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.FRACTION));
    147         assertTrue(info.isValid("5%", null, null));
    148         assertTrue(info.isValid("25%p", null, null));
    149 
    150         // We don't validate fractions accurately yet
    151         //assertFalse(info.isValid(""));
    152         //assertFalse(info.isValid("50%%"));
    153         //assertFalse(info.isValid("50"));
    154         //assertFalse(info.isValid("-2%"));
    155     }
    156 
    157     public void testIsValidReference() throws Exception {
    158         AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET);
    159         assertTrue(info.isValid("@android:string/foo", null, null));
    160         assertTrue(info.isValid("@string/foo", null, null));
    161         assertTrue(info.isValid("@dimen/foo", null, null));
    162         assertTrue(info.isValid("@color/foo", null, null));
    163         assertTrue(info.isValid("@animator/foo", null, null));
    164         assertTrue(info.isValid("@anim/foo", null, null));
    165         assertTrue(info.isValid("?android:attr/textAppearanceMedium", null, null));
    166         assertTrue(info.isValid("?textAppearanceMedium", null, null));
    167 
    168         assertFalse(info.isValid("", null, null));
    169         assertFalse(info.isValid("foo", null, null));
    170         assertFalse(info.isValid("3.4", null, null));
    171     }
    172 
    173     public void testIsValidEnum() throws Exception {
    174         AttributeInfo info = new AttributeInfo("test", Format.ENUM_SET);
    175         info.setEnumValues(new String[] { "wrap_content", "match_parent" });
    176         assertTrue(info.isValid("wrap_content", null, null));
    177         assertTrue(info.isValid("match_parent", null, null));
    178         assertFalse(info.isValid("", null, null));
    179         assertFalse(info.isValid("other", null, null));
    180         assertFalse(info.isValid("50", null, null));
    181     }
    182 
    183     public void testIsValidFlag() throws Exception {
    184         AttributeInfo info = new AttributeInfo("test", Format.FLAG_SET);
    185         info.setFlagValues(new String[] { "left", "top", "right", "bottom" });
    186         assertTrue(info.isValid("left", null, null));
    187         assertTrue(info.isValid("top", null, null));
    188         assertTrue(info.isValid("left|top", null, null));
    189         assertTrue(info.isValid("", null, null));
    190 
    191         assertFalse(info.isValid("other", null, null));
    192         assertFalse(info.isValid("50", null, null));
    193     }
    194 
    195     public void testCombined1() throws Exception {
    196         AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.INTEGER,
    197                 Format.REFERENCE));
    198         assertTrue(info.isValid("1", null, null));
    199         assertTrue(info.isValid("@dimen/foo", null, null));
    200         assertFalse(info.isValid("foo", null, null));
    201     }
    202 
    203     public void testCombined2() throws Exception {
    204         AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.COLOR,
    205                 Format.REFERENCE));
    206         assertTrue(info.isValid("#ff00ff00", null, null));
    207         assertTrue(info.isValid("@color/foo", null, null));
    208         assertFalse(info.isValid("foo", null, null));
    209     }
    210 
    211     public void testCombined3() throws Exception {
    212         AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.STRING,
    213                 Format.REFERENCE));
    214         assertTrue(info.isValid("test", null, null));
    215         assertTrue(info.isValid("@color/foo", null, null));
    216     }
    217 
    218     public void testCombined4() throws Exception {
    219         AttributeInfo info = new AttributeInfo("test", EnumSet.<Format>of(Format.ENUM,
    220                 Format.DIMENSION));
    221         info.setEnumValues(new String[] { "wrap_content", "match_parent" });
    222         assertTrue(info.isValid("wrap_content", null, null));
    223         assertTrue(info.isValid("match_parent", null, null));
    224         assertTrue(info.isValid("50dp", null, null));
    225         assertFalse(info.isValid("50", null, null));
    226         assertFalse(info.isValid("test", null, null));
    227     }
    228 
    229     public void testResourcesExist() throws Exception {
    230         IAbstractFolder folder = Mocks.createAbstractFolder(
    231                 SdkConstants.FD_RESOURCES, new IAbstractResource[0]);
    232 
    233         AttributeInfo info = new AttributeInfo("test", Format.REFERENCE_SET);
    234         TestResourceRepository projectResources = new TestResourceRepository(folder,false);
    235         projectResources.addResource(ResourceType.STRING, "mystring");
    236         projectResources.addResource(ResourceType.DIMEN, "mydimen");
    237         TestResourceRepository frameworkResources = new TestResourceRepository(folder, true);
    238         frameworkResources.addResource(ResourceType.LAYOUT, "mylayout");
    239 
    240         assertTrue(info.isValid("@string/mystring", null, null));
    241         assertTrue(info.isValid("@dimen/mydimen", null, null));
    242         assertTrue(info.isValid("@android:layout/mylayout", null, null));
    243         assertTrue(info.isValid("?android:attr/listPreferredItemHeigh", null, null));
    244 
    245         assertTrue(info.isValid("@string/mystring", projectResources, frameworkResources));
    246         assertTrue(info.isValid("@dimen/mydimen", projectResources, frameworkResources));
    247         assertTrue(info.isValid("@android:layout/mylayout", projectResources, frameworkResources));
    248 
    249         assertFalse(info.isValid("@android:string/mystring", projectResources,
    250                 frameworkResources));
    251         assertFalse(info.isValid("@android:dimen/mydimen", projectResources, frameworkResources));
    252         assertFalse(info.isValid("@layout/mylayout", projectResources, frameworkResources));
    253         assertFalse(info.isValid("@layout/foo", projectResources, frameworkResources));
    254         assertFalse(info.isValid("@anim/foo", projectResources, frameworkResources));
    255         assertFalse(info.isValid("@android:anim/foo", projectResources, frameworkResources));
    256     }
    257 
    258     private class TestResourceRepository extends ResourceRepository {
    259         private Multimap<ResourceType, String> mResources = ArrayListMultimap.create();
    260 
    261         protected TestResourceRepository(IAbstractFolder resFolder, boolean isFrameworkRepository) {
    262             super(resFolder, isFrameworkRepository);
    263         }
    264 
    265         void addResource(ResourceType type, String name) {
    266             mResources.put(type, name);
    267         }
    268 
    269         @Override
    270         @NonNull
    271         protected ResourceItem createResourceItem(@NonNull String name) {
    272             fail("Not used in test");
    273             return null;
    274         }
    275 
    276         @Override
    277         public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) {
    278             Collection<String> names = mResources.get(type);
    279             if (names != null) {
    280                 return names.contains(name);
    281             }
    282 
    283             return false;
    284         }
    285     };
    286 
    287 
    288     public void testIsValid() throws Exception {
    289         // This test loads the full attrs.xml file and then processes a bunch of platform
    290         // resource file and makes sure that they're all considered valid. This helps
    291         // make sure that isValid() closely matches what aapt accepts.
    292         String sdkPath = System.getenv("ADT_SDK_SOURCE_PATH");
    293         assertNotNull("This test requires ADT_SDK_SOURCE_PATH to be set to point to the" +
    294                 "SDK git repository", sdkPath);
    295         File sdk = new File(sdkPath);
    296         assertNotNull("$ADT_SDK_SOURCE_PATH (" + sdk.getPath() + ") is not a directory",
    297                 sdk.isDirectory());
    298         File git = sdk.getParentFile();
    299         File attrsPath = new File(git, "frameworks" + File.separator + "base"
    300                 + File.separator + "core" + File.separator + "res" + File.separator + "res"
    301                 + File.separator + "values" + File.separator + "attrs.xml");
    302         assertTrue(attrsPath.getPath(), attrsPath.exists());
    303         AttrsXmlParser parser = new AttrsXmlParser(attrsPath.getPath(),
    304                 new StdLogger(StdLogger.Level.VERBOSE), 1100);
    305         parser.preload();
    306         Map<String, AttributeInfo> attributeMap = parser.getAttributeMap();
    307         assertNotNull(attributeMap);
    308         assertNotNull(attributeMap.get("layout_width"));
    309         Set<String> seen = Sets.newHashSet();
    310 
    311         checkDir(new File(git, "packages" + File.separator + "apps"), false, attributeMap, seen);
    312     }
    313 
    314     private void checkDir(File dir, boolean isResourceDir,
    315             Map<String, AttributeInfo> map, Set<String> seen) throws IOException {
    316         assertTrue(dir.isDirectory());
    317         File[] list = dir.listFiles();
    318         if (list != null) {
    319             for (File file : list) {
    320                 if (isResourceDir && file.isFile() && file.getPath().endsWith(DOT_XML)) {
    321                     checkXmlFile(file, map, seen);
    322                 } else if (file.isDirectory()) {
    323                     checkDir(file, isResourceDir || file.getName().equals("res"), map, seen);
    324                 }
    325             }
    326         }
    327     }
    328 
    329     private void checkXmlFile(File file, Map<String, AttributeInfo> map,
    330             Set<String> seen) throws IOException {
    331         String xml = Files.toString(file, Charsets.UTF_8);
    332         if (xml != null) {
    333             //Document doc = DomUtilities.parseStructuredDocument(xml);
    334             Document doc = DomUtilities.parseDocument(xml, false);
    335             if (doc != null && doc.getDocumentElement() != null) {
    336                 checkElement(file, doc.getDocumentElement(), map, seen);
    337             }
    338         }
    339     }
    340 
    341     private void checkElement(File file, Element element, Map<String, AttributeInfo> map,
    342             Set<String> seen) {
    343         NamedNodeMap attributes = element.getAttributes();
    344         for (int i = 0, n = attributes.getLength(); i < n; i++) {
    345             Attr attribute = (Attr) attributes.item(i);
    346 
    347             String uri = attribute.getNamespaceURI();
    348             String name = attribute.getLocalName();
    349             String value = attribute.getValue();
    350             if (ANDROID_URI.equals(uri)) {
    351                 AttributeInfo info = map.get(name);
    352                 if (info == null) {
    353                     System.out.println("Warning: Unknown attribute '" + name + "' in " + file);
    354                             return;
    355                 }
    356                 if (!info.isValid(value, null, null)) {
    357                     if (name.equals("duration") || name.equals("exitFadeDuration")) {
    358                         // Already known
    359                         return;
    360                     }
    361                     String message = "In file " +
    362                             file.getPath() + ":\nCould not validate value \"" + value
    363                             + "\" for attribute '"
    364                             + name + "' where the attribute info has formats " + info.getFormats()
    365                             + "\n";
    366                     System.out.println("\n" + message);
    367                     fail(message);
    368                 }
    369                 if ((value.startsWith("@") || value.startsWith("?")) &&
    370                         !info.getFormats().contains(Format.REFERENCE)) {
    371                     // Print out errors in attrs.xml
    372 
    373                     if (!seen.contains(name)) {
    374                         seen.add(name);
    375                         System.out.println("\"" + name + "\" with formats " + info.getFormats()
    376                                 + " was passed a reference (" + value + ") in file " + file);
    377                     }
    378                 }
    379             }
    380         }
    381     }
    382 }
    383