| // Copyright 2010-2014, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package org.mozc.android.inputmethod.japanese.view; |
| |
| import org.mozc.android.inputmethod.japanese.resources.R; |
| import org.mozc.android.inputmethod.japanese.util.ParserUtil; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Maps; |
| |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.content.res.XmlResourceParser; |
| import android.graphics.drawable.Drawable; |
| import android.util.AttributeSet; |
| import android.util.Xml; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Map; |
| |
| /** |
| * Parses .xml file for skin and generates {@code Skin} instance. |
| */ |
| public class SkinParser { |
| |
| static class SkinParserException extends Exception { |
| |
| public SkinParserException(XmlPullParser parser, Throwable throwable) { |
| super(composeMessage(parser, throwable.getLocalizedMessage()), throwable); |
| } |
| |
| public SkinParserException(XmlPullParser parser, String detailMessage) { |
| super(composeMessage(parser, detailMessage)); |
| } |
| |
| private static String composeMessage(XmlPullParser parser, String message) { |
| return new StringBuffer(parser.getPositionDescription()) |
| .append(':').append(message).toString(); |
| } |
| } |
| |
| private final XmlResourceParser parser; |
| private final Resources resources; |
| |
| private static final int[] COLOR_ATTRIBUTES = { |
| android.R.attr.name, |
| android.R.attr.color, |
| }; |
| static { |
| Arrays.sort(COLOR_ATTRIBUTES); |
| } |
| private static final int COLOR_KEY_NAME_INDEX = |
| Arrays.binarySearch(COLOR_ATTRIBUTES, android.R.attr.name); |
| private static final int COLOR_KEY_COLOR_INDEX = |
| Arrays.binarySearch(COLOR_ATTRIBUTES, android.R.attr.color); |
| |
| private static final int[] DRAWABLE_ATTRIBUTES = { |
| android.R.attr.name, |
| }; |
| private static final int DRAWABLE_KEY_NAME_INDEX = |
| Arrays.binarySearch(DRAWABLE_ATTRIBUTES, android.R.attr.name); |
| |
| private static final int[] DIMENSION_ATTRIBUTES = { |
| android.R.attr.name, |
| R.attr.dimension, |
| }; |
| static { |
| Arrays.sort(DIMENSION_ATTRIBUTES); |
| } |
| private static final int DIMENSION_KEY_NAME_INDEX = |
| Arrays.binarySearch(DIMENSION_ATTRIBUTES, android.R.attr.name); |
| private static final int DIMENSION_KEY_DIMENSION_INDEX = |
| Arrays.binarySearch(DIMENSION_ATTRIBUTES, R.attr.dimension); |
| |
| public SkinParser(Resources resources, XmlResourceParser parser) { |
| this.resources = Preconditions.checkNotNull(resources); |
| this.parser = Preconditions.checkNotNull(parser); |
| } |
| |
| private static final Map<String, Field> fieldMap; |
| static { |
| Map<String, Field> tempMap = Maps.newHashMapWithExpectedSize(Skin.class.getFields().length); |
| for (Field field : Skin.class.getFields()) { |
| tempMap.put(field.getName(), field); |
| } |
| fieldMap = Collections.unmodifiableMap(tempMap); |
| } |
| |
| public Skin parseSkin() throws SkinParserException { |
| XmlResourceParser parser = this.parser; |
| Skin skin = new Skin(); |
| AttributeSet attributeSet = Xml.asAttributeSet(parser); |
| |
| try { |
| // Initial two events should be START_DOCUMENT and then START_TAG. |
| parser.next(); |
| ParserUtil.assertStartDocument(parser); |
| parser.nextTag(); |
| if (!"Skin".equals(parser.getName())) { |
| throw new SkinParserException(parser, |
| "<Skin> element is expected but met <" + parser.getName() + ">"); |
| } |
| |
| while (parser.nextTag() == XmlResourceParser.START_TAG) { |
| String tagName = parser.getName(); |
| if ("Color".equals(tagName)) { |
| TypedArray attributes = resources.obtainAttributes(parser, COLOR_ATTRIBUTES); |
| try { |
| String name = attributes.getString(COLOR_KEY_NAME_INDEX); |
| if (name == null) { |
| throw new SkinParserException(parser, |
| "<Color> element's \"name\" attribute is mandatory."); |
| } |
| int color = attributes.getColor(COLOR_KEY_COLOR_INDEX, 0); |
| Field field = fieldMap.get(name); |
| if (field == null) { |
| throw new SkinParserException(parser, name + " is undefined field."); |
| } |
| field.setInt(skin, color); |
| if (parser.nextTag() != XmlResourceParser.END_TAG) { |
| throw new SkinParserException(parser, "</Color> is expected but not found."); |
| } |
| } finally { |
| attributes.recycle(); |
| } |
| continue; |
| } |
| if ("Drawable".equals(tagName)) { |
| TypedArray attributes = resources.obtainAttributes(parser, DRAWABLE_ATTRIBUTES); |
| try { |
| String name = attributes.getString(DRAWABLE_KEY_NAME_INDEX); |
| if (name == null) { |
| throw new SkinParserException(parser, |
| "<Drawable> element's \"name\" attribute is mandatory."); |
| } |
| // Go forward to inner drawable tag. |
| if (parser.nextTag() != XmlResourceParser.START_TAG) { |
| throw new SkinParserException(parser, "Start tag for drawable is expected."); |
| } |
| Drawable drawable = Drawable.createFromXmlInner(resources, parser, attributeSet); |
| if (drawable == null) { |
| throw new SkinParserException(parser, "Invalid drawable."); |
| } |
| if (parser.nextTag() != XmlResourceParser.END_TAG) { |
| throw new SkinParserException(parser, "End tag for drawable is expected."); |
| } |
| Field field = fieldMap.get(name); |
| if (field == null) { |
| throw new SkinParserException(parser, name + " is undefined field."); |
| } |
| field.set(skin, drawable); |
| if (parser.nextTag() != XmlResourceParser.END_TAG) { // Skip end tag. |
| throw new SkinParserException(parser, "</Drawable> is expected but not found."); |
| } |
| } finally { |
| attributes.recycle(); |
| } |
| continue; |
| } |
| if ("Dimension".equals(tagName)) { |
| TypedArray attributes = resources.obtainAttributes(parser, DIMENSION_ATTRIBUTES); |
| try { |
| String name = attributes.getString(DIMENSION_KEY_NAME_INDEX); |
| if (name == null) { |
| throw new SkinParserException(parser, |
| "<Dimension> element's \"name\" attribute is mandatory."); |
| } |
| float dimension = attributes.getDimension(DIMENSION_KEY_DIMENSION_INDEX, 0); |
| Field field = fieldMap.get(name); |
| if (field == null) { |
| throw new SkinParserException(parser, name + " is undefined field."); |
| } |
| field.setFloat(skin, dimension); |
| if (parser.nextTag() != XmlResourceParser.END_TAG) { |
| throw new SkinParserException(parser, "</Dimension> is expected but not found."); |
| } |
| } finally { |
| attributes.recycle(); |
| } |
| continue; |
| } |
| throw new SkinParserException(parser, "Unexpected <" + tagName + "> is found."); |
| } |
| parser.next(); |
| ParserUtil.assertEndDocument(parser); |
| } catch (IllegalAccessException e) { |
| throw new SkinParserException(parser, e); |
| } catch (IllegalArgumentException e) { |
| throw new SkinParserException(parser, e); |
| } catch (XmlPullParserException e) { |
| throw new SkinParserException(parser, e); |
| } catch (IOException e) { |
| throw new SkinParserException(parser, e); |
| } |
| return skin; |
| } |
| } |