/* ===========================================================
* Orson Charts : a 3D chart library for the Java(tm) platform
* ===========================================================
*
* (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved.
*
* http://www.object-refinery.com/orsoncharts/index.html
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* If you do not wish to be bound by the terms of the GPL, an alternative
* commercial license can be purchased. For details, please see visit the
* Orson Charts home page:
*
* http://www.object-refinery.com/orsoncharts/index.html
*
*/
package com.orsoncharts.data;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.orsoncharts.data.category.StandardCategoryDataset3D;
import com.orsoncharts.util.json.JSONValue;
import com.orsoncharts.util.json.parser.JSONParser;
import com.orsoncharts.util.json.parser.ParseException;
import com.orsoncharts.util.ArgChecks;
import com.orsoncharts.util.json.parser.ContainerFactory;
import com.orsoncharts.data.xyz.XYZDataset;
import com.orsoncharts.data.xyz.XYZSeries;
import com.orsoncharts.data.xyz.XYZSeriesCollection;
/**
* Utility methods for interchange between datasets ({@link KeyedValues},
* {@link KeyedValues3D} and {@link XYZDataset}) and JSON format strings.
*
* @since 1.3
*/
public class JSONUtils {
/**
* Parses the supplied JSON string into a {@link KeyedValues} instance.
* <br><br>
* Implementation note: this method returns an instance of
* {@link StandardPieDataset3D}).
*
* @param json the JSON string ({@code null} not permitted).
*
* @return A {@link KeyedValues} instance.
*/
public static KeyedValues<String, Number> readKeyedValues(String json) {
ArgChecks.nullNotPermitted(json, "json");
StringReader in = new StringReader(json);
KeyedValues<String, Number> result;
try {
result = readKeyedValues(in);
} catch (IOException ex) {
// not for StringReader
result = null;
}
return result;
}
/**
* Parses characters from the supplied reader and returns the corresponding
* {@link KeyedValues} instance.
* <br><br>
* Implementation note: this method returns an instance of
* {@link StandardPieDataset3D}).
*
* @param reader the reader ({@code null} not permitted).
*
* @return A {@code KeyedValues} instance.
*
* @throws IOException if there is an I/O problem.
*/
public static KeyedValues<String, Number> readKeyedValues(
Reader reader) throws IOException {
ArgChecks.nullNotPermitted(reader, "reader");
try {
JSONParser parser = new JSONParser();
// parse with custom containers (to preserve item order)
List list = (List) parser.parse(reader, createContainerFactory());
StandardPieDataset3D<String> result = new StandardPieDataset3D<String>();
for (Object item : list) {
List itemAsList = (List) item;
result.add(itemAsList.get(0).toString(), (Number) itemAsList.get(1));
}
return result;
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns a string containing the data in JSON format. The format is
* an array of arrays, where each sub-array represents one data value.
* The sub-array should contain two items, first the item key as a string
* and second the item value as a number. For example:
* {@code [["Key A", 1.0], ["Key B", 2.0]]}
* <br><br>
* Note that this method can be used with instances of {@link PieDataset3D}.
*
* @param data the data ({@code null} not permitted).
*
* @return A string in JSON format.
*/
@SuppressWarnings("unchecked")
public static String writeKeyedValues(KeyedValues data) {
ArgChecks.nullNotPermitted(data, "data");
StringWriter sw = new StringWriter();
try {
writeKeyedValues(data, sw);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return sw.toString();
}
/**
* Writes the data in JSON format to the supplied writer.
* <br><br>
* Note that this method can be used with instances of {@link PieDataset3D}.
*
* @param data the data ({@code null} not permitted).
* @param writer the writer ({@code null} not permitted).
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static void writeKeyedValues(KeyedValues data, Writer writer)
throws IOException {
ArgChecks.nullNotPermitted(data, "data");
ArgChecks.nullNotPermitted(writer, "writer");
writer.write("[");
boolean first = true;
for (Object key : data.getKeys()) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write("[");
writer.write(JSONValue.toJSONString(key.toString()));
writer.write(", ");
writer.write(JSONValue.toJSONString(data.getValue((Comparable) key)));
writer.write("]");
}
writer.write("]");
}
/**
* Reads a data table from a JSON format string.
*
* @param json the string ({@code null} not permitted).
*
* @return A data table.
*/
@SuppressWarnings("unchecked")
public static KeyedValues2D<String, String, Number>
readKeyedValues2D(String json) {
ArgChecks.nullNotPermitted(json, "json");
StringReader in = new StringReader(json);
KeyedValues2D<String, String, Number> result;
try {
result = readKeyedValues2D(in);
} catch (IOException ex) {
// not for StringReader
result = null;
}
return result;
}
/**
* Reads a data table from a JSON format string coming from the specified
* reader.
*
* @param reader the reader ({@code null} not permitted).
*
* @return A data table.
*
* @throws java.io.IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static KeyedValues2D<String, String, Number>
readKeyedValues2D(Reader reader) throws IOException {
JSONParser parser = new JSONParser();
try {
Map map = (Map) parser.parse(reader, createContainerFactory());
DefaultKeyedValues2D<String, String, Number> result
= new DefaultKeyedValues2D<String, String, Number>();
if (map.isEmpty()) {
return result;
}
// read the keys
Object keysObj = map.get("columnKeys");
List<String> keys = null;
if (keysObj instanceof List) {
keys = (List<String>) keysObj;
} else {
if (keysObj == null) {
throw new RuntimeException("No 'columnKeys' defined.");
} else {
throw new RuntimeException("Please check the 'columnKeys', "
+ "the format does not parse to a list.");
}
}
Object dataObj = map.get("rows");
if (dataObj instanceof List) {
List<String> rowList = (List<String>) dataObj;
// each entry in the map has the row key and an array of
// values (the length should match the list of keys above
for (Object rowObj : rowList) {
processRow(rowObj, keys, result);
}
} else { // the 'data' entry is not parsing to a list
if (dataObj == null) {
throw new RuntimeException("No 'rows' section defined.");
} else {
throw new RuntimeException("Please check the 'rows' "
+ "entry, the format does not parse to a list of "
+ "rows.");
}
}
return result;
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
/**
* Processes an entry for one row in a {@link KeyedValues2D}.
*
* @param rowObj the series object.
* @param columnKeys the required column keys.
* @param dataset the dataset.
*/
@SuppressWarnings("unchecked")
static void processRow(Object rowObj, List<String> columnKeys,
DefaultKeyedValues2D dataset) {
if (!(rowObj instanceof List)) {
throw new RuntimeException("Check the 'data' section it contains "
+ "a row that does not parse to a list.");
}
// we expect the row data object to be an array containing the
// rowKey and rowValueArray entries, where rowValueArray
// should have the same number of entries as the columnKeys
List rowList = (List) rowObj;
Object rowKey = rowList.get(0);
Object rowDataObj = rowList.get(1);
if (!(rowDataObj instanceof List)) {
throw new RuntimeException("Please check the row entry for "
+ rowKey + " because it is not parsing to a list (of "
+ "rowKey and rowDataValues items.");
}
List<?> rowData = (List<?>) rowDataObj;
if (rowData.size() != columnKeys.size()) {
throw new RuntimeException("The values list for series "
+ rowKey + " does not contain the correct number of "
+ "entries to match the columnKeys.");
}
for (int c = 0; c < rowData.size(); c++) {
Object columnKey = columnKeys.get(c);
dataset.setValue(objToDouble(rowData.get(c)),
rowKey.toString(), columnKey.toString());
}
}
/**
* Writes a data table to a string in JSON format.
*
* @param data the data ({@code null} not permitted).
*
* @return The string.
*/
public static String writeKeyedValues2D(KeyedValues2D data) {
ArgChecks.nullNotPermitted(data, "data");
StringWriter sw = new StringWriter();
try {
writeKeyedValues2D(data, sw);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return sw.toString();
}
/**
* Writes the data in JSON format to the supplied writer.
*
* @param data the data ({@code null} not permitted).
* @param writer the writer ({@code null} not permitted).
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static void writeKeyedValues2D(KeyedValues2D data, Writer writer)
throws IOException {
ArgChecks.nullNotPermitted(data, "data");
ArgChecks.nullNotPermitted(writer, "writer");
List<Comparable> columnKeys = data.getColumnKeys();
List<Comparable> rowKeys = data.getRowKeys();
writer.write("{");
if (!columnKeys.isEmpty()) {
writer.write("\"columnKeys\": [");
boolean first = true;
for (Comparable columnKey : columnKeys) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write(JSONValue.toJSONString(columnKey.toString()));
}
writer.write("]");
}
if (!rowKeys.isEmpty()) {
writer.write(", \"rows\": [");
boolean firstRow = true;
for (Comparable rowKey : rowKeys) {
if (!firstRow) {
writer.write(", [");
} else {
writer.write("[");
firstRow = false;
}
// write the row data
writer.write(JSONValue.toJSONString(rowKey.toString()));
writer.write(", [");
boolean first = true;
for (Comparable columnKey : columnKeys) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write(JSONValue.toJSONString(data.getValue(rowKey,
columnKey)));
}
writer.write("]]");
}
writer.write("]");
}
writer.write("}");
}
/**
* Parses the supplied string and (if possible) creates a
* {@link KeyedValues3D} instance.
*
* @param json the JSON string ({@code null} not permitted).
*
* @return A {@code KeyedValues3D} instance.
*/
public static KeyedValues3D<String, String, String, Number>
readKeyedValues3D(String json) {
StringReader in = new StringReader(json);
KeyedValues3D<String, String, String, Number> result;
try {
result = readKeyedValues3D(in);
} catch (IOException ex) {
// not for StringReader
result = null;
}
return result;
}
/**
* Parses character data from the reader and (if possible) creates a
* {@link KeyedValues3D} instance. This method will read back the data
* written by {@link JSONUtils#writeKeyedValues3D(
* com.orsoncharts.data.KeyedValues3D, java.io.Writer) }.
*
* @param reader the reader ({@code null} not permitted).
*
* @return A {@code KeyedValues3D} instance.
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static KeyedValues3D<String, String, String, Number>
readKeyedValues3D(Reader reader) throws IOException {
JSONParser parser = new JSONParser();
try {
Map map = (Map) parser.parse(reader, createContainerFactory());
StandardCategoryDataset3D result = new StandardCategoryDataset3D();
if (map.isEmpty()) {
return result;
}
// read the row keys, we'll use these to validate the row keys
// supplied with the data
Object rowKeysObj = map.get("rowKeys");
List<String> rowKeys;
if (rowKeysObj instanceof List) {
rowKeys = (List<String>) rowKeysObj;
} else {
if (rowKeysObj == null) {
throw new RuntimeException("No 'rowKeys' defined.");
} else {
throw new RuntimeException("Please check the 'rowKeys', "
+ "the format does not parse to a list.");
}
}
// read the column keys, the data is provided later in rows that
// should have the same number of entries as the columnKeys list
Object columnKeysObj = map.get("columnKeys");
List<String> columnKeys;
if (columnKeysObj instanceof List) {
columnKeys = (List<String>) columnKeysObj;
} else {
if (columnKeysObj == null) {
throw new RuntimeException("No 'columnKeys' defined.");
} else {
throw new RuntimeException("Please check the 'columnKeys', "
+ "the format does not parse to a list.");
}
}
// the data object should be a list of data series
Object dataObj = map.get("data");
if (dataObj instanceof List) {
List<String> seriesList = (List<String>) dataObj;
// each entry in the map has the series name as the key, and
// the value is a map of row data (rowKey, list of values)
for (Object seriesObj : seriesList) {
processSeries(seriesObj, rowKeys, columnKeys, result);
}
} else { // the 'data' entry is not parsing to a list
if (dataObj == null) {
throw new RuntimeException("No 'data' section defined.");
} else {
throw new RuntimeException("Please check the 'data' "
+ "entry, the format does not parse to a list of "
+ "series.");
}
}
return result;
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
/**
* Processes an entry for one series.
*
* @param seriesObj the series object.
* @param rowKeys the expected row keys.
* @param columnKeys the required column keys.
*/
static <R extends Comparable<R>, C extends Comparable<C>>
void processSeries(Object seriesObj, List<R> rowKeys,
List<C> columnKeys,
StandardCategoryDataset3D<String, String, String> dataset) {
if (!(seriesObj instanceof Map)) {
throw new RuntimeException("Check the 'data' section it contains "
+ "a series that does not parse to a map.");
}
// we expect the series data object to be a map of
// rowKey ==> rowValueArray entries, where rowValueArray
// should have the same number of entries as the columnKeys
Map seriesMap = (Map) seriesObj;
Object seriesKey = seriesMap.get("seriesKey");
Object seriesRowsObj = seriesMap.get("rows");
if (!(seriesRowsObj instanceof Map)) {
throw new RuntimeException("Please check the series entry for "
+ seriesKey + " because it is not parsing to a map (of "
+ "rowKey -> rowDataValues items.");
}
Map<?, ?> seriesData = (Map<?, ?>) seriesRowsObj;
for (Object rowKey : seriesData.keySet()) {
if (!rowKeys.contains(rowKey)) {
throw new RuntimeException("The row key " + rowKey + " is not "
+ "listed in the rowKeys entry.");
}
Object rowValuesObj = seriesData.get(rowKey);
if (!(rowValuesObj instanceof List<?>)) {
throw new RuntimeException("Please check the entry for series "
+ seriesKey + " and row " + rowKey + " because it "
+ "does not parse to a list of values.");
}
List<?> rowValues = (List<?>) rowValuesObj;
if (rowValues.size() != columnKeys.size()) {
throw new RuntimeException("The values list for series "
+ seriesKey + " and row " + rowKey + " does not "
+ "contain the correct number of entries to match "
+ "the columnKeys.");
}
for (int c = 0; c < rowValues.size(); c++) {
Object columnKey = columnKeys.get(c);
dataset.addValue(objToDouble(rowValues.get(c)),
seriesKey.toString(), rowKey.toString(),
columnKey.toString());
}
}
}
/**
* Returns a string containing the data in JSON format.
*
* @param dataset the data ({@code null} not permitted).
*
* @return A string in JSON format.
*/
public static String writeKeyedValues3D(KeyedValues3D dataset) {
ArgChecks.nullNotPermitted(dataset, "dataset");
StringWriter sw = new StringWriter();
try {
writeKeyedValues3D(dataset, sw);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return sw.toString();
}
/**
* Writes the dataset in JSON format to the supplied writer.
*
* @param dataset the dataset ({@code null} not permitted).
* @param writer the writer ({@code null} not permitted).
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static void writeKeyedValues3D(KeyedValues3D dataset, Writer writer)
throws IOException {
ArgChecks.nullNotPermitted(dataset, "dataset");
ArgChecks.nullNotPermitted(writer, "writer");
writer.write("{");
if (!dataset.getColumnKeys().isEmpty()) {
writer.write("\"columnKeys\": [");
boolean first = true;
for (Object key : dataset.getColumnKeys()) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write(JSONValue.toJSONString(key.toString()));
}
writer.write("], ");
}
// write the row keys
if (!dataset.getRowKeys().isEmpty()) {
writer.write("\"rowKeys\": [");
boolean first = true;
for (Object key : dataset.getRowKeys()) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write(JSONValue.toJSONString(key.toString()));
}
writer.write("], ");
}
// write the data which is zero, one or many data series
// a data series has a 'key' and a 'rows' attribute
// the 'rows' attribute is a Map from 'rowKey' -> array of data values
if (dataset.getSeriesCount() != 0) {
writer.write("\"series\": [");
boolean first = true;
for (Object seriesKey : dataset.getSeriesKeys()) {
if (!first) {
writer.write(", ");
} else {
first = false;
}
writer.write("{\"seriesKey\": ");
writer.write(JSONValue.toJSONString(seriesKey.toString()));
writer.write(", \"rows\": [");
boolean firstRow = true;
for (Object rowKey : dataset.getRowKeys()) {
if (countForRowInSeries(dataset, (Comparable) seriesKey,
(Comparable) rowKey) > 0) {
if (!firstRow) {
writer.write(", [");
} else {
writer.write("[");
firstRow = false;
}
// write the row values
writer.write(JSONValue.toJSONString(rowKey.toString())
+ ", [");
for (int c = 0; c < dataset.getColumnCount(); c++) {
Object columnKey = dataset.getColumnKey(c);
if (c != 0) {
writer.write(", ");
}
writer.write(JSONValue.toJSONString(
dataset.getValue((Comparable) seriesKey,
(Comparable) rowKey,
(Comparable) columnKey)));
}
writer.write("]]");
}
}
writer.write("]}");
}
writer.write("]");
}
writer.write("}");
}
/**
* Returns the number of non-{@code null} entries for the specified
* series and row.
*
* @param data the dataset ({@code null} not permitted).
* @param seriesKey the series key ({@code null} not permitted).
* @param rowKey the row key ({@code null} not permitted).
*
* @return The count.
*/
@SuppressWarnings("unchecked")
private static int countForRowInSeries(KeyedValues3D data,
Comparable seriesKey, Comparable rowKey) {
ArgChecks.nullNotPermitted(data, "data");
ArgChecks.nullNotPermitted(seriesKey, "seriesKey");
ArgChecks.nullNotPermitted(rowKey, "rowKey");
int seriesIndex = data.getSeriesIndex(seriesKey);
if (seriesIndex < 0) {
throw new IllegalArgumentException("Series not found: "
+ seriesKey);
}
int rowIndex = data.getRowIndex(rowKey);
if (rowIndex < 0) {
throw new IllegalArgumentException("Row not found: " + rowKey);
}
int count = 0;
for (int c = 0; c < data.getColumnCount(); c++) {
Object n = data.getValue(seriesIndex, rowIndex, c);
if (n != null) {
count++;
}
}
return count;
}
/**
* Parses the string and (if possible) creates an {XYZDataset} instance
* that represents the data. This method will read back the data that
* is written by
* {@link #writeXYZDataset(com.orsoncharts.data.xyz.XYZDataset)}.
*
* @param json a JSON formatted string ({@code null} not permitted).
*
* @return A dataset.
*
* @see #writeXYZDataset(com.orsoncharts.data.xyz.XYZDataset)
*/
public static XYZDataset<String> readXYZDataset(String json) {
ArgChecks.nullNotPermitted(json, "json");
StringReader in = new StringReader(json);
XYZDataset<String> result;
try {
result = readXYZDataset(in);
} catch (IOException ex) {
// not for StringReader
result = null;
}
return result;
}
/**
* Parses character data from the reader and (if possible) creates an
* {XYZDataset} instance that represents the data.
*
* @param reader a reader ({@code null} not permitted).
*
* @return A dataset.
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static XYZDataset<String> readXYZDataset(Reader reader) throws IOException {
JSONParser parser = new JSONParser();
XYZSeriesCollection<String> result = new XYZSeriesCollection<String>();
try {
List<?> list = (List<?>) parser.parse(reader,
createContainerFactory());
// each entry in the array should be a series array (where the
// first item is the series name and the next value is an array
// (of arrays of length 3) containing the data items
for (Object seriesArray : list) {
if (seriesArray instanceof List) {
List<?> seriesList = (List<?>) seriesArray;
XYZSeries series = createSeries(seriesList);
result.add(series);
} else {
throw new RuntimeException(
"Input for a series did not parse to a list.");
}
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
return result;
}
/**
* Returns a string containing the dataset in JSON format.
*
* @param dataset the dataset ({@code null} not permitted).
*
* @return A string in JSON format.
*/
public static String writeXYZDataset(XYZDataset dataset) {
StringWriter sw = new StringWriter();
try {
writeXYZDataset(dataset, sw);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return sw.toString();
}
/**
* Writes the dataset in JSON format to the supplied writer.
*
* @param dataset the data ({@code null} not permitted).
* @param writer the writer ({@code null} not permitted).
*
* @throws IOException if there is an I/O problem.
*/
@SuppressWarnings("unchecked")
public static void writeXYZDataset(XYZDataset dataset, Writer writer)
throws IOException {
writer.write("[");
boolean first = true;
for (Object seriesKey : dataset.getSeriesKeys()) {
if (!first) {
writer.write(", [");
} else {
writer.write("[");
first = false;
}
writer.write(JSONValue.toJSONString(seriesKey.toString()));
writer.write(", [");
int seriesIndex = dataset.getSeriesIndex((Comparable) seriesKey);
int itemCount = dataset.getItemCount(seriesIndex);
for (int i = 0; i < itemCount; i++) {
if (i != 0) {
writer.write(", ");
}
writer.write("[");
writer.write(JSONValue.toJSONString(Double.valueOf(
dataset.getX(seriesIndex, i))));
writer.write(", ");
writer.write(JSONValue.toJSONString(Double.valueOf(
dataset.getY(seriesIndex, i))));
writer.write(", ");
writer.write(JSONValue.toJSONString(Double.valueOf(
dataset.getZ(seriesIndex, i))));
writer.write("]");
}
writer.write("]]");
}
writer.write("]");
}
/**
* Converts an arbitrary object to a double.
*
* @param obj an object ({@code null} permitted).
*
* @return A double primitive (possibly Double.NaN).
*/
private static double objToDouble(Object obj) {
if (obj == null) {
return Double.NaN;
}
if (obj instanceof Number) {
return ((Number) obj).doubleValue();
}
double result = Double.NaN;
try {
result = Double.valueOf(obj.toString());
} catch (Exception e) {
}
return result;
}
/**
* Creates an {@link XYZSeries} from the supplied list. The list is
* coming from the JSON parser and should contain the series name as the
* first item, and a list of data items as the second item. The list of
* data items should be a list of lists (
*
* @param sArray the series array.
*
* @return A data series.
*/
@SuppressWarnings("unchecked")
private static XYZSeries createSeries(List<?> sArray) {
Comparable<?> key = (Comparable<?>) sArray.get(0);
List<?> dataItems = (List<?>) sArray.get(1);
XYZSeries series = new XYZSeries(key);
for (Object item : dataItems) {
if (item instanceof List<?>) {
List<?> xyz = (List<?>) item;
if (xyz.size() != 3) {
throw new RuntimeException(
"A data item should contain three numbers, "
+ "but we have " + xyz);
}
double x = objToDouble(xyz.get(0));
double y = objToDouble(xyz.get(1));
double z = objToDouble(xyz.get(2));
series.add(x, y, z);
} else {
throw new RuntimeException(
"Expecting a data item (x, y, z) for series " + key
+ " but found " + item + ".");
}
}
return series;
}
/**
* Returns a custom container factory for the JSON parser. We create this
* so that the collections respect the order of elements.
*
* @return The container factory.
*/
private static ContainerFactory createContainerFactory() {
return new ContainerFactory() {
@Override
public Map createObjectContainer() {
return new LinkedHashMap();
}
@Override
public List creatArrayContainer() {
return new ArrayList();
}
};
}
}