com.wit.and.dialog.internal.xml.XmlDialogInflater.java Source code

Java tutorial

Introduction

Here is the source code for com.wit.and.dialog.internal.xml.XmlDialogInflater.java

Source

/*
 * =================================================================================
 * Copyright (C) 2013 Martin Albedinsky [Wolf-ITechnologies]
 * =================================================================================
 * Licensed under the Apache License, Version 2.0 or later (further "License" only);
 * ---------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy
 * of this License you may obtain at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * You can redistribute, modify or publish any part of the code written in this
 * file but as it is described in the License, the software distributed under the 
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF
 * ANY KIND.
 * 
 * See the License for the specific language governing permissions and limitations
 * under the License.
 * =================================================================================
 */
package com.wit.and.dialog.internal.xml;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.support.v4.app.DialogFragment;
import android.util.AndroidRuntimeException;
import android.util.Log;

import com.wit.and.dialog.Dialog;
import com.wit.and.dialog.xml.XmlDialog;
import com.wit.and.dialog.xml.XmlDialogParser;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.HashMap;

/**
 * <h4>Class Overview</h4>
 * <p>
 * </p>
 * <h5>Supported dialog Root tags:</h5>
 * <h6>Dialog</h6>
 * <p>
 * Parsed to instance of {@link com.wit.and.dialog.Dialog}.
 * </p>
 * <h6>InfoDialog</h6>
 * <p>
 * Parsed to instance of {@link com.wit.and.dialog.Dialog} with default info icon
 * and single button.
 * </p>
 * <p>
 * Should contains only title and message text and can overrides text value of neutral
 * button ({@link com.wit.and.dialog.R.attr#dialogButtonNeutral}).
 * </p>
 * <h6>AlertDialog</h6>
 * <p>
 * Parsed to instance of {@link com.wit.and.dialog.Dialog} with default alert icon
 * and two buttons.
 * </p>
 * <p>
 * Should contains only title and message text and can overrides text value of negative
 * ({@link com.wit.and.dialog.R.attr#dialogButtonNegative}) and positive
 * ({@link com.wit.and.dialog.R.attr#dialogButtonPositive}) button.
 * </p>
 * <h6>ErrorDialog</h6>
 * <p>
 * Parsed to instance of {@link com.wit.and.dialog.Dialog} with default error icon
 * and single button.
 * </p>
 * <p>
 * Should contains only title and message text and can overrides text value of neutral
 * button ({@link com.wit.and.dialog.R.attr#dialogButtonNeutral}).
 * </p>
 * <h6>WebDialog</h6>
 * <p>
 * </p>
 * <h6>LoadingDialog</h6>
 * <p>
 * </p>
 * <h6>ProgressDialog</h6>
 * <p>
 * </p>
 * <h6>LoginDialog</h6>
 * <p>
 * </p>
 *
 * @author Martin Albedinsky
 * @see Dialog
 * @see com.wit.and.dialog.manage.DialogOptions
 * @see com.wit.and.dialog.manage.DialogFactory
 */
public final class XmlDialogInflater {

    /**
     * Constants =============================
     */

    /**
     * Log TAG.
     */
    private static final String TAG = XmlDialogInflater.class.getSimpleName();

    /**
     * Indicates if debug private output trough log-cat is enabled.
     */
    private static final boolean DEBUG = false;

    /**
     * Indicates if logging for user output trough log-cat is enabled.
     */
    private static final boolean USER_LOG = true;

    private static final String DIALOGS_SET_ROOT_TAG = "Dialogs";

    private static final int MAX_CACHED_PARSERS = 10;

    /**
     * Enums =================================
     */

    /**
     * Static members ========================
     */

    /**
     *
     */
    private static final HashMap<String, Class<? extends XmlDialogParser>> PARSERS = new HashMap<String, Class<? extends XmlDialogParser>>();

    // Register default parsers.
    static {
        PARSERS.put(XmlDialog.XML_TAG, XmlDialog.class);
        PARSERS.put(XmlDialog.XmlInfoDialog.XML_TAG, XmlDialog.XmlInfoDialog.class);
        PARSERS.put(XmlDialog.XmlAlertDialog.XML_TAG, XmlDialog.XmlAlertDialog.class);
        PARSERS.put(XmlDialog.XmlErrorDialog.XML_TAG, XmlDialog.XmlErrorDialog.class);
        PARSERS.put(XmlProgressDialog.XML_TAG, XmlProgressDialog.class);
        PARSERS.put(XmlProgressDialog.XmlLoadingDialog.XML_TAG, XmlProgressDialog.XmlLoadingDialog.class);
        PARSERS.put(XmlWebDialog.XML_TAG, XmlWebDialog.class);
        PARSERS.put(XmlEditDialog.XML_TAG, XmlEditDialog.class);
        PARSERS.put(XmlLoginDialog.XML_TAG, XmlLoginDialog.class);
    }

    /**
     *
     */
    private static final HashMap<String, XmlDialogParser> CACHED_PARSERS = new HashMap<String, XmlDialogParser>();

    /**
     * Members ===============================
     */

    /**
     * Resources to bind dialog options.
     */
    private final Resources mResources;

    /**
     * Listeners -----------------------------
     */

    /**
     * Arrays --------------------------------
     */

    /**
     * Booleans ------------------------------
     */

    /**
     * Constructors ==========================
     */

    /**
     * <p>
     * Constructs parser manager for XML dialogs.
     * </p>
     *
     * @param context Current context.
     */
    public XmlDialogInflater(Context context) {
        this.mResources = context.getResources();
    }

    /**
     * Methods ===============================
     */

    /**
     * Public --------------------------------
     */

    /**
     * <p>
     * Registers XML dialog parser. Note, that parser will be registered only in case, that
     * there is no such a parser already registered for the <var>dialogTag</var>.
     * </p>
     *
     * @param parserClass Class of XML dialog parser to register.
     * @param dialogTag   Tag of XML dialog for which should be the given parser registered.
     */
    public void registerParser(Class<? extends XmlDialogParser> parserClass, String dialogTag) {
        if (!PARSERS.containsKey(dialogTag)) {
            PARSERS.put(dialogTag, parserClass);
        } else {
            if (USER_LOG) {
                Log.i(TAG, "Xml dialog parser for dialog tag(" + dialogTag + ") is already registered.");
            }
        }
    }

    /**
     * <p>
     * Inflates dialog fragment instance form the given XML dialogs set resource using one
     * of the registered XML dialog parsers.
     * </p>
     *
     * @param dialogID  Id of dialog in the given <var>xmlSetRes</var> to inflate.
     * @param xmlSetRes Resource of XML file placed in the application resources. All XML dialogs in this XML file
     *                  must be placed inside <b>&lt;Dialogs&gt&lt;/Dialogs&gt;</b> root tag.
     * @return New instance of {@link DialogFragment} or its derived classes, depends on the XML dialog
     * root tag.
     * @throws XmlDialogInflater.UnsupportedXmlDialogTagException
     * @throws java.security.InvalidParameterException
     * @throws java.lang.IllegalStateException
     * @see #registerParser(Class, String)
     */
    public DialogFragment inflateDialog(int dialogID, int xmlSetRes) {
        XmlResourceParser xmlParser = mResources.getXml(xmlSetRes);
        if (xmlParser == null)
            return null;

        DialogFragment dialog = null;

        long time;
        if (DEBUG) {
            time = System.currentTimeMillis();
        }

        try {
            int xmlEvent;
            boolean dialogsTagResolved = false;

            while ((xmlEvent = xmlParser.getEventType()) != XmlResourceParser.END_DOCUMENT) {
                switch (xmlEvent) {
                case XmlResourceParser.START_DOCUMENT:
                    break;
                case XmlResourceParser.START_TAG:
                    String tag = xmlParser.getName();

                    if (!dialogsTagResolved) {
                        // Check valid root tag.
                        if (!tag.equals(DIALOGS_SET_ROOT_TAG)) {
                            throw new UnsupportedXmlDialogTagException(
                                    "Only 'Dialogs' tag is allowed as root tag of XML dialogs set.");
                        }
                        dialogsTagResolved = true;
                    } else {
                        // Check for empty dialog.
                        if (xmlParser.getAttributeCount() == 0) {
                            throw new IllegalStateException("Empty dialogs are not allowed.");
                        }

                        // Find the dialog with requested id.
                        // Note, that first attribute of each dialog must be its "id" to preserve
                        // fast parsing of xml file.
                        final int attr = xmlParser.getAttributeNameResource(0);
                        if (attr != android.R.attr.id) {
                            throw new InvalidParameterException(
                                    "First attribute of XML dialog must be always 'android:id'.");
                        }

                        // Finally check the dialog id.
                        final int id = xmlParser.getAttributeResourceValue(0, 0);
                        if (dialogID == id) {
                            dialog = parseDialogInner(xmlParser);
                        }
                    }
                    break;
                }
                if (dialog != null) {
                    break;
                }
                xmlParser.next();
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (DEBUG) {
            Log.d(TAG, "Parsing of XML dialog from dialogs set in "
                    + Long.toString(System.currentTimeMillis() - time) + "ms");
        }

        return dialog;
    }

    /**
     * <p>
     * Inflates dialog fragment instance form the given XML resource using one
     * of the registered XML dialog parsers.
     * </p>
     *
     * @param xmlRes Resource of XML file placed in the application resources. File should contains only
     *               one dialog root tag.
     * @return New instance of {@link DialogFragment} or its derived classes, depends on the XML dialog
     * root tag.
     * @throws XmlDialogInflater.UnsupportedXmlDialogTagException
     * @see #registerParser(Class, String)
     */
    public DialogFragment inflateDialog(int xmlRes) {
        XmlResourceParser xmlParser = mResources.getXml(xmlRes);
        if (xmlParser == null)
            return null;

        DialogFragment dialog = null;

        try {
            int xmlEvent;

            while ((xmlEvent = xmlParser.getEventType()) != XmlResourceParser.END_DOCUMENT) {
                switch (xmlEvent) {
                case XmlResourceParser.START_DOCUMENT:
                    break;
                case XmlResourceParser.START_TAG:
                    dialog = parseDialogInner(xmlParser);
                    break;
                }
                if (dialog != null) {
                    break;
                }
                xmlParser.next();
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return dialog;
    }

    /**
     * Getters + Setters ---------------------
     */

    /**
     * Protected -----------------------------
     */

    /**
     * Private -------------------------------
     */

    /**
     * Parses dialog instance form the given xml parser depends on the current parser name.
     *
     * @param xmlParser Xml parser with currently being parsed xml file.
     * @return New instance of dialog or <code>null</code> if some error occur.
     * @throws XmlDialogInflater.UnsupportedXmlDialogTagException
     */
    private DialogFragment parseDialogInner(XmlResourceParser xmlParser) {
        final String rootTag = xmlParser.getName();
        DialogFragment dialog;

        // Get the parser for root tag.
        XmlDialogParser parser = getParser(rootTag);
        if (parser != null) {
            // Prepare parser.
            parser.prepare();

            // Parse dialog.
            dialog = parser.parseXmlDialog(xmlParser);
        } else {
            throw new UnsupportedXmlDialogTagException(
                    "Failed to parse XML dialog. Unknown XML dialog root tag('" + rootTag + "').");
        }
        return dialog;
    }

    /**
     * Returns parser for the requested XML dialog root tag.
     *
     * @param rootTag XML dialog root tag.
     * @return Xml dialog parser or <code>null</code> if there is no parser registered for
     * the requested root tag.
     */
    private XmlDialogParser getParser(String rootTag) {
        // Check if there is cached parser for requested tag.
        XmlDialogParser parser = CACHED_PARSERS.get(rootTag);

        if (parser == null) {
            final Class<? extends XmlDialogParser> parserClass = PARSERS.get(rootTag);
            if (parserClass != null) {
                try {
                    // Create new parser.
                    parser = parserClass.newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } finally {
                    if (parser != null) {
                        // Set up and add parser into cache.
                        parser.dispatchSetUp(mResources);

                        // Clear cached parsers if needed.
                        if (CACHED_PARSERS.size() > MAX_CACHED_PARSERS) {
                            CACHED_PARSERS.clear();
                        }
                        CACHED_PARSERS.put(rootTag, parser);
                    }
                }
            }
        }
        return parser;
    }

    /**
     * Abstract methods ----------------------
     */

    /**
     * Inner classes =========================
     */

    /**
     * <h4>Class Overview</h4>
     * <p>
     * Exception which is thrown when there is unsupported XML (dialog) tag inside parsed XML file with XML dialog/-s.
     * </p>
     */
    public static class UnsupportedXmlDialogTagException extends AndroidRuntimeException {

        public UnsupportedXmlDialogTagException(String name) {
            super(name);
        }
    }

    /**
     * Interface =============================
     */
}