16224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala/*
26224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Copyright 2012 AndroidPlot.com
36224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *
46224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    Licensed under the Apache License, Version 2.0 (the "License");
56224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    you may not use this file except in compliance with the License.
66224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    You may obtain a copy of the License at
76224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *
86224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *        http://www.apache.org/licenses/LICENSE-2.0
96224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *
106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    Unless required by applicable law or agreed to in writing, software
116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    distributed under the License is distributed on an "AS IS" BASIS,
126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    See the License for the specific language governing permissions and
146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala *    limitations under the License.
156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala */
166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalapackage com.androidplot.util;
186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.content.Context;
206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.content.res.XmlResourceParser;
216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.graphics.Color;
226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.util.Log;
236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport android.util.TypedValue;
246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport org.xmlpull.v1.XmlPullParserException;
256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.io.IOException;
276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.lang.reflect.InvocationTargetException;
286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.lang.reflect.Method;
296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.lang.reflect.Type;
306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalaimport java.util.HashMap;
316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala/**
336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Utility class for "configuring" objects via XML config files.  Supports the following field types:
346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * String
356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Enum
366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * int
376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * float
386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * boolean
396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Config files should be stored in /res/xml.  Given the XML configuration /res/xml/myConfig.xml, one can apply the
416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * configuration to an Object instance as follows:
426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * MyObject obj = new MyObject();
446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Configurator.configure(obj, R.xml.myConfig);
456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * WHAT IT DOES:
476224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Given a series of parameters stored in an XML file, Configurator iterates through each parameter, using the name
486224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * as a map to the field within a given object.  For example:
496224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
506224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <pre>
516224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * {@code
526224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <config car.engine.sparkPlug.condition="poor"/>
536224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * }
546224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * </pre>
556224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
566224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Given a Car instance car and assuming the method setCondition(String) exists within the SparkPlug class,
576224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Configurator does the following:
586224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
596224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <pre>
606224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * {@code
616224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * car.getEngine().getSparkPlug().setCondition("poor");
626224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * }
636224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * </pre>
646224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
656224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Now let's pretend that setCondition takes an instance of the Condition enum as it's argument.
666224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Configurator then does the following:
676224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
686224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * car.getEngine().getSparkPlug().setCondition(Condition.valueOf("poor");
696224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
706224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Now let's look at how ints are handled.  Given the following xml:
716224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
726224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <config car.engine.miles="100000"/>
736224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
746224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * would result in:
756224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * car.getEngine.setMiles(Integer.ParseInt("100000");
766224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
776224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * That's pretty straight forward.  But colors are expressed as ints too in Android
786224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * but can be defined using hex values or even names of colors.  When Configurator
796224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * attempts to parse a parameter for a method that it knows takes an int as it's argument,
806224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Configurator will first attempt to parse the parameter as a color.  Only after this
816224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * attempt fails will Configurator resort to Integer.ParseInt.  So:
826224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
836224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <config car.hood.paint.color="Red"/>
846224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
856224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * would result in:
866224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * car.getHood().getPaint().setColor(Color.parseColor("Red");
876224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
886224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Next lets talk about float.  Floats can appear in XML a few different ways in Android,
896224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * especially when it comes to defining dimensions:
906224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
916224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <config height="10dp" depth="2mm" width="5em"/>
926224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
936224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Configurator will correctly parse each of these into their corresponding real pixel value expressed as a float.
946224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
956224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * One last thing to keep in mind when using Configurator:
966224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * Values for Strings and ints can be assigned to localized values, allowing
976224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * a cleaner solution for those developing apps to run on multiple form factors
986224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * or in multiple languages:
996224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <p/>
1006224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * <config thingy.description="@string/thingyDescription"
1016224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala * thingy.titlePaint.textSize=""/>
1026224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala */
1036224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala@SuppressWarnings("WeakerAccess")
1046224eda509d436a575f801942337da92a6c18767Eino-Ville Talvalapublic abstract class Configurator {
1056224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1066224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    private static final String TAG = Configurator.class.getName();
1076224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static final String CFG_ELEMENT_NAME = "config";
1086224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1096224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static int parseResId(Context ctx, String prefix, String value) {
1106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        String[] split = value.split("/");
1116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        // is this a localized resource?
1126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        if (split.length > 1 && split[0].equalsIgnoreCase(prefix)) {
1136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            String pack = split[0].replace("@", "");
1146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            String name = split[1];
1156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return ctx.getResources().getIdentifier(name, pack, ctx.getPackageName());
1166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } else {
1176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            throw new IllegalArgumentException();
1186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
1196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static int parseIntAttr(Context ctx, String value) {
1226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        try {
1236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return ctx.getResources().getColor(parseResId(ctx, "@color", value));
1246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } catch (IllegalArgumentException e1) {
1256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            try {
1266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                return Color.parseColor(value);
1276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } catch (IllegalArgumentException e2) {
1286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                // wasn't a color so try parsing as a plain old int:
1296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                return Integer.parseInt(value);
1306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
1316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
1326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    /**
1356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * Treats value as a float parameter.  First value is tested to see whether
1366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * it contains a resource identifier.  Failing that, it is tested to see whether
1376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * a dimension suffix (dp, em, mm etc.) exists.  Failing that, it is evaluated as
1386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * a plain old float.
1396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param ctx
1406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param value
1416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @return
1426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     */
1436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static float parseFloatAttr(Context ctx, String value) {
1446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        try {
1456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return ctx.getResources().getDimension(parseResId(ctx, "@dimen", value));
1466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } catch (IllegalArgumentException e1) {
1476224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            try {
1486224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                return PixelUtils.stringToDimension(value);
1496224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } catch (Exception e2) {
1506224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                return Float.parseFloat(value);
1516224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
1526224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
1536224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1546224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1556224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static String parseStringAttr(Context ctx, String value) {
1566224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        try {
1576224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return ctx.getResources().getString(parseResId(ctx, "@string", value));
1586224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } catch (IllegalArgumentException e1) {
1596224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return value;
1606224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
1616224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1626224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1636224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1646224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static Method getSetter(Class clazz, final String fieldId) throws NoSuchMethodException {
1656224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        Method[] methods = clazz.getMethods();
1666224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1676224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        String methodName = "set" + fieldId;
1686224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        for (Method method : methods) {
1696224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            if (method.getName().equalsIgnoreCase(methodName)) {
1706224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                return method;
1716224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
1726224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
1736224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        throw new NoSuchMethodException("No such public method (case insensitive): " +
1746224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                methodName + " in " + clazz);
1756224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1766224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1776224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    @SuppressWarnings("unchecked")
1786224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static Method getGetter(Class clazz, final String fieldId) throws NoSuchMethodException {
1796224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        Log.d(TAG, "Attempting to find getter for " + fieldId + " in class " + clazz.getName());
1806224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        String firstLetter = fieldId.substring(0, 1);
1816224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        String methodName = "get" + firstLetter.toUpperCase() + fieldId.substring(1, fieldId.length());
1826224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        return clazz.getMethod(methodName);
1836224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
1846224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
1856224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    /**
1866224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * Returns the object containing the field specified by path.
1876224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param obj
1886224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param path Path through member hierarchy to the destination field.
1896224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @return null if the object at path cannot be found.
1906224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @throws java.lang.reflect.InvocationTargetException
1916224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     *
1926224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @throws IllegalAccessException
1936224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     */
1946224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static Object getObjectContaining(Object obj, String path) throws
1956224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            InvocationTargetException, IllegalAccessException, NoSuchMethodException {
1966224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        if(obj == null) {
1976224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            throw new NullPointerException("Attempt to call getObjectContaining(Object obj, String path) " +
1986224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    "on a null Object instance.  Path was: " + path);
1996224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
2006224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        Log.d(TAG, "Looking up object containing: " + path);
2016224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        int separatorIndex = path.indexOf(".");
2026224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2036224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        // not there yet, descend deeper:
2046224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        if (separatorIndex > 0) {
2056224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            String lhs = path.substring(0, separatorIndex);
2066224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            String rhs = path.substring(separatorIndex + 1, path.length());
2076224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2086224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            // use getter to retrieve the instance
2096224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            Method m = getGetter(obj.getClass(), lhs);
2106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            if(m == null) {
2116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                throw new NullPointerException("No getter found for field: " + lhs + " within " + obj.getClass());
2126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
2136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            Log.d(TAG, "Invoking " + m.getName() + " on instance of " + obj.getClass().getName());
2146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            Object o = m.invoke(obj);
2156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            // delve into o
2166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return getObjectContaining(o, rhs);
2176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            //} catch (NoSuchMethodException e) {
2186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            // TODO: log a warning
2196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            //    return null;
2206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            //}
2216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } else {
2226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            // found it!
2236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            return obj;
2246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
2256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
2266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    @SuppressWarnings("unchecked")
2286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    private static Object[] inflateParams(Context ctx, Class[] params, String[] vals) throws NoSuchMethodException,
2296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            InvocationTargetException, IllegalAccessException {
2306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        Object[] out = new Object[params.length];
2316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        int i = 0;
2326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        for (Class param : params) {
2336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            if (Enum.class.isAssignableFrom(param)) {
2346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                out[i] = param.getMethod("valueOf", String.class).invoke(null, vals[i].toUpperCase());
2356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else if (param.equals(Float.TYPE)) {
2366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                out[i] = parseFloatAttr(ctx, vals[i]);
2376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else if (param.equals(Integer.TYPE)) {
2386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                out[i] = parseIntAttr(ctx, vals[i]);
2396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else if (param.equals(Boolean.TYPE)) {
2406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                out[i] = Boolean.valueOf(vals[i]);
2416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else if (param.equals(String.class)) {
2426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                out[i] = parseStringAttr(ctx, vals[i]);
2436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else {
2446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                throw new IllegalArgumentException(
2456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                        "Error inflating XML: Setter requires param of unsupported type: " + param);
2466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
2476224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            i++;
2486224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
2496224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        return out;
2506224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
2516224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2526224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    /**
2536224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     *
2546224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param ctx
2556224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param obj
2566224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param xmlFileId ID of the XML config file within /res/xml
2576224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     */
2586224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    public static void configure(Context ctx, Object obj, int xmlFileId) {
2596224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        XmlResourceParser xrp = ctx.getResources().getXml(xmlFileId);
2606224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        try {
2616224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            HashMap<String, String> params = new HashMap<String, String>();
2626224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            while (xrp.getEventType() != XmlResourceParser.END_DOCUMENT) {
2636224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                xrp.next();
2646224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                String name = xrp.getName();
2656224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                if (xrp.getEventType() == XmlResourceParser.START_TAG) {
2666224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    if (name.equalsIgnoreCase(CFG_ELEMENT_NAME))
2676224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                        for (int i = 0; i < xrp.getAttributeCount(); i++) {
2686224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                            params.put(xrp.getAttributeName(i), xrp.getAttributeValue(i));
2696224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                        }
2706224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    break;
2716224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                }
2726224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
2736224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            configure(ctx, obj, params);
2746224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } catch (XmlPullParserException e) {
2756224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            e.printStackTrace();
2766224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } catch (IOException e) {
2776224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            e.printStackTrace();
2786224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        } finally {
2796224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            xrp.close();
2806224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
2816224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
2826224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2836224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    public static void configure(Context ctx, Object obj, HashMap<String, String> params) {
2846224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        for (String key : params.keySet()) {
2856224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            try {
2866224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                configure(ctx, obj, key, params.get(key));
2876224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } catch (InvocationTargetException e) {
2886224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                e.printStackTrace();
2896224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } catch (IllegalAccessException e) {
2906224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                e.printStackTrace();
2916224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } catch (NoSuchMethodException e) {
2926224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                Log.w(TAG, "Error inflating XML: Setter for field \"" + key + "\" does not exist. ");
2936224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                e.printStackTrace();
2946224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
2956224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
2966224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
2976224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
2986224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    /**
2996224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * Recursively descend into an object using key as the pathway and invoking the corresponding setter
3006224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * if one exists.
3016224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     *
3026224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param key
3036224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     * @param value
3046224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala     */
3056224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static void configure(Context ctx, Object obj, String key, String value)
3066224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
3076224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        Object o = getObjectContaining(obj, key);
3086224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        if (o != null) {
3096224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            int idx = key.lastIndexOf(".");
3106224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            String fieldId = idx > 0 ? key.substring(idx + 1, key.length()) : key;
3116224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
3126224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            Method m = getSetter(o.getClass(), fieldId);
3136224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            Class[] paramTypes = m.getParameterTypes();
3146224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            // TODO: add support for generic type params
3156224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            if (paramTypes.length >= 1) {
3166224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
3176224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                // split on "|"
3186224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                // TODO: add support for String args containing a |
3196224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                String[] paramStrs = value.split("\\|");
3206224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                if (paramStrs.length == paramTypes.length) {
3216224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
3226224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    Object[] oa = inflateParams(ctx, paramTypes, paramStrs);
3236224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    Log.d(TAG, "Invoking " + m.getName() + " with arg(s) " + argArrToString(oa));
3246224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    m.invoke(o, oa);
3256224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                } else {
3266224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    throw new IllegalArgumentException("Error inflating XML: Unexpected number of argments passed to \""
3276224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                            + m.getName() + "\".  Expected: " + paramTypes.length + " Got: " + paramStrs.length);
3286224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                }
3296224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            } else {
3306224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                // Obvious this is not a setter
3316224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                throw new IllegalArgumentException("Error inflating XML: no setter method found for param \"" +
3326224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                        fieldId + "\".");
3336224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            }
3346224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
3356224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
3366224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
3376224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    protected static String argArrToString(Object[] args) {
3386224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        String out = "";
3396224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        for(Object obj : args) {
3406224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala            out += (obj == null ? (out += "[null] ") :
3416224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala                    ("[" + obj.getClass() + ": " + obj + "] "));
3426224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        }
3436224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala        return out;
3446224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala    }
3456224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala}
3466224eda509d436a575f801942337da92a6c18767Eino-Ville Talvala
347