Home | History | Annotate | Download | only in resources
      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