1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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
17package com.android.inputmethod.keyboard.tools;
18
19import org.xml.sax.Attributes;
20import org.xml.sax.SAXException;
21import org.xml.sax.SAXParseException;
22import org.xml.sax.ext.DefaultHandler2;
23
24import java.io.IOException;
25import java.io.InputStream;
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.HashMap;
29import java.util.List;
30import java.util.Locale;
31import java.util.Map;
32
33import javax.xml.parsers.ParserConfigurationException;
34import javax.xml.parsers.SAXParser;
35import javax.xml.parsers.SAXParserFactory;
36
37public class StringResourceMap {
38    // Locale of this string resource map.
39    public final Locale mLocale;
40    // String resource list.
41    private final List<StringResource> mResources;
42    // Name to string resource map.
43    private final Map<String, StringResource> mResourcesMap;
44
45    // The length of String[] that is created from this {@link StringResourceMap}. The length is
46    // calculated in {@link MoreKeysResources#dumpTexts(OutputStream)} and recorded by
47    // {@link #setOutputArraySize(int)}. The recorded length is used as a part of comment by
48    // {@link MoreKeysResources#dumpLocaleMap(OutputStream)} via {@link #getOutputArraySize()}.
49    private int mOutputArraySize;
50
51    public StringResourceMap(final String jarEntryName) {
52        mLocale = JarUtils.getLocaleFromEntryName(jarEntryName);
53        final StringResourceHandler handler = new StringResourceHandler();
54        final SAXParserFactory factory = SAXParserFactory.newInstance();
55        factory.setNamespaceAware(true);
56        final InputStream stream = JarUtils.openResource(jarEntryName);
57        try {
58            final SAXParser parser = factory.newSAXParser();
59            // In order to get comment tag.
60            parser.setProperty("http://xml.org/sax/properties/lexical-handler", handler);
61            parser.parse(stream, handler);
62        } catch (ParserConfigurationException e) {
63            throw new RuntimeException(e.getMessage(), e);
64        } catch (SAXParseException e) {
65            throw new RuntimeException(e.getMessage() + " at line " + e.getLineNumber()
66                    + ", column " + e.getColumnNumber(), e);
67        } catch (SAXException e) {
68            throw new RuntimeException(e.getMessage(), e);
69        } catch (IOException e) {
70            throw new RuntimeException(e.getMessage(), e);
71        } finally {
72            JarUtils.close(stream);
73        }
74
75        mResources = Collections.unmodifiableList(handler.mResources);
76        final HashMap<String, StringResource> map = new HashMap<>();
77        for (final StringResource res : mResources) {
78            map.put(res.mName, res);
79        }
80        mResourcesMap = map;
81    }
82
83    public List<StringResource> getResources() {
84        return mResources;
85    }
86
87    public boolean contains(final String name) {
88        return mResourcesMap.containsKey(name);
89    }
90
91    public StringResource get(final String name) {
92        return mResourcesMap.get(name);
93    }
94
95    public void setOutputArraySize(final int arraySize) {
96        mOutputArraySize = arraySize;
97    }
98
99    public int getOutputArraySize() {
100        return mOutputArraySize;
101    }
102
103    static class StringResourceHandler extends DefaultHandler2 {
104        private static final String TAG_RESOURCES = "resources";
105        private static final String TAG_STRING = "string";
106        private static final String ATTR_NAME = "name";
107
108        final ArrayList<StringResource> mResources = new ArrayList<>();
109
110        private String mName;
111        private final StringBuilder mValue = new StringBuilder();
112        private final StringBuilder mComment = new StringBuilder();
113
114        private void init() {
115            mName = null;
116            mComment.setLength(0);
117        }
118
119        @Override
120        public void comment(char[] ch, int start, int length) {
121            mComment.append(ch, start, length);
122            if (ch[start + length - 1] != '\n') {
123                mComment.append('\n');
124            }
125        }
126
127        @Override
128        public void startElement(String uri, String localName, String qName, Attributes attr) {
129            if (TAG_RESOURCES.equals(localName)) {
130                init();
131            } else if (TAG_STRING.equals(localName)) {
132                mName = attr.getValue(ATTR_NAME);
133                mValue.setLength(0);
134            }
135        }
136
137        @Override
138        public void characters(char[] ch, int start, int length) {
139            mValue.append(ch, start, length);
140        }
141
142        @Override
143        public void endElement(String uri, String localName, String qName) throws SAXException {
144            if (TAG_STRING.equals(localName)) {
145                if (mName == null)
146                    throw new SAXException(TAG_STRING + " doesn't have name");
147                final String comment = mComment.length() > 0 ? mComment.toString() : null;
148                String value = mValue.toString();
149                if (value.startsWith("\"") && value.endsWith("\"")) {
150                    // Trim surroundings double quote.
151                    value = value.substring(1, value.length() - 1);
152                }
153                mResources.add(new StringResource(mName, value, comment));
154                init();
155            }
156        }
157    }
158}
159