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