aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/XmlStringFileHelper.java
blob: 01e814ef23a118f78fdbe9005e9c1cb580c00c4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.refactorings.extractstring;

import com.android.SdkConstants;
import com.android.ide.eclipse.adt.AdtPlugin;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * An helper utility to get IDs out of an Android XML resource file.
 */
@SuppressWarnings("restriction")
class XmlStringFileHelper {

    /** A temporary cache of R.string IDs defined by a given xml file. The key is the
     * project path of the file, the data is a set of known string Ids for that file.
     *
     * Map type: map [String filename] => map [String id => String value].
     */
    private HashMap<String, Map<String, String>> mResIdCache =
        new HashMap<String, Map<String, String>>();

    public XmlStringFileHelper() {
    }

    /**
     * Utility method used by the wizard to retrieve the actual value definition of a given
     * string ID.
     *
     * @param project The project contain the XML file.
     * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml".
     *          The given file may or may not exist.
     * @param stringId The string ID to find.
     * @return The value string if the ID is defined, null otherwise.
     */
    public String valueOfStringId(IProject project, String xmlFileWsPath, String stringId) {
        Map<String, String> cache = getResIdsForFile(project, xmlFileWsPath);
        return cache.get(stringId);
    }

    /**
     * Utility method that retrieves all the *string* IDs defined in the given Android resource
     * file. The instance maintains an internal cache so a given file is retrieved only once.
     * Callers should consider the set to be read-only.
     *
     * @param project The project contain the XML file.
     * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml".
     *          The given file may or may not exist.
     * @return The map of string IDs => values defined in the given file. Cached. Never null.
     */
    public Map<String, String> getResIdsForFile(IProject project, String xmlFileWsPath) {
        Map<String, String> cache = mResIdCache.get(xmlFileWsPath);
        if (cache == null) {
            cache = internalGetResIdsForFile(project, xmlFileWsPath);
            mResIdCache.put(xmlFileWsPath, cache);
        }
        return cache;
    }

    /**
     * Extract all the defined string IDs from a given file using XPath.
     * @param project The project contain the XML file.
     * @param xmlFileWsPath The project path of the file to parse. It may not exist.
     * @return The map of all string IDs => values defined in the file.
     *   The returned set is always non null. It is empty if the file does not exist.
     */
    private Map<String, String> internalGetResIdsForFile(IProject project, String xmlFileWsPath) {

        TreeMap<String, String> ids = new TreeMap<String, String>();

        // Access the project that contains the resource that contains the compilation unit
        IResource resource = project.getFile(xmlFileWsPath);

        if (resource != null && resource.exists() && resource.getType() == IResource.FILE) {
            IStructuredModel smodel = null;

            try {
                IFile file = (IFile) resource;
                IModelManager modelMan = StructuredModelManager.getModelManager();
                smodel = modelMan.getExistingModelForRead(file);
                if (smodel == null) {
                    smodel = modelMan.getModelForRead(file);
                }

                if (smodel instanceof IDOMModel) {
                    IDOMDocument doc = ((IDOMModel) smodel).getDocument();

                    // We want all the IDs in an XML structure like this:
                    // <resources>
                    //    <string name="ID">something</string>
                    // </resources>

                    Node root = findChild(doc, null, SdkConstants.TAG_RESOURCES);
                    if (root != null) {
                        for (Node strNode = findChild(root, null,
                                                      SdkConstants.TAG_STRING);
                             strNode != null;
                             strNode = findChild(null, strNode,
                                                 SdkConstants.TAG_STRING)) {
                            NamedNodeMap attrs = strNode.getAttributes();
                            Node nameAttr = attrs.getNamedItem(SdkConstants.ATTR_NAME);
                            if (nameAttr != null) {
                                String id = nameAttr.getNodeValue();

                                // Find the TEXT node right after the element.
                                // Whitespace matters so we don't try to normalize it.
                                String text = "";                       //$NON-NLS-1$
                                for (Node txtNode = strNode.getFirstChild();
                                        txtNode != null && txtNode.getNodeType() == Node.TEXT_NODE;
                                        txtNode = txtNode.getNextSibling()) {
                                    text += txtNode.getNodeValue();
                                }

                                ids.put(id, text);
                            }
                        }
                    }
                }

            } catch (Throwable e) {
                AdtPlugin.log(e, "GetResIds failed in %1$s", xmlFileWsPath); //$NON-NLS-1$
            } finally {
                if (smodel != null) {
                    smodel.releaseFromRead();
                }
            }
        }

        return ids;
    }

    /**
     * Utility method that finds the next node of the requested element name.
     *
     * @param parent The parent node. If not null, will to start searching its children.
     *               Set to null when iterating through children.
     * @param lastChild The last child returned. Use null when visiting a parent the first time.
     * @param elementName The element name of the node to find.
     * @return The next children or sibling nide with the requested element name or null.
     */
    private Node findChild(Node parent, Node lastChild, String elementName) {
        if (lastChild == null && parent != null) {
            lastChild = parent.getFirstChild();
        } else if (lastChild != null) {
            lastChild = lastChild.getNextSibling();
        }

        for ( ; lastChild != null ; lastChild = lastChild.getNextSibling()) {
            if (lastChild.getNodeType() == Node.ELEMENT_NODE &&
                    lastChild.getNamespaceURI() == null &&  // resources don't have any NS URI
                    elementName.equals(lastChild.getLocalName())) {
                return lastChild;
            }
        }

        return null;
    }

}