Home | History | Annotate | Download | only in resources
      1 /*
      2  * Copyright (C) 2011 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 
     17 package com.android.ide.common.resources;
     18 
     19 import static com.android.AndroidConstants.FD_RES_VALUES;
     20 
     21 import com.android.annotations.NonNull;
     22 import com.android.annotations.Nullable;
     23 import com.android.ide.common.log.ILogger;
     24 import com.android.io.IAbstractFile;
     25 import com.android.io.IAbstractFolder;
     26 import com.android.resources.ResourceType;
     27 
     28 import org.kxml2.io.KXmlParser;
     29 import org.xmlpull.v1.XmlPullParser;
     30 
     31 import java.io.BufferedReader;
     32 import java.io.IOException;
     33 import java.io.InputStreamReader;
     34 import java.io.Reader;
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.Collections;
     38 import java.util.EnumMap;
     39 import java.util.HashMap;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.Map.Entry;
     43 
     44 /**
     45  * Framework resources repository.
     46  *
     47  * This behaves the same as {@link ResourceRepository} except that it differentiates between
     48  * resources that are public and non public.
     49  * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return
     50  * public resources. This is typically used to display resource lists in the UI.
     51  *
     52  * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)}
     53  * returns all resources, even the non public ones so that this can be used for rendering.
     54  */
     55 public class FrameworkResources extends ResourceRepository {
     56 
     57     /**
     58      * Map of {@link ResourceType} to list of items. It is guaranteed to contain a list for all
     59      * possible values of ResourceType.
     60      */
     61     protected final Map<ResourceType, List<ResourceItem>> mPublicResourceMap =
     62         new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class);
     63 
     64     public FrameworkResources() {
     65         super(true /*isFrameworkRepository*/);
     66     }
     67 
     68     /**
     69      * Returns a {@link Collection} (always non null, but can be empty) of <b>public</b>
     70      * {@link ResourceItem} matching a given {@link ResourceType}.
     71      *
     72      * @param type the type of the resources to return
     73      * @return a collection of items, possible empty.
     74      */
     75     @Override
     76     public List<ResourceItem> getResourceItemsOfType(ResourceType type) {
     77         return mPublicResourceMap.get(type);
     78     }
     79 
     80     /**
     81      * Returns whether the repository has <b>public</b> resources of a given {@link ResourceType}.
     82      * @param type the type of resource to check.
     83      * @return true if the repository contains resources of the given type, false otherwise.
     84      */
     85     @Override
     86     public boolean hasResourcesOfType(ResourceType type) {
     87         return mPublicResourceMap.get(type).size() > 0;
     88     }
     89 
     90     @Override
     91     protected ResourceItem createResourceItem(String name) {
     92         return new FrameworkResourceItem(name);
     93     }
     94 
     95     /**
     96      * Reads the public.xml file in data/res/values/ for a given resource folder and builds up
     97      * a map of public resources.
     98      *
     99      * This map is a subset of the full resource map that only contains framework resources
    100      * that are public.
    101      *
    102      * @param resFolder The root folder of the resources
    103      * @param logger a logger to report issues to
    104      */
    105     public void loadPublicResources(@NonNull IAbstractFolder resFolder, @Nullable ILogger logger) {
    106         IAbstractFolder valueFolder = resFolder.getFolder(FD_RES_VALUES);
    107         if (valueFolder.exists() == false) {
    108             return;
    109         }
    110 
    111         IAbstractFile publicXmlFile = valueFolder.getFile("public.xml"); //$NON-NLS-1$
    112         if (publicXmlFile.exists()) {
    113             Reader reader = null;
    114             try {
    115                 reader = new BufferedReader(new InputStreamReader(publicXmlFile.getContents(),
    116                         "UTF-8")); //$NON-NLS-1$
    117                 KXmlParser parser = new KXmlParser();
    118                 parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
    119                 parser.setInput(reader);
    120 
    121                 ResourceType lastType = null;
    122                 String lastTypeName = "";
    123 
    124                 // Precompute maps from name to ResourceItem such that when we find
    125                 // a public item's name we can quickly locate it. Without this,
    126                 // it's a linear search for each item, n times -- O(n^2).
    127                 // Precomputing a map is O(n) and looking up n times in the map is
    128                 // also O(n).
    129                 Map<ResourceType, Map<String, ResourceItem>> nameMap =
    130                         new HashMap<ResourceType, Map<String, ResourceItem>>();
    131                 for (Entry<ResourceType, List<ResourceItem>> entry: mResourceMap.entrySet()) {
    132                     ResourceType type = entry.getKey();
    133                     if (type == ResourceType.PUBLIC || type == ResourceType.DECLARE_STYLEABLE) {
    134                         // These are large maps (in android-15 for example the "public"
    135                         // ResourceType has 1734 items and declare-styleable has 210) that
    136                         // currently have no public exported names. Therefore, don't bother
    137                         // creating name lookup maps for these. (However, if by chance a future
    138                         // public.xml file does specify these, it will be found by the sequential
    139                         // search if map=null below.)
    140                         continue;
    141                     }
    142                     List<ResourceItem> items = entry.getValue();
    143                     int size = items.size();
    144                     Map<String, ResourceItem> map = new HashMap<String, ResourceItem>(size);
    145                     for (ResourceItem item : items) {
    146                         map.put(item.getName(), item);
    147                     }
    148                     nameMap.put(type, map);
    149                 }
    150 
    151                 while (true) {
    152                     int event = parser.next();
    153                     if (event == XmlPullParser.START_TAG) {
    154                         // As of API 15 there are a number of "java-symbol" entries here
    155                         if (!parser.getName().equals("public")) { //$NON-NLS-1$
    156                             continue;
    157                         }
    158 
    159                         String name = null;
    160                         String typeName = null;
    161                         for (int i = 0, n = parser.getAttributeCount(); i < n; i++) {
    162                             String attribute = parser.getAttributeName(i);
    163 
    164                             if (attribute.equals("name")) { //$NON-NLS-1$
    165                                 name = parser.getAttributeValue(i);
    166                                 if (typeName != null) {
    167                                     // Skip id attribute processing
    168                                     break;
    169                                 }
    170                             } else if (attribute.equals("type")) { //$NON-NLS-1$
    171                                 typeName = parser.getAttributeValue(i);
    172                             }
    173                         }
    174 
    175                         if (name != null && typeName != null) {
    176                             ResourceType type = null;
    177                             if (typeName.equals(lastTypeName)) {
    178                                 type = lastType;
    179                             } else {
    180                                 type = ResourceType.getEnum(typeName);
    181                                 lastType = type;
    182                                 lastTypeName = typeName;
    183                             }
    184                             if (type != null) {
    185                                 ResourceItem match = null;
    186                                 Map<String, ResourceItem> map = nameMap.get(type);
    187                                 if (map != null) {
    188                                     match = map.get(name);
    189                                 } else {
    190                                     // We skipped computing name maps for some large lists
    191                                     // that currently don't have any public names, but
    192                                     // on the off chance that they will show up, leave the
    193                                     // old iteration based lookup here
    194                                     List<ResourceItem> typeList = mResourceMap.get(type);
    195                                     if (typeList != null) {
    196                                         for (ResourceItem item : typeList) {
    197                                             if (name.equals(item.getName())) {
    198                                                 match = item;
    199                                                 break;
    200                                             }
    201                                         }
    202                                     }
    203                                 }
    204 
    205                                 if (match != null) {
    206                                     List<ResourceItem> publicList = mPublicResourceMap.get(type);
    207                                     if (publicList == null) {
    208                                         // Pick initial size for the list to hold the public
    209                                         // resources. We could just use map.size() here,
    210                                         // but they're usually much bigger; for example,
    211                                         // in one platform version, there are 1500 drawables
    212                                         // and 1200 strings but only 175 and 25 public ones
    213                                         // respectively.
    214                                         int size;
    215                                         switch (type) {
    216                                             case STYLE: size = 500; break;
    217                                             case ATTR: size = 1000; break;
    218                                             case DRAWABLE: size = 200; break;
    219                                             case ID: size = 50; break;
    220                                             case LAYOUT:
    221                                             case COLOR:
    222                                             case STRING:
    223                                             case ANIM:
    224                                             case INTERPOLATOR:
    225                                                 size = 30;
    226                                                 break;
    227                                             default:
    228                                                 size = 10;
    229                                                 break;
    230                                         }
    231                                         publicList = new ArrayList<ResourceItem>(size);
    232                                         mPublicResourceMap.put(type, publicList);
    233                                     }
    234 
    235                                     publicList.add(match);
    236                                 } else {
    237                                     // log that there's a public resource that doesn't actually
    238                                     // exist?
    239                                 }
    240                             } else {
    241                                 // log that there was a reference to a typo that doesn't actually
    242                                 // exist?
    243                             }
    244                         }
    245                     } else if (event == XmlPullParser.END_DOCUMENT) {
    246                         break;
    247                     }
    248                 }
    249             } catch (Exception e) {
    250                 if (logger != null) {
    251                     logger.error(e, "Can't read and parse public attribute list");
    252                 }
    253             } finally {
    254                 if (reader != null) {
    255                     try {
    256                         reader.close();
    257                     } catch (IOException e) {
    258                         // Nothing to be done here - we don't care if it closed or not.
    259                     }
    260                 }
    261             }
    262         }
    263 
    264         // put unmodifiable list for all res type in the public resource map
    265         // this will simplify access
    266         for (ResourceType type : ResourceType.values()) {
    267             List<ResourceItem> list = mPublicResourceMap.get(type);
    268             if (list == null) {
    269                 list = Collections.emptyList();
    270             } else {
    271                 list = Collections.unmodifiableList(list);
    272             }
    273 
    274             // put the new list in the map
    275             mPublicResourceMap.put(type, list);
    276         }
    277     }
    278 }
    279