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