1 /* 2 * Copyright (C) 2007 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 com.android.ide.common.rendering.api.ResourceValue; 20 import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository; 21 import com.android.io.IAbstractFile; 22 import com.android.io.StreamException; 23 import com.android.resources.ResourceType; 24 25 import org.xml.sax.SAXException; 26 27 import java.io.IOException; 28 import java.util.Collection; 29 import java.util.Collections; 30 import java.util.EnumMap; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 import javax.xml.parsers.ParserConfigurationException; 35 import javax.xml.parsers.SAXParser; 36 import javax.xml.parsers.SAXParserFactory; 37 38 /** 39 * Represents a resource file able to declare multiple resources, which could be of 40 * different {@link ResourceType}. 41 * <p/> 42 * This is typically an XML file inside res/values. 43 */ 44 public final class MultiResourceFile extends ResourceFile implements IValueResourceRepository { 45 46 private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); 47 48 private final Map<ResourceType, Map<String, ResourceValue>> mResourceItems = 49 new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class); 50 51 private Collection<ResourceType> mResourceTypeList = null; 52 MultiResourceFile(IAbstractFile file, ResourceFolder folder)53 public MultiResourceFile(IAbstractFile file, ResourceFolder folder) { 54 super(file, folder); 55 } 56 57 // Boolean flag to track whether a named element has been added or removed, thus requiring 58 // a new ID table to be generated 59 private boolean mNeedIdRefresh; 60 61 @Override load(ScanningContext context)62 protected void load(ScanningContext context) { 63 // need to parse the file and find the content. 64 parseFile(); 65 66 // create new ResourceItems for the new content. 67 mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); 68 69 // We need an ID generation step 70 mNeedIdRefresh = true; 71 72 // create/update the resource items. 73 updateResourceItems(context); 74 } 75 76 @Override update(ScanningContext context)77 protected void update(ScanningContext context) { 78 // Reset the ID generation flag 79 mNeedIdRefresh = false; 80 81 // Copy the previous version of our list of ResourceItems and types 82 Map<ResourceType, Map<String, ResourceValue>> oldResourceItems 83 = new EnumMap<ResourceType, Map<String, ResourceValue>>(mResourceItems); 84 85 // reset current content. 86 mResourceItems.clear(); 87 88 // need to parse the file and find the content. 89 parseFile(); 90 91 // create new ResourceItems for the new content. 92 mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); 93 94 // Check to see if any names have changed. If so, mark the flag so updateResourceItems 95 // can notify the ResourceRepository that an ID refresh is needed 96 if (oldResourceItems.keySet().equals(mResourceItems.keySet())) { 97 for (ResourceType type : mResourceTypeList) { 98 // We just need to check the names of the items. 99 // If there are new or removed names then we'll have to regenerate IDs 100 if (mResourceItems.get(type).keySet() 101 .equals(oldResourceItems.get(type).keySet()) == false) { 102 mNeedIdRefresh = true; 103 } 104 } 105 } else { 106 // If our type list is different, obviously the names will be different 107 mNeedIdRefresh = true; 108 } 109 // create/update the resource items. 110 updateResourceItems(context); 111 } 112 113 @Override dispose(ScanningContext context)114 protected void dispose(ScanningContext context) { 115 ResourceRepository repository = getRepository(); 116 117 // only remove this file from all existing ResourceItem. 118 repository.removeFile(mResourceTypeList, this); 119 120 // We'll need an ID refresh because we deleted items 121 context.requestFullAapt(); 122 123 // don't need to touch the content, it'll get reclaimed as this objects disappear. 124 // In the mean time other objects may need to access it. 125 } 126 127 @Override getResourceTypes()128 public Collection<ResourceType> getResourceTypes() { 129 return mResourceTypeList; 130 } 131 132 @Override hasResources(ResourceType type)133 public boolean hasResources(ResourceType type) { 134 Map<String, ResourceValue> list = mResourceItems.get(type); 135 return (list != null && list.size() > 0); 136 } 137 updateResourceItems(ScanningContext context)138 private void updateResourceItems(ScanningContext context) { 139 ResourceRepository repository = getRepository(); 140 141 // remove this file from all existing ResourceItem. 142 repository.removeFile(mResourceTypeList, this); 143 144 for (ResourceType type : mResourceTypeList) { 145 Map<String, ResourceValue> list = mResourceItems.get(type); 146 147 if (list != null) { 148 Collection<ResourceValue> values = list.values(); 149 for (ResourceValue res : values) { 150 ResourceItem item = repository.getResourceItem(type, res.getName()); 151 152 // add this file to the list of files generating this resource item. 153 item.add(this); 154 } 155 } 156 } 157 158 // If we need an ID refresh, ask the repository for that now 159 if (mNeedIdRefresh) { 160 context.requestFullAapt(); 161 } 162 } 163 164 /** 165 * Parses the file and creates a list of {@link ResourceType}. 166 */ parseFile()167 private void parseFile() { 168 try { 169 SAXParser parser = sParserFactory.newSAXParser(); 170 parser.parse(getFile().getContents(), new ValueResourceParser(this, isFramework())); 171 } catch (ParserConfigurationException e) { 172 } catch (SAXException e) { 173 } catch (IOException e) { 174 } catch (StreamException e) { 175 } 176 } 177 178 /** 179 * Adds a resource item to the list 180 * @param value The value of the resource. 181 */ 182 @Override addResourceValue(ResourceValue value)183 public void addResourceValue(ResourceValue value) { 184 ResourceType resType = value.getResourceType(); 185 186 Map<String, ResourceValue> list = mResourceItems.get(resType); 187 188 // if the list does not exist, create it. 189 if (list == null) { 190 list = new HashMap<String, ResourceValue>(); 191 mResourceItems.put(resType, list); 192 } else { 193 // look for a possible value already existing. 194 ResourceValue oldValue = list.get(value.getName()); 195 196 if (oldValue != null) { 197 oldValue.replaceWith(value); 198 return; 199 } 200 } 201 202 // empty list or no match found? add the given resource 203 list.put(value.getName(), value); 204 } 205 206 @Override hasResourceValue(ResourceType type, String name)207 public boolean hasResourceValue(ResourceType type, String name) { 208 Map<String, ResourceValue> map = mResourceItems.get(type); 209 return map != null && map.containsKey(name); 210 } 211 212 @Override getValue(ResourceType type, String name)213 public ResourceValue getValue(ResourceType type, String name) { 214 // get the list for the given type 215 Map<String, ResourceValue> list = mResourceItems.get(type); 216 217 if (list != null) { 218 return list.get(name); 219 } 220 221 return null; 222 } 223 } 224