oms3.io.DataIO Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oms Show documentation
Show all versions of oms Show documentation
Object Modeling System (OMS) is a pure Java object-oriented framework.
OMS v3.+ is a highly interoperable and lightweight modeling framework for component-based model and simulation development on multiple platforms.
/*
* $Id: DataIO.java 7cba5ba59d73 2018-11-29 [email protected] $
*
* This file is part of the Object Modeling System (OMS),
* 2007-2012, Olaf David and others, Colorado State University.
*
* OMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 2.1.
*
* OMS 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with OMS. If not, see .
*/
package oms3.io;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Arrays;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import oms3.Conversions;
/**
* Data Input/Output management.
*
* @author od
*/
public class DataIO {
private static final String P = "@";
public static final String TABLE = P + "T";
public static final String HEADER = P + "H";
public static final String PROPERTIES = P + "S";
public static final String PROPERTY = P + "P";
public static final String TABLE1 = P + "Table";
public static final String HEADER1 = P + "Header";
public static final String PROPERTIES1 = P + "Properties";
public static final String PROPERTY1 = P + "Property";
//
//
public static final String CSPROPERTIES_EXT = "csp";
public static final String CSTABLE_EXT = "cst";
//
private static final String ROOT_ANN = "___root___";
private static final String COMMENT = "#";
private static final Pattern varPattern = Pattern.compile("\\$\\{([^$}]+)\\}");
/*
* some static helpers, might have to go somewhere else
*/
private static final String ISO8601 = "yyyy-MM-dd'T'hh:mm:ss";
//
// all meta data keys
public static final String KEY_CONVERTED_FROM = "converted_from";
public static final String DATE_FORMAT = "date_format";
public static final String DATE_START = "date_start";
public static final String DATE_END = "date_end";
public static final String KEY_CREATED_AT = "created_at";
public static final String KEY_MODIFIED_AT = "modifed_at";
public static final String KEY_CREATED_BY = "created_by";
public static final String KEY_UNIT = "unit";
public static final String KEY_FORMAT = "format";
public static final String KEY_TYPE = "type";
public static final String KEY_NAME = "name";
public static final String KEY_MISSING_VAL = "missing_value";
public static final String KEY_FC_START = "forecast_start";
public static final String KEY_FC_DAYS = "forecast_days";
public static final String KEY_HIST_YEAR = "historical_year";
public static final String KEY_HIST_YEARS = "historical_years";
public static final String KEY_ESP_DATES = "esp_dates";
public static final String KEY_DIGEST = "digest";
public static final String VAL_DATE = "Date";
//
//TimeStep Enumerations
public static final int DAILY = 0;
public static final int MEAN_MONTHLY = 1;
public static final int MONTHLY_MEAN = 2;
public static final int ANNUAL_MEAN = 3;
public static final int PERIOD_MEAN = 4;
public static final int PERIOD_MEDIAN = 5;
public static final int PERIOD_STANDARD_DEVIATION = 6;
public static final int PERIOD_MIN = 7;
public static final int PERIOD_MAX = 8;
public static final int RAW = 9;
public static final int TIME_STEP = 10;
public static double[] getColumnDoubleValuesInterval(Date start, Date end, CSTable t, String columnName, int timeStep) {
// Uses calendar year with all months as default.
boolean[] periodMask = {true, true, true, true, true, true, true, true, true, true, true, true};
boolean[] subDivideMask = null;
return getColumnDoubleValuesInterval(start, end, t, columnName, timeStep, 0, periodMask, subDivideMask);
}
public static double[] getColumnDoubleValuesInterval(Date start, Date end, CSTable t, String columnName, int timeStep, boolean[] periodMask) {
// Uses calendar year with all months as default.
boolean[] subDivideMask = null;
return getColumnDoubleValuesInterval(start, end, t, columnName, timeStep, 0, periodMask, subDivideMask);
}
public static double[] getColumnDoubleValuesInterval(Date start, Date end, CSTable t, String columnName, int timeStep, int startMonth,
boolean[] periodMask, boolean[] subDivideMask) {
int col = columnIndex(t, columnName);
if (col == -1) {
throw new IllegalArgumentException("No such column: " + columnName);
}
if (periodMask != null) {
boolean pmCheck = false;
for (int i = 0; i < periodMask.length; i++) {
pmCheck = pmCheck | periodMask[i];
}
if (pmCheck == false) {
throw new IllegalArgumentException("PeriodMask (i.e. Mask of months to include in data analysis) is all false, so no data would be used.");
}
}
// do this before the switch
if (timeStep == RAW) {
Double[] a = getColumnDoubleValues(t, columnName);
double[] arr = Conversions.convert(a, double[].class);
return arr;
}
DateFormat fmt = lookupDateFormat(t, 1);
boolean hasYear = DateFormatHasYear(t, 1);
switch (timeStep) {
case TIME_STEP: {
List l = new ArrayList();
double sum = 0;
for (String[] row : t.rows()) {
try {
Date d = fmt.parse(row[1]);
if ((d.equals(start) || d.after(start))
&& (d.equals(end) || d.before(end))) {
int month = d.getMonth();
double data = Double.parseDouble(row[col]);
boolean periodValid = (periodMask == null) || (periodMask[month] == true);
if (periodValid) {
l.add(data);
}
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
// Copy the List to the output array.
double[] arr = new double[l.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = l.get(i);
}
return arr;
}
case DAILY:
case MONTHLY_MEAN: {
int previousMonth = -1;
int previousYear = -1;
int previousDay = -1;
boolean previousValid = false;
boolean useYear = (timeStep == DAILY) || (timeStep == MONTHLY_MEAN) || (timeStep == ANNUAL_MEAN);
boolean useMonth = (timeStep == DAILY) || (timeStep == MONTHLY_MEAN);
boolean useDay = (timeStep == DAILY);
List l = new ArrayList();
double sum = 0;
int count = 0;
int sdindx = 0;
for (String[] row : t.rows()) {
try {
Date d = fmt.parse(row[1]);
if ((d.equals(start) || d.after(start)) && (d.equals(end) || d.before(end))) {
int month = d.getMonth();
int year = d.getYear();
int day = d.getDate();
double data = Double.parseDouble(row[col]);
boolean newYear = (year != previousYear);
boolean newEntry = (previousValid
&& ((useYear && newYear)
|| (useMonth && (month != previousMonth))
|| (useDay && (day != previousDay))));
if (newEntry && (count != 0)) {
l.add(sum / count);
sum = 0;
count = 0;
}
boolean periodValid = (periodMask == null) || (periodMask[month] == true);
boolean subDivideValid = (subDivideMask == null) || subDivideMask[sdindx++];
if (periodValid && subDivideValid) {
sum += data;
count++;
}
previousValid = true;
previousDay = day;
previousMonth = month;
previousYear = year;
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
if (count != 0) {
l.add(sum / count);
} // add the final entry which wasn't yet added
// since it never hit a newEntry.
// Copy the List to the output array.
double[] arr = new double[l.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = l.get(i);
}
return arr;
// break;
}
case MEAN_MONTHLY: {
double[] arr = new double[12]; // 1 per month
int[] count = new int[12];
int sdindx = 0;
for (int i = 0; i < 12; i++) {
arr[i] = 0; // initialize data to 0
count[i] = 0;
}
// Create month if we don't have a date. Assumes 1 month/row.
for (String[] row : t.rows()) {
try {
Date d = fmt.parse(row[1]);
if (!hasYear || ((d.equals(start) || d.after(start)) && (d.equals(end) || d.before(end)))) {
int month = d.getMonth();
if (month > 11) {
throw new RuntimeException("Month > 11 = " + month);
}
double data = Double.parseDouble(row[col]);
boolean periodValid = (periodMask == null) || (periodMask[month] == true);
boolean subDivideValid = (subDivideMask == null) || subDivideMask[sdindx++];
if (periodValid && subDivideValid) {
arr[month] = arr[month] + data;
count[month] = count[month] + 1;
}
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
for (int i = 0; i < 12; i++) {
arr[i] = (count[i] == 0) ? 0 : (arr[i] / count[i]);
//System.out.println("Arr["+i+"] = " + arr[i]);
}
return arr;
// break;
}
case ANNUAL_MEAN:
case PERIOD_MEAN:
case PERIOD_MIN:
case PERIOD_MAX:
case PERIOD_MEDIAN:
case PERIOD_STANDARD_DEVIATION: {
int previousMonth = -1;
int previousYear = -1;
int previousDay = -1;
boolean previousValid = false;
List l = new ArrayList();
List l_median = new ArrayList();
double sum = 0;
double sq_sum = 0;
int count = 0;
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
int sdindx = 0;
for (String[] row : t.rows()) {
try {
Date d = fmt.parse(row[1]);
if ((d.equals(start) || d.after(start)) && (d.equals(end) || d.before(end))) {
int month = d.getMonth();
int year = d.getYear();
int day = d.getDate();
double data = Double.parseDouble(row[col]);
boolean newYear = (month != previousMonth) && (month == startMonth);
boolean newEntry = (previousValid && newYear);
if (newEntry && count != 0) {
// Add new data to list
l.add(selectResult(timeStep, sum, sq_sum, count, min, max, l_median));
// Reset stats for new year
sum = 0;
sq_sum = 0;
count = 0;
min = Double.MAX_VALUE;
max = Double.MIN_VALUE;
l_median.clear();
}
boolean periodValid = (periodMask == null) || (periodMask[month] == true);
boolean subDivideValid = (subDivideMask == null) || subDivideMask[sdindx++];
if (periodValid && subDivideValid) {
sum += data;
sq_sum += (data * data);
count++;
if (data < min) {
min = data;
}
if (data > max) {
max = data;
}
l_median.add(data);
}
previousValid = true;
previousDay = day;
previousMonth = month;
previousYear = year;
}
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
if (count != 0) {
double selVal = selectResult(timeStep, sum, sq_sum, count, min, max, l_median);
l.add(selVal);
} // add the final entry which wasn't yet added
// since it never hit a newEntry.
// Copy the List to the output array.
double[] arr = new double[l.size()];
for (int i = 0; i < arr.length; i++) {
arr[i] = l.get(i);
}
return arr;
// break;
}
default: {
throw new IllegalArgumentException("timeStep " + timeStep + "not supported.");
}
}
}
private static double selectResult(int timeStep, double sum, double sq_sum, int count, double min, double max, List l) {
if ((timeStep == ANNUAL_MEAN) || (timeStep == PERIOD_MEAN)) {
return (count == 0) ? 0 : (sum / count);
} else if (timeStep == PERIOD_MIN) {
return min;
} else if (timeStep == PERIOD_MAX) {
return max;
} else if (timeStep == PERIOD_MEDIAN) {
//Copy to array of double
int lSize = l.size();
if (lSize == 0) {
throw new RuntimeException("No data in file matched the specified period ");
}
double[] arr = new double[lSize];
for (int i = 0; i < arr.length; i++) {
arr[i] = l.get(i);
}
// Sort the Array
Arrays.sort(arr);
double median;
// Pull out the Median
if (lSize % 2 == 1) {
median = arr[(lSize + 1) / 2 - 1];
} else {
double lower = arr[(lSize / 2) - 1];
double upper = arr[lSize / 2];
median = (lower + upper) / 2.0;
}
return median;
} else if (timeStep == PERIOD_STANDARD_DEVIATION) {
double mean = (count == 0) ? 0 : (sum / count);
double variance = sq_sum / count - (mean * mean);
double standardDeviation = Math.sqrt(variance);
return standardDeviation;
} else {
throw new RuntimeException("TimeStep " + timeStep + " not supported here.");
}
}
public static SimpleDateFormat lookupDateFormat(CSTable table, int col) {
if (col < 0 || col > table.getColumnCount()) {
throw new IllegalArgumentException("invalid column: " + col);
}
String format = table.getColumnInfo(col).get(KEY_FORMAT);
if (format == null) {
format = table.getInfo().get(DATE_FORMAT);
}
if (format == null) {
format = Conversions.ISO().toPattern();
}
return new SimpleDateFormat(format);
}
public static boolean DateFormatHasYear(CSTable table, int col) {
if (col < 0 || col > table.getColumnCount()) {
throw new IllegalArgumentException("invalid column: " + col);
}
String format = table.getColumnInfo(col).get(KEY_FORMAT);
if (format == null) {
format = table.getInfo().get(DATE_FORMAT);
}
if (format == null) {
format = Conversions.ISO().toPattern();
}
return (format.contains("YY") || format.contains("yy"));
}
public static int findRowByDate(Date date, int dateColumn, CSTable table) {
long start = System.currentTimeMillis();
String type = table.getColumnInfo(dateColumn).get(KEY_TYPE);
if ((type == null) || !type.equalsIgnoreCase(VAL_DATE)) {
throw new IllegalArgumentException("type " + type);
}
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(date);
String year = Integer.toString(cal.get(Calendar.YEAR));
DateFormat fmt = lookupDateFormat(table, dateColumn);
int rowNo = 0;
TableIterator rows = (TableIterator) table.rows().iterator();
while (rows.hasNext()) {
String[] row = rows.next();
try {
if (row[dateColumn].contains(year)) {
Date d = fmt.parse(row[dateColumn]);
if (d.equals(date)) {
long end = System.currentTimeMillis();
// System.out.println("Found row in " + (end - start) + " ms " + rowNo);
return rowNo;
}
}
rowNo++;
} catch (ParseException ex) {
throw new RuntimeException(ex);
}
}
rows.close();
// for (String[] row : table.rows()) {
// try {
// Date d = fmt.parse(row[dateColumn]);
// if (d.equals(date)) {
// return rowNo;
// }
// rowNo++;
// } catch (ParseException ex) {
// throw new RuntimeException(ex);
// }
// }
throw new IllegalArgumentException(date.toString());
}
/**
* Extract all rows from the table, inclusive startRow and endRow.
*
* @param table the src table
* @param startRow the first row to include
* @param endRow the second row to include.
* @return the rows.
*/
public static List extractRows(CSTable table, int startRow, int endRow) {
if (endRow <= startRow || startRow < 0 || endRow < 0) {
throw new IllegalArgumentException("invalid start/end Row : " + startRow + "/" + endRow);
}
List l = new ArrayList();
for (String[] r : table.rows(startRow)) {
l.add(r);
if (Integer.parseInt(r[0]) > endRow) {
break;
}
}
return l;
}
/**
* Get a slice of rows out of the table matching the time window
*
* @param table the src table
* @param timeCol the column index of the timestamps
* @param start the start date of the window
* @param end the end date of the window
* @return the first and last row that matches the time window {@literal start->end}
*/
public static int[] sliceByTime(CSTable table, int timeCol, Date start, Date end) {
if (end.before(start)) {
throw new IllegalArgumentException("end < start:" + end + " < " + start);
}
if (timeCol < 0) {
throw new IllegalArgumentException("timeCol :" + timeCol);
}
int s = -1;
int e = -1;
int i = -1;
SimpleDateFormat df = lookupDateFormat(table, timeCol);
for (String[] col : table.rows()) {
i++;
Date d = Conversions.convert(col[timeCol], Date.class, df);
if (s == -1 && (start.before(d) || start.equals(d))) {
s = i;
}
if (e == -1 && (end.before(d) || end.equals(d))) {
e = i;
break;
}
}
return new int[]{s, e};
}
/**
* Create a r/o data tablemodel
*
* @param src the src table
* @return a table model to the CSTable
*/
public static TableModel createTableModel(final CSTable src) {
final List rows = new ArrayList();
for (String[] row : src.rows()) {
rows.add(row);
}
return new TableModel() {
@Override
public int getColumnCount() {
return src.getColumnCount();
}
@Override
public String getColumnName(int column) {
return src.getColumnName(column);
}
@Override
public int getRowCount() {
return rows.size();
}
@Override
public Class> getColumnClass(int columnIndex) {
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rows.get(rowIndex)[columnIndex];
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// rows.get(rowIndex)[columnIndex] = (String) aValue;
}
@Override
public void addTableModelListener(TableModelListener l) {
}
@Override
public void removeTableModelListener(TableModelListener l) {
}
};
}
/**
* Get the KVP as table.
*
* @param p the src comma separated properties
* @return an AbstractTableModel for properties (KVP)
*/
public static AbstractTableModel getProperties(final CSProperties p) {
return new AbstractTableModel() {
@Override
public int getRowCount() {
return p.keySet().size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return " " + p.keySet().toArray()[rowIndex];
} else {
return p.values().toArray()[rowIndex];
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex == 1) {
String[] keys = p.keySet().toArray(new String[0]);
p.put(keys[rowIndex], aValue.toString());
}
}
@Override
public String getColumnName(int column) {
return column == 0 ? "Name" : "Value";
}
@Override
public Class> getColumnClass(int columnIndex) {
return String.class;
}
};
}
public static AbstractTableModel get2DBounded(final CSProperties p, final String pname) throws ParseException {
String m = p.getInfo(pname).get("bound");
String[] dims = m.split(",");
final int rows = DataIO.getInt(p, dims[0].trim());
final int cols = DataIO.getInt(p, dims[1].trim());
return new AbstractTableModel() {
@Override
public int getRowCount() {
return rows;
}
@Override
public int getColumnCount() {
return cols;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public Object getValueAt(int row, int col) {
String[][] d = Conversions.convert(p.get(pname), String[][].class);
return d[row][col].trim();
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
String[][] d = Conversions.convert(p.get(pname), String[][].class);
d[rowIndex][columnIndex] = aValue.toString().trim();
String s = toArrayString(d);
p.put(pname, s);
}
@Override
public String getColumnName(int column) {
return Integer.toString(column);
}
@Override
public Class> getColumnClass(int columnIndex) {
return String.class;
}
};
}
static public boolean playsRole(final CSProperties p, String key, String role) {
String r = p.getInfo(key).get("role");
if (r == null) {
return false;
}
return r.contains(role);
}
static public int getBound(final CSProperties p, String key) {
String bound = p.getInfo(key).get("bound");
if (bound == null) {
return 0;
}
StringTokenizer t = new StringTokenizer(bound, ",");
return t.countTokens();
}
static public boolean isBound(final CSProperties p, String key, int dim) {
String bound = p.getInfo(key).get("bound");
if (bound == null) {
return false;
}
StringTokenizer t = new StringTokenizer(bound, ",");
if (t.countTokens() == dim) {
return true;
}
return false;
}
/**
* Copy a property with meta data
*
* @param key the key to copy
* @param from the src comma separated properties
* @param to the dest comma separated properties
*/
public static void copyProperty(String key, CSProperties from, CSProperties to) {
Object v = from.get(key);
Map meta = from.getInfo(key);
to.put(key, v);
to.getInfo(key).putAll(meta);
}
// 1D arrays
public static AbstractTableModel getBoundProperties(final CSProperties p, String boundName) throws ParseException {
final int rows = DataIO.getInt(p, boundName);
final List arr = keysByMeta(p, "bound", boundName);
return new AbstractTableModel() {
@Override
public int getRowCount() {
return rows;
}
@Override
public int getColumnCount() {
return arr.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
String colname = arr.get(columnIndex);
String[] d = Conversions.convert(p.get(colname), String[].class);
return d[rowIndex].trim();
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
String colname = arr.get(columnIndex);
String[] d = Conversions.convert(p.get(colname), String[].class);
d[rowIndex] = aValue.toString().trim();
String s = toArrayString(d);
p.put(colname, s);
}
@Override
public String getColumnName(int column) {
String cname = arr.get(column);
return cname.substring(cname.indexOf('$') + 1);
}
@Override
public Class> getColumnClass(int columnIndex) {
return String.class;
}
};
}
// unbound
public static AbstractTableModel getUnBoundProperties(final CSProperties p) throws ParseException {
final List arr = keysByNotMeta(p, "bound");
return new AbstractTableModel() {
@Override
public int getRowCount() {
return arr.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return arr.get(rowIndex);
} else {
return p.get(arr.get(rowIndex));
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
p.put(arr.get(rowIndex), aValue.toString());
}
@Override
public String getColumnName(int column) {
return (column == 0) ? "Key" : "Value";
}
@Override
public Class> getColumnClass(int columnIndex) {
return String.class;
}
};
}
/**
* Create array string.
*
* @param arr the input array of strings
* @return an array String.
*/
public static String toArrayString(String[] arr) {
StringBuilder b = new StringBuilder();
b.append('{');
for (int i = 0; i < arr.length; i++) {
b.append(arr[i]);
if (i < arr.length - 1) {
b.append(',');
}
}
b.append('}');
return b.toString();
}
public static String toArrayString(String[][] arr) {
StringBuilder b = new StringBuilder();
b.append('{');
for (int i = 0; i < arr.length; i++) {
b.append('{');
for (int j = 0; j < arr[i].length; j++) {
b.append(arr[i][j]);
if (j < arr[i].length - 1) {
b.append(',');
}
}
b.append('}');
if (i < arr.length - 1) {
b.append(',');
}
}
b.append('}');
return b.toString();
}
/**
* Returns a r/o table from a CSP file
*
* @param p the src comma separated properties
* @param dim dimension of the comma separated properties
* @return a table model for properties with dimension.
*/
public static TableModel fromCSP(CSProperties p, final int dim) {
List dims = keysByMeta(p, "role", "dimension");
if (dims.isEmpty()) {
return null;
}
for (String d : dims) {
if (Integer.parseInt(p.get(d).toString()) == dim) {
final List bounds = keysByMeta(p, "bound", d);
final List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy