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 XmlStringFileHelper()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 */ valueOfStringId(IProject project, String xmlFileWsPath, String stringId)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 */ getResIdsForFile(IProject project, String xmlFileWsPath)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 */ internalGetResIdsForFile(IProject project, String xmlFileWsPath)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 */ findChild(Node parent, Node lastChild, String elementName)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