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