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 20 import com.android.SdkConstants; 21 import com.android.annotations.NonNull; 22 import com.android.annotations.Nullable; 23 import com.android.io.IAbstractFile; 24 import com.android.io.IAbstractFolder; 25 import com.android.resources.ResourceType; 26 import com.android.utils.ILogger; 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.List; 40 import java.util.Map; 41 42 /** 43 * Framework resources repository. 44 * 45 * This behaves the same as {@link ResourceRepository} except that it differentiates between 46 * resources that are public and non public. 47 * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return 48 * public resources. This is typically used to display resource lists in the UI. 49 * 50 * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)} 51 * returns all resources, even the non public ones so that this can be used for rendering. 52 */ 53 public class FrameworkResources extends ResourceRepository { 54 55 /** 56 * Map of {@link ResourceType} to list of items. It is guaranteed to contain a list for all 57 * possible values of ResourceType. 58 */ 59 protected final Map<ResourceType, List<ResourceItem>> mPublicResourceMap = 60 new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class); 61 FrameworkResources()62 public FrameworkResources() { 63 super(true /*isFrameworkRepository*/); 64 } 65 66 /** 67 * Returns a {@link Collection} (always non null, but can be empty) of <b>public</b> 68 * {@link ResourceItem} matching a given {@link ResourceType}. 69 * 70 * @param type the type of the resources to return 71 * @return a collection of items, possible empty. 72 */ 73 @Override 74 @NonNull getResourceItemsOfType(@onNull ResourceType type)75 public List<ResourceItem> getResourceItemsOfType(@NonNull ResourceType type) { 76 return mPublicResourceMap.get(type); 77 } 78 79 /** 80 * Returns whether the repository has <b>public</b> resources of a given {@link ResourceType}. 81 * @param type the type of resource to check. 82 * @return true if the repository contains resources of the given type, false otherwise. 83 */ 84 @Override hasResourcesOfType(@onNull ResourceType type)85 public boolean hasResourcesOfType(@NonNull ResourceType type) { 86 return mPublicResourceMap.get(type).size() > 0; 87 } 88 89 @Override 90 @NonNull createResourceItem(@onNull String name)91 protected ResourceItem createResourceItem(@NonNull 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 */ loadPublicResources(@onNull IAbstractFolder resFolder, @Nullable ILogger logger)105 public void loadPublicResources(@NonNull IAbstractFolder resFolder, @Nullable ILogger logger) { 106 IAbstractFolder valueFolder = resFolder.getFolder(SdkConstants.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 while (true) { 124 int event = parser.next(); 125 if (event == XmlPullParser.START_TAG) { 126 // As of API 15 there are a number of "java-symbol" entries here 127 if (!parser.getName().equals("public")) { //$NON-NLS-1$ 128 continue; 129 } 130 131 String name = null; 132 String typeName = null; 133 for (int i = 0, n = parser.getAttributeCount(); i < n; i++) { 134 String attribute = parser.getAttributeName(i); 135 136 if (attribute.equals("name")) { //$NON-NLS-1$ 137 name = parser.getAttributeValue(i); 138 if (typeName != null) { 139 // Skip id attribute processing 140 break; 141 } 142 } else if (attribute.equals("type")) { //$NON-NLS-1$ 143 typeName = parser.getAttributeValue(i); 144 } 145 } 146 147 if (name != null && typeName != null) { 148 ResourceType type = null; 149 if (typeName.equals(lastTypeName)) { 150 type = lastType; 151 } else { 152 type = ResourceType.getEnum(typeName); 153 lastType = type; 154 lastTypeName = typeName; 155 } 156 if (type != null) { 157 ResourceItem match = null; 158 Map<String, ResourceItem> map = mResourceMap.get(type); 159 if (map != null) { 160 match = map.get(name); 161 } 162 163 if (match != null) { 164 List<ResourceItem> publicList = mPublicResourceMap.get(type); 165 if (publicList == null) { 166 // Pick initial size for the list to hold the public 167 // resources. We could just use map.size() here, 168 // but they're usually much bigger; for example, 169 // in one platform version, there are 1500 drawables 170 // and 1200 strings but only 175 and 25 public ones 171 // respectively. 172 int size; 173 switch (type) { 174 case STYLE: size = 500; break; 175 case ATTR: size = 1000; break; 176 case DRAWABLE: size = 200; break; 177 case ID: size = 50; break; 178 case LAYOUT: 179 case COLOR: 180 case STRING: 181 case ANIM: 182 case INTERPOLATOR: 183 size = 30; 184 break; 185 default: 186 size = 10; 187 break; 188 } 189 publicList = new ArrayList<ResourceItem>(size); 190 mPublicResourceMap.put(type, publicList); 191 } 192 193 publicList.add(match); 194 } else { 195 // log that there's a public resource that doesn't actually 196 // exist? 197 } 198 } else { 199 // log that there was a reference to a typo that doesn't actually 200 // exist? 201 } 202 } 203 } else if (event == XmlPullParser.END_DOCUMENT) { 204 break; 205 } 206 } 207 } catch (Exception e) { 208 if (logger != null) { 209 logger.error(e, "Can't read and parse public attribute list"); 210 } 211 } finally { 212 if (reader != null) { 213 try { 214 reader.close(); 215 } catch (IOException e) { 216 // Nothing to be done here - we don't care if it closed or not. 217 } 218 } 219 } 220 } 221 222 // put unmodifiable list for all res type in the public resource map 223 // this will simplify access 224 for (ResourceType type : ResourceType.values()) { 225 List<ResourceItem> list = mPublicResourceMap.get(type); 226 if (list == null) { 227 list = Collections.emptyList(); 228 } else { 229 list = Collections.unmodifiableList(list); 230 } 231 232 // put the new list in the map 233 mPublicResourceMap.put(type, list); 234 } 235 } 236 } 237 238