com.oceans7.mobile.eagleswag.persistence.parsers.JsonDataFileParserStrategy.java Source code

Java tutorial

Introduction

Here is the source code for com.oceans7.mobile.eagleswag.persistence.parsers.JsonDataFileParserStrategy.java

Source

/*
 * EagleSwag Android Mobile Application
 * Copyright (C) 2013 Oceans7
 * Oceans7 Mobile Applications Development Team
 * 
 * This software is free and governed by the terms of the GNU General Public
 * License as published by the Free Software Foundation. This software may be
 * redistributed and/or modified in accordance with version 3, or any later
 * version, of the GNU General Public License.
 * 
 * This software is distributed without any warranty; without even the implied
 * warranty of merchantability or fitness for a particular purpose. For further
 * detail, refer to the GNU General Public License, which can be found in the
 * LICENSE.txt file at the root directory of this project, or online at:
 * 
 * <http://www.gnu.org/licenses/>
 */

package com.oceans7.mobile.eagleswag.persistence.parsers;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.Queue;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import android.content.Context;
import android.util.Log;

import com.oceans7.mobile.eagleswag.domain.Question;
import com.oceans7.mobile.eagleswag.persistence.DataFileParserStrategy;

/**
 * A data parser strategy for a JSON data file. This parser strategy extracts
 * the data for a question type from a JSON file and returns the extracted data
 * in a queue of questions. The file name of the data file containing the
 * questions data to be parsed by this strategy follows a
 * "convention over configuration" paradigm. See
 * {@link #getQuestions(java.lang.Class, android.content.Context)} for more
 * information.
 * 
 * @author Justin Albano
 */
public class JsonDataFileParserStrategy implements DataFileParserStrategy {

    /***************************************************************************
     * Attributes
     **************************************************************************/

    /**
     * The title of the attribute associated with the text of a question stored
     * in a JSON question data file.
     */
    private static final String QUESTION_TEXT_ID = "text";

    /**
     * The title of the attribute associated with the yes point value of a
     * question stored in a JSON question data file.
     */
    private static final String YES_VALUE_ID = "yesValue";

    /**
     * The title of the attribute associated with the no point value of a
     * question stored in a JSON question data file.
     */
    private static final String NO_VALUE_ID = "noValue";

    /**
     * The title of the attribute associated with the used count of a question
     * stored in a JSON question data file.
     */
    private static final String USED_COUNT_ID = "usedCount";

    /**
     * The path (not including file name) of the directory containing the JSON
     * data files to be parsed. This path is relative to the assets/ directory
     * of the Android project structure.
     */
    private static final String DATA_FILE_ASSET_PATH = "data/";

    /**
     * The extension for the data files containing questions data.
     */
    private static final String EXTENSION = ".json";

    /**
     * The context used to access the data files.
     */
    private Context context;

    /***************************************************************************
     * Constructors
     **************************************************************************/

    public JsonDataFileParserStrategy(Context context) {
        // Set the context used to access the data files
        this.setContext(context);
    }

    /***************************************************************************
     * Methods
     **************************************************************************/

    /**
     * This parsing method uses the convention over configuration paradigm,
     * where the name of the data file containing the questions data matches the
     * class name (simple class name) of the key provided, with a ".json" file
     * extension. For example, if GeneralQuestion.class is provided as the key,
     * the data file name expected is "GeneralQuestion.json." Likewise, the
     * object name for the array containing the questions data within the data
     * file is simple class name of the key provided. For example, if
     * GeneralQuestion.class is provided as a key, the format of the data file
     * would be:
     * 
     * <pre>
     * {
     *   "GeneralQuestion": [
     *     {
     *       "text": "General question 1",
     *       "yesValue": "10",
     *       "noValue": "0",
     *       "usedCount": "0"
     *     },
     *           .
     *           .
     *           .
     *     ]
     * }
     * </pre>
     * 
     * If no data file is found for the key provided, an empty queue is
     * returned.
     * <p/>
     * 
     * {@inheritDoc}
     * 
     * @see com.oceans7.mobile.eagleswag.persistence.DataFileParserStrategy#getQuestion(java.lang.Class)
     */
    @Override
    public <T extends Question> Queue<T> getQuestions(Class<T> key) {

        // The queue used to store the questions retrieved from the data file
        Queue<T> questions = new LinkedList<T>();

        // Set the object name for the data file and the name of the data file
        // to the simple name of the key. This is the implementation of the
        // "convention over configuration" mechanism for obtaining data.
        String id = key.getSimpleName();
        String dataFileName = key.getSimpleName() + EXTENSION;

        try {

            // The JSON parser to parse the data file
            JSONParser parser = new JSONParser();

            // Open the JSON file containing the questions
            InputStream dataFileInputStream = this.context.getAssets().open(DATA_FILE_ASSET_PATH + dataFileName);

            // Parse the JSON file
            JSONObject jsonObj = (JSONObject) parser.parse(new InputStreamReader(dataFileInputStream));
            dataFileInputStream.close();

            // Obtain a JSON array for the question type supplied (dependent on
            // the ID supplied)
            JSONArray questionsArray = (JSONArray) jsonObj.get(id);

            for (Object question : questionsArray) {
                // Loop through each of the questions found in the data file

                // Convert the object to a JSON object
                JSONObject jsonQuestion = (JSONObject) question;

                // --------------------------------------------------------------
                // Extract the JSON values (note that the ID value is set to 0
                // because it will be supplied later by the data controller
                // (there is no ID associated with the questions in the
                // questions data file)
                // --------------------------------------------------------------
                int questionId = 0;
                String text = (String) jsonQuestion.get(QUESTION_TEXT_ID);
                int yesValue = Integer.parseInt((String) jsonQuestion.get(YES_VALUE_ID));
                int noValue = Integer.parseInt((String) jsonQuestion.get(NO_VALUE_ID));
                int usedCount = Integer.parseInt((String) jsonQuestion.get(USED_COUNT_ID));

                // Obtain the constructor for the supplied class
                Class<?>[] argTypes = new Class<?>[] { Integer.class, String.class, Integer.class, Integer.class,
                        Integer.class };
                Constructor<T> constructor = key.getDeclaredConstructor(argTypes);
                Object[] args = new Object[] { questionId, text, yesValue, noValue, usedCount };

                // Invoke the constructor to obtain the object
                T questionToAdd = constructor.newInstance(args);

                // Add the new general question (ignoring the ID)
                questions.add(questionToAdd);
            }
        } catch (FileNotFoundException e) {
            // A data file for the key provided cannot be found
            Log.e(this.getClass().getName(),
                    "No data file named '" + dataFileName + "' found in '" + DATA_FILE_ASSET_PATH + "'");
        } catch (IOException e) {
            // IO exception occurred while accessing the JSON data file
            Log.e(this.getClass().getName(), "IO exception occurred while parsing " + dataFileName + ": " + e);
        } catch (ParseException e) {
            // A parse exception occurred while parsing a data file
            Log.e(this.getClass().getName(),
                    "A parser exception occurred while parsing " + dataFileName + ": " + e);
        } catch (NoSuchMethodException e) {
            // A constructor with the specified format cannot be found for key
            Log.e(this.getClass().getName(), "Could not find the desired constructor for the " + id + ": " + e);
        } catch (IllegalArgumentException e) {
            // The arguments for the question constructor are incorrect
            Log.e(this.getClass().getName(),
                    "Invalid constructor arguments while trying to create new " + id + ": " + e);
        } catch (InstantiationException e) {
            // The selected question object could not be instantiated
            Log.e(this.getClass().getName(), "Could not not instantiate an object for " + id + ": " + e);
        } catch (IllegalAccessException e) {
            // The selected question constructor could not be accessed
            Log.e(this.getClass().getName(), "Could not access the desired constructor for " + id + ": " + e);
        } catch (InvocationTargetException e) {
            // The constructor could not be invoked on the target object
            Log.e(this.getClass().getName(), "Could not invoke constructor on the target " + id + ": " + e);
        }

        // Return the queue containing the questions (which may be empty)
        return questions;
    }

    /***************************************************************************
     * Getters & Setters
     **************************************************************************/

    /**
     * @return 
     *      The context.
     */
    public Context getContext() {
        return context;
    }

    /**
     * @param context 
     *       The context to set.
     */
    public void setContext(Context context) {
        this.context = context;
    }

}