Home | History | Annotate | Download | only in extractstring
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
      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.eclipse.adt.internal.refactorings.extractstring;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 
     22 import org.eclipse.core.resources.IFile;
     23 import org.eclipse.core.resources.IProject;
     24 import org.eclipse.core.resources.IResource;
     25 import org.eclipse.wst.sse.core.StructuredModelManager;
     26 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
     27 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
     28 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
     29 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
     30 import org.w3c.dom.NamedNodeMap;
     31 import org.w3c.dom.Node;
     32 
     33 import java.util.HashMap;
     34 import java.util.Map;
     35 import java.util.TreeMap;
     36 
     37 /**
     38  * An helper utility to get IDs out of an Android XML resource file.
     39  */
     40 @SuppressWarnings("restriction")
     41 class XmlStringFileHelper {
     42 
     43     /** A temporary cache of R.string IDs defined by a given xml file. The key is the
     44      * project path of the file, the data is a set of known string Ids for that file.
     45      *
     46      * Map type: map [String filename] => map [String id => String value].
     47      */
     48     private HashMap<String, Map<String, String>> mResIdCache =
     49         new HashMap<String, Map<String, String>>();
     50 
     51     public XmlStringFileHelper() {
     52     }
     53 
     54     /**
     55      * Utility method used by the wizard to retrieve the actual value definition of a given
     56      * string ID.
     57      *
     58      * @param project The project contain the XML file.
     59      * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml".
     60      *          The given file may or may not exist.
     61      * @param stringId The string ID to find.
     62      * @return The value string if the ID is defined, null otherwise.
     63      */
     64     public String valueOfStringId(IProject project, String xmlFileWsPath, String stringId) {
     65         Map<String, String> cache = getResIdsForFile(project, xmlFileWsPath);
     66         return cache.get(stringId);
     67     }
     68 
     69     /**
     70      * Utility method that retrieves all the *string* IDs defined in the given Android resource
     71      * file. The instance maintains an internal cache so a given file is retrieved only once.
     72      * Callers should consider the set to be read-only.
     73      *
     74      * @param project The project contain the XML file.
     75      * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml".
     76      *          The given file may or may not exist.
     77      * @return The map of string IDs => values defined in the given file. Cached. Never null.
     78      */
     79     public Map<String, String> getResIdsForFile(IProject project, String xmlFileWsPath) {
     80         Map<String, String> cache = mResIdCache.get(xmlFileWsPath);
     81         if (cache == null) {
     82             cache = internalGetResIdsForFile(project, xmlFileWsPath);
     83             mResIdCache.put(xmlFileWsPath, cache);
     84         }
     85         return cache;
     86     }
     87 
     88     /**
     89      * Extract all the defined string IDs from a given file using XPath.
     90      * @param project The project contain the XML file.
     91      * @param xmlFileWsPath The project path of the file to parse. It may not exist.
     92      * @return The map of all string IDs => values defined in the file.
     93      *   The returned set is always non null. It is empty if the file does not exist.
     94      */
     95     private Map<String, String> internalGetResIdsForFile(IProject project, String xmlFileWsPath) {
     96 
     97         TreeMap<String, String> ids = new TreeMap<String, String>();
     98 
     99         // Access the project that contains the resource that contains the compilation unit
    100         IResource resource = project.getFile(xmlFileWsPath);
    101 
    102         if (resource != null && resource.exists() && resource.getType() == IResource.FILE) {
    103             IStructuredModel smodel = null;
    104 
    105             try {
    106                 IFile file = (IFile) resource;
    107                 IModelManager modelMan = StructuredModelManager.getModelManager();
    108                 smodel = modelMan.getExistingModelForRead(file);
    109                 if (smodel == null) {
    110                     smodel = modelMan.getModelForRead(file);
    111                 }
    112 
    113                 if (smodel instanceof IDOMModel) {
    114                     IDOMDocument doc = ((IDOMModel) smodel).getDocument();
    115 
    116                     // We want all the IDs in an XML structure like this:
    117                     // <resources>
    118                     //    <string name="ID">something</string>
    119                     // </resources>
    120 
    121                     Node root = findChild(doc, null, SdkConstants.TAG_RESOURCES);
    122                     if (root != null) {
    123                         for (Node strNode = findChild(root, null,
    124                                                       SdkConstants.TAG_STRING);
    125                              strNode != null;
    126                              strNode = findChild(null, strNode,
    127                                                  SdkConstants.TAG_STRING)) {
    128                             NamedNodeMap attrs = strNode.getAttributes();
    129                             Node nameAttr = attrs.getNamedItem(SdkConstants.ATTR_NAME);
    130                             if (nameAttr != null) {
    131                                 String id = nameAttr.getNodeValue();
    132 
    133                                 // Find the TEXT node right after the element.
    134                                 // Whitespace matters so we don't try to normalize it.
    135                                 String text = "";                       //$NON-NLS-1$
    136                                 for (Node txtNode = strNode.getFirstChild();
    137                                         txtNode != null && txtNode.getNodeType() == Node.TEXT_NODE;
    138                                         txtNode = txtNode.getNextSibling()) {
    139                                     text += txtNode.getNodeValue();
    140                                 }
    141 
    142                                 ids.put(id, text);
    143                             }
    144                         }
    145                     }
    146                 }
    147 
    148             } catch (Throwable e) {
    149                 AdtPlugin.log(e, "GetResIds failed in %1$s", xmlFileWsPath); //$NON-NLS-1$
    150             } finally {
    151                 if (smodel != null) {
    152                     smodel.releaseFromRead();
    153                 }
    154             }
    155         }
    156 
    157         return ids;
    158     }
    159 
    160     /**
    161      * Utility method that finds the next node of the requested element name.
    162      *
    163      * @param parent The parent node. If not null, will to start searching its children.
    164      *               Set to null when iterating through children.
    165      * @param lastChild The last child returned. Use null when visiting a parent the first time.
    166      * @param elementName The element name of the node to find.
    167      * @return The next children or sibling nide with the requested element name or null.
    168      */
    169     private Node findChild(Node parent, Node lastChild, String elementName) {
    170         if (lastChild == null && parent != null) {
    171             lastChild = parent.getFirstChild();
    172         } else if (lastChild != null) {
    173             lastChild = lastChild.getNextSibling();
    174         }
    175 
    176         for ( ; lastChild != null ; lastChild = lastChild.getNextSibling()) {
    177             if (lastChild.getNodeType() == Node.ELEMENT_NODE &&
    178                     lastChild.getNamespaceURI() == null &&  // resources don't have any NS URI
    179                     elementName.equals(lastChild.getLocalName())) {
    180                 return lastChild;
    181             }
    182         }
    183 
    184         return null;
    185     }
    186 
    187 }
    188