/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.display;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import org.opensourcephysics.controls.OSPLog;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControlElement;
import org.opensourcephysics.tools.Resource;
import org.opensourcephysics.tools.ResourceLoader;
public class DataFile extends DataAdapter {
java.util.List<Data> dataList = null;
protected static String[] delimiters = new String[] {" ", "\t", ",", ";"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
/**
* Creates a DataFile using data in the given file.
*
* @param fileName
*/
public DataFile(String fileName) {
super(null);
if(fileName!=null) {
open(fileName);
}
}
/**
* Some objects (eg, a Group) do not contain data, but a list of Data
* objects that do. This method is used by Data displaying tools to create
* as many pages as needed.
*
* @return a list of Data objects, or null if this object contains data
*/
public java.util.List<Data> getDataList() {
return dataList;
}
/**
* Opens an xml or data file specified by name.
*
* @param fileName the file name
* @return the file name, if successfully opened
*/
public String open(String fileName) {
dataList = null;
data = null;
OSPLog.fine("opening "+fileName); //$NON-NLS-1$
Resource res = ResourceLoader.getResource(fileName);
if(res!=null) {
Reader in = res.openReader();
String firstLine = readFirstLine(in);
// if xml, read the file into an XML control and add tab
if(firstLine.startsWith("<?xml")) { //$NON-NLS-1$
XMLControlElement control = new XMLControlElement(fileName);
dataList = control.getObjects(Data.class);
return fileName;
}
// if not xml, attempt to import data and add tab
else if(res.getString()!=null) {
data = parseData(res.getString(), fileName);
if(data!=null) {
return fileName;
}
}
}
OSPLog.finest("no data found"); //$NON-NLS-1$
return null;
}
/**
* Parses character-delimited data from a string. This attempts to extract
* the following information from the string:
*
* 1. A title to be used for the tab name 2. One or more columns of double
* data values 3. Column names for the data columns
*
* @param dataString the data string
* @param fileName name of file containing the data string (may be null)
* @return DatasetManager with parsed data, or null if none found
*/
public double[][] parseData(String dataString, String fileName) {
BufferedReader input = new BufferedReader(new StringReader(dataString));
String gnuPlotComment = "#"; //$NON-NLS-1$
try {
String textLine = input.readLine();
for(int i = 0; i<DataFile.delimiters.length; i++) {
ArrayList<double[]> rows = new ArrayList<double[]>();
int columns = Integer.MAX_VALUE;
String[] columnNames = null;
String title = null;
int lineCount = 0;
while(textLine!=null) { // process each line of text
// look for gnuPlot-commented name and/or columnNames
if(textLine.contains(gnuPlotComment)) {
textLine = textLine.trim();
}
if(textLine.startsWith(gnuPlotComment)) {
int k = textLine.indexOf("name:"); //$NON-NLS-1$
if(k>-1) {
title = textLine.substring(k+5).trim();
}
k = textLine.indexOf("columnNames:"); //$NON-NLS-1$
if(k>-1) {
textLine = textLine.substring(k+12).trim();
} else {
textLine = input.readLine();
continue;
}
}
// skip Vernier Format 2 header lines
if((textLine.indexOf("Vernier Format")>-1 //$NON-NLS-1$
)||(textLine.indexOf(".cmbl")>-1)) { //$NON-NLS-1$
textLine = input.readLine();
continue;
}
String[] strings = DataFile.parseStrings(textLine, DataFile.delimiters[i]);
double[] rowData = DataFile.parseDoubles(strings);
// set null title if String[] length > 0, all entries
// are NaN and only one entry is not ""
if(rows.isEmpty()&&(strings.length>0)&&(title==null)) {
String s = ""; //$NON-NLS-1$
for(int k = 0; k<strings.length; k++) {
if(Double.isNaN(rowData[k])&&!strings[k].equals("")) { //$NON-NLS-1$
if(s.equals("")) { //$NON-NLS-1$
s = strings[k];
} else {
s = ""; //$NON-NLS-1$
break;
}
}
}
if(!s.equals("")) { //$NON-NLS-1$
title = s;
textLine = input.readLine();
continue;
}
}
// set null column names if String[] length > 0,
// all entries are NaN and none is ""
if(rows.isEmpty()&&(strings.length>0)&&(columnNames==null)) {
boolean valid = true;
for(int k = 0; k<strings.length; k++) {
if(!Double.isNaN(rowData[k])||strings[k].equals("")) { //$NON-NLS-1$
valid = false;
break;
}
}
if(valid) {
columnNames = strings;
textLine = input.readLine();
continue;
}
}
// add double[] of length 1 or longer to rows
if(strings.length>0) {
lineCount++;
boolean validData = true;
boolean emptyData = true;
for(int k = 0; k<strings.length; k++) {
// invalid if any NaN entries other than ""
if(Double.isNaN(rowData[k])&&!strings[k].equals("")) { //$NON-NLS-1$
validData = false;
}
// look for empty row--every entry is ""
if(!strings[k].equals("")) { //$NON-NLS-1$
emptyData = false;
}
}
if(rows.isEmpty()&&emptyData) {
validData = false;
}
// add valid data, but ignore blank lines
// that may precede real data
if(validData) {
rows.add(rowData);
columns = Math.min(rowData.length, columns);
}
}
// abort processing if no data found in first several lines
if(rows.isEmpty()&&(lineCount>10)) {
break;
}
textLine = input.readLine();
}
// create array if data found
if(!rows.isEmpty()&&(columns>0)) {
input.close();
// first reassemble data from rows into columns
double[][] dataArray = new double[columns][rows.size()];
for(int row = 0; row<rows.size(); row++) {
double[] next = rows.get(row);
for(int j = 0; j<columns; j++) {
dataArray[j][row] = next[j];
}
}
setName((title==null) ? XML.getName(fileName) : title);
this.setColumnNames(columnNames);
OSPLog.finest("data found using delimiter \"" //$NON-NLS-1$
+DataFile.delimiters[i]+"\""); //$NON-NLS-1$
return dataArray;
}
// close the reader and open a new one
input.close();
input = new BufferedReader(new StringReader(dataString));
textLine = input.readLine();
}
} catch(IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch(IOException ex) {
ex.printStackTrace();
}
return null;
}
// ______________________________ protected methods ________________________
protected String readFirstLine(Reader in) {
BufferedReader input = null;
if(in instanceof BufferedReader) {
input = (BufferedReader) in;
} else {
input = new BufferedReader(in);
}
String openingLine;
try {
openingLine = input.readLine();
while((openingLine==null)||openingLine.equals("")) { //$NON-NLS-1$
openingLine = input.readLine();
}
} catch(IOException e) {
e.printStackTrace();
return null;
}
try {
input.close();
} catch(IOException ex) {
ex.printStackTrace();
}
return openingLine;
}
/**
* Parses a String into tokens separated by a specified delimiter. A token
* may be "".
*
* @param text the text to parse
* @param delimiter the delimiter
* @return an array of String tokens
*/
protected static String[] parseStrings(String text, String delimiter) {
Collection<String> tokens = new ArrayList<String>();
if(text!=null) {
// get the first token
String next = text;
int i = text.indexOf(delimiter);
if(i==-1) { // no delimiter
tokens.add(stripQuotes(next));
text = null;
} else {
next = text.substring(0, i);
text = text.substring(i+1);
}
// iterate thru the tokens and add to token list
while(text!=null) {
tokens.add(stripQuotes(next));
i = text.indexOf(delimiter);
if(i==-1) { // no delimiter
next = text;
tokens.add(stripQuotes(next));
text = null;
} else {
next = text.substring(0, i).trim();
text = text.substring(i+1);
}
}
}
return tokens.toArray(new String[0]);
}
/**
* Parses a String into tokens separated by specified row and column
* delimiters.
*
* @param text the text to parse
* @param rowDelimiter the column delimiter
* @param colDelimiter the column delimiter
* @return a 2D array of String tokens
*/
protected static String[][] parseStrings(String text, String rowDelimiter, String colDelimiter) {
String[] rows = parseStrings(text, rowDelimiter);
String[][] tokens = new String[rows.length][0];
for(int i = 0; i<rows.length; i++) {
tokens[i] = parseStrings(rows[i], colDelimiter);
}
return tokens;
}
/**
* Parses a String array into doubles. Unparsable strings are set to
* Double.NaN.
*
* @param strings the String array to parse
* @return an array of doubles
*/
protected static double[] parseDoubles(String[] strings) {
double[] doubles = new double[strings.length];
for(int i = 0; i<strings.length; i++) {
if(strings[i].indexOf("\t")>-1) { //$NON-NLS-1$
doubles[i] = Double.NaN;
} else {
try {
doubles[i] = Double.parseDouble(strings[i]);
} catch(NumberFormatException e) {
doubles[i] = Double.NaN;
}
}
}
return doubles;
}
/**
* Parses a String into doubles separated by specified row and column
* delimiters.
*
* @param text the text to parse
* @param rowDelimiter the column delimiter
* @param colDelimiter the column delimiter
* @return a 2D array of doubles
*/
protected static double[][] parseDoubles(String text, String rowDelimiter, String colDelimiter) {
String[][] strings = parseStrings(text, rowDelimiter, colDelimiter);
double[][] doubles = new double[strings.length][0];
for(int i = 0; i<strings.length; i++) {
double[] row = new double[strings[i].length];
for(int j = 0; j<row.length; j++) {
try {
row[j] = Double.parseDouble(strings[i][j]);
} catch(NumberFormatException e) {
row[j] = Double.NaN;
}
}
doubles[i] = row;
}
return doubles;
}
/**
* Strips quotation marks around a string.
*
* @param text the text to strip
* @return the stripped string
*/
private static String stripQuotes(String text) {
if(text.startsWith("\"")) { //$NON-NLS-1$
String stripped = text.substring(1);
int n = stripped.indexOf("\""); //$NON-NLS-1$
if(n==stripped.length()-1) {
return stripped.substring(0, n);
}
}
return text;
}
/**
* Returns an array of row numbers.
*
* @param rowCount length of the array
* @return the array
*/
protected static double[] getRowArray(int rowCount) {
double[] rows = new double[rowCount];
for(int i = 0; i<rowCount; i++) {
rows[i] = i;
}
return rows;
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/