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 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 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 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 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 128 public Collection<ResourceType> getResourceTypes() { 129 return mResourceTypeList; 130 } 131 132 @Override 133 public boolean hasResources(ResourceType type) { 134 Map<String, ResourceValue> list = mResourceItems.get(type); 135 return (list != null && list.size() > 0); 136 } 137 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 */ 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 public void addResourceValue(ResourceValue value) { 183 ResourceType resType = value.getResourceType(); 184 185 Map<String, ResourceValue> list = mResourceItems.get(resType); 186 187 // if the list does not exist, create it. 188 if (list == null) { 189 list = new HashMap<String, ResourceValue>(); 190 mResourceItems.put(resType, list); 191 } else { 192 // look for a possible value already existing. 193 ResourceValue oldValue = list.get(value.getName()); 194 195 if (oldValue != null) { 196 oldValue.replaceWith(value); 197 return; 198 } 199 } 200 201 // empty list or no match found? add the given resource 202 list.put(value.getName(), value); 203 } 204 205 @Override 206 public ResourceValue getValue(ResourceType type, String name) { 207 // get the list for the given type 208 Map<String, ResourceValue> list = mResourceItems.get(type); 209 210 if (list != null) { 211 return list.get(name); 212 } 213 214 return null; 215 } 216 } 217