-/*******************************************************************************\r
- * Copyright (c) 2011 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.history.csv;\r
-\r
-import java.io.IOException;\r
-import java.io.UnsupportedEncodingException;\r
-import java.math.BigDecimal;\r
-import java.net.URLDecoder;\r
-import java.text.DecimalFormat;\r
-import java.text.DecimalFormatSymbols;\r
-import java.text.Format;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.Locale;\r
-\r
-import org.simantics.databoard.accessor.StreamAccessor;\r
-import org.simantics.databoard.accessor.error.AccessorException;\r
-import org.simantics.history.HistoryException;\r
-import org.simantics.history.HistoryManager;\r
-import org.simantics.history.util.HistoryExportUtil;\r
-import org.simantics.history.util.ProgressMonitor;\r
-import org.simantics.history.util.StreamIterator;\r
-import org.simantics.history.util.ValueBand;\r
-\r
-/**\r
- * CSV writer for history items.\r
- * \r
- * @author Toni Kalajainen\r
- * @author Tuukka Lehtonen\r
- */\r
-public class CSVFormatter {\r
-\r
- List<Item> items = new ArrayList<Item>();\r
- double from = -Double.MAX_VALUE;\r
- double end = Double.MAX_VALUE;\r
- double startTime = 0.0;\r
- double timeStep = 0.0;\r
- ColumnSeparator columnSeparator;\r
- DecimalSeparator decimalSeparator;\r
- boolean resample;\r
- String lineFeed;\r
- Locale locale;\r
-\r
- Format timeFormat;\r
- Format floatFormat;\r
- Format numberFormat;\r
-\r
- Formatter timeFormatter;\r
- Formatter floatFormatter;\r
- Formatter numberFormatter;\r
-\r
- ExportInterpolation numberInterpolation = ExportInterpolation.LINEAR_INTERPOLATION;\r
-\r
- public CSVFormatter() {\r
- this.lineFeed = resolvePlatformLineFeed();\r
- this.locale = Locale.getDefault(Locale.Category.FORMAT);\r
-\r
- DecimalFormat defaultFormat = new DecimalFormat();\r
- defaultFormat.setGroupingUsed(false);\r
- setTimeFormat(defaultFormat);\r
- setFloatFormat(defaultFormat);\r
- setNumberFormat(defaultFormat);\r
- }\r
-\r
- /**\r
- * Add item to formatter\r
- * \r
- * @param historyItemId\r
- * @param label\r
- * @param variableReference\r
- * @param unit\r
- */\r
- public void addItem( HistoryManager history, String historyItemId, String label, String variableReference, String unit ) {\r
- Item i = new Item();\r
- i.history = history;\r
- i.label = label!=null?label:"";\r
- i.variableReference = variableReference!=null?variableReference:"";\r
- i.variableReference = unescape(i.variableReference);\r
- i.historyItemId = historyItemId;\r
- i.unit = unit;\r
- if ( !items.contains(i) ) items.add( i );\r
- }\r
-\r
- private static String unescape(String url) {\r
- try {\r
- return URLDecoder.decode(url, "UTF-8");\r
- } catch (UnsupportedEncodingException e) {\r
- return url;\r
- }\r
- }\r
-\r
- /**\r
- * Sort items by variableId, label1, label2\r
- */\r
- public void sort() {\r
- Collections.sort(items);\r
- }\r
- \r
- public void setTimeRange( double from, double end ) {\r
- this.from = from;\r
- this.end = end;\r
- }\r
- \r
- public void setStartTime( double startTime ) {\r
- this.startTime = startTime; \r
- }\r
-\r
- public void setTimeStep( double timeStep ) {\r
- this.timeStep = timeStep; \r
- }\r
- \r
- void openHistory() throws HistoryException {\r
- try {\r
- for (Item item : items) item.open();\r
- } catch (HistoryException e) {\r
- for (Item item : items) item.close();\r
- throw e;\r
- }\r
- }\r
- \r
- void closeHistory() {\r
- for (Item item : items) item.close();\r
- }\r
- \r
- /**\r
- * Reads visible data of all variables and formats as CSV lines (Comma \r
- * Separated Values). Line Feed is \n, variable separator is \t, and\r
- * decimal separator locale dependent.\r
- * \r
- * ReadData1 outputs separate time and value columns for each variable\r
- * \r
- * Variable1 Time | Variable1 Value | Variable2 Time | Variable2 Value\r
- * 0.0 | 1.0 | 0.1 | 23423.0\r
- * \r
- * @param monitor\r
- * @param sb\r
- * @throws HistoryException \r
- * @throws IOException \r
- */\r
- /*\r
- public void formulate1( ProgressMonitor monitor, Appendable sb ) throws HistoryException, IOException {\r
- if (items.isEmpty()) return;\r
- boolean adaptComma = decimalSeparatorInLocale != decimalSeparator;\r
- openHistory();\r
- try {\r
- // Prepare columns: First time\r
- for (Item item : items)\r
- {\r
- if (monitor.isCanceled())\r
- return;\r
- if (item.stream.isEmpty()) continue;\r
- Double firstTime = (Double) item.stream.getFirstTime( Bindings.DOUBLE );\r
- if (from <= firstTime) {\r
- item.time = firstTime;\r
- } else {\r
- item.time = (Double) item.stream.getFloorTime(Bindings.DOUBLE, from);\r
- }\r
- } \r
- \r
- // Write Headers\r
- for (Item i : items)\r
- {\r
- if (monitor.isCanceled())\r
- return;\r
- boolean lastColumn = i == items.get( items.size()-1 );\r
- sb.append(i.label + " Time");\r
- sb.append( columnSeparator );\r
- sb.append(i.label + " Value");\r
- sb.append(lastColumn ? lineFeed : columnSeparator );\r
- }\r
- \r
- // Iterate until endTime is met for all variables\r
- int readyColumns;\r
- do {\r
- if (monitor.isCanceled())\r
- return;\r
-\r
- readyColumns = 0;\r
- for (Item i : items)\r
- {\r
- boolean lastColumn = i == items.get( items.size()-1 );\r
- \r
- if (i.time == null || i.time > end) {\r
- readyColumns++;\r
- sb.append( lastColumn ? columnSeparator+lineFeed : columnSeparator+columnSeparator);\r
- continue;\r
- }\r
- \r
- sb.append("");\r
- \r
- // Write time\r
- String timeStr = format.format( i.time );\r
- if ( adaptComma ) timeStr = timeStr.replace(decimalSeparatorInLocale, decimalSeparator);\r
- sb.append( timeStr ); \r
- sb.append( columnSeparator );\r
- \r
- // Write value \r
- i.value = i.stream.getValue(Bindings.DOUBLE, i.time);\r
- if (i.value instanceof Number) {\r
- String str = format.format( i.value );\r
- if ( adaptComma ) str = str.replace(decimalSeparatorInLocale, decimalSeparator);\r
- sb.append( str );\r
- } else if (i.value instanceof Boolean) {\r
- sb.append( (Boolean)i.value ? "1": "0");\r
- } else {\r
- sb.append( i.value.toString() );\r
- }\r
- sb.append(lastColumn ? lineFeed : columnSeparator);\r
- \r
- // Get next time\r
- i.time = (Double) i.stream.getHigherTime(Bindings.DOUBLE, i.time);\r
- }\r
- \r
- } while (readyColumns < items.size());\r
- } finally {\r
- closeHistory();\r
- }\r
- }*/\r
- \r
- /**\r
- * Reads visible data of all variables and formats as CSV lines (Comma \r
- * Separated Values). Line Feed is \n, variable separator is \t, and\r
- * decimal separator locale dependent.\r
- * \r
- * ReadData2 outputs one shared time and one value column for each variable\r
- * \r
- * Time | Variable1 Label | Variable3 Label\r
- * | Variable1 Id | Variable3 Id\r
- * | Variable1 Unit | Variable3 Unit\r
- * 0.0 | 1.0 | 0.1\r
- * \r
- * @param monitor\r
- * @param sb\r
- * @throws HistoryException \r
- * @throws IOException \r
- */\r
- public void formulate2( ProgressMonitor monitor, Appendable sb ) throws HistoryException, IOException\r
- {\r
- if ( items.isEmpty() ) return;\r
-\r
- timeFormatter = evaluateFormatter(timeFormat, decimalSeparator);\r
- floatFormatter = evaluateFormatter(floatFormat, decimalSeparator);\r
- numberFormatter = evaluateFormatter(numberFormat, decimalSeparator);\r
-\r
- openHistory();\r
- try {\r
- // What is the time range of all items combined\r
- double allFrom = Double.MAX_VALUE;\r
- double allEnd = -Double.MAX_VALUE;\r
- for (Item i : items) {\r
- if (i.iter.isEmpty()) continue;\r
- allFrom = Math.min(allFrom, i.iter.getFirstTime());\r
- allEnd = Math.max(allEnd, i.iter.getLastTime());\r
- }\r
- \r
- // Write Headers\r
- for (int hl = 0; hl < 3; ++hl) {\r
- switch(hl) {\r
- case 0: sb.append("Time"); break;\r
- case 1: sb.append("----"); break;\r
- case 2: sb.append("Unit"); break;\r
- }\r
- sb.append( columnSeparator.preference );\r
- for (Item i : items)\r
- {\r
- boolean lastColumn = i == items.get( items.size()-1 );\r
- switch (hl) {\r
- case 0:\r
- sb.append(i.label != null ? i.label : "");\r
- break;\r
- case 1:\r
- sb.append(i.variableReference != null ? i.variableReference : "");\r
- break;\r
- case 2:\r
- sb.append(i.unit==null?"no unit":i.unit);\r
- break;\r
- }\r
- if (!lastColumn) sb.append( columnSeparator.preference );\r
- }\r
- sb.append( lineFeed );\r
- }\r
- \r
- // Prepare time \r
- boolean hasAnyValues = allFrom != Double.MAX_VALUE && allEnd != -Double.MAX_VALUE;\r
- \r
- // Make intersection of actual data range (allFrom, allEnd) and requested data (from, end)\r
- double _from = Double.MAX_VALUE, _end = -Double.MAX_VALUE; \r
- if (hasAnyValues) {\r
- _from = Math.max(allFrom, from);\r
- _end = Math.min(allEnd, end);\r
- }\r
-\r
- if (!hasAnyValues) return;\r
-\r
- // Iterate until endTime is met for all variables\r
- double time = _from;\r
-\r
- if(!resample) {\r
- \r
- // If resample is false then all samples are reported as is. The code achieves this by setting startTime to _from and timeStep to 0.0 \r
- time = _from;\r
- timeStep = 0.0;\r
- \r
- } else {\r
-\r
- // time = startTime + n*timeStep \r
- \r
- // Sampling based on given startTime and timeStep\r
- if(timeStep > 0) {\r
-\r
- // Find the first sample time that contains data \r
- double n = Math.max(0, Math.ceil((_from-startTime) / timeStep));\r
- time = startTime + n*timeStep;\r
-\r
- } else {\r
- \r
- // Start sampling from startTime but make sure that it is not less than _from\r
- if(startTime > _from) time = startTime;\r
- \r
- }\r
- \r
-\r
- }\r
- \r
- // Must convert double times to String when initializing BigDecimal.\r
- // Otherwise BigDecimal will pick up inaccuracies from beyond 15 precise digits\r
- // thus making a mess of the time step calculations.\r
-\r
- BigDecimal bigTime = new BigDecimal(String.valueOf(time));\r
- BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));\r
-\r
- for (Item i : items) i.iter.gotoTime(time);\r
- do {\r
- if ( monitor!=null && monitor.isCanceled() ) return;\r
-\r
- // Write time\r
- String timeStr = timeFormatter.format( time );\r
- //System.out.println("SAMPLING TIME: " + time);\r
- sb.append( timeStr );\r
-\r
- // Write values\r
- for (Item i : items)\r
- {\r
- sb.append( columnSeparator.preference );\r
- \r
- // Write value\r
- if ( i.iter.hasValidValue() ) {\r
- Object value = i.iter.getValueBand().getValue();\r
- if (value instanceof Number) {\r
- if (value instanceof Float || value instanceof Double) {\r
- switch (numberInterpolation) {\r
- case PREVIOUS_SAMPLE:\r
- sb.append( formatNumber(value) );\r
- break;\r
-\r
- case LINEAR_INTERPOLATION:\r
- if (time != i.iter.getValueBand().getTimeDouble() && i.iter.hasNext()) {\r
- \r
- // Interpolate\r
- int currentIndex = i.iter.getIndex();\r
- ValueBand band = i.iter.getValueBand();\r
- //double t1 = band.getTimeDouble();\r
- Number v1 = (Number) value;\r
- double t12 = band.getEndTimeDouble();\r
- i.iter.next();\r
- double t2 = i.iter.getValueBand().getTimeDouble();\r
- Number v2 = (Number) i.iter.getValueBand().getValue();\r
- i.iter.gotoIndex(currentIndex);\r
-\r
- double vs = v1.doubleValue();\r
- if(time > t12)\r
- vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time);\r
-\r
- sb.append( formatDouble(vs) );\r
- } else {\r
- // Exact timestamp match, or last sample.\r
- // Don't interpolate nor extrapolate.\r
- sb.append( formatNumber(value) );\r
- }\r
- break;\r
- default:\r
- throw new UnsupportedOperationException("Unsupported interpolation: " + numberInterpolation);\r
- }\r
- } else {\r
- sb.append( value.toString() );\r
- }\r
- } else if (value instanceof Boolean) {\r
- sb.append( (Boolean)value ? "1": "0");\r
- } else {\r
- sb.append( value.toString() );\r
- }\r
- }\r
- }\r
-\r
- sb.append( lineFeed );\r
-\r
- // Read next values, and the following times\r
- if ( timeStep>0.0 ) {\r
- bigTime = bigTime.add(bigTimeStep);\r
- time = bigTime.doubleValue();\r
- } else {\r
- // Get smallest end time that is larger than current time\r
- Double nextTime = null;\r
-// System.out.println("time = "+time);\r
- for (Item i : items) {\r
- Double itemNextTime = i.iter.getNextTime( time );\r
-// System.err.println(" "+i.label+" nextTime="+itemNextTime);\r
- if ( itemNextTime == null ) continue;\r
- if ( itemNextTime < time ) continue;\r
- if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime; \r
- }\r
- if ( nextTime == null || nextTime.equals( time ) ) break;\r
- time = nextTime;\r
- }\r
-\r
- boolean hasMore = false;\r
- \r
- for (Item i : items) {\r
- i.iter.proceedToTime(time);\r
- if(contains(i, time)) hasMore = true;\r
- }\r
- \r
- if(!hasMore) break;\r
- \r
- } while (time<=_end);\r
- } finally {\r
- closeHistory();\r
- }\r
- }\r
-\r
- private boolean contains(Item item, double time) {\r
- double start = item.iter.getStartTime();\r
- double end = item.iter.getEndTime();\r
- // A special case, where start == end => accept\r
- if(time == start) return true;\r
- else if(time < start) return false;\r
- else if(time >= end) return false;\r
- else return true;\r
- }\r
-\r
- private CharSequence formatNumber(Object value) {\r
- return value instanceof Float\r
- ? floatFormatter.format( value )\r
- : numberFormatter.format( value );\r
- }\r
-\r
- private CharSequence formatDouble(double value) {\r
- return numberFormatter.format( value );\r
- }\r
-\r
- public void setDecimalSeparator(DecimalSeparator separator) {\r
- this.decimalSeparator = separator;\r
- }\r
-\r
- public void setColumnSeparator(ColumnSeparator separator) {\r
- this.columnSeparator = separator;\r
- }\r
- \r
- public void setResample(boolean resample) {\r
- this.resample = resample;\r
- }\r
- \r
- public void setLineFeed( String lf ) {\r
- this.lineFeed = lf;\r
- }\r
-\r
- public void setTimeFormat(Format format) {\r
- this.timeFormat = format;\r
- }\r
-\r
- public void setFloatFormat(Format format) {\r
- this.floatFormat = format;\r
- }\r
-\r
- public void setNumberFormat(Format format) {\r
- this.numberFormat = format;\r
- }\r
-\r
- public void setLocale(Locale locale) {\r
- this.locale = locale;\r
- }\r
-\r
- public void setNumberInterpolation(ExportInterpolation interpolation) {\r
- this.numberInterpolation = interpolation;\r
- }\r
-\r
- private static String resolvePlatformLineFeed() {\r
- String osName = System.getProperty("os.name", "");\r
- osName = osName.toLowerCase();\r
- if (osName.contains("windows"))\r
- return "\r\n";\r
- return "\n";\r
- }\r
-\r
- private class Item implements Comparable<Item> {\r
- // Static data\r
- String label; // Label\r
- String variableReference; // Label\r
- HistoryManager history; // History source for this item\r
- String historyItemId;\r
- String unit;\r
-\r
- // State data\r
- StreamAccessor accessor; // Stream accessor\r
- StreamIterator iter;\r
- \r
- public void open() throws HistoryException {\r
- accessor = history.openStream(historyItemId, "r");\r
- iter = new StreamIterator( accessor );\r
- }\r
- \r
- public void close() {\r
- if (accessor!=null) {\r
- try {\r
- accessor.close();\r
- } catch (AccessorException e) {\r
- }\r
- }\r
- accessor = null;\r
- iter = null;\r
- }\r
-\r
- @Override\r
- public int compareTo(Item o) {\r
- int i;\r
- i = label.compareTo(o.label);\r
- if (i!=0) return i;\r
- i = variableReference.compareTo(o.variableReference);\r
- if (i!=0) return i;\r
- i = historyItemId.compareTo(o.historyItemId); \r
- if (i!=0) return i;\r
- return 0;\r
- }\r
- \r
- @Override\r
- public int hashCode() {\r
- int code = 0x2304;\r
- code = 13*code + variableReference.hashCode();\r
- code = 13*code + label.hashCode();\r
- code = 13*code + historyItemId.hashCode();\r
- code = 13*code + history.hashCode(); \r
- return code;\r
- }\r
- \r
- @Override\r
- public boolean equals(Object obj) {\r
- if ( obj == null ) return false;\r
- if ( obj instanceof Item == false ) return false;\r
- Item other = (Item) obj; \r
- if ( !other.label.equals(label) ) return false;\r
- if ( !other.variableReference.equals(variableReference) ) return false;\r
- if ( !other.history.equals(history) ) return false;\r
- if ( !other.historyItemId.equals(historyItemId) ) return false;\r
- return true;\r
- }\r
- \r
- }\r
-\r
- static interface Formatter {\r
- String format(Object number);\r
- }\r
-\r
- static class NopFormatter implements Formatter {\r
- private final Format format;\r
- public NopFormatter(Format format) {\r
- this.format = format;\r
- }\r
- public String format(Object number) {\r
- return format.format(number);\r
- }\r
- }\r
-\r
- static class ReplacingFormatter implements Formatter {\r
- private final Format format;\r
- private final char from;\r
- private final char to;\r
- public ReplacingFormatter(Format format, char from, char to) {\r
- this.format = format;\r
- this.from = from;\r
- this.to = to;\r
- }\r
- public String format(Object number) {\r
- return format.format(number).replace(from, to);\r
- }\r
- }\r
-\r
- private Formatter evaluateFormatter(Format format, DecimalSeparator target) {\r
- // Probe decimal separator\r
- String onePointTwo = format.format(1.2);\r
- System.out.println("formatted zeroPointOne: " + onePointTwo);\r
-\r
- DecimalSeparator formatSeparator;\r
- if (onePointTwo.indexOf('.') != -1) {\r
- formatSeparator = DecimalSeparator.DOT;\r
- } else if (onePointTwo.indexOf(',') != -1) {\r
- formatSeparator = DecimalSeparator.COMMA;\r
- } else {\r
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);\r
- formatSeparator = DecimalSeparator.fromChar(symbols.getDecimalSeparator());\r
- }\r
-\r
- switch (formatSeparator) {\r
- case COMMA:\r
- switch (target) {\r
- case COMMA:\r
- return new NopFormatter(format);\r
- case DOT:\r
- return new ReplacingFormatter(format, ',', '.');\r
- }\r
- case DOT:\r
- switch (target) {\r
- case COMMA:\r
- return new ReplacingFormatter(format, '.', ',');\r
- case DOT:\r
- return new NopFormatter(format);\r
- }\r
- }\r
- return new NopFormatter(format);\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2011 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.history.csv;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.Format;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.simantics.databoard.accessor.StreamAccessor;
+import org.simantics.databoard.accessor.error.AccessorException;
+import org.simantics.history.HistoryException;
+import org.simantics.history.HistoryManager;
+import org.simantics.history.util.HistoryExportUtil;
+import org.simantics.history.util.ProgressMonitor;
+import org.simantics.history.util.StreamIterator;
+import org.simantics.history.util.ValueBand;
+
+/**
+ * CSV writer for history items.
+ *
+ * @author Toni Kalajainen
+ * @author Tuukka Lehtonen
+ */
+public class CSVFormatter {
+
+ List<Item> items = new ArrayList<Item>();
+ double from = -Double.MAX_VALUE;
+ double end = Double.MAX_VALUE;
+ double startTime = 0.0;
+ double timeStep = 0.0;
+ ColumnSeparator columnSeparator;
+ DecimalSeparator decimalSeparator;
+ boolean resample;
+ String lineFeed;
+ Locale locale;
+
+ Format timeFormat;
+ Format floatFormat;
+ Format numberFormat;
+
+ Formatter timeFormatter;
+ Formatter floatFormatter;
+ Formatter numberFormatter;
+
+ ExportInterpolation numberInterpolation = ExportInterpolation.LINEAR_INTERPOLATION;
+
+ public CSVFormatter() {
+ this.lineFeed = resolvePlatformLineFeed();
+ this.locale = Locale.getDefault(Locale.Category.FORMAT);
+
+ DecimalFormat defaultFormat = new DecimalFormat();
+ defaultFormat.setGroupingUsed(false);
+ setTimeFormat(defaultFormat);
+ setFloatFormat(defaultFormat);
+ setNumberFormat(defaultFormat);
+ }
+
+ /**
+ * Add item to formatter
+ *
+ * @param historyItemId
+ * @param label
+ * @param variableReference
+ * @param unit
+ */
+ public void addItem( HistoryManager history, String historyItemId, String label, String variableReference, String unit ) {
+ Item i = new Item();
+ i.history = history;
+ i.label = label!=null?label:"";
+ i.variableReference = variableReference!=null?variableReference:"";
+ i.variableReference = URIs.safeUnescape(i.variableReference);
+ i.historyItemId = historyItemId;
+ i.unit = unit;
+ if ( !items.contains(i) ) items.add( i );
+ }
+
+ /**
+ * Sort items by variableId, label1, label2
+ */
+ public void sort() {
+ Collections.sort(items);
+ }
+
+ public void setTimeRange( double from, double end ) {
+ this.from = from;
+ this.end = end;
+ }
+
+ public void setStartTime( double startTime ) {
+ this.startTime = startTime;
+ }
+
+ public void setTimeStep( double timeStep ) {
+ this.timeStep = timeStep;
+ }
+
+ void openHistory() throws HistoryException {
+ try {
+ for (Item item : items) item.open();
+ } catch (HistoryException e) {
+ for (Item item : items) item.close();
+ throw e;
+ }
+ }
+
+ void closeHistory() {
+ for (Item item : items) item.close();
+ }
+
+ /**
+ * Reads visible data of all variables and formats as CSV lines (Comma
+ * Separated Values). Line Feed is \n, variable separator is \t, and
+ * decimal separator locale dependent.
+ *
+ * ReadData1 outputs separate time and value columns for each variable
+ *
+ * Variable1 Time | Variable1 Value | Variable2 Time | Variable2 Value
+ * 0.0 | 1.0 | 0.1 | 23423.0
+ *
+ * @param monitor
+ * @param sb
+ * @throws HistoryException
+ * @throws IOException
+ */
+ /*
+ public void formulate1( ProgressMonitor monitor, Appendable sb ) throws HistoryException, IOException {
+ if (items.isEmpty()) return;
+ boolean adaptComma = decimalSeparatorInLocale != decimalSeparator;
+ openHistory();
+ try {
+ // Prepare columns: First time
+ for (Item item : items)
+ {
+ if (monitor.isCanceled())
+ return;
+ if (item.stream.isEmpty()) continue;
+ Double firstTime = (Double) item.stream.getFirstTime( Bindings.DOUBLE );
+ if (from <= firstTime) {
+ item.time = firstTime;
+ } else {
+ item.time = (Double) item.stream.getFloorTime(Bindings.DOUBLE, from);
+ }
+ }
+
+ // Write Headers
+ for (Item i : items)
+ {
+ if (monitor.isCanceled())
+ return;
+ boolean lastColumn = i == items.get( items.size()-1 );
+ sb.append(i.label + " Time");
+ sb.append( columnSeparator );
+ sb.append(i.label + " Value");
+ sb.append(lastColumn ? lineFeed : columnSeparator );
+ }
+
+ // Iterate until endTime is met for all variables
+ int readyColumns;
+ do {
+ if (monitor.isCanceled())
+ return;
+
+ readyColumns = 0;
+ for (Item i : items)
+ {
+ boolean lastColumn = i == items.get( items.size()-1 );
+
+ if (i.time == null || i.time > end) {
+ readyColumns++;
+ sb.append( lastColumn ? columnSeparator+lineFeed : columnSeparator+columnSeparator);
+ continue;
+ }
+
+ sb.append("");
+
+ // Write time
+ String timeStr = format.format( i.time );
+ if ( adaptComma ) timeStr = timeStr.replace(decimalSeparatorInLocale, decimalSeparator);
+ sb.append( timeStr );
+ sb.append( columnSeparator );
+
+ // Write value
+ i.value = i.stream.getValue(Bindings.DOUBLE, i.time);
+ if (i.value instanceof Number) {
+ String str = format.format( i.value );
+ if ( adaptComma ) str = str.replace(decimalSeparatorInLocale, decimalSeparator);
+ sb.append( str );
+ } else if (i.value instanceof Boolean) {
+ sb.append( (Boolean)i.value ? "1": "0");
+ } else {
+ sb.append( i.value.toString() );
+ }
+ sb.append(lastColumn ? lineFeed : columnSeparator);
+
+ // Get next time
+ i.time = (Double) i.stream.getHigherTime(Bindings.DOUBLE, i.time);
+ }
+
+ } while (readyColumns < items.size());
+ } finally {
+ closeHistory();
+ }
+ }*/
+
+ /**
+ * Reads visible data of all variables and formats as CSV lines (Comma
+ * Separated Values). Line Feed is \n, variable separator is \t, and
+ * decimal separator locale dependent.
+ *
+ * ReadData2 outputs one shared time and one value column for each variable
+ *
+ * Time | Variable1 Label | Variable3 Label
+ * | Variable1 Id | Variable3 Id
+ * | Variable1 Unit | Variable3 Unit
+ * 0.0 | 1.0 | 0.1
+ *
+ * @param monitor
+ * @param sb
+ * @throws HistoryException
+ * @throws IOException
+ */
+ public void formulate2( ProgressMonitor monitor, Appendable sb ) throws HistoryException, IOException
+ {
+ if ( items.isEmpty() ) return;
+
+ timeFormatter = evaluateFormatter(timeFormat, decimalSeparator);
+ floatFormatter = evaluateFormatter(floatFormat, decimalSeparator);
+ numberFormatter = evaluateFormatter(numberFormat, decimalSeparator);
+
+ openHistory();
+ try {
+ // What is the time range of all items combined
+ double allFrom = Double.MAX_VALUE;
+ double allEnd = -Double.MAX_VALUE;
+ for (Item i : items) {
+ if (i.iter.isEmpty()) continue;
+ allFrom = Math.min(allFrom, i.iter.getFirstTime());
+ allEnd = Math.max(allEnd, i.iter.getLastTime());
+ }
+
+ // Write Headers
+ for (int hl = 0; hl < 3; ++hl) {
+ switch(hl) {
+ case 0: sb.append("Time"); break;
+ case 1: sb.append("----"); break;
+ case 2: sb.append("Unit"); break;
+ }
+ sb.append( columnSeparator.preference );
+ for (Item i : items)
+ {
+ boolean lastColumn = i == items.get( items.size()-1 );
+ switch (hl) {
+ case 0:
+ sb.append(i.label != null ? i.label : "");
+ break;
+ case 1:
+ sb.append(i.variableReference != null ? i.variableReference : "");
+ break;
+ case 2:
+ sb.append(i.unit==null?"no unit":i.unit);
+ break;
+ }
+ if (!lastColumn) sb.append( columnSeparator.preference );
+ }
+ sb.append( lineFeed );
+ }
+
+ // Prepare time
+ boolean hasAnyValues = allFrom != Double.MAX_VALUE && allEnd != -Double.MAX_VALUE;
+
+ // Make intersection of actual data range (allFrom, allEnd) and requested data (from, end)
+ double _from = Double.MAX_VALUE, _end = -Double.MAX_VALUE;
+ if (hasAnyValues) {
+ _from = Math.max(allFrom, from);
+ _end = Math.min(allEnd, end);
+ }
+
+ if (!hasAnyValues) return;
+
+ // Iterate until endTime is met for all variables
+ double time = _from;
+
+ if(!resample) {
+
+ // If resample is false then all samples are reported as is. The code achieves this by setting startTime to _from and timeStep to 0.0
+ time = _from;
+ timeStep = 0.0;
+
+ } else {
+
+ // time = startTime + n*timeStep
+
+ // Sampling based on given startTime and timeStep
+ if(timeStep > 0) {
+
+ // Find the first sample time that contains data
+ double n = Math.max(0, Math.ceil((_from-startTime) / timeStep));
+ time = startTime + n*timeStep;
+
+ } else {
+
+ // Start sampling from startTime but make sure that it is not less than _from
+ if(startTime > _from) time = startTime;
+
+ }
+
+
+ }
+
+ // Must convert double times to String when initializing BigDecimal.
+ // Otherwise BigDecimal will pick up inaccuracies from beyond 15 precise digits
+ // thus making a mess of the time step calculations.
+
+ BigDecimal bigTime = new BigDecimal(String.valueOf(time));
+ BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep));
+
+ for (Item i : items) i.iter.gotoTime(time);
+ do {
+ if ( monitor!=null && monitor.isCanceled() ) return;
+
+ // Write time
+ String timeStr = timeFormatter.format( time );
+ //System.out.println("SAMPLING TIME: " + time);
+ sb.append( timeStr );
+
+ // Write values
+ for (Item i : items)
+ {
+ sb.append( columnSeparator.preference );
+
+ // Write value
+ if ( i.iter.hasValidValue() ) {
+ Object value = i.iter.getValueBand().getValue();
+ if (value instanceof Number) {
+ if (value instanceof Float || value instanceof Double) {
+ switch (numberInterpolation) {
+ case PREVIOUS_SAMPLE:
+ sb.append( formatNumber(value) );
+ break;
+
+ case LINEAR_INTERPOLATION:
+ if (time != i.iter.getValueBand().getTimeDouble() && i.iter.hasNext()) {
+
+ // Interpolate
+ int currentIndex = i.iter.getIndex();
+ ValueBand band = i.iter.getValueBand();
+ //double t1 = band.getTimeDouble();
+ Number v1 = (Number) value;
+ double t12 = band.getEndTimeDouble();
+ i.iter.next();
+ double t2 = i.iter.getValueBand().getTimeDouble();
+ Number v2 = (Number) i.iter.getValueBand().getValue();
+ i.iter.gotoIndex(currentIndex);
+
+ double vs = v1.doubleValue();
+ if(time > t12)
+ vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time);
+
+ sb.append( formatDouble(vs) );
+ } else {
+ // Exact timestamp match, or last sample.
+ // Don't interpolate nor extrapolate.
+ sb.append( formatNumber(value) );
+ }
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported interpolation: " + numberInterpolation);
+ }
+ } else {
+ sb.append( value.toString() );
+ }
+ } else if (value instanceof Boolean) {
+ sb.append( (Boolean)value ? "1": "0");
+ } else {
+ sb.append( value.toString() );
+ }
+ }
+ }
+
+ sb.append( lineFeed );
+
+ // Read next values, and the following times
+ if ( timeStep>0.0 ) {
+ bigTime = bigTime.add(bigTimeStep);
+ time = bigTime.doubleValue();
+ } else {
+ // Get smallest end time that is larger than current time
+ Double nextTime = null;
+// System.out.println("time = "+time);
+ for (Item i : items) {
+ Double itemNextTime = i.iter.getNextTime( time );
+// System.err.println(" "+i.label+" nextTime="+itemNextTime);
+ if ( itemNextTime == null ) continue;
+ if ( itemNextTime < time ) continue;
+ if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime;
+ }
+ if ( nextTime == null || nextTime.equals( time ) ) break;
+ time = nextTime;
+ }
+
+ boolean hasMore = false;
+
+ for (Item i : items) {
+ i.iter.proceedToTime(time);
+ if(contains(i, time)) hasMore = true;
+ }
+
+ if(!hasMore) break;
+
+ } while (time<=_end);
+ } finally {
+ closeHistory();
+ }
+ }
+
+ private boolean contains(Item item, double time) {
+ double start = item.iter.getStartTime();
+ double end = item.iter.getEndTime();
+ // A special case, where start == end => accept
+ if(time == start) return true;
+ else if(time < start) return false;
+ else if(time >= end) return false;
+ else return true;
+ }
+
+ private CharSequence formatNumber(Object value) {
+ return value instanceof Float
+ ? floatFormatter.format( value )
+ : numberFormatter.format( value );
+ }
+
+ private CharSequence formatDouble(double value) {
+ return numberFormatter.format( value );
+ }
+
+ public void setDecimalSeparator(DecimalSeparator separator) {
+ this.decimalSeparator = separator;
+ }
+
+ public void setColumnSeparator(ColumnSeparator separator) {
+ this.columnSeparator = separator;
+ }
+
+ public void setResample(boolean resample) {
+ this.resample = resample;
+ }
+
+ public void setLineFeed( String lf ) {
+ this.lineFeed = lf;
+ }
+
+ public void setTimeFormat(Format format) {
+ this.timeFormat = format;
+ }
+
+ public void setFloatFormat(Format format) {
+ this.floatFormat = format;
+ }
+
+ public void setNumberFormat(Format format) {
+ this.numberFormat = format;
+ }
+
+ public void setLocale(Locale locale) {
+ this.locale = locale;
+ }
+
+ public void setNumberInterpolation(ExportInterpolation interpolation) {
+ this.numberInterpolation = interpolation;
+ }
+
+ private static String resolvePlatformLineFeed() {
+ String osName = System.getProperty("os.name", "");
+ osName = osName.toLowerCase();
+ if (osName.contains("windows"))
+ return "\r\n";
+ return "\n";
+ }
+
+ private class Item implements Comparable<Item> {
+ // Static data
+ String label; // Label
+ String variableReference; // Label
+ HistoryManager history; // History source for this item
+ String historyItemId;
+ String unit;
+
+ // State data
+ StreamAccessor accessor; // Stream accessor
+ StreamIterator iter;
+
+ public void open() throws HistoryException {
+ accessor = history.openStream(historyItemId, "r");
+ iter = new StreamIterator( accessor );
+ }
+
+ public void close() {
+ if (accessor!=null) {
+ try {
+ accessor.close();
+ } catch (AccessorException e) {
+ }
+ }
+ accessor = null;
+ iter = null;
+ }
+
+ @Override
+ public int compareTo(Item o) {
+ int i;
+ i = label.compareTo(o.label);
+ if (i!=0) return i;
+ i = variableReference.compareTo(o.variableReference);
+ if (i!=0) return i;
+ i = historyItemId.compareTo(o.historyItemId);
+ if (i!=0) return i;
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int code = 0x2304;
+ code = 13*code + variableReference.hashCode();
+ code = 13*code + label.hashCode();
+ code = 13*code + historyItemId.hashCode();
+ code = 13*code + history.hashCode();
+ return code;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ( obj == null ) return false;
+ if ( obj instanceof Item == false ) return false;
+ Item other = (Item) obj;
+ if ( !other.label.equals(label) ) return false;
+ if ( !other.variableReference.equals(variableReference) ) return false;
+ if ( !other.history.equals(history) ) return false;
+ if ( !other.historyItemId.equals(historyItemId) ) return false;
+ return true;
+ }
+
+ }
+
+ static interface Formatter {
+ String format(Object number);
+ }
+
+ static class NopFormatter implements Formatter {
+ private final Format format;
+ public NopFormatter(Format format) {
+ this.format = format;
+ }
+ public String format(Object number) {
+ return format.format(number);
+ }
+ }
+
+ static class ReplacingFormatter implements Formatter {
+ private final Format format;
+ private final char from;
+ private final char to;
+ public ReplacingFormatter(Format format, char from, char to) {
+ this.format = format;
+ this.from = from;
+ this.to = to;
+ }
+ public String format(Object number) {
+ return format.format(number).replace(from, to);
+ }
+ }
+
+ private Formatter evaluateFormatter(Format format, DecimalSeparator target) {
+ // Probe decimal separator
+ String onePointTwo = format.format(1.2);
+ System.out.println("formatted zeroPointOne: " + onePointTwo);
+
+ DecimalSeparator formatSeparator;
+ if (onePointTwo.indexOf('.') != -1) {
+ formatSeparator = DecimalSeparator.DOT;
+ } else if (onePointTwo.indexOf(',') != -1) {
+ formatSeparator = DecimalSeparator.COMMA;
+ } else {
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
+ formatSeparator = DecimalSeparator.fromChar(symbols.getDecimalSeparator());
+ }
+
+ switch (formatSeparator) {
+ case COMMA:
+ switch (target) {
+ case COMMA:
+ return new NopFormatter(format);
+ case DOT:
+ return new ReplacingFormatter(format, ',', '.');
+ }
+ case DOT:
+ switch (target) {
+ case COMMA:
+ return new ReplacingFormatter(format, '.', ',');
+ case DOT:
+ return new NopFormatter(format);
+ }
+ }
+ return new NopFormatter(format);
+ }
+
+}