From 54f2fc7f6fe46b86c6df6485ca67ec90871c05bc Mon Sep 17 00:00:00 2001 From: Edward West Date: Wed, 14 Jan 2015 23:48:40 +0000 Subject: [PATCH 01/46] Converted to use the java 'enum' class type and added Time and TimeRange property types svn path=/dasCore/netbeans_trunk/; revision=8257 --- dasCore/src/org/das2/stream/PropertyType.java | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/dasCore/src/org/das2/stream/PropertyType.java b/dasCore/src/org/das2/stream/PropertyType.java index 54ec40e50..274445f08 100644 --- a/dasCore/src/org/das2/stream/PropertyType.java +++ b/dasCore/src/org/das2/stream/PropertyType.java @@ -26,23 +26,33 @@ import org.das2.datum.Units; import java.util.HashMap; import java.util.Map; +import org.das2.datum.CalendarTime; import org.das2.datum.Datum; import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; /** * * @author eew */ -public final class PropertyType { +public enum PropertyType { + DOUBLE("double"), + DOUBLE_ARRAY("doubleArray"), + DATUM("Datum"), + DATUM_RANGE("DatumRange"), + INTEGER("int"), + STRING("String"), + TIME("Time"), + TIME_RANGE("TimeRange"), + ; + private static final Map map = new HashMap(); - public static final PropertyType DOUBLE = new PropertyType("double"); - public static final PropertyType DOUBLE_ARRAY = new PropertyType("doubleArray"); - public static final PropertyType DATUM = new PropertyType("Datum"); - public static final PropertyType DATUM_RANGE = new PropertyType("DatumRange"); - public static final PropertyType INTEGER = new PropertyType("int"); - public static final PropertyType STRING = new PropertyType("String"); - + static { + for (PropertyType t : values()) + map.put(t.name, t); + } + public static PropertyType getByName(String name) { PropertyType result= (PropertyType)map.get(name); if ( result==null ) { @@ -56,30 +66,29 @@ public static PropertyType getByName(String name) { /** Creates a new instance of PropertyType */ private PropertyType(String name) { this.name = name; - map.put(name, this); } public Object parse(String s) throws java.text.ParseException { - switch(name){ - case "String": + switch(this){ + case STRING: { return s; } - case "double": + case DOUBLE: try { return new Double(s); } catch (NumberFormatException nfe) { throw new java.text.ParseException(nfe.getMessage(), 0); } - case "int": + case INTEGER: try { return new Integer(s); } catch (NumberFormatException nfe) { throw new java.text.ParseException(nfe.getMessage(), 0); } - case "doubleArray": + case DOUBLE_ARRAY: try { String[] strings = s.split(","); double[] doubles = new double[strings.length]; @@ -91,7 +100,7 @@ public Object parse(String s) throws java.text.ParseException { catch (NumberFormatException nfe) { throw new java.text.ParseException(nfe.getMessage(), 0); } - case "Datum": + case DATUM: { String[] split = s.split("\\s+"); if (split.length == 1) { @@ -105,7 +114,7 @@ else if (split.length == 2) { throw new IllegalArgumentException("Too many tokens: '" + s + "'"); } } - case "DatumRange": + case DATUM_RANGE: { String[] split = s.split("\\s+"); if (split.length < 3){ @@ -134,6 +143,14 @@ else if (split.length == 2) { return new DatumRange(begin, end); } + case TIME: + { + return new CalendarTime(s).toDatum(); + } + case TIME_RANGE: + { + return DatumRangeUtil.parseTimeRange(s); + } default: throw new IllegalStateException("unrecognized name: " + name); } From 0b62eded163071d0a66702fcad1bccab0839f917 Mon Sep 17 00:00:00 2001 From: Edward West Date: Wed, 14 Jan 2015 23:58:10 +0000 Subject: [PATCH 02/46] properties with an unrecognized property types default to string svn path=/dasCore/netbeans_trunk/; revision=8258 --- dasCore/src/org/das2/util/StreamTool.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dasCore/src/org/das2/util/StreamTool.java b/dasCore/src/org/das2/util/StreamTool.java index 4516908af..bef5b5cee 100755 --- a/dasCore/src/org/das2/util/StreamTool.java +++ b/dasCore/src/org/das2/util/StreamTool.java @@ -604,9 +604,14 @@ public static Map processPropertiesElement(Element element) throws StreamExcepti if (split.length == 1) { map.put(name, attr.getValue()); } else if (split.length == 2) { - PropertyType type = PropertyType.getByName(split[0]); - Object value = type.parse(attr.getValue()); - map.put(split[1], value); + try { + PropertyType type = PropertyType.getByName(split[0]); + Object value = type.parse(attr.getValue()); + map.put(split[1], value); + } + catch (IllegalArgumentException ex) { + map.put(name, attr.getValue()); + } } else { throw new IllegalArgumentException("Invalid typed name: " + name); } From 9a29ed7a5e2eb67852143aeaf7ca943f8a0fed97 Mon Sep 17 00:00:00 2001 From: Edward West Date: Tue, 5 Jan 2016 17:47:51 +0000 Subject: [PATCH 03/46] Resolved merge conflicts in code related to dataset labels svn path=/dasCore/netbeans_trunk/; revision=9064 --- dasCore/src/org/das2/DasIOException.java | 4 + dasCore/src/org/das2/graph/DasAxis.java | 19 ++++- .../das2/stream/StreamMultiYDescriptor.java | 76 +++++++++---------- 3 files changed, 57 insertions(+), 42 deletions(-) diff --git a/dasCore/src/org/das2/DasIOException.java b/dasCore/src/org/das2/DasIOException.java index 2f67d06c8..7b3746092 100644 --- a/dasCore/src/org/das2/DasIOException.java +++ b/dasCore/src/org/das2/DasIOException.java @@ -43,6 +43,10 @@ public DasIOException() { public DasIOException(String msg) { super(msg); } + + public DasIOException(String msg, Throwable t) { + super(msg,t); + } public DasIOException(java.io.IOException ex) { super( ex.getMessage() ); diff --git a/dasCore/src/org/das2/graph/DasAxis.java b/dasCore/src/org/das2/graph/DasAxis.java index f4ba246ea..b098b4e71 100755 --- a/dasCore/src/org/das2/graph/DasAxis.java +++ b/dasCore/src/org/das2/graph/DasAxis.java @@ -87,6 +87,7 @@ public class DasAxis extends DasCanvasComponent implements DataRangeSelectionListener, TimeRangeSelectionListener, Cloneable { public static final String PROP_LABEL = "label"; + public static final String PROP_Y_LABEL = "yLabel"; public static final String PROP_LOG = "log"; public static final String PROP_OPPOSITE_AXIS_VISIBLE = "oppositeAxisVisible"; public static final String PROP_BOUNDS = "bounds"; @@ -1378,8 +1379,15 @@ protected void paintComponent(Graphics graphics) { int width, leftEdge; for (int i = 0; i < tcaData.length; i++) { + String label = (String) tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL); + if (label == null) { + label = (String) tcaData[i].getProperty("label"); + } + if (label == null) { + label = ""; + } baseLine += lineHeight; - idlt.setString(g, (String) tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL)); + idlt.setString(g, label); width = (int) Math.floor(idlt.getWidth() + 0.5); leftEdge = rightEdge - width; idlt.draw(g, (float) leftEdge, (float) baseLine); @@ -2055,7 +2063,14 @@ protected Rectangle getAxisBounds() { idlt.setString(tickLabelFont, "SCET"); int tcaLabelWidth = (int) Math.floor(idlt.getWidth() + 0.5); for (int i = 0; i < tcaData.length; i++) { - idlt.setString(tickLabelFont, (String) tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL)); + String label = (String)tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL); + if (label == null) { + label = (String)tcaData[i].getProperty("label"); + } + if (label == null) { + label = ""; + } + idlt.setString(tickLabelFont, label); int width = (int) Math.floor(idlt.getWidth() + 0.5); tcaLabelWidth = Math.max(tcaLabelWidth, width); } diff --git a/dasCore/src/org/das2/stream/StreamMultiYDescriptor.java b/dasCore/src/org/das2/stream/StreamMultiYDescriptor.java index 0e21f739e..3935b7d1c 100755 --- a/dasCore/src/org/das2/stream/StreamMultiYDescriptor.java +++ b/dasCore/src/org/das2/stream/StreamMultiYDescriptor.java @@ -22,11 +22,11 @@ */ package org.das2.stream; -import org.das2.datum.DatumVector; -import org.das2.datum.Units; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; import org.das2.util.StreamTool; import org.w3c.dom.Document; @@ -36,21 +36,20 @@ import org.w3c.dom.NodeList; public class StreamMultiYDescriptor implements SkeletonDescriptor, Cloneable { - + private String name = ""; private Units units = Units.dimensionless; private DataTransferType transferType = DataTransferType.SUN_REAL4; - + public StreamMultiYDescriptor(Element element) throws StreamException { - if ( element.getTagName().equals("y") ) { + if (element.getTagName().equals("y")) { processElement(element); - } - else { + } else { processLegacyElement(element); } } - + private void processElement(Element element) throws StreamException { //element.getAttribute returns empty string if attr is not specified @@ -67,10 +66,10 @@ private void processElement(Element element) throws StreamException { units = Units.getByName(unitsString); } - NamedNodeMap attrs= element.getAttributes(); - for ( int i=0; i Date: Tue, 5 Jan 2016 17:52:16 +0000 Subject: [PATCH 04/46] Big patch to allow application programs to override DataSet properties when running the rebinner. This was first used by the Voyager low-rate program to override the xTagWidth property as delivered from the Das2 server. This patch changes the rebinner interface definition svn path=/dasCore/netbeans_trunk/; revision=9065 --- .../VerticalSpectrogramAverager.java | 8 +- .../AverageNoInterpolateTableRebinner.java | 13 +- .../dataset/AveragePeakTableRebinner.java | 28 +- .../das2/dataset/AverageTableRebinner.java | 20 +- .../src/org/das2/dataset/DataSetConsumer.java | 2 - .../src/org/das2/dataset/DataSetRebinner.java | 17 +- .../dataset/NearestNeighborTableDataSet.java | 74 +- .../dataset/NearestNeighborTableRebinner.java | 10 +- .../das2/dataset/NewAverageTableRebinner.java | 9 +- .../org/das2/dataset/PeakTableRebinner.java | 12 +- .../org/das2/dataset/QernalTableRebinner.java | 8 +- .../src/org/das2/dataset/RebinDescriptor.java | 13 +- .../src/org/das2/event/CutoffMouseModule.java | 8 +- .../das2/event/PeakDetectorMouseModule.java | 11 +- .../src/org/das2/graph/ContoursRenderer.java | 2 +- dasCore/src/org/das2/graph/DasAxis.java | 5 +- dasCore/src/org/das2/graph/Renderer.java | 72 +- .../org/das2/graph/SpectrogramRenderer.java | 3 +- .../das2/graph/StackedHistogramRenderer.java | 103 ++- .../StackedHistogramRenderer_rework.java.save | 690 ++++++++++++++++++ .../org/das2/graph/StippledTableRenderer.java | 4 +- .../org/das2/graph/SymbolLineRenderer.java | 9 +- .../src/org/das2/graph/TickVDescriptor.java | 2 +- 23 files changed, 1057 insertions(+), 66 deletions(-) create mode 100644 dasCore/src/org/das2/graph/StackedHistogramRenderer_rework.java.save diff --git a/dasCore/src/org/das2/components/VerticalSpectrogramAverager.java b/dasCore/src/org/das2/components/VerticalSpectrogramAverager.java index 34187e0a8..2d275dfb4 100755 --- a/dasCore/src/org/das2/components/VerticalSpectrogramAverager.java +++ b/dasCore/src/org/das2/components/VerticalSpectrogramAverager.java @@ -74,6 +74,7 @@ public void showPopup() { showPopupImpl(); } else { Runnable r = new Runnable() { + @Override public void run() { showPopupImpl(); } @@ -104,6 +105,7 @@ private void createPopup() { BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); JButton close = new JButton("Hide Window"); close.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { popupWindow.setVisible(false); } @@ -133,6 +135,7 @@ public void actionPerformed(ActionEvent e) { popupWindow.setLocation(parentLocation.x + parentPlot.getCanvas().getWidth(),parentLocation.y); } + @Override protected void drawContent(Graphics2D g) { super.drawContent(g); /*int ix= (int)this.getXAxis().transform(yValue); @@ -145,6 +148,7 @@ protected void drawContent(Graphics2D g) { g.drawLine(ix-3,iy1,ix,iy1-3);*/ } + @Override public void dataRangeSelected(DataRangeSelectionEvent e) { DataSet ds = e.getDataSet(); if (ds==null || !(ds instanceof TableDataSet)) @@ -163,7 +167,7 @@ public void dataRangeSelected(DataRangeSelectionEvent e) { ddX.setOutOfBoundsAction(RebinDescriptor.MINUSONE); AverageTableRebinner rebinner = new AverageTableRebinner(); try { - TableDataSet rebinned = (TableDataSet)rebinner.rebin(xtys, ddX, null); + TableDataSet rebinned = (TableDataSet)rebinner.rebin(xtys, ddX, null, null); VectorDataSet ds1 = rebinned.getXSlice(0); renderer.setDataSet(ds1); } catch (DasException de) { @@ -177,10 +181,12 @@ public void dataRangeSelected(DataRangeSelectionEvent e) { } } + @Override protected void uninstallComponent() { super.uninstallComponent(); } + @Override protected void installComponent() { super.installComponent(); getCanvas().getGlassPane().setVisible(false); diff --git a/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java b/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java index 432a6809f..ecf57a7ad 100644 --- a/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java +++ b/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; +import sun.reflect.generics.reflectiveObjects.NotImplementedException; /** * This rebinner will bin average elements that fall on the same bin, and will enlarge cells that @@ -166,8 +167,18 @@ private static BinDescriptor calcBinDescriptor( DatumRange[] inRanges, DatumRang return result; } + + - public DataSet rebin(DataSet ds, RebinDescriptor ddx, RebinDescriptor ddy) throws IllegalArgumentException, DasException { + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddx, RebinDescriptor ddy, Map override + ) throws IllegalArgumentException, DasException { + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + logger= DasLogger.getLogger( DasLogger.DATA_OPERATIONS_LOG ); logger.finest("enter AverageNoInterpolateTableRebinner.rebin"); diff --git a/dasCore/src/org/das2/dataset/AveragePeakTableRebinner.java b/dasCore/src/org/das2/dataset/AveragePeakTableRebinner.java index 24d507119..ed6b3135a 100755 --- a/dasCore/src/org/das2/dataset/AveragePeakTableRebinner.java +++ b/dasCore/src/org/das2/dataset/AveragePeakTableRebinner.java @@ -41,7 +41,11 @@ public class AveragePeakTableRebinner implements DataSetRebinner { public AveragePeakTableRebinner() { } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException { + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { + if (!(ds instanceof TableDataSet)) { throw new IllegalArgumentException(); } @@ -101,13 +105,23 @@ public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throw } } - Datum xTagWidth= DataSetUtil.guessXTagWidth(ds); + Datum xTagWidth = null; + if(override != null && override.get(DataSet.PROPERTY_X_TAG_WIDTH) != null) + xTagWidth = (Datum)override.get(DataSet.PROPERTY_X_TAG_WIDTH); + else + xTagWidth = DataSetUtil.guessXTagWidth(ds); double xTagWidthDouble= xTagWidth.doubleValue(ddX.getUnits().getOffsetUnits()); - AverageTableRebinner.fillInterpolateX(averageData, averageWeights, xTags, xTagMin, xTagMax, xTagWidthDouble, AverageTableRebinner.Interpolate.Linear ); + AverageTableRebinner.fillInterpolateX( + averageData, averageWeights, xTags, xTagMin, xTagMax, xTagWidthDouble, + AverageTableRebinner.Interpolate.Linear + ); if ( ddY!=null ) { Datum yTagWidth= (Datum)ds.getProperty( DataSet.PROPERTY_Y_TAG_WIDTH ); - AverageTableRebinner.fillInterpolateY(averageData, averageWeights, ddY, yTagWidth, AverageTableRebinner.Interpolate.Linear ); + AverageTableRebinner.fillInterpolateY( + averageData, averageWeights, ddY, yTagWidth, + AverageTableRebinner.Interpolate.Linear + ); } if (peaks == null) { @@ -123,9 +137,9 @@ public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throw if ( ddY!=null ) properties.put( DataSet.PROPERTY_Y_TAG_WIDTH, ddY.binWidthDatum() ); int[] tableOffsets = {0}; - String[] planeIDs = {"", DataSet.PROPERTY_PLANE_PEAKS, DataSet.PROPERTY_PLANE_WEIGHTS}; - double[][][] zValues = {averageData, peakData, averageWeights}; - Units[] zUnits = {tds.getZUnits(), tds.getZUnits(), Units.dimensionless}; + String[] planeIDs = {"", DataSet.PROPERTY_PLANE_PEAKS, DataSet.PROPERTY_PLANE_WEIGHTS}; + double[][][] zValues = {averageData, peakData, averageWeights}; + Units[] zUnits = {tds.getZUnits(),tds.getZUnits(),Units.dimensionless}; return new DefaultTableDataSet(xTags, tds.getXUnits(), yTags, tds.getYUnits(), zValues, zUnits, planeIDs, tableOffsets, properties ); } diff --git a/dasCore/src/org/das2/dataset/AverageTableRebinner.java b/dasCore/src/org/das2/dataset/AverageTableRebinner.java index 6df284a17..81bcf2a92 100755 --- a/dasCore/src/org/das2/dataset/AverageTableRebinner.java +++ b/dasCore/src/org/das2/dataset/AverageTableRebinner.java @@ -54,8 +54,15 @@ public static enum Interpolate { public AverageTableRebinner() { } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException, DasException { + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, DasException { logger.finest("enter AverageTableRebinner.rebin"); + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); if (ds == null) { throw new NullPointerException("null data set"); @@ -417,7 +424,11 @@ private final static void multiplyWeights(double[][] data, double[][] weights, d } } - static void fillInterpolateX(final double[][] data, final double[][] weights, final double[] xTags, double[] xTagMin, double[] xTagMax, final double xSampleWidth, Interpolate interpolateType) { + static void fillInterpolateX( + final double[][] data, final double[][] weights, final double[] xTags, + double[] xTagMin, double[] xTagMax, final double xSampleWidth, + Interpolate interpolateType + ) { final int nx = xTags.length; final int ny = data[0].length; @@ -467,7 +478,10 @@ static void fillInterpolateX(final double[][] data, final double[][] weights, fi if (interpolateType == Interpolate.NearestNeighbor) { for (int i = 0; i < nx; i++) { - if (Math.min(i1[i] == -1 ? Double.MAX_VALUE : (xTags[i] - xTagMin[i1[i]]), i2[i] == -1 ? Double.MAX_VALUE : (xTagMax[i2[i]] - xTags[i])) < xSampleWidth / 2) { + if (Math.min( + i1[i] == -1 ? Double.MAX_VALUE : (xTags[i] - xTagMin[i1[i]]), + i2[i] == -1 ? Double.MAX_VALUE : (xTagMax[i2[i]] - xTags[i])) < xSampleWidth / 2 + ) { int idx = -1; if (i1[i] == -1) { diff --git a/dasCore/src/org/das2/dataset/DataSetConsumer.java b/dasCore/src/org/das2/dataset/DataSetConsumer.java index e97aa9f3f..bc9ee0f6a 100644 --- a/dasCore/src/org/das2/dataset/DataSetConsumer.java +++ b/dasCore/src/org/das2/dataset/DataSetConsumer.java @@ -23,8 +23,6 @@ package org.das2.dataset; -import org.das2.dataset.DataSet; - /** * * @author jbf diff --git a/dasCore/src/org/das2/dataset/DataSetRebinner.java b/dasCore/src/org/das2/dataset/DataSetRebinner.java index acdfa5c95..c1f9f30cf 100755 --- a/dasCore/src/org/das2/dataset/DataSetRebinner.java +++ b/dasCore/src/org/das2/dataset/DataSetRebinner.java @@ -23,6 +23,7 @@ package org.das2.dataset; +import java.util.Map; import org.das2.DasException; /** @@ -31,6 +32,18 @@ */ public interface DataSetRebinner { - DataSet rebin(DataSet ds, RebinDescriptor x, RebinDescriptor y) throws IllegalArgumentException, DasException; - + /** Rebin a dataset into a 2-D grid + * + * Datasets are immutable, so you can't alter them in place + * @param ds The dataset to rebin + * @param x bin size and spacing information for the X direction + * @param y bin size and spacing information for the Y direction + * @param override a map of dataset properties to override, may be null. + * property names and values for this map match those given in DataSet.java + * @return a new dataset + * @throws IllegalArgumentException + * @throws DasException + */ + DataSet rebin(DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override) + throws IllegalArgumentException, DasException; } diff --git a/dasCore/src/org/das2/dataset/NearestNeighborTableDataSet.java b/dasCore/src/org/das2/dataset/NearestNeighborTableDataSet.java index 679ba20ee..59905cae0 100755 --- a/dasCore/src/org/das2/dataset/NearestNeighborTableDataSet.java +++ b/dasCore/src/org/das2/dataset/NearestNeighborTableDataSet.java @@ -9,6 +9,8 @@ public class NearestNeighborTableDataSet implements TableDataSet { TableDataSet source; + + Map m_override; /* Have to keep track of your overrides for getProperty */ int[] imap; @@ -19,8 +21,10 @@ public class NearestNeighborTableDataSet implements TableDataSet { RebinDescriptor ddX; RebinDescriptor ddY; - - NearestNeighborTableDataSet( TableDataSet source, RebinDescriptor ddX, RebinDescriptor ddY ) { + + NearestNeighborTableDataSet( + TableDataSet source, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) { imap= new int[ddX.numberOfBins()]; if ( ddY==null ) { if ( source.tableCount()>1 ) { @@ -35,15 +39,28 @@ public class NearestNeighborTableDataSet implements TableDataSet { this.ddX= ddX; this.ddY= ddY; this.source= source; + + if(override == null) + m_override = new HashMap<>(); + else + m_override = override; if ( source.getXLength()==0 ) { for ( int i=0; i mRet = new HashMap<>(m_override); + Map srcProps = source.getProperties(); + for(Object key: srcProps.keySet()){ + mRet.put(key, srcProps.get(key)); + } + return mRet; } + @Override public int getXLength() { return imap.length; } + @Override public VectorDataSet getXSlice(int i) { return new XSliceDataSet(this,i); } + @Override public VectorDataSet getYSlice(int j, int table) { return new YSliceDataSet(this, j, table); } + @Override public Datum getXTagDatum(int i) { return ddX.getUnits().createDatum(getXTagDouble(i,ddX.getUnits())); } + @Override public double getXTagDouble(int i, Units units) { return ddX.binCenter(i,units); } + @Override public int getXTagInt(int i, Units units) { return (int)getXTagDouble(i,units); } + @Override public Units getXUnits() { return ddX.getUnits(); } + @Override public int getYLength(int table) { if ( ddY==null ) { return source.getYLength(table); @@ -180,6 +222,7 @@ public int getYLength(int table) { } } + @Override public Datum getYTagDatum(int table, int j) { if ( ddY==null ) { return source.getYTagDatum( table, j ); @@ -188,6 +231,7 @@ public Datum getYTagDatum(int table, int j) { } } + @Override public double getYTagDouble(int table, int j, Units units) { if ( ddY==null ) { return source.getYTagDouble( table, j, units ); @@ -196,10 +240,12 @@ public double getYTagDouble(int table, int j, Units units) { } } + @Override public int getYTagInt(int table, int j, Units units) { return (int)getYTagDouble( table, j, units); } + @Override public Units getYUnits() { if ( ddY==null ) { return source.getYUnits(); @@ -208,30 +254,37 @@ public Units getYUnits() { } } + @Override public Units getZUnits() { return source.getZUnits(); } + @Override public int tableCount() { return 1; } + @Override public int tableEnd(int table) { return ddX.numberOfBins(); } + @Override public int tableOfIndex(int i) { return 0; } + @Override public int tableStart(int table) { return 0; } + @Override public String toString() { return "NearestNeighborTableDataSet " + TableUtil.toString(this); } + @Override public double[] getDoubleScan(int i, Units units) { int yLength = getYLength(tableOfIndex(i)); double[] array = new double[yLength]; @@ -241,11 +294,13 @@ public double[] getDoubleScan(int i, Units units) { return array; } + @Override public DatumVector getScan(int i) { Units zUnits = getZUnits(); return DatumVector.newDatumVector(getDoubleScan(i, zUnits), zUnits); } + @Override public DatumVector getYTags(int table) { double[] tags = new double[getYLength(table)]; Units yUnits = getYUnits(); @@ -255,6 +310,7 @@ public DatumVector getYTags(int table) { return DatumVector.newDatumVector(tags, yUnits); } + @Override public Object getProperty(int table, String name) { return getProperty(name); } diff --git a/dasCore/src/org/das2/dataset/NearestNeighborTableRebinner.java b/dasCore/src/org/das2/dataset/NearestNeighborTableRebinner.java index 0499c24ea..2f4673ed7 100755 --- a/dasCore/src/org/das2/dataset/NearestNeighborTableRebinner.java +++ b/dasCore/src/org/das2/dataset/NearestNeighborTableRebinner.java @@ -23,6 +23,8 @@ package org.das2.dataset; +import java.util.Map; + /** * * @author Jeremy Faden @@ -33,11 +35,15 @@ public class NearestNeighborTableRebinner implements DataSetRebinner { public NearestNeighborTableRebinner() { } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException { + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { if (!(ds instanceof TableDataSet)) { throw new IllegalArgumentException(); } - return new NearestNeighborTableDataSet((TableDataSet)ds, ddX, ddY ); + + return new NearestNeighborTableDataSet((TableDataSet)ds, ddX, ddY, override ); } } diff --git a/dasCore/src/org/das2/dataset/NewAverageTableRebinner.java b/dasCore/src/org/das2/dataset/NewAverageTableRebinner.java index b99df78a6..07ed4f7c0 100644 --- a/dasCore/src/org/das2/dataset/NewAverageTableRebinner.java +++ b/dasCore/src/org/das2/dataset/NewAverageTableRebinner.java @@ -66,11 +66,18 @@ public NewAverageTableRebinner( DataSet ds, RebinDescriptor ddX, RebinDescriptor ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException, DasException { + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, DasException { if ( ds!=this.tds ) throw new IllegalArgumentException("already set for another dataset"); if ( ddX!=this.ddX ) throw new IllegalArgumentException("already set for another X rebin descriptor"); if ( ddY!=this.ddY ) throw new IllegalArgumentException("already set for another Y rebin descriptor"); + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + TableDataSet weights = (TableDataSet)tds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); if (ddX != null && tds.getXLength() > 0) { double start = tds.getXTagDouble(0, ddX.getUnits()); diff --git a/dasCore/src/org/das2/dataset/PeakTableRebinner.java b/dasCore/src/org/das2/dataset/PeakTableRebinner.java index 9f3dac654..afa1fe07e 100755 --- a/dasCore/src/org/das2/dataset/PeakTableRebinner.java +++ b/dasCore/src/org/das2/dataset/PeakTableRebinner.java @@ -23,6 +23,7 @@ package org.das2.dataset; +import java.util.Map; import org.das2.datum.Units; /** @@ -34,10 +35,19 @@ public class PeakTableRebinner implements DataSetRebinner { public PeakTableRebinner() { } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException { + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { if (!(ds instanceof TableDataSet)) { throw new IllegalArgumentException(); } + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + TableDataSet tds = (TableDataSet)ds; long timer= System.currentTimeMillis(); diff --git a/dasCore/src/org/das2/dataset/QernalTableRebinner.java b/dasCore/src/org/das2/dataset/QernalTableRebinner.java index 3961d4c47..456690b1e 100644 --- a/dasCore/src/org/das2/dataset/QernalTableRebinner.java +++ b/dasCore/src/org/das2/dataset/QernalTableRebinner.java @@ -38,7 +38,9 @@ public QernalTableRebinner( QernalFactory factory ) { this.factory= factory; } - public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throws IllegalArgumentException, org.das2.DasException { + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, org.das2.DasException { logger.finest("enter QernalTableRebinner.rebin"); if (ds == null) { @@ -47,6 +49,10 @@ public DataSet rebin(DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY) throw if (!(ds instanceof TableDataSet)) { throw new IllegalArgumentException("Data set must be an instanceof TableDataSet: " + ds.getClass().getName()); } + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); TableDataSet tds = (TableDataSet)ds; TableDataSet weights = (TableDataSet)ds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); diff --git a/dasCore/src/org/das2/dataset/RebinDescriptor.java b/dasCore/src/org/das2/dataset/RebinDescriptor.java index ba3e78ac2..b273d9243 100755 --- a/dasCore/src/org/das2/dataset/RebinDescriptor.java +++ b/dasCore/src/org/das2/dataset/RebinDescriptor.java @@ -28,7 +28,7 @@ import org.das2.datum.Datum; import org.das2.datum.Units; -/** +/** Nice Documentation... * * @author jbf */ @@ -155,6 +155,10 @@ public double binStop( int ibin, Units units ) { } } + /** So what freaking units are the return value in??? + * Anyone? Buler? + * @return a number in who knows what coordinate system + */ public double[] binStarts() { double [] result= new double[nBin]; for (int i=0; i>") { + @Override public void actionPerformed( ActionEvent e ) { Datum xnew= xValue.add( xResolution ); DataPointSelectionEvent evNew= new DataPointSelectionEvent( this, xnew, yValue ); @@ -406,6 +408,7 @@ public void actionPerformed( ActionEvent e ) { DataPointSelectorMouseModule tweakSlicer= new DataPointSelectorMouseModule( topPlot, levelRenderer, new VerticalSliceSelectionRenderer(topPlot), "tweak cutoff" ) { + @Override public void keyPressed( KeyEvent event ) { System.err.print(event); if ( event.getKeyCode()==KeyEvent.VK_DOWN ) { @@ -418,6 +421,7 @@ public void keyPressed( KeyEvent event ) { }; tweakSlicer.setDragEvents(true); // only key events fire tweakSlicer.addDataPointSelectionListener( new DataPointSelectionListener() { + @Override public void dataPointSelected( DataPointSelectionEvent e ) { Datum x= e.getX(); HashMap properties= new HashMap(); @@ -438,6 +442,7 @@ public void dataPointSelected( DataPointSelectionEvent e ) { new DataPointSelectorMouseModule( topPlot, levelRenderer, new HorizontalSliceSelectionRenderer(topPlot), "peak S/N level" ); levelSlicer.addDataPointSelectionListener( new DataPointSelectionListener() { + @Override public void dataPointSelected( DataPointSelectionEvent e ) { Datum y= e.getY(); PeakDetectorMouseModule.this.setLevelMin( y ); @@ -453,6 +458,7 @@ public void dataPointSelected( DataPointSelectionEvent e ) { } + @Override public void dataPointSelected(org.das2.event.DataPointSelectionEvent event) { logger.fine("got DataPointSelectionEvent: "+event.getX() ); this.lastSelectedPoint= event; @@ -500,6 +506,7 @@ class PeakDasPlot extends DasPlot { PeakDasPlot( DasAxis x, DasAxis y ) { super(x,y); } + @Override protected void drawContent(java.awt.Graphics2D g) { super.drawContent(g); diff --git a/dasCore/src/org/das2/graph/ContoursRenderer.java b/dasCore/src/org/das2/graph/ContoursRenderer.java index ee3b4cdd1..bc3a89f49 100755 --- a/dasCore/src/org/das2/graph/ContoursRenderer.java +++ b/dasCore/src/org/das2/graph/ContoursRenderer.java @@ -239,7 +239,7 @@ public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressM rebinner.setInterpolate(false); if (xRebinDescriptor != null || yRebinDescriptor != null) { - tds = (TableDataSet) rebinner.rebin(tds, xRebinDescriptor, yRebinDescriptor); + tds = (TableDataSet) rebinner.rebin(tds, xRebinDescriptor, yRebinDescriptor, null); } } diff --git a/dasCore/src/org/das2/graph/DasAxis.java b/dasCore/src/org/das2/graph/DasAxis.java index b098b4e71..33906eb5e 100755 --- a/dasCore/src/org/das2/graph/DasAxis.java +++ b/dasCore/src/org/das2/graph/DasAxis.java @@ -84,7 +84,8 @@ * and provides controls for nagivating the 1-D data space. * @author eew */ -public class DasAxis extends DasCanvasComponent implements DataRangeSelectionListener, TimeRangeSelectionListener, Cloneable { +public class DasAxis extends DasCanvasComponent + implements DataRangeSelectionListener, TimeRangeSelectionListener, Cloneable { public static final String PROP_LABEL = "label"; public static final String PROP_Y_LABEL = "yLabel"; @@ -223,7 +224,7 @@ public DasAxis(Datum min, Datum max, int orientation) { this(min, max, orientation, false); } - /** TODO + /** TODO Tell us if these are screen or data coordinates * @param min * @param max * @param orientation diff --git a/dasCore/src/org/das2/graph/Renderer.java b/dasCore/src/org/das2/graph/Renderer.java index 466151b4f..02daf7557 100755 --- a/dasCore/src/org/das2/graph/Renderer.java +++ b/dasCore/src/org/das2/graph/Renderer.java @@ -42,6 +42,9 @@ import javax.swing.*; import java.awt.*; import java.io.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Logger; import org.w3c.dom.*; @@ -77,6 +80,10 @@ public abstract class Renderer implements DataSetConsumer, Editable { * setDataSet and setException. */ DataLoader loader; + + /** DataSet properties to override during the rendering process */ + HashMap override; + /** * When a dataset cannot be loaded, the exception causing the failure * will be rendered instead. @@ -88,16 +95,18 @@ public abstract class Renderer implements DataSetConsumer, Editable { private String PROPERTY_DATASET = "dataSet"; protected Renderer(DataSetDescriptor dsd) { + override = new HashMap<>(); this.loader = new XAxisDataLoader(this, dsd); } protected Renderer(DataSet ds) { + override = new HashMap<>(); this.ds = ds; this.loader = null; } protected Renderer() { - this((DataSetDescriptor) null); + this((DataSetDescriptor) null); } public DasPlot getParent() { @@ -164,6 +173,67 @@ public void setDumpDataSet(boolean dumpDataSet) { this.dumpDataSet = dumpDataSet; } } + + /** Get a copy of this renderer's display override's. + * + * @return The internal property override map wrapped as an unmodifiable map. + */ + public Map getOverrideMap(){ + return Collections.unmodifiableMap(override); + } + + /** Copy in a set of display overrides for this renderer + * + * @param m An object,object map. The keys should be property strings from + * DataSet + */ + public void setOverrideMap(Map m){ + override = new HashMap<>(m); + } + + /** Get and override property value without removing it + * + * @param name a well know dataset property name. These are provided + * in the DataSet class. + * @return The value for the override, see the DataSet class to cast the returned + * value to the appropriate type. + */ + public Object getOverride(String name){ + return override.get(name); + } + + /** Set a dataset property to override during the rendering process + * + * @param name a well know dataset property name. These are provided + * in the DataSet class. + * + * @return The value object for the property, or null if that property has not + * been overridden. See docs for the DataSet class to cast the return type + * appropriately. + */ + public void setOverride(String name, Object o){ + override.put(name, o); + } + + /** Determine if a particular DataSet property will be overridden during display + * processing + * @param name A DataSet property string + * @return true if overridden, false if the key name is not in the renderers internal + * override map + */ + public boolean hasOverride(String name){ + return override.get(name) == null; + } + + /** Remove an override + * + * @param name A DataSet property string + * @return The previous value held for the property override, or null the property in + * question did not have an override value + */ + public Object removeOverride(String name){ + return override.remove(name); + } public void setLastException(Exception e) { this.lastException = e; diff --git a/dasCore/src/org/das2/graph/SpectrogramRenderer.java b/dasCore/src/org/das2/graph/SpectrogramRenderer.java index bfe5d0b42..0584b8457 100755 --- a/dasCore/src/org/das2/graph/SpectrogramRenderer.java +++ b/dasCore/src/org/das2/graph/SpectrogramRenderer.java @@ -428,7 +428,8 @@ public void updatePlotImage( DasAxis xAxis, DasAxis yAxis, ProgressMonitor monit t0 = System.currentTimeMillis(); - rebinDataSet = (TableDataSet) rebinner.rebin(this.ds, xRebinDescriptor, yRebinDescriptor); + rebinDataSet = (TableDataSet) + rebinner.rebin(this.ds, xRebinDescriptor, yRebinDescriptor, null); xmemento = xAxis.getMemento(); ymemento = yAxis.getMemento(); diff --git a/dasCore/src/org/das2/graph/StackedHistogramRenderer.java b/dasCore/src/org/das2/graph/StackedHistogramRenderer.java index d3c91ae96..971035cd7 100644 --- a/dasCore/src/org/das2/graph/StackedHistogramRenderer.java +++ b/dasCore/src/org/das2/graph/StackedHistogramRenderer.java @@ -58,7 +58,9 @@ import java.awt.image.BufferedImage; import java.beans.*; import java.text.*; +import java.util.Map; import javax.swing.*; +import static org.das2.graph.Renderer.logger; import org.w3c.dom.*; /** @@ -126,6 +128,23 @@ public String toString() { * draw a point at the maximum observed. */ public static final PeaksIndicator MaxLines= new PeaksIndicator("Lines"); + + /** Get a peaks indicator object given a string. + * + * @param s + * @return null if the string doesn't match one of the pre-defined items + */ + public static PeaksIndicator fromString(String s){ + if(NoPeaks.toString().equals(s)) return NoPeaks; + if(GrayPeaks.toString().equals(s)) return GrayPeaks; + if(RedPeaks.toString().equals(s)) return RedPeaks; + if(BluePeaks.toString().equals(s)) return BluePeaks; + if(GreenPeaks.toString().equals(s)) return GreenPeaks; + if(LinePeaks.toString().equals(s)) return LinePeaks; + if(BlackPeaks.toString().equals(s)) return BlackPeaks; + if(MaxLines.toString().equals(s)) return MaxLines; + return null; + } @Override public String getListLabel() { @@ -262,6 +281,30 @@ protected String getPrimaryPeaksId(){ else return sPrimary + ".peaks"; // Handle named primary planes } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private int getInterpDistance(DasAxis axis){ + // Get peaks dataset in data coordinates, not screen coordinates + TableDataSet ds = (TableDataSet) getDataSet().getPlanarView(getPrimaryPeaksId()); + + Datum dInterp; + if(override.containsKey(DataSet.PROPERTY_X_TAG_WIDTH)) + dInterp = (Datum)override.get(DataSet.PROPERTY_X_TAG_WIDTH); + else + dInterp = (Datum)ds.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + + // Use the start of the axis a the basis point for finding the interpolation + // distance in screen coordinates + Datum dBeg = axis.getDataMaximum(); + + // Too bad can't do this in java: Datum dEnd = dBeg + dInterp; + Datum dEnd = dBeg.add(dInterp); + // The factor of 1.5 is to match the check used in + // AverageTableRebinner.fillInterpolateX -> line 508. + int iMaxInterp = (int) ((axis.transform(dEnd) - axis.transform(dBeg))*1.5); + return iMaxInterp; + } @Override synchronized public void updatePlotImage( DasAxis xAxis, DasAxis yAxis_1, ProgressMonitor monitor ) throws DasException { @@ -321,14 +364,14 @@ synchronized public void updatePlotImage( DasAxis xAxis, DasAxis yAxis_1, Progre DataSetRebinner rebinner = new Rebinner(); - TableDataSet data= (TableDataSet)rebinner.rebin(xtysData, xbins, null); + TableDataSet data= (TableDataSet)rebinner.rebin(xtysData, xbins, null, override); TableDataSet peaks= (TableDataSet)data.getPlanarView(getPrimaryPeaksId()); DasLabelAxis yAxis= (DasLabelAxis)yAxis_1; int zmid= zAxis.getRow().getDMiddle(); boolean haveLittleRow= false; - + for (int j = 0; j < data.getYLength(0); j++) { int yBase; @@ -366,20 +409,44 @@ synchronized public void updatePlotImage( DasAxis xAxis, DasAxis yAxis_1, Progre if ( yBase1 >= row.getDMinimum() && yBase <= row.getDMaximum() ) { if ( peaksIndicator==PeaksIndicator.LinePeaks && peaks!=null ) { GeneralPath p= new GeneralPath(); - boolean lastWasFill=true; - for (int ibin=0; ibin < data.getXLength(); ibin++) { - int x0= (int)xAxis.transform(binStarts[ibin],xbins.getUnits()); + + boolean bHasBeg = false; + int nMaxInterpPx = getInterpDistance(xAxis); + int xLast = 0; + int yLast = 0; + for (int ibin=0; ibin < data.getXLength(); ibin++) { + double zz= peaks.getDouble( ibin, j, data.getZUnits() ); - if ( !( data.getZUnits().isFill(zz) || Double.isNaN(zz) ) ) { - int yMax= (int)zAxis.transform( zz, data.getZUnits(), yBase, yBase1 ); - if ( lastWasFill ) p.moveTo( x0, Math.min( yMax, y0 ) ); - p.lineTo( x0, Math.min( yMax, y0 ) ); - lastWasFill= false; - } else { - lastWasFill= true; - } + if( data.getZUnits().isFill(zz) || Double.isNaN(zz) ) + continue; + + int x0= (int)xAxis.transform(binStarts[ibin],xbins.getUnits()); + int yMax= (int)zAxis.transform( zz, data.getZUnits(), yBase, yBase1 ); + + // If there is no previous point, set one and move on. + if(!bHasBeg){ + p.moveTo( x0, Math.min( yMax, y0 ) ); + xLast = x0; + yLast = Math.min( yMax, y0 ); + bHasBeg = true; + continue; + } + + // Have data, if previous exists and is close enough, connect + // with line. If not, just put a small line at previous point + // and reset begin to here. + if((x0 - xLast) <= nMaxInterpPx){ + p.lineTo( x0, Math.min( yMax, y0 ) ); + } + else{ + p.lineTo( xLast, yLast); //Can a single point be a line? + p.moveTo( x0, Math.min(yMax, y0)); + } + xLast = x0; + yLast = Math.min( yMax, y0 ); } - g.draw(p); + + g.draw(p); } for (int ibin=0; ibin < data.getXLength(); ibin++) { int x0= (int)xAxis.transform(binStarts[ibin],xbins.getUnits()); @@ -456,7 +523,9 @@ public class Rebinner implements DataSetRebinner { } @Override - public DataSet rebin(DataSet ds, RebinDescriptor x, RebinDescriptor y) throws IllegalArgumentException, DasException { + public DataSet rebin( + DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override + ) throws IllegalArgumentException, DasException { Datum xwidth= (Datum)ds.getProperty( "xTagWidth" ); if ( xwidth==null ) xwidth= DataSetUtil.guessXTagWidth((TableDataSet)ds); Units rdUnits= x.getUnits(); @@ -468,10 +537,10 @@ public DataSet rebin(DataSet ds, RebinDescriptor x, RebinDescriptor y) throws Il DataSet result; if ( x.binWidth() < xwidth.doubleValue(rdUnits) ) { logger.fine("using rebinner "+highResRebinner); - result= highResRebinner.rebin( ds, x, y ); + result= highResRebinner.rebin( ds, x, y, override ); } else { logger.fine("using rebinner "+lowResRebinner); - result= lowResRebinner.rebin( ds, x, y ); + result= lowResRebinner.rebin( ds, x, y, override ); } return result; } catch ( IllegalArgumentException | DasException e ) { diff --git a/dasCore/src/org/das2/graph/StackedHistogramRenderer_rework.java.save b/dasCore/src/org/das2/graph/StackedHistogramRenderer_rework.java.save new file mode 100644 index 000000000..477668d85 --- /dev/null +++ b/dasCore/src/org/das2/graph/StackedHistogramRenderer_rework.java.save @@ -0,0 +1,690 @@ +/* File: DasStackedHistogramPlot.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.MouseModule; +import org.das2.event.LengthDragRenderer; +import org.das2.event.CrossHairMouseModule; +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.NearestNeighborTableRebinner; +import org.das2.dataset.AveragePeakTableRebinner; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.LocationUnits; +import org.das2.datum.DatumRange; +import org.das2.dasml.FormBase; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.DasException; +import org.das2.components.HorizontalSpectrogramSlicer; +import org.das2.components.VerticalSpectrogramSlicer; +import org.das2.event.HorizontalSlicerMouseModule; +import org.das2.event.VerticalSlicerMouseModule; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.components.propertyeditor.Enumeration; + +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.beans.*; +import java.text.*; +import java.util.Map; +import javax.swing.*; +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class StackedHistogramRenderer extends org.das2.graph.Renderer + implements TableDataSetConsumer, PropertyChangeListener, Displayable +{ + + private DasLabelAxis yAxis= null; + private DasAxis zAxis= null; + private RowRowConnector zAxisConnector= null; + private DasRow littleRow=null; + + private RebinDescriptor xBins= null; + + private PeaksIndicator peaksIndicator; + + /** Holds value of property sliceRebinnedData. */ + private boolean sliceRebinnedData; + + Image plotImage; + DatumRange imageXRange, imageYRange; + + /** If set this will override the xTagWidth property during display */ + Datum m_rMaxXInterp = null; + + public static class PeaksIndicator implements Enumeration, Displayable { + + String id; + + PeaksIndicator(String id) { + this.id= id; + } + + @Override + public String toString() { + return this.id; + } + + public static final PeaksIndicator NoPeaks= new PeaksIndicator("None"); + /** + * draw grey bar up to max observed. + */ + public static final PeaksIndicator GrayPeaks= new PeaksIndicator("Gray Peaks"); + /** + * draw red bar up to max observed. + */ + public static final PeaksIndicator RedPeaks= new PeaksIndicator("Red Peaks"); + /** + * draw blue bar up to max observed. + */ + public static final PeaksIndicator BluePeaks= new PeaksIndicator("Blue Peaks"); + /** + * draw green bar up to max observed. + */ + public static final PeaksIndicator GreenPeaks= new PeaksIndicator("Green Peaks"); + /** + * draw a connect-a-dot line from peak to peak. + */ + public static final PeaksIndicator LinePeaks= new PeaksIndicator("Line Peaks"); + /** + * draw black bar up to max observed (only peaks are visible then). + */ + public static final PeaksIndicator BlackPeaks= new PeaksIndicator("Black Peaks"); + /** + * draw a point at the maximum observed. + */ + public static final PeaksIndicator MaxLines= new PeaksIndicator("Lines"); + + /** Get a peaks indicator object given a string. + * + * @param s + * @return null if the string doesn't match one of the pre-defined items + */ + public static PeaksIndicator fromString(String s){ + if(NoPeaks.toString().equals(s)) return NoPeaks; + if(GrayPeaks.toString().equals(s)) return GrayPeaks; + if(RedPeaks.toString().equals(s)) return RedPeaks; + if(BluePeaks.toString().equals(s)) return BluePeaks; + if(GreenPeaks.toString().equals(s)) return GreenPeaks; + if(LinePeaks.toString().equals(s)) return LinePeaks; + if(BlackPeaks.toString().equals(s)) return BlackPeaks; + if(MaxLines.toString().equals(s)) return MaxLines; + return null; + } + + @Override + public String getListLabel() { + return this.id; + } + + @Override + public javax.swing.Icon getListIcon() { + return null; + } + + } + + protected class RebinListener implements PropertyChangeListener { + @Override + public void propertyChange(PropertyChangeEvent e) { + update(); + } + } + + RebinListener rebinListener= new RebinListener(); + + public StackedHistogramRenderer( DasPlot parent, DataSetDescriptor dsd, DasAxis zAxis, DasLabelAxis yAxis ) { + super(); + + this.yAxis= yAxis; + this.zAxis= zAxis; + + zAxis.addPropertyChangeListener(rebinListener); + + setDataSetDescriptor( dsd ); + } + + + @Override + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + Graphics2D g2= (Graphics2D)g.create(); + + Point2D p; + if (getDataSet()==null && getLastException()!=null ) { + renderException(g2,xAxis,yAxis,getLastException()); + } else if (plotImage!=null) { + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ); + p= new Point2D.Float( xAxis.getColumn().getDMinimum(), yAxis.getRow().getDMinimum() ); + + g2.drawImage( plotImage,(int)(p.getX()+0.5),(int)(p.getY()+0.5), getParent() ); + + } + g2.dispose(); + } + + @Override + protected void installRenderer() { + DasCanvas canvas= parent.getCanvas(); + littleRow= new DasRow( canvas, 0.5,0.6 ); + zAxisConnector= new RowRowConnector( canvas, littleRow, zAxis.getRow(), parent.getColumn(), zAxis.getColumn() ); + zAxisConnector.setVisible(false); + canvas.add(zAxisConnector); + + yAxis.setFloppyItemSpacing(true); + yAxis.setOutsidePadding(1); + + this.peaksIndicator= PeaksIndicator.MaxLines; + + DasMouseInputAdapter mouseAdapter = parent.getDasMouseInputAdapter(); + + //TODO: consider delaying construction of slicers until first event + VerticalSpectrogramSlicer vSlicer = VerticalSpectrogramSlicer.createSlicer( parent, this ); + VerticalSlicerMouseModule vsl = VerticalSlicerMouseModule.create(this); + vsl.addDataPointSelectionListener(vSlicer); + mouseAdapter.addMouseModule(vsl); + + HorizontalSpectrogramSlicer hSlicer = HorizontalSpectrogramSlicer.createSlicer( parent, this); + HorizontalSlicerMouseModule hsl = HorizontalSlicerMouseModule.create(this); + hsl.addDataPointSelectionListener(hSlicer); + mouseAdapter.addMouseModule(hsl); + + MouseModule ch= new CrossHairMouseModule(parent,this,parent.getXAxis(), parent.getYAxis()); + mouseAdapter.addMouseModule(ch); + + DasPlot p= parent; + mouseAdapter.addMouseModule( new MouseModule( p, new LengthDragRenderer(p,p.getXAxis(),p.getYAxis()), "Length" ) ); + } + + @Override + protected void uninstallRenderer() { + } + + + public void setZAxis(DasAxis zAxis) { + this.zAxis= zAxis; + throw new IllegalStateException("not supported"); + } + + @Override + public void propertyChange(java.beans.PropertyChangeEvent e) { + // this code was intended to make it so the zaxis component would move up and down + // with the labelAxis. + /* DasLabelAxis axis= (DasLabelAxis)getYAxis(); + + if ( axis!=null ) { + if ( getRow()!=DasRow.NULL ) { + if ( axis.getInterItemSpace() > getRow().getHeight()/3.5 ) { + System.out.println("axis spacing exceeds zAxis spacing"); + int[] labelPositions= axis.getLabelPositions(); + zAxisComponent.getAxis().getRow().setDPosition( labelPositions[0], labelPositions[1] ); + } else { + int xx2= getRow().getDMaximum(); + int xx1= getRow().getDMinimum(); + zAxisComponent.getAxis().getRow().setDPosition( xx1, xx2 ); + } + } + } */ + } + + /** + * @throws IllegalArgumentException if the yAxis is not an instance of DasLabelAxis + */ + public void setYAxis(DasAxis yAxis) { + if (yAxis instanceof DasLabelAxis) { + this.yAxis= (DasLabelAxis)yAxis; + yAxis.addPropertyChangeListener(this); + } else { + throw new IllegalArgumentException("You can't call setYAxis for stackedHistogramPlot"); + } + } + + protected String getPrimaryPeaksId(){ + String sPrimary = getDataSet().getPlaneIds()[0]; + + if(sPrimary.equals("")) // Handle un-named primary planes + return "peaks"; + else + return sPrimary + ".peaks"; // Handle named primary planes + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawAxisLines(Graphics2D g){ + + Color BAR_COLOR = getParent().getForeground(); + Color GREY_PEAKS_COLOR = Color.GRAY; + + if(j == (data.getYLength(0) - 1)){ /* Draw grey line */ + + yMin = yAxis.getItemMin(data.getYTagDatum(0, j)); + g.setColor(GREY_PEAKS_COLOR); + g.drawLine(xDMin, yMin, xDMax, yMin); + g.setColor(BAR_COLOR); + } + + yMax = yAxis.getItemMax(data.getYTagDatum(0, j)); + g.setColor(Color.lightGray); + g.drawLine(xDMin, yMax, xDMax, yMax); + g.setColor(BAR_COLOR); + + yMin = yAxis.getItemMin(data.getYTagDatum(0, j)); + + if(!haveLittleRow && yMin <= zmid){ + littleRow.setDPosition(yMin, yMax); + haveLittleRow = true; + this.zAxisConnector.setVisible(true); + this.zAxisConnector.repaint(); + + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private int getInterpDistance(DasAxis axis){ + // Get peaks dataset in data coordinates, not screen coordinates + TableDataSet ds = (TableDataSet) getDataSet().getPlanarView(getPrimaryPeaksId()); + + Datum dInterp; + if(override.containsKey(DataSet.PROPERTY_X_TAG_WIDTH)) + dInterp = (Datum)override.get(DataSet.PROPERTY_X_TAG_WIDTH); + else + dInterp = (Datum)ds.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + + // Use the start of the axis a the basis point for finding the interpolation + // distance in screen coordinates + Datum dBeg = axis.getDataMaximum(); + + // Too bad can't do this in java: Datum dEnd = dBeg + dInterp; + Datum dEnd = dBeg.add(dInterp); + int iMaxInterp = (int) (axis.transform(dEnd) - axis.transform(dBeg)); + return iMaxInterp; + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawLinePeaks(Graphics2D g, DasAxis xAxis){ + + int iMaxInterp = getInterpDistance(xAxis); + + GeneralPath p = new GeneralPath(); + + double x0data; + int x0, x1, y0, y1; + boolean bHasBeg = false; + + for(int ibin = 0; ibin < data.getXLength(); ibin++){ + // Do nothing until you encounter data + double zTmp = peaks.getDouble(ibin, j, data.getZUnits()); + if(data.getZUnits().isFill(zTmp) || Double.isNaN(zTmp)){ + continue; + } + + x1 = (int) xAxis.transform(binStarts[ibin], xbins.getUnits()); + y1 = (int) zAxis.transform(zTmp, data.getZUnits(), yMin, yMax); + + if(!bHasBeg){ + x0 = x1; + y0 = y1; + x0data = binStarts[ibin]; + p.moveTo(x0, Math.min(yMax, y0)); + bHasBeg = true; + } + else{ + //Check to see if last value is too far away, if so just draw a tiny + //2 pixel straight line + if((binStarts[ibin] - x0Data) > xInterp){ + + } + } + + double zz = peaks.getDouble(ibin, j, data.getZUnits()); + if(!(data.getZUnits().isFill(zz) || Double.isNaN(zz))){ + int yMax = (int) zAxis.transform(zz, data.getZUnits(), yMax, yBase1); + if(lastWasFill){ + p.moveTo(x0, Math.min(yMax, y0)); + } + p.lineTo(x0, Math.min(yMax, y0)); + + } + } + g.draw(p); + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawStdPeaks(Graphics2D g, DasAxis xAxis){ + + Color BAR_COLOR = getParent().getForeground(); + + for(int ibin = 0; ibin < data.getXLength(); ibin++){ + int x0 = (int) xAxis.transform(binStarts[ibin], xbins.getUnits()); + double zz = data.getDouble(ibin, j, data.getZUnits()); + if(!(data.getZUnits().isFill(zz) || Double.isNaN(zz))){ + int yAvg = (int) zAxis.transform(zz, data.getZUnits(), yMin, yMin); + yAvg = yAvg > (y0 - littleRowHeight) ? yAvg : (y0 - littleRowHeight); + int yHeight = (y0 - yAvg) > (0) ? (y0 - yAvg) : 0; + //yHeight= yHeight < littleRowHeight ? yHeight : littleRowHeight; + if(peaks != null){ + double peakValue = peaks.getDouble(ibin, j, peaks.getZUnits()); + if(peakValue > zAxisMin){ + int yMax = (int) zAxis.transform(peakValue, data.getZUnits(), yMax, yBase1); + yMax = Math.min(yMax, y0); + yMax = yMax > (y0 - littleRowHeight) ? yMax : (y0 - littleRowHeight); + + if(peaksIndicator == PeaksIndicator.MaxLines){ + g.drawLine(x0, yMax, x0, yMax); + } + else if(peaksIndicator == PeaksIndicator.GrayPeaks){ + g.setColor(Color.lightGray); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.RedPeaks){ + g.setColor(Color.red); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.GreenPeaks){ + g.setColor(Color.GREEN); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.BluePeaks){ + g.setColor(Color.CYAN); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.LinePeaks){ + //nothing here + } + else if(peaksIndicator == PeaksIndicator.BlackPeaks){ + g.setColor(BAR_COLOR); + g.drawLine(x0, yMax, x0, y0); + } + } + } + if(zz >= zAxisMin){ + g.drawLine(x0, yAvg, x0, yAvg + yHeight); + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawAverages(Graphics2D g, DasAxis xAxis){ + + } + + + @Override + synchronized public void updatePlotImage( + DasAxis xAxis, DasAxis yAxis_1, ProgressMonitor monitor + ) throws DasException{ + + super.updatePlotImage(xAxis, yAxis_1, monitor); + + Component parent = getParent(); + Cursor cursor0 = parent.getCursor(); + parent.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + + DasColumn column = xAxis.getColumn(); + DasRow row = yAxis.getRow(); + + int w = column.getWidth(); + int h = row.getHeight(); + + if(w == 0) return; + + // Set up graphics context + BufferedImage plotImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = plotImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if(!isTransparentBackground()){ + g.setColor(getParent().getBackground()); + g.fillRect(0, 0, plotImage.getWidth(), plotImage.getHeight()); + } + g.translate(-column.getDMinimum(), -row.getDMinimum()); + + imageXRange = xAxis.getDatumRange(); + imageYRange = yAxis.getDatumRange(); + + int xDMax = column.getDMaximum(); + int xDMin = column.getDMinimum(); + + // Handle missing/empty datasets + TableDataSet xtysData = (TableDataSet) getDataSet(); + + if(xtysData == null){ + this.plotImage = null; + if(getLastException() == null){ + this.setLastException(new DasException("null data set")); + } + g.dispose(); + return; + } + + if(xtysData.tableCount() == 0){ + this.setLastException(new DasException("empty data set")); + this.ds = null; + g.dispose(); + return; + } + + // Get the data into pixel space + DataSetRebinner rebinner = new Rebinner(); + + RebinDescriptor xbins = new RebinDescriptor( + xAxis.getDataMinimum(), xAxis.getDataMaximum(), + (int) (Math.abs(column.getWidth()) / 1) + 1, + (xAxis.isLog()) + ); + + TableDataSet data = (TableDataSet) rebinner.rebin(xtysData, xbins, null, override); + TableDataSet peaks = (TableDataSet) data.getPlanarView(getPrimaryPeaksId()); + + DasLabelAxis yAxis = (DasLabelAxis) yAxis_1; + + int zmid = zAxis.getRow().getDMiddle(); + boolean haveLittleRow = false; + + // Loop over Y dimension generating individual histograms + for(int j = 0; j < data.getYLength(0); j++){ + + int yMax, yMin; + + // Axis lines + drawAxisLines(g); + + // Data rendering code + double[] binStarts = xbins.binStarts(); + double[] binStops = xbins.binStops(); + + int littleRowHeight = yMax - yMin; + double zAxisMax = zAxis.getDataMaximum().doubleValue(xtysData.getZUnits()); + double zAxisMin = zAxis.getDataMinimum().doubleValue(xtysData.getZUnits()); + + if(yMin >= row.getDMinimum() && yMax <= row.getDMaximum()){ + + if(peaks != null){ + if(peaksIndicator == PeaksIndicator.LinePeaks) + drawLinePeaks(g, xAxis); + else + drawStdPeaks(g, xAxis); + } + + drawAverages(g, xAxis); + } + } + + g.dispose(); + this.plotImage = plotImage; + parent.setCursor(cursor0); + getParent().repaint(); + + if(sliceRebinnedData){ + super.ds = data; + } + } + + @Override + public DasAxis getZAxis() { + return zAxis; + } + + public void setZTitle(String title) { + getZAxis().setLabel(title); + } + + public class Rebinner implements DataSetRebinner { + DataSetRebinner highResRebinner; + DataSetRebinner lowResRebinner; + Rebinner() { + highResRebinner= new NearestNeighborTableRebinner(); + //highResRebinner= new AveragePeakTableRebinner(); + lowResRebinner= new AveragePeakTableRebinner(); + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override + ) throws IllegalArgumentException, DasException { + Datum xwidth= (Datum)ds.getProperty( "xTagWidth" ); + if ( xwidth==null ) xwidth= DataSetUtil.guessXTagWidth((TableDataSet)ds); + Units rdUnits= x.getUnits(); + if ( rdUnits instanceof LocationUnits ) { + rdUnits= ((LocationUnits)rdUnits).getOffsetUnits(); + } + + try { + DataSet result; + if ( x.binWidth() < xwidth.doubleValue(rdUnits) ) { + logger.fine("using rebinner "+highResRebinner); + result= highResRebinner.rebin( ds, x, y, override ); + } else { + logger.fine("using rebinner "+lowResRebinner); + result= lowResRebinner.rebin( ds, x, y, override ); + } + return result; + } catch ( IllegalArgumentException | DasException e ) { + DasExceptionHandler.handle(e); + return null; + } + } + + } + + /** Getter for property peaksIndicator. + * @return Value of property peaksIndicator. + */ + public PeaksIndicator getPeaksIndicator() { + return this.peaksIndicator; + } + + /** Setter for property peaksIndicator. + * @param peaksIndicator New value of property peaksIndicator. + */ + public void setPeaksIndicator(PeaksIndicator peaksIndicator) { + this.peaksIndicator= peaksIndicator; + refreshImage(); + } + + /** Getter for property sliceRebinnedData. + * @return Value of property sliceRebinnedData. + * + */ + public boolean isSliceRebinnedData() { + return this.sliceRebinnedData; + } + + /** Setter for property sliceRebinnedData. + * @param sliceRebinnedData New value of property sliceRebinnedData. + * + */ + public void setSliceRebinnedData(boolean sliceRebinnedData) { + this.sliceRebinnedData = sliceRebinnedData; + } + protected boolean transparentBackground = false; + public static final String PROP_TRANSPARENTBACKGROUND = "transparentBackground"; + + public boolean isTransparentBackground() { + return transparentBackground; + } + + public void setTransparentBackground(boolean transparentBackground) { + boolean oldTransparentBackground = this.transparentBackground; + this.transparentBackground = transparentBackground; + propertyChangeSupport.firePropertyChange(PROP_TRANSPARENTBACKGROUND, oldTransparentBackground, transparentBackground); + } + + @Override + public Element getDOMElement(Document document) { + + Element element = document.createElement("stackedHistogram"); + element.setAttribute("zAxis", zAxis.getDasName() ); + element.setAttribute("dataSetID", getDataSetID() ); + return element; + } + + public static Renderer processStackedHistogramElement(Element element, DasPlot parent, FormBase form) + throws DasPropertyException, DasNameException, ParseException + { + String dataSetID = element.getAttribute("dataSetID"); + + Renderer renderer = new StackedHistogramRenderer( parent, (DataSetDescriptor)null, (DasAxis)null, (DasLabelAxis)parent.getYAxis() ); + try { + renderer.setDataSetID(dataSetID); + } catch (DasException de) { + DasExceptionHandler.handle(de); + } + return renderer; + } + + @Override + public String getListLabel() { + return "stacked histogram"; + } + + @Override + public Icon getListIcon() { + return null; + } + +} diff --git a/dasCore/src/org/das2/graph/StippledTableRenderer.java b/dasCore/src/org/das2/graph/StippledTableRenderer.java index 2fbc5ee84..1b92c81e1 100644 --- a/dasCore/src/org/das2/graph/StippledTableRenderer.java +++ b/dasCore/src/org/das2/graph/StippledTableRenderer.java @@ -150,7 +150,9 @@ public void updatePlotImage( DasAxis xAxis, DasAxis yAxis, ProgressMonitor monit DataSetRebinner rebinner= new AverageTableRebinner(); - rebinData = (TableDataSet)rebinner.rebin(getDataSet(),xRebinDescriptor, yRebinDescriptor); + rebinData = (TableDataSet)rebinner.rebin( + getDataSet(),xRebinDescriptor, yRebinDescriptor, null + ); //TableDataSet weights= (TableDataSet)rebinData.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); int itable=0; diff --git a/dasCore/src/org/das2/graph/SymbolLineRenderer.java b/dasCore/src/org/das2/graph/SymbolLineRenderer.java index 6dad6f99b..48c75e6c9 100755 --- a/dasCore/src/org/das2/graph/SymbolLineRenderer.java +++ b/dasCore/src/org/das2/graph/SymbolLineRenderer.java @@ -28,7 +28,6 @@ import org.das2.dataset.VectorUtil; import org.das2.dataset.DataSet; import org.das2.dataset.VectorDataSet; -import org.das2.dataset.DataSetUtil; import org.das2.datum.DatumRange; import org.das2.datum.Datum; import org.das2.system.DasLogger; @@ -105,6 +104,7 @@ private void reportCount() { } + @Override public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { renderCount++; // reportCount(); @@ -208,6 +208,7 @@ public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon boolean updating=false; + @Override public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor) { /* *This was an experiment to see if updates were being performed on multiple threads. @@ -394,6 +395,7 @@ public void setLineWidth(float f) { refreshImage(); } + @Override protected void installRenderer() { if ( ! DasApplication.getDefaultApplication().isHeadless() ) { DasMouseInputAdapter mouseAdapter = parent.mouseAdapter; @@ -402,6 +404,7 @@ protected void installRenderer() { } } + @Override protected void uninstallRenderer() { } @@ -423,6 +426,7 @@ public static SymbolLineRenderer processLinePlotElement(Element element, DasPlot return renderer; } + @Override public Element getDOMElement(Document document) { Element element = document.createElement("lineplot"); @@ -461,10 +465,12 @@ public void setHistogram(final boolean b) { } } + @Override public String getListLabel() { return String.valueOf( this.getDataSetDescriptor() ); } + @Override public javax.swing.Icon getListIcon() { Image i= new BufferedImage(15,10,BufferedImage.TYPE_INT_ARGB); Graphics2D g= (Graphics2D)i.getGraphics(); @@ -485,6 +491,7 @@ public javax.swing.Icon getListIcon() { return new ImageIcon(i); } + @Override public boolean acceptContext(int x, int y) { return path!=null && path.intersects( x-5, y-5, 10, 10 ); } diff --git a/dasCore/src/org/das2/graph/TickVDescriptor.java b/dasCore/src/org/das2/graph/TickVDescriptor.java index 5553cd7aa..881ea2683 100755 --- a/dasCore/src/org/das2/graph/TickVDescriptor.java +++ b/dasCore/src/org/das2/graph/TickVDescriptor.java @@ -52,7 +52,7 @@ public static TickVDescriptor newTickVDescriptor(DatumVector majorTicks, DatumVe /** * creates descriptor with two Lists containing Datums. - * java 1.5: List + * java 1.5: List<Datum> */ public static TickVDescriptor newTickVDescriptor(List majorTicks, List minorTicks) { if (majorTicks.size() == 0 && minorTicks.size() == 0) { From fa643e7df92bb35421f480a37b567de82e359c5e Mon Sep 17 00:00:00 2001 From: Edward West Date: Tue, 5 Jan 2016 18:00:16 +0000 Subject: [PATCH 05/46] Removed unused import that was causing a warning svn path=/dasCore/netbeans_trunk/; revision=9067 --- .../src/org/das2/dataset/AverageNoInterpolateTableRebinner.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java b/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java index ecf57a7ad..b1b2bfdbd 100644 --- a/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java +++ b/dasCore/src/org/das2/dataset/AverageNoInterpolateTableRebinner.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.logging.Logger; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; /** * This rebinner will bin average elements that fall on the same bin, and will enlarge cells that From b1715ad3fda345dcf03711e25178c1777c097977 Mon Sep 17 00:00:00 2001 From: Edward West Date: Sun, 21 Feb 2016 19:41:47 +0000 Subject: [PATCH 06/46] DataSetDescriptor implementation for directly executing reader programs svn path=/dasCore/netbeans_trunk/; revision=9098 --- .../org/das2/dataset/DataSetDescriptor.java | 9 ++ .../dataset/ExecutableDataSetDescriptor.java | 96 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java diff --git a/dasCore/src/org/das2/dataset/DataSetDescriptor.java b/dasCore/src/org/das2/dataset/DataSetDescriptor.java index 21a8695b4..967a807b4 100755 --- a/dasCore/src/org/das2/dataset/DataSetDescriptor.java +++ b/dasCore/src/org/das2/dataset/DataSetDescriptor.java @@ -292,6 +292,7 @@ public String getDataSetID() { return this.dataSetID; } private static final Pattern CLASS_ID = Pattern.compile("class:([a-zA-Z0-9_\\.]+)(?:\\?(.*))?"); + private static final Pattern EXEC_ID = Pattern.compile("exec:(.+)\\?(.+)"); private static final Pattern NAME_VALUE = Pattern.compile("([_0-9a-zA-Z%+.-]+)=([_0-9a-zA-Z%+.-]+)"); /** @@ -310,9 +311,12 @@ public String getDataSetID() { */ public static DataSetDescriptor create(final String dataSetID) throws DasException { java.util.regex.Matcher classMatcher = CLASS_ID.matcher(dataSetID); + java.util.regex.Matcher execMatcher = EXEC_ID.matcher(dataSetID); DataSetDescriptor result; if (classMatcher.matches()) { result = createFromClassName(dataSetID, classMatcher); + } else if (execMatcher.matches()) { + result = createFromExecutable(dataSetID, execMatcher); } else { try { result = createFromServerAddress(new URL(dataSetID)); @@ -330,6 +334,11 @@ private static DataSetDescriptor createFromServerAddress(final URL url) throws D StreamDescriptor sd = server.getStreamDescriptor(url); return new StreamDataSetDescriptor(sd, server.getStandardDataStreamSource(url)); } + private static DataSetDescriptor createFromExecutable(String dataSetID, Matcher matcher) { + String executable = matcher.group(1); + String[] paramPatterns = matcher.group(2).split("&"); + return new ExecutableDataSetDescriptor(executable, paramPatterns); + } private static DataSetDescriptor createFromClassName(final String dataSetID, final Matcher matcher) throws DasException { try { diff --git a/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java b/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java new file mode 100644 index 000000000..a2ebb272e --- /dev/null +++ b/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java @@ -0,0 +1,96 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.dataset; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.das2.DasException; +import org.das2.DasIOException; +import org.das2.client.DataSetStreamHandler; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import org.das2.util.DasProgressMonitorInputStream; +import org.das2.util.StreamTool; +import org.das2.util.monitor.ProgressMonitor; + +/** DataSetDescriptor implementation + * + * @author eew + */ +public class ExecutableDataSetDescriptor extends DataSetDescriptor { + + private String exePath; + private String[] commandFmts; + + private static final Logger logger= DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG); + + public ExecutableDataSetDescriptor(String exePath, String[] commandFmts) { + this.exePath = exePath; + this.commandFmts = Arrays.copyOf(commandFmts, commandFmts.length); + + File exeFile = new File(exePath); + if (!exeFile.exists()) { + throw new IllegalArgumentException(exePath + " does not exist"); + } + if (!exeFile.canExecute()) { + throw new IllegalArgumentException(exePath + " is not executable"); + } + } + + @Override + protected DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + InputStream in; + DataSet result; + + String[] command = new String[commandFmts.length+1]; + command[0] = exePath; + for (int iParam = 0; iParam < commandFmts.length; iParam++) { + String sParam = commandFmts[iParam]; + if (sParam.contains("%{start}")) { + sParam = sParam.replace("%{start}", start.toString()); + } + else if (sParam.contains("%{end}")) { + sParam = sParam.replace("%{end}", end.toString()); + } + else if (sParam.contains("%{resolution}")) { + sParam = sParam.replace("%{resolution}", + Double.toString(resolution.doubleValue(Units.seconds))); + } + command[iParam+1] = sParam; + } + ProcessBuilder builder = new ProcessBuilder(command); + builder.redirectError(ProcessBuilder.Redirect.INHERIT); + try { + Process p = builder.start(); + in = p.getInputStream(); + final DasProgressMonitorInputStream mpin = new DasProgressMonitorInputStream(in, monitor); + ReadableByteChannel channel = Channels.newChannel(mpin); + + DataSetStreamHandler handler = new DataSetStreamHandler(properties, monitor); + + StreamTool.readStream(channel, handler); + return handler.getDataSet(); + + } catch (IOException ex) { + throw new DasIOException(ex); + } + + } + + @Override + public Units getXUnits() { + throw new UnsupportedOperationException("Not supported yet."); + } + +} From f42ab46704e21c545982745c34506695869e3701 Mon Sep 17 00:00:00 2001 From: Edward West Date: Sun, 21 Feb 2016 21:53:38 +0000 Subject: [PATCH 07/46] switch a call to lookupUnits back to a call to getByName since we want it to fail if units don't exist svn path=/dasCore/netbeans_trunk/; revision=9101 --- dasCore/src/org/das2/datum/EnumerationUnits.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dasCore/src/org/das2/datum/EnumerationUnits.java b/dasCore/src/org/das2/datum/EnumerationUnits.java index 985a9cf54..3449b2158 100755 --- a/dasCore/src/org/das2/datum/EnumerationUnits.java +++ b/dasCore/src/org/das2/datum/EnumerationUnits.java @@ -157,7 +157,7 @@ public static synchronized EnumerationUnits create(Object o) { } else { Units u= null; try { - u= Units.lookupUnits(c.toString() + "Unit"); + u= Units.getByName(c.toString() + "Unit"); } catch ( IllegalArgumentException ex ) { EnumerationUnits result = new EnumerationUnits(c.toString() + "Unit"); unitsInstances.put(c, result); From b82e36d855611fb2eaa67844b257efa4defead2a Mon Sep 17 00:00:00 2001 From: Edward West Date: Mon, 22 Feb 2016 04:46:38 +0000 Subject: [PATCH 08/46] removed overzealous input checking svn path=/dasCore/netbeans_trunk/; revision=9102 --- .../src/org/das2/dataset/ExecutableDataSetDescriptor.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java b/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java index a2ebb272e..f8dfed2aa 100644 --- a/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java +++ b/dasCore/src/org/das2/dataset/ExecutableDataSetDescriptor.java @@ -38,14 +38,6 @@ public class ExecutableDataSetDescriptor extends DataSetDescriptor { public ExecutableDataSetDescriptor(String exePath, String[] commandFmts) { this.exePath = exePath; this.commandFmts = Arrays.copyOf(commandFmts, commandFmts.length); - - File exeFile = new File(exePath); - if (!exeFile.exists()) { - throw new IllegalArgumentException(exePath + " does not exist"); - } - if (!exeFile.canExecute()) { - throw new IllegalArgumentException(exePath + " is not executable"); - } } @Override From 4df319a042b4a541f1ce030757bd5d2a8fd0af19 Mon Sep 17 00:00:00 2001 From: Edward West Date: Thu, 25 Feb 2016 21:25:22 +0000 Subject: [PATCH 09/46] making sure component state modifications happen on the event thread to prevent intermittent failures svn path=/dasCore/netbeans_trunk/; revision=9111 --- dasCore/src/org/das2/graph/DasCanvas.java | 31 +++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/dasCore/src/org/das2/graph/DasCanvas.java b/dasCore/src/org/das2/graph/DasCanvas.java index f21ac3d93..5a1450434 100755 --- a/dasCore/src/org/das2/graph/DasCanvas.java +++ b/dasCore/src/org/das2/graph/DasCanvas.java @@ -914,23 +914,32 @@ public void resizeAllComponents() { */ public void prepareForOutput(int width, int height) { if (SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("dasCanvas.prepareForOutput must not be called from event queue!"); - setPreferredWidth(width); - setPreferredHeight(height); - if ("true".equals(DasApplication.getProperty("java.awt.headless", "false"))) { - this.addNotify(); - logger.finer("setSize(" + getPreferredSize() + ")"); - this.setSize(getPreferredSize()); - logger.finer("validate()"); - this.validate(); - - resizeAllComponents(); - } try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + setPreferredWidth(width); + setPreferredHeight(height); + + if ("true".equals(DasApplication.getProperty("java.awt.headless", "false"))) { + DasCanvas.this.addNotify(); + logger.finer("setSize(" + getPreferredSize() + ")"); + DasCanvas.this.setSize(getPreferredSize()); + logger.finer("validate()"); + DasCanvas.this.validate(); + + resizeAllComponents(); + } + } + }); waitUntilIdle(); } catch (InterruptedException ex) { throw new RuntimeException(ex); } + catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } } From 791ab0f22a8c5da77cfbf4c434816e4069771290 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Sat, 12 Mar 2016 05:22:42 +0000 Subject: [PATCH 10/46] Added new units and extra fill data types svn path=/dasCore/netbeans_trunk/; revision=9127 --- dasCore/src/org/das2/datum/Units.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/dasCore/src/org/das2/datum/Units.java b/dasCore/src/org/das2/datum/Units.java index a092750a1..87b2a196f 100755 --- a/dasCore/src/org/das2/datum/Units.java +++ b/dasCore/src/org/das2/datum/Units.java @@ -171,11 +171,16 @@ public UnitsConverter getInverse() { static { unitsMap.put("V**2 m**-2 Hz**-1", v2pm2Hz); } + public static final Units V_per_m = new NumberUnits("V m**-1"); + static { + unitsMap.put("V/m", V_per_m); + } /** * Watts / m2 */ public static final Units wpm2= new NumberUnits("W/m!a-2!n"); + public static final Units W_per_m2_Hz = new NumberUnits("W m**-2 Hz**-1"); public static final Units meters = new NumberUnits("m"); @@ -563,11 +568,20 @@ public Basis getBasis() { public abstract Datum createDatum( double value, double resolution ); private final static double FILL_DOUBLE= -1e31; + private final static float FILL_FLOAT= -1e31f; + private final static int FILL_INT= Integer.MAX_VALUE; + private final static long FILL_LONG= Long.MAX_VALUE; public double getFillDouble() { return FILL_DOUBLE; } + public float getFillFloat() { return FILL_FLOAT; } + public int getFillInt() { return FILL_INT; } + public long getFillLong() { return FILL_LONG; } public Datum getFillDatum() { return this.createDatum(FILL_DOUBLE); } public boolean isFill( double value ) { return value Date: Wed, 6 Apr 2016 19:01:02 +0000 Subject: [PATCH 11/46] added final to local variable svn path=/dasCore/netbeans_trunk/; revision=9183 --- dasCore/src/org/das2/graph/DasCanvas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dasCore/src/org/das2/graph/DasCanvas.java b/dasCore/src/org/das2/graph/DasCanvas.java index 5a1450434..5a7d8f6ec 100755 --- a/dasCore/src/org/das2/graph/DasCanvas.java +++ b/dasCore/src/org/das2/graph/DasCanvas.java @@ -912,7 +912,7 @@ public void resizeAllComponents() { * * @throws IllegalStateException if called from the event queue. */ - public void prepareForOutput(int width, int height) { + public void prepareForOutput(final int width, final int height) { if (SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("dasCanvas.prepareForOutput must not be called from event queue!"); try { From ada0d99e52b6a97748a9eb023a017e85e1a4308f Mon Sep 17 00:00:00 2001 From: Edward West Date: Thu, 7 Apr 2016 22:26:16 +0000 Subject: [PATCH 12/46] svn path=/dasCore/netbeans_trunk/; revision=9187 --- dasCore/src/test/dataset/AverageTableRebinnerBenchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dasCore/src/test/dataset/AverageTableRebinnerBenchmark.java b/dasCore/src/test/dataset/AverageTableRebinnerBenchmark.java index 8f711e383..a5f8755cc 100644 --- a/dasCore/src/test/dataset/AverageTableRebinnerBenchmark.java +++ b/dasCore/src/test/dataset/AverageTableRebinnerBenchmark.java @@ -28,7 +28,7 @@ public static void main( String[] args ) throws DasException { long t0= System.currentTimeMillis(); long totalMillis=0; for ( int j=0; j<30.; j++ ) { - rebin.rebin(tds, ddy, ddy); + rebin.rebin(tds, ddy, ddy, java.util.Collections.EMPTY_MAP); long t1= System.currentTimeMillis() - t0; System.err.println( t1 ); totalMillis+= t1; From 2775fedc06b0ca6c3ad4e6bf9e41756d4848e5fb Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Wed, 13 Jul 2016 17:02:10 +0000 Subject: [PATCH 13/46] bugfix: yoffsets was incorrectly indexed, and this showed when using Juno data. svn path=/dasCore/netbeans_trunk/; revision=9312 --- dasCore/src/org/das2/dataset/ClippedTableDataSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dasCore/src/org/das2/dataset/ClippedTableDataSet.java b/dasCore/src/org/das2/dataset/ClippedTableDataSet.java index 1fc6bcf66..bc9b73be1 100755 --- a/dasCore/src/org/das2/dataset/ClippedTableDataSet.java +++ b/dasCore/src/org/das2/dataset/ClippedTableDataSet.java @@ -62,7 +62,7 @@ void calculateYOffsets( Datum ymin, Datum ymax ) { for ( int itable=tableOffset; itable Date: Wed, 13 Jul 2016 19:00:02 +0000 Subject: [PATCH 14/46] findFirstVisibleSegment sometimes returns -1. svn path=/dasCore/netbeans_trunk/; revision=9315 --- dasCore/src/org/das2/graph/SymbolLineRenderer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dasCore/src/org/das2/graph/SymbolLineRenderer.java b/dasCore/src/org/das2/graph/SymbolLineRenderer.java index 48c75e6c9..cd9c87a74 100755 --- a/dasCore/src/org/das2/graph/SymbolLineRenderer.java +++ b/dasCore/src/org/das2/graph/SymbolLineRenderer.java @@ -284,6 +284,13 @@ public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressM double j0 = Double.NaN; boolean skippedLast = true; + if ( ixmin<0 ) { + ixmin=0; + } + if ( ixmax>=dataSet.getXLength() ) { + ixmax= dataSet.getXLength()-1; + } + for (int index = ixmin; index <= ixmax; index++) { double x = dataSet.getXTagDouble(index, xUnits); double y = dataSet.getDouble(index, yUnits); From 4e89faa408337b95a34e79dc10ad487761b3aa1d Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Thu, 14 Jul 2016 21:28:57 +0000 Subject: [PATCH 15/46] avoid doing GUI stuff off of the event thread. svn path=/dasCore/netbeans_trunk/; revision=9320 --- .../das2/components/DataPointRecorder.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dasCore/src/org/das2/components/DataPointRecorder.java b/dasCore/src/org/das2/components/DataPointRecorder.java index 1661b21de..367d38a63 100755 --- a/dasCore/src/org/das2/components/DataPointRecorder.java +++ b/dasCore/src/org/das2/components/DataPointRecorder.java @@ -794,11 +794,14 @@ private void updateClients() { } private void updateStatus() { - String statusString = (saveFile == null ? "" : (String.valueOf(saveFile) + " ")) + + Runnable run= () -> { + String statusString = (saveFile == null ? "" : (String.valueOf(saveFile) + " ")) + (modified ? "(modified)" : ""); - messageLabel.setText(statusString); + messageLabel.setText(statusString); + }; + SwingUtilities.invokeLater(run); } - + private void insertInternal(DataPoint newPoint) { int newSelect; if (sorted) { @@ -851,7 +854,7 @@ public void addDataPoint(Datum x, Datum y, Map planes) { index++; } - myTableModel.fireTableStructureChanged(); + //myTableModel.fireTableStructureChanged(); } if (!x.getUnits().isConvertableTo(unitsArray[0])) { @@ -864,7 +867,14 @@ public void addDataPoint(Datum x, Datum y, Map planes) { insertInternal(new DataPoint(x, y, new LinkedHashMap(planes))); if (active) { - fireDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + Runnable run= new Runnable() { + public void run() { + myTableModel.fireTableStructureChanged(); + fireDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + } + }; + SwingUtilities.invokeLater(run); + } } From 6ac7430d475920e8e02ae2adb0c3ac4a0c33101b Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Thu, 14 Jul 2016 21:38:15 +0000 Subject: [PATCH 16/46] The big move to MAVEN, sources moved from netbeans_trunk svn path=/core/stable/dasCore/; revision=9325 --- .../org/das2/CancelledOperationException.java | 47 + .../main/java/org/das2/DasApplication.java | 415 ++ .../src/main/java/org/das2/DasException.java | 48 + .../main/java/org/das2/DasIOException.java | 55 + .../main/java/org/das2/DasNameException.java | 40 + .../src/main/java/org/das2/DasProperties.java | 308 ++ .../java/org/das2/DasPropertyException.java | 99 + .../src/main/java/org/das2/NameContext.java | 365 ++ .../src/main/java/org/das2/apiProblems.txt | 18 + .../org/das2/beans/AccessLevelBeanInfo.java | 347 ++ .../org/das2/beans/AttachedLabelBeanInfo.java | 66 + .../main/java/org/das2/beans/BeansUtil.java | 284 ++ .../beans/ColumnColumnConnectorBeanInfo.java | 52 + .../das2/beans/CrossHairRendererBeanInfo.java | 48 + .../das2/beans/DasApplicationBeanInfo.java | 48 + .../java/org/das2/beans/DasAxisBeanInfo.java | 63 + .../org/das2/beans/DasCanvasBeanInfo.java | 71 + .../beans/DasCanvasComponentBeanInfo.java | 60 + .../org/das2/beans/DasColorBarBeanInfo.java | 67 + .../org/das2/beans/DasColumnBeanInfo.java | 49 + .../org/das2/beans/DasLabelAxisBeanInfo.java | 54 + .../beans/DasMouseInputAdapterBeanInfo.java | 44 + .../java/org/das2/beans/DasPlotBeanInfo.java | 52 + .../java/org/das2/beans/DasRowBeanInfo.java | 52 + .../org/das2/beans/DasServerBeanInfo.java | 45 + .../das2/beans/DataPointRecorderBeanInfo.java | 39 + .../das2/beans/DataSetDescriptorBeanInfo.java | 42 + .../beans/ImplicitAccessLevelBeanInfo.java | 59 + .../das2/beans/LabelDragRendererBeanInfo.java | 38 + .../org/das2/beans/MouseModuleBeanInfo.java | 39 + .../java/org/das2/beans/RectangleEditor.java | 31 + .../java/org/das2/beans/RendererBeanInfo.java | 44 + .../das2/beans/RowRowConnectorBeanInfo.java | 43 + .../beans/SpectrogramRendererBeanInfo.java | 49 + .../StackedHistogramRendererBeanInfo.java | 54 + .../StreamDataSetDescriptorBeanInfo.java | 52 + .../beans/SymbolLineRendererBeanInfo.java | 52 + .../das2/beans/TickCurveRendererBeanInfo.java | 38 + .../main/java/org/das2/beans/UnitsEditor.java | 26 + .../src/main/java/org/das2/beans/package.html | 22 + .../main/java/org/das2/beans/scratchpad.txt | 43 + .../das2/client/AccessDeniedException.java | 37 + .../java/org/das2/client/AccountManager.java | 114 + .../java/org/das2/client/Authenticator.java | 210 + .../main/java/org/das2/client/DasServer.java | 616 +++ .../org/das2/client/DasServerException.java | 41 + .../client/DasServerNotFoundException.java | 46 + ...ataSetDescriptorNotAvailableException.java | 42 + .../org/das2/client/DataSetStreamHandler.java | 403 ++ .../client/FakeStandardDataStreamSource.java | 123 + .../org/das2/client/InputStreamMeter.java | 165 + .../src/main/java/org/das2/client/Key.java | 42 + .../das2/client/NoSuchDataSetException.java | 46 + .../das2/client/StandardDataStreamSource.java | 40 + .../das2/client/StreamDataSetDescriptor.java | 475 +++ .../client/WebStandardDataStreamSource.java | 479 +++ .../main/java/org/das2/client/package.html | 6 + .../components/AngleSpectrogramSlicer.java | 248 ++ .../org/das2/components/AsciiFileParser.form | 182 + .../org/das2/components/AsciiFileParser.java | 434 +++ .../java/org/das2/components/BatchMaster.java | 232 ++ .../das2/components/ColorBarComponent.java | 38 + .../org/das2/components/ComponentsUtil.java | 76 + .../org/das2/components/DasAxisSelector.java | 113 + .../org/das2/components/DasProgressPanel.java | 565 +++ .../das2/components/DasTimeRangeSelector.java | 456 +++ .../das2/components/DataPointRecorder.java | 1144 ++++++ .../das2/components/DataPointReporter.java | 81 + .../org/das2/components/DataSetBrowser.java | 108 + .../java/org/das2/components/DatumEditor.java | 440 +++ .../org/das2/components/DatumRangeEditor.java | 388 ++ .../das2/components/FavoritesSelector.java | 188 + .../das2/components/FixedColumnTextArea.java | 74 + .../org/das2/components/HistogramSlicer.java | 292 ++ .../HorizontalSpectrogramSlicer.java | 198 + .../das2/components/TearoffTabbedPane.java | 663 ++++ .../java/org/das2/components/Toolbox.java | 513 +++ .../VerticalSpectrogramAverager.java | 195 + .../components/VerticalSpectrogramSlicer.java | 234 ++ .../java/org/das2/components/package.html | 4 + .../propertyeditor/BooleanEditor.java | 188 + .../propertyeditor/ColorCellRenderer.java | 121 + .../propertyeditor/ColorEditor.java | 200 + .../propertyeditor/Displayable.java | 26 + .../components/propertyeditor/Editable.java | 11 + .../propertyeditor/Enumeration.java | 30 + .../propertyeditor/EnumerationEditor.java | 338 ++ .../FloatingPointDocumentFilter.java | 70 + .../FloatingPointFormatter.java | 52 + .../IndexedPropertyItemTreeNode.java | 87 + .../IndexedPropertyTreeNode.java | 109 + .../components/propertyeditor/MapEditor.java | 73 + .../propertyeditor/PeerPropertyTreeNode.java | 167 + .../propertyeditor/PropertyCellEditor.java | 275 ++ .../propertyeditor/PropertyCellRenderer.java | 132 + .../propertyeditor/PropertyEditor.java | 493 +++ .../propertyeditor/PropertyEditorAdapter.java | 262 ++ .../propertyeditor/PropertyTreeNode.java | 361 ++ .../PropertyTreeNodeInterface.java | 38 + .../components/propertyeditor/package.html | 5 + .../treetable/TreeTableCellRenderer.java | 40 + .../components/treetable/TreeTableModel.java | 156 + .../components/treetable/TreeTableNode.java | 40 + .../das2/components/treetable/package.html | 4 + dasCore/src/main/java/org/das2/cvs_status.txt | 50 + .../java/org/das2/dasml/CommandAction.java | 56 + .../java/org/das2/dasml/CommandBlock.java | 392 ++ .../org/das2/dasml/CommandBlockEditor.java | 955 +++++ .../main/java/org/das2/dasml/DOMBuilder.java | 210 + .../java/org/das2/dasml/DasMLValidator.java | 680 ++++ .../org/das2/dasml/DataFormatException.java | 37 + .../dasml/DefaultComponentDnDSupport.java | 59 + .../main/java/org/das2/dasml/FormBase.java | 712 ++++ .../main/java/org/das2/dasml/FormButton.java | 228 ++ .../org/das2/dasml/FormButtonBeanInfo.java | 44 + .../java/org/das2/dasml/FormCheckBox.java | 226 ++ .../org/das2/dasml/FormCheckBoxBeanInfo.java | 45 + .../main/java/org/das2/dasml/FormChoice.java | 248 ++ .../org/das2/dasml/FormChoiceBeanInfo.java | 45 + .../java/org/das2/dasml/FormComponent.java | 62 + .../java/org/das2/dasml/FormContainer.java | 624 +++ .../main/java/org/das2/dasml/FormList.java | 320 ++ .../java/org/das2/dasml/FormListBeanInfo.java | 43 + .../main/java/org/das2/dasml/FormPanel.java | 186 + .../org/das2/dasml/FormPanelBeanInfo.java | 43 + .../java/org/das2/dasml/FormRadioButton.java | 190 + .../das2/dasml/FormRadioButtonBeanInfo.java | 45 + .../org/das2/dasml/FormRadioButtonGroup.java | 129 + .../dasml/FormRadioButtonGroupBeanInfo.java | 43 + .../src/main/java/org/das2/dasml/FormTab.java | 284 ++ .../java/org/das2/dasml/FormTabBeanInfo.java | 42 + .../main/java/org/das2/dasml/FormText.java | 129 + .../java/org/das2/dasml/FormTextBeanInfo.java | 41 + .../java/org/das2/dasml/FormTextField.java | 192 + .../org/das2/dasml/FormTextFieldBeanInfo.java | 43 + .../main/java/org/das2/dasml/FormWindow.java | 437 +++ .../org/das2/dasml/FormWindowBeanInfo.java | 65 + .../main/java/org/das2/dasml/ListOption.java | 107 + .../main/java/org/das2/dasml/OptionList.java | 33 + .../java/org/das2/dasml/OptionListEditor.java | 598 +++ .../main/java/org/das2/dasml/Orientation.java | 67 + .../java/org/das2/dasml/ParsedExpression.java | 734 ++++ .../das2/dasml/ParsedExpressionException.java | 37 + .../java/org/das2/dasml/SerializeUtil.java | 322 ++ .../das2/dasml/TransferableFormComponent.java | 215 ++ .../src/main/java/org/das2/dasml/package.html | 4 + .../java/org/das2/dasml/schema/action.xsd | 1 + .../java/org/das2/dasml/schema/canvas.xsd | 257 ++ .../org/das2/dasml/schema/commandblock.xsd | 48 + .../org/das2/dasml/schema/commandfragment.xsd | 8 + .../main/java/org/das2/dasml/schema/dasML.xsd | 177 + .../java/org/das2/dasml/schema/dasMLtypes.xsd | 66 + .../org/das2/dataset/AbstractDataSet.java | 177 + .../das2/dataset/AbstractDataSetCache.java | 189 + .../das2/dataset/AbstractTableDataSet.java | 213 ++ .../das2/dataset/AbstractVectorDataSet.java | 59 + .../org/das2/dataset/AppendTableDataSet.java | 239 ++ .../AverageNoInterpolateTableRebinner.java | 333 ++ .../dataset/AveragePeakTableRebinner.java | 163 + .../das2/dataset/AverageTableRebinner.java | 733 ++++ .../main/java/org/das2/dataset/CacheTag.java | 116 + .../org/das2/dataset/ClippedTableDataSet.java | 266 ++ .../das2/dataset/ClippedVectorDataSet.java | 88 + .../dataset/ConstantDataSetDescriptor.java | 78 + .../org/das2/dataset/DataRequestThread.java | 188 + .../java/org/das2/dataset/DataRequestor.java | 36 + .../main/java/org/das2/dataset/DataSet.java | 193 + .../java/org/das2/dataset/DataSetCache.java | 38 + .../org/das2/dataset/DataSetConsumer.java | 32 + .../org/das2/dataset/DataSetDescriptor.java | 406 ++ .../org/das2/dataset/DataSetRebinner.java | 49 + .../das2/dataset/DataSetStreamProducer.java | 294 ++ .../org/das2/dataset/DataSetUpdateEvent.java | 80 + .../das2/dataset/DataSetUpdateListener.java | 16 + .../java/org/das2/dataset/DataSetUtil.java | 493 +++ .../org/das2/dataset/DefaultTableDataSet.java | 611 +++ .../das2/dataset/DefaultVectorDataSet.java | 237 ++ .../dataset/ExecutableDataSetDescriptor.java | 88 + .../org/das2/dataset/FastTableDataSet.java | 17 + .../java/org/das2/dataset/GapListDouble.java | 196 + .../das2/dataset/GenericQernalFactory.java | 75 + .../das2/dataset/LimitCountDataSetCache.java | 123 + .../dataset/LimitSizeBytesDataSetCache.java | 135 + .../org/das2/dataset/NNQernalFactory.java | 97 + .../dataset/NearestNeighborTableDataSet.java | 319 ++ .../dataset/NearestNeighborTableRebinner.java | 49 + .../das2/dataset/NewAverageTableRebinner.java | 486 +++ .../dataset/NoDataInIntervalException.java | 36 + .../dataset/NoInterpolateQernalFactory.java | 73 + .../das2/dataset/NoKeyProvidedException.java | 38 + .../org/das2/dataset/NullDataSetCache.java | 37 + .../org/das2/dataset/PeakTableRebinner.java | 132 + .../org/das2/dataset/QernalTableRebinner.java | 168 + .../org/das2/dataset/QuickVectorDataSet.java | 64 + .../org/das2/dataset/RebinDescriptor.java | 251 ++ .../org/das2/dataset/SimpleDataSetCache.java | 90 + .../org/das2/dataset/SimpleTableDataSet.java | 176 + .../org/das2/dataset/SingleVectorDataSet.java | 81 + .../main/java/org/das2/dataset/SyncUtil.java | 166 + .../java/org/das2/dataset/TableDataSet.java | 158 + .../org/das2/dataset/TableDataSetBuilder.java | 431 +++ .../das2/dataset/TableDataSetConsumer.java | 37 + .../das2/dataset/TableDataSetDecorator.java | 17 + .../org/das2/dataset/TableDataSetGridder.java | 107 + .../org/das2/dataset/TableDataSetWrapper.java | 141 + .../main/java/org/das2/dataset/TableUtil.java | 445 +++ .../org/das2/dataset/TagMapTableDataSet.java | 203 + .../java/org/das2/dataset/VectorDataSet.java | 58 + .../das2/dataset/VectorDataSetBuilder.java | 270 ++ .../java/org/das2/dataset/VectorUtil.java | 268 ++ .../java/org/das2/dataset/ViewDataSet.java | 69 + .../org/das2/dataset/WeightsTableDataSet.java | 154 + .../das2/dataset/WritableTableDataSet.java | 232 ++ .../java/org/das2/dataset/XSliceDataSet.java | 71 + .../org/das2/dataset/XTagsVectorDataSet.java | 77 + .../java/org/das2/dataset/YSliceDataSet.java | 62 + .../main/java/org/das2/dataset/cvs_status.txt | 44 + .../main/java/org/das2/dataset/package.html | 19 + .../dataset/parser/VectorDataSetParser.java | 210 + .../java/org/das2/dataset/parser/package.html | 3 + .../main/java/org/das2/dataset/scratchPad.txt | 70 + .../das2/dataset/test/BigVectorDataSet.java | 43 + .../das2/dataset/test/ContourMeDataSet.java | 34 + .../das2/dataset/test/DistTableDataSet.java | 54 + .../dataset/test/FunctionTableDataSet.java | 207 + .../dataset/test/MendelbrotDataLoader.java | 208 + .../test/MendelbrotDataSetDescriptor.java | 145 + .../das2/dataset/test/OrbitVectorDataSet.java | 123 + .../test/PolynomialDataSetDescriptor.java | 107 + .../org/das2/dataset/test/RipplesDataSet.java | 81 + .../test/RipplesDataSetDescriptor.java | 117 + .../test/SineWaveDataSetDescriptor.java | 124 + .../org/das2/dataset/test/SineWaveTable.java | 26 + .../das2/dataset/test/WavVectorDataSet.java | 198 + .../java/org/das2/dataset/test/package.html | 3 + .../src/main/java/org/das2/datum/Basis.java | 87 + .../java/org/das2/datum/BestFormatter.txt | 37 + .../java/org/das2/datum/CalendarTime.java | 1016 +++++ .../src/main/java/org/das2/datum/Datum.java | 576 +++ .../main/java/org/das2/datum/DatumRange.java | 293 ++ .../java/org/das2/datum/DatumRangeUtil.java | 1067 ++++++ .../main/java/org/das2/datum/DatumUtil.java | 355 ++ .../main/java/org/das2/datum/DatumVector.java | 190 + .../java/org/das2/datum/EnumerationUnits.java | 228 ++ .../datum/InconvertibleUnitsException.java | 25 + .../java/org/das2/datum/LocationUnits.java | 95 + .../java/org/das2/datum/MonthDatumRange.java | 82 + .../main/java/org/das2/datum/NumberUnits.java | 276 ++ .../main/java/org/das2/datum/TimeContext.java | 76 + .../org/das2/datum/TimeLocationUnits.java | 58 + .../main/java/org/das2/datum/TimeUtil.java | 400 ++ .../src/main/java/org/das2/datum/Units.java | 821 ++++ .../java/org/das2/datum/UnitsConverter.java | 227 ++ .../main/java/org/das2/datum/UnitsUtil.java | 153 + .../org/das2/datum/format/DatumFormatter.java | 99 + .../datum/format/DatumFormatterFactory.java | 38 + .../datum/format/DefaultDatumFormatter.java | 185 + .../format/DefaultDatumFormatterFactory.java | 61 + .../format/EnumerationDatumFormatter.java | 47 + .../EnumerationDatumFormatterFactory.java | 51 + .../format/ExponentialDatumFormatter.java | 107 + .../format/LatinPrefixDatumFormatter.java | 98 + .../das2/datum/format/TimeDatumFormatter.java | 350 ++ .../format/TimeDatumFormatterFactory.java | 55 + .../java/org/das2/datum/format/package.html | 19 + .../src/main/java/org/das2/datum/package.html | 18 + .../datum/swing/DatumJFormatterFactory.java | 69 + .../das2/datum/swing/SwingDatumFormatter.java | 61 + .../event/AngleSelectionDragRenderer.java | 41 + .../das2/event/AngleSlicerMouseModule.java | 83 + .../org/das2/event/AnnotatorMouseModule.java | 54 + .../org/das2/event/ArrowDragRenderer.java | 51 + .../org/das2/event/BoxGesturesRenderer.java | 82 + .../event/BoxRangeSelectorMouseModule.java | 113 + .../main/java/org/das2/event/BoxRenderer.java | 104 + .../org/das2/event/BoxSelectionEvent.java | 155 + .../org/das2/event/BoxSelectionListener.java | 32 + .../das2/event/BoxSelectorMouseModule.java | 300 ++ .../java/org/das2/event/BoxZoomDialog.form | 165 + .../java/org/das2/event/BoxZoomDialog.java | 229 ++ .../org/das2/event/BoxZoomMouseModule.java | 209 + .../event/ColorBarRepaletteMouseModule.java | 114 + .../event/CommentDataPointSelectionEvent.java | 36 + .../org/das2/event/CrossHairMouseModule.java | 146 + .../org/das2/event/CrossHairRenderer.java | 460 +++ .../org/das2/event/CutoffMouseModule.java | 725 ++++ .../main/java/org/das2/event/DasEvent.java | 37 + .../org/das2/event/DasEventMulticaster.java | 122 + .../java/org/das2/event/DasMouseEvent.java | 37 + .../org/das2/event/DasMouseInputAdapter.java | 1157 ++++++ .../org/das2/event/DasSelectionEvent.java | 148 + .../java/org/das2/event/DasUpdateEvent.java | 39 + .../das2/event/DataPointSelectionEvent.java | 104 + .../event/DataPointSelectionListener.java | 32 + .../event/DataPointSelectorMouseModule.java | 204 + .../das2/event/DataRangeSelectionEvent.java | 87 + .../event/DataRangeSelectionListener.java | 32 + .../das2/event/DisplayDataMouseModule.java | 272 ++ .../java/org/das2/event/DragRenderer.java | 62 + .../org/das2/event/DumpToFileMouseModule.java | 115 + .../org/das2/event/EmptyDragRenderer.java | 52 + .../org/das2/event/FrequencyDragRenderer.java | 198 + .../src/main/java/org/das2/event/Gesture.java | 46 + .../java/org/das2/event/GesturesRenderer.java | 138 + .../event/HorizontalDragRangeRenderer.java | 101 + ...orizontalDragRangeSelectorMouseModule.java | 111 + .../das2/event/HorizontalDragRenderer.java | 36 + .../HorizontalFrequencyDragRenderer.java | 170 + .../HorizontalRangeGesturesRenderer.java | 125 + .../das2/event/HorizontalRangeRenderer.java | 95 + .../HorizontalRangeSelectorMouseModule.java | 117 + .../HorizontalRangeTorsionMouseModule.java | 109 + .../HorizontalSliceSelectionRenderer.java | 86 + .../event/HorizontalSlicerMouseModule.java | 119 + .../org/das2/event/LabelDragRenderer.java | 333 ++ .../org/das2/event/LengthDragRenderer.java | 196 + .../java/org/das2/event/MouseBoxEvent.java | 64 + .../java/org/das2/event/MouseDragEvent.java | 55 + .../main/java/org/das2/event/MouseModule.java | 154 + .../das2/event/MousePointSelectionEvent.java | 57 + .../MouseRangeGestureSelectionEvent.java | 60 + .../das2/event/MouseRangeSelectionEvent.java | 62 + .../das2/event/MoveComponentMouseModule.java | 147 + .../das2/event/PeakDetectorMouseModule.java | 599 +++ .../das2/event/PointSlopeDragRenderer.java | 61 + .../das2/event/RangeAnnotatorMouseModule.java | 34 + .../das2/event/TimeRangeSelectionEvent.java | 51 + .../event/TimeRangeSelectionListener.java | 32 + .../event/TimeRangeSelectorMouseModule.java | 137 + .../event/VerticalRangeGesturesRenderer.java | 114 + .../VerticalRangeSelectorMouseModule.java | 118 + .../event/VerticalSliceSelectionRenderer.java | 84 + .../das2/event/VerticalSlicerMouseModule.java | 111 + .../org/das2/event/ZoomOutMouseModule.java | 62 + .../org/das2/event/ZoomPanMouseModule.java | 256 ++ .../src/main/java/org/das2/event/package.html | 18 + .../main/java/org/das2/event/scratchPad.txt | 18 + .../java/org/das2/fsm/FileStorageModel.java | 774 ++++ ...ageModelAvailabilityDataSetDescriptor.java | 87 + .../org/das2/fsm/FileStorageModelNew.java | 336 ++ .../src/main/java/org/das2/fsm/package.html | 17 + .../src/main/java/org/das2/graph/Arrow.java | 90 + .../java/org/das2/graph/AttachedLabel.java | 551 +++ .../main/java/org/das2/graph/Auralizor.java | 90 + .../org/das2/graph/ColumnColumnConnector.java | 286 ++ .../java/org/das2/graph/ContoursRenderer.java | 455 +++ .../java/org/das2/graph/CurveRenderer.java | 199 + .../java/org/das2/graph/DasAnnotation.java | 377 ++ .../org/das2/graph/DasAnnotationBeanInfo.java | 82 + .../src/main/java/org/das2/graph/DasAxis.java | 3399 +++++++++++++++++ .../main/java/org/das2/graph/DasCanvas.java | 2393 ++++++++++++ .../org/das2/graph/DasCanvasComponent.java | 504 +++ .../graph/DasCanvasComponentInterface.java | 32 + .../org/das2/graph/DasCanvasStateSupport.java | 100 + .../main/java/org/das2/graph/DasColorBar.java | 657 ++++ .../main/java/org/das2/graph/DasColumn.java | 193 + .../org/das2/graph/DasDevicePosition.java | 583 +++ .../org/das2/graph/DasEventsIndicator.java | 71 + .../java/org/das2/graph/DasLabelAxis.java | 470 +++ .../org/das2/graph/DasNumberFormatter.java | 192 + .../src/main/java/org/das2/graph/DasPlot.java | 1543 ++++++++ .../das2/graph/DasRendererUpdateEvent.java | 47 + .../src/main/java/org/das2/graph/DasRow.java | 222 ++ .../java/org/das2/graph/DasSerializeable.java | 23 + .../java/org/das2/graph/DasZAxisPlot.java | 44 + .../main/java/org/das2/graph/DataLoader.java | 124 + .../main/java/org/das2/graph/DataRange.java | 451 +++ .../org/das2/graph/DefaultPlotSymbol.java | 167 + .../java/org/das2/graph/EventsRenderer.java | 258 ++ .../main/java/org/das2/graph/FillStyle.java | 64 + .../org/das2/graph/GrannyTickLabeller.java | 128 + .../main/java/org/das2/graph/GraphUtil.java | 733 ++++ .../graph/ImageVectorDataSetRenderer.java | 358 ++ .../src/main/java/org/das2/graph/Legend.java | 231 ++ .../src/main/java/org/das2/graph/Leveler.java | 352 ++ .../java/org/das2/graph/PathIterable.java | 21 + .../main/java/org/das2/graph/PlotSymbol.java | 27 + .../org/das2/graph/PlotSymbolRenderer.java | 233 ++ .../src/main/java/org/das2/graph/Psym.java | 202 + .../java/org/das2/graph/PsymConnector.java | 141 + .../main/java/org/das2/graph/Renderer.java | 603 +++ .../java/org/das2/graph/RowRowConnector.java | 89 + .../java/org/das2/graph/SeriesRenderer.java | 1586 ++++++++ .../org/das2/graph/SpectrogramRenderer.java | 680 ++++ .../das2/graph/StackedHistogramRenderer.java | 630 +++ .../StackedHistogramRenderer_rework.java.save | 690 ++++ .../org/das2/graph/StippledTableRenderer.java | 270 ++ .../main/java/org/das2/graph/SymColor.java | 113 + .../org/das2/graph/SymbolLineRenderer.java | 527 +++ .../org/das2/graph/TickCurveRenderer.java | 332 ++ .../java/org/das2/graph/TickLabeller.java | 23 + .../java/org/das2/graph/TickVDescriptor.java | 714 ++++ .../java/org/das2/graph/TimeRangeLabel.java | 298 ++ .../java/org/das2/graph/XAxisDataLoader.java | 282 ++ .../java/org/das2/graph/ZDeformRenderer.java | 73 + .../das2/graph/dnd/TransferableCanvas.java | 134 + .../dnd/TransferableCanvasComponent.java | 119 + .../das2/graph/dnd/TransferableRenderer.java | 97 + .../main/java/org/das2/graph/dnd/package.html | 4 + .../org/das2/graph/event/DasAxisEvent.java | 56 + .../org/das2/graph/event/DasAxisListener.java | 35 + .../graph/event/DasDevicePositionEvent.java | 53 + .../event/DasDevicePositionListener.java | 34 + .../org/das2/graph/event/DasUpdateEvent.java | 37 + .../das2/graph/event/DasUpdateListener.java | 33 + .../java/org/das2/graph/event/package.html | 4 + .../src/main/java/org/das2/graph/package.html | 18 + .../main/java/org/das2/graph/scratchPad.txt | 6 + .../src/main/java/org/das2/math/Contour.java | 519 +++ .../main/java/org/das2/math/Interpolate.java | 169 + .../org/das2/math/PoissonDistribution.java | 231 ++ .../main/java/org/das2/math/QuadFitUtil.java | 107 + .../main/java/org/das2/math/Triangulator.java | 1600 ++++++++ .../java/org/das2/math/fft/ComplexArray.java | 276 ++ .../main/java/org/das2/math/fft/FFTUtil.java | 155 + .../java/org/das2/math/fft/GeneralFFT.java | 126 + .../java/org/das2/math/fft/SimpleFFT.java | 90 + .../org/das2/math/fft/WaveformToSpectrum.java | 222 ++ .../org/das2/math/fft/WindowTableDataSet.java | 182 + .../das2/math/fft/jnt/ComplexDoubleFFT.java | 94 + .../math/fft/jnt/ComplexDoubleFFT_Mixed.java | 951 +++++ .../das2/math/fft/jnt/ComplexFloatFFT.java | 109 + .../math/fft/jnt/ComplexFloatFFT_Mixed.java | 960 +++++ .../java/org/das2/math/fft/jnt/Factorize.java | 72 + .../org/das2/math/fft/jnt/RealDoubleFFT.java | 84 + .../das2/math/fft/jnt/RealDoubleFFT_Even.java | 123 + .../java/org/das2/math/fft/jnt/Refactory.java | 160 + .../java/org/das2/math/fft/jnt/package.html | 4 + .../main/java/org/das2/math/fft/package.html | 7 + .../org/das2/math/matrix/ArrayMatrix.java | 96 + .../org/das2/math/matrix/CompositeMatrix.java | 53 + .../java/org/das2/math/matrix/Matrix.java | 63 + .../java/org/das2/math/matrix/MatrixUtil.java | 119 + .../java/org/das2/math/matrix/package.html | 3 + .../src/main/java/org/das2/math/package.html | 8 + .../persistence/DatumPersistenceDelegate.java | 57 + .../DatumRangePersistenceDelegate.java | 46 + .../das2/persistence/StatePersistence.java | 54 + .../persistence/UnitsPersistenceDelegate.java | 52 + .../java/org/das2/pw/ClusterSpacecraft.java | 59 + .../src/main/java/org/das2/pw/Spacecraft.java | 70 + .../src/main/java/org/das2/pw/package.html | 6 + .../org/das2/reader/BadQueryException.java | 24 + .../main/java/org/das2/reader/Constraint.java | 109 + .../org/das2/reader/Das2MsgValidator.java | 76 + .../main/java/org/das2/reader/DasHdrBuf.java | 63 + .../main/java/org/das2/reader/DasPktBuf.java | 186 + .../java/org/das2/reader/NoDataException.java | 16 + .../java/org/das2/reader/OutputFormat.java | 13 + .../java/org/das2/reader/QueryEditor.java | 53 + .../src/main/java/org/das2/reader/Reader.java | 52 + .../main/java/org/das2/reader/Selector.java | 127 + .../main/java/org/das2/stream/Das1ToDas2.java | 265 ++ .../das2/stream/DasStreamFormatException.java | 53 + .../org/das2/stream/DataTransferType.java | 267 ++ .../das2/stream/GUnzipStreamProcessor.java | 110 + .../org/das2/stream/GZipStreamProcessor.java | 109 + .../org/das2/stream/MicrophoneStream.java | 229 ++ .../org/das2/stream/PacketDescriptor.java | 285 ++ .../java/org/das2/stream/PropertyType.java | 164 + .../org/das2/stream/SkeletonDescriptor.java | 48 + .../main/java/org/das2/stream/Sonifier.java | 77 + .../java/org/das2/stream/StreamComment.java | 49 + .../org/das2/stream/StreamDescriptor.java | 429 +++ .../java/org/das2/stream/StreamException.java | 51 + .../java/org/das2/stream/StreamHandler.java | 40 + .../das2/stream/StreamMultiYDescriptor.java | 180 + .../java/org/das2/stream/StreamProcessor.java | 49 + .../java/org/das2/stream/StreamProducer.java | 291 ++ .../org/das2/stream/StreamRequirements.txt | 55 + .../main/java/org/das2/stream/StreamUtil.java | 71 + .../org/das2/stream/StreamXDescriptor.java | 174 + .../das2/stream/StreamYScanDescriptor.java | 240 ++ .../java/org/das2/stream/StripHeader.java | 27 + .../org/das2/stream/TAvStreamProcessor.java | 57 + .../main/java/org/das2/stream/ToAscii.java | 86 + .../main/java/org/das2/stream/package.html | 9 + .../LocalFileStandardDataStreamSource.java | 74 + .../org/das2/stream/test/RipplesStream.java | 123 + .../org/das2/stream/test/SineWaveStream.java | 84 + .../java/org/das2/stream/test/package.html | 5 + .../das2/system/ConsoleExceptionHandler.java | 30 + .../das2/system/ContextMonitorFactory.java | 29 + .../main/java/org/das2/system/DasLogger.java | 174 + .../das2/system/DefaultExceptionHandler.java | 34 + .../das2/system/DefaultMonitorFactory.java | 89 + .../org/das2/system/ExceptionHandler.java | 19 + .../java/org/das2/system/LogCategory.java | 40 + .../main/java/org/das2/system/LoggerId.java | 29 + .../java/org/das2/system/MonitorFactory.java | 35 + .../java/org/das2/system/MutatorLock.java | 17 + .../org/das2/system/NullMonitorFactory.java | 42 + .../java/org/das2/system/NullPreferences.java | 62 + .../das2/system/NullPreferencesFactory.java | 35 + .../org/das2/system/RequestProcessor.java | 306 ++ .../system/ThrowRuntimeExceptionHandler.java | 33 + .../org/das2/system/UserMessageCenter.java | 165 + .../main/java/org/das2/system/package.html | 4 + .../main/java/org/das2/util/AboutUtil.java | 130 + .../main/java/org/das2/util/ArgumentList.java | 520 +++ .../das2/util/AxisAutoRangeController.java | 93 + .../src/main/java/org/das2/util/Base64.java | 1767 +++++++++ .../org/das2/util/ByteBufferInputStream.java | 114 + .../src/main/java/org/das2/util/ClassMap.java | 95 + .../java/org/das2/util/CombinedTreeModel.java | 227 ++ .../java/org/das2/util/CredentialsDialog.form | 133 + .../java/org/das2/util/CredentialsDialog.java | 214 ++ .../org/das2/util/CredentialsManager.java | 314 ++ .../src/main/java/org/das2/util/Crypt.java | 71 + .../src/main/java/org/das2/util/DasDie.java | 118 + .../org/das2/util/DasExceptionHandler.java | 175 + .../src/main/java/org/das2/util/DasMath.java | 259 ++ .../java/org/das2/util/DasPNGConstants.java | 102 + .../java/org/das2/util/DasPNGEncoder.java | 349 ++ .../util/DasProgressMonitorInputStream.java | 210 + ...DasProgressMonitorReadableByteChannel.java | 171 + .../java/org/das2/util/DeflaterChannel.java | 105 + .../org/das2/util/DenseConsoleFormatter.java | 26 + .../main/java/org/das2/util/DnDSupport.java | 204 + .../src/main/java/org/das2/util/Entities.java | 354 ++ .../src/main/java/org/das2/util/FileUtil.java | 51 + .../org/das2/util/FixedWidthFormatter.java | 48 + .../org/das2/util/GrannyTextRenderer.java | 499 +++ .../org/das2/util/GraphicalLogHandler.java | 502 +++ .../main/java/org/das2/util/IDLParser.java | 278 ++ .../src/main/java/org/das2/util/IDLValue.java | 235 ++ .../java/org/das2/util/InflaterChannel.java | 114 + .../src/main/java/org/das2/util/JCrypt.java | 631 +++ .../java/org/das2/util/MemoryPreferences.java | 118 + .../das2/util/MemoryPreferencesFactory.java | 58 + .../main/java/org/das2/util/MessageBox.java | 172 + .../org/das2/util/NBConsoleFormatter.java | 42 + .../java/org/das2/util/NumberFormatUtil.java | 44 + .../java/org/das2/util/ObjectLocator.java | 70 + .../org/das2/util/PersistentStateSupport.java | 520 +++ .../src/main/java/org/das2/util/Probe.java | 594 +++ .../src/main/java/org/das2/util/Splash.java | 132 + .../main/java/org/das2/util/StreamTool.java | 656 ++++ .../src/main/java/org/das2/util/TextUtil.java | 101 + .../util/ThreadDenseConsoleFormatter.java | 28 + .../main/java/org/das2/util/TimeParser.java | 591 +++ .../org/das2/util/TimerConsoleFormatter.java | 75 + .../src/main/java/org/das2/util/URLBuddy.java | 141 + .../org/das2/util/WeakListenerManager.java | 114 + .../org/das2/util/awt/EventQueueBlocker.java | 122 + .../das2/util/awt/EventQueueBlocker_1.java | 95 + .../org/das2/util/awt/GraphicsOutput.java | 25 + .../org/das2/util/awt/LoggingEventQueue.java | 73 + .../org/das2/util/awt/PdfGraphicsOutput.java | 76 + .../org/das2/util/awt/PngGraphicsOutput.java | 60 + .../org/das2/util/awt/SvgGraphicsOutput.java | 81 + .../main/java/org/das2/util/awt/package.html | 7 + .../java/org/das2/util/fft/SimpleFFT.java | 90 + .../main/java/org/das2/util/fft/package.html | 4 + .../util/filesystem/AppletHttpProtocol.java | 71 + .../util/filesystem/DefaultHttpProtocol.java | 87 + .../util/filesystem/DefaultWebProtocol.java | 95 + .../das2/util/filesystem/FTPFileSystem.java | 153 + .../org/das2/util/filesystem/FileObject.java | 196 + .../org/das2/util/filesystem/FileSystem.java | 304 ++ .../util/filesystem/FileSystemFactory.java | 35 + .../util/filesystem/FileSystemSettings.java | 110 + .../das2/util/filesystem/FileSystemUtil.java | 56 + .../util/filesystem/FtpFileSystemFactory.java | 42 + .../java/org/das2/util/filesystem/Glob.java | 149 + .../org/das2/util/filesystem/HtmlUtil.java | 112 + .../das2/util/filesystem/HttpFileSystem.java | 350 ++ .../filesystem/HttpFileSystemFactory.java | 47 + .../das2/util/filesystem/LocalFileObject.java | 125 + .../das2/util/filesystem/LocalFileSystem.java | 118 + .../filesystem/LocalFileSystemFactory.java | 42 + .../das2/util/filesystem/SubFileSystem.java | 70 + .../das2/util/filesystem/WebFileObject.java | 324 ++ .../das2/util/filesystem/WebFileSystem.java | 228 ++ .../org/das2/util/filesystem/WebProtocol.java | 31 + .../org/das2/util/filesystem/package.html | 18 + .../util/monitor/NullProgressMonitor.java | 111 + .../das2/util/monitor/ProgressMonitor.java | 179 + .../org/das2/util/monitor/SubTaskMonitor.java | 123 + .../src/main/java/org/das2/util/package.html | 14 + 580 files changed, 108834 insertions(+) create mode 100644 dasCore/src/main/java/org/das2/CancelledOperationException.java create mode 100755 dasCore/src/main/java/org/das2/DasApplication.java create mode 100644 dasCore/src/main/java/org/das2/DasException.java create mode 100644 dasCore/src/main/java/org/das2/DasIOException.java create mode 100644 dasCore/src/main/java/org/das2/DasNameException.java create mode 100755 dasCore/src/main/java/org/das2/DasProperties.java create mode 100644 dasCore/src/main/java/org/das2/DasPropertyException.java create mode 100644 dasCore/src/main/java/org/das2/NameContext.java create mode 100644 dasCore/src/main/java/org/das2/apiProblems.txt create mode 100644 dasCore/src/main/java/org/das2/beans/AccessLevelBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/AttachedLabelBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/BeansUtil.java create mode 100755 dasCore/src/main/java/org/das2/beans/ColumnColumnConnectorBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/CrossHairRendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasApplicationBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasAxisBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasCanvasBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasCanvasComponentBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasColorBarBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasColumnBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasLabelAxisBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasMouseInputAdapterBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasPlotBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasRowBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DasServerBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DataPointRecorderBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/DataSetDescriptorBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/ImplicitAccessLevelBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/LabelDragRendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/MouseModuleBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/RectangleEditor.java create mode 100755 dasCore/src/main/java/org/das2/beans/RendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/RowRowConnectorBeanInfo.java create mode 100755 dasCore/src/main/java/org/das2/beans/SpectrogramRendererBeanInfo.java create mode 100755 dasCore/src/main/java/org/das2/beans/StackedHistogramRendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/StreamDataSetDescriptorBeanInfo.java create mode 100755 dasCore/src/main/java/org/das2/beans/SymbolLineRendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/TickCurveRendererBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/beans/UnitsEditor.java create mode 100644 dasCore/src/main/java/org/das2/beans/package.html create mode 100644 dasCore/src/main/java/org/das2/beans/scratchpad.txt create mode 100644 dasCore/src/main/java/org/das2/client/AccessDeniedException.java create mode 100644 dasCore/src/main/java/org/das2/client/AccountManager.java create mode 100755 dasCore/src/main/java/org/das2/client/Authenticator.java create mode 100755 dasCore/src/main/java/org/das2/client/DasServer.java create mode 100644 dasCore/src/main/java/org/das2/client/DasServerException.java create mode 100644 dasCore/src/main/java/org/das2/client/DasServerNotFoundException.java create mode 100644 dasCore/src/main/java/org/das2/client/DataSetDescriptorNotAvailableException.java create mode 100755 dasCore/src/main/java/org/das2/client/DataSetStreamHandler.java create mode 100644 dasCore/src/main/java/org/das2/client/FakeStandardDataStreamSource.java create mode 100644 dasCore/src/main/java/org/das2/client/InputStreamMeter.java create mode 100644 dasCore/src/main/java/org/das2/client/Key.java create mode 100644 dasCore/src/main/java/org/das2/client/NoSuchDataSetException.java create mode 100644 dasCore/src/main/java/org/das2/client/StandardDataStreamSource.java create mode 100755 dasCore/src/main/java/org/das2/client/StreamDataSetDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java create mode 100644 dasCore/src/main/java/org/das2/client/package.html create mode 100644 dasCore/src/main/java/org/das2/components/AngleSpectrogramSlicer.java create mode 100644 dasCore/src/main/java/org/das2/components/AsciiFileParser.form create mode 100644 dasCore/src/main/java/org/das2/components/AsciiFileParser.java create mode 100755 dasCore/src/main/java/org/das2/components/BatchMaster.java create mode 100644 dasCore/src/main/java/org/das2/components/ColorBarComponent.java create mode 100644 dasCore/src/main/java/org/das2/components/ComponentsUtil.java create mode 100644 dasCore/src/main/java/org/das2/components/DasAxisSelector.java create mode 100755 dasCore/src/main/java/org/das2/components/DasProgressPanel.java create mode 100755 dasCore/src/main/java/org/das2/components/DasTimeRangeSelector.java create mode 100755 dasCore/src/main/java/org/das2/components/DataPointRecorder.java create mode 100644 dasCore/src/main/java/org/das2/components/DataPointReporter.java create mode 100644 dasCore/src/main/java/org/das2/components/DataSetBrowser.java create mode 100644 dasCore/src/main/java/org/das2/components/DatumEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/DatumRangeEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/FavoritesSelector.java create mode 100644 dasCore/src/main/java/org/das2/components/FixedColumnTextArea.java create mode 100644 dasCore/src/main/java/org/das2/components/HistogramSlicer.java create mode 100755 dasCore/src/main/java/org/das2/components/HorizontalSpectrogramSlicer.java create mode 100644 dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java create mode 100644 dasCore/src/main/java/org/das2/components/Toolbox.java create mode 100755 dasCore/src/main/java/org/das2/components/VerticalSpectrogramAverager.java create mode 100755 dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java create mode 100644 dasCore/src/main/java/org/das2/components/package.html create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/BooleanEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/ColorCellRenderer.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/ColorEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/Displayable.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/Editable.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/Enumeration.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/EnumerationEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointDocumentFilter.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointFormatter.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyItemTreeNode.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyTreeNode.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/MapEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PeerPropertyTreeNode.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyCellEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyCellRenderer.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditor.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditorAdapter.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyTreeNode.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/PropertyTreeNodeInterface.java create mode 100644 dasCore/src/main/java/org/das2/components/propertyeditor/package.html create mode 100644 dasCore/src/main/java/org/das2/components/treetable/TreeTableCellRenderer.java create mode 100644 dasCore/src/main/java/org/das2/components/treetable/TreeTableModel.java create mode 100644 dasCore/src/main/java/org/das2/components/treetable/TreeTableNode.java create mode 100644 dasCore/src/main/java/org/das2/components/treetable/package.html create mode 100644 dasCore/src/main/java/org/das2/cvs_status.txt create mode 100644 dasCore/src/main/java/org/das2/dasml/CommandAction.java create mode 100644 dasCore/src/main/java/org/das2/dasml/CommandBlock.java create mode 100644 dasCore/src/main/java/org/das2/dasml/CommandBlockEditor.java create mode 100644 dasCore/src/main/java/org/das2/dasml/DOMBuilder.java create mode 100644 dasCore/src/main/java/org/das2/dasml/DasMLValidator.java create mode 100644 dasCore/src/main/java/org/das2/dasml/DataFormatException.java create mode 100644 dasCore/src/main/java/org/das2/dasml/DefaultComponentDnDSupport.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormBase.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormButton.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormButtonBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormCheckBox.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormCheckBoxBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormChoice.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormChoiceBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormComponent.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormContainer.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormList.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormListBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormPanel.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormPanelBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormRadioButton.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormRadioButtonBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroup.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroupBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormTab.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormTabBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormText.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormTextBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormTextField.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormTextFieldBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormWindow.java create mode 100644 dasCore/src/main/java/org/das2/dasml/FormWindowBeanInfo.java create mode 100644 dasCore/src/main/java/org/das2/dasml/ListOption.java create mode 100644 dasCore/src/main/java/org/das2/dasml/OptionList.java create mode 100644 dasCore/src/main/java/org/das2/dasml/OptionListEditor.java create mode 100644 dasCore/src/main/java/org/das2/dasml/Orientation.java create mode 100644 dasCore/src/main/java/org/das2/dasml/ParsedExpression.java create mode 100644 dasCore/src/main/java/org/das2/dasml/ParsedExpressionException.java create mode 100644 dasCore/src/main/java/org/das2/dasml/SerializeUtil.java create mode 100644 dasCore/src/main/java/org/das2/dasml/TransferableFormComponent.java create mode 100644 dasCore/src/main/java/org/das2/dasml/package.html create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/action.xsd create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/canvas.xsd create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/commandblock.xsd create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/commandfragment.xsd create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/dasML.xsd create mode 100644 dasCore/src/main/java/org/das2/dasml/schema/dasMLtypes.xsd create mode 100755 dasCore/src/main/java/org/das2/dataset/AbstractDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/AbstractDataSetCache.java create mode 100755 dasCore/src/main/java/org/das2/dataset/AbstractTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/AbstractVectorDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/AppendTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/AverageNoInterpolateTableRebinner.java create mode 100755 dasCore/src/main/java/org/das2/dataset/AveragePeakTableRebinner.java create mode 100755 dasCore/src/main/java/org/das2/dataset/AverageTableRebinner.java create mode 100755 dasCore/src/main/java/org/das2/dataset/CacheTag.java create mode 100755 dasCore/src/main/java/org/das2/dataset/ClippedTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/ClippedVectorDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/ConstantDataSetDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataRequestThread.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataRequestor.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataSetCache.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataSetConsumer.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DataSetDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DataSetRebinner.java create mode 100644 dasCore/src/main/java/org/das2/dataset/DataSetStreamProducer.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DataSetUpdateEvent.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DataSetUpdateListener.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DataSetUtil.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DefaultTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/DefaultVectorDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/ExecutableDataSetDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/dataset/FastTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/GapListDouble.java create mode 100644 dasCore/src/main/java/org/das2/dataset/GenericQernalFactory.java create mode 100644 dasCore/src/main/java/org/das2/dataset/LimitCountDataSetCache.java create mode 100644 dasCore/src/main/java/org/das2/dataset/LimitSizeBytesDataSetCache.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NNQernalFactory.java create mode 100755 dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/NearestNeighborTableRebinner.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NewAverageTableRebinner.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NoDataInIntervalException.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NoInterpolateQernalFactory.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NoKeyProvidedException.java create mode 100644 dasCore/src/main/java/org/das2/dataset/NullDataSetCache.java create mode 100755 dasCore/src/main/java/org/das2/dataset/PeakTableRebinner.java create mode 100644 dasCore/src/main/java/org/das2/dataset/QernalTableRebinner.java create mode 100644 dasCore/src/main/java/org/das2/dataset/QuickVectorDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/RebinDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/dataset/SimpleDataSetCache.java create mode 100644 dasCore/src/main/java/org/das2/dataset/SimpleTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/SingleVectorDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/SyncUtil.java create mode 100755 dasCore/src/main/java/org/das2/dataset/TableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/TableDataSetBuilder.java create mode 100644 dasCore/src/main/java/org/das2/dataset/TableDataSetConsumer.java create mode 100644 dasCore/src/main/java/org/das2/dataset/TableDataSetDecorator.java create mode 100644 dasCore/src/main/java/org/das2/dataset/TableDataSetGridder.java create mode 100644 dasCore/src/main/java/org/das2/dataset/TableDataSetWrapper.java create mode 100755 dasCore/src/main/java/org/das2/dataset/TableUtil.java create mode 100644 dasCore/src/main/java/org/das2/dataset/TagMapTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/VectorDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/VectorDataSetBuilder.java create mode 100755 dasCore/src/main/java/org/das2/dataset/VectorUtil.java create mode 100755 dasCore/src/main/java/org/das2/dataset/ViewDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/WeightsTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/WritableTableDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/XSliceDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/XTagsVectorDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/YSliceDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/cvs_status.txt create mode 100644 dasCore/src/main/java/org/das2/dataset/package.html create mode 100644 dasCore/src/main/java/org/das2/dataset/parser/VectorDataSetParser.java create mode 100644 dasCore/src/main/java/org/das2/dataset/parser/package.html create mode 100755 dasCore/src/main/java/org/das2/dataset/scratchPad.txt create mode 100644 dasCore/src/main/java/org/das2/dataset/test/BigVectorDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/ContourMeDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/DistTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/FunctionTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/MendelbrotDataLoader.java create mode 100755 dasCore/src/main/java/org/das2/dataset/test/MendelbrotDataSetDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/dataset/test/OrbitVectorDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/PolynomialDataSetDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/dataset/test/RipplesDataSet.java create mode 100755 dasCore/src/main/java/org/das2/dataset/test/RipplesDataSetDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/dataset/test/SineWaveDataSetDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/SineWaveTable.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/WavVectorDataSet.java create mode 100644 dasCore/src/main/java/org/das2/dataset/test/package.html create mode 100644 dasCore/src/main/java/org/das2/datum/Basis.java create mode 100644 dasCore/src/main/java/org/das2/datum/BestFormatter.txt create mode 100644 dasCore/src/main/java/org/das2/datum/CalendarTime.java create mode 100644 dasCore/src/main/java/org/das2/datum/Datum.java create mode 100644 dasCore/src/main/java/org/das2/datum/DatumRange.java create mode 100644 dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java create mode 100755 dasCore/src/main/java/org/das2/datum/DatumUtil.java create mode 100644 dasCore/src/main/java/org/das2/datum/DatumVector.java create mode 100755 dasCore/src/main/java/org/das2/datum/EnumerationUnits.java create mode 100644 dasCore/src/main/java/org/das2/datum/InconvertibleUnitsException.java create mode 100644 dasCore/src/main/java/org/das2/datum/LocationUnits.java create mode 100644 dasCore/src/main/java/org/das2/datum/MonthDatumRange.java create mode 100755 dasCore/src/main/java/org/das2/datum/NumberUnits.java create mode 100644 dasCore/src/main/java/org/das2/datum/TimeContext.java create mode 100644 dasCore/src/main/java/org/das2/datum/TimeLocationUnits.java create mode 100755 dasCore/src/main/java/org/das2/datum/TimeUtil.java create mode 100755 dasCore/src/main/java/org/das2/datum/Units.java create mode 100644 dasCore/src/main/java/org/das2/datum/UnitsConverter.java create mode 100644 dasCore/src/main/java/org/das2/datum/UnitsUtil.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/DatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/DatumFormatterFactory.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatterFactory.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatterFactory.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/ExponentialDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/LatinPrefixDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/TimeDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/TimeDatumFormatterFactory.java create mode 100644 dasCore/src/main/java/org/das2/datum/format/package.html create mode 100644 dasCore/src/main/java/org/das2/datum/package.html create mode 100644 dasCore/src/main/java/org/das2/datum/swing/DatumJFormatterFactory.java create mode 100644 dasCore/src/main/java/org/das2/datum/swing/SwingDatumFormatter.java create mode 100644 dasCore/src/main/java/org/das2/event/AngleSelectionDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/AngleSlicerMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/AnnotatorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/ArrowDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxGesturesRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/BoxSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxSelectionListener.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxZoomDialog.form create mode 100644 dasCore/src/main/java/org/das2/event/BoxZoomDialog.java create mode 100644 dasCore/src/main/java/org/das2/event/BoxZoomMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/ColorBarRepaletteMouseModule.java create mode 100755 dasCore/src/main/java/org/das2/event/CommentDataPointSelectionEvent.java create mode 100755 dasCore/src/main/java/org/das2/event/CrossHairMouseModule.java create mode 100755 dasCore/src/main/java/org/das2/event/CrossHairRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/CutoffMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/DasEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/DasEventMulticaster.java create mode 100644 dasCore/src/main/java/org/das2/event/DasMouseEvent.java create mode 100755 dasCore/src/main/java/org/das2/event/DasMouseInputAdapter.java create mode 100644 dasCore/src/main/java/org/das2/event/DasSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/DasUpdateEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/DataPointSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/DataPointSelectionListener.java create mode 100644 dasCore/src/main/java/org/das2/event/DataPointSelectorMouseModule.java create mode 100755 dasCore/src/main/java/org/das2/event/DataRangeSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/DataRangeSelectionListener.java create mode 100644 dasCore/src/main/java/org/das2/event/DisplayDataMouseModule.java create mode 100755 dasCore/src/main/java/org/das2/event/DragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/DumpToFileMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/EmptyDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/FrequencyDragRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/Gesture.java create mode 100644 dasCore/src/main/java/org/das2/event/GesturesRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/HorizontalDragRangeRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/HorizontalDragRangeSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/HorizontalDragRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/HorizontalFrequencyDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/HorizontalRangeGesturesRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/HorizontalRangeRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/HorizontalRangeSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/HorizontalRangeTorsionMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/HorizontalSliceSelectionRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/HorizontalSlicerMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/LabelDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/LengthDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/MouseBoxEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/MouseDragEvent.java create mode 100755 dasCore/src/main/java/org/das2/event/MouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/MousePointSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/MouseRangeGestureSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/MouseRangeSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/MoveComponentMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/PeakDetectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/PointSlopeDragRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/RangeAnnotatorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/TimeRangeSelectionEvent.java create mode 100644 dasCore/src/main/java/org/das2/event/TimeRangeSelectionListener.java create mode 100644 dasCore/src/main/java/org/das2/event/TimeRangeSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/VerticalRangeGesturesRenderer.java create mode 100644 dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/VerticalSliceSelectionRenderer.java create mode 100755 dasCore/src/main/java/org/das2/event/VerticalSlicerMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/ZoomOutMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/ZoomPanMouseModule.java create mode 100644 dasCore/src/main/java/org/das2/event/package.html create mode 100755 dasCore/src/main/java/org/das2/event/scratchPad.txt create mode 100644 dasCore/src/main/java/org/das2/fsm/FileStorageModel.java create mode 100644 dasCore/src/main/java/org/das2/fsm/FileStorageModelAvailabilityDataSetDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/fsm/FileStorageModelNew.java create mode 100644 dasCore/src/main/java/org/das2/fsm/package.html create mode 100644 dasCore/src/main/java/org/das2/graph/Arrow.java create mode 100644 dasCore/src/main/java/org/das2/graph/AttachedLabel.java create mode 100644 dasCore/src/main/java/org/das2/graph/Auralizor.java create mode 100644 dasCore/src/main/java/org/das2/graph/ColumnColumnConnector.java create mode 100755 dasCore/src/main/java/org/das2/graph/ContoursRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/CurveRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasAnnotation.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasAnnotationBeanInfo.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasAxis.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasCanvas.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasCanvasComponentInterface.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasCanvasStateSupport.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasColorBar.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasColumn.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasDevicePosition.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasEventsIndicator.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasLabelAxis.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasNumberFormatter.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasPlot.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasRendererUpdateEvent.java create mode 100755 dasCore/src/main/java/org/das2/graph/DasRow.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasSerializeable.java create mode 100644 dasCore/src/main/java/org/das2/graph/DasZAxisPlot.java create mode 100644 dasCore/src/main/java/org/das2/graph/DataLoader.java create mode 100755 dasCore/src/main/java/org/das2/graph/DataRange.java create mode 100644 dasCore/src/main/java/org/das2/graph/DefaultPlotSymbol.java create mode 100644 dasCore/src/main/java/org/das2/graph/EventsRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/FillStyle.java create mode 100644 dasCore/src/main/java/org/das2/graph/GrannyTickLabeller.java create mode 100644 dasCore/src/main/java/org/das2/graph/GraphUtil.java create mode 100644 dasCore/src/main/java/org/das2/graph/ImageVectorDataSetRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/Legend.java create mode 100644 dasCore/src/main/java/org/das2/graph/Leveler.java create mode 100644 dasCore/src/main/java/org/das2/graph/PathIterable.java create mode 100644 dasCore/src/main/java/org/das2/graph/PlotSymbol.java create mode 100644 dasCore/src/main/java/org/das2/graph/PlotSymbolRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/Psym.java create mode 100644 dasCore/src/main/java/org/das2/graph/PsymConnector.java create mode 100755 dasCore/src/main/java/org/das2/graph/Renderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/RowRowConnector.java create mode 100644 dasCore/src/main/java/org/das2/graph/SeriesRenderer.java create mode 100755 dasCore/src/main/java/org/das2/graph/SpectrogramRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer_rework.java.save create mode 100644 dasCore/src/main/java/org/das2/graph/StippledTableRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/SymColor.java create mode 100755 dasCore/src/main/java/org/das2/graph/SymbolLineRenderer.java create mode 100755 dasCore/src/main/java/org/das2/graph/TickCurveRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/TickLabeller.java create mode 100755 dasCore/src/main/java/org/das2/graph/TickVDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/graph/TimeRangeLabel.java create mode 100644 dasCore/src/main/java/org/das2/graph/XAxisDataLoader.java create mode 100644 dasCore/src/main/java/org/das2/graph/ZDeformRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvas.java create mode 100644 dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvasComponent.java create mode 100644 dasCore/src/main/java/org/das2/graph/dnd/TransferableRenderer.java create mode 100644 dasCore/src/main/java/org/das2/graph/dnd/package.html create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasAxisEvent.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasAxisListener.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasDevicePositionEvent.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasDevicePositionListener.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasUpdateEvent.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/DasUpdateListener.java create mode 100644 dasCore/src/main/java/org/das2/graph/event/package.html create mode 100644 dasCore/src/main/java/org/das2/graph/package.html create mode 100644 dasCore/src/main/java/org/das2/graph/scratchPad.txt create mode 100644 dasCore/src/main/java/org/das2/math/Contour.java create mode 100644 dasCore/src/main/java/org/das2/math/Interpolate.java create mode 100644 dasCore/src/main/java/org/das2/math/PoissonDistribution.java create mode 100644 dasCore/src/main/java/org/das2/math/QuadFitUtil.java create mode 100644 dasCore/src/main/java/org/das2/math/Triangulator.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/ComplexArray.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/FFTUtil.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/GeneralFFT.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/SimpleFFT.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/WaveformToSpectrum.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/WindowTableDataSet.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/ComplexDoubleFFT.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/ComplexDoubleFFT_Mixed.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/ComplexFloatFFT.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/ComplexFloatFFT_Mixed.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/Factorize.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT_Even.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/Refactory.java create mode 100644 dasCore/src/main/java/org/das2/math/fft/jnt/package.html create mode 100644 dasCore/src/main/java/org/das2/math/fft/package.html create mode 100644 dasCore/src/main/java/org/das2/math/matrix/ArrayMatrix.java create mode 100644 dasCore/src/main/java/org/das2/math/matrix/CompositeMatrix.java create mode 100644 dasCore/src/main/java/org/das2/math/matrix/Matrix.java create mode 100644 dasCore/src/main/java/org/das2/math/matrix/MatrixUtil.java create mode 100644 dasCore/src/main/java/org/das2/math/matrix/package.html create mode 100644 dasCore/src/main/java/org/das2/math/package.html create mode 100644 dasCore/src/main/java/org/das2/persistence/DatumPersistenceDelegate.java create mode 100644 dasCore/src/main/java/org/das2/persistence/DatumRangePersistenceDelegate.java create mode 100644 dasCore/src/main/java/org/das2/persistence/StatePersistence.java create mode 100644 dasCore/src/main/java/org/das2/persistence/UnitsPersistenceDelegate.java create mode 100644 dasCore/src/main/java/org/das2/pw/ClusterSpacecraft.java create mode 100644 dasCore/src/main/java/org/das2/pw/Spacecraft.java create mode 100644 dasCore/src/main/java/org/das2/pw/package.html create mode 100644 dasCore/src/main/java/org/das2/reader/BadQueryException.java create mode 100644 dasCore/src/main/java/org/das2/reader/Constraint.java create mode 100644 dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java create mode 100644 dasCore/src/main/java/org/das2/reader/DasHdrBuf.java create mode 100644 dasCore/src/main/java/org/das2/reader/DasPktBuf.java create mode 100644 dasCore/src/main/java/org/das2/reader/NoDataException.java create mode 100644 dasCore/src/main/java/org/das2/reader/OutputFormat.java create mode 100644 dasCore/src/main/java/org/das2/reader/QueryEditor.java create mode 100644 dasCore/src/main/java/org/das2/reader/Reader.java create mode 100644 dasCore/src/main/java/org/das2/reader/Selector.java create mode 100755 dasCore/src/main/java/org/das2/stream/Das1ToDas2.java create mode 100644 dasCore/src/main/java/org/das2/stream/DasStreamFormatException.java create mode 100644 dasCore/src/main/java/org/das2/stream/DataTransferType.java create mode 100755 dasCore/src/main/java/org/das2/stream/GUnzipStreamProcessor.java create mode 100755 dasCore/src/main/java/org/das2/stream/GZipStreamProcessor.java create mode 100644 dasCore/src/main/java/org/das2/stream/MicrophoneStream.java create mode 100755 dasCore/src/main/java/org/das2/stream/PacketDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/stream/PropertyType.java create mode 100644 dasCore/src/main/java/org/das2/stream/SkeletonDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/stream/Sonifier.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamComment.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamDescriptor.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamException.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamHandler.java create mode 100755 dasCore/src/main/java/org/das2/stream/StreamMultiYDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/stream/StreamProcessor.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamProducer.java create mode 100755 dasCore/src/main/java/org/das2/stream/StreamRequirements.txt create mode 100644 dasCore/src/main/java/org/das2/stream/StreamUtil.java create mode 100644 dasCore/src/main/java/org/das2/stream/StreamXDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/stream/StreamYScanDescriptor.java create mode 100755 dasCore/src/main/java/org/das2/stream/StripHeader.java create mode 100644 dasCore/src/main/java/org/das2/stream/TAvStreamProcessor.java create mode 100644 dasCore/src/main/java/org/das2/stream/ToAscii.java create mode 100644 dasCore/src/main/java/org/das2/stream/package.html create mode 100644 dasCore/src/main/java/org/das2/stream/test/LocalFileStandardDataStreamSource.java create mode 100644 dasCore/src/main/java/org/das2/stream/test/RipplesStream.java create mode 100755 dasCore/src/main/java/org/das2/stream/test/SineWaveStream.java create mode 100644 dasCore/src/main/java/org/das2/stream/test/package.html create mode 100644 dasCore/src/main/java/org/das2/system/ConsoleExceptionHandler.java create mode 100644 dasCore/src/main/java/org/das2/system/ContextMonitorFactory.java create mode 100644 dasCore/src/main/java/org/das2/system/DasLogger.java create mode 100644 dasCore/src/main/java/org/das2/system/DefaultExceptionHandler.java create mode 100644 dasCore/src/main/java/org/das2/system/DefaultMonitorFactory.java create mode 100644 dasCore/src/main/java/org/das2/system/ExceptionHandler.java create mode 100644 dasCore/src/main/java/org/das2/system/LogCategory.java create mode 100644 dasCore/src/main/java/org/das2/system/LoggerId.java create mode 100644 dasCore/src/main/java/org/das2/system/MonitorFactory.java create mode 100644 dasCore/src/main/java/org/das2/system/MutatorLock.java create mode 100644 dasCore/src/main/java/org/das2/system/NullMonitorFactory.java create mode 100755 dasCore/src/main/java/org/das2/system/NullPreferences.java create mode 100755 dasCore/src/main/java/org/das2/system/NullPreferencesFactory.java create mode 100644 dasCore/src/main/java/org/das2/system/RequestProcessor.java create mode 100644 dasCore/src/main/java/org/das2/system/ThrowRuntimeExceptionHandler.java create mode 100644 dasCore/src/main/java/org/das2/system/UserMessageCenter.java create mode 100644 dasCore/src/main/java/org/das2/system/package.html create mode 100644 dasCore/src/main/java/org/das2/util/AboutUtil.java create mode 100755 dasCore/src/main/java/org/das2/util/ArgumentList.java create mode 100644 dasCore/src/main/java/org/das2/util/AxisAutoRangeController.java create mode 100644 dasCore/src/main/java/org/das2/util/Base64.java create mode 100644 dasCore/src/main/java/org/das2/util/ByteBufferInputStream.java create mode 100644 dasCore/src/main/java/org/das2/util/ClassMap.java create mode 100644 dasCore/src/main/java/org/das2/util/CombinedTreeModel.java create mode 100644 dasCore/src/main/java/org/das2/util/CredentialsDialog.form create mode 100644 dasCore/src/main/java/org/das2/util/CredentialsDialog.java create mode 100644 dasCore/src/main/java/org/das2/util/CredentialsManager.java create mode 100644 dasCore/src/main/java/org/das2/util/Crypt.java create mode 100644 dasCore/src/main/java/org/das2/util/DasDie.java create mode 100755 dasCore/src/main/java/org/das2/util/DasExceptionHandler.java create mode 100644 dasCore/src/main/java/org/das2/util/DasMath.java create mode 100644 dasCore/src/main/java/org/das2/util/DasPNGConstants.java create mode 100644 dasCore/src/main/java/org/das2/util/DasPNGEncoder.java create mode 100755 dasCore/src/main/java/org/das2/util/DasProgressMonitorInputStream.java create mode 100644 dasCore/src/main/java/org/das2/util/DasProgressMonitorReadableByteChannel.java create mode 100644 dasCore/src/main/java/org/das2/util/DeflaterChannel.java create mode 100644 dasCore/src/main/java/org/das2/util/DenseConsoleFormatter.java create mode 100644 dasCore/src/main/java/org/das2/util/DnDSupport.java create mode 100755 dasCore/src/main/java/org/das2/util/Entities.java create mode 100644 dasCore/src/main/java/org/das2/util/FileUtil.java create mode 100755 dasCore/src/main/java/org/das2/util/FixedWidthFormatter.java create mode 100644 dasCore/src/main/java/org/das2/util/GrannyTextRenderer.java create mode 100644 dasCore/src/main/java/org/das2/util/GraphicalLogHandler.java create mode 100755 dasCore/src/main/java/org/das2/util/IDLParser.java create mode 100755 dasCore/src/main/java/org/das2/util/IDLValue.java create mode 100644 dasCore/src/main/java/org/das2/util/InflaterChannel.java create mode 100644 dasCore/src/main/java/org/das2/util/JCrypt.java create mode 100644 dasCore/src/main/java/org/das2/util/MemoryPreferences.java create mode 100644 dasCore/src/main/java/org/das2/util/MemoryPreferencesFactory.java create mode 100644 dasCore/src/main/java/org/das2/util/MessageBox.java create mode 100644 dasCore/src/main/java/org/das2/util/NBConsoleFormatter.java create mode 100644 dasCore/src/main/java/org/das2/util/NumberFormatUtil.java create mode 100644 dasCore/src/main/java/org/das2/util/ObjectLocator.java create mode 100644 dasCore/src/main/java/org/das2/util/PersistentStateSupport.java create mode 100644 dasCore/src/main/java/org/das2/util/Probe.java create mode 100644 dasCore/src/main/java/org/das2/util/Splash.java create mode 100755 dasCore/src/main/java/org/das2/util/StreamTool.java create mode 100644 dasCore/src/main/java/org/das2/util/TextUtil.java create mode 100644 dasCore/src/main/java/org/das2/util/ThreadDenseConsoleFormatter.java create mode 100644 dasCore/src/main/java/org/das2/util/TimeParser.java create mode 100644 dasCore/src/main/java/org/das2/util/TimerConsoleFormatter.java create mode 100644 dasCore/src/main/java/org/das2/util/URLBuddy.java create mode 100644 dasCore/src/main/java/org/das2/util/WeakListenerManager.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker_1.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/GraphicsOutput.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/LoggingEventQueue.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/PdfGraphicsOutput.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/PngGraphicsOutput.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/SvgGraphicsOutput.java create mode 100644 dasCore/src/main/java/org/das2/util/awt/package.html create mode 100644 dasCore/src/main/java/org/das2/util/fft/SimpleFFT.java create mode 100644 dasCore/src/main/java/org/das2/util/fft/package.html create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/AppletHttpProtocol.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/DefaultHttpProtocol.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/DefaultWebProtocol.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/FTPFileSystem.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/FileObject.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/FileSystem.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/FileSystemFactory.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/FileSystemSettings.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/FileSystemUtil.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/FtpFileSystemFactory.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/Glob.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/HtmlUtil.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystem.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystemFactory.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/LocalFileObject.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/LocalFileSystem.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/LocalFileSystemFactory.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/SubFileSystem.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/WebFileObject.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/WebFileSystem.java create mode 100755 dasCore/src/main/java/org/das2/util/filesystem/WebProtocol.java create mode 100644 dasCore/src/main/java/org/das2/util/filesystem/package.html create mode 100644 dasCore/src/main/java/org/das2/util/monitor/NullProgressMonitor.java create mode 100755 dasCore/src/main/java/org/das2/util/monitor/ProgressMonitor.java create mode 100644 dasCore/src/main/java/org/das2/util/monitor/SubTaskMonitor.java create mode 100644 dasCore/src/main/java/org/das2/util/package.html diff --git a/dasCore/src/main/java/org/das2/CancelledOperationException.java b/dasCore/src/main/java/org/das2/CancelledOperationException.java new file mode 100644 index 000000000..9c2b306c2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/CancelledOperationException.java @@ -0,0 +1,47 @@ +/* File: CancelledOperationException.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on January 20, 2004, 11:33 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +import java.io.InterruptedIOException; + +/** + * + * @author eew + */ +public class CancelledOperationException extends DasException { + + /** Creates a new instance of CancelledOperationException */ + public CancelledOperationException() { + super(); + } + + public CancelledOperationException(String message) { + } + + public CancelledOperationException(InterruptedIOException iioe) { + super(iioe.getMessage()); + initCause(iioe); + } + +} diff --git a/dasCore/src/main/java/org/das2/DasApplication.java b/dasCore/src/main/java/org/das2/DasApplication.java new file mode 100755 index 000000000..ac7c8a79e --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasApplication.java @@ -0,0 +1,415 @@ +/* File: DasApplication.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +import org.das2.dataset.LimitSizeBytesDataSetCache; +import org.das2.dataset.NullDataSetCache; +import org.das2.system.DefaultExceptionHandler; +import org.das2.system.DasLogger; +import org.das2.system.ExceptionHandler; +import org.das2.system.LoggerId; +import org.das2.system.NullMonitorFactory; +import org.das2.system.MonitorFactory; +import org.das2.system.DefaultMonitorFactory; +import org.das2.system.ThrowRuntimeExceptionHandler; +import org.das2.util.Splash; +import org.das2.util.DasExceptionHandler; +import org.das2.util.ClassMap; +import org.das2.client.InputStreamMeter; +import org.das2.dataset.DataSetCache; +import org.das2.graph.DasAnnotation; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasColorBar; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import java.awt.event.*; +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.*; +import java.util.prefs.*; +import javax.swing.*; + +/** + * DasApplication object manages per-application resources, like object name space, + * dataset caching, progress monitoring, exception handling and a network speed limiter. + * + * @author Edward West + */ +public class DasApplication { + + private static DasApplication DEFAULT; + + private JFrame mainFrame; + + /** + * three-state register for keeping track of is applet: null, TRUE, FALSE + * null->try to detect. + * TRUE|FALSE->explicit setting by application + */ + private Boolean applet; + + /** + * three-state register for keeping track of headless: null, TRUE, FALSE + * null->try to detect. + * TRUE|FALSE->explicit setting by application + * in headless, we assume non-interactive. progress monitor factory always returns ProgressMonitor.NULL, dataSetCache is trivial. + * + */ + private Boolean headless; // tristate: null, TRUE, FALSE + + private NameContext nameContext; + + private DataSetCache dataSetCache; + + public Logger getLogger() { + return DasLogger.getLogger(); + } + + /** + * @deprecated use DasLogger.getLogger( LoggerId ) + */ + public Logger getLogger( LoggerId id ) { + return DasLogger.getLogger(id); + } + + /** Creates a new instance of DasApplication */ + private DasApplication() { + nameContext = new NameContext(); + applet= null; + } + + public NameContext getNameContext() { + return nameContext; + } + + static ClassMap classNameMap= new ClassMap(); + static { + classNameMap.put( DasPlot.class, "plot" ); + classNameMap.put( DasAxis.class, "axis" ); + classNameMap.put( DasColorBar.class, "colorbar" ); + classNameMap.put( DasRow.class, "row"); + classNameMap.put( DasColumn.class, "column"); + classNameMap.put( DasAnnotation.class, "annotation"); + classNameMap.put( Object.class, "object" ); + classNameMap.put( DasCanvasComponent.class, "canvasComponent" ); + classNameMap.put( DasCanvas.class, "canvas" ); + } + + private Map hitsMap= new HashMap(); + + // note that only CanvasComponents have a name. + public String suggestNameFor( Object c ) { + String type= (String)classNameMap.get( c.getClass() ); + Integer hits= (Integer)hitsMap.get(type); + int ihits; + if ( hits==null ) { + ihits=0; + } else { + ihits= (hits.intValue())+1; + } + hitsMap.put( type, new Integer(ihits)); + return type+"_"+ihits; + } + + public static DasApplication getDefaultApplication() { + if ( DEFAULT==null ) { + DEFAULT= new DasApplication(); + } + return DEFAULT; + } + + /** + * nasty, evil method for releasing resources on a server. DO NOT USE THIS!!!! + */ + public static void resetDefaultApplication() { + DEFAULT= null; + } + + + public final boolean isApplet() { + if ( applet==null ) { + return Thread.currentThread().getContextClassLoader().getClass().getName().indexOf("plugin") > -1; + } else { + return applet.booleanValue(); + } + } + + /** + * check the security manager to see if all permissions are allowed, + * True indicates is not an applet running in a sandbox. + * @return true if all permissions are allowed + */ + public static boolean hasAllPermission() { + try { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new java.security.AllPermission()); + } + return true; + } catch ( SecurityException ex ) { + return false; + } + } + + /** + * support restricted security environment by checking permissions before + * checking property. + * @param name + * @param deft + * @return + */ + public static String getProperty( String name, String deft ) { + try { + return System.getProperty(name, deft); + } catch ( SecurityException ex ) { + return deft; + } + } + + + // force the application state to be applet or application + public void setApplet( boolean applet ) { + this.applet= Boolean.valueOf(applet); + } + + public void setReloadLoggingProperties( boolean v ) { + if ( v ) { + try { + DasLogger.reload(); + DasLogger.printStatus(); + } catch ( IOException e ) { + DasExceptionHandler.handle(e); + } + } + } + + public boolean isReloadLoggingProperties() { + return false; + } + + private static boolean isX11() { + String osName= System.getProperty( "os.name" ); // applet okay + return "SunOS".equals( osName ) + || "Linux".equals( osName ); + } + + /** + * returns the location of the local directory sandbox. For example, + * The web filesystem object downloads temporary files to here, logging + * properties file, etc. + * + * Assume that this File is local, so I/O is quick, and that the process + * has write access to this area. + * For definition, assume that at least 1Gb of storage is available as + * well. + */ + public static File getDas2UserDirectory() { + File local; + // for applets, if we are running from a disk, then it is okay to write local files, but we can't check permissions + if ( DasApplication.getProperty("user.name", "Web").equals("Web") ) { + local= new File("/tmp"); + } else { + local= new File( System.getProperty("user.home") ); + } + local= new File( local, ".das2" ); + return local; + } + + public static boolean isHeadAvailable() { + return true; + /* + return ( System.getProperty( "awt.toolkit" ) != null ); + /* + //boolean headAvailable= !java.awt.GraphicsEnvironment.isHeadless(); + boolean result= false; + if ( isApplet() ) result= true; + getDefaultApplication().getLogger().fine( System.getProperty( "os.name" ) ); + String osName= System.getProperty( "os.name" ); + if ( "Windows".equals( osName ) ) { + result= true; + } else if ( "Windows XP".equals( osName ) ) { + result= true; + } else if ( isX11() ) { + String DISPLAY= System.getProperty( "DISPLAY" ); + getDefaultApplication().getLogger().fine( System.getProperty( "DISPLAY" ) ); + if ( "".equals(DISPLAY) ) { + result= false; + } else { + result= true; + } + } + return result; + */ + } + + public boolean isHeadless() { +/* if ( !headAvailable() && !"true".equals(System.getProperty("headless")) ) { + getLogger().info("setting headless to true"); + setHeadless( true ); + } */ + if ( headless!=null ) { + return headless.booleanValue(); + } else { + return "true".equals(DasApplication.getProperty("java.awt.headless","false")); + } + } + + public void setHeadless( boolean headless ) { + this.headless= Boolean.valueOf(headless); + if ( headless ) { + System.setProperty("java.awt.headless","true"); + } else { + if ( ! isHeadAvailable() ) { + throw new IllegalArgumentException( "attempt to unset headless when environment is headless." ); + } + System.setProperty("java.awt.headless","false"); + } + } + + + InputStreamMeter meter; + public InputStreamMeter getInputStreamMeter() { + if ( meter==null ) { + meter= new InputStreamMeter(); + } + return meter; + } + + /** + * @deprecated use createMainFrame( String title, Container container ); + */ + public JFrame createMainFrame( java.awt.Container container ) { + return createMainFrame( "Das2", container ); + } + + public JFrame createMainFrame( String title, java.awt.Container container ) { + JFrame frame= createMainFrame(title); + frame.getContentPane().add(container); + frame.pack(); + frame.setVisible(true); + return frame; + } + + /** + * @deprecated use createMainFrame(String title) + */ + public JFrame createMainFrame() { + return createMainFrame("Das2"); + } + + public JFrame createMainFrame( String title ) { + mainFrame= new JFrame(title); + final Preferences prefs= Preferences.userNodeForPackage(DasApplication.class); + int xlocation= prefs.getInt( "xlocation", 20 ); + int ylocation= prefs.getInt( "ylocation", 20 ); + mainFrame.setLocation(xlocation, ylocation); + mainFrame.addWindowListener( new WindowAdapter() { + public void windowClosing( WindowEvent e ) { + quit(); + } + } ); + mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + System.setProperty( "sun.awt.exception.handler", DasExceptionHandler.class.getName() ); + return mainFrame; + } + + public JFrame getMainFrame() { + return this.mainFrame; + } + + public void quit() { + final Preferences prefs= Preferences.userNodeForPackage(DasApplication.class); + prefs.putInt( "xlocation", mainFrame.getLocation().x ); + prefs.putInt( "ylocation", mainFrame.getLocation().y ); + System.out.println("bye!"+mainFrame.getLocation()); + System.exit(0); + } + + /** + * Holds value of property interactive. + */ + private boolean interactive=true; + + /** + * Getter for property interactive. + * @return Value of property interactive. + */ + public boolean isInteractive() { + return this.interactive; + } + + /** + * Setter for property interactive. + * @param interactive New value of property interactive. + */ + public void setInteractive(boolean interactive) { + this.interactive = interactive; + } + + public String getDas2Version() { + return Splash.getVersion(); + } + + private MonitorFactory monitorFactory; + + public MonitorFactory getMonitorFactory() { + if ( monitorFactory==null ) { + if ( !isHeadless() ) { + monitorFactory= new DefaultMonitorFactory(); + } else { + monitorFactory= new NullMonitorFactory(); + } + } + return monitorFactory; + } + + public DataSetCache getDataSetCache() { + if ( dataSetCache==null ) { + if ( isHeadless() ) { + dataSetCache= new NullDataSetCache(); + } else { + dataSetCache= new LimitSizeBytesDataSetCache(30000000); + } + } + return dataSetCache; + } + + ExceptionHandler exceptionHandler; + + public ExceptionHandler getExceptionHandler() { + if ( exceptionHandler==null ) { + if ( isHeadless() ) { + exceptionHandler= new ThrowRuntimeExceptionHandler(); + } else { + exceptionHandler= new DefaultExceptionHandler(); + } + } + return exceptionHandler; + } + +} diff --git a/dasCore/src/main/java/org/das2/DasException.java b/dasCore/src/main/java/org/das2/DasException.java new file mode 100644 index 000000000..7acd248cd --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasException.java @@ -0,0 +1,48 @@ +/* File: DasException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +/** + * + * @author jbf + */ +public class DasException extends java.lang.Exception { + + /** Creates a new instance of DasException */ + public DasException() { + super(); + } + + public DasException(String message) { + super(message); + } + + public DasException( Throwable ex) { + super(ex); + } + + public DasException( String msg, Throwable ex) { + super( msg, ex ); + } +} diff --git a/dasCore/src/main/java/org/das2/DasIOException.java b/dasCore/src/main/java/org/das2/DasIOException.java new file mode 100644 index 000000000..7b3746092 --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasIOException.java @@ -0,0 +1,55 @@ +/* File: DasIOException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +/** + * + * @author jbf + */ +public class DasIOException extends org.das2.DasException { + + /** + * Creates a new instance of DasIOException without detail message. + */ + public DasIOException() { + } + + + /** + * Constructs an instance of DasIOException with the specified detail message. + * @param msg the detail message. + */ + public DasIOException(String msg) { + super(msg); + } + + public DasIOException(String msg, Throwable t) { + super(msg,t); + } + + public DasIOException(java.io.IOException ex) { + super( ex.getMessage() ); + initCause(ex); + } +} diff --git a/dasCore/src/main/java/org/das2/DasNameException.java b/dasCore/src/main/java/org/das2/DasNameException.java new file mode 100644 index 000000000..a39b758cd --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasNameException.java @@ -0,0 +1,40 @@ +/* File: DasNameException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +/** + * + * @author eew + */ +public class DasNameException extends org.das2.DasException { + + /** Creates a new instance of DasNameException */ + public DasNameException() { + } + + public DasNameException(String message) { + super(message); + } + +} diff --git a/dasCore/src/main/java/org/das2/DasProperties.java b/dasCore/src/main/java/org/das2/DasProperties.java new file mode 100755 index 000000000..baf86e0fe --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasProperties.java @@ -0,0 +1,308 @@ +/* File: DasProperties.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Properties; +import java.util.logging.*; + +public class DasProperties extends Properties { + + // Contains the global user-configurable parameters that are + // persistent between sessions. + + private RenderingHints hints; + private boolean antiAlias; + private boolean visualCues; + private Logger logger; + private static ArrayList propertyOrder; + private static Editor editor; + private static JFrame jframe; + + private DasProperties() { + super(); + hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + setDefaults(); + propertyOrder= new ArrayList(); + if ( DasApplication.hasAllPermission() ) readPersistentProperties(); + logger= Logger.getLogger("das2"); + setPropertyOrder(); + } + + private void setPropertyOrder() { + propertyOrder.add(0,"username"); + propertyOrder.add(1,"password"); + propertyOrder.add(2,"debugLevel"); + propertyOrder.add(3,"antiAlias"); + propertyOrder.add(4,"visualCues"); + for (Iterator i= this.keySet().iterator(); i.hasNext(); ) { + String s= (String)i.next(); + if (!propertyOrder.contains(s)) { + propertyOrder.add(s); + } + } + } + + private void setDefaults() { + setProperty("username",""); + setProperty("password",""); + setProperty("debugLevel","endUser"); + setProperty("antiAlias","off"); + setProperty("visualCues","off"); + setProperty("defaultServer","http://www-pw.physics.uiowa.edu/das/dasServer"); + } + + public static RenderingHints getRenderingHints() { + return instance.hints; + } + + public static Logger getLogger() { + return instance.logger; + } + + public static DasProperties getInstance() { + return instance; + } + + private static class DasPropertiesTableModel extends AbstractTableModel { + public int getColumnCount() { return 2; } + public int getRowCount() { + return instance.size(); + } + public Object getValueAt(int row, int col) { + String propertyName= (String)propertyOrder.get(row); + String value; + if (col==0) { + value= propertyName; + } else { + value= instance.getProperty(propertyName); + if (propertyName.equals("password")) { + value=""; + } + } + return value; + } + public void setValueAt(Object value, int row, int col) { + String propertyName= (String)propertyOrder.get(row); + if (propertyName.equals("password")) { + if (!value.toString().equals("")) { + value= org.das2.util.Crypt.crypt(value.toString()); + } + } else if ( propertyName.equals("debugLevel") ) { + String debugLevel= value.toString(); + if (debugLevel.equals("endUser")) { + Logger.getLogger("").setLevel(Level.WARNING); + Logger.getLogger("das2").setLevel(Level.WARNING); + } else if (debugLevel.equals("dasDeveloper")) { + Logger.getLogger("").setLevel(Level.FINE); + Logger.getLogger("das2").setLevel(Level.FINE); + } + else instance.logger.setLevel(Level.parse(debugLevel)); + org.das2.util.DasDie.setDebugVerbosityLevel(value.toString()); + } + instance.setProperty(propertyName,value.toString()); + editor.setDirty(true); + } + + public boolean isCellEditable(int row, int col) { return (col==1); } + } + + private static TableModel getTableModel() { + return new DasPropertiesTableModel(); + } + + private static JTable getJTable() { + return new JTable(getTableModel()) { + {setDefaultRenderer(Object.class,new DefaultTableCellRenderer(){ + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + String propertyName= (String)propertyOrder.get(row); + if (propertyName.equals("password") && column==1 ) { + return super.getTableCellRendererComponent( table, "* * * *", isSelected, hasFocus, row, column ); + } else { + return super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); + } + } + });} + public TableCellEditor getCellEditor(int row, int col) { + String propertyName= (String)propertyOrder.get(row); + if (propertyName.equals("password")) { + return new DefaultCellEditor(new JPasswordField()); + } else if (propertyName.equals("debugLevel")) { + String[] data= {"endUser","dasDeveloper"}; + return new DefaultCellEditor(new JComboBox(data)); + } else if (propertyName.equals("antiAlias")) { + String[] data= {"on","off"}; + return new DefaultCellEditor(new JComboBox(data)); + } else if (propertyName.equals("visualCues")) { + String[] data= {"on","off"}; + return new DefaultCellEditor(new JComboBox(data)); + } else { + return super.getCellEditor(row,col); + } + } + }; + } + + + private static TableCellEditor getTableCellEditor() { + return new DefaultCellEditor(new JTextField()) { + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + String propertyName= (String)propertyOrder.get(row); + if (propertyName.equals("password")) { + return new JPasswordField(); + } else if (propertyName.equals("debugLevel")) { + String[] data= {"endUser","dasDeveloper"}; + return new JList(data); + } else { + return super.getTableCellEditorComponent(table,value,isSelected,row,column); + } + } + }; + } + + public static class Editor extends JPanel implements ActionListener { + JButton saveButton; + + Editor() { + super(); + setLayout(new BoxLayout(this,BoxLayout.Y_AXIS)); + + JTable jtable= getJTable(); + add(jtable); + + JPanel controlPanel= new JPanel(); + controlPanel.setLayout(new BoxLayout(controlPanel,BoxLayout.X_AXIS)); + + controlPanel.add(Box.createHorizontalGlue()); + + JButton b; + saveButton= new JButton("Save"); + saveButton.setActionCommand("Save"); + saveButton.addActionListener(this); + saveButton.setToolTipText("save to $HOME/.das2rc"); + + controlPanel.add(saveButton); + + b= new JButton("Dismiss"); + b.addActionListener(this); + controlPanel.add(b); + + add( Box.createVerticalGlue() ); + + add(controlPanel); + + } + + public void actionPerformed(ActionEvent e) { + String command= e.getActionCommand(); + if (command.equals("Save")) { + instance.writePersistentProperties(); + setDirty(false); + } else if (command.equals("Dismiss")) { + jframe.dispose(); + } + + } + + public void setDirty(boolean dirty){ + if (dirty) { + saveButton.setText("Save*"); + } else { + saveButton.setText("Save"); + } + } + } + + public static void showEditor() { + jframe= new JFrame("Das Properities"); + editor= new Editor(); + + jframe.setSize(400,300); + jframe.setContentPane(editor); + jframe.setVisible(true); + } + + private static final DasProperties instance = new DasProperties(); + + public void readPersistentProperties() { + + try { + String file= System.getProperty("user.home")+System.getProperty("file.separator")+".das2rc"; + File f= new File(file); + + if (f.canRead()) { + try { + InputStream in= new FileInputStream(f); + load(in); + in.close(); + } catch (IOException e) { + org.das2.util.DasExceptionHandler.handle(e); + } + } else { + try { + OutputStream out= new FileOutputStream(f); + store(out,""); + out.close(); + } catch (IOException e) { + org.das2.util.DasExceptionHandler.handle(e); + } + } + } catch ( SecurityException ex ) { + ex.printStackTrace(); + } + } + + public void writePersistentProperties() { + + String file= System.getProperty("user.home")+System.getProperty("file.separator")+".das2rc"; + File f= new File(file); + + if (f.canWrite()) { + org.das2.util.DasDie.println("Attempt to write .das2rc..."); + try { + OutputStream out= new FileOutputStream(f); + store(out,""); + out.close(); + } catch (IOException e) { + org.das2.util.DasExceptionHandler.handle(e); + } + } else { + DasException e= new org.das2.DasIOException("Can't write to file "+f); + org.das2.util.DasExceptionHandler.handle(e); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/DasPropertyException.java b/dasCore/src/main/java/org/das2/DasPropertyException.java new file mode 100644 index 000000000..dec265786 --- /dev/null +++ b/dasCore/src/main/java/org/das2/DasPropertyException.java @@ -0,0 +1,99 @@ +/* File: DasPropertyException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2; + +public class DasPropertyException extends org.das2.DasException { + + public static final class MessageType { + private final String message; + private MessageType(String message) { + this.message = message; + } + public String toString() { + return message; + } + } + + public static final MessageType NOT_DEFINED + = new MessageType("The property is undefined for ."); + public static final MessageType READ_ONLY + = new MessageType("The property of is read-only."); + public static final MessageType TYPE_MISMATCH + = new MessageType("Type mismatch: of cannot be set to "); + public static final MessageType NOT_INDEXED + = new MessageType("The property of is not an indexed property"); + + private String propertyName; + private String objectName; + private String value; + private MessageType type; + + public DasPropertyException(MessageType type, String propertyName, String objectName, String value) { + this.type = type; + this.propertyName = propertyName; + this.objectName = objectName; + this.value = value; + } + + public DasPropertyException(MessageType type, String propertyName, String objectName) { + this(type, propertyName, objectName, null); + } + + public DasPropertyException(MessageType type) { + this(type, null, null); + } + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String name) { + propertyName = name; + } + + public String getObjectName() { + return objectName; + } + + public void setObjectName(String name) { + objectName = name; + } + + public MessageType getType() { + return type; + } + + public void setMessageType(MessageType type) { + this.type = type; + } + + public String getMessage() { + String message = type.toString(); + String objStr = objectName == null ? "" : objectName; + String ptyStr = propertyName == null ? "" : propertyName; + String valueStr = value == null ? "" : value; + return message.replaceAll("", objStr) + .replaceAll("", ptyStr) + .replaceAll("", valueStr); + } +} diff --git a/dasCore/src/main/java/org/das2/NameContext.java b/dasCore/src/main/java/org/das2/NameContext.java new file mode 100644 index 000000000..11851c3a1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/NameContext.java @@ -0,0 +1,365 @@ +/* File: NameContext.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2; + +import org.das2.datum.Datum; +import org.das2.datum.TimeUtil; +import org.das2.beans.BeansUtil; +import org.das2.dasml.ParsedExpression; +import org.das2.dasml.ParsedExpressionException; + +import java.beans.*; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** An instance of NameContext defines the name space for a + * dasml/das2 application. Methods for querying values of properties are + * also provided. + * + * @author eew + */ +public class NameContext { + + private static final String SIMPLE_NAME_STRING = "[A-Za-z][A-Za-z0-9_]*"; + private static final String INDEX_STRING = "0|[1-9][0-9]*"; + private static final String INDEXED_NAME_STRING + = "(" + SIMPLE_NAME_STRING + ")" + "\\[(" + INDEX_STRING + ")\\]"; + private static final String QUALIFIED_NAME_STRING + = SIMPLE_NAME_STRING + "(\\." + SIMPLE_NAME_STRING + "|\\." + INDEXED_NAME_STRING + ")*"; + + public static final Pattern SIMPLE_NAME = Pattern.compile(SIMPLE_NAME_STRING); + public static final Pattern INDEXED_NAME = Pattern.compile(INDEXED_NAME_STRING); + public static final Pattern QUALIFIED_NAME = Pattern.compile(QUALIFIED_NAME_STRING); + public static final Pattern refPattern = Pattern.compile("\\$\\{([^\\}]+)\\}"); + public static final Pattern intPattern = Pattern.compile("-?(0|[1-9][0-9]*)"); + public static final Pattern floatPattern = Pattern.compile("-?[0-9]*(\\.[0-9]*)?([eE]-?[0-9]+)?"); + + + private Map nameMap; + private Map propertyMap; + + /** Creates a new instance of NameContext */ + NameContext() { + nameMap = new HashMap(); + propertyMap = new HashMap(); + } + + /** Associates a value with a name in this context. The name + * parameter must being with a letter and can only consist of alphanumeric + * characters and '_'. + * @param name the name for the value to be associated with + * @param value the value being named + */ + public void put(String name, Object value) throws DasNameException { + Matcher m = SIMPLE_NAME.matcher(name); + if (m.matches()) { + nameMap.put(name, new WeakReference( value ) ); + } + else { + throw new DasNameException(name + " must match " + SIMPLE_NAME_STRING); + } + } + + public Object get(String name) throws DasPropertyException, InvocationTargetException { + if (name == null) { + throw new NullPointerException("name"); + } + Matcher m = SIMPLE_NAME.matcher(name); + if (m.matches()) { + return ((WeakReference)nameMap.get(name)).get(); + } + int index = name.lastIndexOf('.'); + Object obj = get(name.substring(0, index)); + String property = name.substring(index + 1); + m = INDEXED_NAME.matcher(property); + if (m.matches()) { + property = m.group(1); + index = Integer.parseInt(m.group(2)); + return getIndexedPropertyValue(obj, property, index); + } + else { + return getPropertyValue(obj, property); + } + } + + public void set(String name, Object value) throws InvocationTargetException, ParsedExpressionException, DasPropertyException, DasNameException { + if (name == null) { + throw new NullPointerException("name"); + } + Matcher m = SIMPLE_NAME.matcher(name); + if (m.matches()) { + put(name, value); + } + int index = name.lastIndexOf('.'); + Object obj = get(name.substring(0, index)); + String property = name.substring(index + 1); + m = INDEXED_NAME.matcher(property); + if (m.matches()) { + property = m.group(1); + index = Integer.parseInt(m.group(2)); + setIndexedPropertyValue(obj, property, index, value); + } + else { + setPropertyValue(obj, property, value); + } + } + + public Object getPropertyValue(Object obj, String property) throws DasPropertyException, InvocationTargetException { + try { + Class type = obj.getClass(); + maybeLoadPropertiesForClass(type); + Map map = (Map)propertyMap.get(type); + PropertyDescriptor pd = (PropertyDescriptor)map.get(property); + if (pd == null) { + throw new DasPropertyException(DasPropertyException.NOT_DEFINED, null, property); + } + Method readMethod = pd.getReadMethod(); + return readMethod.invoke(obj); + } + catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + public Object getIndexedPropertyValue(Object obj, String property, int index) throws DasPropertyException, InvocationTargetException { + try { + Class type = obj.getClass(); + maybeLoadPropertiesForClass(type); + Map map = (Map)propertyMap.get(type); + PropertyDescriptor pd = (PropertyDescriptor)map.get(property); + if (pd == null) { + throw new DasPropertyException(DasPropertyException.NOT_DEFINED, null, property); + } + if (!(pd instanceof IndexedPropertyDescriptor)) { + throw new DasPropertyException(DasPropertyException.NOT_INDEXED, null, property); + } + IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)pd; + Method readMethod = ipd.getIndexedReadMethod(); + return readMethod.invoke(obj, new Object[] { new Integer(index) }); + } + catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + public void setPropertyValue(Object obj, String property, Object value) throws InvocationTargetException, ParsedExpressionException, DasPropertyException { + try { + Class type = obj.getClass(); + maybeLoadPropertiesForClass(type); + Map map = (Map)propertyMap.get(type); + PropertyDescriptor pd = (PropertyDescriptor)map.get(property); + if (pd == null) { + throw new DasPropertyException(DasPropertyException.NOT_DEFINED, null, property); + } + Method writeMethod = pd.getWriteMethod(); + if (writeMethod == null) { + throw new DasPropertyException(DasPropertyException.READ_ONLY, null, property); + } + Class propertyType = pd.getPropertyType(); + if (value instanceof String) { + value = parseValue((String)value, propertyType); + } + if (!propertyType.isInstance(value) + && !(propertyType == boolean.class && value instanceof Boolean) + && !(propertyType == char.class && value instanceof Character) + && !(propertyType == double.class && value instanceof Double) + && !(propertyType == short.class && value instanceof Short) + && !(propertyType == int.class && value instanceof Integer) + && !(propertyType == float.class && value instanceof Float) + && !(propertyType == byte.class && value instanceof Byte) + && !(propertyType == long.class && value instanceof Long)) { + throw new DasPropertyException(DasPropertyException.TYPE_MISMATCH, null, property); + } + writeMethod.invoke(obj, new Object[] { value } ); + } + catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + public void setIndexedPropertyValue(Object obj, String property, int index, Object value) throws InvocationTargetException, ParsedExpressionException, DasPropertyException { + try { + Class type = obj.getClass(); + maybeLoadPropertiesForClass(type); + Map map = (Map)propertyMap.get(type); + PropertyDescriptor pd = (PropertyDescriptor)map.get(property); + if (pd == null) { + throw new DasPropertyException(DasPropertyException.NOT_DEFINED, null, property); + } + if (!(pd instanceof IndexedPropertyDescriptor)) { + throw new DasPropertyException(DasPropertyException.NOT_INDEXED, null, property); + } + IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)pd; + Method writeMethod = ipd.getIndexedWriteMethod(); + if (writeMethod == null) { + throw new DasPropertyException(DasPropertyException.READ_ONLY, null, property); + } + Class propertyType = pd.getPropertyType(); + if (value instanceof String) { + value = parseValue((String)value, propertyType); + } + if (!propertyType.isInstance(value) + && !(propertyType == boolean.class && value instanceof Boolean) + && !(propertyType == char.class && value instanceof Character) + && !(propertyType == double.class && value instanceof Double) + && !(propertyType == short.class && value instanceof Short) + && !(propertyType == int.class && value instanceof Integer) + && !(propertyType == float.class && value instanceof Float) + && !(propertyType == byte.class && value instanceof Byte) + && !(propertyType == long.class && value instanceof Long)) { + throw new DasPropertyException(DasPropertyException.TYPE_MISMATCH, null, property); + } + writeMethod.invoke(obj, new Object[] { new Integer(index), value }); + } + catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + private void maybeLoadPropertiesForClass(Class cl) { + try { + if (propertyMap.get(cl) == null) { + BeanInfo info = BeansUtil.getBeanInfo(cl); + HashMap map = new HashMap(); + PropertyDescriptor[] properties = info.getPropertyDescriptors(); + for (int i = 0; i < properties.length; i++) { + if (properties[i].getReadMethod() == null) { + continue; + } + if (properties[i] instanceof IndexedPropertyDescriptor) { + IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)properties[i]; + if (ipd.getIndexedReadMethod() == null) { + continue; + } + } + map.put(properties[i].getName(), properties[i]); + } + propertyMap.put(cl, map); + } + } + catch (IntrospectionException ie) { + } + } + + public void remove(String name) { + nameMap.remove(name); + } + + /** + * Parses the given String object in an attempt to + * produce the an object of the given type. + * + * @param valueString the given String + * @param type the given type + */ + public Object parseValue(String valueString, Class type) throws org.das2.dasml.ParsedExpressionException, InvocationTargetException, DasPropertyException { + Object parsedValue; + valueString = replaceReferences(valueString); + if (type == String.class) { + return valueString; + } + else if (type == boolean.class || type == Boolean.class) { + if (valueString.equals("true")) return Boolean.TRUE; + if (valueString.equals("false")) return Boolean.FALSE; + ParsedExpression exp = new ParsedExpression(valueString); + Object o = exp.evaluate(this); + if (!(o instanceof Boolean)) throw new ParsedExpressionException("'" + valueString + "' does not evaluate to a boolean value"); + return o; + } + else if (type == int.class || type == Integer.class) { + if (intPattern.matcher(valueString).matches()) { + return new Integer(valueString); + } + ParsedExpression exp = new ParsedExpression(valueString); + Object o = exp.evaluate(this); + if (!(o instanceof Number)) throw new ParsedExpressionException("'" + valueString + "' does not evaluate to a numeric value"); + return (o instanceof Integer ? (Integer)o : new Integer(((Number)o).intValue())); + } + else if (type == long.class || type == Long.class) { + if (intPattern.matcher(valueString).matches()) { + parsedValue = new Long(valueString); + } + ParsedExpression exp = new ParsedExpression(valueString); + Object o = exp.evaluate(this); + if (!(o instanceof Number)) throw new ParsedExpressionException("'" + valueString + "' does not evaluate to a numeric value"); + return new Long(((Number)o).longValue()); + } + else if (type == float.class || type == Float.class) { + if (floatPattern.matcher(valueString).matches()) { + parsedValue = new Float(valueString); + } + ParsedExpression exp = new ParsedExpression(valueString); + Object o = exp.evaluate(this); + if (!(o instanceof Number)) throw new ParsedExpressionException("'" + valueString + "' does not evaluate to a numeric value"); + return new Float(((Number)o).floatValue()); + } + else if (type == double.class || type == Double.class) { + if (floatPattern.matcher(valueString).matches()) { + parsedValue = new Double(valueString); + } + ParsedExpression exp = new ParsedExpression(valueString); + Object o = exp.evaluate(this); + if (!(o instanceof Number)) throw new ParsedExpressionException("'" + valueString + "' does not evaluate to a numeric value"); + return (o instanceof Double ? (Double)o : new Double(((Number)o).doubleValue())); + } + else if (type == Datum.class) { + try { + return TimeUtil.create(valueString); + } + catch ( java.text.ParseException ex ) { + try { + return Datum.create(Double.parseDouble(valueString)); + } + catch (NumberFormatException iae) { + throw new ParsedExpressionException(valueString + " cannot be parsed as a Datum"); + } + } + + } + else { + throw new IllegalStateException(type.getName() + " is not a recognized type"); + } + } + + protected String replaceReferences(String str) throws DasPropertyException, InvocationTargetException { + Matcher matcher = refPattern.matcher(str); + while (matcher.find()) { + String name = matcher.group(1).trim(); + Object value = get(name); + str = matcher.replaceFirst(value.toString()); + matcher.reset(str); + } + return str; + } + + public String toString() { + return getClass().getName() + nameMap.keySet().toString(); + } +} diff --git a/dasCore/src/main/java/org/das2/apiProblems.txt b/dasCore/src/main/java/org/das2/apiProblems.txt new file mode 100644 index 000000000..b958202f4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/apiProblems.txt @@ -0,0 +1,18 @@ +1. event package + 1. mouse events / drag renderers / mouse modules + 1. difficult to reuse existing objects because a lack of composition. + 2. too much typeness in drag renderers, should let composing code enforce typeness. + 3. poor definition of the role of each class. + 4. need definition of how key strokes can be used, which are used by the DMIA which + can the mousemodule use. + +2. system/das package + 1. DasApplication + 1. DasApplication.getDefaultApplication() does not allow for multiple applications to + run within the same java context, as in a web applications server. + 2. Perhaps the application object should be explicitly created, then canvases and JPanels attached to + it. An implicit application object, perhaps the legacy DasApplication.getDefaultApplication() + could be used as well. + + + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/beans/AccessLevelBeanInfo.java b/dasCore/src/main/java/org/das2/beans/AccessLevelBeanInfo.java new file mode 100644 index 000000000..d048e1f38 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/AccessLevelBeanInfo.java @@ -0,0 +1,347 @@ +/* File: AccessLevelBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.DasApplication; +import java.beans.*; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * This class is designed to implement access levels for bean properties. + * The system property "edu.uiowa.physics.das.beans.AccessLevelBeanInfo.AccessLevel" will + * determine the access level of the bean. The access levels that are currently supported + * are "ALL" and "END_USER". The access level must be set prior to this class being loaded. + * + * @author Edward West + */ +public abstract class AccessLevelBeanInfo extends SimpleBeanInfo { + + /** + * Type-safe enumeration class used to specify access levels + * for bean properties. + * + * **NOTE TO DEVELOPERS** + * The order parameter for the constructor should not be specifed as a negative number + */ + public static class AccessLevel implements Comparable { + public static final AccessLevel ALL = new AccessLevel("ALL", 0); + public static final AccessLevel DASML = new AccessLevel("DASML", 1000); + public static final AccessLevel END_USER = new AccessLevel("END_USER", 0x7FFF0000); + private String str; + private int order; + private AccessLevel(String str, int order) { + this.str = str; this.order = order; + } + public int compareTo(Object o) { + return order - ((AccessLevel)o).order; + } + public String toString() { + return str; + } + } + + /** + * this level indicates what persistence is allowed. + */ + public static class PersistenceLevel implements Comparable { + public static final PersistenceLevel NONE = new PersistenceLevel( "NONE", 0); + public static final PersistenceLevel TRANSIENT = new PersistenceLevel( "TRANSIENT", 1000 ); + public static final PersistenceLevel PERSISTENT = new PersistenceLevel( "PERSISTENT", 2000); + + private String str; + private int order; + private PersistenceLevel(String str, int order) { + this.str = str; this.order = order; + } + public int compareTo(Object o) { + return order - ((PersistenceLevel)o).order; + } + public String toString() { + return str; + } + } + + private Property[] properties; + private Class beanClass; + private static AccessLevel accessLevel; + private static Object lockObject = new Object(); + + static { + String level = DasApplication.getProperty("edu.uiowa.physics.das.beans.AccessLevelBeanInfo.AccessLevel",null); + if (level==null) { + accessLevel = AccessLevel.ALL; + } else if (level.equals("ALL")) { + accessLevel = AccessLevel.ALL; + } else if (level.equals("DASML")) { + accessLevel = AccessLevel.DASML; + } else if (level.equals("END_USER")) { + accessLevel = AccessLevel.END_USER; + } else { + accessLevel = AccessLevel.ALL; + } + } + + /** + * Returns the access level for AccessLevelBeanInfo objects. + */ + public static AccessLevel getAccessLevel() { + return accessLevel; + } + + /** + * Sets the access level for AccessLevelBeanInfo objects. + */ + public static void setAccessLevel(AccessLevel level) { + synchronized (lockObject) { + accessLevel = level; + } + } + + public static Object getLock() { + return lockObject; + } + + /** + * Creates and instance of AccessLevelBeanInfo. + * Each element of the properties array must be of the type + * Object[] with the following format: + * { propertyName, accessorMethod, mutatorMethod, accessLevel} + * where the elements have the following meaning. + *
    + *
  • propertyName - A String naming the property being specified.
  • + *
  • accessorMethod - A String specifying the name of the read method + * for this property.
  • + *
  • mutatorMethod - A String specifying the name of the write method + * for this property
  • + *
  • accessLevel - A org.das2.beans.AccessLevelBeanInfo.AccessLevel instance specifying + * the access level for this property.
  • + *
+ */ + protected AccessLevelBeanInfo(Property[] properties, Class beanClass) { + this.properties = properties; + this.beanClass = beanClass; + } + + /** + * convenient method that only returns the descriptors for the specified persistence level. + * Also implements the property inheritance. + */ + public PropertyDescriptor[] getPropertyDescriptors( PersistenceLevel persistenceLevel ) { + synchronized (lockObject) { + try { + ArrayList result= new ArrayList(); + int propertyIndex = 0; + for (int index = 0; index < properties.length; index++) { + if (persistenceLevel.compareTo(properties[index].getPersistenceLevel()) <= 0) { + result.add( properties[index].getPropertyDescriptor(beanClass) ); + } + } + BeanInfo[] moreBeanInfos= getAdditionalBeanInfo() ; + if ( moreBeanInfos!=null ) { + for ( int i=0; i0) { + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; +import org.das2.graph.DasAxis; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; + +/** + * BeanInfo class for DasColorBar + * + * @author Edward West + */ +public class AttachedLabelBeanInfo extends AccessLevelBeanInfo { + + protected static final Property[] properties = { + new Property("label", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getLabel", "setLabel", null ), + new Property("orientation", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getOrientation", "setOrientation", null ), + new Property("emOffset", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getEmOffet", "setEmOffset", null ), + }; + + public AttachedLabelBeanInfo() { + super(properties, org.das2.graph.AttachedLabel.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new DasCanvasComponentBeanInfo(), + }; + return additional; + + /*try { + BeanInfo[] additional = { + Introspector.getBeanInfo( DasAxis.class ), + }; + return additional; + } catch ( IntrospectionException e ) { + throw new RuntimeException(e); + }*/ + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/BeansUtil.java b/dasCore/src/main/java/org/das2/beans/BeansUtil.java new file mode 100644 index 000000000..c5b557339 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/BeansUtil.java @@ -0,0 +1,284 @@ +/* + * BeanInfoUtil.java + * + * Created on May 31, 2005, 11:49 AM + */ +package org.das2.beans; + +import org.das2.graph.FillStyle; +import org.das2.graph.PsymConnector; +import org.das2.graph.Psym; +import org.das2.graph.PlotSymbol; +import org.das2.components.DatumEditor; +import org.das2.components.propertyeditor.BooleanEditor; +import org.das2.components.propertyeditor.EnumerationEditor; +import org.das2.components.propertyeditor.ColorEditor; +import org.das2.components.DatumRangeEditor; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.NumberUnits; +import org.das2.DasApplication; +import org.das2.system.DasLogger; +import org.das2.util.ClassMap; +import java.awt.Color; +import java.beans.*; +import java.util.*; +import java.util.logging.Logger; + +/** + * + * @author Jeremy + */ +public class BeansUtil { + + static Logger log = DasLogger.getLogger(DasLogger.SYSTEM_LOG); + + + static { + String[] beanInfoSearchPath = {"org.das2.beans", "sun.beans.infos"}; + if (DasApplication.hasAllPermission()) { + Introspector.setBeanInfoSearchPath(beanInfoSearchPath); + } + } + static ClassMap editorRegistry = new ClassMap(); + + /** + * see BeanBindingDemo2.java + */ + public static void registerEditor(Class beanClass, Class editorClass) { + if ( DasApplication.hasAllPermission() ) { + PropertyEditorManager.registerEditor(beanClass, editorClass); + editorRegistry.put(beanClass, editorClass); + } else { + + } + } + /** + * See that the known editors are registered with the PropertyEditorManager + */ + + + static { + if (DasApplication.hasAllPermission()) { + registerEditor(Datum.class, DatumEditor.class); + registerEditor(DatumRange.class, DatumRangeEditor.class); + registerEditor(Color.class, ColorEditor.class); + registerEditor(Units.class, UnitsEditor.class); + registerEditor(NumberUnits.class, UnitsEditor.class); + registerEditor(Boolean.TYPE, BooleanEditor.class); + registerEditor(Boolean.class, BooleanEditor.class); + registerEditor(PsymConnector.class, EnumerationEditor.class); + registerEditor(Psym.class, EnumerationEditor.class); + registerEditor(PlotSymbol.class, EnumerationEditor.class); + registerEditor(FillStyle.class, EnumerationEditor.class); + // registerEditor(Rectangle.class, RectangleEditor.class); + //registerEditor(DasServer.class, DasServerEditor.class); + } + } + /** + * There's an annoyance with PropertyEditorManager.findEditor, in that it + * always goes looking for the editor with the classLoader. This is annoying + * for applets, because this causes an applet codebase hit each time its called, + * even if it's already been called for the given class. This is problematic + * with the propertyEditor, which calls this for each property, making it + * sub-interactive at best. Here we keep track of the results, either in a + * list of nullPropertyEditors or by registering the editor we just found. + */ + static HashSet nullPropertyEditors = new HashSet(); + + public static java.beans.PropertyEditor findEditor(Class propertyClass) { + java.beans.PropertyEditor result = null; + if (nullPropertyEditors.contains(propertyClass)) { + result = null; + } else { + result = PropertyEditorManager.findEditor(propertyClass); + + if (result == null) { + Class resultClass = (Class) editorRegistry.get(propertyClass); + if (resultClass != null) { + try { + result = (java.beans.PropertyEditor) resultClass.newInstance(); // TODO: this branch will cause excessive lookups for applets. + } catch (InstantiationException ex) { + ex.printStackTrace(); + } catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + } + } + + // if still null, then keep track of the null so we don't have to search again. + if (result == null) { + nullPropertyEditors.add(propertyClass); + //result= new PropertyEditorSupport(); + } else { + if ( DasApplication.hasAllPermission() ) PropertyEditorManager.registerEditor(propertyClass, result.getClass()); // TODO: this will cause problems when the super class comes before subclass + } + + } + return result; + } + + /** + * One-stop place to get the editor for the given propertyDescriptor. + */ + public static java.beans.PropertyEditor getEditor(PropertyDescriptor pd) { + java.beans.PropertyEditor editor = null; + + try { + Class editorClass = pd.getPropertyEditorClass(); + if (editorClass != null) { + editor = (java.beans.PropertyEditor) editorClass.newInstance(); + } + } catch (InstantiationException ex) { + } catch (IllegalAccessException ex) { + } + + if (editor == null) { + editor = BeansUtil.findEditor(pd.getPropertyType()); + } + return editor; + } + + /** + * Use reflection to get a list of all the property names for the class. + * The properties are returned in the order specified, and put inherited properties + * at the end of the list. Implement the include/exclude logic. + */ + public static PropertyDescriptor[] getPropertyDescriptors(Class c) { + Set excludePropertyNames; + excludePropertyNames = new HashSet(); + excludePropertyNames.add("class"); + excludePropertyNames.add("listLabel"); + excludePropertyNames.add("listIcon"); + + try { + BeanInfo beanInfo = getBeanInfo(c); + + List propertyList = new ArrayList(); + + PropertyDescriptor[] pdthis; + try { + pdthis = beanInfo.getPropertyDescriptors(); + } catch (IllegalStateException e) { + throw new RuntimeException(e); + } + + for (int i = 0; i < pdthis.length; i++) { + boolean isWriteable = pdthis[i].getWriteMethod() != null; + boolean isUseable = pdthis[i].getReadMethod() != null && !excludePropertyNames.contains(pdthis[i].getName()); + if (isUseable || (pdthis[i] instanceof IndexedPropertyDescriptor)) { + propertyList.add(pdthis[i]); + } + } + + if (beanInfo.getAdditionalBeanInfo() != null) { + List additionalBeanInfo = new ArrayList(Arrays.asList(beanInfo.getAdditionalBeanInfo())); + while (additionalBeanInfo.size() > 0) { + BeanInfo aBeanInfo = (BeanInfo) additionalBeanInfo.remove(0); + pdthis = aBeanInfo.getPropertyDescriptors(); + for (int i = 0; i < pdthis.length; i++) { + String name = pdthis[i].getName(); + boolean isWriteable = pdthis[i].getWriteMethod() != null; + boolean isUseable = pdthis[i].getReadMethod() != null && !excludePropertyNames.contains(name); + if (isUseable || (pdthis[i] instanceof IndexedPropertyDescriptor)) { + propertyList.add(pdthis[i]); + } + } + /* This is commented to mimic the behavior of the Introspector, which doesn't climb up the bean inheritance + tree unless the additional are specified via the Introspector. */ + // if ( aBeanInfo.getAdditionalBeanInfo()!=null ) { + // additionalBeanInfo.addAll( Arrays.asList( aBeanInfo.getAdditionalBeanInfo() ) ); + // } + } + } + + return (PropertyDescriptor[]) propertyList.toArray(new PropertyDescriptor[propertyList.size()]); + + } catch (IntrospectionException e) { + return null; + } + } + + public static BeanInfo getBeanInfo(final Class c) throws IntrospectionException { + + // goal: get BeanInfo for the class, or the AccessLevelBeanInfo if it exists. + BeanInfo beanInfo = null; + + if (c.getPackage() == null) { // e.g. String array + beanInfo = Introspector.getBeanInfo(c); + + log.finer("using BeanInfo " + beanInfo.getClass().getName() + " for " + c.getName()); + } else { + + String s; + try { + s = c.getName().substring(c.getPackage().getName().length() + 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Class maybeClass = null; + String beanInfoClassName = null; + + try { + beanInfoClassName = c.getPackage() + "." + s + "BeanInfo"; + maybeClass = Class.forName(beanInfoClassName); + } catch (ClassNotFoundException e) { + try { + beanInfoClassName = "org.das2.beans." + s + "BeanInfo"; + maybeClass = Class.forName(beanInfoClassName); + } catch (ClassNotFoundException e2) { + beanInfo = Introspector.getBeanInfo(c); + beanInfoClassName = beanInfo.getClass().getName(); + } + } + + log.finer("using BeanInfo " + beanInfoClassName + " for " + c.getName()); + + if (beanInfo == null) { + try { + beanInfo = (BeanInfo) maybeClass.newInstance(); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } catch (InstantiationException ex) { + throw new RuntimeException(ex); + } + } + } + return beanInfo; + } + + public static String[] getPropertyNames(PropertyDescriptor[] propertyList) { + String[] result = new String[propertyList.length]; + for (int i = 0; i < result.length; i++) { + result[i] = ((PropertyDescriptor) propertyList[i]).getName(); + } + return result; + } + + /** + * Use reflection to get a list of all the property names for the class. + * The properties are returned in the order specified, and put inherited properties + * at the end of the list. This is motivated by the arbitary order that the + * Introspector presents the properties, which is in conflict with our desire to control + * the property order. + */ + public static String[] getPropertyNames(Class c) { + PropertyDescriptor[] propertyList = getPropertyDescriptors(c); + return getPropertyNames(propertyList); + } + + /** + * Returns an AccessLevelBeanInfo for the BeanInfo class, implementing the logic + * of how to handle implicit properties. + */ + public static AccessLevelBeanInfo asAccessLevelBeanInfo(BeanInfo beanInfo, Class beanClass) { + if (beanInfo instanceof AccessLevelBeanInfo) { + return (AccessLevelBeanInfo) beanInfo; + } else { + return ImplicitAccessLevelBeanInfo.create(beanInfo, beanClass); + } + + } +} diff --git a/dasCore/src/main/java/org/das2/beans/ColumnColumnConnectorBeanInfo.java b/dasCore/src/main/java/org/das2/beans/ColumnColumnConnectorBeanInfo.java new file mode 100755 index 000000000..b046bf109 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/ColumnColumnConnectorBeanInfo.java @@ -0,0 +1,52 @@ +/* File: DasColorBarBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; +import org.das2.graph.ColumnColumnConnector; + +/** + * BeanInfo class for DasColorBar + * + * @author Edward West + */ +public class ColumnColumnConnectorBeanInfo extends AccessLevelBeanInfo { + + ColumnColumnConnector c; + + protected static final Property[] properties = { + new Property("bottomCurtain", AccessLevel.DASML, "isBottomCurtain", "setBottomCurtain", null), + new Property("curtainOpacityPercent", AccessLevel.DASML, "getCurtainOpacityPercent", "setCurtainOpacityPercent", null), + new Property("fill", AccessLevel.DASML, "isFill", "setFill", null), + new Property("fillColor", AccessLevel.DASML, "getFillColor", "setFillColor", null), + new Property("color", AccessLevel.DASML, "getForeground", "setForeground", null), + new Property("visible", AccessLevel.DASML, "isVisible", "setVisible", null), + }; + + public ColumnColumnConnectorBeanInfo() { + super(properties, org.das2.graph.ColumnColumnConnector.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/CrossHairRendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/CrossHairRendererBeanInfo.java new file mode 100644 index 000000000..1270c9730 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/CrossHairRendererBeanInfo.java @@ -0,0 +1,48 @@ +/* File: DasPlotBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +public class CrossHairRendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("debugging", AccessLevel.DASML, "isDebugging", "setDebugging", null), + new Property("allPlanesReport", AccessLevel.DASML, "isAllPlanesReport", "setAllPlanesReport", null), + new Property("snapping", AccessLevel.DASML, "isSnapping", "setSnapping", null), + new Property("multiLine", AccessLevel.DASML, "isMultiLine", "setMultiLine", null), + }; + + public CrossHairRendererBeanInfo() { + super(properties, org.das2.event.CrossHairRenderer.class); + } + + public java.beans.BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new LabelDragRendererBeanInfo(), + }; + return additional; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasApplicationBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasApplicationBeanInfo.java new file mode 100644 index 000000000..285141d04 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasApplicationBeanInfo.java @@ -0,0 +1,48 @@ +/* File: RendererBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.DasApplication; +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; + + + +public class DasApplicationBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("reloadLoggingProperties", AccessLevel.DASML, "isReloadLoggingProperties", "setReloadLoggingProperties", null), + new Property("headless", AccessLevel.DASML, "isHeadless", null, null), + new Property("applet", AccessLevel.DASML, "isApplet", null, null), + new Property("dataSetCache", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getDataSetCache", null, null), + new Property("inputStreamMeter", AccessLevel.DASML, "getInputStreamMeter", null, null), + new Property("monitorManager", AccessLevel.DASML, "getMonitorFactory", null, null), + new Property("das2Version", AccessLevel.DASML, "getDas2Version", null, null), + }; + + public DasApplicationBeanInfo() { + super(properties, org.das2.DasApplication.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasAxisBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasAxisBeanInfo.java new file mode 100644 index 000000000..fab7111db --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasAxisBeanInfo.java @@ -0,0 +1,63 @@ +/* File: DasAxisBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +/** + * BeanInfo class for org.das2.graph.DasAxis. + * + * @author Edward West + * @see org.das2.graph.DasAxis + */ +public class DasAxisBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("datumRange", AccessLevel.END_USER, "getDatumRange", "setDatumRange", null ), + new Property("dataMaximum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getDataMaximum", "setDataMaximum", null), + new Property("dataMinimum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getDataMinimum", "setDataMinimum", null), + new Property("flipped", AccessLevel.DASML, "isFlipped", "setFlipped", null), + new Property("label", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getLabel", "setLabel", null), + new Property("log", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "isLog", "setLog", null), + new Property("units", AccessLevel.DASML, "getUnits", null, null), + new Property("format", AccessLevel.DASML, "getFormat", "setFormat", null), + new Property("tickLabelsVisible", AccessLevel.DASML, "isTickLabelsVisible", "setTickLabelsVisible", null), + new Property("oppositeAxisVisible", AccessLevel.DASML, "isOppositeAxisVisible", "setOppositeAxisVisible", null), + new Property("flipLabel", AccessLevel.DASML, "isFlipLabel", "setFlipLabel", null), + new Property("animated", AccessLevel.DASML, "isAnimated", "setAnimated", null), + new Property("dataPath", AccessLevel.DASML, "getDataPath", "setDataPath", null), + new Property("showTca", AccessLevel.DASML, "getDrawTca", "setDrawTca", null), + }; + + public DasAxisBeanInfo() { + super(properties, org.das2.graph.DasAxis.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + java.beans.BeanInfo[] additional = { + new DasCanvasComponentBeanInfo() + }; + return additional ; + } +} diff --git a/dasCore/src/main/java/org/das2/beans/DasCanvasBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasCanvasBeanInfo.java new file mode 100644 index 000000000..c68965ec7 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasCanvasBeanInfo.java @@ -0,0 +1,71 @@ +/* File: DasCanvasBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.MethodDescriptor; + +/** + * + * @author eew + */ +public class DasCanvasBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getDasName", "setDasName", null), + new Property("fitted", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "isFitted", "setFitted", null), + new Property("width", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getPreferredWidth", "setPreferredWidth", null), + new Property("height", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getPreferredHeight", "setPreferredHeight", null), + new Property("backgroundColor", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getBackground", "setBackground", null), + new Property("foregroundColor", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getForeground", "setForeground", null), + new Property("baseFont", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getBaseFont", "setBaseFont", null), + new Property("printTag", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getPrintingTag", "setPrintingTag", null ), + new Property("textAntiAlias", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "isTextAntiAlias", "setTextAntiAlias", null ), + new Property("antiAlias", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "isAntiAlias", "setAntiAlias", null ), + new Property("components", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getCanvasComponents", null, "getCanvasComponents", null, null ), + new Property("application", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getApplication", null, null), + }; + + private static MethodDescriptor[] methods; + static { + try { + methods = new MethodDescriptor[1]; + Class[] writeToPngParams = { String.class }; + methods[0] = new MethodDescriptor(org.das2.graph.DasCanvas.class.getMethod("writeToPng", writeToPngParams)); + } + catch (NoSuchMethodException nsme) { + IllegalStateException ise = new IllegalStateException(nsme.getMessage()); + ise.initCause(nsme); + throw ise; + } + } + + public DasCanvasBeanInfo() { + super(properties, org.das2.graph.DasCanvas.class); + } + + public MethodDescriptor[] getMethodDescriptors() { + return methods; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasCanvasComponentBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasCanvasComponentBeanInfo.java new file mode 100644 index 000000000..44d44b004 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasCanvasComponentBeanInfo.java @@ -0,0 +1,60 @@ +/* File: DasCanvasComponentBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.graph.DasCanvasComponent; + +import java.beans.MethodDescriptor; + +public class DasCanvasComponentBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getDasName", "setDasName", null), + new Property("row", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getRow", "setRow", null), + new Property("column", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getColumn", "setColumn", null), + new Property("mouseAdapter", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getDasMouseInputAdapter", "setDasMouseInputAdapter", null) + }; + + private static MethodDescriptor[] methods; + static { + try { + methods = new MethodDescriptor[1]; + methods[0] = new MethodDescriptor(DasCanvasComponent.class.getMethod("update")); + } + catch (NoSuchMethodException nsme) { + IllegalStateException ise = new IllegalStateException(nsme.getMessage()); + ise.initCause(nsme); + throw ise; + } + } + + public DasCanvasComponentBeanInfo() { + super(properties, org.das2.graph.DasCanvasComponent.class); + } + + public MethodDescriptor[] getMethodDescriptors() { + return methods; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasColorBarBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasColorBarBeanInfo.java new file mode 100644 index 000000000..082360274 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasColorBarBeanInfo.java @@ -0,0 +1,67 @@ +/* File: DasColorBarBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; +import org.das2.graph.DasAxis; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import org.das2.components.propertyeditor.EnumerationEditor; + +/** + * BeanInfo class for DasColorBar + * + * @author Edward West + */ +public class DasColorBarBeanInfo extends AccessLevelBeanInfo { + + protected static final Property[] properties = { + new Property("type", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getType", "setType", EnumerationEditor.class), + new Property("fillColor", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getFillColor", "setFillColor", null ) + }; + + public DasColorBarBeanInfo() { + super(properties, org.das2.graph.DasColorBar.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new DasAxisBeanInfo(), + new DasCanvasComponentBeanInfo(), + }; + return additional; + + /*try { + BeanInfo[] additional = { + Introspector.getBeanInfo( DasAxis.class ), + }; + return additional; + } catch ( IntrospectionException e ) { + throw new RuntimeException(e); + }*/ + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasColumnBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasColumnBeanInfo.java new file mode 100644 index 000000000..0f2645be4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasColumnBeanInfo.java @@ -0,0 +1,49 @@ +/* File: DasColumnBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +/** + * Bean Info implementation for DasDevicePosition + * + * @author Edward West + */ +public class DasColumnBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, PersistenceLevel.PERSISTENT, "getDasName", "setDasName", null), + new Property("minimum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getMinimum", "setMinimum", null), + new Property("maximum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getMaximum", "setMaximum", null), + new Property("dminimum", AccessLevel.DASML, "getDMinimum", "setDMinimum", null), + new Property("dmaximum", AccessLevel.DASML, "getDMaximum", "setDMaximum", null), + new Property("emMinimum", AccessLevel.DASML, "getEmMinimum", "setEmMinimum", null), + new Property("emMaximum", AccessLevel.DASML, "getEmMaximum", "setEmMaximum", null), + new Property("ptMinimum", AccessLevel.DASML, "getPtMinimum", "setPtMinimum", null), + new Property("ptMaximum", AccessLevel.DASML, "getPtMaximum", "setPtMaximum", null), + }; + + public DasColumnBeanInfo() { + super(properties, org.das2.graph.DasColumn.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasLabelAxisBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasLabelAxisBeanInfo.java new file mode 100644 index 000000000..4dfb88a20 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasLabelAxisBeanInfo.java @@ -0,0 +1,54 @@ +/* File: DasAxisBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +/** + * BeanInfo class for org.das2.graph.DasAxis. + * + * @author Edward West + * @see org.das2.graph.DasAxis + */ +public class DasLabelAxisBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("label", AccessLevel.DASML, "getLabel", "setLabel", null), + new Property("outsidePadding", AccessLevel.DASML, "getOutsidePadding", "setOutsidePadding", null), + new Property("floppyItemSpacing", AccessLevel.DASML, "isFloppyItemSpacing", "setFloppyItemSpacing", null), + new Property("tickLabelsVisible", AccessLevel.DASML, "areTickLabelsVisible", "setTickLabelsVisible", null), + new Property("oppositeAxisVisible", AccessLevel.DASML, "isOppositeAxisVisible", "setOppositeAxisVisible", null), + }; + + public DasLabelAxisBeanInfo() { + super(properties, org.das2.graph.DasLabelAxis.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + java.beans.BeanInfo[] additional = { + new DasCanvasComponentBeanInfo() + }; + return additional ; + } +} diff --git a/dasCore/src/main/java/org/das2/beans/DasMouseInputAdapterBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasMouseInputAdapterBeanInfo.java new file mode 100644 index 000000000..4ab9c6f5d --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasMouseInputAdapterBeanInfo.java @@ -0,0 +1,44 @@ +/* File: DasPlotBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +public class DasMouseInputAdapterBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("mouseModules", AccessLevel.DASML, "getMouseModules", null, "getMouseModule", null, null), + //new Property("hoverHighlite", AccessLevel.DASML, "isHoverHighlite", "setHoverHighlite", null ), + + new Property("primaryModule", AccessLevel.DASML, PersistenceLevel.PERSISTENT, + "getPrimaryModule", "setPrimaryModule", null ), + new Property("secondaryModule", AccessLevel.DASML, PersistenceLevel.PERSISTENT, + "getSecondaryModule", "setSecondaryModule", null ) }; + + + public DasMouseInputAdapterBeanInfo() { + super(properties, org.das2.event.DasMouseInputAdapter.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasPlotBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasPlotBeanInfo.java new file mode 100644 index 000000000..c66c4befb --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasPlotBeanInfo.java @@ -0,0 +1,52 @@ +/* File: DasPlotBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +public class DasPlotBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("title", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getTitle", "setTitle", null), + new Property("drawGrid", AccessLevel.DASML, "isDrawGrid", "setDrawGrid", null), + new Property("drawMinorGrid", AccessLevel.DASML, "isDrawMinorGrid", "setDrawMinorGrid", null), + new Property("preview", AccessLevel.DASML, "isPreviewEnabled", "setPreviewEnabled", null ), + new Property("oversize", AccessLevel.DASML, "isOverSize", "setOverSize", null ), + new Property("renderers", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getRenderers", null, "getRenderer", null, null), + new Property("xAxis", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getXAxis", "setXAxis", null), + new Property("yAxis", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getYAxis", "setYAxis", null), + }; + + public DasPlotBeanInfo() { + super(properties, org.das2.graph.DasPlot.class); + } + + public java.beans.BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new DasCanvasComponentBeanInfo() + }; + return additional; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasRowBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasRowBeanInfo.java new file mode 100644 index 000000000..57effa40d --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasRowBeanInfo.java @@ -0,0 +1,52 @@ +/* File: DasRowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +/** + * Bean Info implementation for DasDevicePosition + * + * @author Edward West + */ +public class DasRowBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getDasName", "setDasName", null), + new Property("minimum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getMinimum", "setMinimum", null), + new Property("maximum", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getMaximum", "setMaximum", null), + new Property("dminimum", AccessLevel.DASML, "getDMinimum", "setDMinimum", null), + new Property("dmaximum", AccessLevel.DASML, "getDMaximum", "setDMaximum", null), + new Property("emMinimum", AccessLevel.DASML, "getEmMinimum", "setEmMinimum", null), + new Property("emMaximum", AccessLevel.DASML, "getEmMaximum", "setEmMaximum", null), + new Property("ptMinimum", AccessLevel.DASML, "getPtMinimum", "setPtMinimum", null), + new Property("ptMaximum", AccessLevel.DASML, "getPtMaximum", "setPtMaximum", null), + /* new Property("top", AccessLevel.DASML, "getTop", "setTop", null), + new Property("bottom", AccessLevel.DASML, "getBottom", "setBottom", null), */ + + }; + + public DasRowBeanInfo() { + super(properties, org.das2.graph.DasRow.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DasServerBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DasServerBeanInfo.java new file mode 100644 index 000000000..b5e289a89 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DasServerBeanInfo.java @@ -0,0 +1,45 @@ +/* File: DasRowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; +import org.das2.client.DasServer; + +/** + * Bean Info implementation for DasDevicePosition + * + * @author Edward West + */ +public class DasServerBeanInfo extends AccessLevelBeanInfo { + DasServer me; + private static Property[] properties = { + new Property("name", AccessLevel.DASML, "getName", null, null), + }; + + public DasServerBeanInfo() { + super(properties, org.das2.client.DasServer.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DataPointRecorderBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DataPointRecorderBeanInfo.java new file mode 100644 index 000000000..dead015d0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DataPointRecorderBeanInfo.java @@ -0,0 +1,39 @@ +/* File: RendererBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + + + +public class DataPointRecorderBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("xTagWidth", AccessLevel.DASML, "getXTagWidth", "setXTagWidth", null), + new Property("snapToGrid", AccessLevel.DASML, "isSnapToGrid", "setSnapToGrid", null), + }; + + public DataPointRecorderBeanInfo() { + super(properties, org.das2.components.DataPointRecorder.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/DataSetDescriptorBeanInfo.java b/dasCore/src/main/java/org/das2/beans/DataSetDescriptorBeanInfo.java new file mode 100644 index 000000000..612ac8387 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/DataSetDescriptorBeanInfo.java @@ -0,0 +1,42 @@ +/* File: DasRowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +/** + * Bean Info implementation for DasDevicePosition + * + * @author Edward West + */ +public class DataSetDescriptorBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("dataSetID", AccessLevel.DASML, "getDataSetID", null, null), + new Property("dataSetCache", AccessLevel.DASML, "getDataSetCache", null, null), + }; + + public DataSetDescriptorBeanInfo() { + super(properties, org.das2.dataset.DataSetDescriptor.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/ImplicitAccessLevelBeanInfo.java b/dasCore/src/main/java/org/das2/beans/ImplicitAccessLevelBeanInfo.java new file mode 100644 index 000000000..2c9d3b1c5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/ImplicitAccessLevelBeanInfo.java @@ -0,0 +1,59 @@ +/* + * ImplicitAccessLevelBeanInfo.java + * + * Created on April 21, 2006, 3:58 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.beans; + +import org.das2.beans.AccessLevelBeanInfo.AccessLevel; +import org.das2.beans.AccessLevelBeanInfo.PersistenceLevel; +import org.das2.beans.AccessLevelBeanInfo.Property; +import java.beans.BeanInfo; +import java.beans.IndexedPropertyDescriptor; +import java.beans.PropertyDescriptor; + +/** + * ImplicitAccessLevelBeanInfo makes any BeanInfo class look like an AccessLevelBeanInfo by implementing + * the default access level and persistence level settings. + * + * @author Jeremy + */ +public class ImplicitAccessLevelBeanInfo extends AccessLevelBeanInfo { + + BeanInfo beanInfo; + + /** Creates a new instance of ImplicitAccessLevelBeanInfo */ + private ImplicitAccessLevelBeanInfo( BeanInfo beanInfo, Class beanClass, Property[] properties ) { + super( properties, beanClass ); + this.beanInfo= beanInfo; + } + + public static ImplicitAccessLevelBeanInfo create( BeanInfo beanInfo, Class beanClass ) { + Property[] properties; + PropertyDescriptor[] pds = BeansUtil.getPropertyDescriptors( beanClass ); + String[] propertyNameList= BeansUtil.getPropertyNames( pds ); + + properties= new Property[propertyNameList.length]; + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + + + +public class LabelDragRendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("tooltip", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "isTooltip", "setTooltip", null), + }; + + public LabelDragRendererBeanInfo() { + super(properties, org.das2.event.LabelDragRenderer.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/MouseModuleBeanInfo.java b/dasCore/src/main/java/org/das2/beans/MouseModuleBeanInfo.java new file mode 100644 index 000000000..c714d1cb2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/MouseModuleBeanInfo.java @@ -0,0 +1,39 @@ +/* File: DasPlotBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; + +public class MouseModuleBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("dragRenderer", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getDragRenderer", null, null), + new Property("label", AccessLevel.DASML, "getLabel", "setLabel", null), + }; + + public MouseModuleBeanInfo() { + super(properties, org.das2.event.MouseModule.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/RectangleEditor.java b/dasCore/src/main/java/org/das2/beans/RectangleEditor.java new file mode 100644 index 000000000..8846e50d9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/RectangleEditor.java @@ -0,0 +1,31 @@ +/* + * UnitsEditor.java + * + * Created on June 30, 2005, 10:23 AM + * + * To change this template, choose Tools | Options and locate the template under + * the Source Creation and Management node. Right-click the template and choose + * Open. You can then make changes to the template in the Source Editor. + */ + +package org.das2.beans; + +import org.das2.datum.Units; +import java.awt.Rectangle; +import java.beans.PropertyEditorSupport; +import java.beans.XMLEncoder; + +/** + * + * @author Jeremy + */ +public class RectangleEditor extends PropertyEditorSupport { + + public void setAsText(String str) throws IllegalArgumentException { + setValue( Units.getByName(str) ); + } + + public String getAsText() { + return String.valueOf( getValue() ); + } +} diff --git a/dasCore/src/main/java/org/das2/beans/RendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/RendererBeanInfo.java new file mode 100755 index 000000000..0f166fa7a --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/RendererBeanInfo.java @@ -0,0 +1,44 @@ +/* File: RendererBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + + + +public class RendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("active", AccessLevel.DASML, "isActive", "setActive", null), + new Property("dataSetID", AccessLevel.DASML, "getDataSetID", "setDataSetID", null), + new Property("dumpDataSet", AccessLevel.DASML, "isDumpDataSet", "setDumpDataSet", null), + new Property("dataSet", AccessLevel.DASML, "getDataSet", null, null), + new Property("lastException", AccessLevel.DASML, "getLastException", null, null), + new Property("dataLoader", AccessLevel.DASML, "getDataLoader", null, null), + //new Property("overLoading", AccessLevel.DASML, "isOverloading", "setOverloading", null), + }; + + public RendererBeanInfo() { + super(properties, org.das2.graph.Renderer.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/RowRowConnectorBeanInfo.java b/dasCore/src/main/java/org/das2/beans/RowRowConnectorBeanInfo.java new file mode 100644 index 000000000..41366ee4f --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/RowRowConnectorBeanInfo.java @@ -0,0 +1,43 @@ +/* File: DasColorBarBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import org.das2.components.propertyeditor.EnumerationEditor; +import java.beans.BeanInfo; + +/** + * BeanInfo class for DasColorBar + * + * @author Edward West + */ +public class RowRowConnectorBeanInfo extends AccessLevelBeanInfo { + + protected static final Property[] properties = { + }; + + public RowRowConnectorBeanInfo() { + super(properties, org.das2.graph.RowRowConnector.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/SpectrogramRendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/SpectrogramRendererBeanInfo.java new file mode 100755 index 000000000..5a597f306 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/SpectrogramRendererBeanInfo.java @@ -0,0 +1,49 @@ +/* File: SpectrogramRendererBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; +import org.das2.components.propertyeditor.EnumerationEditor; + +public class SpectrogramRendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("rebinner", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getRebinner", "setRebinner", EnumerationEditor.class), + new Property("colorBar", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getColorBar", "setColorBar", null), + new Property("sliceRebinnedData", AccessLevel.DASML, "isSliceRebinnedData", "setSliceRebinnedData", null), + new Property("print300dpi", AccessLevel.DASML, "isPrint300dpi", "setPrint300dpi", null), + }; + + public SpectrogramRendererBeanInfo() { + super(properties, org.das2.graph.SpectrogramRenderer.class); + } + + public java.beans.BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new RendererBeanInfo() + }; + return additional; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/StackedHistogramRendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/StackedHistogramRendererBeanInfo.java new file mode 100755 index 000000000..2b08eb15b --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/StackedHistogramRendererBeanInfo.java @@ -0,0 +1,54 @@ +/* File: DasStackedHistogramPlotBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; +import org.das2.components.propertyeditor.EnumerationEditor; + +/** + * BeanInfo class for DasStackedHistogramPlot + * + * @author Edward West + */ +public class StackedHistogramRendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("ZAxis", AccessLevel.DASML, "getZAxis", "setZAxis", null), + new Property("PeaksIndicator", AccessLevel.DASML, "getPeaksIndicator", "setPeaksIndicator", EnumerationEditor.class ), + new Property("sliceRebinnedData", AccessLevel.DASML, "isSliceRebinnedData", "setSliceRebinnedData", null), + new Property("transparentBackground", AccessLevel.DASML, "isTransparentBackground", "setTransparentBackground", null) + }; + + public StackedHistogramRendererBeanInfo() { + super(properties, org.das2.graph.StackedHistogramRenderer.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new RendererBeanInfo() + }; + return additional; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/StreamDataSetDescriptorBeanInfo.java b/dasCore/src/main/java/org/das2/beans/StreamDataSetDescriptorBeanInfo.java new file mode 100644 index 000000000..1e878088f --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/StreamDataSetDescriptorBeanInfo.java @@ -0,0 +1,52 @@ +/* File: DasRowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.*; + +/** + * Bean Info implementation for DasDevicePosition + * + * @author Edward West + */ +public class StreamDataSetDescriptorBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("standardDataStreamSource", AccessLevel.DASML, "getStandardDataStreamSource", null, null), + new Property("restrictedAccess", AccessLevel.DASML, "isRestrictedAccess", null, null), + new Property("serverSideReduction", AccessLevel.DASML, "isServerSideReduction", null, null), + }; + + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new DataSetDescriptorBeanInfo() + }; + return additional; + } + + public StreamDataSetDescriptorBeanInfo() { + super(properties, org.das2.client.StreamDataSetDescriptor.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/SymbolLineRendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/SymbolLineRendererBeanInfo.java new file mode 100755 index 000000000..446e3934b --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/SymbolLineRendererBeanInfo.java @@ -0,0 +1,52 @@ +/* File: SymbolLineRendererBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +import java.beans.BeanInfo; +import org.das2.components.propertyeditor.EnumerationEditor; + +public class SymbolLineRendererBeanInfo extends AccessLevelBeanInfo { + + private static final Property[] properties = { + new Property("psym", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getPsym", "setPsym", EnumerationEditor.class), + new Property("psymConnector", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getPsymConnector", "setPsymConnector", EnumerationEditor.class), + new Property("histogram", AccessLevel.DASML, "isHistogram", "setHistogram", null), + new Property("color", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getColor", "setColor", null), + new Property("lineWidth", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getLineWidth", "setLineWidth", null), + new Property("symSize", AccessLevel.DASML, PersistenceLevel.PERSISTENT, "getSymSize", "setSymSize", null), + new Property("antiAliased", AccessLevel.DASML, "isAntiAliased", "setAntiAliased", null), + }; + + public SymbolLineRendererBeanInfo() { + super(properties, org.das2.graph.SymbolLineRenderer.class); + } + + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new RendererBeanInfo() + }; + return additional; + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/TickCurveRendererBeanInfo.java b/dasCore/src/main/java/org/das2/beans/TickCurveRendererBeanInfo.java new file mode 100644 index 000000000..ee24a529f --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/TickCurveRendererBeanInfo.java @@ -0,0 +1,38 @@ +/* File: DasRowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.beans; + +public class TickCurveRendererBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("TickStyle", AccessLevel.DASML, "getTickStyle", "setTickStyle", null), + new Property("LineWidth", AccessLevel.DASML, "getLineWidth", "setLineWidth", null), + new Property("TickLength", AccessLevel.DASML, "getTickLength", "setTickLength", null), + }; + + public TickCurveRendererBeanInfo() { + super(properties, org.das2.graph.TickCurveRenderer.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/UnitsEditor.java b/dasCore/src/main/java/org/das2/beans/UnitsEditor.java new file mode 100644 index 000000000..23eebf689 --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/UnitsEditor.java @@ -0,0 +1,26 @@ +/* + * UnitsEditor.java + * + * Created on June 30, 2005, 10:23 AM + * + * To change this template, choose Tools | Options and locate the template under + * the Source Creation and Management node. Right-click the template and choose + * Open. You can then make changes to the template in the Source Editor. + */ + +package org.das2.beans; + +import org.das2.datum.Units; +import java.beans.PropertyEditorSupport; + +/** + * + * @author Jeremy + */ +public class UnitsEditor extends PropertyEditorSupport { + + public void setAsText(String str) throws IllegalArgumentException { + setValue( Units.getByName(str) ); + } + +} diff --git a/dasCore/src/main/java/org/das2/beans/package.html b/dasCore/src/main/java/org/das2/beans/package.html new file mode 100644 index 000000000..53a4182dc --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/package.html @@ -0,0 +1,22 @@ + +

Provides BeanInfos that wrap das2 objects to control the properties +that are exposed. When the PropertyEditor is used to edit an object, it +uses java bean conventions for identifying its properties. Properties of +objects that do not have a corresponding BeanInfo are discovered using the +java beans convention of looking for methods +of the form getX() and setX() where X is the property name.

+

The BeanInfos also provide a mechanism where the writable property set can be +reduced depending on the role of the person using the application. For example, +application developers would have the ability to freely adjust all parameters, +such as layout, datasets and rendering methods, while end users would only be +able to control the timeaxis. This mechanism has never been used, but it's worth +mentioning since the implementing code is in there. +

+Also a utility class, BeansUtil, provides methods for discovering object properties. + +

Note that this package might be removed in a future version of das2. We +plan to make everything more beany, and the BeanInfos may be moved into the same directory +as the Bean objects. Also, we expect that Java 5 annotations might be used to implement the +access levels and other property metadata. Also, XMLEncoder might be used to encode +the beans, instead of SerializeUtil.

+ \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/beans/scratchpad.txt b/dasCore/src/main/java/org/das2/beans/scratchpad.txt new file mode 100644 index 000000000..5a54ad42b --- /dev/null +++ b/dasCore/src/main/java/org/das2/beans/scratchpad.txt @@ -0,0 +1,43 @@ +Definition of a property. A property is an adjustable parameter of an object. A property may be read-only and used to peek +at the status of an object. A property may be another object. A property may be an array of objects. This is all beany. + +1. Attributes of Properties. + 1. Security model + 1. that allows us to restrict which properties may be adjusted by the end-user. + 2. that allows us to restrict the properties which may be stored persistently. + 2. Properties should be editable and serializable "for free," meaning that no effort is required to get these features for new objects. + 3. Properties of new objects should be discovered in a reasonable and safe way. + +2. Example uses. + 1. application development + 1. layout, labels, colorbar, etc are set interactively by the application developer. + 2. satisfied, the developer releases the application, but limiting adjustable properties to the time axis range. + 2. non-persistent properties + 1. the developer wishes to add a new parameter for testing which is used interactively + 2. this parameter should not be serialized to persistent storage, however, since its meaning may change. + +3. discovery of properties. + 1. a "BeanInfo" object for the object class identifies the properties explicitly + 2. or the java beans convention for getX/setX is used to identify implicit properties. + +4. Access Levels--not implemented + 1. ALL -- the property may be adjusted freely, no restrictions + 2. DEVELOPER -- adjustable by the application developer ( This is the default for implicit properties. ) + 3. END_USER -- adjustable by the end-user. + +5. Persistence. + 1. none -- the property should never be used as a part of the state. Including within session. + 2. transient -- the property is only persistent on a per-session basis. For example, undo/redo should include this. + 3. persistent -- the property may be serialized and stored. + +6. Arbitary xml to application + 1. bean requirements + 1. no-argument constructor + 2. properties adjusted in any order--this is a bean requirement because of property editor as well. + 3. must be named + 2. update messages + 1. propertyChanged messages used to implement bean inter-dependence. + 2. "listens to" notation needed. + 3. message-coalescing mechanism needs to be preserved. + 3. missing objects + 1. some sort of canvas layout manager that manages rows diff --git a/dasCore/src/main/java/org/das2/client/AccessDeniedException.java b/dasCore/src/main/java/org/das2/client/AccessDeniedException.java new file mode 100644 index 000000000..f1b8087fb --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/AccessDeniedException.java @@ -0,0 +1,37 @@ +/* File: AccessDeniedException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.client; + +/** + * + * @author jbf + */ +public class AccessDeniedException extends org.das2.DasException { + //The the provided key does not allow access to the dataset. + + /** Creates a new instance of NoKeyProvidedException */ + public AccessDeniedException(String msg) { + super(msg); + } + +} diff --git a/dasCore/src/main/java/org/das2/client/AccountManager.java b/dasCore/src/main/java/org/das2/client/AccountManager.java new file mode 100644 index 000000000..734847723 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/AccountManager.java @@ -0,0 +1,114 @@ +/* File: AccountManager.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.client; + +import org.das2.client.DasServer; + +import javax.swing.*; +import java.awt.*; + +public class AccountManager extends JPanel { + + JLabel feedbackLabel; + JTextField tfUser; + + JPasswordField tfPass; + JPasswordField tfNewPass; + JPasswordField tfConfirmPass; + + DasServer dasServer; + Key key; + + public AccountManager(DasServer dasServer) { + + this.dasServer= dasServer; + + setLayout( new BoxLayout(this,BoxLayout.Y_AXIS)); + + add(new JLabel(dasServer.getName(),JLabel.LEFT)); + add(new JLabel(dasServer.getLogo(),JLabel.LEFT)); + + add(new JLabel("Changing Password")); + + add(new JLabel("Username: ",JLabel.LEFT)); + tfUser= new JTextField(); + add(tfUser); + + add(new JLabel("Password: ",JLabel.LEFT)); + tfPass= new JPasswordField(); + add(tfPass); + + add(new JLabel("New Password: ",JLabel.LEFT)); + tfNewPass= new JPasswordField(); + add(tfNewPass); + + add(new JLabel("Confirm Password: ",JLabel.LEFT)); + tfConfirmPass= new JPasswordField(); + add(tfConfirmPass); + + feedbackLabel= new JLabel("",JLabel.LEFT); + add(feedbackLabel); + + } + + public void changePassword() { + + int okayCancel=JOptionPane.OK_OPTION; + boolean success= false; + + while ( okayCancel==JOptionPane.OK_OPTION && success==false ) { + okayCancel= + JOptionPane.showConfirmDialog(null,this,"Account Manager", + JOptionPane.OK_CANCEL_OPTION,JOptionPane.PLAIN_MESSAGE); + + if (okayCancel==JOptionPane.OK_OPTION) { + String pass= String.valueOf(tfPass.getPassword()); + String newPass= String.valueOf(tfNewPass.getPassword()); + String confirmPass= String.valueOf(tfConfirmPass.getPassword()); + + if (newPass.equals(confirmPass)) { + try { + dasServer.changePassword(tfUser.getText(),pass,newPass); + success= true; + } + catch ( org.das2.DasException e ) { + feedbackLabel.setText(e.toString()); + } + catch ( Exception e ) { + feedbackLabel.setText("Failed connect to server"); + } + } else { + feedbackLabel.setText("Passwords do not match"); + feedbackLabel.setForeground(Color.red); + } + } + } + } + + public static void main( String[] args ) throws Exception { + AccountManager a= new AccountManager(DasServer.create(new java.net.URL("http://www-pw.physics.uiowa.edu/das/dasServer"))); + a.changePassword(); + + } +} + diff --git a/dasCore/src/main/java/org/das2/client/Authenticator.java b/dasCore/src/main/java/org/das2/client/Authenticator.java new file mode 100755 index 000000000..972446950 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/Authenticator.java @@ -0,0 +1,210 @@ +/* File: Authenticator.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.client; + +import org.das2.DasApplication; +import org.das2.DasProperties; +import java.awt.Color; +import java.awt.Component; +import java.awt.Toolkit; +import java.util.prefs.Preferences; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.List; + +public class Authenticator extends JPanel { + + JLabel feedbackLabel; + JTextField tfUser; + + JPasswordField tfPass; + + DasServer dasServer; + String resourceId; // identifies the DasServer and the resource + String resource; + + final String KEY_AUTOLOGIN= "autoLogin"; + final String KEY_SAVECREDENTIALS= "saveCredentials"; + + Preferences prefs= Preferences.userNodeForPackage( Authenticator.class ); + + public Authenticator(DasServer dasServer) { + this( dasServer, "" ); + } + + public Authenticator(DasServer dasServer, String restrictedResourceLabel ) { + + this.dasServer= dasServer; + this.resourceId= String.valueOf( dasServer.getURL() ) + "::" + restrictedResourceLabel;; + this.resource= restrictedResourceLabel; + + setLayout( new BoxLayout(this,BoxLayout.Y_AXIS)); + + add(new JLabel(dasServer.getName(),JLabel.LEFT)); + add(new JLabel(dasServer.getLogo(),JLabel.LEFT)); + + if ( ! "".equals( restrictedResourceLabel ) ) { + add( new JLabel( ""+restrictedResourceLabel ) ); + } + + add(new JLabel("Username: ",JLabel.LEFT)); + tfUser= new JTextField(); + add(tfUser); + + add(new JLabel("Password: ",JLabel.LEFT)); + tfPass= new JPasswordField(); + add(tfPass); + + JPanel prefsPanel= new JPanel(); + prefsPanel.setLayout( new BoxLayout( prefsPanel, BoxLayout.Y_AXIS ) ); + + { + final JCheckBox cb= new JCheckBox( ); + cb.setSelected( prefs.getBoolean( KEY_SAVECREDENTIALS, true) ); + cb.setAction( new AbstractAction("save credentials" ) { + public void actionPerformed( ActionEvent e ) { + prefs.putBoolean( KEY_SAVECREDENTIALS,cb.isSelected() ); + } + } ); + prefsPanel.add( cb ); + } + + { + final JCheckBox cb= new JCheckBox( ); + cb.setSelected( prefs.getBoolean(KEY_AUTOLOGIN,false) ); + cb.setAction( new AbstractAction( "allow automatic logins" ) { + public void actionPerformed( ActionEvent e ) { + prefs.putBoolean( KEY_AUTOLOGIN,cb.isSelected() ); + } + } ); + prefsPanel.add( cb ); + } + + add( prefsPanel ); + + if ( prefs.getBoolean(KEY_SAVECREDENTIALS,true) ) { + String username= prefs.get( resourceId+".username", DasProperties.getInstance().getProperty("username") ); + if (!"".equals(username)) tfUser.setText(username); + String passwordCrypt= prefs.get( resourceId+".passwordCrypt", DasProperties.getInstance().getProperty("password") ); + if (!"".equals(passwordCrypt)) tfPass.setText("usePrefs"); + } + + feedbackLabel= new JLabel("",JLabel.LEFT); + feedbackLabel.setForeground(Color.red); + add(feedbackLabel); + + try { + String lockingKeyWarning= ""; + if ( Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) ) { + lockingKeyWarning+= ", CAPS LOCK is on"; + } + + if ( Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_NUM_LOCK ) ) { + lockingKeyWarning+= ", NUM LOCK is on"; + } + + if ( !"".equals( lockingKeyWarning ) ) { + feedbackLabel.setText(lockingKeyWarning.substring(2)); + } + } catch ( UnsupportedOperationException e ) { + // I sure hope they don't have caps lock on! + } + + } + + public Key authenticate() { + + Key result=null; + int okayCancel=JOptionPane.OK_OPTION; + + if ( prefs.getBoolean(KEY_AUTOLOGIN,false) ) { + String username= prefs.get( resourceId+".username", DasProperties.getInstance().getProperty("username") ); + String passCrypt= prefs.get( resourceId+".passwordCrypt", DasProperties.getInstance().getProperty("password") ); + result= dasServer.authenticate(username,passCrypt); + if ( result!=null ) { + if ( checkGroup(result) ) { + return result; + } else { + feedbackLabel.setText(username+" doesn't have access to "+resource); + } + } else { + feedbackLabel.setText("stored credentials rejected by server"); + } + } + + Component parent=DasApplication.getDefaultApplication().getMainFrame(); + + while ( okayCancel==JOptionPane.OK_OPTION && result==null ) { + okayCancel= + JOptionPane.showConfirmDialog(parent,this,"Authenticator", + JOptionPane.OK_CANCEL_OPTION,JOptionPane.PLAIN_MESSAGE); + + if (okayCancel==JOptionPane.OK_OPTION) { + + String username= tfUser.getText().trim(); + String password= String.valueOf( tfPass.getPassword() ); + + String passCrypt= null; + if ( password.equals("usePrefs") ) { + passCrypt= prefs.get( resourceId+".passwordCrypt", DasProperties.getInstance().getProperty("password") ); + } else { + passCrypt= org.das2.util.Crypt.crypt(password); + } + + try { + result= dasServer.authenticate(username,passCrypt); + if (result==null) { + feedbackLabel.setText("Login incorrect"); + } else { + if ( !checkGroup( result ) ) { + feedbackLabel.setText(username+" doesn't have access to "+resource ); + result= null; + } + if (prefs.getBoolean(KEY_SAVECREDENTIALS,true) ) { + prefs.put( resourceId+".username", username ); + prefs.put( resourceId+".passwordCrypt", passCrypt ); + prefs.flush(); + } + } + } catch ( Exception e ) { + feedbackLabel.setText("Failed connect to server"); + } + } + } + + return result; + } + + private boolean checkGroup(Key result) { + if ( resource.equals("") ) { + return true; + } else { + List groups= dasServer.groups(result); + return ( groups.contains( resource ) ); + } + } + +} + diff --git a/dasCore/src/main/java/org/das2/client/DasServer.java b/dasCore/src/main/java/org/das2/client/DasServer.java new file mode 100755 index 000000000..63b036690 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/DasServer.java @@ -0,0 +1,616 @@ +/* File: DasServer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.DasStreamFormatException; +import org.das2.util.URLBuddy; +import org.das2.DasIOException; +import org.das2.system.DasLogger; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.logging.Logger; + +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** Represents a remote Das 2.1 compliant server. + * + * Use the create() method to instantiate new Das 2 server objects. Each call to + * create() will only allocate a new server instance if no server matching the given URL + * has already been created. + * + * @author jbf + */ + +public class DasServer { + + private String sProto; + private String host; + private String path; + private int port; + + @Deprecated + private HashMap keys; // Holds a list of all non-http auth das2 server keys + + //Probably dead code, let's see + //private Key key; + + private static final Logger logger= DasLogger.getLogger( DasLogger.DATA_TRANSFER_LOG ); + + /* Holds the global list of Das2 Server objects */ + private static HashMap instanceHashMap= new HashMap(); + + @Deprecated + public static DasServer plasmaWaveGroup; + + @Deprecated + public static DasServer sarahandjeremy; + + static { + try { + plasmaWaveGroup= DasServer.create(new URL("http://www-pw.physics.uiowa.edu/das/das2Server")); + sarahandjeremy= DasServer.create(new URL("http://www.sarahandjeremy.net/das/dasServer.cgi")); + } catch ( java.net.MalformedURLException e ) { + org.das2.util.DasExceptionHandler.handle(e); + } + } + + private DasServer(String sProto, String host, String path) { + String[] s= host.split(":"); + if ( s.length>1 ) { + this.port= Integer.parseInt(s[1]); + host= s[0]; + } else { + port= -1; + } + this.sProto = sProto; + this.host= host; + this.path= path; + this.keys= new HashMap(); + } + + /** Provide the Das2 Server location. + * Note that more than one Das2 server may be present on a single host web-site. + * @return A URL string containing protocol, host and path information. + */ + public String getURL() { + if ( port==-1 ) { + return sProto+"://"+host+path; + } else { + return sProto+"://"+host+":"+port+path; + } + } + + /** Get a Das2 server instance. + * + * @param url A Das2 resource URL. Only the protocol, host, port and path information + * are used. All other items, such as a GET query are ignored. + * + * @return If a server matching the given url's protocol, host and path has already + * been created, that instance is returned, otherwise a new instance is created. + */ + public static DasServer create( URL url ) { + String proto = url.getProtocol(); + String host= url.getHost(); + int port = url.getPort(); + if ( port!=-1 ) { + host+= ":"+port; + } + String key= proto+"://" + host + url.getPath(); + if ( instanceHashMap.containsKey( key ) ) { + logger.fine( "Using existing DasServer for "+url); + return (DasServer) instanceHashMap.get( key ); + } else { + String path= url.getPath(); + logger.fine( "Creating DasServer for "+url); + DasServer result= new DasServer(proto, host, path); + instanceHashMap.put(key,result); + return result; + } + } + + /** Query the remote DasServer for it's id string. + * For Das 2.1 servers this is handled by sending the GET query ?server=id. + * + * @return A string containing the id, or the empty string if the query failed + */ + public String getName() { + String formData= "server=id"; + + try { + URL server= new URL("http",host,port,path+"?"+formData); + + logger.fine( "connecting to "+server); + URLConnection urlConnection = server.openConnection(); + urlConnection.connect(); + + String contentType = urlConnection.getContentType(); + InputStream in= urlConnection.getInputStream(); + + String result= new String( read(in) ); + logger.fine( "response="+result); + + return result; + } catch (IOException e) { + return ""; + } + } + + /** Query the remote DasServer for it's image icon. + * For Das 2.1 servers this is handled by sending the GET query ?server=logo. + * + * @return An ImageIcon with the server logo, or an empty ImageIcon if the request + * failed + */ + public ImageIcon getLogo() { + String formData= "server=logo"; + + try { + URL server= new URL("http",host,port,path+"?"+formData); + + logger.fine( "connecting to "+server); + URLConnection urlConnection = server.openConnection(); + urlConnection.connect(); + + String contentType = urlConnection.getContentType(); + InputStream in= urlConnection.getInputStream(); + + byte[] data= read(in); + logger.fine( "response="+data.length+" bytes"); + return new ImageIcon(data); + + } catch (IOException e) { + return new ImageIcon(); + } + } + + /** Query the remote DasServer for a hierarchical tree of all data sources on + * the server. + * For Das 2.1 servers this is handled by sending the GET query ?server=list. + * + * @return + */ + public TreeModel getDataSetList() throws org.das2.DasException { + String formData= "server=list"; + + try { + URL server= new URL("http",host,port,path+"?"+formData); + + logger.fine( "connecting to "+server); + + URLConnection urlConnection = server.openConnection(); + urlConnection.connect(); + + String contentType = urlConnection.getContentType(); + InputStream in= urlConnection.getInputStream(); + + TreeModel result= createModel(in); + logger.fine( "response->"+result); + return result; + + } catch (IOException e) { + throw new DasIOException( e.getMessage() ); + } + } + + private TreeModel createModel(InputStream uin) throws IOException { + + BufferedReader in = new BufferedReader( new InputStreamReader(uin) ); + + DefaultMutableTreeNode root = + new DefaultMutableTreeNode( getURL(), true ); + DefaultTreeModel model = new DefaultTreeModel(root, true); + String line = in.readLine(); + + while (line != null) { + DefaultMutableTreeNode current = root; + StringTokenizer tokenizer = new StringTokenizer(line, "/"); + token: while (tokenizer.hasMoreTokens()) { + String tok = tokenizer.nextToken(); + for (int index = 0; index < current.getChildCount(); index++) { + String str = current.getChildAt(index).toString(); + if (str.equals(tok)) { + current = + (DefaultMutableTreeNode)current.getChildAt(index); + continue token; + } + } + DefaultMutableTreeNode node = + new DefaultMutableTreeNode(tok, + (tokenizer.hasMoreElements() + ? true + : line.endsWith("/"))); + current.add(node); + current = node; + } + line = in.readLine(); + } + return model; + } + + public StandardDataStreamSource getStandardDataStreamSource(URL url) { + return new WebStandardDataStreamSource(this, url); + } + + public StreamDescriptor getStreamDescriptor( URL dataSetID ) throws DasException { + try { + String dsdf = dataSetID.getQuery().split("&")[0]; + URL url = new URL(sProto, host, port, path+"?server=dsdf&dataset=" + dsdf); + + logger.fine( "connecting to "+url); + URLConnection connection = url.openConnection(); + connection.connect(); + String contentType = connection.getContentType(); + String[] s1= contentType.split(";"); // dump charset info + contentType= s1[0]; + + InputStream inStream = null; + if(connection instanceof HttpURLConnection){ + HttpURLConnection httpConn = (HttpURLConnection) connection; + int nStatus = httpConn.getResponseCode(); + if(nStatus >= 400) + inStream = httpConn.getErrorStream(); + } + if(inStream == null) inStream = connection.getInputStream(); + + if (contentType.equalsIgnoreCase("text/plain") || + contentType.equalsIgnoreCase("text/vnd.das2.das2stream") ) { + PushbackReader reader = new PushbackReader(new InputStreamReader(inStream), 4); + char[] four = new char[4]; + reader.read(four); + if (new String(four).equals("[00]")) { + logger.fine("response is a das2Stream"); + reader.skip(6); + Document header = StreamDescriptor.parseHeader(reader); + Element root = header.getDocumentElement(); + switch(root.getTagName()){ + case "stream": + return new StreamDescriptor(root); + case "exception": + logger.fine("response is an exception"); + String type= root.getAttribute("type"); + String sMsg = root.getAttribute("message"); + String sExceptMsg = "stream exception: "+type; + if(!sMsg.isEmpty()) + sExceptMsg = sExceptMsg + ", " + sMsg; + StreamException se= new StreamException( sExceptMsg ); + throw new DasException(sExceptMsg, se); + case "": + throw new DasStreamFormatException(); + default: + throw new DasStreamFormatException(); + } + } + else { + logger.fine("response is a legacy descriptor"); + reader.unread(four); + BufferedReader in = new BufferedReader(reader); + StreamDescriptor result = StreamDescriptor.createLegacyDescriptor(in); + return result; + } + } + else { + BufferedReader in = new BufferedReader(new InputStreamReader(inStream)); + StringBuilder message = new StringBuilder(); + for (String line = in.readLine(); line != null; line = in.readLine()) { + message.append(line).append('\n'); + } + throw new IOException(message.toString()); + } + } catch ( MalformedURLException e ) { + throw new DataSetDescriptorNotAvailableException("malformed URL"); + } catch ( FileNotFoundException e ) { + throw new DasServerNotFoundException( e.getMessage() ); + } catch ( IOException e ) { + throw new DasIOException(e.toString()); + } + } + + /** Handles key based authentication */ + @Deprecated + public Key authenticate( String user, String passCrypt) { + try { + Key result= null; + + String formData= "server=authenticator"; + formData+= "&user="+URLBuddy.encodeUTF8(user); + formData+= "&passwd="+URLBuddy.encodeUTF8(passCrypt); + + URL server= new URL("http",host,port,path+"?"+formData); + + logger.fine( "connecting to "+server); + + InputStream in= server.openStream(); + BufferedInputStream bin= new BufferedInputStream(in); + + String serverResponse= readServerResponse(bin); + + String errTag= "error"; + String keyTag= "key"; + + if ( serverResponse.substring(0,keyTag.length()+2).equals("<"+keyTag+">")) { + int index= serverResponse.indexOf(""); + String keyString= serverResponse.substring(keyTag.length()+2,index); + result= new Key(keyString); + } else { + result= null; + } + return result; + } catch (UnsupportedEncodingException uee) { + throw new AssertionError("UTF-8 not supported"); + } catch ( IOException e ) { + return null; + } + } + + /** returns a List of resource Id's available with this key */ + @Deprecated + public List groups( Key key ) { + try { + String formData= "server=groups"; + formData+= "&key="+URLBuddy.encodeUTF8(key.toString()); + + URL server= new URL("http",host,port,path+"?"+formData); + + logger.fine( "connecting to "+server); + + InputStream in= server.openStream(); + BufferedInputStream bin= new BufferedInputStream(in); + + String serverResponse= readServerResponse(bin); + + String[] groups= serverResponse.split(","); + ArrayList result= new ArrayList(); + for ( int i=0; i")) { + int index= serverResponse.indexOf(""); + String errString= serverResponse.substring(errTag.length()+2,index); + if (errString.equals("")) { + throw new DasServerException("Bad User/Pass"); + } + } + } catch (UnsupportedEncodingException uee) { + throw new AssertionError("UTF-8 not supported"); + } catch ( IOException e ) { + throw new DasServerException("Failed Connection"); + } + + + } + + @Deprecated + public String readServerResponse( BufferedInputStream in ) { + // Read ..., leaving the InputStream immediately after // + + in.mark(Integer.MAX_VALUE); + + String das2Response; + + byte[] data = new byte[4096]; + + int lastBytesRead = -1; + + String s; + + int offset=0; + + try { + int bytesRead= in.read(data,offset,4096-offset); + + String das2ResponseTag= "das2Response"; + // beware das2ResponseTagLength=14 assumed below!!! + + if (bytesRead<(das2ResponseTag.length()+2)) { + offset+= bytesRead; + bytesRead= in.read(data,offset,4096-offset); + } + + if ( new String(data,0,14,"UTF-8").equals("<"+das2ResponseTag+">")) { + while ( !new String( data,0,offset,"UTF-8" ).contains("") && + offset<4096 ) { + offset+= bytesRead; + bytesRead= in.read(data,offset,4096-offset); + } + + int index= new String( data,0,offset,"UTF-8" ).indexOf(""); + + das2Response= new String(data,14,index-14); + + org.das2.util.DasDie.println("das2Response="+das2Response); + + in.reset(); + in.skip( das2Response.length() + 2 * das2ResponseTag.length() + 5 ); + + } else { + in.reset(); + + das2Response=""; + } + } catch ( IOException e ) { + das2Response= ""; + } + + logger.fine( "response="+das2Response); + + return das2Response; + } + + // Utility function to handle reading data off the HTTP stream. Used by functions + // such as getName and getLogo that don't expect to receive a Das2 Stream + private byte[] read(InputStream uin) throws IOException { + LinkedList list = new LinkedList(); + byte[] data; + int bytesRead=0; + int totalBytesRead=0; + + //BufferedInputStream in= new BufferedInputStream(uin,4096*2); + InputStream in= uin; + + long time = System.currentTimeMillis(); + // fireReaderStarted(); + + //FileOutputStream out= new FileOutputStream("x."+time+".dat"); + + data = new byte[4096]; + + int lastBytesRead = -1; + + String s; + + int offset=0; + + // if (requestor != null) { + // requestor.totalByteCount(-1); + // } + + bytesRead= in.read(data,offset,4096-offset); + + while (bytesRead != -1) { + + int bytesSoFar = totalBytesRead; + // fireReaderUpdate(bytesSoFar); + // if (requestor != null) { + // requestor.currentByteCount(bytesSoFar); + // } + + offset+=bytesRead; + lastBytesRead= offset; + + if (offset==4096) { + list.addLast(data); + data = new byte[4096]; + offset=0; + } + + totalBytesRead+= bytesRead; + + bytesRead= in.read(data,offset,4096-offset); + + } + + if (lastBytesRead<4096) { + list.addLast(data); + } + + if (list.size()== 0) { + return new byte[0]; + } + + int dataLength = (list.size()-1)*4096 + lastBytesRead; + + data = new byte[dataLength]; + + Iterator iterator = list.iterator(); + int i; + for (i = 0; i < list.size()-1; i++) { + System.arraycopy(iterator.next(), 0, data, i*4096, 4096); + } + System.arraycopy(iterator.next(), 0, data, i*4096, lastBytesRead); + + return data; + } + + public String getProto() { + return sProto; + } + + public String getHost() { + return host; + } + + public int getPort() { + // returns -1 if the port was not specified, which can be used with URL constructor + return port; + } + + public String getPath() { + return path; + } + + public URL getURL( String formData ) throws MalformedURLException { + return new URL( sProto, host, port, path+"?"+formData ); + } + + public Key getKey( String resource ) { + synchronized (this) { + if ( keys.get(resource)==null ) { + Authenticator authenticator; + authenticator= new Authenticator(this,resource); + Key key= authenticator.authenticate(); + if ( key!=null ) keys.put( resource, key); + } + } + return (Key)keys.get(resource); + } + + //Let's see if this is actually used anywhere + //public void setKey( Key key ) { + // this.key= key; + //} + + @Override + public String toString() { + return this.getURL(); + } +} diff --git a/dasCore/src/main/java/org/das2/client/DasServerException.java b/dasCore/src/main/java/org/das2/client/DasServerException.java new file mode 100644 index 000000000..a52df99c5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/DasServerException.java @@ -0,0 +1,41 @@ +/* File: DasServerException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +/** + * + * @author jbf + */ +public class DasServerException extends org.das2.DasException { + + /** Creates a new instance of DasServerException */ + public DasServerException() { + super(); + } + + public DasServerException(String msg) { + super(msg); + } + +} diff --git a/dasCore/src/main/java/org/das2/client/DasServerNotFoundException.java b/dasCore/src/main/java/org/das2/client/DasServerNotFoundException.java new file mode 100644 index 000000000..99bb48c83 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/DasServerNotFoundException.java @@ -0,0 +1,46 @@ +/* File: DasServerNotFoundException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +/** + * + * @author jbf + */ +public class DasServerNotFoundException extends org.das2.DasException { + + /** + * Creates a new instance of DasServerNotFoundException without detail message. + */ + public DasServerNotFoundException() { + } + + + /** + * Constructs an instance of DasServerNotFoundException with the specified detail message. + * @param msg the detail message. + */ + public DasServerNotFoundException(String msg) { + super(msg); + } +} diff --git a/dasCore/src/main/java/org/das2/client/DataSetDescriptorNotAvailableException.java b/dasCore/src/main/java/org/das2/client/DataSetDescriptorNotAvailableException.java new file mode 100644 index 000000000..f7cff06b9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/DataSetDescriptorNotAvailableException.java @@ -0,0 +1,42 @@ +/* File: DataSetDescriptorNotAvailableException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +/** + * + * @author jbf + */ +public class DataSetDescriptorNotAvailableException extends org.das2.DasException { + /** + * Creates a new instance of DataSetDescriptionNotAvailableException without detail message. + */ + + /** + * Constructs an instance of DataSetDescriptionNotAvailableException with the specified detail message. + * @param msg the detail message. + */ + public DataSetDescriptorNotAvailableException(String msg) { + super(msg); + } +} diff --git a/dasCore/src/main/java/org/das2/client/DataSetStreamHandler.java b/dasCore/src/main/java/org/das2/client/DataSetStreamHandler.java new file mode 100755 index 000000000..f7194cf53 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/DataSetStreamHandler.java @@ -0,0 +1,403 @@ +/* File: DataSetStreamHandler.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 11, 2004, 4:26 PM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +import org.das2.stream.StreamYScanDescriptor; +import org.das2.stream.StreamComment; +import org.das2.stream.StreamMultiYDescriptor; +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.StreamHandler; +import org.das2.stream.SkeletonDescriptor; +import org.das2.stream.PacketDescriptor; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.dataset.CacheTag; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableDataSetBuilder; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.DatumUtil; +import org.das2.datum.DatumVector; +import org.das2.datum.TimeUtil; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import java.text.ParseException; +import java.util.*; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author Edward E. West + */ +public class DataSetStreamHandler implements StreamHandler { + + StreamHandlerDelegate delegate; + StreamDescriptor sd; + Map extraProperties; + ProgressMonitor monitor; + int totalPacketCount= -1; + int taskSize= -1; + int packetCount= 0; + Datum xTagMax= null; + boolean bReadPkts = true; + + private static final Logger logger= DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG); + private boolean monotonic= true; // generally streams are monotonic, so check for monotonicity. + + public DataSetStreamHandler( Map extraProperties, ProgressMonitor monitor ) { + this.extraProperties = new HashMap(extraProperties); + this.monitor= monitor==null ? new NullProgressMonitor() : monitor; + } + + @Override + public void streamDescriptor(StreamDescriptor sd) throws StreamException { + logger.finest("got stream descriptor"); + this.sd = sd; + Object o; + if ( ( o= sd.getProperty("taskSize") )!=null ) { + this.taskSize= ((Integer)o).intValue(); + monitor.setTaskSize( taskSize ); + monitor.started(); + } else if ( ( o= sd.getProperty("packetCount" ) )!=null ) { + this.totalPacketCount= ((Integer)o).intValue(); + monitor.setTaskSize( totalPacketCount ); + monitor.started(); + } + if ( ( o= sd.getProperty("cacheTagString" ) ) !=null ) { + // kludge to xmit cacheTags. "start,resolution,end" + try { + String[] ss= ((String)o).split(","); + Datum min= TimeUtil.create(ss[0]); + Datum max= TimeUtil.create(ss[2]); + Datum res= DatumUtil.parse(ss[1]); + if ( res.doubleValue( res.getUnits() ) == 0 ) res= null; // intrinsic resolution + extraProperties.put( DataSet.PROPERTY_CACHE_TAG, new CacheTag( min, max, res ) ); + } catch ( ParseException e ) { + e.printStackTrace(); + } + } + if ( ( o=sd.getProperty( DataSet.PROPERTY_X_MONOTONIC ) )!=null ) { + extraProperties.put( DataSet.PROPERTY_X_MONOTONIC, Boolean.valueOf((String)o) ); + + } + if ( ( o=sd.getProperty("pid") )!=null ) { + logger.log(Level.FINE, "stream pid={0}", o); + } + } + + @Override + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + logger.finest("got packet descriptor"); + if (delegate == null) { + SkeletonDescriptor descriptor = pd.getYDescriptor(0); + if (descriptor instanceof StreamMultiYDescriptor) { + logger.fine("using VectorDS delegate"); + delegate = new VectorDataSetStreamHandler(pd); + } else if (descriptor instanceof StreamYScanDescriptor) { + logger.fine("using TableDS delegate"); + delegate = new TableDataSetStreamHandler(pd); + } + } else { + delegate.packetDescriptor(pd); + } + } + + public void setReadPackets(boolean b){ + bReadPkts = b; + } + + public boolean getReadPackets(){ + return bReadPkts; + } + + @Override + public void packet(PacketDescriptor pd, Datum xTag, DatumVector[] vectors) throws StreamException { + if(!bReadPkts) return; + + logger.finest("got packet"); + ensureNotNullDelegate(); + if ( xTagMax==null || xTag.ge( this.xTagMax ) ) { + xTagMax= xTag; + } else { + monotonic= false; + } + delegate.packet(pd, xTag, vectors); + packetCount++; + if ( totalPacketCount!=-1 ) { + monitor.setTaskProgress(packetCount); + } + } + + @Override + public void streamClosed(StreamDescriptor sd) throws StreamException { + logger.finest("got streamClosed"); + if (delegate != null) { + delegate.streamClosed(sd); + } + } + + @Override + public void streamException(StreamException se) throws StreamException { + logger.finest("got stream exception"); + } + + @Override + public void streamComment(StreamComment sc) throws StreamException { + logger.log(Level.FINEST, "got stream comment: {0}", sc); + + if (sc.getType().equals(sc.TYPE_TASK_SIZE)){ + if(! monitor.isCancelled()){ + taskSize = Integer.parseInt(sc.getValue()); + monitor.setTaskSize(taskSize); + monitor.started(); + } + return; + } + + if ( sc.getType().equals(sc.TYPE_TASK_PROGRESS) ) { + if ( taskSize != -1 && !monitor.isCancelled() ) + monitor.setTaskProgress( Long.parseLong(sc.getValue() ) ); + return; + } + + if ( sc.getType().matches(sc.TYPE_LOG) ) { + String level= sc.getType().substring(4); + Level l= Level.parse(level.toUpperCase()); + if ( l.intValue()>Level.FINE.intValue() ) { + logger.log(Level.FINE,sc.getValue()); + } else { + logger.log(l,sc.getValue()); + } + monitor.setProgressMessage(sc.getValue()); + } + } + + public DataSet getDataSet() { + if (delegate == null) { + System.err.println("never established delegate, which might mean the stream contains no packets."); + return null; + } else { + return delegate.getDataSet(); + } + } + + private void ensureNotNullDelegate() { + if (delegate == null) { + throw new IllegalStateException("Null delegate"); + } + } + + private static double getXWithBase(Datum base, Datum x) { + if (base == null) { + return x.doubleValue(x.getUnits()); + } else { + return base.doubleValue(base.getUnits()) + x.doubleValue(base.getUnits().getOffsetUnits()); + } + } + + private static interface StreamHandlerDelegate extends StreamHandler { + DataSet getDataSet(); + } + + private class VectorDataSetStreamHandler implements StreamHandlerDelegate { + + private VectorDataSetBuilder builder; + + private DatumRange validRange=null; + + private VectorDataSetStreamHandler(PacketDescriptor pd) throws StreamException { + StreamMultiYDescriptor y = (StreamMultiYDescriptor)pd.getYDescriptor(0); + Datum base = pd.getXDescriptor().getBase(); + Units xUnits = base == null ? pd.getXDescriptor().getUnits() : base.getUnits(); + Units yUnits = y.getUnits(); + builder = new VectorDataSetBuilder(xUnits,yUnits); + builder.addProperties( Collections.singletonMap( DataSet.PROPERTY_Y_LABEL, + y.getProperty("name") )); + for ( int i=1; i props= my.getProperties(); + for ( Entry p: props.entrySet() ) { + if ( i==0 ) { + builder.setProperty( p.getKey(), p.getValue() ); + } else { + builder.setProperty( my.getName()+"."+p.getKey(), p.getValue() ); + } + } + } else { + throw new StreamException("Mixed data sets are not currently supported"); + } + } + //TODO: see TableDataSet below, where PacketDescriptor can have properties. + + } + + @Override + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + logger.log(Level.FINE, "got packet descriptor: {0}", pd); + for (int i = 1; i < pd.getYCount(); i++) { + StreamMultiYDescriptor y = (StreamMultiYDescriptor)pd.getYDescriptor(i); + builder.addPlane(y.getName(),y.getUnits()); + } + } + + @Override + public void streamClosed(StreamDescriptor sd) throws StreamException {} + + @Override + public void streamDescriptor(StreamDescriptor sd) throws StreamException {} + + @Override + public void streamException(StreamException se) throws StreamException {} + + @Override + public void streamComment(StreamComment sc) throws StreamException {} + + @Override + public DataSet getDataSet() { + builder.addProperties(sd.getProperties()); + builder.addProperties(extraProperties); + if ( monotonic&& builder.getProperty(DataSet.PROPERTY_X_MONOTONIC)==null ) { + builder.setProperty( DataSet.PROPERTY_X_MONOTONIC, Boolean.TRUE ); + } + return builder.toVectorDataSet(); + } + + } + + private class TableDataSetStreamHandler implements StreamHandlerDelegate { + + private TableDataSetBuilder builder; + + private TableDataSetStreamHandler(PacketDescriptor pd) throws StreamException { + StreamYScanDescriptor y = (StreamYScanDescriptor)pd.getYDescriptor(0); + Datum base = pd.getXDescriptor().getBase(); + Units xUnits = base != null ? base.getUnits() : pd.getXDescriptor().getUnits(); + Units yUnits = y.getYUnits(); + Units zUnits = y.getZUnits(); + + builder = new TableDataSetBuilder(xUnits, yUnits, zUnits, y.getName()); + this.packetDescriptor(pd); + } + + @Override + public void packet(PacketDescriptor pd, Datum xTag, DatumVector[] vectors) throws StreamException { + StreamYScanDescriptor yscan = (StreamYScanDescriptor)pd.getYDescriptor(0); + Datum base = pd.getXDescriptor().getBase(); + Datum x = base == null ? xTag : base.add(xTag); + DatumVector y = DatumVector.newDatumVector(yscan.getYTags(), yscan.getYUnits()); + String[] planeIDs = new String[pd.getYCount()]; + for (int i = 0; i < pd.getYCount(); i++) { + planeIDs[i] = ((StreamYScanDescriptor)pd.getYDescriptor(i)).getName(); + } + builder.insertYScan(x, y, vectors, planeIDs); + } + + @Override + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + StreamYScanDescriptor y = (StreamYScanDescriptor)pd.getYDescriptor(0); + for (int i = 1; i < pd.getYCount(); i++) { + y = (StreamYScanDescriptor)pd.getYDescriptor(i); + builder.addPlane(y.getName(), y.getZUnits()); + } + Map p= pd.getProperties(); + for ( Entry e: p.entrySet() ) { + String key= (String)e.getKey(); + Object p0= builder.getProperty(key); + if ( p0==null ) { + builder.setProperty( key, e.getValue() ); + } else { + if ( ! p0.equals( e.getValue() ) ) { + int i2; + for ( i2=1; builder.getProperty(""+key+"."+i2)!=null; i2++ ) { /* nothing */ } + builder.setProperty( ""+key+"."+i2, e.getValue() ); + } + } + } + //TODO: see VectorDataSet above, where each plane can have properties. + } + + @Override + public void streamClosed(StreamDescriptor sd) throws StreamException {} + + @Override + public void streamDescriptor(StreamDescriptor sd) throws StreamException {} + + @Override + public void streamException(StreamException se) throws StreamException {} + + @Override + public void streamComment(StreamComment sc) throws StreamException {} + + @Override + public DataSet getDataSet() { + builder.addProperties(sd.getProperties()); + builder.addProperties(extraProperties); + if ( monotonic&& builder.getProperty(DataSet.PROPERTY_X_MONOTONIC)==null ) { + builder.setProperty( DataSet.PROPERTY_X_MONOTONIC, Boolean.TRUE ); + } + return builder.toTableDataSet(); + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/client/FakeStandardDataStreamSource.java b/dasCore/src/main/java/org/das2/client/FakeStandardDataStreamSource.java new file mode 100644 index 000000000..214097593 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/FakeStandardDataStreamSource.java @@ -0,0 +1,123 @@ +/* File: FakeStandardDataStreamSource.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.DasException; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +/** + * + * @author jbf + */ +public class FakeStandardDataStreamSource implements StandardDataStreamSource { + + class FakeInputStream extends InputStream { + int nitems; + long nRecs; + double recsPerSecond; + long floatCount; + long byteCount; + long recCount; + long recSize; + float currentFloat; + byte[] iCurrentFloat; + ByteBuffer buff; + FloatBuffer fbuff; + double transferRateBps; + long transferBirthMilli; + + FakeInputStream( long nRecs, double recsPerSecond, int nitems ) { + this.nitems= nitems; + this.nRecs= nRecs; + this.recsPerSecond= recsPerSecond; + buff= ByteBuffer.allocate(4*(nitems+1)); + fbuff = buff.asFloatBuffer(); + buff.position(buff.limit()); + floatCount=0; + byteCount=0; + recCount=0; + recSize= (nitems+1)*4; + iCurrentFloat= new byte[4]; + transferRateBps= 2.1e5; + transferBirthMilli= System.currentTimeMillis(); + } + + public int read() throws java.io.IOException { + try { + while( ( byteCount * 1000 / ( 1 + ( System.currentTimeMillis() - transferBirthMilli ) ) ) > transferRateBps ) { + Thread.sleep(10); + } + } catch ( InterruptedException e ) { + } + + int result; + if ( ! buff.hasRemaining() ) { + buff.position(0); + fbuff.put(0, (float) ( recCount / recsPerSecond )); + for (int i=1; i<=nitems; i++) { + float f= (float)Math.random(); + fbuff.put(i,f); + } + recCount++; + } + + if (recCount>nRecs) { + return -1; + } else { + result= buff.get() & 0xff; + byteCount++; + return result; + } + } + + } + + /** Creates a new instance of FakeStandardDataStreamSource */ + public FakeStandardDataStreamSource() { + } + + public InputStream getInputStream( StreamDataSetDescriptor dsd, Datum start, Datum end) throws DasException { + + double recsPerSecond= 1 / 120000.0; + int nRec= (int) ( end.subtract(start).doubleValue(Units.seconds) * recsPerSecond ); + + int nitems = 12; + + InputStream result= new FakeStandardDataStreamSource.FakeInputStream(nRec,recsPerSecond,nitems); + return result; + } + + public InputStream getReducedInputStream( StreamDataSetDescriptor dsd, Datum start, Datum end, Datum timeResolution) throws DasException { + return getInputStream( dsd, start, end ); + } + + public void reset() { + } + +} diff --git a/dasCore/src/main/java/org/das2/client/InputStreamMeter.java b/dasCore/src/main/java/org/das2/client/InputStreamMeter.java new file mode 100644 index 000000000..924c8f9cb --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/InputStreamMeter.java @@ -0,0 +1,165 @@ +/* + * InputStreamMeter.java + * + * Created on October 21, 2005, 5:35 PM + * + * To change this template, choose Tools | Options and locate the template under + * the Source Creation and Management node. Right-click the template and choose + * Open. You can then make changes to the template in the Source Editor. + */ + +package org.das2.client; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author Jeremy + */ +public class InputStreamMeter { + + long totalBytesRead; + long millisElapsed; + double speedLimit; + long meterCount; // only clock while there are > 0 inputStreams out there + long startTime; + + /** Creates a new instance of InputStreamMeter */ + public InputStreamMeter() { + this.speedLimit= 0; + this.totalBytesRead= 0; + this.millisElapsed= 0; + this.startTime= -1; + } + + /** MeteredInputStream is used to monitor the read. It is required to notify + * the InputStreamMeter of the number of bytesRead, and allow the InputStreamMeter + * to govern download speed by inserting blocking sleeps. + * + * @author jbf + */ + private class MeteredInputStream extends InputStream { + + InputStream in; + InputStreamMeter meter; + + /** Creates a new instance of MeteredInputStream + */ + private MeteredInputStream( InputStream in, InputStreamMeter meter ) { + this.meter= meter; + this.in= in; + } + + public int read( byte[] b, int off, int len ) throws IOException { + try { + int bytesRead= in.read(b,off,len); + meter.addBytes(bytesRead,this); + meter.governSpeed(this); + return bytesRead; + } catch ( IOException e ) { + meter.exception(this); + throw e; + } + } + + public int read() throws IOException { + try { + int byteRead= in.read(); + meter.addBytes(1,this); + meter.governSpeed(this); + return byteRead; + } catch ( IOException e ) { + meter.exception(this); + throw e; + } + } + + public void close() throws IOException { + meter.closing(this); + in.close(); + } + + } + + + public InputStream meterInputStream( InputStream in ) { + meterCount++; + if ( meterCount==1 ) { + startTime= System.currentTimeMillis(); + totalBytesRead= 0; + millisElapsed= 0; + } + return new MeteredInputStream( in, this ); + } + + /* limit total speed, possibly balancing load btw streams */ + private void governSpeed( MeteredInputStream mis ) { + if ( speedLimit>0 ) { + if ( calcTransmitSpeed() > speedLimit ) { + long targetMillis= (long) ( ( totalBytesRead ) / ( speedLimit / 1000. ) ); + long waitMs= Math.min( 1000, targetMillis - calcMillisElapsed() ); + DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG).fine("limiting speed by waiting "+waitMs+" ms"); + try { Thread.sleep(waitMs); } catch ( InterruptedException ex ) { } + } + } + } + + /* add these bytes to the meter, on behalf of mis. */ + private void addBytes( long bytes, MeteredInputStream mis ) { + totalBytesRead+= bytes; + } + + + private void closing( MeteredInputStream mis ) { + meterCount--; + if ( meterCount==0 ) { + this.millisElapsed+= System.currentTimeMillis() - startTime; + this.startTime=-1; + } + } + + private void exception( MeteredInputStream mis ) { + meterCount--; + if ( meterCount==0 ) { + this.millisElapsed+= System.currentTimeMillis() - startTime; + this.startTime=-1; + } + } + + private long calcMillisElapsed() { + long millis= this.millisElapsed; + if ( startTime!=-1 ) { + millis+= System.currentTimeMillis() - startTime; + } + return millis; + } + + private double calcTransmitSpeed() { + long millis= calcMillisElapsed(); + if ( millis==0 ) { + return Units.bytesPerSecond.getFillDouble(); + } else { + return 1000 * totalBytesRead / millis; + } + } + + public Datum getTransmitSpeed() { + return Units.bytesPerSecond.createDatum( calcTransmitSpeed(), 10 ); + } + + public long getBytesTransmitted() { + return totalBytesRead; + } + + public Datum getSpeedLimit() { + return Units.bytesPerSecond.createDatum( this.speedLimit ); + } + + public void setSpeedLimit( Datum speedLimit) { + this.speedLimit = speedLimit.doubleValue( Units.bytesPerSecond ); + } +} diff --git a/dasCore/src/main/java/org/das2/client/Key.java b/dasCore/src/main/java/org/das2/client/Key.java new file mode 100644 index 000000000..ee420e2ff --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/Key.java @@ -0,0 +1,42 @@ +/* File: Key.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +/** + * + * @author jbf + */ +public class Key { + + String value; + + /** Creates a new instance of Key */ + public Key(String value) { + this.value= value; + } + + public String toString() { + return this.value; + } +} diff --git a/dasCore/src/main/java/org/das2/client/NoSuchDataSetException.java b/dasCore/src/main/java/org/das2/client/NoSuchDataSetException.java new file mode 100644 index 000000000..ff922a174 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/NoSuchDataSetException.java @@ -0,0 +1,46 @@ +/* File: NoSuchDataSetException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +/** + * + * @author jbf + */ +public class NoSuchDataSetException extends org.das2.client.DasServerException { + + /** + * Creates a new instance of NoSuchDataSet without detail message. + */ + public NoSuchDataSetException() { + } + + + /** + * Constructs an instance of NoSuchDataSet with the specified detail message. + * @param msg the detail message. + */ + public NoSuchDataSetException(String msg) { + super(msg); + } +} diff --git a/dasCore/src/main/java/org/das2/client/StandardDataStreamSource.java b/dasCore/src/main/java/org/das2/client/StandardDataStreamSource.java new file mode 100644 index 000000000..645c949bb --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/StandardDataStreamSource.java @@ -0,0 +1,40 @@ +/* File: StandardDataStreamSource.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +import org.das2.datum.Datum; +import org.das2.DasException; + +import java.io.InputStream; + +/** + * + * @author jbf + */ +public interface StandardDataStreamSource { + + public InputStream getInputStream( StreamDataSetDescriptor dsd, Datum start, Datum end) throws DasException; + public InputStream getReducedInputStream( StreamDataSetDescriptor dsd, Datum start, Datum end, Datum timeResolution) throws DasException; + public void reset(); +} diff --git a/dasCore/src/main/java/org/das2/client/StreamDataSetDescriptor.java b/dasCore/src/main/java/org/das2/client/StreamDataSetDescriptor.java new file mode 100755 index 000000000..1f4770133 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/StreamDataSetDescriptor.java @@ -0,0 +1,475 @@ +/* File: DataSetDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.client; + +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.TableDataSetBuilder; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.UnitsConverter; +import org.das2.stream.StreamYScanDescriptor; +import org.das2.stream.StreamMultiYDescriptor; +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.PacketDescriptor; +import org.das2.DasIOException; +import org.das2.CancelledOperationException; +import org.das2.DasException; +import org.das2.util.DasProgressMonitorInputStream; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.datum.DatumVector; +import org.das2.system.DasLogger; +import org.das2.util.StreamTool; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.logging.Logger; +import javax.xml.parsers.*; +import org.xml.sax.*; +import org.w3c.dom.*; +import org.w3c.dom.Document; + +public class StreamDataSetDescriptor extends DataSetDescriptor { + + protected StandardDataStreamSource standardDataStreamSource; + private boolean serverSideReduction = true; + private PacketDescriptor defaultPacketDescriptor; + + private static final Logger logger= DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG); + + public Units getXUnits() { + return Units.us2000; + } + + /** + * Creates a new instance of StreamDataSetDescriptor + * from the specified file + */ + protected StreamDataSetDescriptor(Map properties) { + setProperties(properties); + } + + protected StreamDataSetDescriptor(Map properties, boolean legacy) { + setProperties(properties, legacy); + } + + public StreamDataSetDescriptor(StreamDescriptor sd, StandardDataStreamSource sdss) { + this(sd.getProperties(), "true".equals(sd.getProperty("legacy"))); + this.standardDataStreamSource = sdss; + } + + public void setStandardDataStreamSource(StandardDataStreamSource sdss) { + this.standardDataStreamSource= sdss; + } + + public StandardDataStreamSource getStandardDataStreamSource() { + return this.standardDataStreamSource; + } + + protected void setProperties(Map properties, boolean legacy) { + super.setProperties(properties); + if (properties.containsKey("form") && properties.get("form").equals("x_multi_y") + && properties.containsKey("items")) { + setDefaultCaching(false); + } + if (legacy) { + defaultPacketDescriptor = PacketDescriptor.createLegacyPacketDescriptor(properties); + } + } + + @Override + protected void setProperties( Map properties ) { + setProperties(properties, false); + } + + private ByteBuffer getByteBuffer(InputStream in) throws DasException { + byte[] data = readBytes(in); + ByteBuffer buffer = ByteBuffer.wrap(data); + return buffer; + } + + /** + * Auxiliary method used by readDoubles(InputStream, Object, Datum, Datum); + * + * Read data for the given start and end dates and returns an array of bytes + * + * @author eew + * @throws java.io.IOException If there is an error getting data from the reader, and IOException is thrown + */ + protected byte[] readBytes(InputStream in) throws DasException { + LinkedList list = new LinkedList(); + byte[] data = new byte[4096]; + int bytesRead=0; + int totalBytesRead=0; + int lastBytesRead = -1; + int offset=0; + long time = System.currentTimeMillis(); + + try { + bytesRead= in.read(data,offset,4096-offset); + while (bytesRead != -1) { + int bytesSoFar = totalBytesRead; + offset+=bytesRead; + lastBytesRead= offset; + if (offset==4096) { + list.addLast(data); + data = new byte[4096]; + offset=0; + } + totalBytesRead+= bytesRead; + bytesRead= in.read(data,offset,4096-offset); + } + } catch ( IOException e ) { + throw new DasIOException(e); + } + + if (lastBytesRead>=0 && lastBytesRead<4096) { + list.addLast(data); + } + + if (list.size()== 0) { + throw new DasIOException("Error reading data: no data available"); + } + int dataLength = (list.size()-1)*4096 + lastBytesRead; + data = new byte[dataLength]; + Iterator iterator = list.iterator(); + for (int i = 0; i < list.size()-1; i++) { + System.arraycopy(list.get(i), 0, data, i*4096, 4096); + } + System.arraycopy(list.get(list.size()-1), 0, data, (list.size() - 1)*4096, lastBytesRead); + return data; + } + + @Override + public String toString() { + return "dsd "+getDataSetID(); + } + + @Override + protected DataSet getDataSetImpl( Datum start, Datum end, Datum resolution, ProgressMonitor monitor ) throws DasException { + if ( resolution != null && !resolution.isFinite() ) throw new IllegalArgumentException( "resolution is not finite" ); + InputStream in; + DataSet result; + if ( serverSideReduction ) { + logger.fine("getting stream from standard data stream source"); + in= standardDataStreamSource.getReducedInputStream( this, start, end, resolution); + } else { + in= standardDataStreamSource.getInputStream( this, start, end ); + } + + logger.fine("reading stream"); + result = getDataSetFromStream( in, start, end, monitor ); + return result; + } + + protected DataSet getDataSetFromStream(InputStream in, Datum start, Datum end, ProgressMonitor monitor ) throws DasException { + if ( monitor==null ) monitor= new NullProgressMonitor(); + + PushbackInputStream pin = new PushbackInputStream(in, 4096); + try { + byte[] four = new byte[4]; + int bytesRead= pin.read(four); + logger.finer("read first four bytes bytesRead="+bytesRead); + if ( bytesRead!=4 ) { + logger.fine("no data returned from server"); + throw new DasIOException( "No data returned from server" ); + } + + if (new String(four).equals("[00]")) { + logger.finer("got stream header [00]"); + pin.unread(four); + + if ( monitor.isCancelled() ) { + pin.close(); + throw new InterruptedIOException("Operation cancelled"); + } + + final DasProgressMonitorInputStream mpin = new DasProgressMonitorInputStream(pin, monitor); + //InputStream mpin = pin; + + logger.finer("creating Channel"); + ReadableByteChannel channel = Channels.newChannel(mpin); + + DataSetStreamHandler handler = new DataSetStreamHandler( properties, monitor ) { + public void streamDescriptor(StreamDescriptor sd) throws StreamException { + super.streamDescriptor( sd ); + if ( super.taskSize!=-1 ) { + mpin.setEnableProgressPosition(false); + } + } + }; + + logger.finer("using StreamTool to read the stream"); + StreamTool.readStream(channel, handler); + return handler.getDataSet(); + } + else { + pin.unread(four); + + if ( monitor.isCancelled() ) { + pin.close(); + throw new InterruptedIOException("Operation cancelled"); + } + + monitor.started(); + + InputStream mpin = new DasProgressMonitorInputStream(pin, monitor); + + if (getProperty("form").equals("x_tagged_y_scan")) { + return getLegacyTableDataSet(mpin, start); + } + else if (getProperty("form").equals("x_multi_y")) { + return getLegacyVectorDataSet(mpin, start); + } + else { + throw new IllegalStateException("Unrecognized data set type: " + getProperty("form")); + } + } + } + catch (UnsupportedEncodingException uee) { + //UTF-8 should be supported by all JVM's + throw new RuntimeException(uee); + } + catch (IOException ioe) { + throw new DasIOException(ioe); + } + catch ( StreamException se ) { + /* kludge for when an InterruptedIOException is caused by the user's cancel. + * TODO: This is danger code, because it masks the condition where the + * interruption happened for some other reason the user isn't aware of. + */ + if ( se.getCause() instanceof InterruptedIOException ) { + DasException e= new CancelledOperationException(); + e.initCause(se); + throw e; + } else { + throw se; + } + } + finally { + try { pin.close(); } catch (IOException ioe) {} + } + } + + private static String getPacketID(byte[] four) throws DasException { + if ((four[0] == (byte)'[' && four[3] == (byte)']') || (four[0] == (byte)':' && four[3] == (byte)':')) { + return new String(new char[]{(char)four[1], (char)four[2]}); + } + else { + throw new DasException("Invalid stream, expecting 4 byte header, encountered '" + new String(four) + "'"); + } + } + + private DataSet getLegacyVectorDataSet(InputStream in0, Datum start) throws DasException { + try { + PushbackInputStream in = new PushbackInputStream(in0, 50); + PacketDescriptor sd = getPacketDescriptor(in); + VectorDataSetBuilder builder = new VectorDataSetBuilder(start.getUnits(),Units.dimensionless); // Units will be set when "" is encountered + for (Iterator i = sd.getYDescriptors().iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof StreamMultiYDescriptor) { + StreamMultiYDescriptor y = (StreamMultiYDescriptor)o; + String name = y.getName(); + if (name != null && !name.equals("")) { + builder.addPlane(name,y.getUnits()); + } else if ( "".equals(name) ) { + builder.setYUnits(y.getUnits()); + } + } + else { + throw new DasIOException("Invalid Stream Header: Non-Y-descriptor encountered"); + } + } + StreamMultiYDescriptor[] yDescriptors = (StreamMultiYDescriptor[])sd.getYDescriptors().toArray(new StreamMultiYDescriptor[0]); + int planeCount = yDescriptors.length - 1; + String[] planeIDs = new String[planeCount]; + int recordSize = sd.getXDescriptor().getSizeBytes() + yDescriptors[0].getSizeBytes(); + for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { + planeIDs[planeIndex] = yDescriptors[planeIndex+1].getName(); + recordSize += yDescriptors[planeIndex+1].getSizeBytes(); + } + ByteBuffer data = getByteBuffer(in); + double timeBaseValue = start.doubleValue(start.getUnits()); + Units offsetUnits = start.getUnits().getOffsetUnits(); + UnitsConverter uc = sd.getXDescriptor().getUnits().getConverter(offsetUnits); + while (data.remaining() > recordSize) { + DatumVector vector = sd.getXDescriptor().read(data); + double xTag = timeBaseValue + vector.doubleValue(0, offsetUnits); + vector = yDescriptors[0].read(data); + double yValue = vector.doubleValue(0, yDescriptors[0].getUnits()); + builder.insertY(xTag, yValue); + for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { + vector = yDescriptors[planeIndex + 1].read(data); + yValue = vector.doubleValue(0, yDescriptors[planeIndex + 1].getUnits()); + builder.insertY(xTag, yValue, yDescriptors[planeIndex + 1].getName()); + } + } + + if ( properties.containsKey("x_sample_width") ) { + properties.put( "xTagWidth", Datum.create( ((Double)properties.get("x_sample_width")).doubleValue(), + Units.seconds ) ); + } + + builder.addProperties(properties); + VectorDataSet result = builder.toVectorDataSet(); + return result; + } + catch (DasException de) { + de.printStackTrace(); + throw de; + } + } + + private DataSet getLegacyTableDataSet(InputStream in0, Datum start) throws DasException { + PushbackInputStream in= new PushbackInputStream(in0,50); + PacketDescriptor sd = getPacketDescriptor(in); + TableDataSetBuilder builder = new TableDataSetBuilder(start.getUnits(),Units.dimensionless,Units.dimensionless); + Units yUnits = Units.dimensionless; + Units zUnits= Units.dimensionless; + + for (Iterator i = sd.getYDescriptors().iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof StreamYScanDescriptor) { + StreamYScanDescriptor scan = (StreamYScanDescriptor)o; + String name = scan.getName(); + if (name != null && !name.equals("")) { + builder.addPlane(name,scan.getZUnits()); + } + } + else { + throw new DasIOException("Invalid Stream Header: Non-yScan descriptor encountered"); + } + } + StreamYScanDescriptor[] yScans = (StreamYScanDescriptor[])sd.getYDescriptors().toArray(new StreamYScanDescriptor[0]); + final int planeCount = yScans.length; + String[] planeIDs = new String[planeCount]; + int recordSize = sd.getXDescriptor().getSizeBytes(); + for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { + planeIDs[planeIndex] = yScans[planeIndex].getName(); + recordSize += yScans[planeIndex].getSizeBytes(); + } + ByteBuffer data = getByteBuffer(in); + double timeBaseValue= start.doubleValue(start.getUnits()); + Units offsetUnits = start.getUnits().getOffsetUnits(); + Units xdescUnits= sd.getXDescriptor().getUnits(); + if ( xdescUnits==null ) xdescUnits= Units.seconds; + //UnitsConverter uc = xdescUnits.getConverter(offsetUnits); + double[] yCoordinates = yScans[0].getYTags(); + DatumVector y = DatumVector.newDatumVector(yCoordinates, yUnits); + + while (data.remaining() > recordSize) { + DatumVector vector = sd.getXDescriptor().read(data); + Datum xTag = start.add(vector.get(0)); + DatumVector[] z = new DatumVector[planeCount]; + for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { + z[planeIndex]= yScans[planeIndex].read(data); + } + builder.insertYScan(xTag, y, z, planeIDs); + } + + if ( properties.containsKey("x_sample_width") ) { + properties.put( "xTagWidth", Datum.create( ((Double)properties.get("x_sample_width")).doubleValue(), + Units.seconds ) ); + } + + builder.addProperties(properties); + TableDataSet result = builder.toTableDataSet(); + return result; + } + + private static final byte[] HEADER = { (byte)'d', (byte)'a', (byte)'s', (byte)'2', (byte)0177, (byte)0177 }; + + private PacketDescriptor getPacketDescriptor(PushbackInputStream in) + throws DasIOException, StreamException + { + try { + byte[] four = new byte[HEADER.length]; + + int bytesRead = 0; + int totalBytesRead = 0; + do { + bytesRead = in.read(four, totalBytesRead, HEADER.length - totalBytesRead); + if (bytesRead != -1) totalBytesRead += bytesRead; + } while (totalBytesRead < HEADER.length && bytesRead != -1); + if (Arrays.equals(four, HEADER)) { + byte[] header = StreamTool.advanceTo(in, "\177\177".getBytes()); + ByteArrayInputStream source = new ByteArrayInputStream(header); + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.parse(source); + Element docNode= document.getDocumentElement(); + PacketDescriptor packetDescriptor= new PacketDescriptor(docNode); + return packetDescriptor; + } + else { + in.unread(four, 0, totalBytesRead); + return defaultPacketDescriptor; + } + } + catch ( ParserConfigurationException ex ) { + throw new IllegalStateException(ex.getMessage()); + } + catch ( StreamTool.DelimeterNotFoundException dnfe) { + DasIOException dioe = new DasIOException(dnfe.getMessage()); + dioe.initCause(dioe); + throw dioe; + } + catch ( SAXException ex ) { + DasIOException e= new DasIOException(ex.getMessage()); + e.initCause(ex); + throw e; + } + catch ( IOException ex) { + throw new DasIOException(ex); + } + } + + public boolean isRestrictedAccess() { + boolean result; + if (getProperty("groupAccess") != null) { + result= !("".equals(getProperty("groupAccess"))); + } else { + result= false; + } + return result; + } + + public void setServerSideReduction(boolean x) { + serverSideReduction= x; + } + + public boolean isServerSideReduction() { + return serverSideReduction; + } + + public PacketDescriptor getDefaultPacketDescriptor() { + return defaultPacketDescriptor; + } + +} diff --git a/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java b/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java new file mode 100755 index 000000000..35fa94ebb --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java @@ -0,0 +1,479 @@ +/* File: WebStandardDataStreamSource.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.client; + +import org.das2.dataset.NoDataInIntervalException; +import org.das2.dataset.NoKeyProvidedException; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.stream.StreamYScanDescriptor; +import org.das2.DasApplication; +import org.das2.util.URLBuddy; +/** + * + * @author jbf + */ + +import org.das2.DasException; +import org.das2.DasIOException; +import org.das2.datum.format.DatumFormatter; +import java.io.*; +import java.net.*; +import java.util.logging.Logger; +import javax.swing.ImageIcon; +import org.das2.CancelledOperationException; +import org.das2.system.LogCategory; +import org.das2.util.CredentialsManager; + +/** Web standard data stream source + * + * This class handles Das 1 and Das 2 streams served from a server implementing the + * Das 2.1 client - server protocol, including HTTP authentication. The Das 2.2 + * client - server protocol is not handled at this time. + */ +public class WebStandardDataStreamSource implements StandardDataStreamSource { + + private DasServer server; + protected String m_sHost; + protected String m_sDataSet; + private String extraParameters; + + /* Holds value of property compress. */ + private boolean compress; + + /* Holds value of property lastRequestURL. */ + private String lastRequestURL; + + public WebStandardDataStreamSource(DasServer server, URL url) { + this.server = server; + m_sHost = url.getHost(); + String[] query = url.getQuery() == null ? new String[0] : url.getQuery().split("&"); + if (query.length > 0) m_sDataSet = query[0]; + if (query.length > 1) extraParameters = query[1]; + } + + @Override + public InputStream getInputStream(StreamDataSetDescriptor dsd, Datum start, Datum end) + throws DasException { + String serverType="dataset"; + + StringBuffer formData = new StringBuffer(); + formData.append("server=").append(serverType); + + InputStream in= openURLConnection( dsd, start, end, formData ); + in= DasApplication.getDefaultApplication().getInputStreamMeter().meterInputStream(in); + return in; + } + + @Override + public InputStream getReducedInputStream( StreamDataSetDescriptor dsd, Datum start, Datum end, Datum timeResolution) throws DasException { + + StringBuffer formData = new StringBuffer(); + String form = (String)dsd.getProperty("form"); + + if ("true".equals(dsd.getProperty("legacy")) && "x_tagged_y_scan".equals(form) ) { + formData.append("server=compactdataset"); + StreamYScanDescriptor y = (StreamYScanDescriptor)dsd.getDefaultPacketDescriptor().getYDescriptors().get(0); + formData.append("&nitems=").append(y.getNItems() + 1); + if (timeResolution != null) { + formData.append("&resolution=").append(timeResolution.doubleValue(Units.seconds)); + } + } + else if( "1".equals(dsd.getProperty("requiresInterval")) + || ("x_multi_y".equals(form) && dsd.getProperty("items") != null) ){ + formData.append("server=dataset"); + if (timeResolution != null) { + formData.append("&interval=").append(timeResolution.doubleValue(Units.seconds)); + } + } + else { + formData.append("server=compactdataset"); + if (timeResolution != null) { + formData.append("&resolution=").append(timeResolution.doubleValue(Units.seconds)); + } + } + + if (extraParameters != null) { + formData.append("¶ms=").append(extraParameters); //Should already be url encoded. + } + + //if ( min!=null && min.calcTransmitSpeed()>30000 ) { + // compress= false; + //} + + //compress= true; + if (compress) formData.append("&compress=true"); + + if ( !devel.equals("") ) formData.append("&devel=").append(devel); + + InputStream in= openURLConnection( dsd, start, end, formData ); + + in= DasApplication.getDefaultApplication().getInputStreamMeter().meterInputStream(in); + return in; + } + + private String createFormDataString(String dataSetID, Datum start, Datum end, StringBuffer additionalFormData) throws UnsupportedEncodingException { + DatumFormatter formatter = start.getUnits().getDatumFormatterFactory().defaultFormatter(); + String startStr = formatter.format(start); + String endStr= formatter.format(end); + StringBuilder formData= new StringBuilder("dataset="); + formData.append(URLBuddy.encodeUTF8(dataSetID)); + formData.append("&start_time=").append(URLBuddy.encodeUTF8(startStr)); + formData.append("&end_time=").append(URLBuddy.encodeUTF8(endStr)); + formData.append("&").append(additionalFormData); + return formData.toString(); + } + + // Get the location ID associated with the dataSetId + protected String getLocId(){ + if((m_sHost != null)&&(m_sDataSet != null)) + return String.format("%s|%s", m_sHost, m_sDataSet); + else + return null; + } + + // Check / Set location information + protected void checkSetLocDescription(CredentialsManager cm){ + String sLocId = getLocId(); + if(sLocId == null) return; + + if(cm.hasDescription(sLocId)) return; + + String sSvrName = server.getName(); + String sDesc = String.format("

%s


Server: %s
" + + "Data Set: %s", sSvrName, m_sHost, m_sDataSet); + + ImageIcon icon = server.getLogo(); + cm.setDescription(sLocId, sDesc, icon); + } + + @SuppressWarnings("null") + protected synchronized InputStream openURLConnection( + StreamDataSetDescriptor dsd, Datum start, Datum end, StringBuffer additionalFormData ) + throws DasException { + + String[] tokens = dsd.getDataSetID().split("\\?|\\&"); + String dataSetID = tokens[1]; + + URL serverURL; + InputStream inStream = null; + String sContentType = null; + try{ + //Construct the GET string + String formData = createFormDataString(dataSetID, start, end, additionalFormData); + + //Handle old-style das2 authentication + if(dsd.isRestrictedAccess()){ + key = server.getKey(""); + if(key != null){ + formData += "&key=" + URLEncoder.encode(key.toString(), "UTF-8"); + } + } + + if(redirect){ + formData += "&redirect=1"; + } + serverURL = server.getURL(formData); + + // Loop handling authentication if needed + Logger.getLogger(LogCategory.DATA_TRANSFER_LOG).fine("opening " + serverURL.toString()); + String sLocId = getLocId(); + + while(inStream == null){ + HttpURLConnection httpConn = null; + URLConnection conn = serverURL.openConnection(); + if(serverURL.getProtocol().startsWith("http")){ + httpConn = (HttpURLConnection) conn; + } + else{ + throw new DasException("Das2 Server Protocol " + serverURL.getProtocol() + + "is not supported."); + } + + CredentialsManager cm = CredentialsManager.getMannager(); + if(cm.hasCredentials(sLocId)){ + httpConn.setRequestProperty("Authorization", + "Basic " + cm.getHttpBasicHash(sLocId)); + } + + httpConn.connect(); + sContentType = httpConn.getContentType(); + + int nStatus = httpConn.getResponseCode(); + + if(nStatus == HttpURLConnection.HTTP_UNAUTHORIZED){ + checkSetLocDescription(cm); + // If the cm had credentials for this location, they obviously didn't + // work, so invalidate them. + cm.invalidate(sLocId); + if(cm.getHttpBasicHash(sLocId) == null){ + throw new CancelledOperationException("Failed to gather credentials for "+sLocId); + } + + // Try again... + httpConn.disconnect(); + continue; + } + + if(nStatus >= 400){ + inStream = httpConn.getErrorStream(); + } + else{ + inStream = httpConn.getInputStream(); + } + } + } + catch(IOException ex){ + throw new DasIOException(ex); + } + + this.lastRequestURL = String.valueOf(serverURL); + + try{ + + //if (!contentType.equalsIgnoreCase("application/octet-stream")) { + if (sContentType.equalsIgnoreCase("text/plain")) { + BufferedReader bin = new BufferedReader(new InputStreamReader(inStream)); + String line = bin.readLine(); + String message = ""; + while (line != null) { + message = message.concat(line); + line = bin.readLine(); + } + throw new DasIOException(message); + } + + return processStream(inStream); + } + catch(IOException ex){ + throw new DasIOException(ex); + } + } + + private InputStream processStream(InputStream in) throws IOException, DasException { + /* advances the inputStream past the old das2server tags if needed */ + BufferedInputStream bin= new BufferedInputStream(in); + + bin.mark(Integer.MAX_VALUE); + String serverResponse= readServerResponse(bin); + + if ( serverResponse.equals("") ) { + return bin; + + } else { + + if (serverResponse.equals("")) { + throw new NoDataInIntervalException("no data in interval"); + } + + String errorTag= "error"; + if (serverResponse.startsWith("<"+errorTag+">")) { + int index2= serverResponse.indexOf(""); + + String error= serverResponse.substring( errorTag.length()+2, + serverResponse.length()-(errorTag.length()+3)); + + org.das2.util.DasDie.println("error="+error); + + /* presume that the endUser has opted out */ + if (error.equals("")) { + throw new NoKeyProvidedException(""); + } + + if (error.equals("")) { + /* the server says the key used does not have access to the resouce requested. Allow the user to reauthenticate */ + //server.setKey(null); + //server.getKey(""); + throw new AccessDeniedException(""); + } + + if (error.equals("" )) { + throw new NoKeyProvidedException("invalid Key"); + } + + if (error.equals("")) { + throw new NoSuchDataSetException(""); + } + + else { + throw new DasServerException("Error response from server: "+error); + } + } + + return bin; + } + } + + private String readServerResponse(InputStream in) { + + // Read ..., leaving the InputStream immediately after // + + String das2Response; + + byte[] data = new byte[4096]; + + int lastBytesRead = -1; + + String s; + + int offset=0; + + try { + int bytesRead= in.read(data,offset,4096-offset); + + String das2ResponseTag= "das2Response"; + // beware das2ResponseTagLength=14 assumed below!!! + + if (bytesRead<(das2ResponseTag.length()+2)) { + offset+= bytesRead; + bytesRead= in.read(data,offset,4096-offset); + } + + if ( new String(data,0,14,"UTF-8").equals("<"+das2ResponseTag+">")) { + while (! new String( data,0,offset,"UTF-8" ).contains("") && + offset<4096 ) { + offset+= bytesRead; + bytesRead= in.read(data,offset,4096-offset); + } + + int index= new String( data,0,offset,"UTF-8" ).indexOf(""); + + das2Response= new String(data,14,index-14); + + org.das2.util.DasDie.println("das2Response="+das2Response); + + in.reset(); + in.skip( das2Response.length() + 2 * das2ResponseTag.length() + 5 ); + + } else { + in.reset(); + + das2Response=""; + } + } catch ( IOException e ) { + das2Response= ""; + } + + return das2Response; + + } + + @Override + public void reset() { + } + + // Looks like graveyard code, bring back if app building fails + //public void authenticate( String restrictedResourceLabel ) { + // Authenticator authenticator; + // authenticator= new Authenticator(server,restrictedResourceLabel); + // Key key= authenticator.authenticate(); + // if ( key!=null ) server.setKey(key); + //} + + /** + * Getter for property compress. + * @return Value of property compress. + */ + public boolean isCompress() { + return this.compress; + } + + /** + * Setter for property compress. + * @param compress New value of property compress. + */ + public void setCompress(boolean compress) { + this.compress = compress; + } + + /** + * Getter for property lastRequestURL. + * @return Value of property lastRequestURL. + */ + public String getLastRequestURL() { + return this.lastRequestURL; + } + + public void setLastRequestURL( String url ) { + } + + public DasServer getDasServer() { + return this.server; + } + + /** + * Holds value of property redirect. + */ + private boolean redirect= false; + + /** + * Getter for property redirect. + * @return Value of property redirect. + */ + public boolean isRedirect() { + return this.redirect; + } + + /** + * Setter for property redirect. + * @param redirect New value of property redirect. + */ + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + /** + * Holds value of property key. + */ + private Key key=null; + + /** + * Getter for property key. + * @return Value of property key. + */ + public Key getKey() { + return this.key; + } + + private String devel=""; + + /** + * use the develop version of the reader instead of the + * production version. + */ + public String getDevel() { + return this.devel; + } + + /** + * use the develop version of the reader instead of the + * production version. If a reader was in dasHome/readers, + * then use the one in /home//readers/ instead. + * + */ + public void setDevel(String devel) { + this.devel = devel; + } + +} diff --git a/dasCore/src/main/java/org/das2/client/package.html b/dasCore/src/main/java/org/das2/client/package.html new file mode 100644 index 000000000..8fdafbb99 --- /dev/null +++ b/dasCore/src/main/java/org/das2/client/package.html @@ -0,0 +1,6 @@ + +

Provides classes for interacting with a das2Server, such as data set queries, +authorization, and server identity.

+

Also support for reading Das2Streams and legacy +streams is found here. These classes should probably be moved to the stream package.

+ \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/components/AngleSpectrogramSlicer.java b/dasCore/src/main/java/org/das2/components/AngleSpectrogramSlicer.java new file mode 100644 index 000000000..2c83e5b5a --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/AngleSpectrogramSlicer.java @@ -0,0 +1,248 @@ +/* File: HorizontalSpectrogramSlicer.java + * Copyright (C) 2002-2008 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.DasColumn; +import org.das2.graph.DasCanvas; +import org.das2.graph.GraphUtil; +import org.das2.graph.DasRow; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.event.BoxSelectionEvent; +import org.das2.event.BoxSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + + +public class AngleSpectrogramSlicer extends DasPlot implements BoxSelectionListener { + + private JDialog popupWindow; + + private SymbolLineRenderer renderer; + private DasPlot parentPlot; + private TableDataSetConsumer consumer; + private Datum xstart; + private Datum ystart; + private DatumRange xrange; + private DatumRange yrange; + + private int sliceDir; + private final int SLICEDIR_HORIZ=0; + private final int SLICEDIR_VERTICAL=1; + + private AngleSpectrogramSlicer(DasPlot plot, DasAxis xAxis, DasAxis yAxis, TableDataSetConsumer consumer ) { + super(xAxis, yAxis); + parentPlot = plot; + renderer= new SymbolLineRenderer(); + this.consumer= consumer; + addRenderer(renderer); + } + + public static AngleSpectrogramSlicer createSlicer( DasPlot plot, TableDataSetConsumer dataSetConsumer ) { + DasAxis sourceXAxis = plot.getXAxis(); + DasAxis xAxis = sourceXAxis.createAttachedAxis(DasAxis.HORIZONTAL); + DasAxis yAxis = dataSetConsumer.getZAxis().createAttachedAxis(DasAxis.VERTICAL); + + return new AngleSpectrogramSlicer(plot, xAxis, yAxis, dataSetConsumer ); + } + + public void showPopup() { + if (SwingUtilities.isEventDispatchThread()) { + showPopupImpl(); + } + else { + Runnable r = new Runnable() { + public void run() { + showPopupImpl(); + } + }; + } + } + + /** This method should ONLY be called by the AWT event thread */ + private void showPopupImpl() { + if (popupWindow == null) { + createPopup(); + } + popupWindow.setVisible(true); + } + + /** This method should ONLY be called by the AWT event thread */ + private void createPopup() { + int width = parentPlot.getCanvas().getWidth() / 2; + int height = parentPlot.getCanvas().getHeight() / 2; + DasCanvas canvas = new DasCanvas(width, height); + DasRow row = new DasRow(canvas, 0.1, 0.9); + DasColumn column = new DasColumn(canvas, 0.1, 0.9); + canvas.add(this, row, column); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + Window parentWindow = SwingUtilities.getWindowAncestor(parentPlot); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } + else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } + else { + popupWindow = new JDialog(); + } + popupWindow.setTitle("Angle Slicer"); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen(parentLocation, parentPlot.getCanvas()); + popupWindow.setLocation(parentLocation.x + parentPlot.getCanvas().getWidth(),parentLocation.y + height); + } + + + private VectorDataSet angleSliceHoriz( TableDataSet tds, DatumRange xlimit, Datum xbase, Datum ybase, Datum slope ) { + VectorDataSetBuilder builder= new VectorDataSetBuilder( tds.getXUnits(), tds.getZUnits() ); + int i0= DataSetUtil.closestColumn( tds, xlimit.min() ); + int i1= DataSetUtil.closestColumn( tds, xlimit.max() ); + + int irow0=0; + int irow1=0; + + Units zunits= tds.getZUnits(); + Units yunits= tds.getYUnits(); + + for ( int i=i0; i0 && tds.getYTagDatum( itable, irow0 ).gt( y ) ) irow0--; + irow1= irow0+1; + while ( (irow1+1)0 ) { + double z0= tds.getDouble( i, irow0, zunits ); + double z1= tds.getDouble( i, irow1, zunits ); + double y0= tds.getYTagDouble( itable, irow0, yunits ); + double y1= tds.getYTagDouble( itable, irow1, yunits ); + double yy= y.doubleValue(yunits); + double alpha= ( yy-y0 ) / ( y1-y0 ); + double zinterp= z0 + (z1-z0) * alpha; + builder.insertY( x, Datum.create(zinterp,zunits) ); + } + } + + return builder.toVectorDataSet(); + } + + @Override + public void drawContent(Graphics2D g) { + + double[] ixs; + double ix; + if ( sliceDir==SLICEDIR_HORIZ ) { + ixs= GraphUtil.transformRange( this.getXAxis(), xrange ); + ix= this.getXAxis().transform( xstart ); + } else { + ixs= GraphUtil.transformRange( this.getYAxis(), yrange ); + ix= this.getYAxis().transform( ystart ); + } + + DasRow row= getRow(); + + g.setColor( new Color(230,230,230) ); + g.fillRect( (int)ixs[0], row.getDMinimum(), (int)(ixs[1]-ixs[0]), row.getHeight() ); + + g.setColor( Color.LIGHT_GRAY ); + g.drawLine( (int)ix, row.getDMinimum(), (int)ix, row.getDMaximum() ); + + super.drawContent(g); + + } + + protected void processDasUpdateEvent(org.das2.event.DasUpdateEvent e) { + if (isDisplayable()) { + updateImmediately(); + resize(); + } + } + + public void BoxSelected(BoxSelectionEvent e) { + Datum xbase= e.getStartX(); + Datum ybase= e.getStartY(); + + xstart= e.getStartX(); + ystart= e.getStartY(); + + xrange= e.getXRange(); + yrange= e.getYRange(); + + Datum slope= e.getFinishY().subtract(ybase) .divide( e.getFinishX().subtract(xbase) ); + + DataSet ds = consumer.getConsumedDataSet(); + + if (ds==null || !(ds instanceof TableDataSet)) { + return; + } + + TableDataSet tds = (TableDataSet)ds; + VectorDataSet sliceDataSet; + + sliceDataSet= angleSliceHoriz( tds, getXAxis().getDatumRange(), xbase, ybase, slope ); + sliceDir= SLICEDIR_HORIZ; + + renderer.setDataSet(sliceDataSet); + + if (!(popupWindow == null || popupWindow.isVisible()) || getCanvas() == null) { + showPopup(); + } + + } +} diff --git a/dasCore/src/main/java/org/das2/components/AsciiFileParser.form b/dasCore/src/main/java/org/das2/components/AsciiFileParser.form new file mode 100644 index 000000000..9da4f7151 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/AsciiFileParser.form @@ -0,0 +1,182 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/dasCore/src/main/java/org/das2/components/AsciiFileParser.java b/dasCore/src/main/java/org/das2/components/AsciiFileParser.java new file mode 100644 index 000000000..15982d0c3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/AsciiFileParser.java @@ -0,0 +1,434 @@ +/* + * AsciiFileParser.java + * + * Created on July 13, 2006, 3:26 PM + */ + +package org.das2.components; + +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.util.DasExceptionHandler; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.MouseEvent; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import javax.swing.JFileChooser; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.event.MouseInputAdapter; + +/** + * Interactive GUI for parsing ascii tables into VectorDataSets + * + * @author Jeremy + */ +public class AsciiFileParser extends javax.swing.JPanel { + + /** Creates new form AsciiFileParser */ + public AsciiFileParser() { + initComponents(); + jTextField1.addFocusListener( new FocusAdapter() { + public void focusLost(FocusEvent e) { + updateSkipLines(); + } + }); + model= new AsciiTableModel(); + } + + class AsciiTableModel { + int columnCount=0; + int[] columnOffsets= new int[50]; + int[] columnWidths= new int[50]; + Units[] units= new Units[50]; + String[] names= new String[50]; + int skipLines=0; + } + + AsciiTableModel model; + File file; + + public DataSet parse() throws FileNotFoundException, IOException, ParseException { + + ArrayList usableColumns= new ArrayList(); + for ( int i=0; i//GEN-BEGIN:initComponents + private void initComponents() { + jLabel1 = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + delimSelector = new javax.swing.JComboBox(); + jLabel3 = new javax.swing.JLabel(); + jButton1 = new javax.swing.JButton(); + firstColumnTimeCheckBox = new javax.swing.JCheckBox(); + jScrollPane1 = new javax.swing.JScrollPane(); + jPanel1 = new javax.swing.JPanel(); + jTextArea1 = new FixedColumnTextArea(); + jTextArea2 = new FixedColumnTextArea(); + jLabel4 = new javax.swing.JLabel(); + jTextField1 = new javax.swing.JTextField(); + + jLabel1.setText("File:"); + + jLabel2.setText("f:\\myfile.txt"); + + delimSelector.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "WhiteSpace", "Comma", "RegExp (\\s\\s+)" })); + delimSelector.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + delimSelectorActionPerformed(evt); + } + }); + + jLabel3.setText("Delimiter:"); + + jButton1.setText("choose"); + jButton1.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(evt); + } + }); + + firstColumnTimeCheckBox.setText("FirstColumnTime"); + firstColumnTimeCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); + firstColumnTimeCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0)); + + jPanel1.setLayout(new java.awt.BorderLayout()); + + jTextArea1.setColumns(20); + jTextArea1.setRows(5); + jPanel1.add(jTextArea1, java.awt.BorderLayout.CENTER); + + jTextArea2.setBackground(new java.awt.Color(238, 238, 255)); + jTextArea2.setColumns(20); + jTextArea2.setRows(1); + MouseInputAdapter mia= new ColumnMouseInputAdapter((FixedColumnTextArea)jTextArea2); + jTextArea2.addMouseListener(mia); + jPanel1.add(jTextArea2, java.awt.BorderLayout.NORTH); + + jScrollPane1.setViewportView(jPanel1); + + jLabel4.setText("SkipLines:"); + + jTextField1.setText("0"); + jTextField1.addPropertyChangeListener(new java.beans.PropertyChangeListener() { + public void propertyChange(java.beans.PropertyChangeEvent evt) { + jTextField1PropertyChange(evt); + } + }); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(org.jdesktop.layout.GroupLayout.TRAILING, jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 453, Short.MAX_VALUE) + .add(layout.createSequentialGroup() + .add(jLabel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 45, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jButton1) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jLabel2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 264, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(layout.createSequentialGroup() + .add(jLabel3) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(delimSelector, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(layout.createSequentialGroup() + .add(firstColumnTimeCheckBox) + .add(77, 77, 77) + .add(jLabel4) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 47, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(jLabel2) + .add(jLabel1) + .add(jButton1)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(delimSelector, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(jLabel3)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(firstColumnTimeCheckBox) + .add(jLabel4) + .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 335, Short.MAX_VALUE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void jTextField1PropertyChange(java.beans.PropertyChangeEvent evt) {//GEN-FIRST:event_jTextField1PropertyChange + updateSkipLines(); + }//GEN-LAST:event_jTextField1PropertyChange + + private void delimSelectorActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_delimSelectorActionPerformed + try { + resetDelims(); + } catch ( IOException e ) { + handleException(e); + } + }//GEN-LAST:event_delimSelectorActionPerformed + + private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed + JFileChooser chooser= new JFileChooser(); + int retVal= chooser.showOpenDialog(this); + if ( retVal==chooser.APPROVE_OPTION ) { + try { + setFile( chooser.getSelectedFile() ); + } catch (IOException ex) { + handleException(ex); + } + } + }//GEN-LAST:event_jButton1ActionPerformed + + private void handleException( Throwable t ) { + DasExceptionHandler.handle(t); + } + + private void setFile(File file) throws IOException { + this.file= file; + resetDelims(); + } + + public String getDelimRegex() { + String result; + switch ( delimSelector.getSelectedIndex() ) { + case 0: result="\\s+"; break; + case 1: result=","; break; + case 2: result="\\s\\s+"; break; + default: throw new IllegalStateException("not implemented"); + } + return result; + } + + public void resetDelims() throws IOException { + String regex= getDelimRegex(); + BufferedReader reader= new BufferedReader( new FileReader( file ) ); + + for ( int i=0; i=taskList.size() ) { + if ( exit ) System.exit(0); + } else { + DasLogger.getLogger(DasLogger.SYSTEM_LOG).fine( "itask="+taskList.get(itask) ); + DataRangeSelectionEvent ev= (DataRangeSelectionEvent) taskList.get(itask++); + fireDataRangeSelectionListenerDataRangeSelected( ev ); + try { + canvas.waitUntilIdle(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + tod.completeTask( ev.getDatumRange() ); + submitNextTask(); + } + } + }); + thread.start(); + } + + /** Utility field used by event firing mechanism. */ + private javax.swing.event.EventListenerList listenerList = null; + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + + +} diff --git a/dasCore/src/main/java/org/das2/components/ColorBarComponent.java b/dasCore/src/main/java/org/das2/components/ColorBarComponent.java new file mode 100644 index 000000000..c96d59e01 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/ColorBarComponent.java @@ -0,0 +1,38 @@ +/* + * ColorBarComponent.java + * + * Created on November 15, 2003, 11:16 AM + */ + +package org.das2.components; + +import org.das2.graph.DasColorBar; +import org.das2.graph.DasColumn; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; +import org.das2.datum.Datum; +import javax.swing.*; + +/** + * + * @author Owner + */ +public class ColorBarComponent extends JPanel { + DasColorBar colorBar; + DasCanvas canvas; + + /** Creates a new instance of ColorBarComponent */ + public ColorBarComponent(Datum min, Datum max, boolean isLog) { + canvas= new DasCanvas(100, 500); + DasRow row= DasRow.create(canvas); + DasColumn column= DasColumn.create(canvas); + colorBar= new DasColorBar( min, max, isLog ); + canvas.add(colorBar,row, column); + this.add(canvas); + } + + public DasColorBar getColorBar() { + return colorBar; + } + +} diff --git a/dasCore/src/main/java/org/das2/components/ComponentsUtil.java b/dasCore/src/main/java/org/das2/components/ComponentsUtil.java new file mode 100644 index 000000000..29764af39 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/ComponentsUtil.java @@ -0,0 +1,76 @@ +/* + * ComponentsUtil.java + * + * Created on February 28, 2006, 12:10 PM + * + * + */ + +package org.das2.components; + +import org.das2.graph.DasCanvas; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.Point; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +/** + * + * @author Jeremy + */ +public class ComponentsUtil { + public static DasCanvas createPopupCanvas( Component parent, String title, int width, int height ) { + DasCanvas canvas = new DasCanvas(width, height); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + final JDialog popupWindow; + Window parentWindow = SwingUtilities.getWindowAncestor(parent); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } else { + popupWindow = new JDialog(); + } + + popupWindow.setTitle(title); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen( parentLocation, parent ); + popupWindow.setLocation( parentLocation.x + parent.getWidth(),parentLocation.y); + + return canvas; + } +} diff --git a/dasCore/src/main/java/org/das2/components/DasAxisSelector.java b/dasCore/src/main/java/org/das2/components/DasAxisSelector.java new file mode 100644 index 000000000..a65c008c3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DasAxisSelector.java @@ -0,0 +1,113 @@ +/* File: DasAxisSelector.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +/** + * + * @author jbf + */ +import org.das2.datum.Datum; +import org.das2.graph.DasAxis; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.text.DecimalFormat; + +public class DasAxisSelector extends javax.swing.JPanel implements ActionListener { + + private DasAxis axis= null; + + JTextField idStart= null; + JTextField idStop= null; + + /** Creates a new instance of DasTimeRangeSelector */ + private DasAxisSelector() { + super(); + buildComponents(); + } + + private void buildComponents() { + this.setLayout(new FlowLayout(FlowLayout.RIGHT)); + + idStart= new JTextField(""); + idStart.setSize(9,1); + idStart.addActionListener(this); + idStart.setActionCommand("setMinimum"); + this.add(idStart); + + idStop= new JTextField(""); + idStop.setSize(9,1); + idStop.addActionListener(this); + idStop.setActionCommand("setMaximum"); + this.add(idStop); + + } + + public DasAxisSelector(DasAxis axis) { + this(); + this.axis= axis; + update(); + } + + public double getStartTime() { + double s1= Double.valueOf(idStart.getText()).doubleValue(); + return s1; + } + + public double getEndTime() { + double s1= Double.valueOf(idStop.getText()).doubleValue(); + return s1; + } + + private void update() { + DecimalFormat df= new DecimalFormat(); + df.setMaximumFractionDigits(2); + idStart.setText(df.format(axis.getDataMinimum())); + idStop.setText(df.format(axis.getDataMaximum())); + idStart.setText(""+axis.getDataMinimum()); + idStop.setText(""+axis.getDataMaximum()); + } + + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + String command= actionEvent.getActionCommand(); + update(); + if (command.equals("setMinimum")) { + try { + axis.setDataRange(Datum.create(Double.valueOf(idStart.getText()).doubleValue(),axis.getUnits()), + axis.getDataMaximum()); + } catch (NumberFormatException e) { + org.das2.util.DasDie.println(e); + } + } else if (command.equals("setMaximum")) { + try { + axis.setDataRange(axis.getDataMinimum(), + Datum.create(Double.valueOf(idStop.getText()).doubleValue(), axis.getUnits() )); + } catch (NumberFormatException e) { + org.das2.util.DasDie.println(e); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/components/DasProgressPanel.java b/dasCore/src/main/java/org/das2/components/DasProgressPanel.java new file mode 100755 index 000000000..1f7a04887 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DasProgressPanel.java @@ -0,0 +1,565 @@ +/* File: DasProgressPanel.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.components; + +import org.das2.graph.DasCanvasComponent; +import org.das2.system.DasLogger; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.InvocationTargetException; +import java.text.DecimalFormat; +import java.util.logging.Level; +import javax.swing.*; +import javax.swing.border.*; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.NumberFormatUtil; +import java.util.*; +import java.util.logging.Logger; +import org.das2.graph.DasCanvas; + +/** + * + * @author eew + */ +public class DasProgressPanel implements ProgressMonitor { + + private long taskStartedTime; + private long currentTaskPosition; + private long maximumTaskPosition; + private DecimalFormat transferRateFormat; + private String transferRateString; + private JLabel taskLabel; + private boolean labelDirty = false; + private JLabel progressMessageLabel; + private String progressMessageString; + private boolean progressMessageDirty = false; + private JLabel kbLabel; + private JProgressBar progressBar; + private JFrame jframe = null; // created when createFramed() is used. + private boolean isCancelled = false; + private JButton cancelButton; + private int cancelCheckFailures = 0; // number of times client codes failed to check cancelled before setTaskProgress. + private boolean cancelChecked = false; + private String label; + private static final int hideInitiallyMilliSeconds = 300; + private static final int refreshPeriodMilliSeconds = 500; + private boolean running = false; + private boolean finished = false; + private long lastRefreshTime; + private ArrayList refreshTimeQueue; + private Thread updateThread; + private Logger logger = DasLogger.getLogger(DasLogger.SYSTEM_LOG); + private boolean showProgressRate; + private JPanel thePanel; + private boolean componentsInitialized; + private DasCanvasComponent parentComponent; + private DasCanvas parentCanvas; + private static int createComponentCount = 0; + + class MyPanel extends JPanel { + + protected void paintComponent(Graphics g1) { + Graphics2D g2 = (Graphics2D) g1; + + g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); + g2.setColor(new Color(0xdcFFFFFF, true)); + Rectangle rect = g2.getClipBounds(); + if (rect == null) { + g2.fillRect(0, 0, getWidth(), getHeight()); + } else { + g2.fillRect(rect.x, rect.y, rect.width, rect.height); + } + super.paintComponent(g1); + } + } // provides details button, which shows who creates and who consumes the ProgressPanel + final static boolean useDetails = false; + Exception source; + Exception consumer; + + public DasProgressPanel(String label) { + if (useDetails) + source = new Exception(); + + componentsInitialized = false; + this.label = label; + + transferRateFormat = NumberFormatUtil.getDecimalFormat(); + transferRateFormat.setMaximumFractionDigits(2); + maximumTaskPosition = -1; + lastRefreshTime = Integer.MIN_VALUE; + showProgressRate = true; + isCancelled = false; + running = false; + } + + private void details() { + System.err.println("Source: "); + source.printStackTrace(); + System.err.println("Consumer: "); + consumer.printStackTrace(); + String stateString; + if (finished) { + stateString = "finished"; + } else if (running) { + stateString = "running"; + } else if (isCancelled) { + stateString = "cancelled"; + } + System.err.println("State: "); + System.err.println(" running: " + running); + System.err.println(" cancelled: " + isCancelled); + System.err.println(" finished: " + finished); + } + + /** + * returns the JPanel component. + */ + public Component getComponent() { + if (!componentsInitialized) + initComponents(); + return this.thePanel; + } + + public static DasProgressPanel createComponentPanel(DasCanvasComponent component, String initialMessage) { + DasProgressPanel progressPanel = new DasProgressPanel(initialMessage); + progressPanel.parentComponent = component; + return progressPanel; + } + + public static DasProgressPanel createComponentPanel(DasCanvas canvas, String initialMessage) { + DasProgressPanel progressPanel = new DasProgressPanel(initialMessage); + progressPanel.parentCanvas = canvas; + return progressPanel; + } + + + /** Returning true here keeps the progress bar from forcing the whole canvas + * to repaint when the label of the progress bar changes. + */ + public boolean isValidateRoot() { + return true; + } + + public static DasProgressPanel createFramed(String label) { + DasProgressPanel result; + result = new DasProgressPanel(label); + result.jframe = new JFrame("Das Progress Monitor"); + result.initComponents(); + result.jframe.getContentPane().add(result.thePanel); + result.jframe.pack(); + result.jframe.setVisible(false); + result.jframe.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + return result; + } + + public void setLabel(String label) { + this.label = label; + this.labelDirty = true; + if (thePanel != null) + thePanel.repaint(); + } + + public String getLabel() { + return label; + } + + private void initComponents() { + // get a stack trace so we can see what caused this. + if (useDetails) + consumer = new Exception(); + + createComponentCount++; + //System.err.println("createComponentCount="+createComponentCount ); + JPanel mainPanel, buttonPanel; + + taskLabel = new JLabel(); + taskLabel.setOpaque(false); + taskLabel.setFont(new Font("Dialog", 1, 18)); + taskLabel.setHorizontalAlignment(JLabel.CENTER); + taskLabel.setText(label); + taskLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); + + progressMessageLabel = new JLabel() { + + public void paint(Graphics g) { + ((java.awt.Graphics2D) g).setRenderingHint( + java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, + java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + super.paint(g); + } + }; + progressMessageLabel.setOpaque(false); + progressMessageLabel.setFont(new Font("Dialog", 1, 8)); + progressMessageLabel.setHorizontalAlignment(JLabel.CENTER); + progressMessageLabel.setText(" "); + progressMessageLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); + + progressBar = new JProgressBar(); + progressBar.setOpaque(false); + progressBar.setMaximumSize(progressBar.getPreferredSize()); + progressBar.setMinimumSize(progressBar.getPreferredSize()); + progressBar.setAlignmentX(JComponent.CENTER_ALIGNMENT); + + kbLabel = new JLabel(); + kbLabel.setOpaque(false); + kbLabel.setHorizontalAlignment(SwingConstants.CENTER); + kbLabel.setText("0 kb"); + kbLabel.setAlignmentX(0.5F); + kbLabel.setMaximumSize(progressBar.getPreferredSize()); + kbLabel.setMinimumSize(progressBar.getPreferredSize()); + kbLabel.setPreferredSize(progressBar.getPreferredSize()); + kbLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT); + + mainPanel = new JPanel(); + mainPanel.setOpaque(false); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + mainPanel.add(taskLabel); + mainPanel.add(progressMessageLabel); + mainPanel.add(progressBar); + mainPanel.add(kbLabel); + + Border lineBorder = new LineBorder(Color.BLACK, 2); + Border emptyBorder = new EmptyBorder(2, 2, 2, 2); + CompoundBorder border = new CompoundBorder(lineBorder, emptyBorder); + + JButton detailsButton; + if (useDetails) { + detailsButton = new JButton("details"); + detailsButton.setOpaque(false); + detailsButton.setBorder(border); + detailsButton.setFocusPainted(false); + detailsButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + details(); + } + }); + } + + cancelButton = new JButton("cancel"); + cancelButton.setEnabled(false); + cancelButton.setOpaque(false); + cancelButton.setBorder(border); + cancelButton.setFocusPainted(false); + cancelButton.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + cancel(); + } + }); + + buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel.setOpaque(false); + + if (useDetails) + buttonPanel.add(detailsButton); + buttonPanel.add(cancelButton); + + thePanel = new MyPanel(); + thePanel.setOpaque(false); + thePanel.setLayout(new BorderLayout()); + thePanel.add(mainPanel, BorderLayout.CENTER); + thePanel.add(buttonPanel, BorderLayout.SOUTH); + + if (parentComponent != null) { + thePanel.setSize(thePanel.getPreferredSize()); + int x = parentComponent.getColumn().getDMiddle(); + int y = parentComponent.getRow().getDMiddle(); + thePanel.setLocation(x - thePanel.getWidth() / 2, y - thePanel.getHeight() / 2); + ((Container) (parentComponent.getCanvas().getGlassPane())).add(thePanel); + thePanel.setVisible(false); + } else if ( parentCanvas!=null ) { + thePanel.setSize(thePanel.getPreferredSize()); + int x = parentCanvas.getWidth()/2; + int y = parentCanvas.getHeight()/2; + thePanel.setLocation(x - thePanel.getWidth() / 2, y - thePanel.getHeight() / 2); + ((Container) (parentCanvas.getGlassPane())).add(thePanel); + thePanel.setVisible(false); + } + + componentsInitialized = true; + } + + public synchronized void finished() { + running = false; + finished = true; + if (jframe == null) { + setVisible(false); + } else { + jframe.dispose(); + } + } + + /* ProgressMonitor interface */ + public void setTaskProgress(long position) throws IllegalStateException { + if (logger.isLoggable(Level.FINEST)) { + logger.finest("progressPosition=" + position); + } + + if (isCancelled) { + // note that the monitored process should check isCancelled before setTaskProgress, but this is no longer required. + // If this is not done, we throw a IllegalStateException to kill the thread, and the monitored process is killed that way. + logger.fine("setTaskProgress called when isCancelled true. consider checking isCancelled before calling setTaskProgress."); + throw new IllegalStateException("Operation cancelled: developers: consider checking isCancelled before calling setTaskProgress."); + } + + if (!running) { + throw new IllegalStateException("setTaskProgress called before started"); + } + + if (position != 0 && position < currentTaskPosition) { + logger.finest("progress position goes backwards"); + } + + if (!cancelChecked) { + // cancelCheckFailures is used to detect when if the monitored process is not checking cancelled. If it is not, then we + // disable the cancel button. Note the cancel() method can still be called from elsewhere, killing the process. + cancelCheckFailures++; + } + cancelChecked = false; // reset for next time, isCancelled will set true. + + if (maximumTaskPosition == 0) { + throw new IllegalArgumentException("when taskSize is 0, setTaskProgress must not be called."); + } + + currentTaskPosition = position; + + long elapsedTimeMs = System.currentTimeMillis() - taskStartedTime; + if (elapsedTimeMs > hideInitiallyMilliSeconds && !isVisible()) { + setVisible(true); + } + /* long tnow; + if ( (tnow=System.currentTimeMillis()) - lastRefreshTime > 30 ) { + updateUIComponents(); + if (Toolkit.getDefaultToolkit().getSystemEventQueue().isDispatchThread()) { + paintImmediately(0, 0, getWidth(), getHeight()); + } + else { + repaint(); + } + lastRefreshTime= tnow; + } */ + } + + private void startUpdateThread() { + Runnable run = new Runnable() { + + public void run() { + while (!DasProgressPanel.this.finished) { + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + updateUIComponents(); + thePanel.repaint(); + } + }); + Thread.sleep(refreshPeriodMilliSeconds); + } catch (InterruptedException ex) { + Logger.getLogger(DasProgressPanel.class.getName()).log(Level.SEVERE, null, ex); + } catch (InvocationTargetException ex) { + Logger.getLogger(DasProgressPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + }; + updateThread = new Thread(run, "progressMonitorUpdateThread"); + updateThread.start(); + } + + private void updateUIComponents() { + long elapsedTimeMs = System.currentTimeMillis() - taskStartedTime; + + long kb = currentTaskPosition; + + if (maximumTaskPosition == -1) { + progressBar.setIndeterminate(true); + } else { + progressBar.setIndeterminate(false); + } + if (maximumTaskPosition > 0) { + progressBar.setValue((int) (kb * 100 / (maximumTaskPosition))); + } else { + progressBar.setValue((int) kb % 100); + } + + String bytesReadLabel; + if (maximumTaskPosition > 0) { + bytesReadLabel = "" + kb + "/" + maximumTaskPosition + ""; + } else { + bytesReadLabel = "" + kb + ""; + } + + if (progressMessageDirty) { + if (progressMessageString.length() > 33) { + int n = progressMessageString.length(); + progressMessageString = progressMessageString.substring(0, 10) + "..." + progressMessageString.substring(n - 22, n); + } + progressMessageLabel.setText(progressMessageString); + progressMessageDirty = false; + } + + if (labelDirty) { + taskLabel.setText(label); + labelDirty = false; + } + + if (showProgressRate && elapsedTimeMs > 1000 && transferRateString != null) { + double transferRate = ((double) currentTaskPosition * 1000) / (elapsedTimeMs); + kbLabel.setText(bytesReadLabel + " " + transferRateString); + } else { + kbLabel.setText(bytesReadLabel); + } + + boolean cancelEnabled = cancelCheckFailures < 2; + if (cancelEnabled != cancelButton.isEnabled()) { + cancelButton.setEnabled(cancelEnabled); + } + } + + @Deprecated + public void setAdditionalInfo(String s) { + transferRateString = s; + } + + public long getTaskProgress() { + return currentTaskPosition; + } + + public long getTaskSize() { + return maximumTaskPosition; + } + + public void setTaskSize(long taskSize) { + if (taskSize < -1) { + throw new IllegalArgumentException("taskSize must be positive, -1, or 0, not " + taskSize); + } else { + if (componentsInitialized) + progressBar.setIndeterminate(false); + } + maximumTaskPosition = taskSize; + } + + public synchronized void setVisible(boolean visible) { + if (!componentsInitialized && !visible) + return; + if (!componentsInitialized && !finished) + initComponents(); + if (thePanel != null) + thePanel.setVisible(visible); + if (this.jframe != null) + this.jframe.setVisible(visible); + + if (visible) { + startUpdateThread(); + } + } + + public boolean isVisible() { + return (!componentsInitialized || thePanel.isVisible()); + } + + public void started() { + taskStartedTime = System.currentTimeMillis(); + running = true; + + if (hideInitiallyMilliSeconds > 0) { + setVisible(false); + new Thread(new Runnable() { + + public void run() { + try { + Thread.sleep(hideInitiallyMilliSeconds); + } catch (InterruptedException e) { + } + ; + if (running) { + logger.fine("hide time=" + (System.currentTimeMillis() - taskStartedTime)); + setVisible(true); + } + } + }, "progressPanelUpdateThread").start(); + } else { + setVisible(true); + } + + // cancel() might have been called before we got here, so check it. + if (isCancelled) + return; + if (maximumTaskPosition > 0) + setTaskProgress(0); + } + + public void cancel() { + isCancelled = true; + finished(); + } + + public boolean isCancelled() { + cancelCheckFailures = 0; + cancelChecked = true; + return isCancelled; + } + + public Exception getSource() { + return source; + } + + public Exception getConsumer() { + return consumer; + } + + /** + * Setter for property showProgressRate. + * @param showProgressRate New value of property showProgressRate. + */ + public void setShowProgressRate(boolean showProgressRate) { + this.showProgressRate = showProgressRate; + } + + public String toString() { + if (isCancelled) { + return "cancelled"; + } else if (finished) { + return "finished"; + } else if (running) { + return "" + currentTaskPosition + " of " + this.maximumTaskPosition; + } else { + return "waiting for start"; + } + } + + public void setProgressMessage(String message) { + this.progressMessageString = message; + this.progressMessageDirty = true; + } + + public boolean isStarted() { + return running; + } + + public boolean isFinished() { + return finished; + } +} diff --git a/dasCore/src/main/java/org/das2/components/DasTimeRangeSelector.java b/dasCore/src/main/java/org/das2/components/DasTimeRangeSelector.java new file mode 100755 index 000000000..5a5f173b4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DasTimeRangeSelector.java @@ -0,0 +1,456 @@ +/* File: DasTimeRangeSelector.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.DatumRangeUtil; +import org.das2.util.DasExceptionHandler; +/** + * + * @author jbf + */ +import org.das2.datum.TimeUtil; +import org.das2.event.TimeRangeSelectionEvent; +import org.das2.event.TimeRangeSelectionListener; +import org.das2.system.DasLogger; +import java.awt.CardLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; + +import javax.swing.event.EventListenerList; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.text.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.prefs.*; +import javax.swing.*; + +public class DasTimeRangeSelector extends JPanel implements TimeRangeSelectionListener { + public static final String PROP_RANGE = "range"; + + private DatumRange range= null; + + JTextField idStart= null; + JTextField idStop= null; + JButton viewButton= null; + JPanel startStopModePane=null; + CardLayout cardLayout= null; + + boolean updateRangeString= false; // true indicates use formatted range string in start time cell. + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + /** Action that is associated with the previous button. + * Access is given to subclasses so that other widgets can be associated + * with this action (Popup menu, etc). + */ + protected final Action previousAction = new AbstractAction("<<") { + public void actionPerformed(ActionEvent e) { + fireTimeRangeSelectedPrevious(); + } + }; + + /** Action that is associated with the next button. + * Access is given to subclasses so that other widgets can be associated + * with this action (Popup menu, etc). + */ + protected final Action nextAction = new AbstractAction(">>") { + public void actionPerformed(ActionEvent e) { + fireTimeRangeSelectedNext(); + } + }; + + protected final Action rangeAction = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + fireTimeRangeSelected(); + } + }; + + private boolean favoritesEnabled= false; + + private List favoritesList= null; + private JPopupMenu favoritesMenu= null; + + private final int FAVORITES_LIST_SIZE= 5; + + private String favoritesGroup; + + private JButton favoritesButton; + + private JPanel timesPane; + + private JComboBox rangeComboBox; + + private boolean pref; + + /** Creates a new instance of DasTimeRangeSelector */ + public DasTimeRangeSelector() { + super(); + pref= !org.das2.DasApplication.getDefaultApplication().isApplet(); + if ( pref ) { + updateRangeString= Preferences.userNodeForPackage(this.getClass()).getBoolean("updateRangeString", false); + } else { + updateRangeString= false; + } + buildComponents(); + } + + private Action getModeAction() { + return new AbstractAction("mode") { + public void actionPerformed( ActionEvent e ) { + updateRangeString= !updateRangeString; + if ( pref ) Preferences.userNodeForPackage(this.getClass()).putBoolean("updateRangeString", updateRangeString ); + revalidateUpdateMode(); + update(); + } + }; + } + + private void revalidateUpdateMode() { + if ( updateRangeString ) { + //idStop.setColumns(8); + idStart.setColumns(28); + idStop.setVisible(false); + viewButton.setVisible(true); + //cardLayout.show( timesPane, "range" ); + } else { + idStart.setColumns(18); + // idStop.setColumns(18); + idStop.setVisible(true); + //cardLayout.show( timesPane, "startStop" ); + } + startStopModePane.revalidate(); + } + + private void buildComponents() { + this.setLayout(new FlowLayout()); + + JButton b= new JButton(); + b.setAction(previousAction); + b.setActionCommand("previous"); + b.setToolTipText("Scan back in time"); + this.add(b); + + startStopModePane= new JPanel(new FlowLayout()); + + cardLayout= new CardLayout(); + timesPane= new JPanel( cardLayout ); + + JPanel startStopPane2= new JPanel(new FlowLayout()); + + idStart= new JTextField(18); + idStart.setAction(rangeAction); + idStart.setActionCommand("startTime"); + startStopPane2.add(idStart); + + idStop= new JTextField(18); + idStop.addActionListener(rangeAction); + idStop.setActionCommand("endTime"); + startStopPane2.add(idStop); + + timesPane.add( startStopPane2, "startStop" ); + + startStopModePane.add( timesPane ); + favoritesButton= new JButton("v"); + favoritesButton.setToolTipText("recently entries times"); + favoritesButton.setPreferredSize(new Dimension( 20,20 ) ); + favoritesButton.setVisible(false); + startStopModePane.add(favoritesButton); + + viewButton= new JButton(getModeAction()); + viewButton.setToolTipText("input mode: start/end vs time range string"); + viewButton.setPreferredSize(new Dimension( 20,20 ) ); + startStopModePane.add(viewButton); + + this.add(startStopModePane); + + b= new JButton(); + b.setAction(nextAction); + b.setActionCommand("next"); + b.setToolTipText("Scan forward in time"); + this.add(b); + + revalidateUpdateMode(); + } + + public DasTimeRangeSelector(Datum startTime, Datum endTime) { + this(new DatumRange( startTime, endTime )); + } + + public DasTimeRangeSelector( DatumRange range ) { + this(); + this.range= range; + update(); + } + + private void parseRange() { + boolean updateRangeString0= updateRangeString; + if ( idStop.getText().equals("") ) { + DatumRange dr; + try { + String rangeString= idStart.getText(); + if ( rangeString.equals("") ) { + rangeString= (String)rangeComboBox.getEditor().getItem(); + } + dr= DatumRangeUtil.parseTimeRange(rangeString); + DatumRange oldRange= range; + range= dr; + updateRangeString= true; + firePropertyChange( PROP_RANGE, oldRange, range ); + + } catch ( ParseException e ) { + DasExceptionHandler.handle(e); + } + } else { + updateRangeString= false; + try { + Datum s1= TimeUtil.create(idStart.getText()); + Datum s2= TimeUtil.create(idStop.getText()); + DatumRange oldRange= range; + range= new DatumRange(s1,s2); + firePropertyChange( PROP_RANGE, oldRange ,range); + } catch ( ParseException e ) { + DasExceptionHandler.handle(e); + } + } + if ( updateRangeString!=updateRangeString0 ) { + if (pref) Preferences.userNodeForPackage(getClass()).putBoolean("updateRangeString", updateRangeString ); + } + return; + } + + private void refreshFavorites() { + favoritesMenu.removeAll(); + + for ( Iterator i= favoritesList.iterator(); i.hasNext(); ) { + final String fav= (String) i.next(); + Action favAction= new AbstractAction( fav ) { + public void actionPerformed( ActionEvent e ) { + DasTimeRangeSelector.this.setRange( DatumRangeUtil.parseTimeRangeValid(fav) ); + fireTimeRangeSelected(new TimeRangeSelectionEvent(this,range)); + } + }; + favoritesMenu.add( favAction ); + } + } + + private void buildFavorites( ) { + String favorites=""; + if ( pref ) favorites= Preferences.userNodeForPackage(getClass()).get( "timeRangeSelector.favorites."+favoritesGroup, "" ); + String[] ss= favorites.split("\\|\\|"); + favoritesList= new ArrayList(); + for ( int i=0; i=0; i-=2) { + if (listeners[i]==TimeRangeSelectionListener.class) { + String logmsg= "fire event: "+this.getClass().getName()+"-->"+listeners[i+1].getClass().getName()+" "+event; + DasLogger.getLogger( DasLogger.GUI_LOG ).fine(logmsg); + ((org.das2.event.TimeRangeSelectionListener)listeners[i+1]).timeRangeSelected(event); + ((TimeRangeSelectionListener)listeners[i+1]).timeRangeSelected(event); + } + } + } + + public Dimension getMaximumSize() { + return super.getPreferredSize(); + } + + public Dimension getMinimumSize() { + return super.getPreferredSize(); + } + + private void saveFavorites() { + if ( favoritesList.size()==0 ) return; + StringBuffer favorites= new StringBuffer( (String)favoritesList.get(0) ); + for ( int i=1; i 0 ? nrow : 1; + return dataPoints.size(); + } + + public Object getValueAt(int i, int j) { + DataPoint x = (DataPoint) dataPoints.get(i); + if (j < x.data.length) { + Datum d = x.get(j); + DatumFormatter format = d.getFormatter(); + return format.format(d, unitsArray[j]); + } else { + Object o = x.getPlane(planesArray[j]); + if (o instanceof Datum) { + Datum d = (Datum) o; + return d.getFormatter().format(d, unitsArray[j]); + } else { + return (String) o; + } + } + } + } + + public void deleteRow(int row) { + dataPoints.remove(row); + modified = true; + updateClients(); + updateStatus(); + fireDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + } + + class MyDataSetDescriptor extends DataSetDescriptor { + + MyDataSetDescriptor() { + super(null); + } + + public void fireUpdate() { + fireDataSetUpdateEvent(new DataSetUpdateEvent((Object) this)); + } + + protected DataSet getDataSetImpl(Datum s1, Datum s2, Datum s3, ProgressMonitor monitor) throws DasException { + if (dataPoints.size() == 0) { + return null; + } else { + VectorDataSetBuilder builder = new VectorDataSetBuilder(unitsArray[0], unitsArray[1]); + for (int irow = 0; irow < dataPoints.size(); irow++) { + DataPoint dp = (DataPoint) dataPoints.get(irow); + builder.insertY(dp.get(0), dp.get(1)); + } + return builder.toVectorDataSet(); + } + } + + public Units getXUnits() { + return unitsArray[0]; + } + } + private MyDataSetDescriptor dataSetDescriptor; + + /** + * @deprecated use getDataSet() and getSelectedDataSet() instead + */ + public DataSetDescriptor getDataSetDescriptor() { + if (dataSetDescriptor == null) { + dataSetDescriptor = new MyDataSetDescriptor(); + } + return dataSetDescriptor; + } + + /** + * returns a data set of the table data. + */ + public VectorDataSet getDataSet() { + if (dataPoints.size() == 0) { + return null; + } else { + VectorDataSetBuilder builder = new VectorDataSetBuilder(unitsArray[0], unitsArray[1]); + for (int i = 2; i < planesArray.length; i++) { + if (unitsArray[i] != null) { + builder.addPlane(planesArray[i], unitsArray[i]); + } + } + for (int irow = 0; irow < dataPoints.size(); irow++) { + DataPoint dp = (DataPoint) dataPoints.get(irow); + builder.insertY(dp.get(0), dp.get(1)); + for (int i = 2; i < planesArray.length; i++) { + if (unitsArray[i] != null) { + Object s= dp.getPlane(planesArray[i]); + if ( s instanceof Datum ) { + builder.insertY(dp.get(0), (Datum)s, planesArray[i]); + } else { + builder.insertY(dp.get(0), ((EnumerationUnits)unitsArray[i]).createDatum(s),planesArray[i]); + } + } + } + } + if (this.xTagWidth != null) { + builder.setProperty("xTagWidth", xTagWidth); + } + return builder.toVectorDataSet(); + } + } + + /** + * returns a data set of the selected table data + */ + public VectorDataSet getSelectedDataSet() { + int[] selectedRows = table.getSelectedRows(); + if (selectedRows.length == 0) { + return null; + } else { + VectorDataSetBuilder builder = new VectorDataSetBuilder(unitsArray[0], unitsArray[1]); + for (int j = 2; j < planesArray.length; j++) { + builder.addPlane(planesArray[j], unitsArray[j]); + } + for (int i = 0; i < selectedRows.length; i++) { + int irow = selectedRows[i]; + DataPoint dp = (DataPoint) dataPoints.get(irow); + builder.insertY(dp.get(0), dp.get(1)); + Map map = dp.planes; + for (int j = 2; j < planesArray.length; j++) { + Object s= dp.getPlane(planesArray[j]); + if ( s instanceof Datum ) { + builder.insertY(dp.get(0).doubleValue(unitsArray[0]), + ((Datum) dp.getPlane(planesArray[j])).doubleValue(unitsArray[j]), + planesArray[j]); + } else { + builder.insertY(dp.get(0).doubleValue(unitsArray[0]), + ((EnumerationUnits)unitsArray[j]).createDatum(dp.getPlane(planesArray[j])).doubleValue(unitsArray[j]), + planesArray[j]); + } + } + } + if (this.xTagWidth != null) { + builder.setProperty("xTagWidth", xTagWidth); + } + return builder.toVectorDataSet(); + } + } + + /** + * Selects all the points within the DatumRange + */ + public void select(DatumRange xrange, DatumRange yrange) { + List selectMe = new ArrayList(); + for (int i = 0; i < dataPoints.size(); i++) { + DataPoint p = (DataPoint) dataPoints.get(i); + if (xrange.contains(p.data[0]) && yrange.contains(p.data[1])) { + selectMe.add(new Integer(i)); + } + } + table.getSelectionModel().clearSelection(); + for (int i = 0; i < selectMe.size(); i++) { + int iselect = ((Integer) selectMe.get(i)).intValue(); + table.getSelectionModel().addSelectionInterval(iselect, iselect); + } + } + + public void saveToFile(File file) throws IOException { + FileOutputStream out = new FileOutputStream(file); + BufferedWriter r = new BufferedWriter(new OutputStreamWriter(out)); + + StringBuffer header = new StringBuffer(); + header.append("## "); + for (int j = 0; j < planesArray.length; j++) { + header.append(myTableModel.getColumnName(j) + "\t"); + } + r.write(header.toString()); + r.newLine(); + for (int i = 0; i < dataPoints.size(); i++) { + DataPoint x = (DataPoint) dataPoints.get(i); + StringBuffer s = new StringBuffer(); + for (int j = 0; j < 2; j++) { + DatumFormatter formatter = x.get(j).getFormatter(); + s.append(formatter.format(x.get(j), unitsArray[j]) + "\t"); + } + for (int j = 2; j < planesArray.length; j++) { + Object o = x.getPlane(planesArray[j]); + if (unitsArray[j] == null) { + if (o == null) { + o = ""; + } + s.append("\"" + o + "\"\t"); + } else { + if ( o instanceof Datum ) { + Datum d = (Datum) o; + DatumFormatter f = d.getFormatter(); + s.append(f.format(d, unitsArray[j]) + "\t"); + } else { + s.append( o.toString() + "\t" ); + } + } + } + r.write(s.toString()); + r.newLine(); + prefs.put("components.DataPointRecorder.lastFileSave", file.toString()); + prefs.put("components.DataPointRecorder.lastFileLoad", file.toString()); + } + r.close(); + modified = false; + updateStatus(); + + } + + public void loadFromFile(File file) throws IOException { + + ProgressMonitor mon= new NullProgressMonitor(); + + try { + active = false; + + FileInputStream in = new FileInputStream(file); + BufferedReader r = new BufferedReader(new InputStreamReader(in)); + + int lineCount = 0; + for (String line = r.readLine(); line != null; line = r.readLine()) { + lineCount++; + } + r.close(); + + in = new FileInputStream(file); + r = new BufferedReader(new InputStreamReader(in)); + + dataPoints.clear(); + String[] planesArray = null; + Units[] unitsArray = null; + + Datum x; + Datum y; + Map planes;// = new LinkedHashMap(); + + if (lineCount > 500) { + mon = DasProgressPanel.createFramed("reading file"); + } else { + mon = new NullProgressMonitor(); + } + + // tabs detected in file. + boolean hasTabs= false; + String delim= "\t"; + + mon.setTaskSize(lineCount); + mon.started(); + int linenum = 0; + for (String line = r.readLine(); line != null; line = r.readLine()) { + linenum++; + if (mon.isCancelled()) { + break; + } + mon.setTaskProgress(linenum); + if (line.startsWith("## ")) { + if ( unitsArray!=null ) continue; + line = line.substring(3); + if ( line.indexOf("\t")==-1 ) delim= "\\s+"; + String[] s = line.trim().split(delim); + Pattern ordinalPattern= Pattern.compile("(.+)\\((.*)\\(ordinal\\)\\)"); + Pattern p = Pattern.compile("(.+)\\((.*)\\)"); + planesArray = new String[s.length]; + unitsArray = new Units[s.length]; + for (int i = 0; i < s.length; i++) { + Matcher m= ordinalPattern.matcher(s[i]); + if ( m.matches() ) { + planesArray[i] = m.group(1).trim(); + String sunits= m.group(2).trim(); + try { + unitsArray[i] = Units.lookupUnits(sunits); + } catch ( IllegalArgumentException ex ) { + unitsArray[i] = new EnumerationUnits(sunits); + } + continue; + } + m = p.matcher(s[i]); + if (m.matches()) { + //System.err.printf("%d %s\n", i, m.group(1) ); + planesArray[i] = m.group(1).trim(); + String sunits= m.group(2).trim(); + unitsArray[i] = Units.lookupUnits(sunits); + } else { + planesArray[i] = s[i]; + unitsArray[i] = null; + } + } + continue; + } + String[] s = line.trim().split(delim); + if (unitsArray == null) { + // support for legacy files + unitsArray = new Units[s.length]; + for (int i = 0; i < s.length; i++) { + String field= s[i].trim(); + if (field.charAt(0) == '"') { + unitsArray[i] = null; + } else if (TimeUtil.isValidTime(field)) { + unitsArray[i] = Units.us2000; + } else { + unitsArray[i] = DatumUtil.parseValid(field).getUnits(); + } + } + planesArray = new String[]{"X", "Y", "comment"}; + } + + try { + + x = unitsArray[0].parse(s[0]); + y = unitsArray[1].parse(s[1]); + + planes = new LinkedHashMap(); + + for (int i = 2; i < s.length; i++) { + String field= s[i].trim(); + if (unitsArray[i] == null) { + Pattern p = Pattern.compile("\"(.*)\".*"); + Matcher m = p.matcher(field); + if (m.matches()) { + planes.put(planesArray[i], m.group(1)); + } else { + throw new ParseException("parse error, expected \"\"", 0); + } + } else { + try { + planes.put(planesArray[i], unitsArray[i].parse(field)); + } catch (ParseException e) { + if ( unitsArray[i] instanceof EnumerationUnits ) { + EnumerationUnits u= (EnumerationUnits)unitsArray[i]; + u.createDatum(field); + planes.put(planesArray[i], unitsArray[i].parse(field)); + } else { + throw new RuntimeException(e); + } + } + } + } + + DataPointSelectionEvent e; + e = new DataPointSelectionEvent(this, x, y, planes); + dataPointSelected(e); + } catch (ParseException e) { + throw new RuntimeException(e); + } + + + } + } finally { + + mon.finished(); + active = true; + modified = false; + + updateStatus(); + + updateClients(); + + prefs.put("components.DataPointRecorder.lastFileLoad", file.toString()); + fireDataSetUpdateListenerDataSetUpdated( + new DataSetUpdateEvent(this)); + + //table.getColumnModel().getColumn(0).setPreferredWidth(200); + + table.getColumnModel(); + table.repaint(); + } + + } + + private class MyMouseAdapter extends MouseAdapter { + + JPopupMenu popup; + JMenuItem menuItem; + final JTable parent; + + MyMouseAdapter(final JTable parent) { + this.parent = parent; + popup = new JPopupMenu("Options"); + menuItem = new JMenuItem("Delete Row(s)"); + menuItem.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + int[] selectedRows = parent.getSelectedRows(); + for (int i = 0; i < selectedRows.length; i++) { + deleteRow(selectedRows[i]); + for (int j = i + 1; j < selectedRows.length; j++) { + selectedRows[j]--; // indeces change because of deletion + } + } + } + }); + popup.add(menuItem); + } + MouseAdapter mm; + + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON3) { + int rowCount = parent.getSelectedRows().length; + menuItem.setText("Delete " + rowCount + " Row" + (rowCount != 1 ? "s" : "")); + popup.show(e.getComponent(), e.getX(), e.getY()); + } + } + + public void mouseReleased(MouseEvent e) { + // hide popup + } + } + + private Action getSaveAsAction() { + return new AbstractAction("Save As...") { + + public void actionPerformed(ActionEvent e) { + JFileChooser jj = new JFileChooser(); + String lastFileString = prefs.get("components.DataPointRecorder.lastFileSave", ""); + File lastFile = null; + if (lastFileString != "") { + lastFile = new File(lastFileString); + jj.setSelectedFile(lastFile); + } + + int status = jj.showSaveDialog(DataPointRecorder.this); + if (status == JFileChooser.APPROVE_OPTION) { + try { + DataPointRecorder.this.saveFile = jj.getSelectedFile(); + saveToFile(saveFile); + //messageLabel.setText("saved data to "+saveFile); + } catch (IOException e1) { + DasExceptionHandler.handle(e1); + } + + } + } + }; + } + + private Action getSaveAction() { + return new AbstractAction("Save") { + + public void actionPerformed(ActionEvent e) { + if (saveFile == null) { + getSaveAsAction().actionPerformed(e); + } else { + try { + saveToFile(saveFile); + } catch (IOException ex) { + DasExceptionHandler.handle(ex); + } + + } + } + }; + } + + private Action getLoadAction() { + return new AbstractAction("Open...") { + + public void actionPerformed(ActionEvent e) { + if (checkModified(e)) { + JFileChooser jj = new JFileChooser(); + String lastFileString = prefs.get("components.DataPointRecorder.lastFileLoad", ""); + File lastFile = null; + if (lastFileString != "") { + lastFile = new File(lastFileString); + jj.setSelectedFile(lastFile); + } + + int status = jj.showOpenDialog(DataPointRecorder.this); + if (status == JFileChooser.APPROVE_OPTION) { + final File loadFile = jj.getSelectedFile(); + prefs.put("components.DataPointRecorder.lastFileLoad", loadFile.toString()); + Runnable run = new Runnable() { + + public void run() { + try { + loadFromFile(loadFile); + saveFile = + loadFile; + updateStatus(); + } catch (IOException e) { + + DasExceptionHandler.handle(e); + } + + } + }; + new Thread(run).start(); + } + + } + } + }; + } + + /** + * returns true if the operation should continue, false + * if not, meaning the user pressed cancel. + */ + private boolean checkModified(ActionEvent e) { + if (modified) { + int n = JOptionPane.showConfirmDialog( + DataPointRecorder.this, + "Current work has not been saved.\n Save first?", + "Save work first", + JOptionPane.YES_NO_CANCEL_OPTION); + if (n == JOptionPane.YES_OPTION) { + getSaveAction().actionPerformed(e); + } + + return (n != JOptionPane.CANCEL_OPTION); + } else { + return true; + } + + } + + private Action getNewAction() { + return new AbstractAction("New") { + + public void actionPerformed(ActionEvent e) { + if (checkModified(e)) { + dataPoints.removeAll(dataPoints); + saveFile = + null; + updateStatus(); + updateClients(); + } + + } + }; + } + + private Action getPropertiesAction() { + return new AbstractAction("Properties") { + + public void actionPerformed(ActionEvent e) { + new PropertyEditor(DataPointRecorder.this).showDialog(DataPointRecorder.this); + } + }; + } + + private Action getUpdateAction() { + return new AbstractAction("Update") { + + public void actionPerformed(ActionEvent e) { + // what if no one is listening? + if (dataSetDescriptor != null) { + dataSetDescriptor.fireUpdate(); + } + + fireDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + fireSelectedDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + } + }; + } + + /** Creates a new instance of DataPointRecorder */ + public DataPointRecorder() { + super(); + dataPoints = + new ArrayList(); + myTableModel = + new MyTableModel(); + this.setLayout(new BorderLayout()); + + JMenuBar menuBar = new JMenuBar(); + JMenu fileMenu = new JMenu("File"); + fileMenu.add(new JMenuItem(getNewAction())); + fileMenu.add(new JMenuItem(getLoadAction())); + fileMenu.add(new JMenuItem(getSaveAction())); + fileMenu.add(new JMenuItem(getSaveAsAction())); + menuBar.add(fileMenu); + + JMenu editMenu = new JMenu("Edit"); + editMenu.add(new JMenuItem(getPropertiesAction())); + menuBar.add(editMenu); + + this.add(menuBar, BorderLayout.NORTH); + + planesArray = + new String[]{"X", "Y"}; + unitsArray = + new Units[]{null, null}; + + table = + new JTable(myTableModel); + + table.getTableHeader().setReorderingAllowed(true); + + table.setRowSelectionAllowed(true); + table.addMouseListener(new DataPointRecorder.MyMouseAdapter(table)); + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + + public void valueChanged(ListSelectionEvent e) { + fireSelectedDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(DataPointRecorder.this)); + int selected = table.getSelectedRow(); // we could do a better job here + if (selected > -1) { + DataPoint dp = (DataPoint) dataPoints.get(selected); + DataPointSelectionEvent e2 = new DataPointSelectionEvent(DataPointRecorder.this, dp.get(0), dp.get(1)); + fireDataPointSelectionListenerDataPointSelected(e2); + } + + } + }); + + scrollPane = + new JScrollPane(table); + this.add(scrollPane, BorderLayout.CENTER); + + JPanel controlStatusPanel = new JPanel(); + controlStatusPanel.setLayout(new BoxLayout(controlStatusPanel, BoxLayout.Y_AXIS)); + + final JPanel controlPanel = new JPanel(); + controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.X_AXIS)); + + JButton updateButton = new JButton(getUpdateAction()); + + controlPanel.add(updateButton); + + messageLabel = + new JLabel("ready"); + messageLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT); + + controlStatusPanel.add(messageLabel); + + controlPanel.setAlignmentX(JLabel.LEFT_ALIGNMENT); + controlStatusPanel.add(controlPanel); + + this.add(controlStatusPanel, BorderLayout.SOUTH); + } + + public static DataPointRecorder createFramed() { + DataPointRecorder result; + JFrame frame = new JFrame("Data Point Recorder"); + result = + new DataPointRecorder(); + frame.getContentPane().add(result); + frame.pack(); + frame.setVisible(true); + frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + return result; + } + + /** + * update fires off the TableDataChanged, and sets the current selected + * row if necessary. + */ + private void updateClients() { + if (active) { + myTableModel.fireTableDataChanged(); + if (selectRow != -1 && table.getRowCount()>selectRow ) { + table.setRowSelectionInterval(selectRow, selectRow); + table.scrollRectToVisible(table.getCellRect(selectRow, 0, true)); + selectRow = + -1; + } + + } + } + + private void updateStatus() { + Runnable run= () -> { + String statusString = (saveFile == null ? "" : (String.valueOf(saveFile) + " ")) + + (modified ? "(modified)" : ""); + messageLabel.setText(statusString); + }; + SwingUtilities.invokeLater(run); + } + + private void insertInternal(DataPoint newPoint) { + int newSelect; + if (sorted) { + int index = Collections.binarySearch(dataPoints, newPoint); + if (index < 0) { + dataPoints.add(~index, newPoint); + newSelect = + ~index; + } else { + dataPoints.set(index, newPoint); + newSelect = + index; + } + + } else { + dataPoints.add(newPoint); + newSelect = + dataPoints.size() - 1; + } + + selectRow = newSelect; + modified = + true; + updateStatus(); + } + + public void addDataPoint(Datum x, Datum y, Map planes) { + if ( planes==null ) planes= new HashMap(); + if (dataPoints.size() == 0) { + Datum[] datums = new Datum[]{x, y}; + unitsArray = + new Units[2 + planes.size()]; + unitsArray[0] = x.getUnits(); + unitsArray[1] = y.getUnits(); + planesArray = + new String[2 + planes.size()]; + planesArray[0] = "x"; + planesArray[1] = "y"; + int index = 2; + for (Iterator i = planes.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + planesArray[index] = String.valueOf(key); + Object value = planes.get(key); + if (value instanceof String) { + unitsArray[index] = EnumerationUnits.create("dpr_"+planesArray[index]); + } else { + unitsArray[index] = ((Datum) value).getUnits(); + } + + index++; + } + + //myTableModel.fireTableStructureChanged(); + } + + if (!x.getUnits().isConvertableTo(unitsArray[0])) { + throw new RuntimeException("inconvertable units: " + x + " expected " + unitsArray[0]); + } + + if (!y.getUnits().isConvertableTo(unitsArray[1])) { + throw new RuntimeException("inconvertable units: " + y + " expected " + unitsArray[1]); + } + + insertInternal(new DataPoint(x, y, new LinkedHashMap(planes))); + if (active) { + Runnable run= new Runnable() { + public void run() { + myTableModel.fireTableStructureChanged(); + fireDataSetUpdateListenerDataSetUpdated(new DataSetUpdateEvent(this)); + } + }; + SwingUtilities.invokeLater(run); + + } + + } + + public void appendDataSet(VectorDataSet ds) { + + String comment; + + Map planesMap = new LinkedHashMap(); + + if (ds.getProperty("comment") != null) { + planesMap.put("comment", ds.getProperty("comment")); + } + + if (ds.getProperty("xTagWidth") != null) { + DataPointRecorder.this.xTagWidth = (Datum) ds.getProperty("xTagWidth"); + } + + String[] planes = ds.getPlaneIds(); + + for (int i = 0; i < + ds.getXLength(); i++) { + for (int j = 0; j < + planes.length; j++) { + if (!planes[j].equals("")) { + planesMap.put(planes[j], ((VectorDataSet) ds.getPlanarView(planes[j])).getDatum(i)); + } + + } + addDataPoint(ds.getXTagDatum(i), ds.getDatum(i), planesMap); + } + + updateClients(); + + } + + /** + * this adds all the points in the DataSet to the list. This will also check the dataset for the special + * property "comment" and add it as a comment. + */ + public DataSetUpdateListener getAppendDataSetUpListener() { + return new DataSetUpdateListener() { + + public void dataSetUpdated(DataSetUpdateEvent e) { + VectorDataSet ds = (VectorDataSet) e.getDataSet(); + if (ds == null) { + throw new RuntimeException("not supported, I need the DataSet in the update event"); + } else { + appendDataSet((VectorDataSet) e.getDataSet()); + } + + } + }; + } + + @SuppressWarnings("deprecation") + public void dataPointSelected(org.das2.event.DataPointSelectionEvent e) { + String comment = ""; + Map planesMap; + + if (e instanceof CommentDataPointSelectionEvent) { + comment = ((CommentDataPointSelectionEvent) e).getComment(); + planesMap = + new LinkedHashMap(); + planesMap.put("comment", comment); + } else { + String[] x = e.getPlaneIds(); + planesMap = + new LinkedHashMap(); + for (int i = 0; i < + x.length; i++) { + planesMap.put(x[i], e.getPlane(x[i])); + } + + } + + // if a point exists within xTagWidth of the point, then have this point replace + Datum x = e.getX(); + if (snapToGrid && xTagWidth != null && dataPoints.size() > 0) { + DataSet ds = getDataSet(); + int i = DataSetUtil.closestColumn(ds, e.getX()); + Datum diff = e.getX().subtract(ds.getXTagDatum(i)); + if (Math.abs(diff.divide(xTagWidth).doubleValue(Units.dimensionless)) < 0.5) { + x = ds.getXTagDatum(i); + } + + } + addDataPoint(x, e.getY(), planesMap); + updateClients(); + } + private javax.swing.event.EventListenerList listenerList = null; + + public synchronized void addDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + if (listenerList == null) { + listenerList = new javax.swing.event.EventListenerList(); + } + + + + + listenerList.add(org.das2.dataset.DataSetUpdateListener.class, listener); + } + + public synchronized void removeDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + listenerList.remove(org.das2.dataset.DataSetUpdateListener.class, listener); + } + + private void fireDataSetUpdateListenerDataSetUpdated(org.das2.dataset.DataSetUpdateEvent event) { + if (listenerList == null) { + return; + } + + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= + 0; i -= + 2) { + if (listeners[i] == org.das2.dataset.DataSetUpdateListener.class) { + ((org.das2.dataset.DataSetUpdateListener) listeners[i + 1]).dataSetUpdated(event); + } + } + + } + private javax.swing.event.EventListenerList selectedListenerList = null; + + public synchronized void addSelectedDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + if (selectedListenerList == null) { + selectedListenerList = new javax.swing.event.EventListenerList(); + } + + + + + selectedListenerList.add(org.das2.dataset.DataSetUpdateListener.class, listener); + } + + public synchronized void removeSelectedDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + selectedListenerList.remove(org.das2.dataset.DataSetUpdateListener.class, listener); + } + + private void fireSelectedDataSetUpdateListenerDataSetUpdated(org.das2.dataset.DataSetUpdateEvent event) { + if (selectedListenerList == null) { + return; + } + + Object[] listeners = selectedListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= + 0; i -= + 2) { + if (listeners[i] == org.das2.dataset.DataSetUpdateListener.class) { + ((org.das2.dataset.DataSetUpdateListener) listeners[i + 1]).dataSetUpdated(event); + } + } + + } + /** + * Holds value of property sorted. + */ + private boolean sorted = true; + + /** + * Getter for property sorted. + * @return Value of property sorted. + */ + public boolean isSorted() { + + return this.sorted; + } + + /** + * Setter for property sorted. + * @param sorted New value of property sorted. + */ + public void setSorted(boolean sorted) { + + this.sorted = sorted; + } + + /** + * Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + + if (listenerList == null) { + listenerList = new javax.swing.event.EventListenerList(); + } + + + + + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** + * Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + + listenerList.remove(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** + * Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataPointSelectionListenerDataPointSelected(org.das2.event.DataPointSelectionEvent event) { + if (listenerList == null) { + return; + } + + logger.fine("firing data point selection event"); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= + 0; i -= + 2) { + if (listeners[i] == org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener) listeners[i + 1]).dataPointSelected(event); + } + } + + } + /** + * Holds value of property xTagWidth. + */ + private Datum xTagWidth = null; + + /** + * Getter for property xTagWidth. + * @return Value of property xTagWidth. + */ + public Datum getXTagWidth() { + return this.xTagWidth; + } + + /** + * Setter for property xTagWidth. + * @param xTagWidth New value of property xTagWidth. + */ + public void setXTagWidth(Datum xTagWidth) { + this.xTagWidth = xTagWidth; + } + /** + * Holds value of property snapToGrid. + */ + private boolean snapToGrid = false; + + /** + * Getter for property snapToGrid. + * @return Value of property snapToGrid. + */ + public boolean isSnapToGrid() { + + return this.snapToGrid; + } + + /** + * Setter for property snapToGrid. true indicates the xtag will be reset + * so that the tags are equally spaced, each xTagWidth apart. + * @param snapToGrid New value of property snapToGrid. + */ + public void setSnapToGrid(boolean snapToGrid) { + + this.snapToGrid = snapToGrid; + } +} diff --git a/dasCore/src/main/java/org/das2/components/DataPointReporter.java b/dasCore/src/main/java/org/das2/components/DataPointReporter.java new file mode 100644 index 000000000..0fe13292f --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DataPointReporter.java @@ -0,0 +1,81 @@ +/* File: DataPointReporter.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.event.DataPointSelectionEvent; + + +/** + * + * @author Owner + */ +public class DataPointReporter extends javax.swing.JPanel implements org.das2.event.DataPointSelectionListener { + + private javax.swing.JTextField output; + + /** Creates a new instance of DataPointReporter */ + public DataPointReporter() { + super(); + this.setLayout(new java.awt.FlowLayout()); + output= new javax.swing.JTextField(20); + this.add(output); + + } + + public void dataPointSelected(org.das2.event.DataPointSelectionEvent e) { + output.setText("("+e.getX()+","+e.getY()+")"); + fireDataPointSelectionListenerDataPointSelected(e); + } + + /** Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + listenerList.remove(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataPointSelectionListenerDataPointSelected(DataPointSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener)listeners[i+1]).dataPointSelected(event); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/DataSetBrowser.java b/dasCore/src/main/java/org/das2/components/DataSetBrowser.java new file mode 100644 index 000000000..c76615227 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DataSetBrowser.java @@ -0,0 +1,108 @@ +/* File: DataSetBrowser.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.DasException; +import org.das2.DasIOException; +import org.das2.client.DasServer; + +import javax.swing.*; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.*; + +/** + * JTree veiw of a Das2Server's available data sets, with drag-n-drop of data set id's. + */ +public class DataSetBrowser extends JPanel implements DragSourceListener, DragGestureListener { + + DasServer dasServer; + JTree tree; + TreeModel dataSetListTreeModel; + + /** Creates a new instance of DataSetBrowser */ + public DataSetBrowser(DasServer dasServer) { + super(); + this.dasServer= dasServer; + try { + dataSetListTreeModel= dasServer.getDataSetList(); + } catch ( DasIOException e ) { + System.out.println(e); + } catch ( DasException e ) { + System.out.println(e); + } + + this.setLayout(new java.awt.BorderLayout()); + + tree= new JTree(dataSetListTreeModel); + + DragSource ds= DragSource.getDefaultDragSource(); + DragGestureRecognizer dgr= + ds.createDefaultDragGestureRecognizer(tree,DnDConstants.ACTION_COPY_OR_MOVE, + this ); + + this.add(new JScrollPane(tree),java.awt.BorderLayout.CENTER); + + } + + public String getSelectedDataSetId() { + TreePath tp= tree.getLeadSelectionPath(); + TreeNode tn = (TreeNode)tp.getLastPathComponent(); + if (tn.isLeaf()) { + String s = dasServer.getURL()+"?"+tp.getPathComponent(1); + for (int index = 2; index < tp.getPathCount(); index++) + s = s + "/" + tp.getPathComponent(index); + return s; + } else { + return null; + } + } + + public void dragDropEnd(java.awt.dnd.DragSourceDropEvent dragSourceDropEvent) { + } + + public void dragEnter(java.awt.dnd.DragSourceDragEvent dragSourceDragEvent) { + } + + public void dragExit(java.awt.dnd.DragSourceEvent dragSourceEvent) { + } + + public void dragGestureRecognized(java.awt.dnd.DragGestureEvent dragGestureEvent) { + String s= getSelectedDataSetId(); + if ( s!=null ) { + Transferable t= new StringSelection(s); + dragGestureEvent.startDrag(null,t,this); + } + } + + public void dragOver(java.awt.dnd.DragSourceDragEvent dragSourceDragEvent) { + } + + public void dropActionChanged(java.awt.dnd.DragSourceDragEvent dragSourceDragEvent) { + } + +} diff --git a/dasCore/src/main/java/org/das2/components/DatumEditor.java b/dasCore/src/main/java/org/das2/components/DatumEditor.java new file mode 100644 index 000000000..a1fa576e1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DatumEditor.java @@ -0,0 +1,440 @@ +/* File: DatumEditor.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 1, 2003, 9:20 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.components; + +import org.das2.datum.Units; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.datum.Datum; +import org.das2.datum.TimeLocationUnits; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.beans.PropertyEditor; +import java.text.ParseException; +import java.util.EventObject; +import javax.swing.*; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; + +/**maybeInitGui + * + * @author Edward West + */ +public class DatumEditor implements PropertyEditor, TableCellEditor { + + private JTextField editor; + private JPanel panel; + private JButton unitsButton; + private Units units = Units.dimensionless; + private ActionListener actionListener; + private Datum value; + private EventListenerList listeners; + private PropertyChangeSupport pcs = new PropertyChangeSupport(this); + + public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + pcs.removePropertyChangeListener(propertyName, listener); + } + + public synchronized void removePropertyChangeListener(PropertyChangeListener listener) { + pcs.removePropertyChangeListener(listener); + } + + public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + pcs.addPropertyChangeListener(propertyName, listener); + } + + public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { + pcs.addPropertyChangeListener(listener); + } + + /** Creates a new instance of DatumEditor */ + public DatumEditor() { + } + + private synchronized void initGui() { + initComponents(); + installListeners(); + initToolTips(); + panel.setFocusable(true); + } + + private synchronized void maybeInitGui() { + if (panel == null) { + initGui(); + } + } + + private void initComponents() { + panel = new JPanel(); + panel.setLayout(new BorderLayout()); + + editor = new JTextField(8); + if (value != null) setDatum(value); + editor.setFocusable(false); + panel.add(editor, BorderLayout.CENTER); + + unitsButton = new JButton(); + unitsButton.setFocusable(false); + unitsButton.setToolTipText("units selection"); + if ( units!=null ) { + setUnits(units); // set labels and such + } + panel.add(unitsButton, BorderLayout.EAST); + } + + private void installListeners() { + UniversalListener ul = new UniversalListener(); + editor.addMouseListener(ul); + unitsButton.addMouseListener(ul); + panel.addKeyListener(ul); + panel.addFocusListener(ul); + } + + private void initToolTips() { + ToolTipManager.sharedInstance().registerComponent(panel); + } + + public void setColumns(int columns) { + editor.setColumns(columns); + } + + /** + * @param value the Datum object to be editted. + * @throws IllegalArgumentException if value is not a Datum + */ + public void setValue(Object value) { + if (value instanceof Datum) { + setDatum((Datum) value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * @param datum the Datum object to be editted. + */ + private void setDatum(Datum datum) { + Datum oldValue = value; + value = datum; + Units u = datum.getUnits(); + if (editor != null) { + if (datum.getUnits() instanceof TimeLocationUnits) { + editor.setText(TimeDatumFormatter.DEFAULT.format(datum, u)); + } else { + editor.setText(datum.getFormatter().format(datum, u)); + } + } + setUnits(u); + if (oldValue != value && oldValue != null && !oldValue.equals(value)) { + pcs.firePropertyChange("value", oldValue, value); + } + } + + public void setAsText(String text) throws IllegalArgumentException { + try { + setDatum(units.parse(text)); + } catch (ParseException pe) { + IllegalArgumentException iae = new IllegalArgumentException(pe.getMessage()); + iae.initCause(pe); + throw iae; + } + } + + public Object getValue() { + return getDatum(); + } + + public Datum getDatum() { + try { + Datum d; + if (editor != null) { + String text = editor.getText(); + d = units.parse(text); + if (!d.equals(value)) { + Datum oldValue = value; + value = d; + pcs.firePropertyChange("value", oldValue, value); + } + } + return value; + } catch (ParseException e) { + if (value != null) { + setDatum(value); // cause reformat of old Datum + return value; + } else { + return null; + } + } + } + + public String getAsText() { + Datum v = getDatum(); + if (v == null) { + return null; + } else { + return v.toString(); + } + } + + public void setUnits(Units units) { + if (unitsButton != null) { + if (units instanceof TimeLocationUnits) { + unitsButton.setVisible(false); + } else { + unitsButton.setVisible(true); + unitsButton.setText(units.toString()); + unitsButton.setToolTipText("units selection"); + } + } + this.units = units; + } + + public Units getUnits() { + return units; + } + + private void fireActionPerformed() { + setDatum(getDatum()); + if (actionListener != null) { + actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "DatumEditor")); + } + } + + public void addActionListener(ActionListener al) { + actionListener = AWTEventMulticaster.add(actionListener, al); + } + + public void removeActionListener(ActionListener al) { + actionListener = AWTEventMulticaster.remove(actionListener, al); + } + + /* Why all one class? To reduce the number of object created. + * The purposes of the listeners are not necessarily related and it is + * generally not a good idea to implement multiple interfaces unless there + * is some sort of correlation between them. It is okay to do that here + * since this class is not a part of the public interface. + */ + private class UniversalListener implements MouseListener, KeyListener, FocusListener { + + /** Listens for focus events on the DatumEditor and sets the editor + * caret visible when focus is gained. + */ + public void focusGained(FocusEvent e) { + editor.getCaret().setVisible(true); + editor.getCaret().setSelectionVisible(true); + } + + /** Listens for focus events on the DatumEditor and sets the editor + * caret invisible when focus is lost. + */ + public void focusLost(FocusEvent e) { + editor.getCaret().setVisible(false); + editor.getCaret().setSelectionVisible(false); + } + + /** All key events are forwarded to the editor. Except for keyPresses + * for VK_ENTER + */ + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + fireActionPerformed(); + } else { + forwardKeyEvent(e); + } + } + + public void keyReleased(KeyEvent e) { + forwardKeyEvent(e); + } + + public void keyTyped(KeyEvent e) { + forwardKeyEvent(e); + } + + private void forwardKeyEvent(KeyEvent e) { + e.setSource(editor); + editor.dispatchEvent(e); + } + + /** Request focus when sub-components are clicked */ + public void mousePressed(MouseEvent e) { + panel.requestFocusInWindow(); + } + + /** Unused */ + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + } + + public String getToolTipText(MouseEvent event) { + if (unitsButton.getBounds().contains(event.getX(), event.getY())) { + return unitsButton.getToolTipText(); + } else { + return null; + } + } + + /** @see java.beans.PropertyEditor#supportsCustomEditor() + * @return true + */ + public boolean supportsCustomEditor() { + return true; + } + + /** @see java.beans.PropertyEditor#getCustomEditor() + * @return this + */ + public Component getCustomEditor() { + maybeInitGui(); + return panel; + } + + /** + * This PropertyEditor implementation does not support generating java + * code. + * @return The string "???" + */ + public String getJavaInitializationString() { + return "???"; + } + + /** + * This PropertyEditor implementation does not support enumerated + * values. + * @return null + */ + public String[] getTags() { + return null; + } + + /** @see java.beans.PropertyEditor#isPaintable() + * @return false + */ + public boolean isPaintable() { + return false; + } + + /** Does nothing. + * @param g + * @param r + */ + public void paintValue(Graphics g, Rectangle r) { + } + + /* CellEditor stuff */ + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + setValue(value); + maybeInitGui(); + return panel; + } + + /** + */ + public void addCellEditorListener(CellEditorListener l) { + if (listeners == null) { + listeners = new EventListenerList(); + } + listeners.add(CellEditorListener.class, l); + } + + /** + */ + public void removeCellEditorListener(CellEditorListener l) { + if (listeners != null) { + listeners.remove(CellEditorListener.class, l); + } + } + + /** @see javax.swing.CellEditor#isCellEditable(java.util.EventObject) + * @return true + */ + public boolean isCellEditable(EventObject anEvent) { + return true; + } + + /** @see javax.swing.CellEditor#shouldSelectCell(java.util.EventObject) + * @return true + */ + public boolean shouldSelectCell(EventObject anEvent) { + return true; + } + + /** Returns the value stored in this editor. + * @return the current value being edited + */ + public Object getCellEditorValue() { + return getDatum(); + } + + public boolean stopCellEditing() { + if (getDatum() == null) { + return false; + } + fireEditingStopped(); + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + } + private ChangeEvent evt; + + private void fireEditingStopped() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i += 2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener) l[i + 1]; + if (evt == null) { + evt = new ChangeEvent(this); + } + cel.editingStopped(evt); + } + } + } + + private void fireEditingCanceled() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i += 2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener) l[i + 1]; + if (evt == null) { + evt = new ChangeEvent(this); + } + cel.editingCanceled(evt); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/DatumRangeEditor.java b/dasCore/src/main/java/org/das2/components/DatumRangeEditor.java new file mode 100644 index 000000000..0c2d8c0e0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/DatumRangeEditor.java @@ -0,0 +1,388 @@ +/* File: DatumEditor.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 1, 2003, 9:20 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.TimeLocationUnits; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyEditor; +import java.text.ParseException; +import java.util.EventObject; +import javax.swing.*; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; + + +/** + * + * @author Edward West + */ +public class DatumRangeEditor extends JComponent implements PropertyEditor, TableCellEditor { + + private JTextField editor; + private JButton unitsButton; + private Units units = Units.dimensionless; + private ActionListener actionListener; + private DatumRange value; + private EventListenerList listeners; + + /** Creates a new instance of DatumEditor */ + public DatumRangeEditor() { + initComponents(); + installListeners(); + initToolTips(); + setFocusable(true); + } + + private void initComponents() { + setLayout(new BorderLayout()); + + editor = new JTextField(8); + editor.setFocusable(false); + add(editor, BorderLayout.CENTER); + + unitsButton = new JButton(); + unitsButton.setFocusable(false); + unitsButton.setToolTipText("units selection"); + add(unitsButton, BorderLayout.EAST); + } + + private void installListeners() { + UniversalListener ul = new UniversalListener(); + editor.addMouseListener(ul); + unitsButton.addMouseListener(ul); + addKeyListener(ul); + addFocusListener(ul); + } + + private void initToolTips() { + ToolTipManager.sharedInstance().registerComponent(this); + } + + public void setColumns(int columns) { + editor.setColumns(columns); + } + + /** + * @param value the DatumRange to be editted. + * @throws IllegalArgumentException if value is not a Datum + */ + public void setValue(Object value) { + if (value instanceof DatumRange) { + setDatumRange((DatumRange)value); + } else { + throw new IllegalArgumentException(); + } + } + + /** + * @param datumRange the DatumRange to be edited. + */ + private void setDatumRange(DatumRange datumRange ) { + DatumRange oldValue = value; + value = datumRange; + Units u= datumRange.getUnits(); + editor.setText( datumRange.toString() ); + setUnits(u); + if (oldValue != value && oldValue != null && !oldValue.equals(value)) { + firePropertyChange("value", oldValue, value); + } + } + + public void setAsText(String text) throws IllegalArgumentException { + try { + setDatumRange( parseText( text ) ); + } catch (ParseException pe) { + IllegalArgumentException iae = new IllegalArgumentException(pe.getMessage()); + iae.initCause(pe); + throw iae; + } + } + + public Object getValue() { + return getDatumRange(); + } + + private DatumRange parseText( String text ) throws ParseException { + DatumRange result=null; + if ( units instanceof TimeLocationUnits ) { + result= DatumRangeUtil.parseTimeRange( text ); + } else { + result= DatumRangeUtil.parseDatumRange( text, value ); + } + return result; + } + + public DatumRange getDatumRange() { + try { + String text = editor.getText(); + DatumRange dr = parseText(text); + if (!dr.equals(value)) { + DatumRange oldValue = value; + value = dr; + firePropertyChange("value", oldValue, value); + } + return value; + } catch (ParseException e) { + if ( value!=null ) { + setDatumRange( value ); // cause reformat of old Datum + return value; + } else { + return null; + } + } + } + + public String getAsText() { + String text; + Object value = getDatumRange(); + if (value == null) { + return null; + } + return editor.getText(); + } + + public void setUnits(Units units) { + if (units instanceof TimeLocationUnits) { + unitsButton.setVisible(false); + } else { + unitsButton.setVisible(true); + unitsButton.setText(units.toString()); + unitsButton.setToolTipText("units selection"); + } + this.units = units; + } + + public Units getUnits() { + return units; + } + + private void fireActionPerformed() { + setDatumRange(getDatumRange()); + if (actionListener != null) { + actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "DatumEditor")); + } + } + + public void addActionListener(ActionListener al) { + actionListener = AWTEventMulticaster.add(actionListener, al); + } + + public void removeActionListener(ActionListener al) { + actionListener = AWTEventMulticaster.remove(actionListener, al); + } + + /* Why all one class? To reduce the number of object created. + * The purposes of the listeners are not necessarily related and it is + * generally not a good idea to implement multiple interfaces unless there + * is some sort of correlation between them. It is okay to do that here + * since this class is not a part of the public interface. + */ + private class UniversalListener implements MouseListener, KeyListener, FocusListener { + + /** Listens for focus events on the DatumEditor and sets the editor + * caret visible when focus is gained. + */ + public void focusGained(FocusEvent e) { + editor.getCaret().setVisible(true); + editor.getCaret().setSelectionVisible(true); + } + + /** Listens for focus events on the DatumEditor and sets the editor + * caret invisible when focus is lost. + */ + public void focusLost(FocusEvent e) { + editor.getCaret().setVisible(false); + editor.getCaret().setSelectionVisible(false); + } + + /** All key events are forwarded to the editor. Except for keyPresses + * for VK_ENTER + */ + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + fireActionPerformed(); + } else { + forwardKeyEvent(e); + } + } + public void keyReleased(KeyEvent e) { forwardKeyEvent(e); } + public void keyTyped(KeyEvent e) { forwardKeyEvent(e); } + private void forwardKeyEvent(KeyEvent e) { + e.setSource(editor); + editor.dispatchEvent(e); + } + + /** Request focus when sub-components are clicked */ + public void mousePressed(MouseEvent e) { + requestFocusInWindow(); + } + + /** Unused */ + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + public void mouseClicked(MouseEvent e) {} + public void mouseReleased(MouseEvent e) {} + } + + public String getToolTipText(MouseEvent event) { + if (unitsButton.getBounds().contains(event.getX(), event.getY())) { + return unitsButton.getToolTipText(); + } else { + return null; + } + } + + /** @see java.beans.PropertyEditor#supportsCustomEditor() + * @return true + */ + public boolean supportsCustomEditor() { + return true; + } + + /** @see java.beans.PropertyEditor#getCustomEditor() + * @return this + */ + public Component getCustomEditor() { + return this; + } + + /** + * This PropertyEditor implementation does not support generating java + * code. + * @return The string "???" + */ + public String getJavaInitializationString() { + return "???"; + } + + /** + * This PropertyEditor implementation does not support enumerated + * values. + * @return null + */ + public String[] getTags() { return null; } + + /** @see java.beans.PropertyEditor#isPaintable() + * @return false + */ + public boolean isPaintable() { + return false; + } + + /** Does nothing. + * @param g + * @param r + */ + public void paintValue(Graphics g, Rectangle r) {} + + /* CellEditor stuff */ + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + setValue(value); + return this; + } + + /** + */ + public void addCellEditorListener(CellEditorListener l) { + if (listeners == null) { + listeners = new EventListenerList(); + } + listeners.add(CellEditorListener.class, l); + } + + /** + */ + public void removeCellEditorListener(CellEditorListener l) { + if (listeners != null) { + listeners.remove(CellEditorListener.class, l); + } + } + + /** @see javax.swing.CellEditor#isCellEditable(java.util.EventObject) + * @return true + */ + public boolean isCellEditable(EventObject anEvent) { + return true; + } + + /** @see javax.swing.CellEditor#shouldSelectCell(java.util.EventObject) + * @return true + */ + public boolean shouldSelectCell(EventObject anEvent) { + return true; + } + + /** Returns the value stored in this editor. + * @return the current value being edited + */ + public Object getCellEditorValue() { + return getDatumRange(); + } + + public boolean stopCellEditing() { + if (getDatumRange() == null) { + return false; + } + fireEditingStopped(); + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + } + + private ChangeEvent evt; + + private void fireEditingStopped() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i+=2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener)l[i+1]; + if (evt == null) { evt = new ChangeEvent(this); } + cel.editingStopped(evt); + } + } + } + + private void fireEditingCanceled() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i+=2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener)l[i+1]; + if (evt == null) { evt = new ChangeEvent(this); } + cel.editingCanceled(evt); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/FavoritesSelector.java b/dasCore/src/main/java/org/das2/components/FavoritesSelector.java new file mode 100644 index 000000000..2b07b39af --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/FavoritesSelector.java @@ -0,0 +1,188 @@ +/* + * FavoritesSelector.java + * + * Created on March 18, 2004, 5:33 PM + */ + +package org.das2.components; + +import java.util.*; +import java.util.prefs.*; +import javax.swing.*; +import java.awt.event.*; +import java.text.*; + +/** + * + * @author Jeremy + */ +public class FavoritesSelector { + + java.util.List favoritesList; + String favoritesType; + FavoritesListener listener=null; + ActionListener actionListener; + JPopupMenu popupMenu; + boolean nextSelectionDeletes= false; + + /** Creates a new instance of FavoritesSelector */ + private FavoritesSelector( String _favoritesType) { + favoritesList= new ArrayList(); + this.favoritesType= _favoritesType; + this.readFromPersistentPrefs(); + } + + public interface FavoritesListener extends EventListener { + void itemSelected( Object o ); + Object addFavoriteSelected(); + } + + public void addFavoritesListener( FavoritesListener _listener ) { + if ( this.listener!=null ) { + throw new IllegalArgumentException( "only one listener supported" ); + } + this.listener= _listener; + } + + public ActionListener getActionListener() { + if ( this.actionListener==null ) { + this.actionListener= new ActionListener() { + public void actionPerformed( ActionEvent ev ) { + try { + String cmd= ev.getActionCommand(); + System.out.println(cmd); + if ( cmd.equals("delete") ) { + FavoritesSelector.this.popupMenu.setVisible(true); + FavoritesSelector.this.nextSelectionDeletes= true; + } else if ( cmd.equals("add") ) { + if ( listener!=null ) { + Object o= FavoritesSelector.this.listener.addFavoriteSelected(); + if ( o!=null ) { + addFavorite(o); + } + } + } else { + if ( FavoritesSelector.this.nextSelectionDeletes ) { + removeFavorite( favoritesList.get( Integer.parseInt(cmd) ) ); + FavoritesSelector.this.nextSelectionDeletes= false; + } else { + FavoritesSelector.this.listener.itemSelected( favoritesList.get( Integer.parseInt(cmd) ) ); + } + } + } catch ( NumberFormatException e ) { + throw new RuntimeException(e); + } + } + }; + } + return this.actionListener; + } + + private void resetPopupMenu() { + JPopupMenu pm= getMenu(); + + JMenuItem item; + item= popupMenu.add("Add to favorites"); + item.setActionCommand("add"); + item.addActionListener(getActionListener()); + for ( int i= 0; i0 ) { + listString.append(favoritesList.get(0).toString()); + } + for ( int i=1; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.DasColorBar; +import org.das2.graph.SpectrogramRenderer; +import org.das2.graph.DasColumn; +import org.das2.graph.Psym; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dataset.DefaultVectorDataSet; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.Units; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.datum.TimeLocationUnits; +import org.das2.util.DasMath; +import org.das2.datum.Datum; +import org.das2.event.DataPointSelectionEvent; +import org.das2.event.DataPointSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.*; +import java.util.List; +import javax.swing.*; + + +public class HistogramSlicer extends DasPlot implements DataPointSelectionListener { + + private JDialog popupWindow; + private Datum xValue; + private SpectrogramRenderer renderer; + + private HistogramSlicer(SpectrogramRenderer parentRenderer, DasAxis xAxis, DasAxis yAxis) { + super(xAxis, yAxis); + renderer = parentRenderer; + SymbolLineRenderer symLineRenderer= new SymbolLineRenderer(); + symLineRenderer.setHistogram(true); + symLineRenderer.setLineWidth(1.0f); + symLineRenderer.setPsym(Psym.CROSS); + addRenderer(symLineRenderer); + } + + public static HistogramSlicer createSlicer(SpectrogramRenderer renderer) { + DasAxis sourceZAxis = renderer.getColorBar(); + DasAxis xAxis = new DasAxis(sourceZAxis.getDataMinimum(), sourceZAxis.getDataMaximum(), DasAxis.HORIZONTAL, sourceZAxis.isLog()); + DasAxis yAxis = new DasAxis(Datum.create(0.0), Datum.create(1.0), DasAxis.VERTICAL); + return new HistogramSlicer(renderer, xAxis, yAxis); + } + + public void showPopup() { + if (SwingUtilities.isEventDispatchThread()) { + showPopupImpl(); + } + else { + Runnable r = new Runnable() { + public void run() { + showPopupImpl(); + } + }; + } + } + + /** This method should ONLY be called by the AWT event thread */ + private void showPopupImpl() { + if (popupWindow == null) { + createPopup(); + } + popupWindow.setVisible(true); + } + + /** This method should ONLY be called by the AWT event thread */ + private void createPopup() { + int width = renderer.getParent().getCanvas().getWidth() / 2; + int height = renderer.getParent().getCanvas().getHeight() / 2; + DasCanvas canvas = new DasCanvas(width, height); + DasRow row = new DasRow(canvas, 0.1, 0.9); + DasColumn column = new DasColumn(canvas, 0.1, 0.9); + canvas.add(this, row, column); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + Window parentWindow = SwingUtilities.getWindowAncestor(renderer.getParent()); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } + else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } + else { + popupWindow = new JDialog(); + } + popupWindow.setTitle("Histogram Slicer"); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen(parentLocation, renderer.getParent().getCanvas()); + popupWindow.setLocation(parentLocation.x + renderer.getParent().getCanvas().getWidth(),parentLocation.y + height); + } + + public void dataPointSelected(DataPointSelectionEvent e) { + + DataSet ds = e.getDataSet(); + if (ds==null || !(ds instanceof TableDataSet)) { + return; + } + + Datum yValue = e.getY(); + xValue = e.getX(); + + TableDataSet tds = (TableDataSet)ds; + //TableDataSet tds = (TableDataSet)renderer.getDataSet(); + + int itable= TableUtil.tableIndexAt( tds, DataSetUtil.closestColumn( tds, e.getX() ) ); + VectorDataSet sliceDataSet= tds.getYSlice( TableUtil.closestRow( tds, itable, e.getY() ), itable ); + + DasColorBar cb = renderer.getColorBar(); + DasAxis xAxis = getXAxis(); + if (!xAxis.getUnits().equals(cb.getUnits())) { + xAxis.setUnits(cb.getUnits()); + xAxis.setDataRange(cb.getDataMinimum(), cb.getDataMaximum()); + xAxis.setLog(cb.isLog()); + } + + VectorDataSet hist = getHistogram(sliceDataSet); + + getRenderer(0).setDataSet(hist); + + DatumFormatter formatter; + if ( xValue.getUnits() instanceof TimeLocationUnits ) { + formatter= TimeDatumFormatter.DEFAULT; + } else { + formatter= xValue.getFormatter(); + } + + if (!(popupWindow == null || popupWindow.isVisible()) || getCanvas() == null) { + showPopup(); + } + else { + repaint(); + } + } + + /** This should handle non-log data too probably. */ + public VectorDataSet getHistogram(VectorDataSet vds) { + final int BINS_PER_DECADE = 8; + DasAxis zAxis = renderer.getColorBar(); + Units yUnits = zAxis.getUnits(); + double min = getXAxis().getDataMinimum(yUnits); + double max = getXAxis().getDataMaximum(yUnits); + int sampleCount = vds.getXLength(); + if (zAxis.isLog()) { + double minLog = Math.floor(DasMath.log10(min)); + double maxLog = Math.ceil(DasMath.log10(max)); + int binCount = (int)(maxLog - minLog) * BINS_PER_DECADE; //4 bins per decade + double[] bins = new double[binCount]; + for (int i = 0; i < vds.getXLength(); i++) { + double y = vds.getDouble(i, yUnits); + if (yUnits.isFill(y) || Double.isNaN(y)) { + sampleCount--; + continue; + } + double yLog = DasMath.log10(y); + if (yLog < minLog) { + double newMinLog = Math.floor(yLog); + int binCountDelta = ((int)Math.floor(minLog - newMinLog)) * BINS_PER_DECADE; + binCount += binCountDelta; + double[] newBins = new double[binCount]; + System.arraycopy(bins, 0, newBins, binCountDelta, bins.length); + minLog = newMinLog; + bins = newBins; + } + else if (yLog >= maxLog) { + double newMaxLog = Math.ceil(yLog+0.001); + int binCountDelta = (int)(newMaxLog - maxLog) * BINS_PER_DECADE; + binCount += binCountDelta; + double[] newBins = new double[binCount]; + System.arraycopy(bins, 0, newBins, 0, bins.length); + maxLog = newMaxLog; + bins = newBins; + } + int index = (int)Math.floor((yLog - minLog) * (double)BINS_PER_DECADE); + if (index >= 0 && index < bins.length) { + bins[index] += 1.0; + } + } + double[] x = new double[binCount]; + for (int index = 0; index < binCount; index++) { + x[index] = DasMath.exp10(minLog + (index / (double)BINS_PER_DECADE)); + bins[index] = bins[index] / (double)sampleCount; + } + return new DefaultVectorDataSet(x, yUnits, bins, Units.dimensionless, Collections.EMPTY_MAP); + } + else { + //Should add logic that determine bin size instead of just using 1.0 + min = Math.floor(min); + max = Math.ceil(max); + int binCount = (int)(max - min); + double[] bins = new double[binCount]; + for (int i = 0; i < vds.getXLength(); i++) { + double y = vds.getDouble(i, yUnits); + if (yUnits.isFill(y) || Double.isNaN(y)) { + sampleCount--; + continue; + } + if (y < min) { + double newMin = Math.floor(y); + int binCountDelta = (int)(min - newMin); + binCount += binCountDelta; + double[] newBins = new double[binCount]; + System.arraycopy(bins, 0, newBins, binCountDelta, bins.length); + min = newMin; + bins = newBins; + } + else if (y >= max) { + double newMax = Math.ceil(y+0.001); + int binCountDelta = (int)(newMax - max); + binCount += binCountDelta; + double[] newBins = new double[binCount]; + System.arraycopy(bins, 0, newBins, 0, bins.length); + max = newMax; + bins = newBins; + } + int index = (int)Math.floor(y - min); + if (index >= 0 && index < bins.length) { + bins[index] += 1.0; + } + } + double[] x = new double[binCount]; + for (int index = 0; index < binCount; index++) { + x[index] = min + (double)index + 0.5; + bins[index] = bins[index] / (double)sampleCount; + } + return new DefaultVectorDataSet(x, yUnits, bins, Units.dimensionless, Collections.EMPTY_MAP); + } + } + + protected void uninstallComponent() { + super.uninstallComponent(); + } + + protected void installComponent() { + super.installComponent(); + } + +} diff --git a/dasCore/src/main/java/org/das2/components/HorizontalSpectrogramSlicer.java b/dasCore/src/main/java/org/das2/components/HorizontalSpectrogramSlicer.java new file mode 100755 index 000000000..68467f576 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/HorizontalSpectrogramSlicer.java @@ -0,0 +1,198 @@ +/* File: HorizontalSpectrogramSlicer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.DasColumn; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.datum.TimeLocationUnits; +import org.das2.datum.Datum; +import org.das2.event.DataPointSelectionEvent; +import org.das2.event.DataPointSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + + +public class HorizontalSpectrogramSlicer extends DasPlot implements DataPointSelectionListener { + + private JDialog popupWindow; + private Datum xValue; + private SymbolLineRenderer renderer; + private DasPlot parentPlot; + + private HorizontalSpectrogramSlicer(DasPlot plot, DasAxis xAxis, DasAxis yAxis) { + super(xAxis, yAxis); + parentPlot = plot; + renderer= new SymbolLineRenderer(); + addRenderer(renderer); + } + + public static HorizontalSpectrogramSlicer createSlicer(DasPlot plot, TableDataSetConsumer dataSetConsumer) { + DasAxis sourceXAxis = plot.getXAxis(); + DasAxis xAxis = sourceXAxis.createAttachedAxis(DasAxis.HORIZONTAL); + DasAxis yAxis = dataSetConsumer.getZAxis().createAttachedAxis(DasAxis.VERTICAL); + return new HorizontalSpectrogramSlicer(plot, xAxis, yAxis); + } + + public void showPopup() { + if (SwingUtilities.isEventDispatchThread()) { + showPopupImpl(); + } + else { + Runnable r = new Runnable() { + public void run() { + showPopupImpl(); + } + }; + } + } + + /** This method should ONLY be called by the AWT event thread */ + private void showPopupImpl() { + if (popupWindow == null) { + createPopup(); + } + popupWindow.setVisible(true); + } + + /** This method should ONLY be called by the AWT event thread */ + private void createPopup() { + int width = parentPlot.getCanvas().getWidth() / 2; + int height = parentPlot.getCanvas().getHeight() / 2; + DasCanvas canvas = new DasCanvas(width, height); + DasRow row = new DasRow(canvas, 0.1, 0.9); + DasColumn column = new DasColumn(canvas, 0.1, 0.9); + canvas.add(this, row, column); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + Window parentWindow = SwingUtilities.getWindowAncestor(parentPlot); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } + else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } + else { + popupWindow = new JDialog(); + } + popupWindow.setTitle("Horizontal Slicer"); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen(parentLocation, parentPlot.getCanvas()); + popupWindow.setLocation(parentLocation.x + parentPlot.getCanvas().getWidth(),parentLocation.y + height); + } + + public void dataPointSelected(DataPointSelectionEvent e) { + + DataSet ds = e.getDataSet(); + if (ds==null || !(ds instanceof TableDataSet)) { + return; + } + + Datum yValue = e.getY(); + xValue = e.getX(); + + TableDataSet tds = (TableDataSet)ds; + + int itable= TableUtil.tableIndexAt( tds, DataSetUtil.closestColumn( tds, e.getX() ) ); + VectorDataSet sliceDataSet= tds.getYSlice( TableUtil.closestRow( tds, itable, e.getY() ), itable ); + + renderer.setDataSet(sliceDataSet); + + DatumFormatter formatter; + if ( xValue.getUnits() instanceof TimeLocationUnits ) { + formatter= TimeDatumFormatter.DEFAULT; + } else { + formatter= xValue.getFormatter(); + } + + setTitle("x: "+ formatter.format(xValue) + " y: "+yValue); + + if (!(popupWindow == null || popupWindow.isVisible()) || getCanvas() == null) { + showPopup(); + } + } + + public void drawContent(Graphics2D g) { + super.drawContent(g); + int ix= (int)this.getXAxis().transform(xValue); + DasRow row= this.getRow(); + int iy0= row.getDMinimum(); + int iy1= row.getDMaximum(); + g.drawLine(ix+3,iy0,ix,iy0+3); + g.drawLine(ix-3,iy0,ix,iy0+3); + g.drawLine(ix+3,iy1,ix,iy1-3); + g.drawLine(ix-3,iy1,ix,iy1-3); + + g.setColor( new Color(230,230,230) ); + g.drawLine( ix, iy0+4, ix, iy1-4 ); + } + + protected void uninstallComponent() { + super.uninstallComponent(); + } + + protected void installComponent() { + super.installComponent(); + } + + protected void processDasUpdateEvent(org.das2.event.DasUpdateEvent e) { + if (isDisplayable()) { + updateImmediately(); + resize(); + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java b/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java new file mode 100644 index 000000000..86226e6cd --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java @@ -0,0 +1,663 @@ +/* + * TearoffTabbedPane.java + * + * Created on January 26, 2006, 7:31 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.das2.components; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowStateListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTabbedPane; +import javax.swing.SwingUtilities; + +/** + * + * @author Jeremy + */ +public class TearoffTabbedPane extends JTabbedPane { + + int selectedTab; + Point dragStart; + Point dragOffset; + JFrame draggingFrame; + JPopupMenu tearOffMenu = new JPopupMenu(); + JPopupMenu dockMenu = new JPopupMenu(); + + private TearoffTabbedPane parentPane; + + private TearoffTabbedPane rightPane = null; + private JFrame rightFrame = null; + private ComponentListener rightFrameListener; + private int rightOffset= 0; + + private final static Logger logger= Logger.getLogger( TearoffTabbedPane.class.getCanonicalName() ); + + HashMap tabs = new HashMap(); + int lastSelected; /* keep track of selected index before context menu */ + + private static void copyInputMap(JFrame parent, JFrame babySitter) { + Component c; + JComponent parentc, babySitterC; + + c = parent.getContentPane(); + if (!(c instanceof JComponent)) { + return; + } + parentc = (JComponent) c; + + c = babySitter.getContentPane(); + if (!(c instanceof JComponent)) { + return; + } + babySitterC = (JComponent) c; + + InputMap m = parentc.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + if (m == null) { + return; + } + babySitterC.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, m); + ActionMap am = parentc.getActionMap(); + if (am == null) { + return; + } + babySitterC.setActionMap(am); + } + + public void slideRight(Component tab) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + class TabDesc { + + Icon icon; + String title; + String tip; + int index; + Container babysitter; + Component component; + + TabDesc(String title, Icon icon, Component component, String tip, int index) { + this.title = title; + this.icon = icon; + this.component = component; + this.tip = tip; + this.index = index; + this.babysitter = null; + } + } + + public TearoffTabbedPane() { + this(null); + } + + private TearoffTabbedPane(TearoffTabbedPane parent) { + super(); + if (parent == null) { + MouseAdapter ma = getParentMouseAdapter(); + addMouseListener(ma); + addMouseMotionListener(getMouseMotionListener()); + } else { + parentPane = parent; + addMouseListener(getChildMouseAdapter()); + } + } + + private MouseMotionListener getMouseMotionListener() { + return new MouseMotionListener() { + + public void mouseDragged(MouseEvent e) { + if (selectedTab == -1) { + return; + } + if (dragStart == null) { + dragStart = e.getPoint(); + } else { + if (dragStart.distance(e.getPoint()) > 10) { + if (draggingFrame == null) { + dragOffset= getComponentAt(selectedTab).getLocationOnScreen(); + Point ds= new Point(dragStart); + SwingUtilities.convertPointToScreen(ds, e.getComponent() ); + int tabAndWindowHeight=40; // ubuntu, TODO: calculate + dragOffset.translate( -ds.x, -ds.y - tabAndWindowHeight ); + draggingFrame = TearoffTabbedPane.this.tearOffIntoFrame(selectedTab); + if (draggingFrame == null) { + return; + } + setCursor(new Cursor(Cursor.MOVE_CURSOR)); + if ( draggingFrame.getWidth()< -1*dragOffset.x ) { + int borderWidth=5; + dragOffset.x= -1*(draggingFrame.getWidth()-borderWidth); + } + } + Point p = e.getPoint(); + SwingUtilities.convertPointToScreen(p, (Component) e.getSource()); + p.translate(dragOffset.x, dragOffset.y); + draggingFrame.setLocation(p); + } + } + } + + public void mouseMoved(MouseEvent e) { + } + }; + } + + private void showPopupMenu(MouseEvent event) { + Component selectedComponent; + selectedTab = TearoffTabbedPane.this.indexAtLocation(event.getX(), event.getY()); + if (selectedTab != -1) { + selectedComponent = TearoffTabbedPane.this.getComponentAt(selectedTab); + if (parentPane == null && tabs.get(selectedComponent) != null) { + tearOffMenu.show(TearoffTabbedPane.this, event.getX(), event.getY()); + } else { + dockMenu.show(TearoffTabbedPane.this, event.getX(), event.getY()); + } + } + } + + private MouseAdapter getChildMouseAdapter() { + return new MouseAdapter() { + + Component selectedComponent; + + + { + dockMenu.add(new JMenuItem(new AbstractAction("dock") { + + public void actionPerformed(ActionEvent event) { + TabDesc desc = null; + + for (Iterator i = tabs.keySet().iterator(); i.hasNext();) { + Component key = (Component) i.next(); + TabDesc d = (TabDesc) tabs.get(key); + } + + if (parentPane != null) { + selectedComponent = getComponent(selectedTab); + remove(selectedComponent); + parentPane.dock(selectedComponent); + if ( getTabCount()==0 ) { + SwingUtilities.getWindowAncestor(TearoffTabbedPane.this).dispose(); + } + } else { + if (desc.babysitter instanceof Window) { + ((Window) desc.babysitter).dispose(); + } + parentPane.dock(selectedComponent); + } + + } + })); + } + + public void mousePressed(MouseEvent event) { + selectedTab = TearoffTabbedPane.this.indexAtLocation(event.getX(), event.getY()); + if (event.isPopupTrigger()) { + showPopupMenu(event); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (dragStart != null && selectedTab != -1) { + //JFrame f= TearoffTabbedPane.this.tearOffIntoFrame( selectedTab ); + //Point p= e.getPoint(); + //SwingUtilities.convertPointToScreen( p ,(Component) e.getSource() ); + //f.setLocation( p ); + setCursor(null); + draggingFrame = null; + } + dragStart = null; + if (e.isPopupTrigger()) { + showPopupMenu(e); + } + } + }; + + } + + private MouseAdapter getParentMouseAdapter() { + return new MouseAdapter() { + + { + tearOffMenu.add(new JMenuItem(new AbstractAction("undock") { + + public void actionPerformed(ActionEvent event) { + TearoffTabbedPane.this.tearOffIntoFrame(selectedTab); + } + })); + tearOffMenu.add(new JMenuItem(new AbstractAction("slide right") { + + public void actionPerformed(ActionEvent event) { + TearoffTabbedPane.this.slideRight(selectedTab); + } + })); + } + Component selectedComponent; + + + { + dockMenu.add(new JMenuItem(new AbstractAction("show") { + + public void actionPerformed(ActionEvent event) { + TabDesc desc = null; + Component babyComponent = null; + for (Iterator i = tabs.keySet().iterator(); i.hasNext();) { + Component key = (Component) i.next(); + TabDesc d = (TabDesc) tabs.get(key); + if (d.index == selectedTab) { + desc = d; + babyComponent = key; + break; + } + } + if (desc.babysitter instanceof Window) { + Window babySitter = (Window) desc.babysitter; + babySitter.setVisible(false); + babySitter.setVisible(true); + } else if ( desc.babysitter instanceof TearoffTabbedPane ) { + Window parent= SwingUtilities.getWindowAncestor(babyComponent); + parent.setVisible(false); + parent.setVisible(true); + } + + //babySitter.toFront(); // no effect on Linux/Gnome + } + })); + dockMenu.add(new JMenuItem(new AbstractAction("dock") { + + public void actionPerformed(ActionEvent event) { + TabDesc desc = null; + Component babyComponent = null; + for (Iterator i = tabs.keySet().iterator(); i.hasNext();) { + Component key = (Component) i.next(); + TabDesc d = (TabDesc) tabs.get(key); + if (d.index == selectedTab) { + desc = d; + babyComponent = key; + break; + + } + } + + if (desc.babysitter instanceof Window) { + ((Window) desc.babysitter).dispose(); + } else if ( desc.babysitter instanceof TearoffTabbedPane ) { + TearoffTabbedPane bb= (TearoffTabbedPane) desc.babysitter; + if ( bb.getTabCount()==1 ) { + SwingUtilities.getWindowAncestor(bb).dispose(); + } + // do nothing + } + + TearoffTabbedPane.this.dock(babyComponent); + } + })); + } + + public void mousePressed(MouseEvent event) { + selectedTab = TearoffTabbedPane.this.indexAtLocation(event.getX(), event.getY()); + if (event.isPopupTrigger()) { + showPopupMenu(event); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (dragStart != null && selectedTab != -1) { + //JFrame f= TearoffTabbedPane.this.tearOffIntoFrame( selectedTab ); + //Point p= e.getPoint(); + //SwingUtilities.convertPointToScreen( p ,(Component) e.getSource() ); + //f.setLocation( p ); + setCursor(null); + draggingFrame = null; + } + dragStart = null; + if (e.isPopupTrigger()) { + showPopupMenu(e); + } + } + }; + } + + /** + * get a component to occupy the space when a tab is undocked. + * @return + */ + static Component getTornOffComponent() { + JPanel tornOffComponent = new JPanel(); + tornOffComponent.setLayout(new BorderLayout()); + tornOffComponent.add(new JLabel("This tab is undocked. Right-click on the tab name and select dock."), BorderLayout.NORTH); + return tornOffComponent; + } + + public void tearOff(int tabIndex, Container newContainer) { + int lastSelected = this.lastSelected; + Component c = getComponentAt(tabIndex); + String title = super.getTitleAt(tabIndex); + super.removeTabAt(tabIndex); + super.insertTab("(" + title + ")", null, getTornOffComponent(), null, tabIndex); + super.setEnabledAt(tabIndex, false); + TabDesc td = ((TabDesc) tabs.get(c)); + td.babysitter = newContainer; + setSelectedIndex(lastSelected); + } + + private final static Object STICK_LEFT= "left"; + private final static Object STICK_RIGHT= "right"; + + /** + * get the listener that will keep the two JFrames close together + * @param panel1 component within the master frame. + * @param frame1 master frame that controls. + * @param panel2 component within the compliant frame + * @param frame2 compliant frame that follows. + * @param direction + * @return + */ + public ComponentListener getFrameComponentListener( + final Component panel1, final Component frame1, + final Component panel2, final Component frame2, final Object direction ) { + + return new ComponentListener() { + Component activeComponent; + long activeComponentTime=0; + + public void componentResized(ComponentEvent e) { + long t= System.currentTimeMillis(); + if ( ( t-activeComponentTime ) > 100 ) { + activeComponent= e.getComponent(); + } + if ( e.getComponent()==activeComponent ) { + activeComponentTime= t; + updateAttached( activeComponent, panel1, frame1, panel2, frame2, direction, true ); + } + } + + public void componentMoved(ComponentEvent e) { + long t= System.currentTimeMillis(); + if ( ( t-activeComponentTime ) > 100 ) { + activeComponent= e.getComponent(); + } + if ( e.getComponent()==activeComponent ) { + activeComponentTime= t; + updateAttached( activeComponent, panel1, frame1, panel2, frame2, direction, false ); + } + } + + public void componentShown(ComponentEvent e) { + } + + public void componentHidden(ComponentEvent e) { + } + }; + } + + private void updateAttached( + final Component active, + final Component panel1, final Component frame1, + final Component panel2, final Component frame2, Object direction, boolean updateSize ) { + Point p = SwingUtilities.convertPoint(panel1, 0, 0, frame1); + Point p2 = SwingUtilities.convertPoint(panel2, 0, 0, frame2); + Dimension s1= panel1.getSize(); + Dimension frameSize1= frame1.getSize(); + Dimension s2= panel2.getSize(); + Dimension frameSize2= frame2.getSize(); + + if ( direction==STICK_RIGHT ) { + if ( active==frame1 ) { + if ( updateSize ) frame2.setSize( new Dimension( s1.width, s1.height + p2.y ) ); + frame2.setLocation( frame1.getX() + frame1.getWidth() - p2.x + rightOffset, frame1.getY() + p.y - p2.y ); + } else { + if ( false && updateSize ) { + int frame1NotTabs= frameSize1.height-s1.height; + System.err.println(frame1NotTabs); + frame1.setSize( new Dimension( frameSize1.width, ( frameSize1.height-s1.height ) + s2.height ) ); + } + int x= Math.max( frame1.getX(), frame2.getX()-frameSize1.width + p2.x ); + rightOffset= frame2.getX()-s1.width - frame1.getX(); + if ( rightOffset>0 ) rightOffset=0; + if ( rightOffset< -1*s1.width ) { + x+= s1.width + rightOffset; + rightOffset= -1 * s1.width; + } + frame1.setLocation( x, frame2.getY() - p.y + p2.y ); + } + } + } + + private synchronized TearoffTabbedPane getRightTabbedPane() { + if (rightPane == null) { + + final JFrame parent = (JFrame) SwingUtilities.getWindowAncestor(this); + rightPane = new TearoffTabbedPane(this); + rightFrame = new JFrame(); + + rightFrame.add(rightPane); + rightFrame.setIconImage( parent.getIconImage() ); + + final WindowStateListener listener = new WindowStateListener() { + + public void windowStateChanged(WindowEvent e) { + rightFrame.setExtendedState(parent.getExtendedState()); + } + }; + parent.addWindowStateListener(listener); + + rightFrame.addWindowListener(new WindowAdapter() { + + public void windowClosing(WindowEvent e) { + parent.removeWindowStateListener(listener); + parent.removeComponentListener(rightFrameListener); + + for (Component c : new ArrayList(rightPane.tabs.keySet())) { + TearoffTabbedPane.this.dock(c); + } + + rightFrame = null; + rightPane = null; + } + }); + + copyInputMap(parent, rightFrame); + rightFrameListener = getFrameComponentListener(this, parent, rightPane, rightFrame, STICK_RIGHT ); + + parent.addComponentListener(rightFrameListener); + rightFrame.addComponentListener(rightFrameListener); + + rightPane.setPreferredSize(this.getSize()); + + rightFrame.pack(); + updateAttached( parent, this, parent, rightPane, rightFrame, STICK_RIGHT, true ); + + rightFrame.setVisible(true); + parent.toFront(); + + } + return rightPane; + } + + public void slideRight(int tabIndex) { + + final Component c = getComponentAt(tabIndex); + logger.finest("slideRight "+c); + + setSelectedIndex(tabIndex); + c.setVisible(true); // darwin bug297 + + TabDesc td = (TabDesc) tabs.get(c); + if (td == null) { + return; + } + final JFrame parent = (JFrame) SwingUtilities.getWindowAncestor(this); + + TearoffTabbedPane right = getRightTabbedPane(); + + tearOff(tabIndex, right); + + right.add(td.title, c); + right.setSelectedIndex(right.getTabCount()-1); + if ( !right.isShowing() ) { + Window w= SwingUtilities.getWindowAncestor(right); + w.setVisible(false); + w.setVisible(true); + } + } + + protected JFrame tearOffIntoFrame(int tabIndex) { + final Component c = getComponentAt(tabIndex); + logger.finest("tearOffInfoFrame "+c); + setSelectedIndex(tabIndex); + c.setVisible(true); // darwin bug297 + Point p = c.getLocationOnScreen(); + TabDesc td = (TabDesc) tabs.get(c); + if (td == null) { + return null; + } + final JFrame parent = (JFrame) SwingUtilities.getWindowAncestor(this); + final JFrame babySitter = new JFrame(td.title); + babySitter.setIconImage( parent.getIconImage() ); + final WindowStateListener listener = new WindowStateListener() { + + public void windowStateChanged(WindowEvent e) { + babySitter.setExtendedState(parent.getExtendedState()); + } + }; + parent.addWindowStateListener(listener); + + p.translate(20, 20); + babySitter.setLocation(p); + babySitter.addWindowListener(new WindowAdapter() { + + public void windowClosing(WindowEvent e) { + parent.removeWindowStateListener(listener); + dock(c); + } + }); + + copyInputMap(parent, babySitter); + + JTabbedPane pane = new TearoffTabbedPane(this); + babySitter.getContentPane().add(pane); + + tearOff(tabIndex, babySitter); + pane.add(td.title, c); + + babySitter.pack(); + babySitter.setVisible(true); + + return babySitter; + } + + public void dock(Component c) { + logger.finest("dock "+c); + int selectedIndex = getSelectedIndex(); + TabDesc td = (TabDesc) tabs.get(c); + int index = td.index; + super.removeTabAt(index); + super.insertTab(td.title, td.icon, c, td.tip, index); + super.setEnabledAt(index, true); + setSelectedIndex(selectedIndex); + } + + public void addTab(String title, Icon icon, Component component) { + super.addTab(title, icon, component); + TabDesc td = new TabDesc(title, icon, component, null, indexOfComponent(component)); + tabs.put(component, td); + } + + public void addTab(String title, Component component) { + super.addTab(title, component); + TabDesc td = new TabDesc(title, null, component, null, indexOfComponent(component)); + tabs.put(component, td); + } + + public void insertTab(String title, Icon icon, Component component, String tip, int index) { + super.insertTab(title, icon, component, tip, index); + TabDesc td = new TabDesc(title, icon, component, tip, index); + tabs.put(component, td); + } + + public void addTab(String title, Icon icon, Component component, String tip) { + super.addTab(title, icon, component, tip); + TabDesc td = new TabDesc(title, icon, component, tip, indexOfComponent(component)); + tabs.put(component, td); + } + + private Component getTabComponentByIndex(int index) { + for (Component key : tabs.keySet()) { + TabDesc td = tabs.get(key); + if (td.index == index) { + return key; + } + } + return null; + } + + private Component getTabComponentByTitle(String title) { + for (Component key : tabs.keySet()) { + TabDesc td = tabs.get(key); + if (td.title == title) { + return key; + } + } + return null; + } + + public void removeTabAt(int index) { + Component c = getTabComponentByIndex(index); + super.removeTabAt(index); + TabDesc tab = tabs.get(c); + if ( tab!=null ) { + if ( tab.babysitter != null ) { //perhaps better to dock it first + if (tab.babysitter instanceof Window) { + ((Window) tab.babysitter).dispose(); + } + } + tabs.remove(c); + } else { + logger.fine("tabs didn't contain c, someone else removed it."); + //TODO: clean this up. + } + + } + + public void setSelectedIndex(int index) { + if (index != getSelectedIndex()) { + lastSelected = getSelectedIndex(); + } + super.setSelectedIndex(index); + logger.finest("setSelectedIndex "+getSelectedComponent()); + } +} diff --git a/dasCore/src/main/java/org/das2/components/Toolbox.java b/dasCore/src/main/java/org/das2/components/Toolbox.java new file mode 100644 index 000000000..7f9f65b59 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/Toolbox.java @@ -0,0 +1,513 @@ +/* File: Toolbox.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.DasColorBar; +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.SpectrogramRenderer; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dasml.FormTab; +import org.das2.dasml.FormChoice; +import org.das2.dasml.FormList; +import org.das2.dasml.FormPanel; +import org.das2.dasml.FormText; +import org.das2.dasml.FormRadioButtonGroup; +import org.das2.dasml.FormRadioButton; +import org.das2.dasml.FormWindow; +import org.das2.dasml.FormTextField; +import org.das2.dasml.FormCheckBox; +import org.das2.dasml.TransferableFormComponent; +import org.das2.dasml.FormButton; + +import org.das2.graph.dnd.TransferableCanvas; +import org.das2.graph.dnd.TransferableCanvasComponent; +import org.das2.graph.dnd.TransferableRenderer; +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import java.awt.*; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.*; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; +import java.net.URL; + +/** + * A component that allows the user to create new objects and pass them to + * components using the drag and drop interface. The objects that can be + * created are represented by icons. The icons are grouped by tabs for each\ + * group. + * + * @author Edward West + */ +public class Toolbox extends JTabbedPane { + + + + + /** Image used to combine with Icons to create a pointer */ + private static Image pointerOverlay; + + + + + /** Dummy component used for loading images and creating pointer images */ + private static Component dummy = new Component(){}; + + + + + /** initialize comp and load pointer image */ + static { + /** get image from jar resource */ + Class cl = ToolComponent.class; + URL pointerURL = cl.getResource("/images/toolbox/dragpointer.gif"); + Image image = Toolkit.getDefaultToolkit().getImage(pointerURL); + /** Make sure the whole image is loaded */ + MediaTracker tracker = new MediaTracker(dummy); + tracker.addImage(image, 0); + try { + tracker.waitForAll(); + } + catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + pointerOverlay = image; + } + + + + + /** Creates a new instance of Toolbox */ + public Toolbox() { + initializeFormToolComponent(); + initializeGraphToolComponent(); + } + + + + /** initializes the ToolComponent containing icons for form elements */ + private void initializeFormToolComponent() { + String[] ids = { + "form tab", + "window", + "panel", + "static text", + "text field", + "button", + "check box", + "button group", + "radio button", + "choice", + "list" + }; + Class c = Toolbox.class; + Icon[] icons = { + new ImageIcon(c.getResource("/images/toolbox/tab.gif")), + new ImageIcon(c.getResource("/images/toolbox/window.gif")), + new ImageIcon(c.getResource("/images/toolbox/panel.gif")), + new ImageIcon(c.getResource("/images/toolbox/text.gif")), + new ImageIcon(c.getResource("/images/toolbox/textfield.gif")), + new ImageIcon(c.getResource("/images/toolbox/button.gif")), + new ImageIcon(c.getResource("/images/toolbox/checkbox.gif")), + new ImageIcon(c.getResource("/images/toolbox/buttongroup.gif")), + new ImageIcon(c.getResource("/images/toolbox/radiobutton.gif")), + new ImageIcon(c.getResource("/images/toolbox/choice.gif")), + //new ImageIcon(l.getResource("/images/toolbox/list.png")), + }; + + ToolComponent tc = new ToolComponent(ids, icons, 4); + add("Form", tc); + } + + + + + /** initializes the ToolComponent containing icons for graph elements */ + private void initializeGraphToolComponent() { + String[] ids = { + "canvas", + "plot", + "axis", + "time axis", + "spectrogram renderer", + "line plot renderer", + "spectrogram plot" + }; + Class c = Toolbox.class; + Icon[] icons = { + new ImageIcon(c.getResource("/images/toolbox/canvas.gif")), + new ImageIcon(c.getResource("/images/toolbox/plot.gif")), + new ImageIcon(c.getResource("/images/toolbox/axis.gif")), + new ImageIcon(c.getResource("/images/toolbox/taxis.gif")), + new ImageIcon(c.getResource("/images/toolbox/spectrogram.gif")), + new ImageIcon(c.getResource("/images/toolbox/line.gif")), + new ImageIcon(c.getResource("/images/toolbox/spectrogram_plot.gif")) + }; + ToolComponent tc = new ToolComponent(ids, icons, 4); + add("Graph", tc); + } + + + + + + /** Creates a transferable based on the String parameter */ + private static Transferable createTransferable(String id) { + if (id.equals("form tab")) { + return new TransferableFormComponent( + new FormTab(null, "label")); + } + else if (id.equals("window")) { + return new TransferableFormComponent( + new FormWindow(null, "title", 640, 480)); + } + else if (id.equals("panel")) { + return new TransferableFormComponent(new FormPanel()); + } + else if (id.equals("static text")) { + return new TransferableFormComponent(new FormText()); + } + else if (id.equals("text field")) { + return new TransferableFormComponent(new FormTextField(null)); + } + else if (id.equals("button")) { + return new TransferableFormComponent( + new FormButton(null, "label")); + } + else if (id.equals("check box")) { + return new TransferableFormComponent( + new FormCheckBox(null, "label")); + } + else if (id.equals("button group")) { + return new TransferableFormComponent( + new FormRadioButtonGroup()); + } + else if (id.equals("radio button")) { + return new TransferableFormComponent( + new FormRadioButton(null, "label")); + } + else if (id.equals("choice")) { + return new TransferableFormComponent(new FormChoice(null)); + } + else if (id.equals("list")) { + return new TransferableFormComponent(new FormList(null)); + } + else if (id.equals("canvas")) { + return new TransferableCanvas( + DasCanvas.createFormCanvas(null, 640, 480)); + } + else if (id.equals("plot")) { + return new TransferableCanvasComponent( + DasPlot.createNamedPlot(null)); + } + else if (id.equals("axis")) { + return new TransferableCanvasComponent( + DasAxis.createNamedAxis(null)); + } + else if (id.equals("time axis")) { + return new TransferableCanvasComponent( + DasAxis.createNamedAxis(null)); + } + else if (id.equals("spectrogram renderer")) { + DasColorBar cb = DasColorBar.createNamedColorBar(null); + return new TransferableRenderer( + new SpectrogramRenderer(null, cb)); + } + else if (id.equals("line plot renderer")) { + return new TransferableRenderer( + new SymbolLineRenderer()); + } + else if (id.equals("spectrogram plot")) { + DasPlot plot = DasPlot.createNamedPlot(null); + DasColorBar colorBar = DasColorBar.createNamedColorBar( + plot.getDasName() + "_colorbar"); + SpectrogramRenderer renderer + = new SpectrogramRenderer(null, colorBar); + plot.addRenderer(renderer); + return new TransferableCanvasComponent(plot); + } + else { + throw new IllegalArgumentException(id); + } + } + + + + /** Creates an array of cursors to be used for drag and drop operations */ + private static Cursor[] getCursors(String[] ids, Icon[] icons) { + Cursor[] cursors = new Cursor[icons.length]; + Point origin = new Point(0, 0); + for (int i = 0; i < cursors.length; i++) { + int width = icons[i].getIconWidth(); + int height = icons[i].getIconHeight(); + BufferedImage cimage = new BufferedImage(32, 32, + BufferedImage.TYPE_INT_ARGB); + Graphics g = cimage.getGraphics(); + icons[i].paintIcon(dummy, g, 8, 8); + g.drawImage(pointerOverlay, 0, 0, dummy); + cursors[i] =Toolkit.getDefaultToolkit().createCustomCursor( + cimage, origin, ids[i]); + } + return cursors; + } + + + + /** DragGesture and DragSourceListener implementation used to initiate drag + * and drop operations for ToolboxComponents. + */ + private static class ToolboxDragGestureListener + implements DragGestureListener, DragSourceListener { + + /** A DragGestureRecognizer has detected + * a platform-dependent drag initiating gesture and + * is notifying this listener + * in order for it to initiate the action for the user. + *

+ * @param dge the DragGestureEvent describing + * the gesture that has just occurred + */ + public void dragGestureRecognized(DragGestureEvent dge) { + ToolComponent tc = (ToolComponent)dge.getComponent(); + int index = tc.selectedIndex; + if (index >= 0) { + Cursor dragCursor = tc.cursors[index]; + Transferable t = createTransferable(tc.ids[index]); + dge.startDrag(dragCursor, t, this); + } + } + + /** This method is invoked to signify that the Drag and Drop + * operation is complete. The getDropSuccess() method of + * the DragSourceDropEvent can be used to + * determine the termination state. The getDropAction() method + * returns the operation that the drop site selected + * to apply to the Drop operation. Once this method is complete, the + * current DragSourceContext and + * associated resources become invalid. + * + * @param dsde the DragSourceDropEvent + */ + public void dragDropEnd(DragSourceDropEvent dsde) { + } + + /** Called as the cursor's hotspot enters a platform-dependent drop site. + * This method is invoked when all the following conditions are true: + *

    + *
  • The cursor's hotspot enters the operable part of a platform- + * dependent drop site. + *
  • The drop site is active. + *
  • The drop site accepts the drag. + *
+ * + * @param dsde the DragSourceDragEvent + */ + public void dragEnter(DragSourceDragEvent dsde) { + } + + /** Called as the cursor's hotspot exits a platform-dependent drop site. + * This method is invoked when any of the following conditions are true: + *
    + *
  • The cursor's hotspot no longer intersects the operable part + * of the drop site associated with the previous dragEnter() invocation. + *
+ * OR + *
    + *
  • The drop site associated with the previous dragEnter() invocation + * is no longer active. + *
+ * OR + *
    + *
  • The current drop site has rejected the drag. + *
+ * + * @param dse the DragSourceEvent + */ + public void dragExit(DragSourceEvent dse) { + } + + /** Called as the cursor's hotspot moves over a platform-dependent drop site. + * This method is invoked when all the following conditions are true: + *
    + *
  • The cursor's hotspot has moved, but still intersects the + * operable part of the drop site associated with the previous + * dragEnter() invocation. + *
  • The drop site is still active. + *
  • The drop site accepts the drag. + *
+ * + * @param dsde the DragSourceDragEvent + */ + public void dragOver(DragSourceDragEvent dsde) { + } + + /** Called when the user has modified the drop gesture. + * This method is invoked when the state of the input + * device(s) that the user is interacting with changes. + * Such devices are typically the mouse buttons or keyboard + * modifiers that the user is interacting with. + * + * @param dsde the DragSourceDragEvent + */ + public void dropActionChanged(DragSourceDragEvent dsde) { + } + + } + + + + /** Displays a group of icons for the Toolbox component. */ + private static class ToolComponent extends JComponent { + + /** array of icons for this group */ + private Icon[] icons; + + + + /** array of ids for this group */ + private String[] ids; + + + + /** array of drag cursors for this group */ + private Cursor[] cursors; + + + + + /** maximum number of icons per row */ + private int width; + + + + + /** number of rows of icons */ + private int height; + + + + + /** The index of the last icon that that was the recipient of a + * mousePressed event + */ + private int selectedIndex = -1; + + + + /** Create a new ToolComponent */ + private ToolComponent(String[] ids, Icon [] icons, int w) { + this.ids = ids; + this.icons = icons; + this.cursors = getCursors(ids, icons); + this.width = w; + this.height = (int)Math.ceil((double)icons.length / (double)w); + setBackground(Color.WHITE); + setForeground(Color.BLACK); + setTransferHandler(null); + DragSource dragSource = DragSource.getDefaultDragSource(); + dragSource.createDefaultDragGestureRecognizer(this, + DnDConstants.ACTION_COPY, new ToolboxDragGestureListener()); + addMouseListener(new InputListener()); + ToolTipManager.sharedInstance().registerComponent(this); + }//public ToolComponent + + protected void paintComponent(Graphics g){ + Rectangle clip = g.getClipBounds(); + g.setColor(getBackground()); + if (clip == null) { + g.fillRect(0, 0, getWidth(), getHeight()); + } + else { + g.fillRect(clip.x, clip.y, clip.width, clip.height); + } + g.setColor(getForeground()); + for(int index = 0; index < icons.length; index++) { + int i = index % width; + int j = index / width; + int x = i * 32 + 7; + int y = j * 32 + 7; + icons[index].paintIcon(this,g, x, y); + g.drawRect(x - 1, y - 1, 24, 24); + } + }//public void paintComponent(Graphics g) + + public Dimension getPreferredSize() { + int w = width * 32 + 6; + int h = height * 32 + 6; + return new Dimension(w, h); + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + public Dimension getMaximumSize() { + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + private int positionToIndex(int x, int y) { + for (int index = 0; index < icons.length; index++) { + int i = index % width; + int j = index / width; + int xi = i * 32 + 7; + int yi = j * 32 + 7; + if (x >= xi && x < xi + 24 && y >= yi && y < yi + 24) { + return index; + } + } + return -1; + } + + public String getToolTipText(MouseEvent event) { + int index = positionToIndex(event.getX(), event.getY()); + if (index == -1) { + return null; + } + else { + return ids[index]; + } + } + + } + + /** A listener to listen for mousePressed events on a ToolComponent */ + private static class InputListener extends MouseInputAdapter { + public void mousePressed(MouseEvent e) { + ToolComponent tc = (ToolComponent)e.getComponent(); + tc.selectedIndex = tc.positionToIndex(e.getX(), e.getY()); + } + } + + public static void main(String[] args) { + JFrame frame = new JFrame(); + frame.getContentPane().add(new Toolbox(), "Center"); + frame.getContentPane().add(new JTextArea(10, 10), "West"); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } + +} diff --git a/dasCore/src/main/java/org/das2/components/VerticalSpectrogramAverager.java b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramAverager.java new file mode 100755 index 000000000..2d275dfb4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramAverager.java @@ -0,0 +1,195 @@ +/* File: VerticalSpectrogramAverager.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.DasColumn; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.DatumRange; +import org.das2.DasException; +import org.das2.datum.Datum; +import org.das2.event.DataRangeSelectionEvent; +import org.das2.event.DataRangeSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + + +public class VerticalSpectrogramAverager extends DasPlot implements DataRangeSelectionListener { + + private JDialog popupWindow; + + private Datum yValue; + + private DasPlot parentPlot; + private SymbolLineRenderer renderer; + + protected VerticalSpectrogramAverager(DasPlot plot, DasAxis xAxis, DasAxis yAxis) { + super(xAxis, yAxis); + parentPlot = plot; + renderer= new SymbolLineRenderer(); + addRenderer(renderer); + } + + public static VerticalSpectrogramAverager createAverager(DasPlot plot, TableDataSetConsumer dataSetConsumer) { + DasAxis sourceYAxis = plot.getYAxis(); + DasAxis xAxis = sourceYAxis.createAttachedAxis(DasAxis.HORIZONTAL); + DasAxis yAxis = dataSetConsumer.getZAxis().createAttachedAxis(DasAxis.VERTICAL); + return new VerticalSpectrogramAverager(plot, xAxis, yAxis); + } + + public void showPopup() { + if (SwingUtilities.isEventDispatchThread()) { + showPopupImpl(); + } else { + Runnable r = new Runnable() { + @Override + public void run() { + showPopupImpl(); + } + }; + } + } + + /** This method should ONLY be called by the AWT event thread */ + private void showPopupImpl() { + if (popupWindow == null) { + createPopup(); + } + popupWindow.setVisible(true); + } + + /** This method should ONLY be called by the AWT event thread */ + private void createPopup() { + int width = parentPlot.getCanvas().getWidth() / 2; + int height = parentPlot.getCanvas().getHeight() / 2; + DasCanvas canvas = new DasCanvas(width, height); + DasRow row = new DasRow(canvas, null, 0, 1.0, 3, -5, 0, 0 ); + DasColumn column = new DasColumn(canvas, null, 0, 1.0, 7, -3, 0, 0 ); + canvas.add(this, row, column); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + close.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + Window parentWindow = SwingUtilities.getWindowAncestor(parentPlot); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } else { + popupWindow = new JDialog(); + } + popupWindow.setTitle("Vertical Slicer"); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen(parentLocation, parentPlot.getCanvas()); + popupWindow.setLocation(parentLocation.x + parentPlot.getCanvas().getWidth(),parentLocation.y); + } + + @Override + protected void drawContent(Graphics2D g) { + super.drawContent(g); + /*int ix= (int)this.getXAxis().transform(yValue); + DasRow row= this.getRow(); + int iy0= (int)row.getDMinimum(); + int iy1= (int)row.getDMaximum(); + g.drawLine(ix+3,iy0,ix,iy0+3); + g.drawLine(ix-3,iy0,ix,iy0+3); + g.drawLine(ix+3,iy1,ix,iy1-3); + g.drawLine(ix-3,iy1,ix,iy1-3);*/ + } + + @Override + public void dataRangeSelected(DataRangeSelectionEvent e) { + DataSet ds = e.getDataSet(); + if (ds==null || !(ds instanceof TableDataSet)) + return; + TableDataSet xtys = (TableDataSet)ds; + Datum xValue1 = e.getMinimum(); + Datum xValue2 = e.getMaximum(); + + if ( xValue2.equals(xValue1) ) { + return; + } + + this.setTitle( new DatumRange( xValue1, xValue2 ).toString() ); + + RebinDescriptor ddX = new RebinDescriptor(xValue1, xValue2, 1, false); + ddX.setOutOfBoundsAction(RebinDescriptor.MINUSONE); + AverageTableRebinner rebinner = new AverageTableRebinner(); + try { + TableDataSet rebinned = (TableDataSet)rebinner.rebin(xtys, ddX, null, null); + VectorDataSet ds1 = rebinned.getXSlice(0); + renderer.setDataSet(ds1); + } catch (DasException de) { + //Do nothing. + } + + if (!(popupWindow == null || popupWindow.isVisible()) || getCanvas() == null) { + showPopup(); + } else { + repaint(); + } + } + + @Override + protected void uninstallComponent() { + super.uninstallComponent(); + } + + @Override + protected void installComponent() { + super.installComponent(); + getCanvas().getGlassPane().setVisible(false); + } + +} diff --git a/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java new file mode 100755 index 000000000..cbabcaf6a --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java @@ -0,0 +1,234 @@ +/* File: VerticalSpectrogramSlicer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.DasColumn; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; +import org.das2.graph.DasPlot; +import org.das2.graph.DasAxis; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.datum.TimeLocationUnits; +import org.das2.system.DasLogger; +import org.das2.datum.Datum; +import org.das2.event.DataPointSelectionEvent; +import org.das2.event.DataPointSelectionListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.*; + + +public class VerticalSpectrogramSlicer +extends DasPlot implements DataPointSelectionListener { + + private JDialog popupWindow; + private DasPlot parentPlot; + protected Datum yValue; + private long eventBirthMilli; + private SymbolLineRenderer renderer; + private Color yMarkColor = new Color(230,230,230); + + protected VerticalSpectrogramSlicer(DasPlot parent, DasAxis xAxis, DasAxis yAxis) { + super( xAxis, yAxis); + this.parentPlot = parent; + renderer= new SymbolLineRenderer(); + addRenderer(renderer); + } + + protected void setDataSet( VectorDataSet ds ) { + renderer.setDataSet(ds); + } + + public static VerticalSpectrogramSlicer createSlicer( DasPlot plot, TableDataSetConsumer dataSetConsumer) { + DasAxis sourceYAxis = plot.getYAxis(); + DasAxis sourceZAxis = dataSetConsumer.getZAxis(); + DasAxis xAxis = sourceYAxis.createAttachedAxis(DasAxis.HORIZONTAL); + DasAxis yAxis = sourceZAxis.createAttachedAxis(DasAxis.VERTICAL); + return new VerticalSpectrogramSlicer(plot, xAxis, yAxis); + } + + public static VerticalSpectrogramSlicer createSlicer( DasPlot plot, DasAxis xAxis, TableDataSetConsumer dataSetConsumer) { + DasAxis sourceYAxis = plot.getYAxis(); + DasAxis sourceZAxis = dataSetConsumer.getZAxis(); + DasAxis yAxis = sourceZAxis.createAttachedAxis(DasAxis.VERTICAL); + return new VerticalSpectrogramSlicer(plot, xAxis, yAxis); + } + + public void showPopup() { + if (SwingUtilities.isEventDispatchThread()) { + showPopupImpl(); + } + else { + Runnable r = new Runnable() { + public void run() { + showPopupImpl(); + } + }; + } + } + + /** This method should ONLY be called by the AWT event thread */ + private void showPopupImpl() { + if (popupWindow == null) { + createPopup(); + } + popupWindow.setVisible(true); + } + + /** This method should ONLY be called by the AWT event thread */ + private void createPopup() { + int width = parentPlot.getCanvas().getWidth() / 2; + int height = parentPlot.getCanvas().getHeight() / 2; + DasCanvas canvas = new DasCanvas(width, height); + DasRow row = new DasRow(canvas, 0.1, 0.9); + DasColumn column = new DasColumn(canvas, 0.1, 0.9); + canvas.add(this, row, column); + + JPanel content = new JPanel(new BorderLayout()); + + JPanel buttonPanel = new JPanel(); + BoxLayout buttonLayout = new BoxLayout(buttonPanel, BoxLayout.X_AXIS); + JButton close = new JButton("Hide Window"); + close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + popupWindow.setVisible(false); + } + }); + buttonPanel.setLayout(buttonLayout); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(close); + + content.add(canvas, BorderLayout.CENTER); + content.add(buttonPanel, BorderLayout.SOUTH); + + Window parentWindow = SwingUtilities.getWindowAncestor(parentPlot); + if (parentWindow instanceof Frame) { + popupWindow = new JDialog((Frame)parentWindow); + } + else if (parentWindow instanceof Dialog) { + popupWindow = new JDialog((Dialog)parentWindow); + } + else { + popupWindow = new JDialog(); + } + popupWindow.setTitle("Vertical Slicer"); + popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + popupWindow.setContentPane(content); + popupWindow.pack(); + + Point parentLocation = new Point(); + SwingUtilities.convertPointToScreen(parentLocation, parentPlot.getCanvas()); + popupWindow.setLocation(parentLocation.x + parentPlot.getCanvas().getWidth(),parentLocation.y); + } + + protected void drawContent(Graphics2D g) { + long x; + x= System.currentTimeMillis()-eventBirthMilli; + + int ix= (int)this.getXAxis().transform(yValue); + DasRow row= this.getRow(); + int iy0= (int)row.getDMinimum(); + int iy1= (int)row.getDMaximum(); + g.drawLine(ix+3,iy0,ix,iy0+3); + g.drawLine(ix-3,iy0,ix,iy0+3); + g.drawLine(ix+3,iy1,ix,iy1-3); + g.drawLine(ix-3,iy1,ix,iy1-3); + + g.setColor(yMarkColor); + g.drawLine( ix, iy0+4, ix, iy1-4 ); + + super.drawContent(g); + x= System.currentTimeMillis()-eventBirthMilli; + //org.das2.util.DasDie.println("event handled in "+x+" milliseconds"); + } + + protected boolean isPopupVisible() { + return ( popupWindow != null && popupWindow.isVisible()) && getCanvas() != null; + } + + public void dataPointSelected(DataPointSelectionEvent e) { + long xxx[]= { 0,0,0,0 }; + xxx[0] = System.currentTimeMillis()-e.birthMilli; + + DataSet ds = e.getDataSet(); + if (ds==null || !(ds instanceof TableDataSet)) + return; + + TableDataSet tds = (TableDataSet)ds; + + VectorDataSet sliceDataSet= tds.getXSlice( DataSetUtil.closestColumn( tds, e.getX() ) ); + + renderer.setDataSet(sliceDataSet); + DasLogger.getLogger(DasLogger.GUI_LOG).finest("setDataSet sliceDataSet"); + if (!isPopupVisible()) { + showPopup(); + } + + yValue= e.getY(); + Datum xValue = e.getX(); + + DatumFormatter formatter; + if ( xValue.getUnits() instanceof TimeLocationUnits ) { + formatter= TimeDatumFormatter.DEFAULT; + } else { + formatter= xValue.getFormatter(); + } + + setTitle("x: "+ formatter.format(xValue) + " y: "+yValue); + + eventBirthMilli= e.birthMilli; + } + + protected void uninstallComponent() { + super.uninstallComponent(); + } + + protected void installComponent() { + super.installComponent(); + getCanvas().getGlassPane().setVisible(false); + } + + protected void processDasUpdateEvent(org.das2.event.DasUpdateEvent e) { + if (isDisplayable()) { + updateImmediately(); + resize(); + } + } + + public Color getYMarkColor() { + return yMarkColor; + } + + public void setYMarkColor(Color yMarkColor) { + this.yMarkColor = yMarkColor; + } +} diff --git a/dasCore/src/main/java/org/das2/components/package.html b/dasCore/src/main/java/org/das2/components/package.html new file mode 100644 index 000000000..37e3d1e28 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/package.html @@ -0,0 +1,4 @@ + + Provides GUI components for building applications. Classes are generally +instances of Java Swing's JComponent. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/BooleanEditor.java b/dasCore/src/main/java/org/das2/components/propertyeditor/BooleanEditor.java new file mode 100644 index 000000000..4eda4af37 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/BooleanEditor.java @@ -0,0 +1,188 @@ +/* + * BooleanEditor.java + * + * Created on April 14, 2005, 9:18 AM + */ + +package org.das2.components.propertyeditor; + +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.EventObject; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import javax.swing.DefaultButtonModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JCheckBox; +import javax.swing.JList; +import javax.swing.JTable; +import javax.swing.JToggleButton; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; + +/** + * + * @author eew + */ +public class BooleanEditor implements java.beans.PropertyEditor, TableCellEditor { + + private JCheckBox editor; + private Model model; + private boolean selected; + private Class type; + private PropertyChangeSupport pcSupport; + private EventListenerList listeners = new EventListenerList(); + + /** Creates a new instance of BooleanEditor */ + public BooleanEditor() { + pcSupport = new PropertyChangeSupport(this); + } + + private void initEditor() { + if (editor == null) { + model = new Model(); + editor = new JCheckBox(); + editor.setModel(model); + } + } + + public String getAsText() { + return String.valueOf(selected); + } + + public Object getValue() { + return selected ? Boolean.TRUE : Boolean.FALSE; + } + + public void setAsText(String str) throws IllegalArgumentException { + Boolean value; + if ("true".equalsIgnoreCase(str)) { + value = Boolean.TRUE; + } + else if ("false".equalsIgnoreCase(str)) { + value = Boolean.FALSE; + } + else { + throw new IllegalArgumentException(str); + } + setValue(value); + } + + public void setValue(Object obj) { + Boolean oldValue; + Boolean value = (Boolean)obj; + + if (selected ^ value.booleanValue()) { + oldValue = selected ? Boolean.TRUE : Boolean.FALSE; + selected = value.booleanValue(); + pcSupport.firePropertyChange("value", oldValue, value); + } + } + + public boolean supportsCustomEditor() { + return true; + } + + public Component getCustomEditor() { + initEditor(); + return editor; + } + + public String getJavaInitializationString() { return "???"; } + + public String[] getTags() { return null; } + + public boolean isPaintable() { return false; } + + public void paintValue(Graphics g, Rectangle r) {} + + public void addPropertyChangeListener(PropertyChangeListener l) { + pcSupport.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + pcSupport.removePropertyChangeListener(l); + } + + /*TableCellEditor stuff*/ + + public Object getCellEditorValue() { + return selected ? Boolean.TRUE : Boolean.FALSE; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + initEditor(); + editor.setForeground(table.getForeground()); + editor.setBackground(table.getBackground()); + setValue(value); + return editor; + } + + public boolean isCellEditable(EventObject evt) { return true; } + + public boolean shouldSelectCell(EventObject evt) { return true; } + + public boolean stopCellEditing() { + fireEditingStopped(); + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + } + + public void addCellEditorListener(CellEditorListener l) { + listeners.add(CellEditorListener.class, l); + } + + public void removeCellEditorListener(CellEditorListener l) { + listeners.add(CellEditorListener.class, l); + } + + private ChangeEvent evt; + + private void fireEditingStopped() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i+=2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener)l[i+1]; + if (evt == null) { evt = new ChangeEvent(this); } + cel.editingStopped(evt); + } + } + } + + private void fireEditingCanceled() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i+=2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener)l[i+1]; + if (evt == null) { evt = new ChangeEvent(this); } + cel.editingCanceled(evt); + } + } + } + + private class Model extends JToggleButton.ToggleButtonModel { + private Model() {} + public void setSelected(boolean b) { + setValue(b ? Boolean.TRUE : Boolean.FALSE); + fireEditingStopped(); + } + public boolean isSelected() { + return ((Boolean)getValue()).booleanValue(); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/ColorCellRenderer.java b/dasCore/src/main/java/org/das2/components/propertyeditor/ColorCellRenderer.java new file mode 100644 index 000000000..d9c7f049d --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/ColorCellRenderer.java @@ -0,0 +1,121 @@ +/* + * ColorCellRenderer.java + * + * Created on April 28, 2005, 4:39 PM + */ + +package org.das2.components.propertyeditor; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.util.HashMap; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JTable; +import javax.swing.ListCellRenderer; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.table.TableCellRenderer; + +/** + * + * @author eew + */ +class ColorCellRenderer implements ListCellRenderer, TableCellRenderer, Icon { + private static Map names = new HashMap(); + static { + names.put(Color.BLACK, "black"); + names.put(Color.WHITE, "white"); + names.put(Color.BLUE, "blue"); + names.put(Color.CYAN, "cyan"); + names.put(Color.DARK_GRAY, "dark gray"); + names.put(Color.GRAY, "gray"); + names.put(Color.GREEN, "green"); + names.put(Color.LIGHT_GRAY, "light gray"); + names.put(Color.MAGENTA, "magenta"); + names.put(Color.ORANGE, "orange"); + names.put(Color.PINK, "pink"); + names.put(Color.RED, "red"); + names.put(Color.YELLOW, "yellow"); + } + + private JLabel label; + private Border noFocusBorder = BorderFactory.createEmptyBorder(1, 1, 1, 1); + private Color iconColor; + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int rowIndex, int columnIndex) { + Color f = isSelected ? table.getSelectionForeground() : table.getForeground(); + Color b = isSelected ? table.getSelectionBackground() : table.getBackground(); + return getLabel(table, f, b, value, isSelected, hasFocus); + } + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) { + Color f = isSelected ? list.getSelectionForeground() : list.getForeground(); + Color b = isSelected ? list.getSelectionBackground() : list.getBackground(); + return getLabel(list, f, b, value, isSelected, hasFocus); + } + + public Component getLabel(JComponent c, Color f, Color b, Object value, boolean isSelected, boolean hasFocus) { + initLabel(); + label.setForeground(f); + label.setBackground(b); + label.setEnabled(c.isEnabled()); + label.setFont(c.getFont()); + label.setBorder(hasFocus ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder); + if (value instanceof Color) { + String name = (String)names.get(value); + if (name == null) { + if ( ((Color)value).getAlpha()==0 ) { + name="none"; + } else { + name = toString((Color)value); + } + } + label.setIcon(this); + label.setText(name); + iconColor = (Color)value; + } + else { + label.setIcon(null); + label.setText(String.valueOf(value)); + } + return label; + } + + private static String toString(Color c) { + return "[" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + "]"; + } + + private void initLabel() { + if (label == null) { + label = new JLabel(); + label.setOpaque(true); + label.setBorder(noFocusBorder); + } + } + + public int getIconHeight() { return 16; } + + public int getIconWidth() { return 16; } + + public void paintIcon(Component c, Graphics g, int x, int y) { + Color save = g.getColor(); + if ( iconColor.getAlpha()!=255 ) { + for ( int j=0; j<16/4; j++ ) { + for ( int i=0; i<16/4; i++ ) { + g.setColor( (i-j)%2 ==0 ? Color.GRAY : Color.WHITE ); + g.fillRect( x+i*4,y+j*4,4,4); + } + } + } + g.setColor(iconColor); + g.fillRect(x, y, getIconWidth(), getIconHeight()); + g.setColor(save); + } + +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/ColorEditor.java b/dasCore/src/main/java/org/das2/components/propertyeditor/ColorEditor.java new file mode 100644 index 000000000..dd94fc368 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/ColorEditor.java @@ -0,0 +1,200 @@ +/* + * ColorEditor.java + * + * Created on April 19, 2005, 2:52 PM + */ + +package org.das2.components.propertyeditor; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyEditorSupport; +import java.util.ArrayList; +import java.util.List; +import javax.swing.AbstractCellEditor; +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.JColorChooser; +import javax.swing.JComboBox; +import javax.swing.JTable; +import javax.swing.table.TableCellEditor; + +/** + * + * @author eew + */ +public class ColorEditor extends AbstractCellEditor implements java.beans.PropertyEditor, TableCellEditor { + + private static List colors = new ArrayList(); + static { + colors.add(Color.BLACK); + colors.add(Color.WHITE); + colors.add(Color.BLUE); + colors.add(Color.CYAN); + colors.add(Color.DARK_GRAY); + colors.add(Color.GRAY); + colors.add(Color.GREEN); + colors.add(Color.LIGHT_GRAY); + colors.add(Color.MAGENTA); + colors.add(Color.ORANGE); + colors.add(Color.PINK); + colors.add(Color.RED); + colors.add(Color.YELLOW); + colors.add( new Color(0,true) ); + } + + private JColorChooser custom; + private PropertyEditorSupport editorSupport; + private JComboBox choice; + + /** Creates a new instance of ColorEditor */ + public ColorEditor() { + editorSupport = new PropertyEditorSupport(this){}; + custom = new JColorChooser(); + choice = new JComboBox(new ColorChoiceModel()) { + public void setBounds(int x, int y, int width, int height) { + Dimension preferred = getPreferredSize(); + super.setBounds(x, y, width, preferred.height); + } + }; + choice.setRenderer(new ColorCellRenderer()); + choice.setBorder(null); + choice.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED + && choice.isDisplayable()) { + stopCellEditing(); + } + } + }); + custom.addPropertyChangeListener("color", new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + setValue(e.getNewValue()); + } + }); + } + + public boolean supportsCustomEditor() { return true; } + + public String getAsText() { + int rgb= ((Color)editorSupport.getValue()).getRGB(); + String hex; + if ( rgb==0 ) { + hex= "#000000"; + } else { + hex= "#"+Integer.toHexString( rgb ).substring(2); + } + return hex; + } + + public Component getCustomEditor() { + Color c = (Color)getValue(); + custom.setColor(c); + return custom; + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + editorSupport.addPropertyChangeListener(l); + } + + public Object getCellEditorValue() { + return editorSupport.getValue(); + } + + public String getJavaInitializationString() { + return "???"; + } + + public String[] getTags() { + return null; + } + + public Object getValue() { + return editorSupport.getValue(); + } + + public boolean isPaintable() { + return false; + } + + public void paintValue(Graphics graphics, Rectangle rectangle) { + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + editorSupport.removePropertyChangeListener(l); + } + + public void setAsText(String str) throws IllegalArgumentException { + Color c= Color.decode(str); + setValue(c); + } + + public void setValue(Object obj) { + Object oldValue= this.editorSupport.getValue(); + editorSupport.setValue(obj); + if ( oldValue!=obj ) { + choice.setSelectedItem(obj); + choice.repaint(); + } + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row, int column) { + setValue(value); + choice.setSelectedItem(value); + choice.setForeground(table.getForeground()); + choice.setBackground(table.getBackground()); + return choice; + } + + public Component getSmallEditor() { + choice.setSelectedItem(getValue()); + return choice; + } + + private class ColorChoiceModel extends AbstractListModel implements ComboBoxModel { + + private final String CUSTOM_LABEL = "custom..."; + + public Object getElementAt(int index) { + if (index < colors.size()) { + return colors.get(index); + } + else if (index == colors.size()) { + return CUSTOM_LABEL; + } + else { + throw new IndexOutOfBoundsException(Integer.toString(index)); + } + } + + public Object getSelectedItem() { + return getValue(); + } + + public int getSize() { + return colors.size() + 1; + } + + public void setSelectedItem(Object obj) { + if (obj instanceof Color) { + setValue(obj); + } + else if (CUSTOM_LABEL.equals(obj)) { + Color c = custom.showDialog(choice, "Color Editor", (Color)getValue()); + if (c != null) { + setValue(c); + } + } + else { + throw new IllegalArgumentException(String.valueOf(obj)); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/Displayable.java b/dasCore/src/main/java/org/das2/components/propertyeditor/Displayable.java new file mode 100644 index 000000000..dc443d74f --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/Displayable.java @@ -0,0 +1,26 @@ +package org.das2.components.propertyeditor; + +import javax.swing.Icon; + +/** Type-safe enumerations that are used as property types + * that are editable with a PropertyEditor should + * implement this interface. + * + */ +public interface Displayable { + + /** return a String that will help the user + * identify this item when choosing from a list. + */ + String getListLabel(); + + /** An icon can be provided that will be shown in a list + * along with the textual description of the element. + * This method should return null if there + * is no icon available. + * + */ + Icon getListIcon(); + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/Editable.java b/dasCore/src/main/java/org/das2/components/propertyeditor/Editable.java new file mode 100644 index 000000000..d22dda6b1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/Editable.java @@ -0,0 +1,11 @@ +package org.das2.components.propertyeditor; + +/** Objects that are instances of classes that implement + * this interface can be expanded in a PropertyEditor + * property list. + * + */ +public interface Editable { + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/Enumeration.java b/dasCore/src/main/java/org/das2/components/propertyeditor/Enumeration.java new file mode 100644 index 000000000..163b99143 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/Enumeration.java @@ -0,0 +1,30 @@ +package org.das2.components.propertyeditor; + +import javax.swing.Icon; + +/** Type-safe enumerations that are used as property types + * that are editable with a PropertyEditor should + * implement this interface. + * + */ +public interface Enumeration { + + /** Type-safe Enumerations implementing this interface + * should override the toString() method to return a + * String that will be helpful to the user + * when choosing this as an option from a list. + * + */ + String toString(); + //TODO: getListLabel() better, because toString should be reserved for programmers. + + /** An icon can be provided that will be shown in a list + * along with the textual description of the element. + * This method should return null if there + * is no icon available. + * + */ + Icon getListIcon(); + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/EnumerationEditor.java b/dasCore/src/main/java/org/das2/components/propertyeditor/EnumerationEditor.java new file mode 100644 index 000000000..be9f7cd50 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/EnumerationEditor.java @@ -0,0 +1,338 @@ +/* + * EnumerationEditor.java + * + * Created on April 14, 2005, 9:18 AM + */ +package org.das2.components.propertyeditor; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EventObject; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JComboBox; +import javax.swing.JList; +import javax.swing.JTable; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; + +/** + * + * @author eew + */ +public class EnumerationEditor implements java.beans.PropertyEditor, TableCellEditor { + + private JComboBox editor; + private Model model; + private Object selected; + private Class type; + private boolean guessType; + private PropertyChangeSupport pcSupport; + private EventListenerList listeners = new EventListenerList(); + private Map valueMap; + private Map nameMap; + private Map toStringMap; + + /** Creates a new instance of EnumerationEditor */ + public EnumerationEditor() { + guessType = true; + pcSupport = new PropertyChangeSupport(this); + } + + protected EnumerationEditor(Class c) { + setClass(c); + guessType = false; + } + + private void initEditor() { + if (editor == null) { + model = new Model(); + editor = new JComboBox(model) { + + public void setBounds(int x, int y, int width, int height) { + Dimension preferred = getPreferredSize(); + super.setBounds(x, y, width, preferred.height); + } + }; + editor.setRenderer(new Renderer()); + editor.setSelectedItem(selected); + } + } + private static final int PUBLIC_STATIC_FINAL = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; + + private void setClass(Class c) { + type = c; + Map valueM = new HashMap(); + Map nameM = new IdentityHashMap(); + Map toStringM = new HashMap(); + + if (c.isEnum()) { + Object[] vals = c.getEnumConstants(); + for (Object o : vals) { + Enum e = (Enum) o; + nameM.put(e.name(), e); + toStringM.put(e.toString(), e); + valueM.put(e, e.name()); + } + + } else { + + Field[] fields = type.getDeclaredFields(); + Set psf = new HashSet(); + for (int i = 0; i < fields.length; i++) { + int modifiers = fields[i].getModifiers(); + if ((modifiers & PUBLIC_STATIC_FINAL) == PUBLIC_STATIC_FINAL) { + psf.add(fields[i]); + } + } + for (Iterator i = psf.iterator(); i.hasNext();) { + try { + Field f = (Field) i.next(); + String name = f.getName(); + Object value = f.get(null); + nameM.put(name, value); + toStringM.put(value.toString(), value); + valueM.put(value, name); + } catch (IllegalAccessException iae) { + IllegalAccessError err = new IllegalAccessError(iae.getMessage()); + err.initCause(iae); + throw err; + } + } + } + nameMap = nameM; + valueMap = valueM; + toStringMap = toStringM; + } + + public String getAsText() { + return selected.toString(); + } + + public Object getValue() { + return selected; + } + + public void setAsText(String str) throws IllegalArgumentException { + Object oldValue; + Object value = nameMap.get(str); + if (value == null) { + value = toStringMap.get(str); + } + if (value == null) { + throw new IllegalArgumentException(str); + } + if (selected != value) { + oldValue = selected; + selected = value; + if ( editor!=null ) { + editor.setSelectedItem(selected); + editor.repaint(); + } + pcSupport.firePropertyChange("value", oldValue, selected); + } + } + + public void setValue(Object obj) { + Class c = getTypeClass(obj); + if (type != c) { + int size = 0; + if (model != null) { + size = model.getSize(); + } + setClass(c); + if (model != null) { + model.fireIntervalRemoved(model, 0, size - 1); + model.fireIntervalAdded(model, 0, model.getSize() - 1); + } + } + Object oldValue = selected; + selected = obj; + if (oldValue != obj) { + pcSupport.firePropertyChange("value", oldValue, selected); + if ( editor!=null ) { + editor.setSelectedItem(selected); + editor.repaint(); + } + } + } + + private Class getTypeClass(Object obj) { + Class c = obj.getClass(); + String name = c.getName(); + if (name.matches(".+?\\$\\d+")) { + c = c.getSuperclass(); + } + return c; + } + + public boolean supportsCustomEditor() { + return true; + } + + public Component getCustomEditor() { + initEditor(); + return editor; + } + + public String getJavaInitializationString() { + return "???"; + } + + public String[] getTags() { + return null; + } + + public boolean isPaintable() { + return false; + } + + public void paintValue(Graphics g, Rectangle r) { + } + + public void addPropertyChangeListener(PropertyChangeListener l) { + pcSupport.addPropertyChangeListener(l); + } + + public void removePropertyChangeListener(PropertyChangeListener l) { + pcSupport.removePropertyChangeListener(l); + } + + /*TableCellEditor stuff*/ + public Object getCellEditorValue() { + return selected; + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + initEditor(); + editor.setForeground(table.getForeground()); + editor.setBackground(table.getBackground()); + setValue(value); + return editor; + } + + public boolean isCellEditable(EventObject evt) { + return true; + } + + public boolean shouldSelectCell(EventObject evt) { + return true; + } + + public boolean stopCellEditing() { + fireEditingStopped(); + return true; + } + + public void cancelCellEditing() { + fireEditingCanceled(); + } + + public void addCellEditorListener(CellEditorListener l) { + listeners.add(CellEditorListener.class, l); + } + + public void removeCellEditorListener(CellEditorListener l) { + listeners.add(CellEditorListener.class, l); + } + private ChangeEvent evt; + + private void fireEditingStopped() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i += 2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener) l[i + 1]; + if (evt == null) { + evt = new ChangeEvent(this); + } + cel.editingStopped(evt); + } + } + } + + private void fireEditingCanceled() { + Object[] l = listeners.getListenerList(); + for (int i = 0; i < l.length; i += 2) { + if (l[i] == CellEditorListener.class) { + CellEditorListener cel = (CellEditorListener) l[i + 1]; + if (evt == null) { + evt = new ChangeEvent(this); + } + cel.editingCanceled(evt); + } + } + } + + private class Renderer extends DefaultListCellRenderer { + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) { + String s = (String) valueMap.get(value); + super.getListCellRendererComponent(list, s, index, isSelected, hasFocus); + if (value instanceof Enumeration) { + setText(value.toString()); + setIcon(((Enumeration) value).getListIcon()); + } + return this; + } + } + + private class Model extends AbstractListModel implements ComboBoxModel { + + private List list; + + private Model() { + list = new ArrayList(nameMap.keySet()); + Collections.sort(list, String.CASE_INSENSITIVE_ORDER); + } + + public Object getSelectedItem() { + return selected; + } + + public void setSelectedItem(Object o) { + setValue(o); + stopCellEditing(); + } + + public Object getElementAt(int index) { + return nameMap.get(list.get(index)); + } + + public int getSize() { + return list.size(); + } + + //TODO: remove? + protected void fireIntervalRemoved(Object source, int index0, int index1) { + super.fireIntervalRemoved(source, index0, index1); + } + + //TODO: remove? + protected void fireIntervalAdded(Object source, int index0, int index1) { + super.fireIntervalAdded(source, index0, index1); + } + + //TODO: remove? + protected void fireContentsChanged(Object source, int index0, int index1) { + super.fireContentsChanged(source, index0, index1); + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointDocumentFilter.java b/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointDocumentFilter.java new file mode 100644 index 000000000..129452a01 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointDocumentFilter.java @@ -0,0 +1,70 @@ +package org.das2.components.propertyeditor; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DocumentFilter; +import javax.swing.text.DocumentFilter.FilterBypass; + +class FloatingPointDocumentFilter extends DocumentFilter { + + public void insertString(FilterBypass fb, int offset, String text, AttributeSet atts) throws BadLocationException { + if (text.length() == 1) { + if (text.charAt(0) == '-') { + String content = fb.getDocument().getText(0, fb.getDocument().getLength()); + int eIndex = Math.max(content.indexOf('e'), content.indexOf('E')); + if (content.length() == 0) { + super.insertString(fb, 0, text, atts); + } + else if (eIndex < 0 || offset <= eIndex) { + if (content.charAt(0) == '-') { + super.remove(fb, 0, 1); + } + else { + super.insertString(fb, 0, text, atts); + } + } + else { + if (content.length() == eIndex+1) { + super.insertString(fb, eIndex+1, text, atts); + } + else if (content.charAt(eIndex+1) == '-') { + super.remove(fb, eIndex+1, 1); + } + else { + super.insertString(fb, eIndex+1, text, atts); + } + } + } + else if (text.charAt(0) == '.') { + String content = fb.getDocument().getText(0, fb.getDocument().getLength()); + int dotIndex = content.indexOf('.'); + if (offset <= dotIndex) { + super.replace(fb, offset, dotIndex-offset+1, text, atts); + } + else if (dotIndex < 0) { + super.insertString(fb, offset, text, atts); + } + } + else if (text.charAt(0) == 'e' || text.charAt(0) == 'E') { + String content = fb.getDocument().getText(0, fb.getDocument().getLength()); + int eIndex = Math.max(content.indexOf('e'), content.indexOf('E')); + if (offset <= eIndex) { + super.replace(fb, offset, eIndex-offset+1, text, atts); + } + else if (eIndex < 0) { + super.insertString(fb, offset, text, atts); + } + } + else if (Character.isDigit(text.charAt(0))) { + super.insertString(fb, offset, text, atts); + } + } + } + + public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet atts) throws BadLocationException { + remove(fb, offset, length); + insertString(fb, offset, text, atts); + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointFormatter.java b/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointFormatter.java new file mode 100644 index 000000000..a2a8594c9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/FloatingPointFormatter.java @@ -0,0 +1,52 @@ +package org.das2.components.propertyeditor; + +import java.text.ParseException; +import javax.swing.JFormattedTextField; +import javax.swing.text.DocumentFilter; + +class FloatingPointFormatter extends JFormattedTextField.AbstractFormatter { + + /** Parses text returning an arbitrary Object. Some + * formatters may return null. + * + * @throws ParseException if there is an error in the conversion + * @param text String to convert + * @return Object representation of text + * + */ + public Object stringToValue(String text) throws ParseException { + try { + Double d = new Double(text); + if (d.doubleValue() == Double.NEGATIVE_INFINITY || + d.doubleValue() == Double.POSITIVE_INFINITY || + d.doubleValue() == Double.NaN) { + throw new ParseException("+/-infinity and NaN are not allowed", 0); + } + return d; + } + catch (NumberFormatException nfe) { + throw new ParseException(nfe.getMessage(), 0); + } + } + + /** Returns the string value to display for value. + * + * @throws ParseException if there is an error in the conversion + * @param value Value to convert + * @return String representation of value + * + */ + public String valueToString(Object value) throws ParseException { + if (value instanceof Number) { + double doubleValue = ((Number)value).doubleValue(); + return value.toString(); + } + else throw new ParseException("value must be of type Number", 0); + } + + protected DocumentFilter getDocumentFilter() { + return new FloatingPointDocumentFilter(); + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyItemTreeNode.java b/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyItemTreeNode.java new file mode 100644 index 000000000..c7ec149ea --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyItemTreeNode.java @@ -0,0 +1,87 @@ +package org.das2.components.propertyeditor; +import org.das2.util.DasExceptionHandler; +import java.beans.IndexedPropertyDescriptor; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +class IndexedPropertyItemTreeNode extends PropertyTreeNode { + + private IndexedPropertyDescriptor indexedPropertyDescriptor; + + private int index; + + IndexedPropertyItemTreeNode(PropertyTreeNode parent, IndexedPropertyDescriptor indexedPropertyDescriptor, int index) { + super(Array.get(parent.value, index)); + setTreeModel(parent.treeModel); + this.index = index; + this.parent = parent; + this.propertyDescriptor = indexedPropertyDescriptor; + this.indexedPropertyDescriptor = indexedPropertyDescriptor; + } + + public boolean getAllowsChildren() { + return indexedPropertyDescriptor.getPropertyEditorClass() == null; + } + + public String getDisplayName() { + return propertyDescriptor.getName() + "[" + index + "]"; + } + + public void flush() { + try { + if (dirty) { + Method writeMethod = indexedPropertyDescriptor.getIndexedWriteMethod(); + writeMethod.invoke(parent.parent.value, new Object[]{new Integer(index), value} ); + dirty = false; + } + if (childDirty) { + for (Iterator i = children.iterator(); i.hasNext(); ) { + PropertyTreeNode child = (PropertyTreeNode)i.next(); + child.flush(); + } + childDirty = false; + } + } catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } catch ( InvocationTargetException e ) { + DasExceptionHandler.handle(e); + } + } + + protected Object read() { + Object[] parentValue= (Object[])parent.read(); + return parentValue[this.index]; + } + + public void refresh( ) { + Object newValue = read(); + boolean foldMe= false; + if ( newValue!=value ) { + boolean allowsChildren= getAllowsChildren(); + if ( allowsChildren ) { + foldMe= true; + } + } + if (newValue != value && newValue != null && !newValue.equals(value)) { + value = newValue; + } + if ( foldMe ) { + children= null; + treeModel.nodeStructureChanged( this ); + } else { + if (getAllowsChildren()) { + if (children != null) { + for (Iterator i = children.iterator(); i.hasNext();) { + PropertyTreeNode child = (PropertyTreeNode)i.next(); + child.refresh( ); + } + } + } + } + + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyTreeNode.java b/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyTreeNode.java new file mode 100644 index 000000000..c32f9330a --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/IndexedPropertyTreeNode.java @@ -0,0 +1,109 @@ +package org.das2.components.propertyeditor; +import java.beans.IndexedPropertyDescriptor; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.MutableTreeNode; + +class IndexedPropertyTreeNode extends PropertyTreeNode { + + private IndexedPropertyDescriptor indexedPropertyDescriptor; + + IndexedPropertyTreeNode(PropertyTreeNode parent, IndexedPropertyDescriptor indexedPropertyDescriptor) throws InvocationTargetException { + super(parent, indexedPropertyDescriptor ); + this.indexedPropertyDescriptor = indexedPropertyDescriptor; + } + + public boolean getAllowsChildren() { + return true; + } + + protected void maybeLoadChildren() { + if (children == null) { + children = new ArrayList(); + int childCount = Array.getLength(value); + for (int i = 0; i < childCount; i++) { + children.add(new IndexedPropertyItemTreeNode(this, indexedPropertyDescriptor, i)); + } + } + } + + public String getDisplayName() { + return propertyDescriptor.getName() + "[]"; + } + + @Override + public Object getDisplayValue() { + return ""+ getChildCount() + " element" + ( getChildCount()!=1 ? "s" : "" ); + } + + + public void flush() { + if (childDirty) { + for (Iterator i = children.iterator(); i.hasNext(); ) { + PropertyTreeNode child = (PropertyTreeNode)i.next(); + child.flush(); + } + childDirty = false; + } + } + + protected Object read() { + if (propertyDescriptor == null) { + return value; + } else { + Method readMethod = propertyDescriptor.getReadMethod(); + if (readMethod == null) { + String pName = propertyDescriptor.getName(); + String pId = value.getClass().getName() + "#" + pName; + throw new IllegalStateException( + "Null read method for: " + pId); + } + try { + return readMethod.invoke(parent.value); + } catch (IllegalAccessException iae) { + Error err = new IllegalAccessError(iae.getMessage()); + err.initCause(iae); + throw err; + } catch (InvocationTargetException ite) { + Throwable cause = ite.getCause(); + if (cause instanceof Error) { + throw (Error)cause; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException)cause; + } else { + throw new RuntimeException(cause); + } + } + } + } + public boolean isCellEditable(int column) { + return false; + } + + public void refresh( ) { + Object newValue = read(); + + value= newValue; + if ( children!=null ) { + if ( children.size()<((Object[])newValue).length ) { + children=null; + treeModel.nodeStructureChanged(this); + } else if ( children.size()>((Object[])newValue).length ) { + children= null; + treeModel.nodeStructureChanged(this); + } + } + if (children != null) { + for (Iterator i = children.iterator(); i.hasNext();) { + PropertyTreeNode child = (PropertyTreeNode)i.next(); + child.refresh( ); + } + } + + } +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/MapEditor.java b/dasCore/src/main/java/org/das2/components/propertyeditor/MapEditor.java new file mode 100644 index 000000000..15bc183d5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/MapEditor.java @@ -0,0 +1,73 @@ +/* + * MapEditor.java + * + * Created on September 27, 2005, 1:37 PM + * + * + */ + +package org.das2.components.propertyeditor; + +import java.util.Iterator; +import java.util.Map; +import javax.swing.JTextArea; + +/** + * + * @author Jeremy + */ +public class MapEditor implements java.beans.PropertyEditor { + Map value; + + public void addPropertyChangeListener(java.beans.PropertyChangeListener propertyChangeListener) { + } + + public String getAsText() { + return "lookup"; + } + + public java.awt.Component getCustomEditor() { + JTextArea result= new JTextArea(20,6); + for ( Iterator i= value.keySet().iterator(); i.hasNext(); ) { + Object key= i.next(); + Object value= i.next(); + result.append(""+key+"=\t"+value+"\n"); + } + return result; + } + + public String getJavaInitializationString() { + return "lookup"; + } + + public String[] getTags() { + return null; + } + + public Object getValue() { + return value; + } + + public boolean isPaintable() { + return false; + } + + public void paintValue(java.awt.Graphics graphics, java.awt.Rectangle rectangle) { + + } + + public void removePropertyChangeListener(java.beans.PropertyChangeListener propertyChangeListener) { + } + + public void setAsText(String str) throws IllegalArgumentException { + } + + public void setValue(Object obj) { + this.value= (Map)obj; + } + + public boolean supportsCustomEditor() { + return true; + } + +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/PeerPropertyTreeNode.java b/dasCore/src/main/java/org/das2/components/propertyeditor/PeerPropertyTreeNode.java new file mode 100644 index 000000000..998a1d3ed --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/PeerPropertyTreeNode.java @@ -0,0 +1,167 @@ +/* + * PeerPropertyTreeNode.java + * + * Created on December 20, 2005, 11:15 AM + * + * + */ + +package org.das2.components.propertyeditor; + +import java.beans.PropertyDescriptor; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; + +/** + * + * @author Jeremy + */ +public class PeerPropertyTreeNode implements PropertyTreeNodeInterface { + PeerPropertyTreeNode parent; + PropertyTreeNode leader; + PropertyTreeNode[] peers; + + public PeerPropertyTreeNode( PeerPropertyTreeNode parent, PropertyTreeNode leader, PropertyTreeNode[] peers ) { + this.parent= parent; + this.leader= leader; + this.peers= peers; + } + + public java.util.Enumeration children() { + return new java.util.Enumeration() { + int index=0; + public boolean hasMoreElements() { + return index= 10000.0) { + value = expFormat.format(doubleValue); + } + } + if ( value instanceof Displayable ) { + setText( ((Displayable)value).getListLabel() ); + } else { + setText(String.valueOf(value)); + } + setOpaque(true); + if (table != null) { + setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + } + if (value instanceof Displayable) { + Displayable enm = (Displayable)value; + setIcon(enm.getListIcon()); + setDisabledIcon(enm.getListIcon()); + } else { + setIcon(null); + setDisabledIcon(null); + } + c = this; + + } + + c.setEnabled(writable); + return c; + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected, boolean isExpanded, boolean isLeaf, int row, boolean hasFocus) { + setText(String.valueOf(value)); + setIcon(null); + setOpaque(false); + setEnabled(true); + return this; + } + + public Dimension getMinimumSize() { + return getPreferredSize(); + } + + /** Return a component that has been configured to display the specified + * value. That component's paint method is then called to + * "render" the cell. If it is necessary to compute the dimensions + * of a list because the list cells do not have a fixed size, this method + * is called to generate a component on which getPreferredSize + * can be invoked. + * + * @param list The JList we're painting. + * @param value The value returned by list.getModel().getElementAt(index). + * @param index The cells index. + * @param isSelected True if the specified cell was selected. + * @param cellHasFocus True if the specified cell has the focus. + * @return A component whose paint() method will render the specified value. + * + * @see JList + * @see ListSelectionModel + * @see ListModel + * + */ + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + if (value instanceof Displayable ) { + Displayable enm = (Displayable)value; + setIcon(enm.getListIcon()); + setText(enm.getListLabel()); + } else { + setIcon(null); + setText(String.valueOf(value)); + } + setOpaque(true); + setBackground(isSelected ? Color.gray : Color.lightGray); + return this; + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditor.java b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditor.java new file mode 100644 index 000000000..400a31291 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditor.java @@ -0,0 +1,493 @@ +/* File: PropertyEditor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.components.propertyeditor; + +import org.das2.components.treetable.TreeTableCellRenderer; +import org.das2.components.treetable.TreeTableModel; +import org.das2.dasml.SerializeUtil; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.graph.DasCanvas; +import org.das2.system.DasLogger; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.StringWriter; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.tree.DefaultTreeModel; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +//import org.apache.xml.serialize.OutputFormat; +//import org.apache.xml.serialize.XMLSerializer; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * This class implements a Hierarchical property editor + * + * @author Edward West + */ +public class PropertyEditor extends JComponent { + + static final Set editableTypes; + + + static { + HashSet set = new HashSet(); + + //Primitives + set.add(byte.class); + set.add(short.class); + set.add(int.class); + set.add(long.class); + set.add(float.class); + set.add(double.class); + set.add(boolean.class); + + //Object types + set.add(String.class); + set.add(Datum.class); + set.add(DatumRange.class); + set.add(Color.class); + //set.add(PsymConnector.class); + + editableTypes = Collections.unmodifiableSet(set); + } + private JTable table; + private JButton closeButton; + private JDialog dialog; + private Object bean; + /* row of the last mouse click. This object is the fellow who is edited when + * applying properties to a group + */ + private int focusRow = 0; + private JPopupMenu popupMenu; + private Logger logger = DasLogger.getLogger(DasLogger.GUI_LOG); + + private PropertyEditor(PropertyTreeNodeInterface root, Object bean) { + this.bean = bean; + setLayout(new BorderLayout()); + this.bean = bean; + + DefaultTreeModel treeModel = new DefaultTreeModel(root, true); + root.setTreeModel(treeModel); + TreeTableCellRenderer tree = new TreeTableCellRenderer(treeModel); + tree.setRootVisible(false); + tree.setShowsRootHandles(true); + TreeTableModel model = new TreeTableModel(root, tree); + table = new JTable(model); + table.setAutoCreateColumnsFromModel(false); + + add(new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER); + + initButtonPanel(bean instanceof DasCanvas); + initPopupMenu(); + + PropertyCellRenderer valueRenderer = new PropertyCellRenderer(); + //PropertyCellEditor editor = new PropertyCellEditor(tree); + PropertyEditorAdapter editor = new PropertyEditorAdapter(); + int cellHeight = 21; // c.getPreferredSize().height; + + table.setRowHeight(cellHeight); + tree.setRowHeight(cellHeight); + tree.setCellRenderer(valueRenderer); + table.getColumnModel().getColumn(0).setCellRenderer(tree); + table.getColumnModel().getColumn(1).setCellRenderer(valueRenderer); + table.getColumnModel().getColumn(0).setMaxWidth(250); + table.getColumnModel().getColumn(0).setPreferredWidth(150); + table.setDefaultEditor(Object.class, editor); + table.addMouseListener(new PropertyTableMouseListener()); + table.setSurrendersFocusOnKeystroke(true); + table.addKeyListener(getKeyListener()); + addActions(table); + table.getSelectionModel().addListSelectionListener(getListSelectionListener()); + } + + public PropertyEditor(Object bean) { + this(new PropertyTreeNode(bean), bean); + if (bean instanceof PropertyTreeNodeInterface) { + throw new IllegalArgumentException("whoops!"); + } + } + + public static PropertyEditor createPeersEditor(Object leader, Object[] peers) { + PropertyTreeNode[] peerNodes = new PropertyTreeNode[peers.length]; + for (int i = 0; i < peers.length; i++) { + peerNodes[i] = new PropertyTreeNode(peers[i]); + } + PeerPropertyTreeNode root = new PeerPropertyTreeNode(null, + new PropertyTreeNode(leader), + peerNodes); + return new PropertyEditor(root, null); + } + + private void addActions(final JTable table) { + table.getActionMap().put("MY_EDIT", new AbstractAction() { + + public void actionPerformed(ActionEvent e) { + table.editCellAt(focusRow, 1); + } + }); + table.getInputMap().put( + KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), + "MY_EDIT"); + } + + private ListSelectionListener getListSelectionListener() { + return new ListSelectionListener() { + + public void valueChanged(ListSelectionEvent e) { + focusRow = table.getSelectedRow(); // we could do a better job here + + logger.fine("focusRow=" + focusRow); + } + }; + } + + private KeyListener getKeyListener() { + KeyAdapter ka; + return new KeyAdapter() { + + public void keyReleased(KeyEvent event) { + logger.fine(String.valueOf(event)); + if (event.getKeyCode() == KeyEvent.VK_RIGHT) { + TreeTableModel model = (TreeTableModel) table.getModel(); + model.expand(focusRow); + } else if (event.getKeyCode() == KeyEvent.VK_LEFT) { + TreeTableModel model = (TreeTableModel) table.getModel(); + model.collapse(focusRow); + } + } + }; + } + + private Action createSaveAction(final Object bean) { + return new AbstractAction("Save") { + + public void actionPerformed(ActionEvent ev) { + try { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { + + public boolean accept(File f) { + return f.toString().matches(".*\\.das2PropertySheet"); + } + + public String getDescription() { + return "*.das2PropertySheet"; + } + }); + chooser.setSelectedFile(new File("default.das2PropertySheet")); + int result = chooser.showSaveDialog(PropertyEditor.this); + if (result == JFileChooser.APPROVE_OPTION) { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + Element element = SerializeUtil.getDOMElement(document, bean); + document.appendChild(element); + OutputStream out = new FileOutputStream(chooser.getSelectedFile()); + + StringWriter writer = new StringWriter(); + DOMImplementation impl = document.getImplementation(); + DOMImplementationLS ls = (DOMImplementationLS)impl.getFeature("LS", "3.0"); + LSSerializer serializer = ls.createLSSerializer(); + LSOutput output = ls.createLSOutput(); + output.setEncoding("UTF-8"); + output.setByteStream(out); + serializer.write(document, output); + + //OutputFormat format = new OutputFormat(org.apache.xml.serialize.Method.XML, "UTF-8", true); + //XMLSerializer serializer = new XMLSerializer( new OutputStreamWriter(out), format); + //serializer.serialize(document); + out.close(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + + private static Document readDocument(File file) throws IOException, ParserConfigurationException, SAXException { + InputStream in = new FileInputStream(file); + InputSource source = new InputSource(); + source.setCharacterStream(new InputStreamReader(in)); + DocumentBuilder builder; + ErrorHandler eh = null; + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + builder = domFactory.newDocumentBuilder(); + builder.setErrorHandler(eh); + Document document = builder.parse(source); + return document; + } + + private Action createLoadAction(final Object bean) { + return new AbstractAction("Load") { + + public void actionPerformed(ActionEvent ev) { + try { + JFileChooser chooser = new JFileChooser(); + chooser.setFileFilter(new javax.swing.filechooser.FileFilter() { + + public boolean accept(File f) { + return f.toString().matches(".*\\.das2PropertySheet"); + } + + public String getDescription() { + return "*.das2PropertySheet"; + } + }); + int result = chooser.showOpenDialog(PropertyEditor.this); + if (result == JFileChooser.APPROVE_OPTION) { + try { + Document document = readDocument(chooser.getSelectedFile()); + Element element = document.getDocumentElement(); + SerializeUtil.processElement(element, bean); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } catch (SAXException e) { + throw new RuntimeException(e); + } + + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + } + + private Action getEditSelectedAction() { + return new AbstractAction("Edit Selected") { + + public void actionPerformed(ActionEvent e) { + PropertyEditor p; + TreeTableModel model = (TreeTableModel) table.getModel(); + PropertyTreeNodeInterface node = (PropertyTreeNodeInterface) model.getNodeForRow(focusRow); + int[] selected = table.getSelectedRows(); + if (selected.length == 1) { + p = new PropertyEditor(node.getValue()); + } else { + Object[] peers = new Object[selected.length]; + for (int i = 0; i < selected.length; i++) { + peers[i] = ((PropertyTreeNode) model.getNodeForRow(selected[i])).getValue(); + } + p = createPeersEditor(node.getValue(), peers); + } + p.showDialog(PropertyEditor.this); + + } + }; + } + + private void initPopupMenu() { + popupMenu = new JPopupMenu(); + popupMenu.add(getEditSelectedAction()); + } + + private void initButtonPanel(boolean saveLoadButton) { + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + if (saveLoadButton) { + JButton saveButton = new JButton(createSaveAction(this.bean)); + buttonPanel.add(saveButton); + JButton loadButton = new JButton(createLoadAction(this.bean)); + buttonPanel.add(loadButton); + } + final JButton apply = new JButton("Apply Changes"); + closeButton = new JButton("Dismiss"); + + ActionListener al = new ActionListener() { + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == apply) { + globalApplyChanges(); + refresh(); + } else if (e.getSource() == closeButton) { + dismissDialog(); + } + } + }; + apply.addActionListener(al); + closeButton.addActionListener(al); + + JButton refresh = new JButton("Refresh"); + refresh.addActionListener(new ActionListener() { + + public void actionPerformed(ActionEvent e) { + refresh(); + } + }); + + buttonPanel.add(refresh); + buttonPanel.add(Box.createHorizontalGlue()); + buttonPanel.add(apply); + buttonPanel.add(closeButton); + add(buttonPanel, BorderLayout.SOUTH); + } + + private void refresh() { + TreeTableModel model = (TreeTableModel) table.getModel(); + PropertyTreeNodeInterface root = (PropertyTreeNodeInterface) model.getRoot(); + root.refresh(); + model.fireTableDataChanged(); + } + + private void globalApplyChanges() { + TreeTableModel model = (TreeTableModel) table.getModel(); + PropertyTreeNodeInterface root = (PropertyTreeNodeInterface) model.getRoot(); + root.flush(); + } + + private void dismissDialog() { + PropertyTreeNodeInterface root = (PropertyTreeNodeInterface) ((TreeTableModel) table.getModel()).getRoot(); + if (root.isDirty()) { + String[] message = new String[]{ + "You have unsaved changes", + "Would you like to apply them?" + }; + int result = JOptionPane.showConfirmDialog(this, message, "", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.CANCEL_OPTION) { + return; + } + if (result == JOptionPane.YES_OPTION) { + globalApplyChanges(); + } + } + dialog.setVisible(false); + dialog.dispose(); + } + + public void showModalDialog(Component c) { + Container top = (c == null ? null : SwingUtilities.getAncestorOfClass(Window.class, c)); + if (top instanceof JFrame) { + dialog = new JDialog((JFrame) top); + } else if (top instanceof JDialog) { + dialog = new JDialog((JDialog) top); + } else { + dialog = new JDialog(); + } + dialog.setModal(true); + dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + dialog.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + dismissDialog(); + } + }); + dialog.setContentPane(this); + dialog.pack(); + if (c != null) { + dialog.setLocationRelativeTo(c); + } + dialog.setVisible(true); + } + + public void showDialog(Component c) { + if (dialog == null) { + Container top = (c == null ? null : SwingUtilities.getAncestorOfClass(Window.class, c)); + if (top instanceof JFrame) { + dialog = new JDialog((JFrame) top); + } else if (top instanceof JDialog) { + dialog = new JDialog((JDialog) top); + } else { + dialog = new JDialog(); + } + + dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); + dialog.addWindowListener(new WindowAdapter() { + + public void windowClosing(WindowEvent e) { + dismissDialog(); + } + }); + dialog.setContentPane(this); + dialog.pack(); + } + if (c != null) { + dialog.setLocationRelativeTo(c); + } + dialog.setVisible(true); + } + + public void doLayout() { + if (SwingUtilities.isDescendingFrom(this, dialog)) { + closeButton.setVisible(true); + } else { + closeButton.setVisible(false); + } + super.doLayout(); + } + + class PropertyTableMouseListener extends MouseAdapter { + + public void mouseClicked(MouseEvent event) { + Point p = event.getPoint(); + int row = table.rowAtPoint(p); + int column = table.columnAtPoint(p); + TreeTableModel model = (TreeTableModel) table.getModel(); + PropertyTreeNodeInterface node = (PropertyTreeNodeInterface) model.getNodeForRow(row); + + focusRow = row; + int modifiers = event.getModifiers() & (MouseEvent.SHIFT_MASK | MouseEvent.CTRL_MASK); + + if (event.getButton() == MouseEvent.BUTTON1 && modifiers == 0 && !node.isLeaf()) { + model.toggleExpanded(row); + } + } + + public void mousePressed(MouseEvent event) { + Point p = event.getPoint(); + focusRow = table.rowAtPoint(p); + if (event.getButton() == MouseEvent.BUTTON3) { + popupMenu.show(PropertyEditor.this.table, event.getX(), event.getY()); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditorAdapter.java b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditorAdapter.java new file mode 100644 index 000000000..f1c20215b --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyEditorAdapter.java @@ -0,0 +1,262 @@ +/* + * PropertyEditorAdapter.java + * + * Created on April 14, 2005, 2:53 PM + */ + +package org.das2.components.propertyeditor; + +import org.das2.beans.BeansUtil; +import org.das2.components.treetable.TreeTableModel; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.IndexedPropertyDescriptor; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.util.EventObject; +import javax.swing.JButton; +import javax.swing.JOptionPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.event.EventListenerList; +import javax.swing.table.TableCellEditor; + +/** + * + * @author eew + */ +public class PropertyEditorAdapter implements TableCellEditor { + + private EventListenerList listenerList = new EventListenerList(); + private PropertyEditor editor; + private EditorState state; + + /* Possible states */ + private EditorState simple = new SimpleEditor(); + private EditorState custom = new CustomEditor(); + private EditorState cellEditor = new CustomTableCellEditor(); + + /** Creates a new instance of PropertyEditorAdapter */ + public PropertyEditorAdapter() { + } + + public void addCellEditorListener(CellEditorListener l) { + listenerList.add(CellEditorListener.class, l); + } + + public void cancelCellEditing() { + if (state != null) state.cancel(); + state = null; + fireEditingCanceled(); + } + + public Object getCellEditorValue() { + return editor.getValue(); + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int rowIndex, int columnIndex) { + TreeTableModel model = (TreeTableModel)table.getModel(); + PropertyTreeNodeInterface node = (PropertyTreeNodeInterface)model.getNodeForRow(rowIndex); + PropertyDescriptor pd = node.getPropertyDescriptor(); + editor = getEditor(pd); + if (editor == null) { + cancelCellEditing(); + } + else { + editor.setValue(value); + } + + if (editor instanceof TableCellEditor) { + state = cellEditor; + } + else if (editor.supportsCustomEditor()) { + state = custom; + } + else { + state = simple; + } + return state.getEditorComponent(table, selected, rowIndex, columnIndex); + } + + private static PropertyEditor getEditor(PropertyDescriptor pd) { + PropertyEditor ed; + if (pd.getPropertyEditorClass() != null) { + try { + Object instance = pd.getPropertyEditorClass().newInstance(); + if (instance instanceof PropertyEditor) { + ed = (PropertyEditor)instance; + } + else { + ed = null; + } + } + catch (InstantiationException ie) { + ed = null; + } + catch (IllegalAccessException iae) { + ed = null; + } + } + else { + ed = BeansUtil.findEditor(pd instanceof IndexedPropertyDescriptor + ? ((IndexedPropertyDescriptor)pd).getIndexedPropertyType() + : pd.getPropertyType()); + } + return ed; + } + + public boolean isCellEditable(EventObject eventObject) { + return true; + } + + public void removeCellEditorListener(CellEditorListener l) { + listenerList.remove(CellEditorListener.class, l); + } + + public boolean shouldSelectCell(EventObject eventObject) { + return true; + } + + public boolean stopCellEditing() { + boolean stopped = state.stop(); + if (stopped) { + fireEditingStopped(); + state = null; + } + return stopped; + } + + private void fireEditingCanceled() { + Class clazz = CellEditorListener.class; + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = 0; i < listeners.length; i+=2) { + if (listeners[i] == clazz) { + CellEditorListener l = (CellEditorListener)listeners[i+1]; + if (e == null) { e = new ChangeEvent(this); } + l.editingCanceled(e); + } + } + } + + private void fireEditingStopped() { + Class clazz = CellEditorListener.class; + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = 0; i < listeners.length; i+=2) { + if (listeners[i] == clazz) { + CellEditorListener l = (CellEditorListener)listeners[i+1]; + if (e == null) { e = new ChangeEvent(this); } + l.editingStopped(e); + } + } + } + + private static interface EditorState { + void cancel(); + boolean stop(); + Component getEditorComponent(JTable table, boolean selected, int rowIndex, int columnIndex); + } + + private class SimpleEditor implements EditorState, ActionListener { + private JTextField textField; + public void cancel() {} + public boolean stop() { + String text = textField.getText(); + try { + editor.setAsText(text); + return true; + } + catch (IllegalArgumentException iae) { + return false; + } + } + public Component getEditorComponent(JTable table, boolean selected, int rowIndex, int columnIndex) { + initTextField(); + textField.setText(editor.getAsText()); + return textField; + } + public void actionPerformed(ActionEvent e) { + stopCellEditing(); + } + private void initTextField() { + if (textField == null) { + textField = new JTextField(); + textField.setBorder(null); + textField.addActionListener(this); + } + } + } + + private class CustomEditor implements EditorState, ActionListener { + private JButton button; + + public boolean stop() { + return true; + } + + public void cancel() { + } + + public Component getEditorComponent(JTable table, boolean selected, int rowIndex, int columnIndex) { + init(); + String s = editor.getAsText(); + if (s == null) { + s = String.valueOf(editor.getValue()); + } + button.setText(s); + return button; + } + + public void actionPerformed(ActionEvent e) { + Component customEditor = editor.getCustomEditor(); + int result = JOptionPane.showConfirmDialog(button, customEditor, "", JOptionPane.OK_CANCEL_OPTION); + if (result == JOptionPane.CLOSED_OPTION || result == JOptionPane.CANCEL_OPTION) { + cancelCellEditing(); + } + else { + stopCellEditing(); + } + } + + private void init() { + if (button == null) { + button = new JButton(); + button.setBorder(null); + button.addActionListener(this); + } + } + } + + private class CustomTableCellEditor implements EditorState, CellEditorListener { + public Component getEditorComponent(JTable table, boolean selected, int rowIndex, int columnIndex) { + TableCellEditor tce = (TableCellEditor)editor; + Component c = tce.getTableCellEditorComponent(table, editor.getValue(), selected, rowIndex, columnIndex); + tce.addCellEditorListener(this); + return c; + } + public boolean stop() { + TableCellEditor tce = (TableCellEditor)editor; + return tce.stopCellEditing(); + } + public void cancel() { + TableCellEditor tce = (TableCellEditor)editor; + tce.cancelCellEditing(); + } + + public void editingStopped(ChangeEvent e) { + TableCellEditor tce = (TableCellEditor)editor; + tce.removeCellEditorListener(this); + PropertyEditorAdapter.this.fireEditingStopped(); + } + + public void editingCanceled(ChangeEvent e) { + TableCellEditor tce = (TableCellEditor)editor; + tce.removeCellEditorListener(this); + PropertyEditorAdapter.this.fireEditingCanceled(); + } + } +} diff --git a/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyTreeNode.java b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyTreeNode.java new file mode 100644 index 000000000..589ccd869 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/propertyeditor/PropertyTreeNode.java @@ -0,0 +1,361 @@ +package org.das2.components.propertyeditor; + +import org.das2.beans.BeansUtil; +import org.das2.system.DasLogger; +import org.das2.util.DasExceptionHandler; +import java.beans.*; +import java.beans.IndexedPropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.Enumeration; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeNode; + +class PropertyTreeNode implements PropertyTreeNodeInterface { + + protected static final Object[] NULL_ARGS = new Object[0]; + + protected List children; + + protected PropertyTreeNode parent; + + protected PropertyDescriptor propertyDescriptor; + + protected DefaultTreeModel treeModel; + + protected Object value; + + protected boolean dirty; + + protected boolean childDirty; + + PropertyTreeNode( Object value ) { + this.value = value; + } + + /** + * Used to put the tree model into the root tree node so that it can be + * passed down into the tree. + */ + public void setTreeModel( DefaultTreeModel treeModel ) { + if ( this.treeModel!=null ) throw new IllegalArgumentException("Improper use, see documentation"); + this.treeModel= treeModel; + } + + PropertyTreeNode(PropertyTreeNode parent, PropertyDescriptor propertyDescriptor ) throws InvocationTargetException { + this.parent = parent; + this.propertyDescriptor = propertyDescriptor; + this.treeModel= parent.treeModel; + if ( treeModel==null ) { + throw new IllegalArgumentException("null treeModel in parent"); + } + try { + if ( propertyDescriptor.getReadMethod()==null ) { + throw new RuntimeException("read method not defined for "+propertyDescriptor.getName()); + } + value = propertyDescriptor.getReadMethod().invoke(parent.value, NULL_ARGS); + } catch (IllegalAccessException iae) { + throw new RuntimeException(iae); + } + } + + public Enumeration children() { + maybeLoadChildren(); + return Collections.enumeration(children); + } + + public boolean getAllowsChildren() { + + //No propertyDescriptor indicates the root node. + if (propertyDescriptor == null) { + return true; + } + + //Properties that define an editor should not be expanded + else if (propertyDescriptor.getPropertyEditorClass() != null) { + return false; + } + else { + Class type; + if (propertyDescriptor instanceof IndexedPropertyDescriptor) { + IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)propertyDescriptor; + type = ipd.getIndexedPropertyType(); + } else { + type = propertyDescriptor.getPropertyType(); + } + + //Types with identified as editable by PropertyEditor and + //types with registered PropertyEditors should not be expanded. + return !PropertyEditor.editableTypes.contains(type) + && BeansUtil.findEditor(type) == null; + } + } + + public TreeNode getChildAt(int childIndex) { + maybeLoadChildren(); + return (TreeNode)children.get(childIndex); + } + + public int getChildCount() { + maybeLoadChildren(); + return children.size(); + } + + public int getIndex(TreeNode node) { + maybeLoadChildren(); + return children.indexOf(node); + } + + public TreeNode getParent() { + return parent; + } + + public boolean isLeaf() { + if ( value==null ) return true; + maybeLoadChildren(); + return children.isEmpty(); + } + + public PropertyDescriptor getPropertyDescriptor() { + return propertyDescriptor; + } + + + protected void maybeLoadChildren() { + if (children == null) { + ArrayList children = new ArrayList(); + if (getAllowsChildren()) { + try { + PropertyDescriptor[] properties= BeansUtil.getPropertyDescriptors(value.getClass()); + String[] propertyNameList= BeansUtil.getPropertyNames( properties ); + if ( propertyNameList==null ) { + propertyNameList= new String[ properties.length ]; + for ( int i=0; i + Classes implementing the property sheet. The property sheet is a table of bean properties organized hierarchically. If a bean has +another bean as a property, its properties may be accessed or hidden by folding and unfolding the property row (see treetable package). When an editor +for the property is found, the property may be edited directly. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/components/treetable/TreeTableCellRenderer.java b/dasCore/src/main/java/org/das2/components/treetable/TreeTableCellRenderer.java new file mode 100644 index 000000000..5584dfe1d --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/treetable/TreeTableCellRenderer.java @@ -0,0 +1,40 @@ +package org.das2.components.treetable; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import javax.swing.JTable; +import javax.swing.JTree; +import javax.swing.table.TableCellRenderer; +import javax.swing.tree.TreeModel; + +public class TreeTableCellRenderer extends JTree implements TableCellRenderer { + + private JTable table; + + private int visibleRow; + + public TreeTableCellRenderer(TreeModel model) { + super(model); + } + + public void setBounds(int x, int y, int w, int h) { + super.setBounds(x, 0, w, table.getRowHeight()*table.getRowCount()); + } + + public void paint(Graphics g) { + g.translate(0, -visibleRow * getHeight()/getRowCount()); + super.paint(g); + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + this.table = table; + this.visibleRow = row; + if (table != null) { + setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); + } + return this; + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/treetable/TreeTableModel.java b/dasCore/src/main/java/org/das2/components/treetable/TreeTableModel.java new file mode 100644 index 000000000..c7f941935 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/treetable/TreeTableModel.java @@ -0,0 +1,156 @@ +package org.das2.components.treetable; + +import javax.swing.JTree; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableModel; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +public class TreeTableModel extends AbstractTableModel implements TableModel { + + private TreeTableNode root; + + private JTree tree; + + public TreeTableModel(TreeTableNode root, JTree tree) { + this.root = root; + this.tree = tree; + tree.addTreeExpansionListener(new TreeTableTreeListener()); + TreeModelListener treeModelListener= new TreeTableTreeModelListener(); + tree.getModel().addTreeModelListener(new TreeTableTreeModelListener()); + } + + public Class getColumnClass(int columnIndex) { + return root.getColumnClass(columnIndex); + } + + public int getColumnCount() { + return root.getColumnCount(); + } + + public String getColumnName(int columnIndex) { + return root.getColumnName(columnIndex); + } + + public int getRowCount() { + return tree.getRowCount(); + } + + public Object getValueAt(int rowIndex, int columnIndex) { + return getNodeForRow(rowIndex).getValueAt(columnIndex); + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + return getNodeForRow(rowIndex).isCellEditable(columnIndex); + } + + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + getNodeForRow(rowIndex).setValueAt(aValue, columnIndex); + } + + public void toggleExpanded(int rowIndex) { + if (tree.isExpanded(rowIndex)) { + tree.collapseRow(rowIndex); + } + else { + tree.expandRow(rowIndex); + } + } + + public void expand( int rowIndex ) { + if ( tree.isCollapsed(rowIndex) ) tree.expandRow(rowIndex); + } + + public void collapse( int rowIndex ) { + if ( tree.isExpanded(rowIndex) ) tree.collapseRow(rowIndex); + } + + public TreeTableNode getNodeForRow(int rowIndex) { + TreePath path = tree.getPathForRow(rowIndex); + return (TreeTableNode)path.getLastPathComponent(); + } + + public TreeTableNode getRoot() { + return root; + } + + public void setRoot(TreeTableNode node) { + if (node == null) { + throw new NullPointerException("null root node not allowed"); + } + tree.setModel(new DefaultTreeModel(node, true)); + } + + private class TreeTableTreeModelListener implements TreeModelListener { + + public void treeNodesChanged(TreeModelEvent e) { + TreePath path = new TreePath(e.getPath()); + int row = tree.getRowForPath(path); + TreeTableNode node = (TreeTableNode)path.getLastPathComponent(); + int count = node.getChildCount(); + if (row != -1 && tree.isExpanded(row)) { + TreeTableModel.this.fireTableRowsUpdated(row + 1, row + count); + } + } + + public void treeNodesInserted(TreeModelEvent e) { + TreePath path = e.getTreePath(); + int row = tree.getRowForPath(path); + if (row != -1 && tree.isExpanded(row)) { + int[] indices = e.getChildIndices(); + java.util.Arrays.sort(indices); + for (int i = 0; i < indices.length; i++) { + TreeTableModel.this.fireTableRowsInserted(indices[i], indices[i]); + } + } + } + + public void treeNodesRemoved(TreeModelEvent e) { + TreePath path = e.getTreePath(); + int row = tree.getRowForPath(path); + if (row != -1 && tree.isExpanded(row)) { + int[] indices = e.getChildIndices(); + java.util.Arrays.sort(indices); + for (int i = indices.length - 1; i >= 0; i++) { + TreeTableModel.this.fireTableRowsDeleted(indices[i], indices[i]); + } + } + } + + public void treeStructureChanged(TreeModelEvent e) { + TreeTableModel.this.fireTableStructureChanged(); + } + + } + + private class TreeTableTreeListener implements TreeExpansionListener { + + public void treeCollapsed(TreeExpansionEvent event) { + TreePath path = event.getPath(); + int row = tree.getRowForPath(path); + TreeTableNode node = (TreeTableNode)path.getLastPathComponent(); + int count = node.getChildCount(); + if (count != 0) { + TreeTableModel.this.fireTableRowsDeleted(row + 1, row + count); + } + } + + public void treeExpanded(TreeExpansionEvent event) { + TreePath path = event.getPath(); + int row = tree.getRowForPath(path); + TreeTableNode node = (TreeTableNode)path.getLastPathComponent(); + int count = node.getChildCount(); + if (count != 0) { + TreeTableModel.this.fireTableRowsInserted(row + 1, row + count); + } + } + + } + +} + diff --git a/dasCore/src/main/java/org/das2/components/treetable/TreeTableNode.java b/dasCore/src/main/java/org/das2/components/treetable/TreeTableNode.java new file mode 100644 index 000000000..c40231429 --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/treetable/TreeTableNode.java @@ -0,0 +1,40 @@ +/* File: TreeTableNode.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on January 28, 2004, 10:18 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.components.treetable; + +import javax.swing.tree.TreeNode; + +/** + * + * @author eew + */ +public interface TreeTableNode extends TreeNode { + + boolean isCellEditable(int columnIndex); + Object getValueAt(int columnIndex); + void setValueAt(Object value, int columnIndex); + Class getColumnClass(int columnIndex); + int getColumnCount(); + String getColumnName(int columnIndex); +} diff --git a/dasCore/src/main/java/org/das2/components/treetable/package.html b/dasCore/src/main/java/org/das2/components/treetable/package.html new file mode 100644 index 000000000..ba06b7f5f --- /dev/null +++ b/dasCore/src/main/java/org/das2/components/treetable/package.html @@ -0,0 +1,4 @@ + + Contains classes implementing the TreeTable. A tree table is a Table with rows that may be folded or collapsed to expose row + children. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/cvs_status.txt b/dasCore/src/main/java/org/das2/cvs_status.txt new file mode 100644 index 000000000..54ed33ec9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/cvs_status.txt @@ -0,0 +1,50 @@ +File: CancelledOperationException.java Status: Locally Modified +File: DasApplication.java Status: Locally Modified +File: DasException.java Status: Locally Modified +File: DasIOException.java Status: Locally Modified +File: DasNameException.java Status: Locally Modified +File: DasProperties.java Status: Locally Modified +File: DasPropertyException.java Status: Locally Modified +File: NameContext.java Status: Locally Modified +File: cvs_status.txt Status: Unknown +File: AbstractDataSet.java Status: Up-to-date +File: AbstractTableDataSet.java Status: Up-to-date +File: AbstractVectorDataSet.java Status: Up-to-date +File: AccessDeniedException.java Status: Up-to-date +File: AveragePeakTableRebinner.java Status: Up-to-date +File: AverageTableRebinner.java Status: Needs Merge +File: CacheTag.java Status: Up-to-date +File: ClippedTableDataSet.java Status: Locally Modified +File: ConstantDataSetDescriptor.java Status: Up-to-date +File: DataRequestThread.java Status: Up-to-date +File: DataRequestor.java Status: Up-to-date +File: DataSet.java Status: Up-to-date +File: DataSetCache.java Status: Up-to-date +File: DataSetConsumer.java Status: Up-to-date +File: DataSetDescriptor.java Status: Locally Modified +File: DataSetRebinner.java Status: Up-to-date +File: DataSetUpdateEvent.java Status: Up-to-date +File: DataSetUpdateListener.java Status: Up-to-date +File: DataSetUtil.java Status: Locally Modified +File: DefaultTableDataSet.java Status: Locally Modified +File: DefaultVectorDataSet.java Status: Needs Merge +File: GapListDouble.java Status: Up-to-date +File: NearestNeighborTableDataSet.java Status: Up-to-date +File: NearestNeighborTableRebinner.java Status: Up-to-date +File: NoDataInIntervalException.java Status: Up-to-date +File: NoKeyProvidedException.java Status: Up-to-date +File: PeakTableRebinner.java Status: Up-to-date +File: RebinDescriptor.java Status: Up-to-date +File: SyncUtil.java Status: Up-to-date +File: TableDataSet.java Status: Up-to-date +File: TableDataSetBuilder.java Status: Up-to-date +File: TableDataSetConsumer.java Status: Up-to-date +File: TableUtil.java Status: Locally Modified +File: VectorDataSet.java Status: Up-to-date +File: VectorDataSetBuilder.java Status: Needs Patch +File: VectorUtil.java Status: Up-to-date +File: ViewDataSet.java Status: Up-to-date +File: WritableTableDataSet.java Status: Up-to-date +File: XSliceDataSet.java Status: Up-to-date +File: YSliceDataSet.java Status: Up-to-date +File: scratchPad.txt Status: Locally Modified diff --git a/dasCore/src/main/java/org/das2/dasml/CommandAction.java b/dasCore/src/main/java/org/das2/dasml/CommandAction.java new file mode 100644 index 000000000..33bdb9a73 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/CommandAction.java @@ -0,0 +1,56 @@ +/* File: CommandAction.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.dasml; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +class CommandAction implements ActionListener { + + private CommandBlock commandBlock; + + public CommandAction(CommandBlock commandBlock) { + this.commandBlock = commandBlock; + } + + public void actionPerformed(ActionEvent e) { + try { + FormComponent x = (FormComponent)e.getSource(); + commandBlock.execute(x.getForm()); + } + catch (org.das2.DasException de) { + org.das2.util.DasExceptionHandler.handle(de); + } + catch (DataFormatException dfe) { + org.das2.util.DasExceptionHandler.handle(dfe); + } + catch (ParsedExpressionException pee) { + org.das2.util.DasExceptionHandler.handle(pee); + } + catch (java.lang.reflect.InvocationTargetException ite) { + org.das2.util.DasExceptionHandler.handle(ite.getCause()); + } + } + +} + diff --git a/dasCore/src/main/java/org/das2/dasml/CommandBlock.java b/dasCore/src/main/java/org/das2/dasml/CommandBlock.java new file mode 100644 index 000000000..bc14c9625 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/CommandBlock.java @@ -0,0 +1,392 @@ +/* File: CommandBlock.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.dasml; + +import org.das2.DasPropertyException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.regex.Matcher; + +public class CommandBlock { + + private static final int NONE = 0; + + private static final int SET = 1; + + private static final int UPDATE = 2; + + private static final int IF = 4; + + private static final int ELSEIF = 8; + + private static final int INVOKE = 16; + + private static final int ALERT = 32; + + ArrayList commandList; + + CommandBlock() { + commandList = new ArrayList(); + } + + CommandBlock(Element element, FormBase form) { + + commandList = new ArrayList(); + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + + for (int index = 0; index < childCount; index++) { + Node node = children.item(index); + if (node instanceof Element) { + String tagName = node.getNodeName(); + if (tagName.equals("set")) { + addCommand(processSetElement(form, (Element)node)); + } + else if (tagName.equals("if")) { + addCommand(processIfElement(form, (Element)node)); + } + else if (tagName.equals("elseif")) { + addCommand(processElseifElement(form, (Element)node)); + } + else if (tagName.equals("else")) { + addCommand(processElseElement(form, (Element)node)); + } + else if (tagName.equals("invoke")) { + addCommand(processInvokeElement(form, (Element)node)); + } + } + } + } + + private Command processSetElement(FormBase form, Element element) { + String property = element.getAttribute("property"); + String value = element.getAttribute("value"); + + if (!org.das2.NameContext.QUALIFIED_NAME.matcher(property).matches()) { + throw new IllegalArgumentException("property attribute must be a valid identifier: 0) { + String argsString = java.util.Arrays.asList(args).toString(); + argsString = argsString.substring(0, argsString.length() - 1); + element.setAttribute("args", argsString); + } + return element; + } + + public void setParent(CommandBlock parent) { + this.parent = parent; + } + + public CommandBlock getParent() { + return parent; + } + + public String toString() { + return "INVOKE " + target + (args == null ? "[]" : Arrays.asList(args).toString()); + } + + } + + static class IfCommand extends BlockCommand { + String test; + boolean shouldSkip; + + public IfCommand(String test) { + super(); + this.test = test; + } + + public IfCommand(String test, Element element, FormBase form) { + super(element, form); + this.test = test; + } + + public boolean getShouldSkip() { + return shouldSkip; + } + + public void execute(FormBase form)throws org.das2.DasException, DataFormatException, ParsedExpressionException, InvocationTargetException { + Matcher refMatcher = org.das2.NameContext.refPattern.matcher(test); + Object value; + if (refMatcher.matches()) { + value = form.getDasApplication().getNameContext().get(refMatcher.group(1)); + } + else { + value = form.getDasApplication().getNameContext().parseValue(test, boolean.class); + } + Boolean bool; + if (value instanceof Boolean) { + bool = (Boolean)value; + } + else { + throw new DataFormatException(value + " is not a boolean"); + } + if (bool.booleanValue()) { + super.execute(form); + } + shouldSkip = bool.booleanValue(); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("if"); + element.setAttribute("test", test); + appendDOMElements(element); + return element; + } + + public String toString() { + return "IF " + test; + } + + } + + static class ElseIfCommand extends IfCommand { + ElseIfCommand(String test) { + super(test); + } + + ElseIfCommand(String test, Element element, FormBase form) { + super(test, element, form); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("elseif"); + element.setAttribute("test", test); + appendDOMElements(element); + return element; + } + + public String toString() { + return "ELSEIF " + test; + } + } + + static class ElseCommand extends IfCommand { + ElseCommand() { + super("true"); + } + + ElseCommand(Element element, FormBase form) { + super("true", element, form); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("else"); + appendDOMElements(element); + return element; + } + + public String toString() { + return "ELSE"; + } + } + + static interface Command { + CommandBlock getParent(); + void setParent(CommandBlock parent); + void execute(FormBase form)throws org.das2.DasException, DataFormatException, ParsedExpressionException, InvocationTargetException; + Element getDOMElement(Document document); + } + + static abstract class BlockCommand extends CommandBlock implements Command { + CommandBlock parent; + public BlockCommand() { + super(); + } + public BlockCommand(Element element, FormBase form) { + super(element, form); + } + public void setParent(CommandBlock parent) { + this.parent = parent; + } + public CommandBlock getParent() { + return parent; + } + } + + static class Identifier { + + public String text; + + public Identifier(String t) { text = t; } + + public String toString() { + return text; + } + + } + +} + diff --git a/dasCore/src/main/java/org/das2/dasml/CommandBlockEditor.java b/dasCore/src/main/java/org/das2/dasml/CommandBlockEditor.java new file mode 100644 index 000000000..ce1191665 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/CommandBlockEditor.java @@ -0,0 +1,955 @@ +/* File: CommandBlockEditor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.table.TableCellEditor; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.*; +import java.util.List; + +/** + * + * @author eew + */ +public class CommandBlockEditor extends JButton implements TableCellEditor, java.beans.PropertyEditor { + + EventListenerList listenerList = new EventListenerList(); + + JDialog dialog; + + JPanel contentPanel; + + JCheckBox enable; + + JTree commandTree; + + CommandBlockTreeModel commandBlockTreeModel; + + MultiPurposeListener listener; + + JButton commitChanges; + + JButton cancelEdit; + + JPanel editorPanel; + + CardLayout switcher; + + JTextField x1Field; + + JLabel x1FieldLabel; + + JButton x1Commit, x1Cancel; + + JTextField x2Field1, x2Field2; + + JLabel x2Field1Label, x2Field2Label; + + JButton x2Commit, x2Cancel; + + JButton newCommand; + + JButton editCommand; + + JButton removeCommand; + + JButton moveUpCommand; + + JButton moveDownCommand; + + /** Creates a new instance of CommandBlockEditor */ + public CommandBlockEditor() { + super("edit"); + + listener = new MultiPurposeListener(); + addActionListener(listener); + + commandBlockTreeModel = new CommandBlockTreeModel(new CommandBlock()); + commandTree = new JTree(commandBlockTreeModel); + commandTree.setRootVisible(true); + commandTree.setShowsRootHandles(false); + commandTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + commandTree.setVisibleRowCount(10); + commandTree.setCellRenderer(new CommandRenderer()); + + contentPanel = new JPanel(new BorderLayout()); + + JPanel centerPanel = new JPanel(new BorderLayout()); + + centerPanel.add(new JScrollPane(commandTree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS), BorderLayout.CENTER); + centerPanel.add(editorPanel = initEditorPanels(), BorderLayout.SOUTH); + centerPanel.add(initButtonPanel(), BorderLayout.EAST); + + contentPanel.add(centerPanel, BorderLayout.CENTER); + + enable = new JCheckBox("Enabled", false); + setTopEnabled(false); + enable.addActionListener(listener); + contentPanel.add(enable, BorderLayout.NORTH); + + JPanel dialogButtons = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + commitChanges = new JButton("Commit Changes"); + commitChanges.addActionListener(listener); + cancelEdit = new JButton("Cancel Changes"); + cancelEdit.addActionListener(listener); + dialogButtons.add(commitChanges); + dialogButtons.add(cancelEdit); + + contentPanel.add(dialogButtons, BorderLayout.SOUTH); + } + + private JPanel initButtonPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + newCommand = new JButton("new"); + editCommand = new JButton("edit"); + removeCommand = new JButton("remove"); + moveUpCommand = new JButton("move up"); + moveDownCommand = new JButton("move down"); + Dimension d = moveDownCommand.getPreferredSize(); + newCommand.setPreferredSize(d); + newCommand.setMaximumSize(d); + editCommand.setPreferredSize(d); + editCommand.setMaximumSize(d); + removeCommand.setPreferredSize(d); + removeCommand.setMaximumSize(d); + moveUpCommand.setPreferredSize(d); + moveUpCommand.setMaximumSize(d); + newCommand.addActionListener(listener); + editCommand.addActionListener(listener); + removeCommand.addActionListener(listener); + moveUpCommand.addActionListener(listener); + moveDownCommand.addActionListener(listener); + panel.add(newCommand); + panel.add(editCommand); + panel.add(removeCommand); + panel.add(moveUpCommand); + panel.add(moveDownCommand); + return panel; + } + + void addCommand(CommandBlock.Command c) { + TreePath selection = commandTree.getSelectionPath(); + CommandBlock parent; + int index; + if (selection == null) { + parent = commandBlock; + index = parent.commandList.size(); + } + else if (selection.getLastPathComponent() instanceof CommandBlock) { + parent = (CommandBlock)selection.getLastPathComponent(); + index = parent.commandList.size(); + } + else { + CommandBlock.Command item = (CommandBlock.Command)selection.getLastPathComponent(); + parent = item.getParent(); + index = parent.indexOf(item) + 1; + } + parent.insertCommand(c, index); + Object[] path = commandBlockTreeModel.getPathToNode(parent); + commandBlockTreeModel.fireTreeNodeInserted(path, index, c); + } + + void removeCommands(TreePath[] selection) { + HashMap map = new HashMap(); + for (int i = 0; i < selection.length; i++) { + if (selection[i].getPathCount() == 1) { + continue; + } + TreePath parent = selection[i].getParentPath(); + Object child = selection[i].getLastPathComponent(); + List l = (List)map.get(parent); + if (l == null) { + l = new ArrayList(); + map.put(parent, l); + } + l.add(child); + } + for (Iterator i = map.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)i.next(); + TreePath parent = (TreePath)entry.getKey(); + List list = (List)entry.getValue(); + CommandBlock.Command[] children = new CommandBlock.Command[list.size()]; + list.toArray(children); + int[] indices = new int[children.length]; + for (int index = 0; index < children.length; index++) { + indices[index] = children[index].getParent().indexOf(children[index]); + children[index].getParent().removeCommand(children[index]); + } + commandBlockTreeModel.fireTreeNodesRemoved(parent, indices, children); + } + } + + private JPanel initEditorPanels() { + switcher = new CardLayout(); + JPanel panel = new JPanel(switcher); + panel.setBorder(new CompoundBorder(new CompoundBorder(new EmptyBorder(2, 2, 2, 2), new EtchedBorder()), new EmptyBorder(2, 2, 2, 2))); + + JPanel emptyPanel = new JPanel(); + panel.add(emptyPanel, "EMPTY"); + + JPanel singlePanel = new JPanel(); + singlePanel.setLayout(new BoxLayout(singlePanel, BoxLayout.Y_AXIS)); + x1Field = new JTextField(30); + x1Field.setMaximumSize(x1Field.getPreferredSize()); + x1Field.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x1FieldLabel = new JLabel("field", JLabel.LEFT); + x1FieldLabel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + singlePanel.add(x1FieldLabel); + singlePanel.add(x1Field); + JPanel x1ButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + x1ButtonPanel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x1Commit = new JButton("Commit Changes"); + x1Commit.addActionListener(listener); + x1Cancel = new JButton("Cancel Changes"); + x1Cancel.addActionListener(listener); + x1ButtonPanel.add(x1Commit); + x1ButtonPanel.add(x1Cancel); + singlePanel.add(Box.createVerticalGlue()); + singlePanel.add(x1ButtonPanel); + panel.add(singlePanel, "SINGLE"); + + JPanel doublePanel = new JPanel(); + doublePanel.setLayout(new BoxLayout(doublePanel, BoxLayout.Y_AXIS)); + x2Field1 = new JTextField(30); + x2Field1.setMaximumSize(x2Field1.getPreferredSize()); + x2Field1.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x2Field1Label = new JLabel("field1"); + x2Field1Label.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x2Field2 = new JTextField(30); + x2Field2.setMaximumSize(x2Field2.getPreferredSize()); + x2Field2.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x2Field2Label = new JLabel("field2"); + x2Field2Label.setAlignmentX(JComponent.LEFT_ALIGNMENT); + doublePanel.add(x2Field1Label); + doublePanel.add(x2Field1); + doublePanel.add(x2Field2Label); + doublePanel.add(x2Field2); + JPanel x2ButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + x2ButtonPanel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + x2Commit = new JButton("Commit Changes"); + x2Commit.addActionListener(listener); + x2Cancel = new JButton("Cancel Changes"); + x2Cancel.addActionListener(listener); + x2ButtonPanel.add(x2Commit); + x2ButtonPanel.add(x2Cancel); + doublePanel.add(Box.createVerticalGlue()); + doublePanel.add(x2ButtonPanel); + panel.add(doublePanel, "DOUBLE"); + + //switcher.show(panel, "EMPTY"); + return panel; + } + + public void showDialog() { + if (dialog == null) { + Window w = SwingUtilities.windowForComponent(this); + if (w instanceof Frame) { + dialog = new JDialog((Frame)w, true); + } + else if (w instanceof Dialog) { + dialog = new JDialog((Dialog)w, true); + } + else { + dialog = new JDialog(); + dialog.setModal(true); + } + dialog.setContentPane(contentPanel); + dialog.pack(); + dialog.setResizable(false); + dialog.addWindowListener(listener); + } + dialog.setVisible(true); + } + + CommandBlock commandBlock; + + public CommandBlock getCommandBlock() { + if (enable.isSelected()) { + return commandBlock; + } + return null; + } + + public void setCommandBlock(CommandBlock commandBlock) { + if (commandBlock == null) { + commandBlock = new CommandBlock(); + enable.setSelected(false); + setTopEnabled(false); + } + else { + enable.setSelected(true); + setTopEnabled(true); + } + this.commandBlock = commandBlock; + commandBlockTreeModel.root = commandBlock; + commandBlockTreeModel.fireTreeChanged(); + } + + public boolean stopCellEditing() { + fireEditingStopped(); + dialog.setVisible(false); + return true; + } + + public void cancelCellEditing() { + dialog.setVisible(false); + fireEditingCanceled(); + } + + private class MultiPurposeListener extends WindowAdapter implements ActionListener { + final JPopupMenu newCommandMenu = new JPopupMenu("new command"); + { + newCommandMenu.add("SET").addActionListener(this); + newCommandMenu.add("INVOKE").addActionListener(this); + newCommandMenu.add("IF").addActionListener(this); + newCommandMenu.add("ELSEIF").addActionListener(this); + newCommandMenu.add("ELSE").addActionListener(this); + } + public void actionPerformed(ActionEvent e) { + Object source = e.getSource(); + String command = e.getActionCommand(); + if (source == CommandBlockEditor.this) { + showDialog(); + } + else if (source == commitChanges) { + stopCellEditing(); + } + else if (source == cancelEdit) { + cancelCellEditing(); + } + else if (source == enable) { + switcher.show(editorPanel, "EMPTY"); + setTopEnabled(enable.isSelected()); + } + else if (source == newCommand) { + newCommandMenu.show(newCommand, newCommand.getWidth(), 0); + } + else if (source == editCommand) { + TreePath selection = commandTree.getSelectionPath(); + if (selection != null && selection.getPathCount() != 1) { + Object o = selection.getLastPathComponent(); + if (o instanceof CommandBlock.SetCommand) { + CommandBlock.SetCommand c = (CommandBlock.SetCommand)o; + x2Field1Label.setText("Property"); + x2Field1.setText(c.id); + x2Field2Label.setText("Value"); + x2Field2.setText(c.value); + switcher.show(editorPanel, "DOUBLE"); + setEditing(true); + } + else if (o instanceof CommandBlock.InvokeCommand) { + CommandBlock.InvokeCommand c = (CommandBlock.InvokeCommand)o; + x2Field1Label.setText("Method"); + x2Field1.setText(c.target); + x2Field2Label.setText("Arguments (comma separated)"); + if (c.args != null) { + String args = Arrays.asList(c.args).toString(); + x2Field2.setText(args.substring(1, args.length() - 1)); + } + else { + x2Field2.setText(""); + } + switcher.show(editorPanel, "DOUBLE"); + setEditing(true); + } + else if (o instanceof CommandBlock.ElseCommand) { + return; + } + else if (o instanceof CommandBlock.IfCommand) { + CommandBlock.IfCommand c = (CommandBlock.IfCommand)o; + x1FieldLabel.setText("Test"); + x1Field.setText(c.test); + switcher.show(editorPanel, "SINGLE"); + setEditing(true); + } + } + } + else if (source == removeCommand) { + TreePath[] selection = commandTree.getSelectionPaths(); + if (selection != null) { + removeCommands(selection); + } + } + else if (source == moveUpCommand) { + TreePath selection = commandTree.getSelectionPath(); + if (selection == null || selection.getPathCount() == 1) { + return; + } + CommandBlock.Command c = (CommandBlock.Command)selection.getLastPathComponent(); + CommandBlock parent = c.getParent(); + int index = parent.indexOf(c); + if (index == 0) { + return; + } + else { + removeCommands(new TreePath[]{selection}); + parent.insertCommand(c, index - 1); + commandBlockTreeModel.fireTreeNodeInserted(selection.getParentPath().getPath(), index - 1, c); + } + } + else if (source == moveDownCommand) { + TreePath selection = commandTree.getSelectionPath(); + if (selection == null || selection.getPathCount() == 1) { + return; + } + CommandBlock.Command c = (CommandBlock.Command)selection.getLastPathComponent(); + CommandBlock parent = c.getParent(); + int index = parent.indexOf(c); + if (index == parent.commandList.size()-1) { + return; + } + else { + removeCommands(new TreePath[]{selection}); + parent.insertCommand(c, index + 1); + commandBlockTreeModel.fireTreeNodeInserted(selection.getParentPath().getPath(), index + 1, c); + } + } + else if (source == x1Commit) { + TreePath selection = commandTree.getSelectionPath(); + Object node = selection.getLastPathComponent(); + CommandBlock.IfCommand c = (CommandBlock.IfCommand)node; + c.test = x1Field.getText(); + setEditing(false); + } + else if (source == x2Commit) { + TreePath selection = commandTree.getSelectionPath(); + Object node = selection.getLastPathComponent(); + if (node instanceof CommandBlock.SetCommand) { + CommandBlock.SetCommand c = (CommandBlock.SetCommand)node; + c.id = x2Field1.getText(); + c.value = x2Field2.getText(); + setEditing(false); + } + else { + CommandBlock.InvokeCommand c = (CommandBlock.InvokeCommand)node; + c.target = x2Field1.getText(); + c.args = x2Field2.getText().split("\\s*,\\s*"); + setEditing(false); + } + } + else if (source == x1Cancel || source == x2Cancel) { + setEditing(false); + } + else if (command.equals("SET")) { + addCommand(new CommandBlock.SetCommand("property", "value")); + } + else if (command.equals("INVOKE")) { + addCommand(new CommandBlock.InvokeCommand("object.method", null)); + } + else if (command.equals("IF")) { + addCommand(new CommandBlock.IfCommand("test")); + } + else if (command.equals("ELSEIF")) { + addCommand(new CommandBlock.ElseIfCommand("test")); + } + else if (command.equals("ELSE")) { + addCommand(new CommandBlock.ElseCommand()); + } + } + + public void windowClosing(WindowEvent e) { + cancelCellEditing(); + } + private void setEditing(boolean b) { + b = !b; + if (b) { + switcher.show(editorPanel, "EMPTY"); + } + setTopEnabled(b); + enable.setEnabled(b); + TreePath selection = commandTree.getSelectionPath(); + CommandBlock.Command c = (CommandBlock.Command)selection.getLastPathComponent(); + int index = c.getParent().indexOf(c); + commandBlockTreeModel.fireTreeNodesChanged(selection.getParentPath().getPath(), index, c); + } + } + + private void setTopEnabled(boolean b) { + commandTree.setEnabled(b); + newCommand.setEnabled(b); + editCommand.setEnabled(b); + removeCommand.setEnabled(b); + moveUpCommand.setEnabled(b); + moveDownCommand.setEnabled(b); + } + + public static void main(String[] args) { + + CommandBlockEditor editor = new CommandBlockEditor(); + + CommandBlock block = new CommandBlock(); + block.addCommand(new CommandBlock.SetCommand("property", "value")); + block.addCommand(new CommandBlock.InvokeCommand("fred", new String[] {"arg1", "arg2"})); + CommandBlock.IfCommand ifc = new CommandBlock.IfCommand("test"); + ifc.addCommand(new CommandBlock.SetCommand("fred", "larry")); + block.addCommand(ifc); + editor.setCommandBlock(block); + + JFrame frame = new JFrame(); + frame.setContentPane(editor); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + + public void addCellEditorListener(CellEditorListener l) { + listenerList.add(CellEditorListener.class, l); + } + + public Object getCellEditorValue() { + return getCommandBlock(); + } + + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + //this.table = table; + setCommandBlock((CommandBlock)value); + return this; + } + + public boolean isCellEditable(EventObject anEvent) { + return true; + } + + public void removeCellEditorListener(CellEditorListener l) { + listenerList.remove(CellEditorListener.class, l); + } + + public boolean shouldSelectCell(EventObject anEvent) { + return true; + } + + private void fireEditingCanceled() { + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + if (e == null) { + e = new ChangeEvent(this); + } + ((CellEditorListener)listeners[i+1]).editingCanceled(e); + } + } + } + + private void fireEditingStopped() { + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + if (e == null) { + e = new ChangeEvent(this); + } + ((CellEditorListener)listeners[i+1]).editingStopped(e); + } + } + } + + /** Gets the property value as text. + * + * @return The property value as a human editable string. + *

Returns null if the value can't be expressed as an editable string. + *

If a non-null value is returned, then the PropertyEditor should + * be prepared to parse that string back in setAsText(). + */ + public String getAsText() { + return "dflkjd"; + } + + /** A PropertyEditor may choose to make available a full custom Component + * that edits its property value. It is the responsibility of the + * PropertyEditor to hook itself up to its editor Component itself and + * to report property value changes by firing a PropertyChange event. + *

+ * The higher-level code that calls getCustomEditor may either embed + * the Component in some larger property sheet, or it may put it in + * its own individual dialog, or ... + * + * @return A java.awt.Component that will allow a human to directly + * edit the current property value. May be null if this is + * not supported. + */ + public java.awt.Component getCustomEditor() { + return this; + } + + /** This method is intended for use when generating Java code to set + * the value of the property. It should return a fragment of Java code + * that can be used to initialize a variable with the current property + * value. + *

+ * Example results are "2", "new Color(127,127,34)", "Color.orange", etc. + * + * @return A fragment of Java code representing an initializer for the + * current value. + */ + public String getJavaInitializationString() { + return "???"; + } + + /** If the property value must be one of a set of known tagged values, + * then this method should return an array of the tags. This can + * be used to represent (for example) enum values. If a PropertyEditor + * supports tags, then it should support the use of setAsText with + * a tag value as a way of setting the value and the use of getAsText + * to identify the current value. + * + * @return The tag values for this property. May be null if this + * property cannot be represented as a tagged value. + * + */ + public String[] getTags() { + return null; + } + + /** Gets the property value. + * + * @return The value of the property. Primitive types such as "int" will + * be wrapped as the corresponding object type such as "java.lang.Integer". + */ + public Object getValue() { + return commandBlock; + } + + /** Determines whether this property editor is paintable. + * + * @return True if the class will honor the paintValue method. + */ + public boolean isPaintable() { + return false; + } + + /** Paint a representation of the value into a given area of screen + * real estate. Note that the propertyEditor is responsible for doing + * its own clipping so that it fits into the given rectangle. + *

+ * If the PropertyEditor doesn't honor paint requests (see isPaintable) + * this method should be a silent noop. + *

+ * The given Graphics object will have the default font, color, etc of + * the parent container. The PropertyEditor may change graphics attributes + * such as font and color and doesn't need to restore the old values. + * + * @param gfx Graphics object to paint into. + * @param box Rectangle within graphics object into which we should paint. + */ + public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) { + } + + /** Set the property value by parsing a given String. May raise + * java.lang.IllegalArgumentException if either the String is + * badly formatted or if this kind of property can't be expressed + * as text. + * @param text The string to be parsed. + */ + public void setAsText(String text) throws java.lang.IllegalArgumentException { + } + + /** Set (or change) the object that is to be edited. Primitive types such + * as "int" must be wrapped as the corresponding object type such as + * "java.lang.Integer". + * + * @param value The new target object to be edited. Note that this + * object should not be modified by the PropertyEditor, rather + * the PropertyEditor should create a new object to hold any + * modified value. + */ + public void setValue(Object value) { + setCommandBlock((CommandBlock)value); + } + + /** Determines whether this property editor supports a custom editor. + * + * @return True if the propertyEditor can provide a custom editor. + */ + public boolean supportsCustomEditor() { + return true; + } + + private static class CommandBlockTreeModel implements TreeModel { + + private EventListenerList eventListenerList; + private CommandBlock root; + + CommandBlockTreeModel(CommandBlock root) { + this.root = root; + } + + /** Adds a listener for the TreeModelEvent + * posted after the tree changes. + * + * @param l the listener to add + * @see #removeTreeModelListener + */ + public void addTreeModelListener(TreeModelListener l) { + if (eventListenerList == null) { + eventListenerList = new EventListenerList(); + } + eventListenerList.add(TreeModelListener.class, l); + } + + /** Returns the child of parent at index index + * in the parent's + * child array. parent must be a node previously obtained + * from this data source. This should not return null + * if index + * is a valid index for parent (that is index >= 0 && + * index < getChildCount(parent)). + * + * @param parent a node in the tree, obtained from this data source + * @return the child of parent at index index + */ + public Object getChild(Object parent, int index) { + if (parent instanceof CommandBlock) { + return ((CommandBlock)parent).commandList.get(index); + } + else { + return null; + } + } + + /** Returns the number of children of parent. + * Returns 0 if the node + * is a leaf or if it has no children. parent must be a node + * previously obtained from this data source. + * + * @param parent a node in the tree, obtained from this data source + * @return the number of children of the node parent + */ + public int getChildCount(Object parent) { + if (parent instanceof CommandBlock) { + return ((CommandBlock)parent).commandList.size(); + } + else { + return 0; + } + } + + /** Returns the index of child in parent. If parent + * is null or child is null, + * returns -1. + * + * @param parent a note in the tree, obtained from this data source + * @param child the node we are interested in + * @return the index of the child in the parent, or -1 if either + * child or parent are null + */ + public int getIndexOfChild(Object parent, Object child) { + if (parent instanceof CommandBlock) { + return ((CommandBlock)parent).commandList.indexOf(child); + } + return -1; + } + + /** Returns the root of the tree. Returns null + * only if the tree has no nodes. + * + * @return the root of the tree + */ + public Object getRoot() { + return root; + } + + /** Returns true if node is a leaf. + * It is possible for this method to return false + * even if node has no children. + * A directory in a filesystem, for example, + * may contain no files; the node representing + * the directory is not a leaf, but it also has no children. + * + * @param node a node in the tree, obtained from this data source + * @return true if node is a leaf + */ + public boolean isLeaf(Object node) { + return !(node instanceof CommandBlock); + } + + /** Removes a listener previously added with + * addTreeModelListener. + * + * @see #addTreeModelListener + * @param l the listener to remove + */ + public void removeTreeModelListener(TreeModelListener l) { + if (eventListenerList != null) { + eventListenerList.remove(TreeModelListener.class, l); + } + } + + protected void fireTreeChanged() { + TreeModelEvent evt = null; + Object[] listeners = eventListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i-=2) { + if (listeners[i] == TreeModelListener.class) { + if (evt == null) { + evt = new TreeModelEvent(this, new Object[]{root}); + } + ((TreeModelListener)listeners[i+1]).treeStructureChanged(evt); + } + } + } + + protected void fireTreeNodesChanged(Object[] path, int index, Object child) { + TreeModelEvent evt = null; + Object[] listeners = eventListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i-=2) { + if (listeners[i] == TreeModelListener.class) { + if (evt == null) { + evt = new TreeModelEvent(this, path, new int[]{index}, new Object[]{child}); + } + ((TreeModelListener)listeners[i+1]).treeNodesChanged(evt); + } + } + } + + protected void fireTreeNodeInserted(Object[] path, int index, Object child) { + TreeModelEvent evt = null; + Object[] listeners = eventListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i-=2) { + if (listeners[i] == TreeModelListener.class) { + if (evt == null) { + evt = new TreeModelEvent(this, path, new int[]{index}, new Object[]{child}); + } + ((TreeModelListener)listeners[i+1]).treeNodesInserted(evt); + } + } + } + + protected void fireTreeNodesRemoved(TreePath parent, int[] indices, Object[] children) { + TreeModelEvent evt = null; + Object[] listeners = eventListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i-=2) { + if (listeners[i] == TreeModelListener.class) { + if (evt == null) { + evt = new TreeModelEvent(this, parent, indices, children); + } + ((TreeModelListener)listeners[i+1]).treeNodesRemoved(evt); + } + } + } + + protected void fireTreeStructureChanged(Object[] path) { + TreeModelEvent evt = null; + Object[] listeners = eventListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i-=2) { + if (listeners[i] == TreeModelListener.class) { + if (evt == null) { + evt = new TreeModelEvent(this, path); + } + ((TreeModelListener)listeners[i+1]).treeStructureChanged(evt); + } + } + } + + Object[] getPathToNode(Object node) { + if (!(node instanceof CommandBlock.Command)) { + return new Object[]{node}; + } + else { + CommandBlock.Command command = (CommandBlock.Command)node; + CommandBlock parent = command.getParent(); + int count = 2; + while (parent instanceof CommandBlock.Command) { + parent = ((CommandBlock.Command)parent).getParent(); + count ++; + } + Object[] path = new Object[count]; + parent = command.getParent(); + path[count - 1] = node; + path[count - 2] = parent; + int index = count - 2; + while (parent instanceof CommandBlock.Command) { + index --; + parent = ((CommandBlock.Command)parent).getParent(); + path[index] = parent; + } + System.out.println(Arrays.asList(path)); + return path; + } + } + + /** Messaged when the user has altered the value for the item identified + * by path to newValue. + * If newValue signifies a truly new value + * the model should post a treeNodesChanged event. + * + * @param path path to the node that the user has altered + * @param newValue the new value from the TreeCellEditor + */ + public void valueForPathChanged(TreePath path, Object newValue) { + } + + } + + private static class CommandRenderer extends JLabel implements TreeCellRenderer { + + Color textForeground; + + Color textBackground; + + Color selectionForeground; + + Color selectionBackground; + + Border focusedBorder; + + Border unfocusedBorder; + + CommandRenderer() { + setOpaque(true); + textForeground = UIManager.getColor("Tree.textForeground"); + textBackground = UIManager.getColor("Tree.textBackground"); + selectionForeground = UIManager.getColor("Tree.selectionForeground"); + selectionBackground = UIManager.getColor("Tree.selectionBackground"); + focusedBorder = new LineBorder(UIManager.getColor("Tree.selectionBorderColor")); + unfocusedBorder = new LineBorder(textBackground); + } + + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + setForeground(selected ? selectionForeground : textForeground); + setBackground(selected ? selectionBackground : textBackground); + setBorder(hasFocus ? focusedBorder : unfocusedBorder); + if (!(value instanceof CommandBlock.Command)) { + setText("[Command Block]"); + } + else { + setText(value.toString()); + } + setEnabled(tree.isEnabled()); + return this; + } + + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/DOMBuilder.java b/dasCore/src/main/java/org/das2/dasml/DOMBuilder.java new file mode 100644 index 000000000..d7bafcf68 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/DOMBuilder.java @@ -0,0 +1,210 @@ +/* + * Serializer.java + * + * Created on April 28, 2006, 4:49 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.dasml; + +import org.das2.graph.DasCanvasComponent; +import org.das2.DasApplication; +import org.das2.NameContext; +import org.das2.beans.AccessLevelBeanInfo; +import org.das2.beans.BeansUtil; +import org.das2.system.DasLogger; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import java.beans.*; +import java.beans.PropertyDescriptor; +import java.lang.reflect.*; +import java.util.*; +import java.util.logging.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * New stateful serialize utility that uses names when available. + * @author Jeremy + */ +public class DOMBuilder { + + Object bean; + HashMap serializedObjects; + NameContext nameContext; + + /** Creates a new instance of Serializer */ + public DOMBuilder( Object bean ) { + this.bean= bean; + } + + /** + * returns name or null. + */ + private String getBeanName( Object bean ) { + try { + PropertyDescriptor[] pds= BeansUtil.getPropertyDescriptors(bean.getClass()); + for ( int i=0; i0 ) monitor.setTaskSize( propertyNameList.length ); + monitor.started(); + + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.xml.sax.*; +import org.xml.sax.helpers.DefaultHandler; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.util.*; +import java.util.regex.Pattern; + +/** + * A validator for the dasML language developed for the University of + * Iowa Space Plasma Wave Group. This class is used as a pre-processor + * to (hopefully) provide clear and helpful error messages. + * + * Warning: This class is not thread-safe. Unexpected results can occur + * if multiple threads use an instance of this class concurrently. + * + * @author Edward West + */ +public class DasMLValidator extends DefaultHandler { + + public static Pattern INTEGER_PATTERN = Pattern.compile("(0|[1-9][0-9]*)"); + + public static Pattern WINDOW_POSITION_PATTERN = Pattern.compile("\\((0|[1-9][0-9]*),(0|[1-9][0-9]*)\\)"); + + public static Pattern FLOAT_PATTERN = Pattern.compile("-?[0-9]*(\\.[0-9]*)?([eE]-?[0-9]+)?"); + + /** + * Instance of the SAXParserFactory that this class uses to create + * instances of SAXParser. + */ + private static SAXParserFactory factory; + + /** + * Static initialization block to property initialize factory + */ + static { + factory = SAXParserFactory.newInstance(); + factory.setValidating(true); + } + + /** + * Instance of SAXParser used by this validator + * to parse documents. + */ + private SAXParser parser; + + /** + * Instance of ErrorHandler that the error events + * from the SAXParser are delegated to. This member is + * only valid during a call to validate() + */ + private ErrorHandler errorHandler; + + /** + * The last error encountered by this validator. + */ + private SAXException lastError; + + /** + * Locator used to locate the position in the + * XML document that where certain events have taken place. + */ + private Locator locator; + + /** + * Mapping of 'name' attributes to 'type' of element (element name) + */ + private Map typeMap; + + /** + * A list of TypeCheck that are to be processed once the whole + * document is loaded. + */ + private List typeCheckList; + + /** Creates a new instance of DasMLValidator */ + public DasMLValidator() throws ParserConfigurationException, SAXException { + parser = factory.newSAXParser(); + typeMap = new HashMap(); + typeCheckList = new LinkedList(); + } + + /** + * Parses and validates a dasML document. All errors are + * passed to the ErrorHandler instance specified. SAXExceptions + * thrown by the underlying parser are caught and suppressed by + * this method. If an application needs access to the errors, + * an ErrorHandler must be provided. + * + * @param source The source of the XML document + * @param errorHandler The ErrorHandler instance that will receive + * error messages from the parser. This can be null + * @return true if the document is a valid dasML document. + * @throws IOException if the there is an error while reading the document. + */ + public boolean validate(InputSource source, ErrorHandler errorHandler) throws java.io.IOException { + this.errorHandler = errorHandler; + if (this == errorHandler) throw new IllegalArgumentException("cannot pass an instance of DasMLValidator to its own validate() method"); + lastError = null; + try { + typeMap.clear(); + typeCheckList.clear(); + parser.parse(source, this); + } + catch (SAXException se) { + //Save a reference to the error and return false + lastError = se; + } + + return lastError == null; + } + + /** + * Returns the last error encountered by this validator + * or null if no error has been found. This method + * will only return an error if the last call to + * validate(InputSource, ErrorHandler) returned false. + * If an application wishes to have access to warnings + * and non-fatal errors then an ErrorHandler must be provided. + */ + public SAXException getLastError() { + return lastError; + } + + /** Report a fatal XML parsing error. + * + * @param e The error information encoded as an exception. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ErrorHandler#fatalError + * @see org.xml.sax.SAXParseException + */ + public void fatalError(SAXParseException e) throws SAXException { + if (errorHandler != null) { + errorHandler.fatalError(e); + } + throw e; + } + + /** Receive a Locator object for document events. + * + * @param locator A locator for all SAX document events. + * @see org.xml.sax.ContentHandler#setDocumentLocator + * @see org.xml.sax.Locator + */ + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + + /** Receive notification of a recoverable parser error. + * + * @param e The warning information encoded as an exception. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ErrorHandler#warning + * @see org.xml.sax.SAXParseException + */ + public void error(SAXParseException e) throws SAXException { + if (errorHandler != null) { + errorHandler.error(e); + } + lastError = e; + } + + /** Receive notification of a parser warning. + * + * @param e The warning information encoded as an exception. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ErrorHandler#warning + * @see org.xml.sax.SAXParseException + */ + public void warning(SAXParseException e) throws SAXException { + if (errorHandler != null) { + errorHandler.warning(e); + } + lastError = e; + } + + /** Receive notification of the beginning of the document. + * + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ContentHandler#startDocument + */ + public void startDocument() throws SAXException { + } + + /** Receive notification of the start of an element. + * + * @param qName The element type name. + * @param attributes The specified or defaulted attributes. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + String name = attributes.getValue("name"); + if (name != null) { + if (typeMap.containsKey(name)) + errorInternal("An element with the name " + name + " already exists. " + + "The values of name attributes must be unique."); + typeMap.put(name, qName); + } + if (qName.equals("window")) + checkWindow(attributes); + else if (qName.equals("form")) + checkForm(attributes); + else if (qName.equals("textfield")) + checkTextfield(attributes); + else if (qName.equals("checkbox")) + checkCheckbox(attributes); + else if (qName.equals("if")) + ;//CHECK EXPRESSION + else if (qName.equals("elseif")) + ;//CHECK EXPRESSION + else if (qName.equals("update")) //KLUDGE - Will be removed once is implemented + typeCheckList.add(new TypeCheck("update", + "target", + "spectrogram", + attributes.getValue("target"), + locator)); + else if (qName.equals("radiobutton")) + checkRadiobutton(attributes); + else if (qName.equals("panel")) + checkPanel(attributes); + else if (qName.equals("glue")) + checkGlue(attributes); + else if (qName.equals("canvas")) + checkCanvas(attributes); + else if (qName.equals("row")) + checkRowColumn("row", attributes); + else if (qName.equals("column")) + checkRowColumn("column", attributes); + else if (qName.equals("spectrogram")) + checkSpectrogram(attributes); + else if (qName.equals("xAxis")) + checkXAxis(attributes); + else if (qName.equals("yAxis")) + checkYAxis(attributes); + else if (qName.equals("zAxis")) + checkZAxis(attributes); + else if (qName.equals("axis")) + checkAxis(attributes); + else if (qName.equals("timeaxis")) + checkTimeaxis(attributes); + else if (qName.equals("attachedaxis")) + checkAttachedaxis(attributes); + else if (qName.equals("colorbar")) + checkColorbar(attributes); + } + + /** + * Checks the attribute values for a colorbar element + */ + private void checkColorbar(Attributes attributes) throws SAXException { + String minimum = attributes.getValue("minimum"); + if (!FLOAT_PATTERN.matcher(minimum).matches() || minimum.charAt(0)=='-') + errorInternal("The minimum attribute of a colorbar element must be a positive number"); + String maximum = attributes.getValue("maximum"); + if (!FLOAT_PATTERN.matcher(maximum).matches() || maximum.charAt(0)=='-') + errorInternal("The maximum attribute of a colorbar element must be a positive number"); + String row = attributes.getValue("row"); + if (row != null) { + typeCheckList.add(new TypeCheck("colorbar", "row", "row", row, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"row\" attribute of a \"colorbar\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String column = attributes.getValue("column"); + if (column != null) { + typeCheckList.add(new TypeCheck("colorbar", "column", "column", column, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"column\" attribute of a \"colorbar\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String log = attributes.getValue("log"); + if (!(log.equals("true") || log.equals("false"))) + errorInternal("The log attribute of a colorbar must be either 'true' or 'false'"); + } + + private boolean hasXAxis = false; + private boolean hasYAxis = false; + private boolean hasZAxis = false; + private boolean insideSpectrogram = false; + + /** + * Checks the attribute values for a spectrogram element + */ + private void checkSpectrogram(Attributes attributes) throws SAXParseException { + String row = attributes.getValue("row"); + typeCheckList.add(new TypeCheck("spectrogram", "row", "row", row, locator)); + String column = attributes.getValue("column"); + typeCheckList.add(new TypeCheck("spectrogram", "column", "column", column, locator)); + + String xAxis = attributes.getValue("xAxis"); + if (xAxis != null) { + typeCheckList.add(new TypeCheck("spectrogram", "xAxis", "axis|timeaxis|attachedaxis", xAxis, locator)); + hasXAxis = true; + } + + String yAxis = attributes.getValue("yAxis"); + if (yAxis != null) { + typeCheckList.add(new TypeCheck("spectrogram", "yAxis", "axis|timeaxis|attachedaxis", yAxis, locator)); + hasYAxis = true; + } + + String colorbar = attributes.getValue("colorbar"); + if (colorbar != null) { + typeCheckList.add(new TypeCheck("spectrogram", "colorbar", "colorbar", colorbar, locator)); + hasZAxis = true; + } + + insideSpectrogram = true; + } + + private void endCheckSpectrogram() throws SAXException { + + if (!hasXAxis) { + errorInternal("No xAxis specified. Spectrograms require an xAxis to be specified"); + } + if (!hasYAxis) { + errorInternal("No yAxis specified. Spectrograms required a yAxis to be specified"); + } + if (!hasZAxis) { + errorInternal("No zAxis specified. Spectrograms required a zAxis to be specified"); + } + + hasXAxis = false; + hasYAxis = false; + hasZAxis = false; + insideSpectrogram = false; + } + + private void checkXAxis(Attributes attributes) throws SAXException { + hasXAxis = true; + } + + private void checkYAxis(Attributes attributes) throws SAXException { + hasYAxis = true; + } + + private void checkZAxis(Attributes attributes) throws SAXException { + hasZAxis = true; + } + + /** + * Checks the attribute values for a attachedaxis element + */ + private void checkAttachedaxis(Attributes attributes) throws SAXException { + String ref = attributes.getValue("ref"); + typeCheckList.add(new TypeCheck("attachedaxis", "ref", "axis|timeaxis", ref, locator)); + String row = attributes.getValue("row"); + if (row != null) { + typeCheckList.add(new TypeCheck("attachedaxis", "row", "row", row, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"row\" attribute of an \"attachedaxis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String column = attributes.getValue("column"); + if (column != null) { + typeCheckList.add(new TypeCheck("attachedaxis", "column", "column", column, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"column\" attribute of an \"attachedaxis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String orientation = attributes.getValue("orientation"); + if (!(orientation.equals("horizontal") || orientation.equals("vertical"))) + errorInternal("The orientation attibute of an attachedaxis element must be either 'horizontal' or 'vertical'"); + } + + /** + * Checks the attribute values for a timeaxis element + */ + private void checkTimeaxis(Attributes attributes) throws SAXException { + String showTca = attributes.getValue("showTca"); + if (!(showTca.equals("true") || showTca.equals("false"))) + errorInternal("The showTca attribute of a timeaxis element must be either 'true' or 'false'"); + String row = attributes.getValue("row"); + if (row != null) { + typeCheckList.add(new TypeCheck("timeaxis", "row", "row", row, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"row\" attribute of a \"timeaxis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String column = attributes.getValue("column"); + if (column != null) { + typeCheckList.add(new TypeCheck("timeaxis", "column", "column", column, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"column\" attribute of a \"timeaxis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String orientation = attributes.getValue("orientation"); + if (!(orientation.equals("horizontal") || orientation.equals("vertical"))) + errorInternal("The orientation attibute of an axis element must be either 'horizontal' or 'vertical'"); + if (showTca.equals("true") && orientation.equals("vertical")) + errorInternal("Vertical axes cannot diplay time correlated annotations"); + } + + /** + * Checks the attribute values for an axis element + */ + private void checkAxis(Attributes attributes) throws SAXException { + String log = attributes.getValue("log"); + if (!(log.equals("true") || log.equals("false"))) + errorInternal("The log attribute of an axis element must be either 'true' or 'false'"); + String dataMinimum = attributes.getValue("dataMinimum"); + if (!FLOAT_PATTERN.matcher(dataMinimum).matches()) + errorInternal("The dataMinimum attribute of an axis element must be a valid number'"); + String dataMaximum = attributes.getValue("dataMaximum"); + if (!FLOAT_PATTERN.matcher(dataMinimum).matches()) + errorInternal("The dataMaximum attribute of an axis element must be a valid number'"); + String row = attributes.getValue("row"); + if (row != null) { + typeCheckList.add(new TypeCheck("axis", "row", "row", row, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"row\" attribute of an \"axis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String column = attributes.getValue("column"); + if (column != null) { + typeCheckList.add(new TypeCheck("axis", "column", "column", column, locator)); + } + else if (!insideSpectrogram) { + errorInternal("The \"column\" attribute of an \"axis\" element must be specified" + + " if the element is not nested in a \"spectrogram\" element"); + } + String orientation = attributes.getValue("orientation"); + if (!(orientation.equals("horizontal") || orientation.equals("vertical"))) + errorInternal("The orientation attibute of an axis element must be either 'horizontal' or 'vertical'"); + } + + /** + * Checks the attribute values for a row or column element + */ + private void checkRowColumn(String tagName, Attributes attributes) throws SAXException { + String minimum = attributes.getValue("minimum"); + if (!FLOAT_PATTERN.matcher(minimum).matches() || minimum.charAt(0)=='-') + errorInternal("The minimum attribute of a " + tagName + " element must be a positive number"); + String maximum = attributes.getValue("maximum"); + if (!FLOAT_PATTERN.matcher(maximum).matches() || maximum.charAt(0)=='-') + errorInternal("The maximum attribute of a " + tagName + " element must be a positive number"); + } + + /** + * Checks the attribute values for a canvas element + */ + private void checkCanvas(Attributes attributes) throws SAXException { + String width = attributes.getValue("width"); + if (!INTEGER_PATTERN.matcher(width).matches()) + errorInternal("The width attribute of a canvas element must be a positive integer"); + String height = attributes.getValue("height"); + if (!INTEGER_PATTERN.matcher(height).matches()) + errorInternal("The height attribute of a canvas element must be a positive integer"); + } + + /** + * Checks the attribute values for a glue element + */ + private void checkGlue(Attributes attributes) throws SAXException { + String direction = attributes.getValue("direction"); + if (!(direction.equals("horizontal") || direction.equals("vertical"))) + errorInternal("The direction attribute of a glue element must be either 'horizontal' or 'vertical'"); + } + + /** + * Checks the attribute values for a panel element + */ + private void checkPanel(Attributes attributes) throws SAXException { + String direction = attributes.getValue("direction"); + if (!(direction.equals("horizontal") || direction.equals("vertical"))) + errorInternal("The direction attribute of a panel element must be either 'horizontal' or 'vertical'"); + String border = attributes.getValue("border"); + if (!(border.equals("true") || border.equals("false"))) + errorInternal("The border attribute of a panel element must be either 'true' or 'false'"); + } + + /** + * Checks the attribute values for a checkbox element + */ + private void checkRadiobutton(Attributes attributes) throws SAXException { + String group = attributes.getValue("group"); + typeCheckList.add(new TypeCheck("radiobutton", + "group", + "buttongroup", + group, + locator)); + String selected = attributes.getValue("selected"); + if (!(selected.equals("true") || selected.equals("false"))) + errorInternal("The selected attribute of a radiobutton element must be either 'true' or 'false'"); + } + + /** + * Checks the attribute values for a checkbox element + */ + private void checkCheckbox(Attributes attributes) throws SAXException { + String selected = attributes.getValue("selected"); + if (!(selected.equals("true") || selected.equals("false"))) + errorInternal("The selected attribute of a checkbox element must be either 'true' or 'false'"); + } + + /** + * Checks the attribute values for a textfield element + */ + private void checkTextfield(Attributes attributes) throws SAXException { + String length = attributes.getValue("length"); + if (!INTEGER_PATTERN.matcher(length).matches()) + errorInternal("The length attribute of textfield elements must be a positive integer"); + } + + /** + * Checks the attribute values for a form element + */ + private void checkForm(Attributes attributes) throws SAXException { + String alignment = attributes.getValue("alignment"); + if (!(alignment.equals("left") || alignment.equals("center") || alignment.equals("right"))) + errorInternal("The alignment attribute of a form element must be 'left', 'center', or 'right'"); + } + + /** + * Checks the attribute values for a window element + */ + private void checkWindow(Attributes attributes) throws SAXException { + String width = attributes.getValue("width"); + if (!INTEGER_PATTERN.matcher(width).matches()) + errorInternal("The width attribute of a window element must be a positve integer."); + String height = attributes.getValue("height"); + if (!INTEGER_PATTERN.matcher(height).matches()) + errorInternal("The height attribute of a window element must be a positive integer."); + String location = attributes.getValue("location"); + if (!WINDOW_POSITION_PATTERN.matcher(location).matches()) + errorInternal("The location attribute of a window element must be a pair of the form (x,y)"); + String visible = attributes.getValue("visible"); + if (!visible.equals("true") && !visible.equals("false")) + errorInternal("The visible attribute of a window element must be either 'true' or 'false'"); + } + + /** Receive notification of the end of the document. + * + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @see org.xml.sax.ContentHandler#endDocument + */ + public void endDocument() throws SAXException { + Iterator iterator = typeCheckList.iterator(); + SAXParseException exception = null; + while (iterator.hasNext()) { + TypeCheck check = (TypeCheck)iterator.next(); + String type = (String)typeMap.get(check.value); + if (type == null) { + exception = new SAXParseException("No element of type \"" + check.type + "\" with attribute " + + "name=\"" + check.value + "\" exists.", + check); + error(exception); + } + if (!Pattern.matches(check.type, type)) { + exception = new SAXParseException("Element '" + check.elementName + "', " + + "attribute '" + check.attributeName + "' : " + + type + " expected, but found " + type + + " (" + check.value + ")", + check); + error(exception); + } + } + if (exception != null) throw exception; + } + + /** + * This class encapsulates the information necessary + * to check that the value of an attribute references + * an element of the property type. + */ + private static class TypeCheck implements Locator { + public String elementName; + public String attributeName; + public String type; + public String value; + private int lineNumber; + private int columnNumber; + private String publicId; + private String systemId; + public TypeCheck(String elementName, String attributeName, String type, String value, Locator locator) { + this.elementName = elementName; + this.attributeName = attributeName; + this.type = type; + this.value = value; + this.lineNumber = locator.getLineNumber(); + this.columnNumber = locator.getColumnNumber(); + this.publicId = locator.getPublicId(); + this.systemId = locator.getSystemId(); + } + public int getColumnNumber() { return columnNumber; } + public int getLineNumber() { return lineNumber; } + public String getPublicId() { return publicId; } + public String getSystemId() { return systemId; } + + } + + private void errorInternal(String message) throws SAXException { + lastError = new SAXParseException(message, locator); + error((SAXParseException)lastError); + } + + public static void main(String[] args) { + if (args.length < 1) { + System.err.println("usage: java org.das2.dasml.DasMLValidator "); + return; + } + + ErrorHandler errorHandler = new ErrorHandler() { + public void warning(SAXParseException spe) throws SAXException { + org.das2.util.DasDie.println("Line " + spe.getLineNumber() + ", " + spe.getMessage()); + } + public void error(SAXParseException spe) throws SAXException { + org.das2.util.DasDie.println("Line " + spe.getLineNumber() + ", " + spe.getMessage()); + } + public void fatalError(SAXParseException spe) throws SAXException { + org.das2.util.DasDie.println("Line " + spe.getLineNumber() + ", " + spe.getMessage()); + } + }; + + try { + String path = new java.io.File(args[0]).getCanonicalPath(); + DasMLValidator validator = new DasMLValidator(); + if (validator.validate(new InputSource("file://" + path), errorHandler)) { + org.das2.util.DasDie.println("No errors"); + } + } + catch (ParserConfigurationException pce) { + org.das2.util.DasDie.println(pce.getMessage()); + } + catch (SAXException se) { + org.das2.util.DasDie.println(se.getMessage()); + } + catch (java.io.IOException ioe) { + org.das2.util.DasDie.println(ioe.getMessage()); + } + + } + + public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equals("spectrogram")) { + endCheckSpectrogram(); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/DataFormatException.java b/dasCore/src/main/java/org/das2/dasml/DataFormatException.java new file mode 100644 index 000000000..6a3dfcffd --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/DataFormatException.java @@ -0,0 +1,37 @@ +/* File: DataFormatException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +/** + * + * @author Edward West + */ +public class DataFormatException extends Exception { + public DataFormatException(String message) { + super(message); + } + public DataFormatException() { + super(); + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/DefaultComponentDnDSupport.java b/dasCore/src/main/java/org/das2/dasml/DefaultComponentDnDSupport.java new file mode 100644 index 000000000..bce7655e6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/DefaultComponentDnDSupport.java @@ -0,0 +1,59 @@ +/* File: DefaultComponentDnDSupport.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +/** + * + * @author eew + */ +class DefaultComponentDnDSupport extends org.das2.util.DnDSupport { + + DefaultComponentDnDSupport(java.awt.Component c) { + this(c, java.awt.dnd.DnDConstants.ACTION_NONE); + } + + /** Creates a new instance of ComponentDnDSupport */ + DefaultComponentDnDSupport(java.awt.Component c, int action) { + super(c, action, null); + } + + protected int canAccept(java.awt.datatransfer.DataFlavor[] flavors, int x, int y, int action) { + return -1; + } + + protected void done() { + } + + protected boolean importData(java.awt.datatransfer.Transferable t, int x, int y, int action) { + return false; + } + + protected java.awt.datatransfer.Transferable getTransferable(int x, int y, int action) { + return null; + } + + protected void exportDone(java.awt.datatransfer.Transferable t, int action) { + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormBase.java b/dasCore/src/main/java/org/das2/dasml/FormBase.java new file mode 100644 index 000000000..65d6df333 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormBase.java @@ -0,0 +1,712 @@ +/* File: FormBase.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.DasApplication; +import org.das2.beans.BeansUtil; +import org.das2.datum.Datum; +import org.das2.util.DasExceptionHandler; +//import org.apache.xml.serialize.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.swing.*; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.*; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.MethodDescriptor; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.*; +import java.util.List; +import org.das2.DasException; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; + +/** This class displays a Java form that is generated from an XML Document that is provided as input. + * + * @author Edward West + */ +public class FormBase extends JTabbedPane implements FormComponent { + + /** The factory object used for creating DOM parsers. */ + private static DocumentBuilderFactory domFactory; + + private DasApplication application = DasApplication.getDefaultApplication(); + + /** static initialization block for this class */ + static { + domFactory = DocumentBuilderFactory.newInstance(); + URL schemaLocation = FormBase.class.getResource("schema/dasML.xsd"); + if (schemaLocation != null) { + domFactory.setAttribute( + "http://apache.org/xml/features/validation/schema-full-checking", + Boolean.TRUE); + domFactory.setAttribute( + "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation", + schemaLocation.toExternalForm()); + domFactory.setValidating(true); + domFactory.setNamespaceAware(true); + } + else { + //TODO: Report this error, also maybe try to recover. + } + domFactory.setCoalescing(true); + domFactory.setIgnoringElementContentWhitespace(true); + } + + /**Initialization commands*/ + private CommandBlock initBlock; + + List windowList = new ArrayList(); + + private JDesktopPane desktop; + + boolean editable; + + private boolean onHover = false; + + /** Setup for DnD support */ + { + DnDSupport dndSupport = new DnDSupport(); + DropTarget dropTarget = new DropTarget(this, dndSupport); + setDropTarget(dropTarget); + } + + /** Creates a FormBase object + * + * @param url A uniform resouce locator pointing to the XML document to parse. + */ + public FormBase(URL url, ErrorHandler eh, boolean editable) throws IOException, SAXException { + this(url.openStream(), eh, editable); + } + + public FormBase(InputStream in, ErrorHandler eh, boolean editable) throws IOException, SAXException { + this(new InputStreamReader(in), eh, editable); + } + + public FormBase(Reader reader, ErrorHandler eh, boolean editable) throws IOException, SAXException { + this.editable = editable; + + try { + Document document = parseDasML(reader, eh); + createFormFromTree(document); + registerComponent(); + } + catch (ParserConfigurationException pce) { + throw new IllegalStateException("DOM parser not configured properly: " + pce.getMessage()); + } + catch (org.das2.DasException de) { + throw new SAXException(de); + } + } + + public FormBase(boolean editable) { + this.editable = editable; + } + + static Document parseDasML(Reader reader, ErrorHandler eh) throws ParserConfigurationException, SAXException, IOException { + InputSource source = new InputSource(); + source.setCharacterStream(reader); + DocumentBuilder builder; + synchronized (domFactory) { + builder = domFactory.newDocumentBuilder(); + } + builder.setErrorHandler(eh); + return builder.parse(source); + } + + /** Sets up the Java form using a DOM document tree. + * + * @param doc The DOM document tree. + */ + private void createFormFromTree(Document doc) throws SAXException { + try { + Element das2 = doc.getDocumentElement(); + if (!das2.getTagName().equals("das2")) + ;//TODO: do some sort of error handling here. + NodeList children = das2.getChildNodes(); + int childCount = children.getLength(); + for (int index = 0; index < childCount; index++) { + Node node = children.item(index); + if (node instanceof Element) { + if (node.getNodeName().equals("form")) { + FormTab form = new FormTab((Element)node, this); + addForm(form); + } + else if (node.getNodeName().equals("window")) { + FormWindow window = new FormWindow((Element)node, this); + addWindow(window); + } + else if (node.getNodeName().equals("init")) { + initBlock = processInitElement((Element)node); + } + else { + //TODO: error message. + } + } + else { + //TODO: nothing, only interested in elements. + } + } + } + catch (DasException dne) { + throw new SAXException(dne); + } + catch (ParsedExpressionException pee) { + throw new SAXException(pee); + } + } + + private int getTabInsertionIndex() { + int index = 0; + int count = getComponentCount(); + while (index < count && getComponent(index) instanceof FormTab) { + index++; + } + return index; + } + + public void addForm(FormTab form) { + synchronized (getTreeLock()) { + if (getEditingMode()) { + int index = getTabInsertionIndex(); + insertTab(form.getLabel(), null, form, null, index); + } + else { + addTab(form.getLabel(), form); + } + } + form.setEditingMode(getEditingMode()); + } + + public void addWindow(FormWindow window) { + if (window.form != null) { + window.form.removeWindow(window); + } + window.form = this; + windowList.add(window); + boolean editingMode = getEditingMode(); + window.setEditingMode(editingMode); + if (editingMode) { + if (desktop == null) { + desktop = new JDesktopPane(); + add(desktop, "Windows"); + } + window.pack(); + desktop.add(window.getInternalFrame()); + } + this.firePropertyChange("window", null, window); + } + + public void removeWindow(FormWindow window) { + if (windowList.contains(window)) { + windowList.remove(window); + if (getEditingMode()) { + desktop.remove(SwingUtilities.getAncestorOfClass(JInternalFrame.class, window)); + } + firePropertyChange("window", window, null); + } + } + + /** Process a <action> element. + * + * @param element The DOM tree node that represents the element + */ + private CommandBlock processActionElement(Element element) { + return new CommandBlock(element, this); + } + + /** Process a <glue> element. + * + * @param element The DOM tree node that represents the element + */ + Component processGlueElement(Element element) { + String direction = element.getAttribute("direction"); + if (direction.equals("horizontal")) + return Box.createHorizontalGlue(); + else return Box.createVerticalGlue(); + } + + private CommandBlock processInitElement(Element element) throws SAXException { + return new CommandBlock(element, this); + } + + + + /** + * Writes the XML representation of this form to the specified + * byte stream + * + * @param out the specified byte stream + */ + public void serialize(OutputStream out) throws IOException { + try { + DocumentBuilder builder = domFactory.newDocumentBuilder(); + Document document = builder.newDocument(); + document.appendChild(getDOMElement(document)); + + DOMImplementationLS ls = (DOMImplementationLS) + document.getImplementation().getFeature("LS", "3.0"); + LSOutput output = ls.createLSOutput(); + output.setEncoding("UTF-8"); + output.setByteStream(out); + ls.createLSSerializer().write(document, output); + out.close(); + + /* + String method = org.apache.xml.serialize.Method.XML; + OutputFormat format = new OutputFormat(method, "UTF-8", true); + format.setLineWidth(0); + XMLSerializer serializer = new XMLSerializer(out, format); + serializer.serialize(document); + */ + } + catch (ParserConfigurationException pce) { + IOException ioe = new IOException(pce.getMessage()); + ioe.initCause(pce); + throw ioe; + } + } + + private boolean isValidType(Class type) { + return type.isPrimitive() + || type == String.class + || type == Datum.class + || org.das2.datum.Datum.class.isAssignableFrom(type) + || Number.class.isAssignableFrom(type); + } + + + public Object checkValue(String name, Class type, String tag) throws org.das2.DasPropertyException, org.das2.DasNameException { + try { + Object obj = application.getNameContext().get(name); + if (obj == null) { + throw new org.das2.DasNameException(name + " must be defined before it is used"); + } + if (!type.isInstance(obj)) { + throw new org.das2.DasPropertyException(org.das2.DasPropertyException.TYPE_MISMATCH, name, null); + } + return obj; + } + catch (InvocationTargetException ite) { + throw new RuntimeException(ite); + } + } + + public Object invoke(String name, String[] args) throws org.das2.DasPropertyException, DataFormatException, ParsedExpressionException, InvocationTargetException { + int lastDot = name.lastIndexOf('.'); + if (lastDot == -1) throw new DataFormatException("No object associated with method name" + name); + String objectName = name.substring(0, lastDot); + String methodName = name.substring(lastDot+1); + org.das2.util.DasDie.println("object name: " + objectName); + org.das2.util.DasDie.println("method name: " + methodName); + Object o = application.getNameContext().get(objectName); + Method method = null; + try { + BeanInfo info = BeansUtil.getBeanInfo(o.getClass()); + MethodDescriptor[] methodDescriptors = info.getMethodDescriptors(); + for (int i = 0; i <= methodDescriptors.length; i++) { + if (i == methodDescriptors.length) + throw new org.das2.DasPropertyException(org.das2.DasPropertyException.NOT_DEFINED, methodName, objectName); + if (!methodDescriptors[i].getName().equals(methodName)) continue; + //if (methodDescriptors[i].getMethod().getParameterTypes().length != args.length) continue; + method = methodDescriptors[i].getMethod(); + break; + } + Class[] parameterTypes = method.getParameterTypes(); + Object[] argValues = new Object[args.length]; + for (int i = 0; i < parameterTypes.length; i++) { + argValues[i] = application.getNameContext().parseValue(args[i], parameterTypes[i]); + } + return method.invoke(o, argValues); + } + catch (IntrospectionException ie) { + throw new DataFormatException(ie.getMessage()); + } + catch (InvocationTargetException ite) { + throw new DataFormatException(ite.getTargetException().getMessage()); + } + catch (IllegalAccessException iae) { + throw new DataFormatException(iae.getMessage()); + } + } + + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + if (editable != b) { + editable = b; + int componentCount = getComponentCount(); + for (int i = 0; i < componentCount; i++) { + Component c = getComponent(i); + if (c instanceof FormTab) { + ((FormTab)c).setEditingMode(b); + } + } + if (windowList.size() > 0) { + if (b) { + if (desktop == null) { + desktop = new JDesktopPane(); + } + add(desktop, "Windows"); + } + else { + remove(desktop); + } + for (Iterator i = windowList.iterator(); i.hasNext();) { + FormWindow window = (FormWindow)i.next(); + window.setEditingMode(b); + if (b) { + window.pack(); + JInternalFrame it = window.getInternalFrame(); + if (it.getParent() != desktop) { + desktop.add(it); + } + } + } + } + revalidate(); + repaint(); + if (b) { + firePropertyChange("editable", Boolean.FALSE, Boolean.TRUE); + } + else { + firePropertyChange("editable", Boolean.TRUE, Boolean.FALSE); + } + } + } + + public FormBase getForm() { + return this; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("das2"); + for (int index = 0; index < getComponentCount(); index++) { + FormComponent child = (FormComponent)getComponent(index); + element.appendChild(child.getDOMElement(document)); + } + if (!editable) { + for (Iterator i = windowList.iterator(); i.hasNext();) { + FormComponent child = (FormComponent)i.next(); + element.appendChild(child.getDOMElement(document)); + } + } + return element; + } + + /** Paints the component's border. + *

+ * If you override this in a subclass you should not make permanent + * changes to the passed in Graphics. For example, you + * should not alter the clip Rectangle or modify the + * transform. If you need to do these operations you may find it + * easier to create a new Graphics from the passed in + * Graphics and manipulate it. + * + * @param g the Graphics context in which to paint + * + * @see #paint + * @see #setBorder + */ + protected void paintBorder(Graphics g) { + super.paintBorder(g); + if (onHover) { + Graphics2D g2 = (Graphics2D)g.create(); + Stroke thick = new BasicStroke(3.0f); + g2.setStroke(thick); + g2.setColor(Color.GRAY); + g2.drawRect(1, 1, getWidth() - 2, getHeight() - 2); + g2.dispose(); + } + } + + public org.das2.util.DnDSupport getDnDSupport() { + return null; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public String getDasName() { + return null; + } + + public List getWindowList() { + return Collections.unmodifiableList(windowList); + } + + public void setDasName(String name) throws org.das2.DasNameException { + throw new org.das2.DasNameException(); + } + + public void deregisterComponent() { + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof FormComponent) { + ((FormComponent)c).deregisterComponent(); + } + } + for (Iterator i = windowList.iterator(); i.hasNext();) { + FormWindow w = (FormWindow)i.next(); + w.deregisterComponent(); + } + } + + public org.das2.DasApplication getDasApplication() { + return application; + } + + public void registerComponent() throws org.das2.DasException { + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof FormComponent) { + ((FormComponent)c).registerComponent(); + } + } + for (Iterator i = windowList.iterator(); i.hasNext();) { + FormWindow w = (FormWindow)i.next(); + w.registerComponent(); + } + } + + private class DnDSupport implements DropTargetListener { + + private final Set acceptableFlavors = new HashSet(Arrays.asList(new DataFlavor[] { + TransferableFormComponent.TAB_FLAVOR, + TransferableFormComponent.WINDOW_FLAVOR + })); + + /** Called while a drag operation is ongoing, when the mouse pointer enters + * the operable part of the drop site for the DropTarget + * registered with this listener. + * + * @param dtde the DropTargetDragEvent + */ + public void dragEnter(DropTargetDragEvent dtde) { + if (canAccept(dtde.getCurrentDataFlavors())) { + dtde.acceptDrag(dtde.getSourceActions()); + onHover = true; + repaint(); + } + } + + /** Called while a drag operation is ongoing, when the mouse pointer has + * exited the operable part of the drop site for the + * DropTarget registered with this listener. + * + * @param dte the DropTargetEvent + */ + public void dragExit(DropTargetEvent dte) { + onHover = false; + repaint(); + } + + /** Called when a drag operation is ongoing, while the mouse pointer is still + * over the operable part of the drop site for the DropTarget + * registered with this listener. + * + * @param dtde the DropTargetDragEvent + */ + public void dragOver(DropTargetDragEvent dtde) { + } + + /** Called when the drag operation has terminated with a drop on + * the operable part of the drop site for the DropTarget + * registered with this listener. + *

+ * This method is responsible for undertaking + * the transfer of the data associated with the + * gesture. The DropTargetDropEvent + * provides a means to obtain a Transferable + * object that represents the data object(s) to + * be transfered.

+ * From this method, the DropTargetListener + * shall accept or reject the drop via the + * acceptDrop(int dropAction) or rejectDrop() methods of the + * DropTargetDropEvent parameter. + *

+ * Subsequent to acceptDrop(), but not before, + * DropTargetDropEvent's getTransferable() + * method may be invoked, and data transfer may be + * performed via the returned Transferable's + * getTransferData() method. + *

+ * At the completion of a drop, an implementation + * of this method is required to signal the success/failure + * of the drop by passing an appropriate + * boolean to the DropTargetDropEvent's + * dropComplete(boolean success) method. + *

+ * Note: The data transfer should be completed before the call to the + * DropTargetDropEvent's dropComplete(boolean success) method. + * After that, a call to the getTransferData() method of the + * Transferable returned by + * DropTargetDropEvent.getTransferable() is guaranteed to + * succeed only if the data transfer is local; that is, only if + * DropTargetDropEvent.isLocalTransfer() returns + * true. Otherwise, the behavior of the call is + * implementation-dependent. + *

+ * @param dtde the DropTargetDropEvent + */ + public void drop(DropTargetDropEvent dtde) { + boolean success = false; + if (canAccept(dtde.getCurrentDataFlavors())) { + Transferable t = dtde.getTransferable(); + if (t.isDataFlavorSupported(TransferableFormComponent.COMPONENT_FLAVOR)) { + dtde.acceptDrop(dtde.getDropAction()); + success = acceptComponent(t); + } + else if (t.isDataFlavorSupported(TransferableFormComponent.DASML_FRAGMENT_FLAVOR)) { + dtde.acceptDrop(DnDConstants.ACTION_COPY); + success = acceptFragment(t); + } + dtde.dropComplete(success); + } + else { + dtde.rejectDrop(); + } + onHover = false; + repaint(); + } + + private boolean acceptFragment(Transferable t) { + boolean success = false; + try { + String data = (String)t.getTransferData(TransferableFormComponent.DASML_FRAGMENT_FLAVOR); + Document document = FormBase.parseDasML(new StringReader(data), null); + Element root = document.getDocumentElement(); + if (root.getTagName().equals("form")) { + FormTab tab = new FormTab(root, FormBase.this); + addForm(tab); + success = true; + revalidate(); + } + else if (root.getTagName().equals("window")) { + FormWindow window = new FormWindow(root, FormBase.this); + addWindow(window); + success = true; + revalidate(); + } + } + catch (org.das2.dasml.ParsedExpressionException pee) { + pee.printStackTrace(); + } + + catch (org.das2.DasException de) { + de.printStackTrace(); + } catch (UnsupportedFlavorException ufe) { + //Allow to fall through. + //exception is handled by allowing success to remain false + } + catch (IOException ioe) { + DasExceptionHandler.handle(ioe); + //Allow to fall through. + //exception is handled by allowing success to remain false + } + catch (ParserConfigurationException pce) { + DasExceptionHandler.handle(pce); + //Allow to fall through. + //exception is handled by allowing success to remain false + } + catch (SAXException se) { + DasExceptionHandler.handle(se); + //Allow to fall through. + //exception is handled by allowing success to remain false + } + return success; + } + + private boolean acceptComponent(Transferable t) { + boolean success = false; + try { + Component c = (Component)t.getTransferData(TransferableFormComponent.COMPONENT_FLAVOR); + if (c instanceof FormTab) { + addForm((FormTab)c); + success = true; + revalidate(); + } + else if (c instanceof FormWindow) { + addWindow((FormWindow)c); + success = true; + revalidate(); + } + else { + System.out.println(c); + } + } + catch (UnsupportedFlavorException ufe) { + //Allow to fall through. + //exception is handled by allowing success to remain false + } + catch (IOException ioe) { + //Allow to fall through. + //exception is handled by allowing success to remain false + } + return success; + } + + /** Called if the user has modified + * the current drop gesture. + *

+ * @param dtde the DropTargetDragEvent + */ + public void dropActionChanged(DropTargetDragEvent dtde) { + if (onHover) { + dtde.acceptDrag(dtde.getDropAction()); + } + } + + private boolean canAccept(DataFlavor[] flavors) { + for (int i = 0; i < flavors.length; i++) { + if (acceptableFlavors.contains(flavors[i])) { + return true; + } + } + return false; + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormButton.java b/dasCore/src/main/java/org/das2/dasml/FormButton.java new file mode 100644 index 000000000..1503fdf08 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormButton.java @@ -0,0 +1,228 @@ +/* File: FormButton.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +/** + * This class is provided to override the Java Beans properties of + * the JButton class. + * + * @author Edward West + */ +public class FormButton extends JButton implements Editable, FormComponent { + + CommandAction commandAction; + + CommandBlock commandBlock; + + private boolean editable; + + private String dasName; + + protected org.das2.util.DnDSupport dndSupport; + + public FormButton(String name, String label) { + super(label); + if (name == null) { + name = "button_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + /** Creates a new instance of FormButton */ + FormButton(Element element, FormBase form) + throws org.das2.DasPropertyException, ParsedExpressionException { + + String name = element.getAttribute("name"); + String label = element.getAttribute("label"); + boolean enabled = element.getAttribute("enabled").equals("true"); + + setText(label); + setEnabled(enabled); + + if (!name.equals("")) { + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int index = 0; index < childCount; index++) { + Node node = children.item(index); + if (node instanceof Element && node.getNodeName().equals("action")) { + Element actionElement = (Element)node; + commandBlock = new CommandBlock(actionElement, form); + commandAction = new CommandAction(commandBlock); + addActionListener(commandAction); + } + } + } + + public CommandBlock getFormAction() { + return commandBlock; + } + + public void setFormAction(CommandBlock cb) { + if (cb == commandBlock) { + return; + } + if (commandBlock != null) { + removeActionListener(commandAction); + } + if (cb == null) { + commandAction = null; + commandBlock = null; + } + else { + commandBlock = cb; + commandAction = new CommandAction(commandBlock); + addActionListener(commandAction); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("button"); + element.setAttribute("name", getDasName()); + element.setAttribute("label", getText()); + element.setAttribute("enabled", String.valueOf(isEnabled())); + if (commandBlock != null) { + Element actionElement = document.createElement("action"); + commandBlock.appendDOMElements(actionElement); + element.appendChild(actionElement); + } + return element; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + editable = b; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormButtonBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormButtonBeanInfo.java new file mode 100644 index 000000000..9dd27469f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormButtonBeanInfo.java @@ -0,0 +1,44 @@ +/* File: FormButtonBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormRadioButton class + */ +public class FormButtonBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null), + new Property("label", AccessLevel.ALL, "getText", "setText", null), + new Property("formAction", AccessLevel.ALL, "getFormAction", "setFormAction", CommandBlockEditor.class) + }; + + public FormButtonBeanInfo() { + super(properties, FormButton.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormCheckBox.java b/dasCore/src/main/java/org/das2/dasml/FormCheckBox.java new file mode 100644 index 000000000..883a38037 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormCheckBox.java @@ -0,0 +1,226 @@ +/* File: FormCheckBox.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +/** + * This class is provided to override the Java Beans properties of + * the JCheckBox class. + * + * @author Edward West + */ +public class FormCheckBox extends JCheckBox implements Editable, FormComponent { + + CommandAction commandAction; + + CommandBlock commandBlock; + + private String dasName; + + protected org.das2.util.DnDSupport dndSupport; + + private boolean editable; + + public FormCheckBox(String name, String label) { + super(label); + if (name == null) { + name = "checkbox_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch(org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + FormCheckBox(Element element, FormBase form) + throws org.das2.DasPropertyException, ParsedExpressionException, + org.das2.DasNameException { + + String name = element.getAttribute("name"); + String label = element.getAttribute("label"); + boolean enabled = element.getAttribute("enabled").equals("true"); + boolean selected = element.getAttribute("selected").equals("true"); + + setText(label); + setEnabled(enabled); + setSelected(selected); + + try { + setDasName(name); + } + catch(org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int index = 0; index < childCount; index++) { + Node node = children.item(index); + if (node instanceof Element && node.getNodeName().equals("action")) { + commandBlock = new CommandBlock((Element)node, form); + commandAction = new CommandAction(commandBlock); + addActionListener(commandAction); + } + } + } + + public CommandBlock getFormAction() { + return commandBlock; + } + + public void setFormAction(CommandBlock cb) { + if (cb == commandBlock) { + return; + } + if (commandBlock != null) { + removeActionListener(commandAction); + } + if (cb == null) { + commandAction = null; + commandBlock = null; + } + else { + commandBlock = cb; + commandAction = new CommandAction(commandBlock); + addActionListener(commandAction); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("checkbox"); + element.setAttribute("name", getDasName()); + element.setAttribute("enabled", String.valueOf(isEnabled())); + element.setAttribute("selected", String.valueOf(isSelected())); + element.setAttribute("label", getText()); + if (commandBlock != null) { + Element commandElement = document.createElement("action"); + commandBlock.appendDOMElements(element); + } + return element; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + editable = b; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormCheckBoxBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormCheckBoxBeanInfo.java new file mode 100644 index 000000000..b183a80b1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormCheckBoxBeanInfo.java @@ -0,0 +1,45 @@ +/* File: FormCheckBoxBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormRadioButton class + */ +public class FormCheckBoxBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null), + new Property("selected", AccessLevel.DASML, "isSelected", "setSelected", null), + new Property("label", AccessLevel.ALL, "getText", "setText", null), + new Property("formAction", AccessLevel.ALL, "getFormAction", "setFormAction", CommandBlockEditor.class) + }; + + public FormCheckBoxBeanInfo() { + super(properties, FormCheckBox.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormChoice.java b/dasCore/src/main/java/org/das2/dasml/FormChoice.java new file mode 100644 index 000000000..2f71ab1ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormChoice.java @@ -0,0 +1,248 @@ +/* File: FormChoice.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +/** + * Drop down list for making single selections. + */ +public class FormChoice extends JComboBox implements Editable, FormComponent, OptionList { + + protected org.das2.util.DnDSupport dndSupport; + + private boolean editable; + + private String dasName; + + public FormChoice(String name) { + if (name == null) { + name = "choice_" + Integer.toString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch(org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + FormChoice(Element element, FormBase form) + throws org.das2.DasPropertyException,org.das2.DasNameException, + ParsedExpressionException { + + super(); + + String name = element.getAttribute("name"); + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int i = 0; i < childCount; i++) { + Node node = children.item(i); + if (node instanceof Element && node.getNodeName().equals("option")) { + processOptionElement((Element)node); + } + else if (node instanceof Element && node.getNodeName().equals("action")) { + CommandBlock cb = new CommandBlock((Element)node, form); + CommandAction action = new CommandAction(cb); + addActionListener(action); + } + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + public java.awt.Dimension getMinimumSize() { + return getPreferredSize(); + } + + public java.awt.Dimension getMaximumSize() { + return getPreferredSize(); + } + + private void processOptionElement(Element element) { + boolean selected = element.getAttribute("selected").equals("true"); + ListOption option = new ListOption(element); + addItem(option); + if (selected) setSelectedItem(option); + } + + public String getSelectedValue() { + ListOption selected = (ListOption)getSelectedItem(); + if (selected == null) { + return null; + } + return selected.getValue(); + } + + public void addOption(ListOption option) { + addItem(option); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("choice"); + element.setAttribute("name", getDasName()); + element.setAttribute("enabled", String.valueOf(isEnabled())); + for (int index = 0; index < getItemCount(); index++) { + ListOption option = (ListOption)getItemAt(index); + element.appendChild(option.getDOMElement(document)); + } + return element; + } + + public ListOption[] getOptions() { + ListModel model = getModel(); + ListOption[] options = new ListOption[model.getSize()]; + for (int index = 0; index < options.length; index++) { + options[index] = (ListOption)model.getElementAt(index); + } + return options; + } + + public void setOptions(ListOption[] options) { + setModel(new DefaultComboBoxModel(options)); + if (options.length == 0) { + setSelectedItem(null); + } + else { + setSelectedItem(options[0]); + for (int index = 0; index < options.length; index++) { + if (options[index].isSelected()) { + setSelectedItem(options[index]); + } + } + } + } + + public Object getPrototypeDisplayValue() { + if (this.getItemCount() == 0) { + return "XXXXXXXXXXXX"; + } + return null; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + editable = b; + } + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormChoiceBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormChoiceBeanInfo.java new file mode 100644 index 000000000..2035feb50 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormChoiceBeanInfo.java @@ -0,0 +1,45 @@ +/* File: FormChoiceBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * BeanInfo class for FormChoice + */ +public class FormChoiceBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("selectedValue", AccessLevel.DASML, "getSelectedValue", null, null), + new Property("selectedIndex", AccessLevel.DASML, "getSelectedIndex", "setSelectedIndex", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null), + new Property("options", AccessLevel.ALL, "getOptions", "setOptions", OptionListEditor.class) + }; + + public FormChoiceBeanInfo() { + super(properties, FormChoice.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormComponent.java b/dasCore/src/main/java/org/das2/dasml/FormComponent.java new file mode 100644 index 000000000..799a79787 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormComponent.java @@ -0,0 +1,62 @@ +/* File: FormComponent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasNameException; +import java.awt.event.MouseEvent; +import org.das2.util.DnDSupport; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * + * @author eew + */ +public interface FormComponent { + + Element getDOMElement(Document document); + + FormBase getForm(); + + boolean getEditingMode(); + + void setEditingMode(boolean b); + + DnDSupport getDnDSupport(); + + boolean startDrag(int x, int y, int action, MouseEvent evt); + + String getDasName(); + + void setDasName(String name) throws DasNameException; + + DasApplication getDasApplication(); + + void registerComponent() throws DasException; + + void deregisterComponent(); + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormContainer.java b/dasCore/src/main/java/org/das2/dasml/FormContainer.java new file mode 100644 index 000000000..33ed5fdfd --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormContainer.java @@ -0,0 +1,624 @@ +/* File: FormContainer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.graph.DasCanvas; +import org.das2.graph.dnd.TransferableCanvas; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.List; +import org.das2.DasNameException; +import org.das2.components.propertyeditor.Editable; + +/** + * A subclass of JPanel to override the default Beans properties of + * a JPanel + * + * @author Edward West + */ +public abstract class FormContainer extends JPanel implements Editable, FormComponent { + + float horizontalComponentAlignment = JComponent.LEFT_ALIGNMENT; + final float verticalComponentAlignment = JComponent.TOP_ALIGNMENT; + + boolean onHover = false; + org.das2.util.DnDSupport dndSupport; + List flavorList; + int dropPosition; + boolean editable; + + /** + * The axis along which child components will be laid out. + */ + private int axis = BoxLayout.X_AXIS; + + /** + * The titled displayed along the panel border. + */ + private String borderTitle = ""; + + /** + * If true, the panel has an etched border. + */ + private boolean hasBorder = false; + + /** + * Empty constructor for use with super classes. + */ + protected FormContainer() { + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + setBorder(new NoBorder()); + } + + /** + * Returns true if this panel has a border + */ + public boolean hasBorder() { + return hasBorder; + } + + /** + * If the given boolean is true then the panel will be + * given a border, if is does not already have one. If the given + * boolean is false then the panel's border will be + * removed, if it has one. + */ + public void setHasBorder(boolean b) { + if (hasBorder != b) { + hasBorder = b; + if (hasBorder) { + EtchedBorder etchedBorder = new EtchedBorder(); + TitledBorder titledBorder = new TitledBorder(etchedBorder, borderTitle, TitledBorder.LEFT, TitledBorder.TOP); + setBorder(titledBorder); + } + else { + setBorder(new NoBorder()); + } + } + } + + /** + * Returns the title that is displayed along the top of this panels border + * (if it has one). + */ + public String getBorderTitle() { + return borderTitle; + } + + /** + * Set the title that is displayed along the top of this panels border + * (if it has one) to the given String. + */ + public void setBorderTitle(String s) { + if (!s.equals(borderTitle)) { + borderTitle = s; + if (hasBorder) { + EtchedBorder etchedBorder = new EtchedBorder(); + TitledBorder titledBorder = new TitledBorder(etchedBorder, borderTitle, TitledBorder.LEFT, TitledBorder.TOP); + setBorder(titledBorder); + } + } + } + + /** Adds the specified component to this container at the specified + * index. This method also notifies the layout manager to add + * the component to this container's layout using the specified + * constraints object via the addLayoutComponent + * method. The constraints are + * defined by the particular layout manager being used. For + * example, the BorderLayout class defines five + * constraints: BorderLayout.NORTH, + * BorderLayout.SOUTH, BorderLayout.EAST, + * BorderLayout.WEST, and BorderLayout.CENTER. + * + *

Note that if the component already exists + * in this container or a child of this container, + * it is removed from that container before + * being added to this container. + *

+ * This is the method to override if a program needs to track + * every add request to a container as all other add methods defer + * to this one. An overriding method should + * usually include a call to the superclass's version of the method: + *

+ *

+ * super.addImpl(comp, constraints, index) + *
+ *

+ * @param comp the component to be added + * @param constraints an object expressing layout constraints + * for this component + * @param index the position in the container's list at which to + * insert the component, where -1 + * means append to the end + * @exception IllegalArgumentException if index is invalid + * @exception IllegalArgumentException if adding the container's parent + * to itself + * @exception IllegalArgumentException if adding a window to a container + * @see java.awt.Container#add(Component) + * @see java.awt.Container#add(Component, int) + * @see java.awt.Container#add(Component, java.lang.Object) + * @see java.awt.LayoutManager + * @see java.awt.LayoutManager2 + */ + protected void addImpl(Component comp, Object constraints, int index) { + if (comp instanceof JComponent) { + ((JComponent)comp).setAlignmentX(horizontalComponentAlignment); + ((JComponent)comp).setAlignmentY(verticalComponentAlignment); + } + super.addImpl(comp, constraints, index); + if (comp instanceof FormComponent) { + FormComponent fc = (FormComponent)comp; + org.das2.util.DnDSupport childDnDSupport = fc.getDnDSupport(); + if (childDnDSupport != null) { + childDnDSupport.setParent(dndSupport); + } + fc.setEditingMode(getEditingMode()); + } + packFormWindowAnscestor(); + } + + private void packFormWindowAnscestor() { + if (isDisplayable()) { + if (this instanceof FormWindow) { + ((FormWindow)this).pack(); + } + else { + FormWindow fw = (FormWindow)SwingUtilities.getAncestorOfClass(FormWindow.class, this); + if (fw != null) { + fw.pack(); + } + } + } + } + + public void removeAll() { + int ncomponents = getComponentCount(); + for (int index = ncomponents-1; index >= 0; index--) { + remove(index); + } + } + + public void remove(int index) { + super.remove(index); + packFormWindowAnscestor(); + } + + public void remove(Component c) { + super.remove(c); + packFormWindowAnscestor(); + } + + public void setDirection(Orientation direction) { + if (direction == Orientation.HORIZONTAL) { + if (axis != BoxLayout.X_AXIS) { + axis = BoxLayout.X_AXIS; + setLayout(new BoxLayout(this, axis)); + getForm().validate(); + } + } + else if (axis != BoxLayout.Y_AXIS) { + axis = BoxLayout.Y_AXIS; + setLayout(new BoxLayout(this, axis)); + revalidate(); + } + } + + public Orientation getDirection() { + if (axis == BoxLayout.X_AXIS) return Orientation.HORIZONTAL; + if (axis == BoxLayout.Y_AXIS) return Orientation.VERTICAL; + throw new AssertionError("Invalid value for axis"); + } + + /** + * Returns the FormBase object this component is associated with, or null + */ + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + /** + * Returns true if this component is in an editing state. + * @return true if this component is in an editing state. + */ + public boolean getEditingMode() { + return editable; + } + + public void paint(Graphics g) { + super.paint(g); + if (onHover) { + Graphics2D g2 = (Graphics2D)g.create(); + Stroke thick = new BasicStroke(3.0f); + g2.setStroke(thick); + g2.setPaint(Color.GRAY); + g2.drawRect(1, 1, getWidth() - 2, getHeight() - 2); + g2.setPaint(Color.ORANGE); + if (getDirection() == Orientation.HORIZONTAL) { + g2.drawLine(dropPosition, 4, dropPosition, getHeight() - 4); + } + else { + g2.drawLine(4, dropPosition, getHeight() - 4, dropPosition); + } + g2.dispose(); + } + } + + public void setEditingMode(boolean b) { + if (editable == b) return; + editable = b; + int componentCount = getComponentCount(); + for (int i = 0; i < componentCount; i++) { + if (getComponent(i) instanceof FormComponent) { + ((FormComponent)getComponent(i)).setEditingMode(b); + } + } + } + + private int[] getInsertionPositions() { + int componentCount = getComponentCount(); + int[] positions = new int[componentCount + 1]; + if (getDirection() == Orientation.HORIZONTAL) { + positions[0] = 2; + for (int i = 1; i <= componentCount; i++) { + Component c = getComponent(i - 1); + positions[i] = c.getX() + c.getWidth() + 1; + } + } + else { + positions[0] = 2; + for (int i = 1; i <= componentCount; i++) { + Component c = getComponent(i - 1); + positions[i] = c.getY() + c.getHeight() + 1; + } + } + return positions; + } + + private int getInsertionPosition(int p) { + int[] positions = getInsertionPositions(); + int insertionPosition = 0; + int dp = Integer.MAX_VALUE; + for (int i = 0; i < positions.length; i++) { + int delta = Math.abs(p - positions[i]); + if (delta < dp) { + dp = delta; + insertionPosition = positions[i]; + } + } + return insertionPosition; + } + + private int getInsertionIndex(int p) { + int[] positions = getInsertionPositions(); + int insertionIndex = 0; + int dp = Integer.MAX_VALUE; + for (int i = 0; i < positions.length; i++) { + int delta = Math.abs(p - positions[i]); + if (delta < dp) { + dp = delta; + insertionIndex = i; + } + } + return insertionIndex; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + for (int i = 0; i < getComponentCount(); i++) { + if (getComponent(i).getBounds().contains(x, y)) { + dndSupport.startDrag(x, y, action, evt); + return true; + } + } + return false; + } + + public Dimension getPreferredSize() { + if (getComponentCount() == 0) { + return new Dimension(100, 100); + } + else { + return super.getPreferredSize(); + } + } + + public Dimension getMinimumSize() { + if (getComponentCount() == 0) { + return new Dimension(100, 100); + } + else { + return super.getMinimumSize(); + } + } + + public Dimension getMaximumSize() { + Dimension pref = getPreferredSize(); + Dimension max = super.getMaximumSize(); + max.width = Math.max(max.width, 100); + max.height = pref.height; + return max; + } + + public String getDasName() { + return null; + } + + public void setDasName(String name) throws org.das2.DasNameException { + throw new org.das2.DasNameException(); + } + + public void deregisterComponent() { + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof FormComponent) { + ((FormComponent)c).deregisterComponent(); + } + } + } + + public DasApplication getDasApplication() { + Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + try { + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof FormComponent) { + ((FormComponent)c).registerComponent(); + } + } + } + catch (DasNameException dne) { + deregisterComponent(); + throw dne; + } + } + + class NoBorder extends EmptyBorder { + + Color color = Color.GRAY; + + NoBorder() { + super(5, 5, 5, 5); + } + + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + if (editable) { + g.setColor(color); + g.drawRect(x + 2, y + 2, width - 4, height - 4); + } + } + } + + protected class ContainerDnDSupport extends org.das2.util.DnDSupport { + + ContainerDnDSupport(org.das2.util.DnDSupport parent) { + super(FormContainer.this, java.awt.dnd.DnDConstants.ACTION_COPY_OR_MOVE, parent); + } + + protected int canAccept(DataFlavor[] flavors, int x, int y, int action) { + if (flavorList != null && getEditingMode()) { + for (int i = 0; i < flavors.length; i++) { + if (flavorList.contains(flavors[i])) { + onHover = true; + if (getDirection() == Orientation.HORIZONTAL) { + dropPosition = getInsertionPosition(x); + } + else { + dropPosition = getInsertionPosition(y); + } + repaint(); + return action; + } + } + } + return -1; + } + + protected void done() { + onHover = false; + repaint(); + } + + protected boolean importData(Transferable t, int x, int y, int action) { + boolean success = false; + try { + int insertionIndex; + if (getDirection() == Orientation.HORIZONTAL) { + insertionIndex = getInsertionIndex(x); + } + else { + insertionIndex = getInsertionIndex(y); + } + Component c = null; + if (t.isDataFlavorSupported(TransferableFormComponent.COMPONENT_FLAVOR)) { + c = (Component)t.getTransferData(TransferableFormComponent.COMPONENT_FLAVOR); + } + else if (t.isDataFlavorSupported(TransferableCanvas.CANVAS_FLAVOR)) { + c = (Component)t.getTransferData(TransferableCanvas.CANVAS_FLAVOR); + } + else if (t.isDataFlavorSupported(TransferableFormComponent.DASML_FRAGMENT_FLAVOR)) { + c = getComponentFromDasMLFragment((String)t.getTransferData(TransferableFormComponent.DASML_FRAGMENT_FLAVOR)); + } + if (c != null) { + if (!(c instanceof FormTab) && !(c instanceof FormWindow)) { + if (c != FormContainer.this && !SwingUtilities.isDescendingFrom(FormContainer.this, c)) { + if (c.getParent() == FormContainer.this) { + int cIndex = -1; + int componentCount = getComponentCount(); + for (int i = 0; i < componentCount; i++) { + if (getComponent(i) == c) { + cIndex = i; + break; + } + } + if (insertionIndex > cIndex) { + insertionIndex--; + } + remove(cIndex); + add(c, insertionIndex); + success = true; + } + else { + add(c, insertionIndex); + success = true; + } + } + } + } + } + catch (UnsupportedFlavorException ufe) { + } + catch (IOException ioe) { + } + if (success) { + revalidate(); + } + return success; + } + + private Component getComponentFromDasMLFragment(String dasML) { + try { + Document document = FormBase.parseDasML(new java.io.StringReader(dasML), null); + Element element = document.getDocumentElement(); + String tag = element.getTagName(); + if (tag.equals("panel")) { + return new FormPanel(element, getForm()); + } + else if (tag.equals("radiobutton")) { + return new FormRadioButton(element, getForm()); + } + else if (tag.equals("textfield")) { + return new FormTextField(element, getForm()); + } + else if (tag.equals("text")) { + return new FormText(element); + } + else if (tag.equals("button")) { + return new FormButton(element, getForm()); + } + else if (tag.equals("checkbox")) { + return new FormCheckBox(element, getForm()); + } + else if (tag.equals("buttongroup")) { + return new FormRadioButtonGroup(element, getForm()); + } + else if (tag.equals("canvas")) { + return DasCanvas.processCanvasElement(element, getForm()); + } + else if (tag.equals("choice")) { + return new FormChoice(element, getForm()); + } + } + catch (javax.xml.parsers.ParserConfigurationException pce) { + throw new RuntimeException(pce); + } + catch (org.xml.sax.SAXException se) { + throw new RuntimeException(se); + } + + catch (org.das2.DasException de) { + org.das2.util.DasExceptionHandler.handle(de); + } catch (org.das2.dasml.ParsedExpressionException pee) { + org.das2.util.DasExceptionHandler.handle(pee); + } + catch (IOException ioe) { + org.das2.util.DasExceptionHandler.handle(ioe); + } + catch ( java.text.ParseException ex ) { + org.das2.util.DasExceptionHandler.handle(ex); + } + return null; + } + + protected Transferable getTransferable(int x, int y, int action) { + for (int i = 0; i < getComponentCount(); i++) { + Component c = getComponent(i); + if (c.getBounds().contains(x, y)) { + if (c instanceof DasCanvas) { + return new TransferableCanvas((DasCanvas)c); + } + else if (c instanceof FormPanel) { + return new TransferableFormComponent((FormPanel)c); + } + else if (c instanceof FormText) { + return new TransferableFormComponent((FormText)c); + } + else if (c instanceof FormTextField) { + return new TransferableFormComponent((FormTextField)c); + } + else if (c instanceof FormButton) { + return new TransferableFormComponent((FormButton)c); + } + else if (c instanceof FormCheckBox) { + return new TransferableFormComponent((FormCheckBox)c); + } + else if (c instanceof FormRadioButtonGroup) { + return new TransferableFormComponent((FormRadioButtonGroup)c); + } + else if (c instanceof FormRadioButton) { + return new TransferableFormComponent((FormRadioButton)c); + } + else if (c instanceof FormTab) { + return new TransferableFormComponent((FormTab)c); + } + else if (c instanceof FormChoice) { + return new TransferableFormComponent((FormChoice)c); + } + else if (c instanceof FormList) { + return new TransferableFormComponent((FormList)c); + } + } + } + return null; + } + + protected void exportDone(Transferable t, int action) { + } + + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormList.java b/dasCore/src/main/java/org/das2/dasml/FormList.java new file mode 100644 index 000000000..433422876 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormList.java @@ -0,0 +1,320 @@ +/* File: FormList.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.swing.event.MouseInputListener; +import javax.swing.plaf.basic.BasicListUI; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.List; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +public class FormList extends JList implements Editable, FormComponent { + + private String delimiter = " "; + + protected org.das2.util.DnDSupport dndSupport; + + private String dasName; + + private boolean editable; + + public FormList(String name) { + try { + setDasName(name); + } + catch(org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + FormList(Element element, FormBase form) + throws org.das2.DasPropertyException,org.das2.DasNameException, org.das2.DasException , + ParsedExpressionException { + + super(new OptionListModel()); + + String name = element.getAttribute("name"); + String selectionMode = element.getAttribute("selectionMode"); + + if (selectionMode.equals("single")) { + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + else if (selectionMode.equals("multiple")) { + setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + } + + setDelimiter(element.getAttribute("delimiter")); + setEnabled(element.getAttribute("enabled").equals("true")); + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int i = 0; i < childCount; i++) { + Node node = children.item(i); + if (node instanceof Element && node.getNodeName().equals("option")) { + processOptionElement((Element)node); + } + } + setMinimumSize(getPreferredSize()); + setMaximumSize(getPreferredSize()); + try { + setDasName(name); + } + catch(org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + private void processOptionElement(Element element) { + ListOption option = new ListOption(element); + ((OptionListModel)getModel()).list.add(option); + boolean selected = element.getAttribute("selected").equals("true"); + if (selected) addSelectionInterval(getModel().getSize()-1, getModel().getSize()-1); + } + + + public void addMouseListener(MouseListener l) { + if (l instanceof BasicListUI.MouseInputHandler) { + l = new CtrlDownMouseInputListener((MouseInputListener)l); + } + super.addMouseListener(l); + } + + public void addMouseMotionListener(MouseMotionListener l) { + if (l instanceof BasicListUI.MouseInputHandler) { + l = new CtrlDownMouseInputListener((MouseInputListener)l); + } + super.addMouseMotionListener(l); + } + + public void addItem(ListOption o) { + ((OptionListModel)getModel()).list.add(o); + } + + public ListOption getItem(int index) { + return (ListOption)((OptionListModel)getModel()).list.get(index); + } + + public int getItemCount() { + return ((OptionListModel)getModel()).list.size(); + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + public String getDelimiter() { + return delimiter; + } + + public String getSelected() { + Object[] o = getSelectedValues(); + if (o.length == 0) return ""; + String result = ((ListOption)o[0]).getValue(); + for (int i = 1; i < o.length; i++) { + result = result + delimiter + ((ListOption)o[i]).getValue(); + } + return result; + } + + private static class CtrlDownMouseEvent extends MouseEvent { + private static final int CTRL_YES = CTRL_MASK | CTRL_DOWN_MASK; + private static final int SHIFT_NO = -(SHIFT_MASK | SHIFT_DOWN_MASK)-1; + public CtrlDownMouseEvent(MouseEvent e) { + super(e.getComponent(), e.getID(), e.getWhen(), + (e.getModifiers() | CTRL_YES) & SHIFT_NO, + e.getX(), e.getY(), e.getClickCount(), + e.isPopupTrigger(), e.getButton()); + } + } + + private static class CtrlDownMouseInputListener extends MouseInputAdapter { + private MouseInputListener listener; + public CtrlDownMouseInputListener(MouseInputListener listener) { + this.listener = listener; + } + public void mousePressed(MouseEvent e) { + listener.mousePressed(new CtrlDownMouseEvent(e)); + } + public void mouseReleased(MouseEvent e) { + listener.mouseReleased(new CtrlDownMouseEvent(e)); + } + public void mouseDragged(MouseEvent e) { + listener.mouseDragged(new CtrlDownMouseEvent(e)); + } + } + + void setSelected(Object item, boolean b) { + OptionListModel model = (OptionListModel)getModel(); + int index = 0; + while (index < model.list.size() && model.list.get(index) != item) { + index++; + } + if (model.list.get(index) != item) return; + if (b) { + addSelectionInterval(index, index); + } + else { + removeSelectionInterval(index, index); + } + } + + + public Element getDOMElement(Document document) { + Element element = document.createElement("list"); + element.setAttribute("name", getDasName()); + element.setAttribute("delimiter", delimiter); + element.setAttribute("enabled", (isEnabled() ? "true" : "false")); + for (int index = 0; index < getItemCount(); index++) { + element.appendChild(getItem(0).getDOMElement(document)); + } + return element; + } + + private static class OptionListModel extends AbstractListModel { + + List list = new ArrayList(); + + /** Returns the value at the specified index.s + * @param index the requested index + * @return the value at index + */ + public Object getElementAt(int index) { + return list.get(index); + } + + /** + * Returns the length of the list. + * @return the length of the list + */ + public int getSize() { + return list.size(); + } + + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + editable = b; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormListBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormListBeanInfo.java new file mode 100644 index 000000000..cf0e70eda --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormListBeanInfo.java @@ -0,0 +1,43 @@ +/* File: FormListBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * BeanInfo class for FormList + */ +public class FormListBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("selected", AccessLevel.DASML, "getSelected", null, null), + new Property("delimiter", AccessLevel.DASML, "getDelimiter", "setDelimiter", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null) + }; + + public FormListBeanInfo() { + super(properties, FormList.class); + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormPanel.java b/dasCore/src/main/java/org/das2/dasml/FormPanel.java new file mode 100644 index 000000000..966500761 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormPanel.java @@ -0,0 +1,186 @@ +/* File: FormPanel.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.graph.DasCanvas; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import org.das2.components.propertyeditor.Editable; + +/** + * A subclass of JPanel to override the default Beans properties of + * a JPanel + * + * @author Edward West + */ +public class FormPanel extends FormContainer implements Editable, FormComponent { + + /** Initializer for flavorList */ + { + DataFlavor[] flavors = { + TransferableFormComponent.BUTTONGROUP_FLAVOR, + TransferableFormComponent.BUTTON_FLAVOR, + TransferableFormComponent.CHECKBOX_FLAVOR, + TransferableFormComponent.CHOICE_FLAVOR, + //TransferableFormComponent.LIST_FLAVOR, + TransferableFormComponent.PANEL_FLAVOR, + TransferableFormComponent.TEXTFIELD_FLAVOR, + TransferableFormComponent.TEXT_FLAVOR, + org.das2.graph.dnd.TransferableCanvas.CANVAS_FLAVOR + }; + flavorList = java.util.Arrays.asList(flavors); + } + + + /** + * Empty constructor for use with super classes. + */ + public FormPanel() { + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + setBorder(new NoBorder()); + } + + /** + * Constructs a FormPanel object associated with the given + * FormBase instance and initialized with the values in the given + * Element instance. + */ + FormPanel(Element element, FormBase form) + throws org.das2.DasException, + ParsedExpressionException, org.xml.sax.SAXException, java.text.ParseException { + + super(); + + String alignment = element.getAttribute("alignment"); + if (alignment.equals("left")) { + horizontalComponentAlignment = JComponent.LEFT_ALIGNMENT; + } + else if (alignment.equals("right")) { + horizontalComponentAlignment = JComponent.RIGHT_ALIGNMENT; + } + else { + horizontalComponentAlignment = JComponent.CENTER_ALIGNMENT; + } + + String direction = element.getAttribute("direction"); + BoxLayout layout; + if (direction.equals("horizontal")) { + layout = new BoxLayout(this, BoxLayout.X_AXIS); + } + else { + layout = new BoxLayout(this, BoxLayout.Y_AXIS); + } + setLayout(layout); + + NodeList children = element.getChildNodes(); + int length = children.getLength(); + for (int index = 0; index < length; index++) { + Node node = children.item(index); + if (node instanceof Element) { + String tagName = node.getNodeName(); + if (tagName.equals("panel")) { + FormPanel p = new FormPanel((Element)node, form); + add(p); + } + else if (tagName.equals("text") || tagName.equals("info")) { + FormText text = new FormText((Element)node); + add(text); + } + else if (tagName.equals("textfield")) { + FormTextField textfield = new FormTextField((Element)node, form); + add(textfield); + } + else if (tagName.equals("button")) { + FormButton button = new FormButton((Element)node, form); + add(button); + } + else if (tagName.equals("checkbox")) { + FormCheckBox checkbox = new FormCheckBox((Element)node, form); + add(checkbox); + } + else if (tagName.equals("list")) { + FormList list = new FormList((Element)node, form); + add(list); + } + else if (tagName.equals("choice")) { + FormChoice choice = new FormChoice((Element)node, form); + add(choice); + } + else if (tagName.equals("glue")) { + add(form.processGlueElement((Element)node)); + } + else if (tagName.equals("buttongroup")) { + add(new FormRadioButtonGroup((Element)node, form)); + } + else if (tagName.equals("canvas")) { + DasCanvas canvas = DasCanvas.processCanvasElement((Element)node, form); + add(canvas); + } + else { + //DO NOTHING RIGHT NOW + } + } + else { + //TODO: do some sort of error handling here. + } + } + + setHasBorder(element.getAttribute("border").equals("true")); + setBorderTitle(element.getAttribute("border-title")); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("panel"); + element.setAttribute("border", Boolean.toString(hasBorder())); + element.setAttribute("border-title", getBorderTitle()); + for (int index = 0; index < getComponentCount(); index++) { + Component comp = getComponent(index); + if (comp instanceof FormComponent) { + FormComponent formComponent = (FormComponent)comp; + Element child = formComponent.getDOMElement(document); + element.appendChild(child); + } + else if (comp instanceof DasCanvas) { + DasCanvas canvas = (DasCanvas)comp; + Element child = canvas.getDOMElement(document); + element.appendChild(child); + } + } + return element; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new ContainerDnDSupport(null); + } + return dndSupport; + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormPanelBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormPanelBeanInfo.java new file mode 100644 index 000000000..5679b53f0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormPanelBeanInfo.java @@ -0,0 +1,43 @@ +/* File: FormPanelBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormPanel class + */ +public class FormPanelBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("border", AccessLevel.ALL, "hasBorder", "setHasBorder", null), + new Property("borderTitle", AccessLevel.ALL, "getBorderTitle", "setBorderTitle", null), + new Property("direction", AccessLevel.ALL, "getDirection", "setDirection", null) + }; + + public FormPanelBeanInfo() { + super(properties, FormPanel.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormRadioButton.java b/dasCore/src/main/java/org/das2/dasml/FormRadioButton.java new file mode 100644 index 000000000..1ed6dead9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormRadioButton.java @@ -0,0 +1,190 @@ +/* File: FormRadioButton.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.swing.*; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +public class FormRadioButton extends JRadioButton implements Editable, FormComponent { + + private String dasName; + + private String value; + + protected org.das2.util.DnDSupport dndSupport; + + private boolean editable; + + public FormRadioButton(String name, String label) { + super(label); + if (name == null) { + name = "radiobutton_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + FormRadioButton(Element element, FormBase form) + throws org.das2.DasPropertyException, ParsedExpressionException, + org.das2.DasNameException { + + String name = element.getAttribute("name"); + String label = element.getAttribute("label"); + boolean selected = element.getAttribute("selected").equals("true"); + boolean enabled = element.getAttribute("enabled").equals("true"); + + setText(label); + setSelected(selected); + setEnabled(enabled); + + if (!name.equals("")) { + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("radiobutton"); + element.setAttribute("name", getDasName()); + element.setAttribute("selected", String.valueOf(isSelected())); + element.setAttribute("enabled", String.valueOf(isEnabled())); + element.setAttribute("label", getText()); + return element; + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public boolean getEditingMode() { + return editable; + } + + public void setEditingMode(boolean b) { + editable = b; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormRadioButtonBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonBeanInfo.java new file mode 100644 index 000000000..d1eaef662 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonBeanInfo.java @@ -0,0 +1,45 @@ +/* File: FormRadioButtonBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormRadioButton class + */ +public class FormRadioButtonBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("value", AccessLevel.DASML, "getValue", "setValue", null), + new Property("selected", AccessLevel.DASML, "isSelected", "setSelected", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null), + new Property("label", AccessLevel.ALL, "getLabel", "setLabel", null) + }; + + public FormRadioButtonBeanInfo() { + super(properties, FormRadioButton.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroup.java b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroup.java new file mode 100644 index 000000000..ed1d6f8ac --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroup.java @@ -0,0 +1,129 @@ +/* File: FormRadioButtonGroup.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import org.das2.components.propertyeditor.Editable; + +public class FormRadioButtonGroup extends FormContainer implements Editable, FormComponent { + + /** + * The button group the radio buttons will be associated with. + */ + private ButtonGroup group; + + /** Initializer for flavorList */ + { + DataFlavor[] flavors = { + TransferableFormComponent.RADIOBUTTON_FLAVOR + }; + flavorList = java.util.Arrays.asList(flavors); + } + + public FormRadioButtonGroup() { + setDirection(Orientation.HORIZONTAL); + setHasBorder(false); + group = new ButtonGroup(); + } + + FormRadioButtonGroup(Element element, FormBase form) + throws org.das2.DasPropertyException, ParsedExpressionException, + org.das2.DasNameException, org.xml.sax.SAXException { + + group = new ButtonGroup(); + + String alignment = element.getAttribute("alignment"); + if (alignment.equals("left")) { + horizontalComponentAlignment = JComponent.LEFT_ALIGNMENT; + } + else if (alignment.equals("right")) { + horizontalComponentAlignment = JComponent.RIGHT_ALIGNMENT; + } + else { + horizontalComponentAlignment = JComponent.CENTER_ALIGNMENT; + } + + String direction = element.getAttribute("direction"); + BoxLayout layout; + if (direction.equals("horizontal")) { + setDirection(Orientation.HORIZONTAL); + } + else { + setDirection(Orientation.VERTICAL); + } + + NodeList children = element.getChildNodes(); + int length = children.getLength(); + for (int index = 0; index < length; index++) { + Node node = children.item(index); + if (node instanceof Element) { + String tagName = node.getNodeName(); + if (tagName.equals("radiobutton")) { + FormRadioButton radiobutton = new FormRadioButton((Element)node, form); + radiobutton.setAlignmentX(horizontalComponentAlignment); + add(radiobutton); + } + } + } + + setHasBorder(element.getAttribute("border").equals("true")); + setBorderTitle(element.getAttribute("border-title")); + } + + protected void addImpl(Component comp, Object constraints, int index) { + if (comp instanceof FormRadioButton) { + FormRadioButton radiobutton = (FormRadioButton)comp; + super.addImpl(radiobutton, constraints, index); + group.add(radiobutton); + } + else { + throw new IllegalArgumentException("Only FormRadioButton instances allowed"); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("group"); + for (int index = 0; index < getComponentCount(); index++) { + FormComponent comp = (FormComponent)getComponent(index); + Element child = comp.getDOMElement(document); + element.appendChild(child); + } + return element; + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new ContainerDnDSupport(null); + } + return dndSupport; + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroupBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroupBeanInfo.java new file mode 100644 index 000000000..2ac0045c3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormRadioButtonGroupBeanInfo.java @@ -0,0 +1,43 @@ +/* File: FormRadioButtonGroupBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * BeanInfo class for FormRadioButtonGroup + */ +public class FormRadioButtonGroupBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("border", AccessLevel.DASML, "hasBorder", "setHasBorder", null), + new Property("borderTitle", AccessLevel.DASML, "getBorderTitle", "setBorderTitle", null), + new Property("direction", AccessLevel.ALL, "getDirection", "setDirection", null) + }; + + public FormRadioButtonGroupBeanInfo() { + super(properties, FormRadioButtonGroup.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormTab.java b/dasCore/src/main/java/org/das2/dasml/FormTab.java new file mode 100644 index 000000000..a59d3bc69 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormTab.java @@ -0,0 +1,284 @@ +/* File: FormTab.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasPropertyException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import org.das2.util.DasExceptionHandler; + +/** + * + * @author eew + */ +public class FormTab extends FormContainer { + + private String label; + + private String dasName; + + /** Initializer for flavorList */ + { + java.awt.datatransfer.DataFlavor[] flavors = { + TransferableFormComponent.BUTTONGROUP_FLAVOR, + TransferableFormComponent.BUTTON_FLAVOR, + TransferableFormComponent.CHECKBOX_FLAVOR, + TransferableFormComponent.CHOICE_FLAVOR, + //TransferableFormComponent.LIST_FLAVOR, + TransferableFormComponent.PANEL_FLAVOR, + TransferableFormComponent.TEXTFIELD_FLAVOR, + TransferableFormComponent.TEXT_FLAVOR, + org.das2.graph.dnd.TransferableCanvas.CANVAS_FLAVOR + }; + flavorList = java.util.Arrays.asList(flavors); + } + + /** Creates a new instance of Form */ + public FormTab(String name, String label) { + super(); + setDirection(Orientation.VERTICAL); + if (name == null) { + name = "form_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + this.label = label; + dndSupport = new ContainerDnDSupport(null); + } + + FormTab(Element element, FormBase form) + throws DasException, + ParsedExpressionException, org.xml.sax.SAXException { + + String alignment = element.getAttribute("alignment"); + if (alignment.equals("left")) { + horizontalComponentAlignment = JComponent.LEFT_ALIGNMENT; + } + else if (alignment.equals("right")) { + horizontalComponentAlignment = JComponent.RIGHT_ALIGNMENT; + } + else { + horizontalComponentAlignment = JComponent.CENTER_ALIGNMENT; + } + String name = element.getAttribute("name"); + String label = element.getAttribute("label"); + setDirection(Orientation.VERTICAL); + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + if (label.equals("")) { + setLabel(name); + } + else { + setLabel(label); + } + + NodeList children = element.getChildNodes(); + int length = children.getLength(); + for (int index = 0; index < length; index++) { + Node node = children.item(index); + if (node instanceof Element) { + String tagName = node.getNodeName(); + if (tagName.equals("panel")) { + try { + FormPanel p = new FormPanel((Element)node, form); + add(p); + } catch ( java.text.ParseException ex ) { + DasExceptionHandler.handle(ex); + } + } + else if (tagName.equals("text") || tagName.equals("info")) { + FormText text = new FormText((Element)node); + add(text); + } + else if (tagName.equals("textfield")) { + FormTextField textfield = new FormTextField((Element)node, form); + add(textfield); + } + else if (tagName.equals("button")) { + FormButton button = new FormButton((Element)node, form); + add(button); + } + else if (tagName.equals("checkbox")) { + FormCheckBox checkbox = new FormCheckBox((Element)node, form); + add(checkbox); + } + else if (tagName.equals("list")) { + FormList list = new FormList((Element)node, form); + add(list); + } + else if (tagName.equals("choice")) { + FormChoice choice = new FormChoice((Element)node, form); + add(choice); + } + else if (tagName.equals("glue")) { + add(form.processGlueElement((Element)node)); + } + else if (tagName.equals("buttongroup")) { + add(new FormRadioButtonGroup((Element)node, form)); + } + else if (tagName.equals("canvas")) { + try { + org.das2.graph.DasCanvas canvas = org.das2.graph.DasCanvas.processCanvasElement((Element)node, form); + canvas.setAlignmentX(horizontalComponentAlignment); + add(canvas); + } + + catch (org.das2.DasException dne) { + DasExceptionHandler.handle(dne); + } catch ( java.text.ParseException ex ) { + DasExceptionHandler.handle(ex); + } + } + else { + //DO NOTHING RIGHT NOW + } + } + else { + //TODO: do some sort of error handling here. + } + } + dndSupport = new ContainerDnDSupport(null); + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + FormBase form = getForm(); + if (form != null) { + form.setTitleAt(getForm().indexOfComponent(this), label); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("form"); + element.setAttribute("name", getDasName()); + element.setAttribute("label", getLabel()); + for (int index = 0; index < getComponentCount(); index++) { + java.awt.Component comp = getComponent(index); + if (comp instanceof FormComponent) { + FormComponent formComponent = (FormComponent)comp; + Element child = formComponent.getDOMElement(document); + element.appendChild(child); + } + else if (comp instanceof org.das2.graph.DasCanvas) { + org.das2.graph.DasCanvas canvas = (org.das2.graph.DasCanvas)comp; + Element child = canvas.getDOMElement(document); + element.appendChild(child); + } + } + return element; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new ContainerDnDSupport(null); + } + return dndSupport; + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + super.deregisterComponent(); + } + + public final DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + super.registerComponent(); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormTabBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormTabBeanInfo.java new file mode 100644 index 000000000..c946afc12 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormTabBeanInfo.java @@ -0,0 +1,42 @@ +/* File: FormTabBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormPanel class + */ +public class FormTabBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("label", AccessLevel.ALL, "getLabel", "setLabel", null) + }; + + public FormTabBeanInfo() { + super(properties, FormTab.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormText.java b/dasCore/src/main/java/org/das2/dasml/FormText.java new file mode 100644 index 000000000..9596ee671 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormText.java @@ -0,0 +1,129 @@ +/* File: FormText.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.*; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import org.das2.components.propertyeditor.Editable; + +/** + * + * @author eew + */ +public class FormText extends JTextArea implements Editable, FormComponent { + + protected org.das2.util.DnDSupport dndSupport; + + private boolean editingMode; + + public FormText() { + super(""); + super.setEditable(false); + setOpaque(false); + setBorder(new EmptyBorder(5,5,5,5)); + } + + FormText(Element element) { + this(); + Text text = getElementContent(element); + if (text != null) { + setText(text.getData()); + } + } + + private static Text getElementContent(Element element) { + NodeList children = element.getChildNodes(); + for (int index = 0; index < children.getLength(); index++) { + Node node = children.item(index); + if (node instanceof Text) { + return (Text)node; + } + } + return null; + } + + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("text"); + Text text = document.createTextNode(getText()); + element.appendChild(text); + return element; + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public void setEditingMode(boolean b) { editingMode = b; } + + public boolean getEditingMode() { return editingMode; } + + public String getDasName() { + return null; + } + + public void setDasName(String name) throws org.das2.DasNameException { + throw new org.das2.DasNameException(); + } + + public void deregisterComponent() { + } + + public DasApplication getDasApplication() { + Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormTextBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormTextBeanInfo.java new file mode 100644 index 000000000..159381bb9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormTextBeanInfo.java @@ -0,0 +1,41 @@ +/* File: FormTextBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormTextField class + */ +public class FormTextBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("content", AccessLevel.ALL, "getText", "setText", null) + }; + + public FormTextBeanInfo() { + super(properties, FormText.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormTextField.java b/dasCore/src/main/java/org/das2/dasml/FormTextField.java new file mode 100644 index 000000000..4733f38ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormTextField.java @@ -0,0 +1,192 @@ +/* File: FormTextField.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import javax.swing.*; +import org.das2.DasPropertyException; +import org.das2.components.propertyeditor.Editable; + +/** + * A subclass of JTextField to override the default Beans properties of + * a JTextField + * + * @author Edward West + */ +public class FormTextField extends JTextField implements Editable, FormComponent { + + private String dasName; + + protected org.das2.util.DnDSupport dndSupport; + + private boolean editingMode; + + public FormTextField(String name) { + super(10); + if (name == null) { + name="textfield_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + setMinimumSize(getPreferredSize()); + setMaximumSize(getPreferredSize()); + } + + public FormTextField(Element element, FormBase form) + throws org.das2.DasPropertyException,org.das2.DasNameException, org.das2.DasException, + ParsedExpressionException { + + String name = element.getAttribute("name"); + + int length = Integer.parseInt(element.getAttribute("length")); + setColumns(length); + setMinimumSize(getPreferredSize()); + setMaximumSize(getPreferredSize()); + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int i = 0; i < childCount; i++) { + if (children.item(i) instanceof Text) { + Text text = (Text)children.item(i); + setText(text.getData()); + break; + } + } + + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("textfield"); + Text text = document.createTextNode(getText()); + element.appendChild(text); + element.setAttribute("name", getDasName()); + element.setAttribute("enabled", (isEnabled() ? "true" : "false")); + element.setAttribute("length", Integer.toString(getColumns())); + return element; + } + + public FormBase getForm() { + FormComponent parent = (FormComponent)getParent(); + if (parent == null) { + return null; + } + return parent.getForm(); + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new DefaultComponentDnDSupport(this); + } + return dndSupport; + } + + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + return false; + } + + public void setEditingMode(boolean b) { editingMode = b; } + public boolean getEditingMode() { return editingMode; } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + public DasApplication getDasApplication() { + java.awt.Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent)p).getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormTextFieldBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormTextFieldBeanInfo.java new file mode 100644 index 000000000..331a45f6f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormTextFieldBeanInfo.java @@ -0,0 +1,43 @@ +/* File: FormTextFieldBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +/** + * Bean info class for the FormTextField class + */ +public class FormTextFieldBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("enabled", AccessLevel.DASML, "isEnabled", "setEnabled", null), + new Property("text", AccessLevel.DASML, "getText", "setText", null) + }; + + public FormTextFieldBeanInfo() { + super(properties, FormTextField.class); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormWindow.java b/dasCore/src/main/java/org/das2/dasml/FormWindow.java new file mode 100644 index 000000000..8fe9f67db --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormWindow.java @@ -0,0 +1,437 @@ +/* File: FormWindow.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasPropertyException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import org.das2.components.propertyeditor.Editable; +import org.das2.util.DasExceptionHandler; + +/** + * @author eew + */ +public class FormWindow extends FormContainer implements Editable, FormComponent { + + FormBase form; + + JDialog dialog; + + JInternalFrame internalFrame; + + String title = ""; + + int windowWidth = -1; + + int windowHeight = -1; + + boolean shouldBeVisible = false; + + private String dasName; + + { + flavorList = java.util.Arrays.asList(new java.awt.datatransfer.DataFlavor[]{ + TransferableFormComponent.BUTTONGROUP_FLAVOR, + TransferableFormComponent.BUTTON_FLAVOR, + TransferableFormComponent.CHECKBOX_FLAVOR, + TransferableFormComponent.CHOICE_FLAVOR, + //TransferableFormComponent.LIST_FLAVOR, + TransferableFormComponent.PANEL_FLAVOR, + TransferableFormComponent.TEXTFIELD_FLAVOR, + TransferableFormComponent.TEXT_FLAVOR, + org.das2.graph.dnd.TransferableCanvas.CANVAS_FLAVOR + }); + setLayout(new BorderLayout()); + } + + public FormWindow(String name, String title) { + this(name, title, -1, -1); + } + + public FormWindow(String name, String title, int width, int height) { + this.title = title; + this.windowWidth = width; + this.windowHeight = height; + if (name == null) { + name = "window_" + Integer.toHexString(System.identityHashCode(this)); + } + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + dndSupport = new ContainerDnDSupport(null); + } + + /** Creates a new instance of FormWindow */ + FormWindow(Element element, FormBase form) + throws DasException, + ParsedExpressionException, org.xml.sax.SAXException { + + this.form = form; + + String name = element.getAttribute("name"); + String title = element.getAttribute("title"); + String alignment = element.getAttribute("alignment"); + int width = Integer.parseInt(element.getAttribute("width")); + int height = Integer.parseInt(element.getAttribute("height")); + java.awt.Point location = parsePoint(element.getAttribute("location")); + boolean visible = element.getAttribute("visible").equals("true"); + + NodeList children = element.getChildNodes(); + for (int index = 0; index < children.getLength(); index++) { + Node node = children.item(index); + if (node instanceof Element && node.getNodeName().equals("panel")) { + try { + FormPanel panel = new FormPanel((Element)node, form); + add(panel); + } catch ( java.text.ParseException ex ) { + DasExceptionHandler.handle(ex); + } + } + else if (node instanceof Element && node.getNodeName().equals("canvas")) { + try { + org.das2.graph.DasCanvas canvas = org.das2.graph.DasCanvas.processCanvasElement((Element)node, form); + add(canvas); + } catch ( java.text.ParseException ex ) { + DasExceptionHandler.handle(ex); + } + } + } + + setTitle(title); + windowWidth = width; + windowHeight = height; + setWindowVisible(visible); + try { + setDasName(name); + } + catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + dndSupport = new ContainerDnDSupport(null); + } + + private static java.awt.Point parsePoint(String str) { + int commaIndex = str.indexOf(','); + return new java.awt.Point(Integer.parseInt(str.substring(1, commaIndex)), + Integer.parseInt(str.substring(commaIndex+1, str.length()-1))); + } + + public FormBase getForm() { + return form; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("window"); + element.setAttribute("name", getDasName()); + element.setAttribute("width", String.valueOf(getWidth())); + element.setAttribute("height", String.valueOf(getHeight())); + element.setAttribute("title", title); + element.setAttribute("visible", String.valueOf(isVisible())); + if (getComponentCount() > 0) { + Component comp = getComponent(0); + if (comp instanceof FormComponent) { + FormComponent child = (FormComponent)comp; + element.appendChild(child.getDOMElement(document)); + } + else if (comp instanceof org.das2.graph.DasCanvas) { + org.das2.graph.DasCanvas child = (org.das2.graph.DasCanvas)comp; + element.appendChild(child.getDOMElement(document)); + } + } + return element; + } + + public String getDasName() { + return dasName; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public boolean isWindowVisible() { + if (getEditingMode()) { + return shouldBeVisible; + } + else { + return (dialog == null ? false : dialog.isVisible()); + } + } + + public void setWindowVisible(boolean b) { + boolean oldValue = isWindowVisible(); + if (oldValue == b) { + return; + } + shouldBeVisible = b; + if (!getEditingMode() && b) { + if (dialog == null) { + initDialog(); + } + dialog.setVisible(b); + } + firePropertyChange("visible", oldValue, b); + } + + public void setEditingMode(boolean b) { + if (getEditingMode() == b) { + return; + } + if (b) { + if (dialog != null) { + shouldBeVisible = dialog.isVisible(); + dialog.setVisible(false); + } + else { + shouldBeVisible = false; + } + maybeInitializeInternalFrame(); + internalFrame.setContentPane(this); + } + else { + if (dialog != null) { + dialog.setContentPane(this); + dialog.pack(); + dialog.setVisible(shouldBeVisible); + } + else if (shouldBeVisible) { + initDialog(); + dialog.pack(); + dialog.setVisible(shouldBeVisible); + } + } + super.setEditingMode(b); + } + + public Dimension getPreferredSize() { + if (windowWidth == -1 || windowHeight == -1) { + return super.getPreferredSize(); + } + else { + return new Dimension(windowWidth, windowHeight); + } + } + + /** Getter for property title. + * @return Value of property title. + * + */ + public String getTitle() { + return title; + } + + /** Setter for property title. + * @param title New value of property title. + * + */ + public void setTitle(String title) { + if (this.title == title || (this.title != null && this.title.equals(title))) { + return; + } + String oldValue = this.title; + this.title = title; + if (getEditingMode() && internalFrame != null) { + internalFrame.setTitle(title); + } + firePropertyChange("title", oldValue, title); + } + + public void pack() { + if (getEditingMode()) { + maybeInitializeInternalFrame(); + internalFrame.pack(); + } + else { + if (dialog == null) { + initDialog(); + } + dialog.pack(); + } + } + + public Dimension getWindowSize() { + return new Dimension(windowWidth, windowHeight); + } + + public void setWindowSize(int width, int height) { + int oldWidth = windowWidth; + int oldHeight = windowHeight; + if (width != windowWidth) { + windowWidth = width; + firePropertyChange("width", oldWidth, width); + } + if (height != windowHeight) { + windowHeight = height; + firePropertyChange("height", oldHeight, height); + } + if (height != windowHeight || width != windowWidth) { + pack(); + } + } + + public void setWindowWidth(int width) { + setWindowSize(width, windowHeight); + } + + public int getWindowWidth() { + return windowWidth; + } + + public void setWindowHeight(int height) { + setWindowSize(windowWidth, height); + } + + public int getWindowHeight() { + return windowHeight; + } + + private void initDialog() { + Window w = SwingUtilities.getWindowAncestor(form); + if (w instanceof Frame) { + dialog = new JDialog((Frame)w); + } + else if (w instanceof Dialog) { + dialog = new JDialog((Dialog)w); + } + else { + dialog = new JDialog(); + } + dialog.setContentPane(this); + dialog.setTitle(title); + } + + private void maybeInitializeInternalFrame() { + if (internalFrame == null) { + internalFrame = new InternalFrame(); + internalFrame.setTitle(title); + internalFrame.setVisible(true); + internalFrame.setDefaultCloseOperation(JInternalFrame.DO_NOTHING_ON_CLOSE); + if (getEditingMode()) { + internalFrame.setContentPane(this); + } + internalFrame.pack(); + } + } + + public org.das2.util.DnDSupport getDnDSupport() { + if (dndSupport == null) { + dndSupport = new ContainerDnDSupport(null); + } + return dndSupport; + } + + protected void addImpl(Component c, Object constraints, int index) { + if (getComponentCount() >= 1) throw new IllegalArgumentException("Only one component allowed"); + super.addImpl(c, constraints, index); + if (c instanceof JComponent) { + ((JComponent)c).setAlignmentY(JComponent.TOP_ALIGNMENT); + } + } + + JInternalFrame getInternalFrame() { + if (getEditingMode()) { + maybeInitializeInternalFrame(); + return internalFrame; + } + return null; + } + + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } + catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } + catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + super.deregisterComponent(); + } + + public DasApplication getDasApplication() { + if (form != null) { + return form.getDasApplication(); + } + else { + return null; + } + } + + public void registerComponent() throws org.das2.DasException { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + nc.put(getDasName(), this); + } + super.registerComponent(); + } + + public class InternalFrame extends JInternalFrame { + InternalFrame() { + super(null, true, false, false, false); + } + public FormWindow getWindow() { + return FormWindow.this; + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/FormWindowBeanInfo.java b/dasCore/src/main/java/org/das2/dasml/FormWindowBeanInfo.java new file mode 100644 index 000000000..e21fd42a2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/FormWindowBeanInfo.java @@ -0,0 +1,65 @@ +/* File: FormWindowBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.beans.AccessLevelBeanInfo; + +import java.beans.MethodDescriptor; + +/** + * + * @author eew + */ +public class FormWindowBeanInfo extends AccessLevelBeanInfo { + + private static Property[] properties = { + new Property("name", AccessLevel.ALL, "getDasName", "setDasName", null), + new Property("visible", AccessLevel.DASML, "isWindowVisible", "setWindowVisible", null), + new Property("title", AccessLevel.ALL, "getTitle", "setTitle", null) + }; + + private static MethodDescriptor[] methods; + + static { + methods = new MethodDescriptor[1]; + try { + methods[0] = new MethodDescriptor(FormWindow.class.getMethod("pack")); + } + catch (NoSuchMethodException nsme) { + IllegalStateException ise = new IllegalStateException(nsme.getMessage()); + ise.initCause(nsme); + throw ise; + } + + } + + public FormWindowBeanInfo() { + super(properties, FormWindow.class); + } + + public MethodDescriptor[] getMethodDescriptors() { + return methods; + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/ListOption.java b/dasCore/src/main/java/org/das2/dasml/ListOption.java new file mode 100644 index 000000000..79a6593c4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/ListOption.java @@ -0,0 +1,107 @@ +/* File: ListOption.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.awt.*; + +public class ListOption { + private String label; + private String value; + private boolean selected; + + ItemSelectable selectable; + + public ListOption(FormBase form, String label, String value) { + this.label = label; + this.value = value; + } + + ListOption(Element element) { + label = element.getAttribute("label"); + value = element.getAttribute("value"); + } + + ListOption(String label, String value) { + this.label = label; + this.value = value; + } + + public String toString() { + return getLabel(); + } + + public String getLabel() { + return label; + } + + public String getValue() { + return value; + } + + public void setLabel(String label) { + this.label = label; + } + + public void setValue(String value) { + this.value = value; + } + + public boolean isSelected() { + if (selectable != null) { + Object[] items = selectable.getSelectedObjects(); + for (int index = 0; index < items.length; index++) { + if (items[index] == this) { + return true; + } + } + } + return false; + } + + public void setSelected(boolean b) { + if (selectable instanceof FormChoice) { + if (b) { + FormChoice choice = (FormChoice)selectable; + choice.setSelectedItem(this); + } + } + else if (selectable instanceof FormList) { + FormList list = (FormList)selectable; + list.setSelected(this, b); + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("option"); + element.setAttribute("label", label); + element.setAttribute("value", value); + element.setAttribute("selected", String.valueOf(isSelected())); + return element; + } + +} + diff --git a/dasCore/src/main/java/org/das2/dasml/OptionList.java b/dasCore/src/main/java/org/das2/dasml/OptionList.java new file mode 100644 index 000000000..1f2aa9998 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/OptionList.java @@ -0,0 +1,33 @@ +/* File: OptionList.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +/** + * + * @author eew + */ +public interface OptionList { + ListOption[] getOptions(); + void setOptions(ListOption[] options); +} diff --git a/dasCore/src/main/java/org/das2/dasml/OptionListEditor.java b/dasCore/src/main/java/org/das2/dasml/OptionListEditor.java new file mode 100644 index 000000000..21d6dbbe1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/OptionListEditor.java @@ -0,0 +1,598 @@ +/* File: OptionListEditor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; + +/** + * + * @author eew + */ +public class OptionListEditor extends JButton implements OptionList, javax.swing.table.TableCellEditor, java.beans.PropertyEditor { + + EventListenerList listenerList = new EventListenerList(); + + ListOption editing; + + JDialog dialog; + + JPanel editorPanel; + + JLabel labelLabel; + + JLabel valueLabel; + + JTextField labelField; + + JTextField valueField; + + JList optionList; + + AllPurposeListener listener = new AllPurposeListener(); + + JButton add; + JButton moveUp; + JButton moveDown; + JButton delete; + JButton commitChanges; + JButton cancelEdit; + + private final ListModel EMPTY_MODEL = new ListModel() { + public void addListDataListener(ListDataListener l) {} + public void removeListDataListener(ListDataListener l) {} + public Object getElementAt(int index) { + return ""; + } + public int getSize() { + return 1; + } + }; + + /** Creates a new instance of FormChoiceEditor */ + public OptionListEditor() { + super("edit"); + addActionListener(listener); + + labelLabel = new JLabel("label:", JLabel.LEFT); + labelField = new JTextField(20); + labelField.addFocusListener(listener); + valueLabel = new JLabel("value:", JLabel.LEFT); + valueField = new JTextField(20); + valueField.addFocusListener(listener); + optionList = new JList(EMPTY_MODEL); + optionList.setVisibleRowCount(10); + optionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + optionList.addListSelectionListener(listener); + + editorPanel = new JPanel(new BorderLayout()); + editorPanel.setBorder(new EmptyBorder(5,5,5,5)); + editorPanel.add(new JScrollPane(optionList, + JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + + JPanel rightPanel = new JPanel(); + rightPanel.setBorder(new EmptyBorder(0, 5, 0, 0)); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); + add = new JButton("Add"); + moveUp = new JButton("Move up"); + moveDown = new JButton("Move down"); + delete = new JButton("Delete"); + add.addActionListener(listener); + moveUp.addActionListener(listener); + moveDown.addActionListener(listener); + delete.addActionListener(listener); + Dimension size = moveDown.getPreferredSize(); + add.setPreferredSize(size); + add.setMaximumSize(size); + moveUp.setPreferredSize(size); + moveUp.setMaximumSize(size); + moveDown.setPreferredSize(size); + moveDown.setMaximumSize(size); + delete.setPreferredSize(size); + delete.setMaximumSize(size); + rightPanel.add(add); + rightPanel.add(moveUp); + rightPanel.add(moveDown); + rightPanel.add(delete); + editorPanel.add(rightPanel, BorderLayout.EAST); + + JPanel buttonPanel = new JPanel(); + commitChanges = new JButton("Commit changes"); + commitChanges.addActionListener(listener); + cancelEdit = new JButton("Cancel Edit"); + cancelEdit.addActionListener(listener); + buttonPanel.add(commitChanges); + buttonPanel.add(cancelEdit); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS)); + labelLabel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + labelField.setAlignmentX(JComponent.LEFT_ALIGNMENT); + valueLabel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + valueField.setAlignmentX(JComponent.LEFT_ALIGNMENT); + buttonPanel.setAlignmentX(JComponent.LEFT_ALIGNMENT); + bottomPanel.add(labelLabel); + bottomPanel.add(labelField); + bottomPanel.add(valueLabel); + bottomPanel.add(valueField); + bottomPanel.add(buttonPanel); + editorPanel.add(bottomPanel, BorderLayout.SOUTH); + } + + public void showDialog() { + if (dialog == null) { + Window w = SwingUtilities.windowForComponent(this); + if (w instanceof Dialog) { + dialog = new JDialog((Dialog)w, true); + } + else if (w instanceof Frame) { + dialog = new JDialog((Frame)w, true); + } + else { + dialog = new JDialog(); + dialog.setModal(true); + } + dialog.setContentPane(editorPanel); + dialog.pack(); + dialog.setResizable(false); + dialog.addWindowListener(listener); + } + dialog.setVisible(true); + } + + private void addOption() { + DefaultListModel model = getListModel(); + model.addElement(new ListOption("label", "value")); + optionList.setSelectedIndex(model.getSize() - 1); + } + + private DefaultListModel getListModel() { + return (DefaultListModel)optionList.getModel(); + } + + private void moveUp(int index) { + if (index >= 0) { + if (index == 0) { + return; + } + DefaultListModel model = getListModel(); + Object option = model.getElementAt(index); + model.removeElementAt(index); + model.insertElementAt(option, index - 1); + optionList.setSelectedIndex(index - 1); + } + } + + private void moveDown(int index) { + if (index >= 0) { + DefaultListModel model = getListModel(); + if (index == model.getSize()) { + return; + } + Object option = model.getElementAt(index); + model.removeElementAt(index); + model.insertElementAt(option, index + 1); + optionList.setSelectedIndex(index + 1); + } + } + + private void deleteOption(int index) { + if (index >= 0) { + DefaultListModel model = getListModel(); + model.removeElementAt(index); + optionList.clearSelection(); + } + } + + private class AllPurposeListener implements ActionListener, ListSelectionListener, FocusListener, WindowListener { + + /** Invoked when an action occurs. + */ + public void actionPerformed(ActionEvent e) { + Object source = e.getSource(); + if (source == OptionListEditor.this) { + showDialog(); + } + else if (source == add) { + addOption(); + } + else if (source == moveUp) { + moveUp(optionList.getSelectedIndex()); + } + else if (source == moveDown) { + moveDown(optionList.getSelectedIndex()); + } + else if (source == delete) { + deleteOption(optionList.getSelectedIndex()); + } + else if (source == commitChanges) { + stopCellEditing(); + } + else if (source == cancelEdit) { + cancelCellEditing(); + } + } + + /** + * Called whenever the value of the selection changes. + * @param e the event that characterizes the change. + */ + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + int index = optionList.getSelectedIndex(); + if (index >= 0) { + editing = (ListOption)getListModel().getElementAt(index); + labelField.setText(editing.getLabel()); + valueField.setText(editing.getValue()); + } + else { + editing = null; + labelField.setText(""); + valueField.setText(""); + } + } + + /** Invoked when a component gains the keyboard focus. + */ + public void focusGained(FocusEvent e) {} + + /** Invoked when a component loses the keyboard focus. + */ + public void focusLost(FocusEvent e) { + if (editing != null) { + if (e.getComponent() == labelField) { + editing.setLabel(labelField.getText()); + optionList.repaint(); + } + else if (e.getComponent() == valueField) { + editing.setValue(valueField.getText()); + } + } + } + + public void windowClosing(WindowEvent e) { + fireEditingCanceled(); + } + + public void windowClosed(WindowEvent e) {} + public void windowActivated(WindowEvent e) {} + public void windowDeactivated(WindowEvent e) {} + public void windowDeiconified(WindowEvent e) {} + public void windowIconified(WindowEvent e) {} + public void windowOpened(WindowEvent e) {} + + } + + public static void main(String[] args) { + + JPanel content = new JPanel(new BorderLayout()); + + FormBase form = new FormBase(true); + FormChoice choice = new FormChoice("fred"); + choice.addOption(new ListOption("One", "1")); + choice.addOption(new ListOption("Two", "2")); + + OptionListEditor editor = new OptionListEditor(); + editor.setOptions(choice.getOptions()); + + content.add(choice, BorderLayout.CENTER); + content.add(editor, BorderLayout.SOUTH); + + + + JFrame frame = new JFrame(); + frame.setContentPane(content); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } + + public ListOption[] getOptions() { + ListModel model = getListModel(); + ListOption[] options = new ListOption[model.getSize()]; + for (int index = 0; index < options.length; index++) { + options[index] = (ListOption)model.getElementAt(index); + } + return options; + } + + public void setOptions(ListOption[] options) { + DefaultListModel model = new DefaultListModel(); + model.ensureCapacity(options.length); + for (int index = 0; index < options.length; index++) { + model.addElement(options[index]); + } + optionList.setModel(model); + } + + /** Adds a listener to the list that's notified when the editor + * stops, or cancels editing. + * + * @param l the CellEditorListener + */ + public void addCellEditorListener(CellEditorListener l) { + listenerList.add(CellEditorListener.class, l); + } + + /** Tells the editor to cancel editing and not accept any partially + * edited value. + */ + public void cancelCellEditing() { + fireEditingCanceled(); + dialog.setVisible(false); + } + + /** Returns the value contained in the editor. + * @return the value contained in the editor + */ + public Object getCellEditorValue() { + return getOptions(); + } + + /** Sets an initial value for the editor. This will cause + * the editor to stopEditing and lose any partially + * edited value if the editor is editing when this method is called.

+ * + * Returns the component that should be added to the client's + * Component hierarchy. Once installed in the client's + * hierarchy this component will then be able to draw and receive + * user input. + * + * @param table the JTable that is asking the + * editor to edit; can be null + * @param value the value of the cell to be edited; it is + * up to the specific editor to interpret + * and draw the value. For example, if value is + * the string "true", it could be rendered as a + * string or it could be rendered as a check + * box that is checked. null + * is a valid value + * @param isSelected true if the cell is to be rendered with + * highlighting + * @param row the row of the cell being edited + * @param column the column of the cell being edited + * @return the component for editing + */ + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + ListOption[] options = (ListOption[])value; + if (dialog != null && dialog.isVisible()) { + dialog.setVisible(false); + } + setOptions(options); + return this; + } + + /** Asks the editor if it can start editing using anEvent. + * anEvent is in the invoking component coordinate system. + * The editor can not assume the Component returned by + * getCellEditorComponent is installed. This method + * is intended for the use of client to avoid the cost of setting up + * and installing the editor component if editing is not possible. + * If editing can be started this method returns true. + * + * @param anEvent the event the editor should use to consider + * whether to begin editing or not + * @return true if editing can be started + * @see #shouldSelectCell + */ + public boolean isCellEditable(java.util.EventObject anEvent) { + return true; + } + + /** Removes a listener from the list that's notified + * + * @param l the CellEditorListener + */ + public void removeCellEditorListener(CellEditorListener l) { + listenerList.remove(CellEditorListener.class, l); + } + + /** Returns true if the editing cell should be selected, false otherwise. + * Typically, the return value is true, because is most cases the editing + * cell should be selected. However, it is useful to return false to + * keep the selection from changing for some types of edits. + * eg. A table that contains a column of check boxes, the user might + * want to be able to change those checkboxes without altering the + * selection. (See Netscape Communicator for just such an example) + * Of course, it is up to the client of the editor to use the return + * value, but it doesn't need to if it doesn't want to. + * + * @param anEvent the event the editor should use to start + * editing + * @return true if the editor would like the editing cell to be selected; + * otherwise returns false + * @see #isCellEditable + */ + public boolean shouldSelectCell(java.util.EventObject anEvent) { + return true; + } + + /** Tells the editor to stop editing and accept any partially edited + * value as the value of the editor. The editor returns false if + * editing was not stopped; this is useful for editors that validate + * and can not accept invalid entries. + * + * @return true if editing was stopped; false otherwise + */ + public boolean stopCellEditing() { + fireEditingStopped(); + dialog.setVisible(false); + return true; + } + + private void fireEditingCanceled() { + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + if (e == null) { + e = new ChangeEvent(this); + } + ((CellEditorListener)listeners[i+1]).editingCanceled(e); + } + } + } + + private void fireEditingStopped() { + ChangeEvent e = null; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==CellEditorListener.class) { + if (e == null) { + e = new ChangeEvent(this); + } + ((CellEditorListener)listeners[i+1]).editingStopped(e); + } + } + } + + /** Gets the property value as text. + * + * @return The property value as a human editable string. + *

Returns null if the value can't be expressed as an editable string. + *

If a non-null value is returned, then the PropertyEditor should + * be prepared to parse that string back in setAsText(). + */ + public String getAsText() { + return null; + } + + /** A PropertyEditor may choose to make available a full custom Component + * that edits its property value. It is the responsibility of the + * PropertyEditor to hook itself up to its editor Component itself and + * to report property value changes by firing a PropertyChange event. + *

+ * The higher-level code that calls getCustomEditor may either embed + * the Component in some larger property sheet, or it may put it in + * its own individual dialog, or ... + * + * @return A java.awt.Component that will allow a human to directly + * edit the current property value. May be null if this is + * not supported. + */ + public java.awt.Component getCustomEditor() { + return this; + } + + /** This method is intended for use when generating Java code to set + * the value of the property. It should return a fragment of Java code + * that can be used to initialize a variable with the current property + * value. + *

+ * Example results are "2", "new Color(127,127,34)", "Color.orange", etc. + * + * @return A fragment of Java code representing an initializer for the + * current value. + */ + public String getJavaInitializationString() { + return "???"; + } + + /** If the property value must be one of a set of known tagged values, + * then this method should return an array of the tags. This can + * be used to represent (for example) enum values. If a PropertyEditor + * supports tags, then it should support the use of setAsText with + * a tag value as a way of setting the value and the use of getAsText + * to identify the current value. + * + * @return The tag values for this property. May be null if this + * property cannot be represented as a tagged value. + * + */ + public String[] getTags() { + return null; + } + + /** Gets the property value. + * + * @return The value of the property. Primitive types such as "int" will + * be wrapped as the corresponding object type such as "java.lang.Integer". + */ + public Object getValue() { + return getOptions(); + } + + /** Determines whether this property editor is paintable. + * + * @return True if the class will honor the paintValue method. + */ + public boolean isPaintable() { + return false; + } + + /** Paint a representation of the value into a given area of screen + * real estate. Note that the propertyEditor is responsible for doing + * its own clipping so that it fits into the given rectangle. + *

+ * If the PropertyEditor doesn't honor paint requests (see isPaintable) + * this method should be a silent noop. + *

+ * The given Graphics object will have the default font, color, etc of + * the parent container. The PropertyEditor may change graphics attributes + * such as font and color and doesn't need to restore the old values. + * + * @param gfx Graphics object to paint into. + * @param box Rectangle within graphics object into which we should paint. + */ + public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) { + } + + /** Set the property value by parsing a given String. May raise + * java.lang.IllegalArgumentException if either the String is + * badly formatted or if this kind of property can't be expressed + * as text. + * @param text The string to be parsed. + */ + public void setAsText(String text) throws java.lang.IllegalArgumentException { + throw new IllegalArgumentException(); + } + + /** Set (or change) the object that is to be edited. Primitive types such + * as "int" must be wrapped as the corresponding object type such as + * "java.lang.Integer". + * + * @param value The new target object to be edited. Note that this + * object should not be modified by the PropertyEditor, rather + * the PropertyEditor should create a new object to hold any + * modified value. + */ + public void setValue(Object value) { + setOptions((ListOption[])value); + } + + /** Determines whether this property editor supports a custom editor. + * + * @return True if the propertyEditor can provide a custom editor. + */ + public boolean supportsCustomEditor() { + return true; + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/Orientation.java b/dasCore/src/main/java/org/das2/dasml/Orientation.java new file mode 100644 index 000000000..ffd383525 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/Orientation.java @@ -0,0 +1,67 @@ +/* File: Orientation.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import org.das2.components.propertyeditor.Enumeration; + +/** + * + * @author eew + */ +public final class Orientation implements Enumeration { + + public static final Orientation HORIZONTAL = new Orientation("horizontal"); + + public static final Orientation VERTICAL = new Orientation("vertical"); + + private String description; + + private Orientation(String description) { + this.description = description; + } + + public String toString() { + return description; + } + + public static Orientation valueOf(String str) { + if (str.equals("vertical")) { + return VERTICAL; + } + if (str.equals("horizontal")) { + return HORIZONTAL; + } + throw new IllegalArgumentException("Orientation must be either 'horizontal' or 'vertical'"); + } + + /** An icon can be provided that will be shown in a list + * along with the textual description of the element. + * This method should return null if there + * is no icon available. + */ + public javax.swing.Icon getListIcon() { + return null; + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/ParsedExpression.java b/dasCore/src/main/java/org/das2/dasml/ParsedExpression.java new file mode 100644 index 000000000..bd43e1fe2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/ParsedExpression.java @@ -0,0 +1,734 @@ +/* File: ParsedExpression.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class encapsulates a pre-parsed expression. + * + * @author Edward West + */ +public class ParsedExpression { + + private static final Pattern SIMPLE_NAME_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_-]*"); + + private static final Pattern INT_PATTERN = Pattern.compile("-?(0|[1-9][0-9]*)"); + + private static final Pattern FLOAT_PATTERN = Pattern.compile("-?[0-9]*(\\.[0-9]*)?([eE]-?[0-9]+)?"); + + private static final Pattern PAREN_PATTERN = Pattern.compile("\\(([^\\(\\)])\\)"); + + private static final Pattern EQUALITY_PATTERN = Pattern.compile("\\b(eq|ne)\\b"); + + private static final Pattern COMPARISON_PATTERN = Pattern.compile("\\b(lt|le|gt|ge)\\b"); + + private static final Pattern OR_PATTERN = Pattern.compile("\\bor\\b"); + + private static final Pattern AND_PATTERN = Pattern.compile("\\band\\b"); + + private static final Pattern NOT_PATTERN = Pattern.compile("\\Anot\\b"); + + static final int ID_LOAD = 0; + static final int ID_ALOAD = 1; + static final int ID_STORE = 2; + static final int ID_ASTORE = 3; + static final int ID_ADD = 4; + static final int ID_SUBTRACT = 5; + static final int ID_MULTIPLY = 6; + static final int ID_DIVIDE = 7; + static final int ID_NEGATE = 8; + static final int ID_EQ = 9; + static final int ID_NE = 10; + static final int ID_GT = 11; + static final int ID_LT = 12; + static final int ID_GE = 13; + static final int ID_LE = 14; + static final int ID_OR = 15; + static final int ID_AND = 16; + static final int ID_NOT = 17; + + private static class Op { + static final Op LOAD = new Op(ID_LOAD); + static final Op ALOAD = new Op(ID_ALOAD); + static final Op STORE = new Op(ID_STORE); + static final Op ADD = new Op(ID_ADD); + static final Op SUBTRACT = new Op(ID_SUBTRACT); + static final Op MULTIPLY = new Op(ID_MULTIPLY); + static final Op DIVIDE = new Op(ID_DIVIDE); + static final Op NEGATE = new Op(ID_NEGATE); + static final Op EQ = new Op(ID_EQ); + static final Op NE = new Op(ID_NE); + static final Op GT = new Op(ID_GT); + static final Op LT = new Op(ID_LT); + static final Op GE = new Op(ID_GE); + static final Op LE = new Op(ID_LE); + static final Op OR = new Op(ID_OR); + static final Op AND = new Op(ID_AND); + static final Op NOT = new Op(ID_NOT); + int id; + private Op(int id) {this.id = id;} + } + + private List list; + + private String expression; + + public ParsedExpression(String expression) throws ParsedExpressionException { + this.expression = expression; + list = new LinkedList(); + if (!parseExpression(expression, list)) throw new ParsedExpressionException("Invalid expression"); + } + + public String toString() { + return expression; + } + + private static boolean parseExpression(String s, List queue) { + return parseOrExpression(s, queue); + } + + private static boolean parseOrExpression(String expression, List queue) { + if (expression.length() < 1) return false; + Matcher matcher = OR_PATTERN.matcher(expression); + while (matcher.find()) { + String andExpression = expression.substring(matcher.end()).trim(); + String orExpression = expression.substring(0, matcher.start()).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseAndExpression(andExpression, rightList) + && parseOrExpression(orExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + queue.add(Op.OR); + return true; + } + } + return parseAndExpression(expression, queue); + } + + private static boolean parseAndExpression(String expression, List queue) { + if (expression.length() < 1) return false; + Matcher matcher = AND_PATTERN.matcher(expression); + while (matcher.find()) { + String notExpression = expression.substring(matcher.end()).trim(); + String andExpression = expression.substring(0, matcher.start()).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseNotExpression(notExpression, rightList) + && parseAndExpression(andExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + queue.add(Op.AND); + return true; + } + } + return parseNotExpression(expression, queue); + } + + private static boolean parseNotExpression(String expression, List queue) { + if (expression.length() < 1) return false; + Matcher matcher = NOT_PATTERN.matcher(expression); + if (matcher.find()) { + String equalityExpression = expression.substring(matcher.end()).trim(); + List list = new LinkedList(); + if (parseEqualityExpression(equalityExpression, list)) { + queue.addAll(list); + queue.add(Op.NOT); + return true; + } + else return false; + } + return parseEqualityExpression(expression, queue); + } + + private static boolean parseEqualityExpression(String expression, List queue) { + if (expression.length() < 1) return false; + Matcher matcher = EQUALITY_PATTERN.matcher(expression); + while (matcher.find()) { + String relationalExpression = expression.substring(matcher.end()).trim(); + String equalityExpression = expression.substring(0, matcher.start()).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseRelationalExpression(relationalExpression, rightList) + && parseEqualityExpression(equalityExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + String op = matcher.group(1); + if (op.equals("eq")) { + queue.add(Op.EQ); + } + else { + queue.add(Op.NE); + } + return true; + } + } + return parseRelationalExpression(expression, queue); + } + + private static boolean parseRelationalExpression(String expression, List queue) { + if (expression.length() < 1) return false; + Matcher matcher = COMPARISON_PATTERN.matcher(expression); + while (matcher.find()) { + String additiveExpression = expression.substring(matcher.end()).trim(); + String relationalExpression = expression.substring(0, matcher.start()).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseAdditiveExpression(additiveExpression, rightList) + && parseRelationalExpression(relationalExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + String op = matcher.group(1); + if (op.equals("lt")) { + queue.add(Op.LT); + } + else if (op.equals("le")) { + queue.add(Op.LE); + } + else if (op.equals("gt")) { + queue.add(Op.GT); + } + else { + queue.add(Op.GE); + } + return true; + } + } + return parseAdditiveExpression(expression, queue); + } + + private static boolean parseAdditiveExpression(String expression, List queue) { + if (expression.length() < 1) return false; + int index = Math.max(expression.lastIndexOf('+'), + expression.lastIndexOf('-')); + while (index >= 0) { + String multiplicativeExpression = expression.substring(index + 1).trim(); + String additiveExpression = expression.substring(0, index).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseMultiplicativeExpression(multiplicativeExpression, rightList) + && parseAdditiveExpression(additiveExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + queue.add(expression.charAt(index) == '+' ? Op.ADD : Op.SUBTRACT); + return true; + } + index = Math.max(expression.lastIndexOf('+', index-1), expression.lastIndexOf('-', index-1)); + } + return parseMultiplicativeExpression(expression, queue); + } + + private static boolean parseMultiplicativeExpression(String expression, List queue) { + if (expression.length() < 1) return false; + int index = Math.max(expression.lastIndexOf('*'), expression.lastIndexOf('/')); + while (index >= 0) { + String unaryExpression = expression.substring(index + 1).trim(); + String multiplicativeExpression = expression.substring(0, index).trim(); + List leftList = new LinkedList(); + List rightList = new LinkedList(); + if (parseUnaryExpression(unaryExpression, rightList) + && parseMultiplicativeExpression(multiplicativeExpression, leftList)) { + queue.addAll(leftList); + queue.addAll(rightList); + queue.add(expression.charAt(index) == '*' ? Op.MULTIPLY : Op.DIVIDE); + return true; + } + } + return parseUnaryExpression(expression, queue); + } + + private static boolean parseUnaryExpression(String expression, List queue) { + return parseNegateExpression(expression, queue); + } + + private static boolean parseNegateExpression(String expression, List queue) { + if (expression.length() < 1) return false; + if (expression.charAt(0) == '-') { + List list = new LinkedList(); + boolean result = parseSimpleExpression(expression.substring(1).trim(), list); + if (result) { + queue.addAll(list); + queue.add(Op.NEGATE); + return true; + } + else return false; + } + return parseSimpleExpression(expression, queue); + } + + private static boolean parseSimpleExpression(String expression, List queue) { + if (expression.length() < 1) return false; + if (expression.startsWith("(") && expression.endsWith(")")) { + List list = new LinkedList(); + boolean result = parseExpression(expression.substring(1, expression.length()-1).trim(), list); + if (result) { + queue.addAll(list); + return true; + } + else { + return false; + } + } + if (expression.startsWith("${") && expression.endsWith("}")) { + List list = new LinkedList(); + boolean result = parseArrayAccess(expression.substring(2, expression.length() - 1).trim(), list); + if (result) { + queue.addAll(list); + } + return result; + } + Matcher matcher = INT_PATTERN.matcher(expression); + if (matcher.matches()) { + queue.add(new Integer(expression)); + return true; + } + matcher = FLOAT_PATTERN.matcher(expression); + if (matcher.matches()) { + queue.add(new Double(expression)); + return true; + } + if (expression.equals("true")) { + queue.add(Boolean.TRUE); + return true; + } + if (expression.equals("false")) { + queue.add(Boolean.FALSE); + return true; + } + return false; + } + + private static boolean parseArrayAccess(String expression, List queue) { + expression = expression.trim(); + if (expression.length() < 1) return false; + if (SIMPLE_NAME_PATTERN.matcher(expression).matches()) { + queue.add(null); + queue.add(expression); + queue.add(Op.LOAD); + return true; + } + else if (expression.charAt(expression.length() - 1) == ']') { + int index = expression.lastIndexOf('['); + while (index >= 0) { + int dotIndex = expression.lastIndexOf('.', index); + String arrayAccess = expression.substring(0, dotIndex); + String simpleName = expression.substring(dotIndex + 1, index).trim(); + String indexExpression = expression.substring(index + 1, expression.length() - 2).trim(); + if (!SIMPLE_NAME_PATTERN.matcher(simpleName).matches()) { + return false; + } + List leftList = new LinkedList(); + List rightList = new LinkedList(); + boolean rightResult = parseExpression(indexExpression, rightList); + if (rightResult) { + boolean leftResult = parseArrayAccess(arrayAccess, leftList); + if (leftResult) { + queue.addAll(leftList); + queue.addAll(rightList); + queue.add(Op.ALOAD); + return true; + } + } + } + } + else { + int index = expression.lastIndexOf('.'); + while (index > 0) { + String arrayAccess = expression.substring(0, index); + String simpleName = expression.substring(index + 1); + if (!SIMPLE_NAME_PATTERN.matcher(simpleName).matches()) { + return false; + } + List leftList = new LinkedList(); + boolean leftResult = parseArrayAccess(arrayAccess, leftList); + if (leftResult) { + queue.addAll(leftList); + queue.add(simpleName); + queue.add(Op.LOAD); + return true; + } + } + } + return false; + } + + public Object evaluate(org.das2.NameContext nc) throws ParsedExpressionException, org.das2.DasPropertyException { + try { + return evaluate(this.list, nc); + } + catch (InvocationTargetException ite) { + Throwable t = ite.getTargetException(); + if (t instanceof DataFormatException) { + ParsedExpressionException pee = new ParsedExpressionException(t.getMessage()); + pee.initCause(t); + throw pee; + } + throw new RuntimeException(ite); + } + } + + private static Object evaluate(List list, org.das2.NameContext nc) throws ParsedExpressionException, InvocationTargetException, org.das2.DasPropertyException { + if (list.size() == 0) { + throw new RuntimeException("empty expression"); + } + if (list.size() == 1 && !(list.get(0) instanceof Op)) { + return list.get(0); + } + List stack = new ArrayList(list.size()); + for (Iterator i = list.iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof Op) { + Op op = (Op)o; + evaluate(stack, op.id, nc); + } + else { + push(stack, o); + } + } + if (stack.size() != 1) { + throw new IllegalArgumentException("Invalid expression: " + list); + } + return pop(stack); + } + + private static void evaluate(List stack, int id, org.das2.NameContext nc) throws InvocationTargetException, ParsedExpressionException, org.das2.DasPropertyException { + switch(id) { + case ID_LOAD: + load(stack, nc); + break; + case ID_ALOAD: + aload(stack, nc); + break; + case ID_STORE: + store(stack, nc); + break; + case ID_ASTORE: + astore(stack, nc); + break; + case ID_NEGATE: + negate(stack); + break; + case ID_ADD: + add(stack); + break; + case ID_SUBTRACT: + subtract(stack); + break; + case ID_MULTIPLY: + multiply(stack); + break; + case ID_DIVIDE: + divide(stack); + break; + case ID_EQ: + eq(stack); + break; + case ID_NE: + ne(stack); + break; + case ID_GT: + gt(stack); + break; + case ID_LT: + lt(stack); + break; + case ID_GE: + ge(stack); + break; + case ID_LE: + le(stack); + break; + case ID_OR: + or(stack); + break; + case ID_AND: + and(stack); + break; + default: throw new IllegalStateException(); + } + } + + private static Object pop(List stack) { + return stack.remove(stack.size() - 1); + } + + private static Boolean popBoolean(List stack) { + return (Boolean)stack.remove(stack.size() - 1); + } + + private static String popString(List stack) { + return (String)stack.remove(stack.size() - 1); + } + + private static Integer popInteger(List stack) { + return (Integer)stack.remove(stack.size() - 1); + } + + private static Double popDouble(List stack) { + return (Double)stack.remove(stack.size() - 1); + } + + private static Number popNumber(List stack) { + return (Number)stack.remove(stack.size() - 1); + } + + private static Comparable popComparable(List stack) { + return (Comparable)stack.remove(stack.size() - 1); + } + + private static void push(List stack, Object o) { + stack.add(o); + } + + private static Class widest(Class c1, Class c2) { + if (c1.getSuperclass() != Number.class || c2.getSuperclass() != Number.class) { + throw new IllegalArgumentException("(" + c1.getName() + ", " + c2.getName()); + } + else if (c1 == Double.class || c2 == Double.class) { + return Double.class; + } + else if (c1 == Float.class || c2 == Float.class) { + return Float.class; + } + else if (c1 == Long.class || c2 == Long.class) { + return Long.class; + } + else if (c1 == Integer.class || c2 == Integer.class) { + return Integer.class; + } + else if (c1 == Short.class || c2 == Short.class) { + return Short.class; + } + else { + return Byte.class; + } + + } + + private static void load(List stack, org.das2.NameContext nc) throws org.das2.DasPropertyException, InvocationTargetException, ParsedExpressionException { + String pname = popString(stack); + Object source = pop(stack); + Object value; + if (source == null) { + value = nc.get(pname); + } + else { + value = nc.getPropertyValue(source, pname); + } + push(stack, value); + } + + private static void aload(List stack, org.das2.NameContext nc) throws org.das2.DasPropertyException, InvocationTargetException, ParsedExpressionException { + Integer index = popInteger(stack); + String pname = popString(stack); + Object source = pop(stack); + push(stack, nc.getIndexedPropertyValue(source, pname, index.intValue())); + } + + private static void store(List stack, org.das2.NameContext nc) throws org.das2.DasPropertyException, InvocationTargetException, ParsedExpressionException { + Object value = pop(stack); + String pname = popString(stack); + Object dest = pop(stack); + nc.setPropertyValue(dest, pname, value); + } + + private static void astore(List stack, org.das2.NameContext nc) throws org.das2.DasPropertyException, InvocationTargetException, ParsedExpressionException { + Object value = pop(stack); + Integer index = popInteger(stack); + String pname = popString(stack); + Object dest = pop(stack); + nc.setIndexedPropertyValue(dest, pname, index.intValue(), value); + } + + private static void negate(List stack) { + Double a = popDouble(stack); + push(stack, new Double(-a.doubleValue())); + } + + private static void add(List stack) { + Number b = popNumber(stack); + Number a = popNumber(stack); + Class widest = widest(a.getClass(), b.getClass()); + Number result; + if (widest == Double.class) { + result = new Double(a.doubleValue() + b.doubleValue()); + } + else if (widest == Float.class) { + result = new Float(a.floatValue() + b.floatValue()); + } + else if (widest == Long.class) { + result = new Long(a.longValue() + b.longValue()); + } + else if (widest == Integer.class) { + result = new Integer(a.intValue() + b.intValue()); + } + else if (widest == Short.class) { + result = new Short((short)(a.shortValue() + b.shortValue())); + } + else { + result = new Byte((byte)(a.byteValue() + b.byteValue())); + } + push(stack, result); + } + + private static void subtract(List stack) { + Number b = popNumber(stack); + Number a = popNumber(stack); + Class widest = widest(a.getClass(), b.getClass()); + Number result; + if (widest == Double.class) { + result = new Double(a.doubleValue() - b.doubleValue()); + } + else if (widest == Float.class) { + result = new Float(a.floatValue() - b.floatValue()); + } + else if (widest == Long.class) { + result = new Long(a.longValue() - b.longValue()); + } + else if (widest == Integer.class) { + result = new Integer(a.intValue() - b.intValue()); + } + else if (widest == Short.class) { + result = new Short((short)(a.shortValue() - b.shortValue())); + } + else { + result = new Byte((byte)(a.byteValue() - b.byteValue())); + } + push(stack, result); + } + + private static void multiply(List stack) { + Number b = popNumber(stack); + Number a = popNumber(stack); + Class widest = widest(a.getClass(), b.getClass()); + Number result; + if (widest == Double.class) { + result = new Double(a.doubleValue() * b.doubleValue()); + } + else if (widest == Float.class) { + result = new Float(a.floatValue() * b.floatValue()); + } + else if (widest == Long.class) { + result = new Long(a.longValue() * b.longValue()); + } + else if (widest == Integer.class) { + result = new Integer(a.intValue() * b.intValue()); + } + else if (widest == Short.class) { + result = new Short((short)(a.shortValue() * b.shortValue())); + } + else { + result = new Byte((byte)(a.byteValue() * b.byteValue())); + } + push(stack, result); + } + + private static void divide(List stack) { + Number b = popNumber(stack); + Number a = popNumber(stack); + Class widest = widest(a.getClass(), b.getClass()); + Number result; + if (widest == Double.class) { + result = new Double(a.doubleValue() / b.doubleValue()); + } + else if (widest == Float.class) { + result = new Float(a.floatValue() / b.floatValue()); + } + else if (widest == Long.class) { + result = new Long(a.longValue() / b.longValue()); + } + else if (widest == Integer.class) { + result = new Integer(a.intValue() / b.intValue()); + } + else if (widest == Short.class) { + result = new Short((short)(a.shortValue() / b.shortValue())); + } + else { + result = new Byte((byte)(a.byteValue() / b.byteValue())); + } + push(stack, result); + } + + private static void eq(List stack) { + Object b = pop(stack); + Object a = pop(stack); + Boolean result = (a == b || (a != null && a.equals(b)) ? Boolean.TRUE : Boolean.FALSE); + push(stack, result); + } + + private static void ne(List stack) { + Object b = pop(stack); + Object a = pop(stack); + push(stack, Boolean.valueOf(a == b || (a != null && a.equals(b)))); + } + + private static void gt(List stack) { + Comparable b = popComparable(stack); + Comparable a = popComparable(stack); + push(stack, Boolean.valueOf(a.compareTo(b) > 0)); + } + + private static void lt(List stack) { + Comparable b = popComparable(stack); + Comparable a = popComparable(stack); + push(stack, Boolean.valueOf(a.compareTo(b) < 0)); + } + + private static void ge(List stack) { + Comparable b = popComparable(stack); + Comparable a = popComparable(stack); + push(stack, Boolean.valueOf(a.compareTo(b) >= 0)); + } + + private static void le(List stack) { + Comparable b = popComparable(stack); + Comparable a = popComparable(stack); + push(stack, Boolean.valueOf(a.compareTo(b) <= 0)); + } + + private static void or(List stack) { + Boolean b = popBoolean(stack); + Boolean a = popBoolean(stack); + push(stack, Boolean.valueOf(a.booleanValue() || b.booleanValue())); + } + + private static void and(List stack) { + Boolean b = popBoolean(stack); + Boolean a = popBoolean(stack); + push(stack, Boolean.valueOf(a.booleanValue() && b.booleanValue())); + } + + private static void not(List stack) { + Boolean a = popBoolean(stack); + push(stack, Boolean.valueOf(!a.booleanValue())); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/ParsedExpressionException.java b/dasCore/src/main/java/org/das2/dasml/ParsedExpressionException.java new file mode 100644 index 000000000..d4afdb77b --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/ParsedExpressionException.java @@ -0,0 +1,37 @@ +/* File: ParsedExpressionException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +/** + * + * @author eew + */ +public class ParsedExpressionException extends java.lang.Exception { + + /** Creates a new instance of ParsedExpressionFormat */ + public ParsedExpressionException(String message) { + super(message); + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/SerializeUtil.java b/dasCore/src/main/java/org/das2/dasml/SerializeUtil.java new file mode 100644 index 000000000..7d019b707 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/SerializeUtil.java @@ -0,0 +1,322 @@ +/* + * SerializeUtil.java + * + * Created on June 21, 2005, 9:55 AM + * + * To change this template, choose Tools | Options and locate the template under + * the Source Creation and Management node. Right-click the template and choose + * Open. You can then make changes to the template in the Source Editor. + */ + +package org.das2.dasml; + +import org.das2.graph.DasCanvasComponent; +import org.das2.beans.AccessLevelBeanInfo; +import org.das2.beans.BeansUtil; +import org.das2.system.DasLogger; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import java.beans.*; +import java.beans.PropertyDescriptor; +import java.lang.reflect.*; +import java.text.*; +import java.util.*; +import java.util.logging.*; +import org.w3c.dom.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * + * @author Jeremy + */ +public class SerializeUtil { + + static Set toStringSet; + static { + toStringSet= new HashSet(); + toStringSet.add( Boolean.class ); + toStringSet.add( Short.class ); + toStringSet.add( Integer.class ); + toStringSet.add( Long.class ); + toStringSet.add( Float.class ); + toStringSet.add( Double.class ); + toStringSet.add( String.class ); + } + + public static org.w3c.dom.Element getDOMElement( Document document, Object object ) { + return getDOMElement( document, object, new NullProgressMonitor() ); + } + + public static org.w3c.dom.Element getDOMElement( Document document, Object object, ProgressMonitor monitor ) { + Logger log= DasLogger.getLogger( DasLogger.SYSTEM_LOG ); + + try { + String elementName= object.getClass().getName(); + elementName= elementName.replaceAll("\\$", "\\_dollar_"); + + Element element=null; + try { + element= document.createElement(elementName); + } catch ( Exception e ) { + System.err.println(e); + throw new RuntimeException(e); + } + + BeanInfo info = BeansUtil.getBeanInfo(object.getClass()); + + AccessLevelBeanInfo alInfo= BeansUtil.asAccessLevelBeanInfo( info, object.getClass() ); + + PropertyDescriptor[] properties = alInfo.getPropertyDescriptors( AccessLevelBeanInfo.PersistenceLevel.PERSISTENT ); + String[] propertyNameList= BeansUtil.getPropertyNames( properties ); + + HashMap nameMap= new HashMap(); + + for ( int i=0; i0 ) monitor.setTaskSize( propertyNameList.length ); + monitor.started(); + + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dasml; + +//import org.apache.xml.serialize.*; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.io.StringWriter; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; + +/** + * + * @author eew + */ +public class TransferableFormComponent implements Transferable { + + public static final DataFlavor COMPONENT_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormComponent"); + public static final DataFlavor PANEL_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormPanel"); + public static final DataFlavor TEXT_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormText"); + public static final DataFlavor TEXTFIELD_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormTextField"); + public static final DataFlavor BUTTON_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormButton"); + public static final DataFlavor CHECKBOX_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormCheckBox"); + public static final DataFlavor BUTTONGROUP_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormRadioButtonGroup"); + public static final DataFlavor RADIOBUTTON_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormRadioButton"); + public static final DataFlavor TAB_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormTab"); + public static final DataFlavor CHOICE_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormChoice"); + public static final DataFlavor LIST_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormList"); + public static final DataFlavor WINDOW_FLAVOR = createJVMLocalDataFlavor("org.das2.dasml.FormWindow"); + public static final DataFlavor DASML_FRAGMENT_FLAVOR; + static { + try { + DASML_FRAGMENT_FLAVOR = new DataFlavor("x-text/dasml-fragment;class=java.lang.String"); + } + catch (ClassNotFoundException cnfe) { + throw new RuntimeException(cnfe); + } + } + + private final FormComponent formComponent; + private final DataFlavor moreSpecificDataFlavor; + private DataFlavor[] flavorList; + + public TransferableFormComponent(FormPanel panel) { + formComponent = panel; + moreSpecificDataFlavor = PANEL_FLAVOR; + } + + public TransferableFormComponent(FormText text) { + formComponent = text; + moreSpecificDataFlavor = TEXT_FLAVOR; + } + + public TransferableFormComponent(FormTextField textField) { + formComponent = textField; + moreSpecificDataFlavor = TEXTFIELD_FLAVOR; + } + + public TransferableFormComponent(FormButton button) { + formComponent = button; + moreSpecificDataFlavor = BUTTON_FLAVOR; + } + + public TransferableFormComponent(FormCheckBox checkBox) { + formComponent = checkBox; + moreSpecificDataFlavor = CHECKBOX_FLAVOR; + } + + public TransferableFormComponent(FormRadioButtonGroup buttonGroup) { + formComponent = buttonGroup; + moreSpecificDataFlavor = BUTTONGROUP_FLAVOR; + } + + public TransferableFormComponent(FormRadioButton radioButton) { + formComponent = radioButton; + moreSpecificDataFlavor = RADIOBUTTON_FLAVOR; + } + + public TransferableFormComponent(FormTab form) { + formComponent = form; + moreSpecificDataFlavor = TAB_FLAVOR; + } + + public TransferableFormComponent(FormChoice choice) { + formComponent = choice; + moreSpecificDataFlavor = CHOICE_FLAVOR; + } + + public TransferableFormComponent(FormList list) { + formComponent = list; + moreSpecificDataFlavor = LIST_FLAVOR; + } + + public TransferableFormComponent(FormWindow window) { + formComponent = window; + moreSpecificDataFlavor = WINDOW_FLAVOR; + } + + /** Returns an object which represents the data to be transferred. The class + * of the object returned is defined by the representation class of the flavor. + * + * @param flavor the requested flavor for the data + * @see DataFlavor#getRepresentationClass + * @exception IOException if the data is no longer available + * in the requested flavor. + * @exception UnsupportedFlavorException if the requested data flavor is + * not supported. + */ + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, java.io.IOException { + if (flavor.equals(moreSpecificDataFlavor) || flavor.equals(COMPONENT_FLAVOR)) { + return formComponent; + } + else if (flavor.equals(DataFlavor.stringFlavor) + || flavor.equals(DASML_FRAGMENT_FLAVOR)) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + document.appendChild(formComponent.getDOMElement(document)); + StringWriter writer = new StringWriter(); + + DOMImplementationLS ls = (DOMImplementationLS) + document.getFeature("LS", "3.0"); + LSOutput output = ls.createLSOutput(); + output.setEncoding("UTF-8"); + output.setCharacterStream(writer); + LSSerializer serializer = ls.createLSSerializer(); + serializer.write(document, output); + + /* + OutputFormat format = new OutputFormat(Method.XML, "UTF-8", true); + format.setOmitXMLDeclaration(true); + format.setOmitDocumentType(true); + XMLSerializer serializer = new XMLSerializer(writer, format); + serializer.serialize(document); + */ + + return writer.toString(); + } + catch (ParserConfigurationException pce) { + throw new RuntimeException(pce); + } + } + throw new UnsupportedFlavorException(flavor); + } + + /** Returns an array of DataFlavor objects indicating the flavors the data + * can be provided in. The array should be ordered according to preference + * for providing the data (from most richly descriptive to least descriptive). + * @return an array of data flavors in which this data can be transferred + */ + public DataFlavor[] getTransferDataFlavors() { + if (flavorList == null) { + flavorList = new DataFlavor[] { + moreSpecificDataFlavor, + COMPONENT_FLAVOR, + DASML_FRAGMENT_FLAVOR, + DataFlavor.stringFlavor, + }; + } + return flavorList; + } + + /** Returns whether or not the specified data flavor is supported for + * this object. + * @param flavor the requested flavor for the data + * @return boolean indicating whether or not the data flavor is supported + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(COMPONENT_FLAVOR) + || flavor.equals(moreSpecificDataFlavor) + || flavor.equals(DataFlavor.stringFlavor); + } + + private static DataFlavor createJVMLocalDataFlavor(String classname) { + try { + String mimeType = DataFlavor.javaJVMLocalObjectMimeType + + ";class="+classname; + return new DataFlavor(mimeType); + } + catch (ClassNotFoundException cnfe) { + throw new RuntimeException(cnfe); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dasml/package.html b/dasCore/src/main/java/org/das2/dasml/package.html new file mode 100644 index 000000000..08bd7f7dc --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/package.html @@ -0,0 +1,4 @@ + + Package for implementing dasml, a language for describing das2 applications. +Note dasml has fallen out of use. + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/action.xsd b/dasCore/src/main/java/org/das2/dasml/schema/action.xsd new file mode 100644 index 000000000..2b5d41167 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/action.xsd @@ -0,0 +1 @@ + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/canvas.xsd b/dasCore/src/main/java/org/das2/dasml/schema/canvas.xsd new file mode 100644 index 000000000..acea1e52c --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/canvas.xsd @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/commandblock.xsd b/dasCore/src/main/java/org/das2/dasml/schema/commandblock.xsd new file mode 100644 index 000000000..b99eafa13 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/commandblock.xsd @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/commandfragment.xsd b/dasCore/src/main/java/org/das2/dasml/schema/commandfragment.xsd new file mode 100644 index 000000000..1a09e828a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/commandfragment.xsd @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/dasML.xsd b/dasCore/src/main/java/org/das2/dasml/schema/dasML.xsd new file mode 100644 index 000000000..27056fb67 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/dasML.xsd @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/dasml/schema/dasMLtypes.xsd b/dasCore/src/main/java/org/das2/dasml/schema/dasMLtypes.xsd new file mode 100644 index 000000000..9e764e53d --- /dev/null +++ b/dasCore/src/main/java/org/das2/dasml/schema/dasMLtypes.xsd @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/dataset/AbstractDataSet.java b/dasCore/src/main/java/org/das2/dataset/AbstractDataSet.java new file mode 100755 index 000000000..e59d99641 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/AbstractDataSet.java @@ -0,0 +1,177 @@ +/* File: AbstractDataSet.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 27, 2003, 9:32 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; + +import java.util.*; + +/** + * + * @author eew + */ +public abstract class AbstractDataSet implements DataSet { + + private Map properties; + + private double[] xTags; + + private Units xUnits; + + private Units yUnits; + + /** Creates a new instance of AbstractDataSet + * The properties map must only have keys of type String. + * @param xTags values of the x tags for this data set in xUnits + * @param xUnits the units of the x tags for this data set + * @param yUnits the units of the y tags/values for this data set + * @param properties map of property names and values + * @throws IllegalArgumentException if properties has one or more keys + * that is not a String + */ + protected AbstractDataSet(double[] xTags, Units xUnits, Units yUnits, Map properties) throws IllegalArgumentException { + for (Iterator i = properties.keySet().iterator(); i.hasNext();) { + if (!(i.next() instanceof String)) { + throw new IllegalArgumentException("Non-String key found in property map"); + } + } + this.properties = new HashMap(properties); + this.xTags = (double[])xTags.clone(); + this.xUnits = xUnits; + this.yUnits = yUnits; + } + + private AbstractDataSet() { + } + + /** Returns the value of the property that name represents + * @param name String name of the property requested + * @return the value of the property that name represents + */ + public Object getProperty(String name) { + return properties.get(name); + } + + protected boolean hasProperty(String name) { + return properties.containsKey(name); + } + + public Map getProperties() { + return new HashMap( properties ); + } + + public int getXLength() { + return xTags.length; + } + + public Datum getXTagDatum(int i) { + return Datum.create(xTags[i], getXUnits()); + } + + public double getXTagDouble(int i, Units units) { + double xTag = xTags[i]; + xTag = getXUnits().getConverter(units).convert(xTag); + return xTag; + } + + public int getXTagInt(int i, Units units) { + return (int)Math.round(getXTagDouble(i, units)); + } + + /** Returns the Units object representing the unit type of the x tags + * for this data set. + * @return the x units + */ + public Units getXUnits() { + return xUnits; + } + + /** Returns the Units object representing the unit type of the y tags + * or y values for this data set. + * @return the y units + */ + public Units getYUnits() { + return yUnits; + } + + /** A DataSet implementation that share properties, yUnits and + * yUnits with the instance of AbstractDataSet it is associated with. + * This class is provided so that sub-classes of AbstractDataSet can + * extend this class when creating views of their data without having + * to copy the immutable data AbstractDataSet contains. + */ + protected abstract class ViewDataSet extends AbstractDataSet implements DataSet { + + protected ViewDataSet() {} + + /** Returns the value of the property that name represents + * @param name String name of the property requested + * @return the value of the property that name represents + */ + public Object getProperty(String name) { + return properties.get(name); + } + + public Map getProperties() { + return new HashMap(properties); + } + + public int getXLength() { + return xTags.length; + } + + public Datum getXTagDatum(int i) { + return Datum.create(xTags[i], getXUnits()); + } + + public double getXTagDouble(int i, Units units) { + double xTag = xTags[i]; + xTag = getXUnits().getConverter(units).convert(xTag); + return xTag; + } + + public int getXTagInt(int i, Units units) { + return (int)Math.round(getXTagDouble(i, units)); + } + + /** Returns the Units object representing the unit type of the x tags + * for this data set. + * @return the x units + */ + public Units getXUnits() { + return xUnits; + } + + /** Returns the Units object representing the unit type of the y tags + * or y values for this data set. + * @return the y units + */ + public Units getYUnits() { + return yUnits; + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/AbstractDataSetCache.java b/dasCore/src/main/java/org/das2/dataset/AbstractDataSetCache.java new file mode 100644 index 000000000..72f1e6535 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/AbstractDataSetCache.java @@ -0,0 +1,189 @@ +/* File: DataSetCache.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.datum.Datum; +import org.das2.DasApplication; +import org.das2.system.DasLogger; +import java.text.*; +import java.util.*; +import java.util.logging.Logger; +/** + * Keeps keep track of cache statistics and to give consistent + * log messages, and provides the Entry class. + * + * @author jbf + */ +public abstract class AbstractDataSetCache implements DataSetCache { + + protected class Entry implements Displayable { + + protected DataSetDescriptor dsd; + protected CacheTag cacheTag; + protected DataSet data; + protected int nhits; + protected long birthTime; + protected long lastAccess; + + Entry() { + this( null, null, null ); + } + + Entry( DataSetDescriptor dsd, CacheTag cacheTag, DataSet data ) { + this.dsd= dsd; + this.cacheTag= cacheTag; + this.data= data; + this.nhits= 0; + this.birthTime= System.currentTimeMillis(); + this.lastAccess= birthTime; + } + + /* + * returns the dataSet attached to the Entry, and updates the lastAccess time + */ + protected DataSet getData() { + this.lastAccess= System.currentTimeMillis(); + return data; + } + + protected boolean satifies( Entry entry ) { + boolean result= ( this.dsd!=null) && ( entry.dsd!=null); + result= result && ( this.dsd.equals( entry.dsd ) ); + result= result && ( cacheTag.contains(entry.cacheTag) ); + return result; + } + + public String toString() { + long sizeBytes= DataSetUtil.guessSizeBytes(this.data); + String sizeBytesString= " ("+NumberFormat.getIntegerInstance().format(sizeBytes)+" bytes)"; + return dsd.toString() + " " + cacheTag + " ["+nhits+" hits]"+sizeBytesString; + } + + public javax.swing.Icon getListIcon() { + return null; + } + + public String getListLabel() { + return toString(); + } + + public CacheTag getCacheTag() { + return this.cacheTag; + } + + } + + private static final Logger logger= DasLogger.getLogger(DasLogger.SYSTEM_LOG); + + public int hits=0; + public int misses=0; + + abstract public void store( DataSetDescriptor dsd, CacheTag cacheTag, DataSet data ); + + abstract boolean haveStoredImpl( DataSetDescriptor dsd, CacheTag cacheTag ); + + public boolean haveStored( DataSetDescriptor dsd, CacheTag cacheTag ) { + boolean result= haveStoredImpl( dsd, cacheTag ); + if ( result ) { + logger.fine("cache hit "+dsd+" "+cacheTag); + hits++; + } else { + logger.fine("cache miss "+dsd+" "+cacheTag); + misses++; + } + return result; + } + + /** + * return a measure of the utility of the entry, presumably so that it may be + * swapped out when a resource limit is met. + */ + protected long cacheValue( Entry e ) { + return e.lastAccess; + } + + abstract DataSet retrieveImpl( DataSetDescriptor dsd, CacheTag cacheTag ); + + public DataSet retrieve( DataSetDescriptor dsd, CacheTag cacheTag ) { + return retrieveImpl( dsd, cacheTag ); + } + + /** + * reset the internal state of the cache + */ + abstract public void reset(); + + /** + * return the DataSet described by the set of DataSets if possible. + * @throws IllegalArgumentException if a subset is not continous, + * non-overlapping, and of the same resolution. Removes + * elements from the list that are not needed for the set. + */ + public DataSet coalese( List result ) { + Collections.sort( result, new Comparator() { + public int compare( Object o1, Object o2 ) { + return ((Entry)o1).cacheTag.range.compareTo( ((Entry)o2).cacheTag.range ); + } + } ); + + Entry e0= (Entry)result.get(0); + CacheTag ct= e0.cacheTag; + Datum t1= ct.range.max(); + Datum resolution= ct.resolution; + + DataSet ds= e0.data; + + // check for continuity and non-overlap + for ( int i=1; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; + +import java.util.*; + +/** + * + * @author Edward West + */ +public abstract class AbstractTableDataSet extends AbstractDataSet implements DataSet, TableDataSet { + + private Units zUnits; + + protected List tableProperties; + + /** Creates a new instance of AbstractTableDataSet */ + public AbstractTableDataSet(double[] xTags, Units xUnits, Units yUnits, Units zUnits, Map properties) { + super(xTags, xUnits, yUnits, properties); + this.zUnits = zUnits; + this.tableProperties= null; + } + + public Units getZUnits() { + return zUnits; + } + + public VectorDataSet getXSlice(int i) { + return new XSliceDataSet(this, i); + } + + public VectorDataSet getYSlice(int j, int table) { + return new YSliceDataSet(this, j, table); + } + + public String toString() { + return TableUtil.toString(this); + } + + public Object getProperty(int table, String name) { + if ( tableProperties!=null ) { + return tableProperties.get(table).get(name); + } else { + return null; + } + } + + protected static class XSliceDataSet extends AbstractDataSet.ViewDataSet implements VectorDataSet{ + + private int iIndex; + private TableDataSet ds; + + protected XSliceDataSet(AbstractDataSet ds, int i) { + ds.super(); + this.ds = (TableDataSet)ds; + this.iIndex = i; + } + + public DataSet getPlanarView(String planeID) { + return new XSliceDataSet( (AbstractDataSet)ds.getPlanarView(planeID), iIndex ); + } + + public String[] getPlaneIds() { + return ds.getPlaneIds(); + } + + public Datum getDatum(int i) { + return ds.getDatum(iIndex, i); + } + + public double getDouble(int i, Units units) { + return ds.getDouble(iIndex, i, units); + } + + public int getInt(int i, Units units) { + return ds.getInt(iIndex, i, units); + } + + public Datum getXTagDatum(int i) { + int table = ds.tableOfIndex(iIndex); + return ds.getYTagDatum(table, i); + } + + public int getXLength() { + int table = ds.tableOfIndex(iIndex); + return ds.getYLength(table); + } + + public Units getXUnits() { + return ds.getYUnits(); + } + + public double getXTagDouble(int i, Units units) { + int table = ds.tableOfIndex(iIndex); + return ds.getYTagDouble(table, i, units); + } + + public Units getYUnits() { + return ds.getZUnits(); + } + + public int getXTagInt(int i, Units units) { + int table = ds.tableOfIndex(iIndex); + return ds.getYTagInt(table, i, units); + } + + public Object getProperty(String name) { + return null; + } + + public String toString() { + return VectorUtil.toString(this); + } + + public Map getProperties() { + return new HashMap(); + } + + } + + protected static class YSliceDataSet extends AbstractDataSet.ViewDataSet implements VectorDataSet { + + private final int table; + private final int jIndex; + private final TableDataSet ds; + + protected YSliceDataSet(AbstractDataSet ds, int jIndex, int table) { + ds.super(); + this.jIndex = jIndex; + this.table = table; + this.ds = (TableDataSet)ds; + } + + public DataSet getPlanarView(String planeID) { + return new YSliceDataSet( (AbstractDataSet)ds.getPlanarView(planeID), jIndex, table ); + } + + public String[] getPlaneIds() { + return ds.getPlaneIds(); + } + + public Datum getDatum(int i) { + int offset = ds.tableStart(table); + return ds.getDatum(i + offset, jIndex); + } + + public double getDouble(int i, Units units) { + int offset = ds.tableStart(table); + return ds.getDouble(i + offset, jIndex, units); + } + + public int getInt(int i, Units units) { + int offset = ds.tableStart(table); + return ds.getInt(i + offset, jIndex, units); + } + + public Datum getXTagDatum(int i) { + int offset = ds.tableStart(table); + return ds.getXTagDatum(i + offset); + } + + public int getXLength() { + return ds.tableEnd(table) - ds.tableStart(table); + } + + public double getXTagDouble(int i, Units units) { + int offset = ds.tableStart(table); + return ds.getXTagDouble(i + offset, units); + } + + public Units getYUnits() { + return ds.getZUnits(); + } + + public int getXTagInt(int i, Units units) { + int offset = ds.tableStart(table); + return ds.getXTagInt(i + offset, units); + } + + public Object getProperty(String name) { + return null; + } + + public Map getProperties() { + return new HashMap(); + } + + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/AbstractVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/AbstractVectorDataSet.java new file mode 100755 index 000000000..56e2740c4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/AbstractVectorDataSet.java @@ -0,0 +1,59 @@ +/* File: AbstractVectorDataSet.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 27, 2003, 10:31 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; + +import java.util.*; + +/** Abstract implementation of the VectorDataSet interface provided to make + * implementation of concrete base classes easier. Subclasses only need to + * implement:

    + *
  • {@link VectorDataSet#getDatum(int)}
  • + *
  • {@link VectorDataSet#getDouble(int, org.das2.datum.Units)}
  • + *
  • {@link VectorDataSet#getInt(int, org.das2.datum.Units)}
  • + *
  • {@link DataSet#getPlanarView(java.lang.String)}
  • + * + * @author Edward West + */ +public abstract class AbstractVectorDataSet extends AbstractDataSet implements DataSet, VectorDataSet { + + /** Creates a new instance of AbstractVectorDataSet + * The properties map must only have keys of type String. + * @param xTags values of the x tags for this data set in xUnits + * @param xUnits the units of the x tags for this data set + * @param yUnits the units of the y tags/values for this data set + * @param properties map of property names and values + * @throws IllegalArgumentException if properties has one or more keys + * that is not a String + */ + protected AbstractVectorDataSet(double[] xTags, Units xUnits, Units yUnits, Map properties) throws IllegalArgumentException { + super(xTags, xUnits, yUnits, properties); + } + + public String toString() { + return VectorUtil.toString(this); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/AppendTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/AppendTableDataSet.java new file mode 100644 index 000000000..3255c99b2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/AppendTableDataSet.java @@ -0,0 +1,239 @@ +/* + * AppendTableDataSet.java + * + * Created on September 27, 2005, 2:12 PM + * + * + */ + +package org.das2.dataset; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Jeremy + */ +public class AppendTableDataSet implements TableDataSet { + TableDataSet[] tableDataSets; + int[] firstIndexs; + int[] firstTables; + + public AppendTableDataSet( TableDataSet tds1, TableDataSet tds2 ) { + List tableDataSetsList= new ArrayList(); + List firstIndexsList= new ArrayList(); + List firstTablesList= new ArrayList(); + + if ( tds1 instanceof AppendTableDataSet && tds2 instanceof AppendTableDataSet ) { + throw new IllegalStateException("not implemented"); + } else if ( tds1 instanceof AppendTableDataSet ) { + AppendTableDataSet atds1= (AppendTableDataSet)tds1; + tableDataSetsList.addAll( box( atds1.tableDataSets ) ); + firstIndexsList.addAll( box( atds1.firstIndexs ) ); + firstTablesList.addAll( box( atds1.firstTables ) ); + tableDataSetsList.add( tds2 ); + firstIndexsList.add( new Integer( atds1.firstIndexs[atds1.tableDataSets.length]+tds2.getXLength() ) ); + firstTablesList.add( new Integer( atds1.firstTables[atds1.tableDataSets.length]+tds2.getXLength() ) ); + + tableDataSets= (TableDataSet[])tableDataSetsList.toArray( new TableDataSet[ tableDataSetsList.size() ] ); + firstIndexs= unbox( firstIndexsList ); + firstTables= unbox( firstTablesList ); + + } else if ( tds2 instanceof AppendTableDataSet ) { + throw new IllegalStateException("not implemented"); + + } else { + tableDataSets= new TableDataSet[2]; + tableDataSets[0]= tds1; + tableDataSets[1]= tds2; + firstIndexs= new int[3]; + firstIndexs[0]= 0; + firstIndexs[1]= tds1.getXLength(); + firstIndexs[2]= firstIndexs[1] + tds2.getXLength(); + firstTables= new int[3]; + firstTables[0]= 0; + firstTables[1]= tds1.tableCount(); + firstTables[2]= firstTables[1] + tds2.tableCount(); + } + } + + private int[] unbox( List intList ) { + int[] result= new int[intList.size()]; + for ( int i=0; i=i ) return itds; + } + return firstIndexs.length-1; + } + + private int tdsTable( int itable ) { + for ( int itds=0; itds=itable ) return itds; + } + return firstIndexs.length-1; + } + + public org.das2.datum.Datum getDatum(int i, int j) { + int itds= tdsIndex(i); + return tableDataSets[itds].getDatum( i-firstIndexs[itds], j ); + } + + public double getDouble(int i, int j, org.das2.datum.Units units) { + int itds= tdsIndex(i); + return tableDataSets[itds].getDouble( i-firstIndexs[itds], j, units ); + } + + public double[] getDoubleScan(int i, org.das2.datum.Units units) { + int itds= tdsIndex(i); + return tableDataSets[itds].getDoubleScan( i-firstIndexs[itds], units ); + } + + public int getInt(int i, int j, org.das2.datum.Units units) { + int itds= tdsIndex(i); + return tableDataSets[itds].getInt( i-firstIndexs[itds], j, units ); + } + + public DataSet getPlanarView(String planeID) { + TableDataSet[] tdsPlane= new TableDataSet[tableDataSets.length]; + for ( int i=0; i "+outputBins[i]+"\n" ); + } + if ( length==0 ) { + result.append( "(no rebinning)\n" ); + } else if ( length>30 ) { + result.append( "("+(length-30)+" more)" ); + } + return result.toString(); + } + } + + private static DatumRange[] getXTagRanges( DataSet ds, int i0, int i1 ) { + Datum tagWidth= DataSetUtil.guessXTagWidth(ds).divide(2); + DatumRange[] result= new DatumRange[ i1-i0 ]; + for ( int i=0; i1 ) throw new IllegalArgumentException( "null yRebinDescriptor not allowed for non-simple table datasets." ); + ybd= getIdentityBinDescriptor( tds.getYLength(itable) ); + } + logger.finest("apply rebinning"); + + logger.finest("ybd.length="+ybd.length); + + int x0= tds.tableStart(itable); + if ( nearestNeighbor ) { + for ( int i=0; i weights[xbd.outputBins[i]][ybd.outputBins[j]] ) { + sum[xbd.outputBins[i]][ybd.outputBins[j]]= z; + weights[xbd.outputBins[i]][ybd.outputBins[j]]= w * w2; + } + } + } + } else { + for ( int i=0; i0. ) { + sum[i][j]/= weights[i][j]; + } else { + sum[i][j]= fill; + } + } + } + } + + logger.finest("calculate dataset"); + double[][][] zValues = {sum,weights}; + + int[] tableOffsets = {0}; + Units[] zUnits = {tds.getZUnits(), Units.dimensionless}; + String[] planeIDs = {"", DataSet.PROPERTY_PLANE_WEIGHTS }; + + Map properties= new HashMap(ds.getProperties()); + + if ( ddx!=null ) properties.put( DataSet.PROPERTY_X_TAG_WIDTH, ddx.binWidthDatum() ); + if ( ddy!=null ) properties.put( DataSet.PROPERTY_Y_TAG_WIDTH, ddy.binWidthDatum() ); + + double[] xTags; + if (ddx != null) { + xTags = ddx.binCenters(); + } else { + xTags = new double[nx]; + for (int i = 0; i < nx; i++) { + xTags[i] = tds.getXTagDouble(i, tds.getXUnits()); + } + } + double[][] yTags; + if (ddy != null) { + yTags = new double[][]{ddy.binCenters()}; + } else { + yTags = new double[1][ny]; + for (int j = 0; j < ny; j++) { + yTags[0][j] = tds.getYTagDouble(0, j, tds.getYUnits()); + } + } + + TableDataSet result= new DefaultTableDataSet( xTags, ddx.getUnits(), yTags, ddy.getUnits(), zValues, zUnits, planeIDs, tableOffsets, properties ); + + logger.finest("done, exiting AverageNoInterpolateTableRebinner.rebin"); + return result; + } + + public boolean isNearestNeighbor( ) { + return this.nearestNeighbor; + } + + public void setNearestNeighbor( boolean v ) { + this.nearestNeighbor= v; + } + + +} diff --git a/dasCore/src/main/java/org/das2/dataset/AveragePeakTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/AveragePeakTableRebinner.java new file mode 100755 index 000000000..ed6b3135a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/AveragePeakTableRebinner.java @@ -0,0 +1,163 @@ +/* File: TableAveragePeakRebinner.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 12, 2003, 4:25 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Edward West + */ +public class AveragePeakTableRebinner implements DataSetRebinner { + + /* adds additional planes for debugging */ + private boolean debug= false; + + /** Creates a new instance of TableAveragePeakRebinner */ + public AveragePeakTableRebinner() { + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { + + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException(); + } + + TableDataSet tds = (TableDataSet)ds; + + if ( ddY==null && tds.tableCount()==0 ) { + throw new IllegalArgumentException( "empty table and null RebinDescriptor for Y, so result YTags are undefined." ); + } + + TableDataSet weights = (TableDataSet)ds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + TableDataSet peaks = (TableDataSet)ds.getPlanarView(DataSet.PROPERTY_PLANE_PEAKS); + + long timer= System.currentTimeMillis(); + + int nx= (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + int ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + double[][] averageData= new double[nx][ny]; + double[][] averageWeights= new double[nx][ny]; + double[][] peakData = new double[nx][ny]; + + AverageTableRebinner.average(tds, weights, averageData, averageWeights, ddX, ddY); + + double[] xTags; + double[] xTagMin, xTagMax; + if (ddX != null) { + xTags = ddX.binCenters(); + xTagMin = ddX.binStops(); + xTagMax = ddX.binStarts(); + for ( int i=0; i-1 && ibin + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.dataset; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.UnitsUtil; +import org.das2.DasException; +import org.das2.system.DasLogger; +import java.util.*; +import java.util.logging.*; + +/** + * + * @author Edward West + */ +public class AverageTableRebinner implements DataSetRebinner { + + private static Logger logger = DasLogger.getLogger(DasLogger.DATA_OPERATIONS_LOG); + /** + * Holds value of property interpolate. + */ + private boolean interpolate = true; + private boolean enlargePixels = true; + + public static enum Interpolate { + + None, Linear, NearestNeighbor + } + + /** Creates a new instance of TableAverageRebinner */ + public AverageTableRebinner() { + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, DasException { + logger.finest("enter AverageTableRebinner.rebin"); + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + + if (ds == null) { + throw new NullPointerException("null data set"); + } + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException("Data set must be an instanceof TableDataSet: " + ds.getClass().getName()); + } + TableDataSet tds = (TableDataSet) ds; + TableDataSet weights = (TableDataSet) ds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + if (ddX != null && tds.getXLength() > 0) { + double start = tds.getXTagDouble(0, ddX.getUnits()); + double end = tds.getXTagDouble(tds.getXLength() - 1, ddX.getUnits()); + if (start > ddX.end) { + throw new NoDataInIntervalException("data starts after range"); + } else if (end < ddX.start) { + throw new NoDataInIntervalException("data ends before range"); + } + } + + long timer = System.currentTimeMillis(); + + Units xunits = ddX.getUnits(); + + int nx = (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + int ny = (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + logger.finest("Allocating rebinData and rebinWeights: " + nx + " x " + ny); + + double[][] rebinData = new double[nx][ny]; + double[][] rebinWeights = new double[nx][ny]; + + average(tds, weights, rebinData, rebinWeights, ddX, ddY); + if (interpolate) { + doBoundaries2RL(tds, weights, rebinData, rebinWeights, ddX, ddY, interpolateType); + doBoundaries2TB(tds, weights, rebinData, rebinWeights, ddX, ddY, interpolateType); + doCorners(tds, weights, rebinData, rebinWeights, ddX, ddY, interpolateType); + } + + double[] xTags; + double[] xTagMin; + double[] xTagMax; + + if (ddX != null) { + xTags = ddX.binCenters(); + xTagMin = ddX.binStops(); + xTagMax = ddX.binStarts(); + for (int i = 0; i < tds.getXLength(); i++) { + double xt = tds.getXTagDouble(i, xunits); + int ibin = ddX.whichBin(xt, xunits); + if (ibin > -1 && ibin < nx) { + xTagMin[ibin] = Math.min(xTagMin[ibin], xt); + xTagMax[ibin] = Math.max(xTagMax[ibin], xt); + } + } + } else { + xTags = new double[nx]; + for (int i = 0; i < nx; i++) { + xTags[i] = tds.getXTagDouble(i, tds.getXUnits()); + } + xTagMin = xTags; + xTagMax = xTags; + } + + + double[][] yTags; + if (ddY != null) { + yTags = new double[][]{ddY.binCenters()}; + } else { + yTags = new double[1][ny]; + for (int j = 0; j < ny; j++) { + yTags[0][j] = tds.getYTagDouble(0, j, tds.getYUnits()); + } + } + + Units resultXUnits = ddX == null ? tds.getXUnits() : ddX.getUnits(); + Units resultYUnits = ddY == null ? tds.getYUnits() : ddY.getUnits(); + + if (this.interpolate) { + Datum xTagWidth = (Datum) ds.getProperty("xTagWidth"); + if (xTagWidth == null) { + xTagWidth = DataSetUtil.guessXTagWidth(tds); + } + double xTagWidthDouble = xTagWidth.doubleValue(ddX.getUnits().getOffsetUnits()); + Datum yTagWidth = (Datum) ds.getProperty("yTagWidth"); + + if (ddX != null) { + fillInterpolateX(rebinData, rebinWeights, xTags, xTagMin, xTagMax, xTagWidthDouble, interpolateType); + } + if (ddY != null) { + /* Note the yTagMin,yTagMax code doesn't work here, because of the + * multiple tables. So here, we'll just up yTagWidth to be twice + * the pixel cadence. When a new data model is introduced, + * this should be revisited. + */ + if (yTagWidth == null && interpolateType == Interpolate.NearestNeighbor) { + yTagWidth = TableUtil.guessYTagWidth(tds); + } + fillInterpolateY(rebinData, rebinWeights, ddY, yTagWidth, interpolateType); + } + } else if (enlargePixels) { + enlargePixels(rebinData, rebinWeights); + } + + double[][][] zValues = {rebinData, rebinWeights}; + + int[] tableOffsets = {0}; + Units[] zUnits = {tds.getZUnits(), Units.dimensionless}; + String[] planeIDs = {"", DataSet.PROPERTY_PLANE_WEIGHTS}; + + Map properties = new HashMap(ds.getProperties()); + + if (ddX != null) { + properties.put(DataSet.PROPERTY_X_TAG_WIDTH, ddX.binWidthDatum()); + } + if (ddY != null) { + properties.put(DataSet.PROPERTY_Y_TAG_WIDTH, ddY.binWidthDatum()); + } + TableDataSet result = new DefaultTableDataSet(xTags, resultXUnits, yTags, resultYUnits, zValues, zUnits, planeIDs, tableOffsets, properties); + logger.finest("done, AverageTableRebinner.rebin"); + return result; + } + + static void doBoundaries2RL(TableDataSet tds, TableDataSet weights, double[][] rebinData, double[][] rebinWeights, RebinDescriptor ddX, RebinDescriptor ddY, Interpolate interpolateType) { + Units yunits = tds.getYUnits(); + Units zunits = tds.getZUnits(); + Units wunits = Units.dimensionless; + TableDataSet wds = WeightsTableDataSet.create(tds); + for (int i = 0; i < 2; i++) { + int ix = i == 0 ? 0 : ddX.numberOfBins() - 1; + Datum xx = i == 0 ? ddX.binCenter(0) : ddX.binCenter(ix); + + int i0 = DataSetUtil.getPreviousColumn(tds, xx); + int i1 = DataSetUtil.getNextColumn(tds, xx); + + int itable = tds.tableOfIndex(i0); + if (itable == tds.tableOfIndex(i1) && (i1 != i0)) { + DatumRange dr = new DatumRange(tds.getXTagDatum(i0), tds.getXTagDatum(i1)); + if (dr.width().gt(DataSetUtil.guessXTagWidth(tds).multiply(0.9))) { + double alpha = DatumRangeUtil.normalize(dr, xx); + if ( interpolateType==Interpolate.NearestNeighbor ) { + alpha= alpha < 0.5 ? 0.0 : 1.0; + } + int ny = ddY == null ? tds.getYLength(itable) : ddY.numberOfBins(); + for (int j = 0; j < tds.getYLength(itable); j++) { + int jj = ddY == null ? j : ddY.whichBin(tds.getYTagDouble(itable, j, yunits), yunits); + if (jj >= 0 && jj < ny) { + if (rebinWeights[ix][jj] > 0.0) { + continue; + } + if (wds.getDouble(i0, j, wunits) * wds.getDouble(i1, j, wunits) == 0.) { + continue; + } + rebinData[ix][jj] = (1 - alpha) * tds.getDouble(i0, j, zunits) + + alpha * tds.getDouble(i1, j, zunits); + rebinWeights[ix][jj] = 1.0; + } + } + } + } + } + + } + + static void doBoundaries2TB(TableDataSet tds, TableDataSet weights, double[][] rebinData, double[][] rebinWeights, RebinDescriptor ddX, RebinDescriptor ddY, Interpolate interpolateType) { + + if (ddY == null) { + return; + } + + Units yunits = tds.getYUnits(); + Units zunits = tds.getZUnits(); + Units xunits = tds.getXUnits(); + Units wunits = Units.dimensionless; + + TableDataSet wds = WeightsTableDataSet.create(tds); + for (int itable = 0; itable < tds.tableCount(); itable++) { + for (int i = 0; i < 2; i++) { + int iy = i == 0 ? 0 : ddY.numberOfBins() - 1; + Datum yy = i == 0 ? ddY.binCenter(0) : ddY.binCenter(iy); + + int j0 = TableUtil.getPreviousRow(tds, itable, yy); + int j1 = TableUtil.getNextRow(tds, itable, yy); + + if (j1 != j0) { + + DatumRange dr; + dr = new DatumRange(tds.getYTagDatum(itable, j0), tds.getYTagDatum(itable, j1)); + Datum dsWidth = TableUtil.guessYTagWidth(tds, itable); + if (ddY.isLog()) { + Units u = dr.getUnits(); + double d = dr.min().doubleValue(u); + double d0 = Math.log(dr.min().doubleValue(u) / d); + double d1 = Math.log(dr.max().doubleValue(u) / d); + dr = new DatumRange(d0, d1, Units.logERatio); + yy = Units.logERatio.createDatum(Math.log(yy.doubleValue(u) / d)); + // TODO: infinity + } + DatumRange xdr = new DatumRange(ddX.binCenter(0), ddX.binCenter(ddX.numberOfBins() - 1)); + double alpha = DatumRangeUtil.normalize(dr, yy); + if ( interpolateType==Interpolate.NearestNeighbor ) { + alpha= alpha < 0.5 ? 0.0 : 1.0; + } + int nx = ddX.numberOfBins(); + for (int ix = tds.tableStart(itable); ix < tds.tableEnd(itable); ix++) { + int ii = ddX.whichBin(tds.getXTagDouble(ix, xunits), xunits); + if (ii >= 0 && ii < nx) { + if (rebinWeights[ii][iy] > 0.0) { + continue; + } + if (wds.getDouble(ix, j0, wunits) * wds.getDouble(ix, j1, wunits) == 0.) { + continue; + } + rebinData[ii][iy] = (1 - alpha) * tds.getDouble(ix, j0, zunits) + alpha * tds.getDouble(ix, j1, zunits); + rebinWeights[ii][iy] = 1.0; + } + } + } + } + } + } + + static void doCorners(TableDataSet tds, TableDataSet weights, double[][] rebinData, double[][] rebinWeights, RebinDescriptor ddX, RebinDescriptor ddY, Interpolate interpolateType) { + if (ddY == null) { + return; + } + Units yunits = tds.getYUnits(); + Units zunits = tds.getZUnits(); + Units xunits = tds.getXUnits(); + Units wunits = Units.dimensionless; + TableDataSet wds = WeightsTableDataSet.create(tds); + for (int i = 0; i < 2; i++) { + int ix = i == 0 ? 0 : ddX.numberOfBins() - 1; + Datum xx = ddX.binCenter(ix); + int i0 = DataSetUtil.getPreviousColumn(tds, xx); + int i1 = DataSetUtil.getNextColumn(tds, xx); + + int itable = tds.tableOfIndex(i0); + if (itable != tds.tableOfIndex(i1)) { + continue; + } + + if (i0 == i1) { + continue; + } + + DatumRange xdr = new DatumRange(tds.getXTagDatum(i0), tds.getXTagDatum(i1)); + double xalpha = DatumRangeUtil.normalize(xdr, xx); + if (interpolateType == Interpolate.NearestNeighbor) { + xalpha = xalpha < 0.5 ? 0.0 : 1.0; + } + + for (int j = 0; j < 2; j++) { + int iy = j == 0 ? 0 : ddY.numberOfBins() - 1; + Datum yy = ddY.binCenter(iy); + + int j0 = TableUtil.getPreviousRow(tds, itable, yy); + int j1 = TableUtil.getNextRow(tds, itable, yy); + + if (j0 != j1) { + DatumRange ydr = new DatumRange(tds.getYTagDatum(itable, j0), tds.getYTagDatum(itable, j1)); + if (xdr.width().lt(DataSetUtil.guessXTagWidth(tds).multiply(1.1))) { + DatumRange xdr1 = new DatumRange(ddX.binCenter(0), ddX.binCenter(ddX.numberOfBins() - 1)); + double yalpha = DatumRangeUtil.normalize(ydr, yy); + if (interpolateType == Interpolate.NearestNeighbor) { + yalpha = yalpha < 0.5 ? 0.0 : 1.0; + } + if (rebinWeights[ix][iy] > 0.0) { + continue; + } + if (wds.getDouble(i1, j1, wunits) * + wds.getDouble(i0, j0, wunits) * + wds.getDouble(i1, j0, wunits) * + wds.getDouble(i0, j1, wunits) == 0.) { + continue; + } + rebinData[ix][iy] = + tds.getDouble(i1, j1, zunits) * xalpha * yalpha + + tds.getDouble(i0, j0, zunits) * (1 - xalpha) * (1 - yalpha) + + tds.getDouble(i1, j0, zunits) * xalpha * (1 - yalpha) + + tds.getDouble(i0, j1, zunits) * (1 - xalpha) * yalpha; + rebinWeights[ix][iy] = 1.0; + } + } + } + } + } + + static void average(TableDataSet tds, TableDataSet weights, double[][] rebinData, double[][] rebinWeights, RebinDescriptor ddX, RebinDescriptor ddY) { + double[] ycoordinate; + int nTables; + Units xUnits, zUnits; + int nx, ny; + + xUnits = tds.getXUnits(); + zUnits = tds.getZUnits(); + + nx = (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + ny = (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + + if (ddY != null) { + ycoordinate = ddY.binCenters(); + } else { + ycoordinate = new double[tds.getYLength(0)]; + for (int j = 0; j < ycoordinate.length; j++) { + ycoordinate[j] = tds.getDouble(0, j, zUnits); + } + } + + nTables = tds.tableCount(); + for (int iTable = 0; iTable < nTables; iTable++) { + int[] ibiny = new int[tds.getYLength(iTable)]; + for (int j = 0; j < ibiny.length; j++) { + if (ddY != null) { + ibiny[j] = ddY.whichBin(tds.getYTagDouble(iTable, j, tds.getYUnits()), tds.getYUnits()); + } else { + ibiny[j] = j; + } + } + for (int i = tds.tableStart(iTable); i < tds.tableEnd(iTable); i++) { + int ibinx; + if (ddX != null) { + ibinx = ddX.whichBin(tds.getXTagDouble(i, xUnits), xUnits); + } else { + ibinx = i; + } + + if (ibinx >= 0 && ibinx < nx) { + for (int j = 0; j < tds.getYLength(iTable); j++) { + double z = tds.getDouble(i, j, zUnits); + double w = weights == null + ? (zUnits.isFill(z) ? 0. : 1.) + : weights.getDouble(i, j, Units.dimensionless); + if (ibiny[j] >= 0 && ibiny[j] < ny) { + rebinData[ibinx][ibiny[j]] += z * w; + rebinWeights[ibinx][ibiny[j]] += w; + } + } + } + } + } + multiplyWeights(rebinData, rebinWeights, zUnits.getFillDouble()); + } + + private final static double linearlyInterpolate(int i0, double z0, int i1, double z1, int i) { + double r = ((double) (i - i0)) / (i1 - i0); + return z0 + r * (z1 - z0); + } + + private final static void multiplyWeights(double[][] data, double[][] weights, double fill) { + for (int i = 0; i < data.length; i++) { + for (int j = 0; j < data[i].length; j++) { + if (weights[i][j] > 0.0) { + data[i][j] = data[i][j] / weights[i][j]; + } else { + data[i][j] = fill; + } + } + } + } + + static void fillInterpolateX( + final double[][] data, final double[][] weights, final double[] xTags, + double[] xTagMin, double[] xTagMax, final double xSampleWidth, + Interpolate interpolateType + ) { + + final int nx = xTags.length; + final int ny = data[0].length; + final int[] i1 = new int[nx]; + final int[] i2 = new int[nx]; + double a1; + double a2; + + for (int j = 0; j < ny; j++) { + int ii1 = -1; + int ii2 = -1; + for (int i = 0; i < nx; i++) { + if (weights[i][j] > 0. && ii1 == (i - 1)) { // ho hum another valid point + i1[i] = -1; + i2[i] = -1; + ii1 = i; + } else if (weights[i][j] > 0. && ii1 == -1) { // first valid point + i1[i] = -1; + i2[i] = -1; + ii1 = i; + if (interpolateType == Interpolate.NearestNeighbor) { + for (int jjj = 0; jjj < i; jjj++) { + i2[jjj] = ii1; + } + } + } else if (weights[i][j] > 0. && ii1 < (i - 1)) { // bracketed a gap, interpolate + if (ii1 > -1) { + i1[i] = -1; + i2[i] = -1; + ii2 = i; + for (int ii = ii1 + 1; ii < i; ii++) { + i1[ii] = ii1; + i2[ii] = ii2; + } + ii1 = i; + } + } else { + i1[i] = -1; + i2[i] = -1; + } + } + if (interpolateType == Interpolate.NearestNeighbor && ii1 > -1) { + for (int jjj = ii1; jjj < nx; jjj++) { + i1[jjj] = ii1; + } + } + if (interpolateType == Interpolate.NearestNeighbor) { + + for (int i = 0; i < nx; i++) { + if (Math.min( + i1[i] == -1 ? Double.MAX_VALUE : (xTags[i] - xTagMin[i1[i]]), + i2[i] == -1 ? Double.MAX_VALUE : (xTagMax[i2[i]] - xTags[i])) < xSampleWidth / 2 + ) { + + int idx = -1; + if (i1[i] == -1) { + if (i2[i] == -1) { + continue; + } + idx = i2[i]; + } else if (i2[i] == -1) { + idx = i1[i]; + } else { + a2 = ((xTags[i] - xTagMax[i1[i]]) / (xTagMin[i2[i]] - xTags[i1[i]])); + if (a2 < 0.5) { + idx = i1[i]; + } else { + idx = i2[i]; + } + } + data[i][j] = data[idx][j]; + weights[i][j] = weights[idx][j]; + } + } + } else { + for (int i = 0; i < nx; i++) { + if (i1[i] > -1 && i2[i] > -1 && (xTagMin[i2[i]] - xTagMax[i1[i]]) <= xSampleWidth * 1.5) { + a2 = ((xTags[i] - xTagMax[i1[i]]) / (xTagMin[i2[i]] - xTags[i1[i]])); + a1 = 1. - a2; + data[i][j] = data[i1[i]][j] * a1 + data[i2[i]][j] * a2; + weights[i][j] = weights[i1[i]][j] * a1 + weights[i2[i]][j] * a2; //approximate + + } + } + } + } + } + + static void fillInterpolateY(final double[][] data, final double[][] weights, RebinDescriptor ddY, Datum yTagWidth, Interpolate interpolateType) { + + final int nx = data.length; + final int ny = ddY.numberOfBins(); + final int[] i1 = new int[ny]; + final int[] i2 = new int[ny]; + final double[] yTagTemp = new double[ddY.numberOfBins()]; + double a1; + double a2; + + final double[] yTags = ddY.binCenters(); + final Units yTagUnits = ddY.getUnits(); + final boolean log = ddY.isLog(); + + if (log) { + for (int j = 0; j < ny; j++) { + yTagTemp[j] = Math.log(yTags[j]); + } + } else { + for (int j = 0; j < ny; j++) { + yTagTemp[j] = yTags[j]; + } + } + + double ySampleWidth; + double fudge = 1.5; + if (interpolateType == Interpolate.NearestNeighbor) { + fudge = 1.1; + } + if (yTagWidth == null) { + double d = Double.MAX_VALUE / 4; // avoid roll-over when *1.5 + ySampleWidth = d; + } else { + if (UnitsUtil.isRatiometric(yTagWidth.getUnits())) { + double p = yTagWidth.doubleValue(Units.logERatio); + ySampleWidth = p * fudge; + } else { + double d = yTagWidth.doubleValue(yTagUnits.getOffsetUnits()); + ySampleWidth = d * fudge; + } + } + + for (int i = 0; i < nx; i++) { + int ii1 = -1; + int ii2 = -1; + for (int j = 0; j < ny; j++) { + if (weights[i][j] > 0. && ii1 == (j - 1)) { // ho hum another valid point + + i1[j] = -1; + i2[j] = -1; + ii1 = j; + } else if (weights[i][j] > 0. && ii1 == -1) { // first valid point + + i1[j] = -1; + i2[j] = -1; + ii1 = j; + if (interpolateType == Interpolate.NearestNeighbor) { + for (int jjj = 0; jjj < j; jjj++) { + i2[jjj] = ii1; + } + } + } else if (weights[i][j] > 0. && ii1 < (j - 1)) { // bracketed a gap, interpolate + + if ((ii1 > -1)) { // need restriction on Y gap size + + i1[j] = -1; + i2[j] = -1; + ii2 = j; + for (int jj = j - 1; jj >= ii1; jj--) { + i1[jj] = ii1; + i2[jj] = ii2; + } + ii1 = j; + } + } else { + i1[j] = -1; + i2[j] = -1; + } + } + if (interpolateType == Interpolate.NearestNeighbor && ii1 > -1) { + for (int jjj = ii1; jjj < ny; jjj++) { + i1[jjj] = ii1; + } + } + if (interpolateType == Interpolate.NearestNeighbor) { + for (int j = 0; j < ny; j++) { + boolean doInterp; + if ( i1[j]!= -1 && i2[j] != -1) { + doInterp= ( yTagTemp[i2[j]]-yTagTemp[i1[j]] ) < ySampleWidth*2; + } else { + //kludge for bug 000321 + doInterp= Math.min(i1[j] == -1 ? Double.MAX_VALUE : (yTagTemp[j] - yTagTemp[i1[j]]), i2[j] == -1 ? Double.MAX_VALUE : (yTagTemp[i2[j]] - yTagTemp[j])) < ySampleWidth / 2; + } + if ( doInterp ) { + int idx; + if (i1[j] == -1) { + idx = i2[j]; + } else if (i2[j] == -1) { + idx = i1[j]; + } else { + a2 = ((yTagTemp[j] - yTagTemp[i1[j]]) / (yTagTemp[i2[j]] - yTagTemp[i1[j]])); + if (a2 < 0.5) { + idx = i1[j]; + } else { + idx = i2[j]; + } + } + data[i][j] = data[i][idx]; + weights[i][j] = weights[i][idx]; + + } + if ( i==1 && j==34 ) { + int jkk=0; + } + + } + } else { + for (int j = 0; j < ny; j++) { + if ((i1[j] != -1) && ((yTagTemp[i2[j]] - yTagTemp[i1[j]]) < ySampleWidth || i2[j] - i1[j] == 2)) { //kludge for bug 000321 + + a2 = ((yTagTemp[j] - yTagTemp[i1[j]]) / (yTagTemp[i2[j]] - yTagTemp[i1[j]])); + a1 = 1. - a2; + data[i][j] = data[i][i1[j]] * a1 + data[i][i2[j]] * a2; + weights[i][j] = weights[i][i1[j]] * a1 + weights[i][i2[j]] * a2; //approximate + + } + } + } + } + } + + private void enlargePixels(double[][] rebinData, double[][] rebinWeights) { + int enlargeSize = 5; + for (int aa = 0; aa < enlargeSize; aa++) { + for (int ii = 0; ii < rebinData.length - 1; ii++) { + for (int jj = 0; jj < rebinData[0].length; jj++) { + if (rebinWeights[ii][jj] == 0) { + rebinData[ii][jj] = rebinData[ii + 1][jj]; + rebinWeights[ii][jj] = rebinWeights[ii + 1][jj]; + } + } + } + for (int ii = rebinData.length - 1; ii > 0; ii--) { + for (int jj = 0; jj < rebinData[0].length; jj++) { + if (rebinWeights[ii][jj] == 0) { + rebinData[ii][jj] = rebinData[ii - 1][jj]; + rebinWeights[ii][jj] = rebinWeights[ii - 1][jj]; + } + } + } + for (int jj = 0; jj < rebinData[0].length - 1; jj++) { + for (int ii = 0; ii < rebinData.length; ii++) { + if (rebinWeights[ii][jj] == 0) { + rebinData[ii][jj] = rebinData[ii][jj + 1]; + rebinWeights[ii][jj] = rebinWeights[ii][jj + 1]; + } + } + } + for (int jj = rebinData[0].length - 1; jj > 0; jj--) { + for (int ii = 0; ii < rebinData.length; ii++) { + if (rebinWeights[ii][jj] == 0) { + rebinData[ii][jj] = rebinData[ii][jj - 1]; + rebinWeights[ii][jj] = rebinWeights[ii][jj - 1]; + } + } + } + } + } + + /** + * Getter for property interpolate. + * @return Value of property interpolate. + */ + public boolean isInterpolate() { + return this.interpolate; + } + + /** + * Setter for property interpolate. + * @param interpolate New value of property interpolate. + */ + public void setInterpolate(boolean interpolate) { + this.interpolate = interpolate; + } + + public void setEnlargePixels(boolean enlargePixels) { + this.enlargePixels = enlargePixels; + } + + public boolean isEnlargePixels() { + return enlargePixels; + } + protected Interpolate interpolateType = Interpolate.Linear; + public static final String PROP_INTERPOLATETYPE = "interpolateType"; + + public Interpolate getInterpolateType() { + return interpolateType; + } + + public void setInterpolateType(Interpolate interpolateType) { + Interpolate oldInterpolateType = this.interpolateType; + this.interpolateType = interpolateType; + propertyChangeSupport.firePropertyChange(PROP_INTERPOLATETYPE, oldInterpolateType, interpolateType); + } + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/CacheTag.java b/dasCore/src/main/java/org/das2/dataset/CacheTag.java new file mode 100755 index 000000000..f6a8bc28d --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/CacheTag.java @@ -0,0 +1,116 @@ +package org.das2.dataset; + +import org.das2.datum.DatumRange; +import org.das2.datum.Datum; +import org.das2.datum.DatumUtil; + +/** + * CacheTags are used to represent the coverage of datasets stored in a cache, and are + * the reference used to decide if a cache entry is capable of satisfying a data request. + * + */ +public class CacheTag { + + public static final String INTRINSIC = "intrinsic"; + + DatumRange range; + Datum resolution; + + /** + * Appends two CacheTags, when possible. The new range covers the ranges, and the resolution is the lower of the two. + * @param tag1 a CacheTag + * @param tag2 a CacheTag that is adjacent to tag1 + * @return a CacheTag that covers both CacheTags precisely. + */ + public static CacheTag append( CacheTag tag1, CacheTag tag2 ) { + Datum res; + if ( tag1.resolution==null && tag2.resolution==null ) { + res= null; + } else { + if ( tag1.resolution!=null && tag2.resolution!=null ) { + res= tag1.resolution.gt( tag2.resolution ) ? tag1.resolution : tag2.resolution; + } else { + res= tag1.resolution==null ? tag2.resolution : tag1.resolution; + } + } + + Datum min; + Datum max; + if ( !tag1.range.intersects(tag2.range) ) { + if ( tag2.range.min().lt( tag1.range.min() ) ) { + CacheTag temp= tag1; + tag1= tag2; + tag2= temp; + } + if ( tag1.range.max().lt(tag2.range.min()) ) { + throw new IllegalArgumentException("cache tags cannot be appended, they are not adjacent"); + } + min= tag1.range.min(); + max= tag2.range.max(); + } else { + min= tag1.range.min().lt( tag2.range.min() ) ? tag1.range.min() : tag2.range.min(); + max= tag1.range.max().gt( tag2.range.max() ) ? tag1.range.max() : tag2.range.max(); + } + + DatumRange range= new DatumRange( min, max ); + return new CacheTag( range, res ); + } + + /** + * Constructs a new CacheTag. + * @param start the beginning of the interval. + * @param end the end of the interval. + * @param resolution the highest resolution request that can be provided. + */ + public CacheTag(Datum start, Datum end, Datum resolution) { + this( new DatumRange( start, end ), resolution ); + } + + /** + * Constucts a new CacheTag. + * @param range the interval covered by the CacheTag. + * @param resolution the highest resolution request that can be provided. + */ + public CacheTag( DatumRange range, Datum resolution) { + this.range= range; + this.resolution= resolution; + } + + /** + * Returns a string representation of the object. + * @return a human consumable representation of the cache tag. This should fit on + * one line as it is used to list cache contents. + */ + public String toString() { + return range + " @ " + ( resolution==null ? INTRINSIC : ""+DatumUtil.asOrderOneUnits(resolution) ); + } + + /** + * Indicates if the cache tag the the capability of satifying the given cache tag. If the tag has a lower (courser) resolution and its bounds are within + * this CacheTag. + * @return true if the tag has a lower resolution and its bounds are within + * this CacheTag. + * @param tag a CacheTag to test. + */ + public boolean contains( CacheTag tag ) { + return ( this.range.contains( tag.range ) + && ( this.resolution==null + || ( tag.resolution!=null && this.resolution.le( tag.resolution ) ) + ) ); + } + + /** + * the range covered by the cache tag. + */ + public DatumRange getRange() { + return this.range; + } + + /** + * the resolution of the cache tag, which may be null. + */ + public Datum getResolution() { + return this.resolution; + } +} + diff --git a/dasCore/src/main/java/org/das2/dataset/ClippedTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/ClippedTableDataSet.java new file mode 100755 index 000000000..bc9b73be1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/ClippedTableDataSet.java @@ -0,0 +1,266 @@ +/* + * ClippedTableDataSet.java + * + * Created on February 16, 2004, 12:19 PM + */ + +package org.das2.dataset; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import java.util.*; + +/** + * + * @author Jeremy + */ +public class ClippedTableDataSet implements TableDataSet { + + /* + * clippedTableDataSet + * + * TableDataSet that is a view of a section of a TableDataSet including an X and Y range, + * but not much more. + */ + + TableDataSet source; + + int xoffset; + int xlength; + + int[] yoffsets; + int[] ylengths; + + int tableOffset; + int tableCount; + + void calculateXOffset( Datum xmin, Datum xmax ) { + xoffset= DataSetUtil.getPreviousColumn(source, xmin); + int ix1= DataSetUtil.getNextColumn(source, xmax ); + xlength= ix1- xoffset+1; + } + + void calculateTableOffset() { + tableOffset= -99; + for ( int itable=0; itable xoffset ) { + tableOffset= itable; + } + if ( tableOffset!=-99 + && source.tableEnd(itable) >= xoffset+xlength ) { + tableCount= itable - tableOffset + 1; + } + } + } + + void calculateYOffsets( Datum ymin, Datum ymax ) { + yoffsets= new int[tableCount]; + ylengths= new int[tableCount]; + for ( int itable=tableOffset; itable 1 ) { + throw new IllegalArgumentException( "this ClippedTableDataSet constructor requires that there be only one table" ); + } + if ( source.getXLength() < xoffset+xlength ) { + throw new IllegalArgumentException( "xoffset + xlength greater than the number of XTags in source" ); + } + if ( source.getYLength(0) < yoffset+ylength ) { + throw new IllegalArgumentException( "yoffset + ylength greater than the number of YTags in source" ); + } + if ( yoffset<0 || xoffset<0 ) { + throw new IllegalArgumentException( "yoffset("+yoffset+") or xoffset("+xoffset+") is negative" ); + } + this.source= source; + this.xoffset= xoffset; + this.xlength= xlength; + this.tableOffset= 0; + this.tableCount= 1; + this.yoffsets= new int[] { yoffset }; + this.ylengths= new int[] { ylength }; + } + + private ClippedTableDataSet( TableDataSet source, int xoffset, int xlength, + int [] yoffsets, int [] ylengths, int tableOffset, int tableCount ) { + if ( source==null ) { + throw new IllegalArgumentException("source is null"); + } + this.source= source; + this.xoffset= xoffset; + this.xlength= xlength; + this.yoffsets= yoffsets; + this.ylengths= ylengths; + this.tableOffset= tableOffset; + this.tableCount= tableCount; + } + + + public Datum getDatum(int i, int j) { + return source.getDatum( i+xoffset, j+yoffsets[tableOfIndex(i)] ); + } + + public double getDouble(int i, int j, Units units) { + return source.getDouble( i+xoffset, j+yoffsets[tableOfIndex(i)], units ); + } + + public double[] getDoubleScan(int i, Units units) { + int table = tableOfIndex(i); + int yLength = getYLength(table); + double[] array = new double[yLength]; + double[] sourceArray = source.getDoubleScan(i + xoffset, units); + System.arraycopy(sourceArray, yoffsets[table], array, 0, yLength); + return array; + } + + public DatumVector getScan(int i) { + int table = tableOfIndex(i); + int yLength = getYLength(table); + return source.getScan(i+xoffset).getSubVector(yoffsets[table], yoffsets[table] + yLength); + } + + public int getInt(int i, int j, Units units) { + return source.getInt( i+xoffset, j+yoffsets[tableOfIndex(i)], units ); + } + + public DataSet getPlanarView(String planeID) { + TableDataSet sourcePlane= (TableDataSet)source.getPlanarView(planeID); + if (sourcePlane==null) { + return null; + } else { + return new ClippedTableDataSet(sourcePlane, + xoffset,xlength,yoffsets,ylengths,tableOffset,tableCount); + } + } + + public String[] getPlaneIds() { + return source.getPlaneIds(); + } + + public Map getProperties() { + return source.getProperties(); + } + + public Object getProperty( String name ) { + return source.getProperty( name ); + } + + public int getXLength() { + return xlength; + } + + public VectorDataSet getXSlice(int i) { + int itable= source.tableOfIndex(i+xoffset); + DatumRange dr= new DatumRange( source.getYTagDatum(itable,yoffsets[itable]), source.getYTagDatum(itable,yoffsets[itable]+ylengths[itable]) ); + return new ClippedVectorDataSet( source.getXSlice( i+xoffset ), dr ); + } + + public Datum getXTagDatum(int i) { + return source.getXTagDatum( i+xoffset ); + } + + public double getXTagDouble(int i, Units units) { + return source.getXTagDouble( i+xoffset, units ); + } + + public int getXTagInt(int i, Units units) { + return source.getXTagInt( i+xoffset, units ); + } + + public Units getXUnits() { + return source.getXUnits(); + } + + public int getYLength(int table) { + return ylengths[table]; + } + + public VectorDataSet getYSlice(int j, int table) { + return source.getYSlice( j+yoffsets[table], table+tableOffset ); + } + + public Datum getYTagDatum(int table, int j) { + return source.getYTagDatum( table+tableOffset, j+yoffsets[table] ); + } + + public double getYTagDouble(int table, int j, Units units) { + return source.getYTagDouble( table+tableOffset, j+yoffsets[table], units ); + } + + public int getYTagInt(int table, int j, Units units) { + return source.getYTagInt( table+tableOffset, j+yoffsets[table], units ); + } + + public org.das2.datum.Units getYUnits() { + return source.getYUnits(); + } + + public org.das2.datum.Units getZUnits() { + return source.getZUnits(); + } + + public int tableCount() { + return tableCount; + } + + public int tableEnd(int table) { + int i= source.tableEnd(table+tableOffset) - xoffset; + if ( i>getXLength() ) { + return getXLength(); + } else { + return i; + } + } + + public int tableOfIndex(int i) { + return source.tableOfIndex( i+xoffset ) - tableOffset; + } + + public int tableStart(int table) { + int i= source.tableStart(table+tableOffset) - xoffset; + if ( i<0 ) { + return 0; + } else { + return i; + } + } + + public String toString() { + return "ClippedTableDataSet " + TableUtil.toString(this); + } + + public DatumVector getYTags(int table) { + double[] tags = new double[getYLength(table)]; + Units yUnits = getYUnits(); + for (int j = 0; j < tags.length; j++) { + tags[j] = getYTagDouble(table, j, yUnits); + } + return DatumVector.newDatumVector(tags, yUnits); + } + + public Object getProperty(int table, String name) { + return source.getProperty(table, name); + } + + +} diff --git a/dasCore/src/main/java/org/das2/dataset/ClippedVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/ClippedVectorDataSet.java new file mode 100644 index 000000000..891bdeeb0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/ClippedVectorDataSet.java @@ -0,0 +1,88 @@ +/* + * ClippedVectorDataSet.java + * + * Created on April 18, 2005, 5:38 PM + */ + +package org.das2.dataset; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; + +/** + * + * @author Jeremy + */ +public class ClippedVectorDataSet implements VectorDataSet { + + int xoffset; + int xlength; + VectorDataSet source; + + /** Creates a new instance of ClippedVectorDataSet */ + public ClippedVectorDataSet( VectorDataSet source, DatumRange xclip ) { + xoffset= DataSetUtil.getPreviousColumn( source, xclip.min() ); + xlength= DataSetUtil.getNextColumn( source, xclip.max() ) - xoffset; + this.source= source; + } + + public ClippedVectorDataSet( VectorDataSet source, int xoffset, int xlength ) { + this.xoffset= xoffset; + this.xlength= xlength; + this.source= source; + } + + public Datum getDatum(int i) { + return source.getDatum(i+xoffset); + } + + public double getDouble(int i, Units units) { + return source.getDouble(i+xoffset,units); + } + + public int getInt(int i, Units units) { + return source.getInt(i+xoffset,units); + } + + public DataSet getPlanarView(String planeID) { + return new ClippedVectorDataSet( (VectorDataSet)source.getPlanarView(planeID), xoffset, xlength ); + } + + public String[] getPlaneIds() { + return source.getPlaneIds(); + } + + public java.util.Map getProperties() { + return source.getProperties(); + } + + public Object getProperty(String name) { + return source.getProperty(name); + } + + public int getXLength() { + return xlength; + } + + public Datum getXTagDatum(int i) { + return source.getXTagDatum( i+xoffset ); + } + + public double getXTagDouble(int i, Units units) { + return source.getXTagDouble( i+xoffset, units ); + } + + public int getXTagInt(int i, Units units) { + return source.getXTagInt( i+xoffset, units ); + } + + public Units getXUnits() { + return source.getXUnits(); + } + + public Units getYUnits() { + return source.getYUnits(); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/ConstantDataSetDescriptor.java b/dasCore/src/main/java/org/das2/dataset/ConstantDataSetDescriptor.java new file mode 100755 index 000000000..6628d6c4f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/ConstantDataSetDescriptor.java @@ -0,0 +1,78 @@ +/* File: ConstantDataSetDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.DasException; +import org.das2.datum.Datum; +import org.das2.util.monitor.ProgressMonitor; + +/** + * This class wraps a DataSet to use where DataSetDescriptors are required. This + * trivially returns the wrapped DataSet regardless of the getDataSet parameters. + * Originally this class was used with Renderers, which needed a DataSetDescriptor + * for their operation. Now that they interact only with DataSets, and this + * class should not be used with them, use Renderer.setDataSet instead. + * + * @author eew + * + */ +public class ConstantDataSetDescriptor extends DataSetDescriptor { + + DataSet ds; + + /** Creates a new instance of ConstantDataSetDescriptor */ + public ConstantDataSetDescriptor(DataSet ds) { + super(); + if (ds == null) throw new NullPointerException("DataSet parameter cannot be null"); + this.ds = ds; + } + + // this is never called because we override getDataSet + public DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + return ds; + } + + public DataSet getDataSet( Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + return ds; + } + + public org.das2.datum.Units getXUnits() { + return ds.getXUnits(); + } + + public void requestDataSet(Datum start, Datum end, Datum resolution, ProgressMonitor monitor, Object lockObject) { + DataSetUpdateEvent dsue= null; + try { + DataSet ds= getDataSet(start, end, resolution, monitor); + dsue= new DataSetUpdateEvent( (Object)this, ds ); + fireDataSetUpdateEvent(dsue); + } catch ( DasException e ) { + dsue= new DataSetUpdateEvent( (Object)this,e); + fireDataSetUpdateEvent(dsue); + } + return; + + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataRequestThread.java b/dasCore/src/main/java/org/das2/dataset/DataRequestThread.java new file mode 100644 index 000000000..912d2785a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataRequestThread.java @@ -0,0 +1,188 @@ +/* File: DataRequestThread.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.util.monitor.ProgressMonitor; +import org.das2.DasException; +import org.das2.dataset.DataRequestor; +import org.das2.datum.Datum; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Edward West + */ +public class DataRequestThread extends Thread { + + private static int threadCount = 0; + + private Object lock = new String("DATA_REQUEST_LOCK"); + + private DataRequest currentRequest; + + private List queue = Collections.synchronizedList(new LinkedList()); + + /** Creates a new instance of DataRequestThread */ + public DataRequestThread() { + //Set the name for debugging purposes. + setName("DataRequest_" + threadCount++); + setDaemon(true); + start(); //Start it up + } + + /** + * Begins a data reqest operation that will be executed + * in a separate thread and pass the resulting DataSet to the + * org.das2.event.DataRequestListener#finished(org.das2.event.DataRequestEvent) + * finished() method of the DataRequestor + * specified. + * + * @param dsd the DataSetDescriptor used to obtain + * the DataSet + * @param start the start of the requested time interval + * @param end the end of the requested time interval + * @param resolution the requested resolution of the data set + * @param requestor DataRequestor that is notified + * when the data loading operation is complete. + */ + public void request(DataSetDescriptor dsd, + Datum start, Datum end, Datum resolution, DataRequestor requestor, ProgressMonitor monitor) + throws InterruptedException { + + requestInternal(new DataRequest(dsd, start, end, resolution, requestor, monitor)); + + } + /** + * Begins a data reqest operation that will be executed + * in a separate thread and pass the resulting DataSet to the + * org.das2.event.DataRequestListener#finished(org.das2.event.DataRequestEvent) + * finished() method of the DataRequestor + * specified. + * + * This method does not return until after the data loading is complete + * or the request had been canceled. + * + * @param dsd the DataSetDescriptor used to obtain + * the DataSet + * @param params extra parameters passed to the + * DataSetDescriptor#getDataSet(org.das2.util.Datum,org.das2.util.Datum,org.das2.util.Datum,org.das2.util.monitor.ProgressMonitor) + * getDataSet() method. (TODO: these are ignored) + * @param start the start of the requested time interval + * @param end the end of the requested time interval + * @param resolution the requested resolution of the data set + * @param requestor DataRequestor that is notified + * when the data loading operation is complete. + */ + public void requestAndWait(DataSetDescriptor dsd, Object params, + Datum start, Datum end, Datum resolution, DataRequestor requestor, ProgressMonitor monitor) + throws InterruptedException { + + DataRequest request = new DataRequest(dsd, start, end, resolution, requestor, monitor); + + //Wait till thread is done loading + synchronized (request) { + requestInternal(request); + request.wait(); + } + } + + private void requestInternal(DataRequest request) { + queue.add(request); + + //Notify loading thread if it is waiting on object lock. + synchronized (lock) { + lock.notify(); + } + } + + public void cancelCurrentRequest() throws InterruptedException { + synchronized (lock) { + if (this.isAlive()) { + this.interrupt(); + lock.wait(); + } + } + } + + public void run() { + if (Thread.currentThread() != this) { + throw new IllegalStateException( + "This method should not be invoked directly"); + } + + while (true) { + while (!queue.isEmpty()) { + currentRequest = (DataRequest)queue.remove(0); + try { + DataSet ds = currentRequest.dsd.getDataSet( + currentRequest.start, + currentRequest.end, + currentRequest.resolution, + currentRequest.monitor); + currentRequest.requestor.finished(ds); + } + catch (DasException e) { + currentRequest.requestor.exception(e); + } + finally { + synchronized (currentRequest) { + currentRequest.notifyAll(); + } + } + } + synchronized (lock) { + try { + lock.wait(); + } + catch(InterruptedException ie) { + return; + } + } + } + } + + private static class DataRequest { + DataSetDescriptor dsd; + Datum start; + Datum end; + Object params; + Datum resolution; + DataRequestor requestor; + ProgressMonitor monitor; + DataRequest(DataSetDescriptor dsd, Datum start, + Datum end, Datum resolution, + DataRequestor requestor, ProgressMonitor monitor) { + this.dsd = dsd; + this.start = start; + this.end = end; + this.resolution = resolution; + this.requestor = requestor; + this.monitor = monitor; + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataRequestor.java b/dasCore/src/main/java/org/das2/dataset/DataRequestor.java new file mode 100644 index 000000000..e5d490c8c --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataRequestor.java @@ -0,0 +1,36 @@ +/* File: DataRequestor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * + * @author Edward West + */ +public interface DataRequestor extends java.util.EventListener { + + void finished(DataSet ds); + + void exception(Exception e); + +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSet.java b/dasCore/src/main/java/org/das2/dataset/DataSet.java new file mode 100644 index 000000000..91c13637b --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSet.java @@ -0,0 +1,193 @@ +/* File: DataSet.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 24, 2003, 11:23 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import java.util.*; + +/** General interface for objects encapsulating a data set + * + * @author Edward West + */ +public interface DataSet { + + /** + * CacheTag object describing the start, end, and resolution of the dataset. + */ + final static String PROPERTY_CACHE_TAG= "cacheTag"; + + /** + * Long estimating the size of the dataset in memory. For example, if a dataset is + * backed by a local file, then zero for this indicates no penalty for storing this + * dataset. + */ + final static String PROPERTY_SIZE_BYTES= "sizeBytes"; + + /** + * Datum which is the nominal distance between successive xTags. This is used for example to prevent + * interpolation between distant measurements + */ + final static String PROPERTY_X_TAG_WIDTH= "xTagWidth"; + + /** + * Datum, see xTagWidth + */ + final static String PROPERTY_Y_TAG_WIDTH= "yTagWidth"; + + /** + * DatumRange useful for setting scales + */ + final static String PROPERTY_X_RANGE="xRange"; + + /** + * DatumRange useful for setting scales + */ + final static String PROPERTY_Y_RANGE="yRange"; + + /** + * DatumRange useful for setting scales + */ + final static String PROPERTY_Z_RANGE="zRange"; + + /** + * suggest render method to use. These are + * canonical: + * spectrogram + * symbolLine + * stackedHistogram + */ + final static String PROPERTY_RENDERER="renderer"; + + /** + * String "log" or "linear" + */ + final static String PROPERTY_Y_SCALETYPE="yScaleType"; + + /** + * String "log" or "linear" + */ + final static String PROPERTY_Z_SCALETYPE="zScaleType"; + + final static String PROPERTY_X_LABEL="xLabel"; + + final static String PROPERTY_Y_LABEL="yLabel"; + + final static String PROPERTY_Z_LABEL="zLabel"; + + /** + * Boolean assuring that the dataset is monotonic in X. This allows + * some optimizations to be made. + */ + final static String PROPERTY_X_MONOTONIC="xMonotonic"; + + /** + * dataset containing the peaks when available + */ + final static String PROPERTY_PLANE_PEAKS= "peaks"; + + /** + * dataset containing the weights when available + */ + final static String PROPERTY_PLANE_WEIGHTS= "weights"; + + /** + * DatumFormatter for formatting data in the dataset. + */ + public static String PROPERTY_FORMATTER= "formatter"; + + /** Returns the property value associated with the string name + * @param name the name of the property requested + * @return the property value for name or null + */ + Object getProperty(String name); + + /** Returns all dataset properties in a Map. + * @return a Map of all properties. + */ + Map getProperties(); + + /** Returns the Units object representing the unit type of the x tags + * for this data set. + * @return the x units + */ + Units getXUnits(); + + /** Returns the Units object representing the unit type of the y tags + * or y values for this data set. + * @return the y units + */ + Units getYUnits(); + + /** Returns the value of the x tag at the given index i as a + * Datum. + * @param i the index of the requested x tag + * @return the value of the x tag at the given index i as a + * Datum. + */ + Datum getXTagDatum(int i); + + /** Returns the value of the x tag at the given index i as a + * double in the given units. XTags must be + * monotonically increasing with i. + * @return the value of the x tag at the given index i as a + * double. + * @param units the units of the returned value + * @param i the index of the requested x tag + */ + double getXTagDouble(int i, Units units); + + /** Returns the value of the x tag at the given index i as an + * int in the given units. XTags must be + * monotonically increasing with i. + * @return the value of the x tag at the given index i as an + * int. + * @param units the units of the returned value. + * @param i the index of the requested x tag + */ + int getXTagInt(int i, Units units); + + /** Returns the number of x tags in this data set. XTags must be + * monotonically increasing with i. + * @return the number of x tags in this data set. + */ + int getXLength(); + + /** Returns a DataSet with the specified view as the primary + * view. + * @param planeID the String id of the requested plane. + * @return the specified view, as a DataSet + */ + //TODO: consider throwing IllegalArgumentException if the plane doesn't exist. + // we have methods to query for the plane names. + DataSet getPlanarView(String planeID); + + /** + * Returns a list of auxillary planes (e.g. weights, peaks) for the dataset. + * Note that the default plane, "" may or may not be returned, based on + * implementations. + */ + public String[] getPlaneIds(); + +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSetCache.java b/dasCore/src/main/java/org/das2/dataset/DataSetCache.java new file mode 100644 index 000000000..7c060e985 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSetCache.java @@ -0,0 +1,38 @@ +/* File: DataSetCache.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; +/** + * + * @author jbf + */ +public interface DataSetCache { + + public void store( DataSetDescriptor dsd, CacheTag cacheTag, DataSet data ); + + public boolean haveStored( DataSetDescriptor dsd, CacheTag cacheTag ); + + public DataSet retrieve( DataSetDescriptor dsd, CacheTag cacheTag ); + + public void reset(); +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSetConsumer.java b/dasCore/src/main/java/org/das2/dataset/DataSetConsumer.java new file mode 100644 index 000000000..bc9ee0f6a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSetConsumer.java @@ -0,0 +1,32 @@ +/* File: DataSetConsumer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * + * @author jbf + */ +public interface DataSetConsumer { + public DataSet getConsumedDataSet(); +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSetDescriptor.java b/dasCore/src/main/java/org/das2/dataset/DataSetDescriptor.java new file mode 100755 index 000000000..967a807b4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSetDescriptor.java @@ -0,0 +1,406 @@ +/* File: DataSetDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 22, 2003, 12:49 PM by __FULLNAME__ <__EMAIL__> + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.dataset; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.client.DasServer; +import org.das2.client.DataSetDescriptorNotAvailableException; +import org.das2.client.StreamDataSetDescriptor; +import org.das2.client.NoSuchDataSetException; +import org.das2.stream.StreamDescriptor; +import org.das2.DasIOException; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.system.DasLogger; +import org.das2.system.RequestProcessor; + +import org.das2.util.URLBuddy; +import java.net.*; +import java.util.*; +import java.util.regex.*; +import java.lang.reflect.*; +import java.util.logging.Logger; +import javax.swing.event.*; + +/** + * DataSetDescriptors are a source from where datasets are produced. These uniquely identify a data set + * that is parameteric. Typically, the parameter is time, so for example, there might be a DataSetDescriptor + * for "discharge of the Iowa River measured at Iowa City." Clients of the class get + * DataSets from the DataSetDescriptor via the getDataSet( Start, End, Resolution ) method. So for + * example, you might ask what is the discharge from June 1 to August 31st, 2005, at a resolution of + * 1 day. Presently, it's implicit that this means to give bin averages of the data. + * + *

    DataSetDescriptors are identified with a URL-like string: + *

    http://www-pw.physics.uiowa.edu/das/das2Server?das2_1/cluster/wbd/r_wbd_dsn_cfd&spacecraft%3Dc1%26antenna%3DEy

    + * + *

    The protocol of the string indicates how the DataSetDescriptor is to be constructed, and presently + * there are: + *

    + *   http     a das2Server provides the specification of the datasetdescriptor.
    + *   class    refers to a loadable java class that is an instanceof DataSetDescriptor and
    + *            has the method newDataSetDescriptor( Map params ) throws DasException
    + *
    + *

    + * @author jbf + */ +public abstract class DataSetDescriptor implements Displayable { + + protected Map properties = new HashMap(); + private boolean defaultCaching = true; + private DataSetCache dataSetCache; + private String dataSetID; + private EventListenerList listenerList; + + protected DataSetDescriptor(final String dataSetID) { + dataSetCache = DasApplication.getDefaultApplication().getDataSetCache(); + this.dataSetID = dataSetID; + } + + protected DataSetDescriptor() { + dataSetCache = DasApplication.getDefaultApplication().getDataSetCache(); + dataSetID = "class:"+this.getClass().getName(); + } + private static final Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + + /** + * getDataSetImpl implements the getDataSet for this DataSetDescriptor implementation. The + * getDataSet call of the abstract DataSetDescriptor uses this routine to satisfy requests and + * fill its cache. This caching may be disabled via setDefaultCaching. To satisfy the request, + * a DataSet should be returned with an x tag range that contains start and end, with a + * resolution finer than that requested. + * + * @param start beginning of range for the request. + * @param end end of the range for the request. + * @param resolution the resolution requirement for the reqeust. null may be used to request the finest resolution available or intrinic resolution. + */ + protected abstract DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException; + + /** + * @return the x units of the DataSetDescriptor that parameterize the data. This is used to identify dataSet requests. + */ + public abstract Units getXUnits(); + + /** + * Requests that a dataSet be loaded, and that the dataSet be returned via a DataSetUpdate event. + * The @param lockObject is an object that is dependent on the load, for example, the DasCanvas, + * and will be passed in to the request processor. If the dataSet is available in interactive time, + * then the dataSetUpdate may be fired on the same thread as the request is made. + */ + public void requestDataSet(final Datum start, final Datum end, final Datum resolution, + final ProgressMonitor monitor, Object lockObject) { + + Runnable request = new Runnable() { + + public void run() { + logger.fine("requestDataSet: " + start + " " + end + " " + resolution); + try { + DataSet ds = getDataSet(start, end, resolution, monitor); + if (ds == null) { + throw new NoDataInIntervalException(new DatumRange(start, end).toString()); + } + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, ds); + dsue.setMonitor(monitor); + fireDataSetUpdateEvent(dsue); + } catch (DasException e) { + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e); + dsue.setMonitor(monitor); + fireDataSetUpdateEvent(dsue); + } + } + + public String toString() { + return "loadDataSet " + new DatumRange(start, end); + } + }; + logger.fine("submit data request"); + + CacheTag tag = new CacheTag(start, end, resolution); + if (dataSetCache.haveStored(this, tag)) { + request.run(); + } else { + RequestProcessor.invokeLater(request, lockObject); + } + + } + + /** + * Request the dataset, and the dataset is returned only to the listener. + * + * @param lockObject object that is waiting for the result of this load, used to block other tasks which use that object. + */ + public void requestDataSet(final Datum start, final Datum end, final Datum resolution, + final ProgressMonitor monitor, Object lockObject, final DataSetUpdateListener listener) { + + if (lockObject == null) { + lockObject = listener; + } + + if (this instanceof ConstantDataSetDescriptor) { + try { + DataSet ds = getDataSet(null, null, null, null); + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)this, ds); + dsue.setMonitor(monitor); + } catch (DasException e) { + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e); + dsue.setMonitor(monitor); + listener.dataSetUpdated(dsue); + } + } else { + Runnable request = new Runnable() { + + public void run() { + logger.fine("request data from dsd: " + start + " " + end + " " + resolution); + try { + DataSet ds = getDataSet(start, end, resolution, monitor); + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, ds); + dsue.setMonitor(monitor); + listener.dataSetUpdated(dsue); + } catch (DasException e) { + DataSetUpdateEvent dsue = new DataSetUpdateEvent((Object)DataSetDescriptor.this, e); + dsue.setMonitor(monitor); + listener.dataSetUpdated(dsue); + } + } + + public String toString() { + return "loadDataSet " + new DatumRange(start, end); + } + }; + RequestProcessor.invokeLater(request, lockObject); + } + + } + + /** + * Retrieve the dataset for this interval and resolution. The contract for this function is that + * identical start,end,resolution parameters will yield an identical dataSet, except for when an + * DataSetUpdate has been fired in the meantime. + * + * null for the data resolution indicates that the data should be returned at its "intrinsic resolution" + * if such a resolution exists. + */ + public DataSet getDataSet(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + + CacheTag tag = null; + if (defaultCaching) { + tag = new CacheTag(start, end, resolution); + DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG).fine("getDataSet " + this + " " + tag); + } + + if (defaultCaching && dataSetCache.haveStored(this, tag)) { + return dataSetCache.retrieve(this, tag); + } else { + try { + DataSet ds = getDataSetImpl(start, end, resolution, monitor); + if (ds != null) { + if (ds.getProperty("cacheTag") != null) { + tag = (CacheTag) ds.getProperty("cacheTag"); + } + if (defaultCaching) { + dataSetCache.store(this, tag, ds); + } + } + return ds; + } catch (DasException e) { + throw e; + } finally { + monitor.finished(); + } + } + } + + /** + * clear any state that's developed, in particular any data caches. Note + * this currently deletes all cached datasets, regardless of the DataSetDescriptor + * that produced them. + */ + public void reset() { + dataSetCache.reset(); + } + + /** + * defaultCaching means that the abstract DataSetDescriptor is allowed to handle + * repeat getDataSet calls by returning a cached dataset. If a dataSetUpdate event + * is thrown, the defaultCache is reset. + * + * Use caution when using this. Note that caching may only be turned off + * with this call. + */ + public void setDefaultCaching(boolean value) { + if (value == false) { + defaultCaching = value; + } + } + + public void addDataSetUpdateListener(DataSetUpdateListener listener) { + if (listenerList == null) { + listenerList = new EventListenerList(); + } + listenerList.add(DataSetUpdateListener.class, listener); + } + + public void removeDataSetUpdateListener(DataSetUpdateListener listener) { + if (listenerList == null) { + listenerList = new EventListenerList(); + } + listenerList.remove(DataSetUpdateListener.class, listener); + } + + protected void fireDataSetUpdateEvent(DataSetUpdateEvent event) { + if (listenerList == null) { + return; + } + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == DataSetUpdateListener.class) { + ((DataSetUpdateListener) listeners[i + 1]).dataSetUpdated(event); + } + } + } + + /** + * @return the string that uniquely identifies this dataset. + */ + public String getDataSetID() { + return this.dataSetID; + } + private static final Pattern CLASS_ID = Pattern.compile("class:([a-zA-Z0-9_\\.]+)(?:\\?(.*))?"); + private static final Pattern EXEC_ID = Pattern.compile("exec:(.+)\\?(.+)"); + private static final Pattern NAME_VALUE = Pattern.compile("([_0-9a-zA-Z%+.-]+)=([_0-9a-zA-Z%+.-]+)"); + + /** + * creates a DataSetDescriptor for the given identification string. The identification + * string is a URL-like string, for example http://www-pw.physics.uiowa.edu/das/das2Server?das2_1/cluster/wbd/r_wbd_dsn_cfd&spacecraft%3Dc1%26antenna%3DEy + * The protocol of the string indicates how the DataSetDescriptor is to be constructed, and presently + * there are: + *
    +     *   http     a das2Server provides the specification of the DataSetDescriptor, and a
    +     *            StreamDataSetDescriptor is created.
    +     *   class    refers to a loadable java class that is an instanceof DataSetDescriptor and
    +     *            has the method newDataSetDescriptor( Map params )
    +     *
    + * Note that DataSetDescriptors are stateless, the same DataSetDescriptor object + * may be returned to multiple clients. + */ + public static DataSetDescriptor create(final String dataSetID) throws DasException { + java.util.regex.Matcher classMatcher = CLASS_ID.matcher(dataSetID); + java.util.regex.Matcher execMatcher = EXEC_ID.matcher(dataSetID); + DataSetDescriptor result; + if (classMatcher.matches()) { + result = createFromClassName(dataSetID, classMatcher); + } else if (execMatcher.matches()) { + result = createFromExecutable(dataSetID, execMatcher); + } else { + try { + result = createFromServerAddress(new URL(dataSetID)); + //result = DasServer.createDataSetDescriptor(new URL(dataSetID)); + } catch (MalformedURLException mue) { + throw new DasIOException(mue.getMessage()); + } + } + result.dataSetID = dataSetID; + return result; + } + + private static DataSetDescriptor createFromServerAddress(final URL url) throws DasException { + DasServer server = DasServer.create(url); + StreamDescriptor sd = server.getStreamDescriptor(url); + return new StreamDataSetDescriptor(sd, server.getStandardDataStreamSource(url)); + } + private static DataSetDescriptor createFromExecutable(String dataSetID, Matcher matcher) { + String executable = matcher.group(1); + String[] paramPatterns = matcher.group(2).split("&"); + return new ExecutableDataSetDescriptor(executable, paramPatterns); + } + + private static DataSetDescriptor createFromClassName(final String dataSetID, final Matcher matcher) throws DasException { + try { + String className = matcher.group(1); + String argString = matcher.group(2); + Map argMap = argString == null ? Collections.EMPTY_MAP : URLBuddy.parseQueryString(argString); + Class dsdClass = Class.forName(className); + Method method = dsdClass.getMethod("newDataSetDescriptor", new Class[]{java.util.Map.class}); + if (!Modifier.isStatic(method.getModifiers())) { + throw new NoSuchDataSetException("newDataSetDescriptor must be static"); + } + return (DataSetDescriptor) method.invoke(null, new Object[]{argMap}); + } catch (ClassNotFoundException cnfe) { + DataSetDescriptorNotAvailableException dsdnae = + new DataSetDescriptorNotAvailableException(cnfe.getMessage()); + dsdnae.initCause(cnfe); + throw dsdnae; + } catch (NoSuchMethodException nsme) { + DataSetDescriptorNotAvailableException dsdnae = + new DataSetDescriptorNotAvailableException(nsme.getMessage()); + dsdnae.initCause(nsme); + throw dsdnae; + } catch (InvocationTargetException ite) { + DataSetDescriptorNotAvailableException dsdnae = + new DataSetDescriptorNotAvailableException(ite.getTargetException().getMessage()); + dsdnae.initCause(ite.getTargetException()); + throw dsdnae; + } catch (IllegalAccessException iae) { + DataSetDescriptorNotAvailableException dsdnae = + new DataSetDescriptorNotAvailableException(iae.getMessage()); + dsdnae.initCause(iae); + throw dsdnae; + } + } + + protected void setProperties(Map properties) { + this.properties.putAll(properties); + } + + /** + * Returns the value of the property with the specified name + * + * @param name The name of the property requested + * @return The value of the requested property as an Object + */ + public Object getProperty(String name) { + return properties.get(name); + } + + public javax.swing.Icon getListIcon() { + return null; + } + + public String getListLabel() { + return this.dataSetID; + } + + /** + * @return the DataSetCache object used to store cached copies of the + * DataSets created by this object. + */ + public DataSetCache getDataSetCache() { + return this.dataSetCache; + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSetRebinner.java b/dasCore/src/main/java/org/das2/dataset/DataSetRebinner.java new file mode 100755 index 000000000..c1f9f30cf --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSetRebinner.java @@ -0,0 +1,49 @@ +/* File: DataSetRebinner.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 5, 2003, 10:28 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import java.util.Map; +import org.das2.DasException; + +/** + * + * @author Edward West + */ +public interface DataSetRebinner { + + /** Rebin a dataset into a 2-D grid + * + * Datasets are immutable, so you can't alter them in place + * @param ds The dataset to rebin + * @param x bin size and spacing information for the X direction + * @param y bin size and spacing information for the Y direction + * @param override a map of dataset properties to override, may be null. + * property names and values for this map match those given in DataSet.java + * @return a new dataset + * @throws IllegalArgumentException + * @throws DasException + */ + DataSet rebin(DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override) + throws IllegalArgumentException, DasException; +} diff --git a/dasCore/src/main/java/org/das2/dataset/DataSetStreamProducer.java b/dasCore/src/main/java/org/das2/dataset/DataSetStreamProducer.java new file mode 100644 index 000000000..07faaf5d3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DataSetStreamProducer.java @@ -0,0 +1,294 @@ +/* + * DataSetStreamProducer.java + * + * Created on October 12, 2007, 10:11 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import org.das2.datum.UnitsUtil; +import org.das2.stream.DataTransferType; +import org.das2.stream.PacketDescriptor; +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.StreamMultiYDescriptor; +import org.das2.stream.StreamProducer; +import org.das2.stream.StreamXDescriptor; +import org.das2.stream.StreamYScanDescriptor; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; +import java.util.Iterator; +import java.util.Map; + +/** + * Configurable class for serializing a DataSet into a das2Stream. This class + * handles both VectorDataSets and TableDataSets, and uses java beans properties + * to control how the stream is produced. This code subsumes the functionality + * of TableUtil.dumpToDas2Stream and VectorUtil.dumpToDas2Stream. + * + * @author jbf + */ +public class DataSetStreamProducer { + + /** Creates a new instance of DataSetStreamProducer */ + public DataSetStreamProducer() { + } + + /** + * convenient method for writing to an OutputStream. Simply + * uses Channels.newChannel to create a WritableByteChannel. + */ + public void writeStream( OutputStream out ) { + this.writeStream( Channels.newChannel(out) ); + } + + /** + * writes the stream to the Channel. + */ + public void writeStream( WritableByteChannel out ) { + if ( dataSet instanceof VectorDataSet ) { + writeVectorDataSetStream( out ); + } else { + writeTableDataSetStream( out ); + } + } + + private void writeTableDataSetStream( WritableByteChannel out ) { + TableDataSet tds= (TableDataSet)dataSet; + + if (tds.getXLength() == 0) { + try { + out.close(); + } catch (IOException ioe) { + //Do nothing. + } + return; + } + try { + StreamProducer producer = new StreamProducer(out); + StreamDescriptor sd = new StreamDescriptor(); + + Map properties= tds.getProperties(); + if ( properties!=null) { + for ( Iterator i= properties.keySet().iterator(); i.hasNext(); ) { + String key= (String)i.next(); + sd.setProperty(key, properties.get(key)); + } + } + + if ( compressed ) { + sd.setCompression( "deflate" ); + } + + producer.streamDescriptor(sd); + + DataTransferType xTransferType; + DataTransferType yTransferType; + + if ( asciiTransferTypes ) { + if ( UnitsUtil.isTimeLocation(tds.getXUnits()) ) { + xTransferType= DataTransferType.getByName("time24"); + } else { + xTransferType= DataTransferType.getByName("ascii10"); + } + yTransferType= DataTransferType.getByName("ascii10"); + } else { + xTransferType= DataTransferType.getByName("sun_real8"); + yTransferType= DataTransferType.getByName("sun_real4"); + } + + PacketDescriptor pd = new PacketDescriptor(); + + StreamXDescriptor xDescriptor = new StreamXDescriptor(); + xDescriptor.setUnits(tds.getXUnits()); + xDescriptor.setDataTransferType(xTransferType); + + pd.setXDescriptor(xDescriptor); + + String[] planeIds= DataSetUtil.getAllPlaneIds(tds); + + DatumVector[] yValues = new DatumVector[planeIds.length]; + + for ( int j=0; j2 ) { + Units units= table.getXUnits(); + double min= table.getXTagDouble(1,units) - table.getXTagDouble( 0,units) ; + for ( int i=2; i(-(insertion point) - 1). (See Arrays.binarySearch) + */ + public static int xTagBinarySearch( DataSet ds, Datum datum, int low, int high ) { + Units units= datum.getUnits(); + double key= datum.doubleValue(units); + while (low <= high) { + int mid = (low + high) >> 1; + double midVal = ds.getXTagDouble(mid,units); + int cmp; + if (midVal < key) { + cmp = -1; // Neither val is NaN, thisVal is smaller + } else if (midVal > key) { + cmp = 1; // Neither val is NaN, thisVal is larger + } else { + long midBits = Double.doubleToLongBits(midVal); + long keyBits = Double.doubleToLongBits(key); + cmp = (midBits == keyBits ? 0 : // Values are equal + (midBits < keyBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) + 1)); // (0.0, -0.0) or (NaN, !NaN) + } + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + /** Searches the xtags of the specified data set for the specified datum + * using the binary search algorithm. + * + * @param ds the data set to search + * @param datum the key to search for + * @return index of the search datum if it exists as an xtag in the + * data set; otherwise the insertion point. The insertion point is + * is defined as -1.0 if the datum is less that the first xtag + * in the data set, ds.getXLength() if the datum is + * larger than the last xtag, or + * (i + (datum - x0) / (x1 - x0) ) + * where x0 is the largest xtag smaller than datum, + * x1 is the smallest xtag larger than datum, and + * i is the index of x0. + */ + public static double columnFindex( DataSet ds, Datum datum ) { + int result = xTagBinarySearch( ds, datum, 0, ds.getXLength()-1); + + if (result >= -1) { + return result; + } + + result = ~result; + + if (result == ds.getXLength()) { + return result; + } + else { + Units u = ds.getXUnits(); + int lowerIndex = result-1; + double lower = ds.getXTagDouble(lowerIndex, u); + double upper = ds.getXTagDouble(result, u); + double key = datum.doubleValue(u); + return lowerIndex + (key - lower) / (upper - lower); + } + } + + public static int closestColumn( DataSet table, Datum datum ) { + int result= xTagBinarySearch( table, datum, 0, table.getXLength()-1 ); + if (result == -1) { + result = 0; //insertion point is 0 + } else if (result < 0) { + result= ~result; // usually this is the case + if ( result >= table.getXLength()-1 ) { + result= table.getXLength()-1; + } else { + double x= datum.doubleValue( datum.getUnits() ); + double x0= table.getXTagDouble(result-1, datum.getUnits() ); + double x1= table.getXTagDouble(result, datum.getUnits() ); + result= ( ( x-x0 ) / ( x1 - x0 ) < 0.5 ? result-1 : result ); + } + } + return result; + } + + public static int closestColumn( DataSet table, double x, Units units ) { + return closestColumn( table, units.createDatum(x) ); + } + + /** + * finds the dataset column closest to the value, starting the search at guessIndex. + * Handles monotonically increasing or decreasing tags. + * @param table + * @param xdatum + * @param guessIndex + * @return + */ + public static int closestColumn( DataSet table, Datum xdatum, int guessIndex ) { + int monotonicDir= 1; + int result= guessIndex; + int len= table.getXLength(); + if ( len==1 ) return 0; + + if ( result>=len-1 ) result=len-2; + if ( result<0 ) result=0; + + Units units= xdatum.getUnits(); + + if ( table.getXTagDouble(result, units) > table.getXTagDouble(result+1, units ) ) { + monotonicDir= -1; + } + + double x= xdatum.doubleValue(units); + if ( monotonicDir==1 ) { + while ( result<(len-1) && table.getXTagDouble(result,units) < x ) result++; + while ( result>0 && table.getXTagDouble(result,units) > x ) result--; + } else { + while ( result<(len-1) && table.getXTagDouble(result,units) > x ) result++; + while ( result>0 && table.getXTagDouble(result,units) < x ) result--; + } + // result should now point to the guy to the left of x, unless x>the last guy or 0 && ds.getXTagDatum(i).ge(datum) ) { + return i-1; + } else { + return i; + } + } + + /** + * returns the first column that is after the given datum. Note the + * if the datum identifies (==) an xtag, then the previous column is + * returned. + */ + public static int getNextColumn( DataSet ds, Datum datum ) { + int i= closestColumn( ds, datum ); + // TODO: consider the virtue of le + if ( i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import org.das2.datum.UnitsConverter; +import org.das2.system.DasLogger; +import java.io.PrintStream; + +import java.util.*; +import java.text.MessageFormat; +import java.util.logging.Logger; + +/** + * + * @author Edward West + */ +public final class DefaultTableDataSet extends AbstractTableDataSet { + + /** + * Description of indices + * From left to right: + * 1. Index of the plane. 0 is primary + * 2. Index of the yscan within the dataset. + * 3. Index of the zValue within the yscan. + */ + private double[][][] tableData; + + private Units[] zUnits; + + private double[][] yTags; + final int tableCount; + + private int[] tableOffsets; + + private String[] planeIDs; + + private static final Logger logger= DasLogger.getLogger(); + + /** Creates a new instance of DefaultTableDataSet for tables where the + * table geometry changes, and the DataSet contains multiple planes. + */ + public DefaultTableDataSet(double[] xTags, Units xUnits, + double[][] yTags, Units yUnits, + double[][][] zValues, Units zUnits, + Map zValuesMap, Map zUnitsMap, + Map properties) { + super(xTags, xUnits, yUnits, zUnits, properties); + if (zValuesMap == null ^ zUnitsMap == null) { + throw new IllegalArgumentException("zValuesMap == null ^ zUnitsMap == null"); + } + if (zValuesMap != null && !zValuesMap.keySet().equals(zUnitsMap.keySet())) { + throw new IllegalArgumentException("mismatched keySets for zValuesMap and zUnitsMap"); + } + if (zValuesMap != null) { + for (Iterator it = zValuesMap.keySet().iterator(); it.hasNext();) { + if (!(it.next() instanceof String)) { + throw new IllegalArgumentException("Non-String key found in zValuesMap"); + } + } + } + int planeCount = 1 + (zValuesMap == null ? 0 : zValuesMap.size()); + this.tableData = new double[planeCount][][]; + this.tableData[0] = flatten(zValues); + this.yTags = copy(yTags); + this.tableCount= yTags.length; + this.zUnits = new Units[planeCount]; + this.zUnits[0] = zUnits; + this.planeIDs = new String[planeCount]; + this.planeIDs[0] = ""; + tableOffsets = computeTableOffsets(zValues, yTags); + if (zValuesMap != null) { + int index = 1; + for (Iterator it = new TreeMap(zValuesMap).entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry)it.next(); + String key = (String)entry.getKey(); + double[][][] d = (double[][][])entry.getValue(); + double[][] f = flatten(d); + this.planeIDs[index] = key; + this.tableData[index] = f; + this.zUnits[index] = (Units)zUnitsMap.get(key); + } + } + } + + private double[][] flatten(double[][][] d) { + int sum = 0; + for(double[][] d1 : d){ + sum += d1.length; + } + double[][] flat = new double[sum][]; + int offset = 0; + for(double[][] d2 : d){ + for(double[] d1 : d2){ + flat[offset] = (double[]) d1.clone(); + offset++; + } + } + return flat; + } + + public static DefaultTableDataSet createSimple( double[] xTags, double[] yTags, double[][] zValues ) { + int nx= zValues.length; + int ny= zValues[0].length; + if ( xTags.length!=nx ) { + throw new IllegalArgumentException("xTags ("+xTags.length+") don't match zValues' first dimension ("+nx+","+ny+")." ); + } + if ( yTags.length!=ny ) { + throw new IllegalArgumentException("yTags ("+yTags.length+") don't match zValues' first dimension ("+nx+","+ny+")." ); + } + return new DefaultTableDataSet( xTags, Units.dimensionless, yTags, Units.dimensionless, zValues, Units.dimensionless, new HashMap() ); + } + + /** Creates a DefaultTableDataSet when the table geometry changes. */ + public DefaultTableDataSet(double[] xTags, Units xUnits, + double[] yTags, Units yUnits, + double[][] zValues, Units zUnits, + Map properties) { + this(xTags, xUnits, new double[][]{yTags}, yUnits, new double[][][]{zValues}, zUnits, + null, null, properties); + } + + DefaultTableDataSet(double[] xTags, Units xUnits, + double[][] yTags, Units yUnits, + double[][][] zValues, Units[] zUnits, + String[] planeIDs, int[] tableOffsets, + Map properties) { + super(xTags, xUnits, yUnits, zUnits[0], properties); + this.yTags = yTags; + this.tableCount= yTags.length; + this.tableData = zValues; + this.zUnits = zUnits; + this.planeIDs = planeIDs; + this.tableOffsets = tableOffsets; + } + + DefaultTableDataSet(double[] xTags, Units xUnits, + double[][] yTags, Units yUnits, + double[][][] zValues, Units[] zUnits, + String[] planeIDs, int[] tableOffsets, + Map properties, List tableProperties ) { + super(xTags, xUnits, yUnits, zUnits[0], properties); + this.yTags = yTags; + this.tableCount= yTags.length; + this.tableData = zValues; + this.zUnits = zUnits; + this.planeIDs = planeIDs; + this.tableOffsets = tableOffsets; + this.tableProperties= tableProperties; + } + + private static double[][][] copy(double[][][] d) { + double[][][] copy = new double[d.length][][]; + for (int index = 0; index < d.length; index++) { + copy[index] = copy(d[index]); + } + return copy; + } + + private static double[][] copy(double[][] d) { + double[][] copy = new double[d.length][]; + for (int index = 0; index < d.length; index++) { + copy[index] = (double[])d[index].clone(); + } + return copy; + } + + private static int[] computeTableOffsets(double[][][] tableData, double[][] yTags) { + int[] tableOffsets = new int[tableData.length]; + int currentOffset = 0; + for (int index = 0; index < tableOffsets.length; index++) { + tableOffsets[index] = currentOffset; + currentOffset += tableData[index].length; + } + return tableOffsets; + } + + @Override + public Datum getDatum(int i, int j) { + int table = tableOfIndex(i); + int yLength = yTags[table].length; + if (i < 0 || i >= tableData[0].length) { + IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException + ("x index is out of bounds: " + i + " xLength: " + getXLength()); + logger.throwing(DefaultTableDataSet.class.getName(), + "getDatum(int,int)", ioobe); + throw ioobe; + } + if (j < 0 || j >= this.yTags[table].length) { + IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException + ("y index is out of bounds: " + i + " yLength(" + table + "): " + getYLength(table)); + logger.throwing(DefaultTableDataSet.class.getName(), + "getDatum(int,int)", ioobe); + throw ioobe; + } + try { + double value = tableData[0][i][j]; + return Datum.create(value, zUnits[0]); + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw aioobe; //This is just here so developers can put a breakpoint + //here if ArrayIndexOutOfBoundsException is thrown. + } + } + + @Override + public DatumVector getScan(int i) { + int table = tableOfIndex(i); + if (i < 0 || i >= tableData[0].length) { + IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException + ("x index is out of bounds: " + i + " xLength: " + getXLength()); + logger.throwing(DefaultTableDataSet.class.getName(), + "getDatum(int,int)", ioobe); + throw ioobe; + } + try { + double[] values = tableData[0][i]; + return DatumVector.newDatumVector(values, 0, getYLength(table), zUnits[0]); + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw aioobe; //This is just here so developers can put a breakpoint + //here if ArrayIndexOutOfBoundsException is thrown. + } + } + + @Override + public double getDouble(int i, int j, Units units) { + int table; + int yLength; + + if (i < 0 || i >= getXLength()) { + throw new IndexOutOfBoundsException("i: " + i + ", xLength: " + getXLength()); + } + + table = tableOfIndex(i); + yLength = yTags[table].length; + + if (j < 0 || j >= yLength) { + throw new IndexOutOfBoundsException("j: " + j + ", yLength: " + yLength); + } + double value = tableData[0][i][j]; + if (units == getZUnits()) { + return value; + } + return zUnits[0].getConverter(units).convert(value); + } + + @Override + public double[] getDoubleScan(int i, Units units) { + int table = tableOfIndex(i); + int yLength = yTags[table].length; + double[] values = tableData[0][i]; + double[] retValues = new double[yLength]; + if (units == getZUnits()) { + System.arraycopy(values, 0, retValues, 0, yLength); + } else { + UnitsConverter uc = zUnits[0].getConverter(units); + for (int j = 0; j < yLength; j++) { + retValues[j] = uc.convert(values[j]); + } + } + return retValues; + } + + @Override + public int getInt(int i, int j, Units units) { + return (int)Math.round(getDouble(i, j, units)); + } + + @Override + public DataSet getPlanarView(String planeID) { + int planeIndex = -1; + for (int index = 0; index < planeIDs.length; index++) { + if (planeIDs[index].equals(planeID)) { + planeIndex = index; + } + } + if (planeIndex == -1) { + return null; + } else { + return new PlanarViewDataSet(planeIndex); + } + } + + @Override + public String[] getPlaneIds() { + String[] result= new String[planeIDs.length]; + System.arraycopy( planeIDs, 0, result, 0, planeIDs.length ); + return result; + } + + @Override + public int getYLength(int table) { + return yTags[table].length; + } + + @Override + public Datum getYTagDatum(int table, int j) { + double value = yTags[table][j]; + return Datum.create(value, getYUnits()); + } + + @Override + public double getYTagDouble(int table, int j, Units units) { + double value = yTags[table][j]; + if (units == getYUnits()) { + return value; + } + return getYUnits().getConverter(units).convert(value); + } + + @Override + public int getYTagInt(int table, int j, Units units) { + return (int)Math.round(getYTagDouble(table, j, units)); + } + + @Override + public DatumVector getYTags(int table) { + return DatumVector.newDatumVector(yTags[table], 0, getYLength(table), getYUnits()); + } + + @Override + public int tableCount() { + return tableCount; + } + + @Override + public int tableEnd(int table) { + if (table == tableOffsets.length - 1) { + return getXLength(); + } else { + return tableOffsets[table + 1]; + } + } + + @Override + public int tableOfIndex(int i) { + if ( yTags.length>5 ) { + int table = Arrays.binarySearch(tableOffsets, i); + if (i >= getXLength()) { + throw new IndexOutOfBoundsException(i + " > " + getXLength()); + } + if (table < 0) { + table = (~table) - 1; + } + return table; + } else { + int result= tableCount-1; + while ( result>=0 && tableOffsets[result]>i ) result--; + return result; + } + } + + @Override + public int tableStart(int table) { + return tableOffsets[table]; + } + + public void dump(java.io.PrintStream out) { + MessageFormat tableFormat = new MessageFormat( + " {0,number,00}. Y Length: {1,number,000}, Start: {2,number,000}, End: {3,number,000}"); + MessageFormat planeFormat = new MessageFormat(" ID: {0}, Z Units: ''{1}''"); + out.println("============================================================"); + out.println(getClass().getName()); + out.println(" X Length: " + getXLength()); + out.println(" X Units: '" + getXUnits() + "'"); + out.print(" X Tags:"); + for (int index = 0; index < getXLength(); index ++) { + out.print(" "); + out.print(getXTagDouble(index, getXUnits())); + } + out.println(); + out.println(" Y Units: '" + getYUnits() + "'"); + out.println(" Z Units: '" + getZUnits() + "'"); + out.println(" Table Count: " + tableCount()); + Object[] args = new Object[4]; + for (int table = 0; table < tableCount(); table++) { + args[0] = table; + args[1] = yTags[table].length; + args[2] = tableStart(table); + args[3] = tableEnd(table); + String str = tableFormat.format(args); + out.println(str); + out.print(" Y Tags:"); + for (int index = 0; index < getYLength(table); index ++) { + out.print(" "); + out.print(getYTagDouble(table, index, getXUnits())); + } + out.println(); + out.println(" Z Values:"); + for (int j = 0; j < getYLength(table); j++) { + out.print(" "); + for (int i = tableStart(table); i < tableEnd(table); i++) { + out.print(getDouble(i, j, getZUnits())); + out.print("\t"); + } + out.println(); + } + } + out.println(" Plane Count: " + planeIDs.length); + for (int plane = 1; plane < planeIDs.length; plane++) { + args[0] = planeIDs[plane]; + args[1] = zUnits[plane]; + String str = planeFormat.format(args); + out.println(str); + } + out.println("============================================================"); + } + + @Override + public String toString() { + return "DefaultTableDataSet "+TableUtil.toString(this); + } + + public void printDebugInfo(PrintStream out) { + out.println("xLength: " + getXLength()); + out.println("tableCount: " + tableCount()); + for (int table = 0; table < tableCount(); table++) { + out.println("tableStart(" + table + "): " + tableStart(table)); + out.println("yLength: " + getYLength(table)); + for (int i = tableStart(table); i < tableEnd(table); i++) { + out.println(i + ": " + tableData[0][i].length); + } + out.println("tableEnd(" + table + "): " + tableEnd(table)); + } + } + + private final class PlanarViewDataSet extends AbstractDataSet.ViewDataSet implements TableDataSet { + + private final int index; + + private PlanarViewDataSet(int index) { + DefaultTableDataSet.this.super(); + this.index = index; + } + + @Override + public DataSet getPlanarView(String planeID) { + return planeID.equals("") ? this : null; + } + + // @Override + // public DataSet getPlanarView(int nPlaneIdx){ + // if(nPlaneIdx > ); + //return null; + // } + + @Override + public String[] getPlaneIds() { + return new String[0]; + } + + @Override + public Datum getDatum(int i, int j) { + int table = tableOfIndex(i); + int yLength = yTags[table].length; + double value = tableData[index][i][j]; + return Datum.create(value, zUnits[index]); + } + + @Override + public double getDouble(int i, int j, Units units) { + int table = tableOfIndex(i); + int yLength = yTags[table].length; + double value = tableData[index][i][j]; + return zUnits[index].getConverter(units).convert(value); + } + + @Override + public double[] getDoubleScan(int i, Units units) { + int table = tableOfIndex(i); + int yLength = yTags[table].length; + double[] values = tableData[index][i]; + double[] retValues = new double[yLength]; + if (units == getZUnits()) { + System.arraycopy(values, 0, retValues, 0, yLength); + } else { + UnitsConverter uc = zUnits[index].getConverter(units); + for (int j = 0; j < yLength; j++) { + retValues[j] = uc.convert(values[j]); + } + } + return retValues; + } + + @Override + public DatumVector getScan(int i) { + int table = tableOfIndex(i); + if (i < 0 || i >= tableData[0].length) { + IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException + ("x index is out of bounds: " + i + " xLength: " + getXLength()); + logger.throwing(DefaultTableDataSet.class.getName(), + "getDatum(int,int)", ioobe); + throw ioobe; + } + try { + double[] values = tableData[index][i]; + return DatumVector.newDatumVector(values, 0, getYLength(table), zUnits[index]); + } catch (ArrayIndexOutOfBoundsException aioobe) { + throw aioobe; //This is just here so developers can put a breakpoint + //here if ArrayIndexOutOfBoundsException is thrown. + } + } + + @Override + public int getInt(int i, int j, Units units) { + return (int)Math.round(getDouble(i, j, units)); + } + + @Override + public VectorDataSet getXSlice(int i) { + return new XSliceDataSet(this, i); + } + + @Override + public int getYLength(int table) { + return DefaultTableDataSet.this.getYLength(table); + } + + @Override + public VectorDataSet getYSlice(int j, int table) { + return new YSliceDataSet(this, j, table); + } + + @Override + public Datum getYTagDatum(int table, int j) { + return DefaultTableDataSet.this.getYTagDatum(table, j); + } + + @Override + public double getYTagDouble(int table, int j, Units units) { + return DefaultTableDataSet.this.getYTagDouble(table, j, units); + } + + @Override + public int getYTagInt(int table, int j, Units units) { + return DefaultTableDataSet.this.getYTagInt(table, j, units); + } + + @Override + public Units getZUnits() { + return DefaultTableDataSet.this.zUnits[index]; + } + + @Override + public int tableCount() { + return DefaultTableDataSet.this.tableCount(); + } + + @Override + public int tableEnd(int table) { + return DefaultTableDataSet.this.tableEnd(table); + } + + @Override + public int tableOfIndex(int i) { + return DefaultTableDataSet.this.tableOfIndex(i); + } + + @Override + public int tableStart(int table) { + return DefaultTableDataSet.this.tableStart(table); + } + + @Override + public Object getProperty(String name) { + Object result= DefaultTableDataSet.this.getProperty(planeIDs[index] + "." + name); + if ( result==null ) result= DefaultTableDataSet.this.getProperty(name); + return result; + } + + @Override + public Object getProperty(int table, String name) { + Object result= DefaultTableDataSet.this.getProperty(table,planeIDs[index] + "." + name); + if ( result==null ) result= DefaultTableDataSet.this.getProperty(table,name); + return result; + } + + @Override + public DatumVector getYTags(int table) { + return DefaultTableDataSet.this.getYTags(table); + } + + @Override + public String toString() { + return TableUtil.toString(this); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/DefaultVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/DefaultVectorDataSet.java new file mode 100755 index 000000000..c30170c34 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/DefaultVectorDataSet.java @@ -0,0 +1,237 @@ +/* File: DefaultVectorDataSet.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 27, 2003, 11:18 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; + +import java.util.*; + +/** + * + * @author Edward West + */ +public final class DefaultVectorDataSet extends AbstractVectorDataSet implements DataSet, VectorDataSet { + + private double[][] yValues; + + private String[] planeIDs; + + private Units[] yUnits; + + /** Creates a new instance of DefaultVectorDataSet + * @param xTags array of double x tag values + * @param xUnits units for the x tags + * @param yValues array of double y values + * @param yUnits untis for the y values + * @param properties map of String property names to their values + */ + public DefaultVectorDataSet(double[] xTags, Units xUnits, + double[] yValues, Units yUnits, + Map properties) { + this(xTags, xUnits, yValues, yUnits, null, null, properties); + } + + /** Creates a new instance of DefaultVectorDataSet + * The keys for the properties, yValuesMap, and unitsMap parameter must + * consist solely of String values. If any of these key sets contain + * non-string values an IllegalArgumentException will be thrown. + * The key set of the yUnitsMap parameter must match exactly the key set + * of the yValuesMap. If their key sets do not exactly match, an + * IllegalArgumentException will be thrown. + * @param xTags array of double x tag values + * @param xUnits units for the x tags + * @param yValues array of double y values + * @param yUnits untis for the y values + * @param yValuesMap map of String plane IDs to their y values + * @param properties map of String property names to their values + */ + public DefaultVectorDataSet(double[] xTags, Units xUnits, + double[] yValues, Units yUnits, + Map yValuesMap, Map yUnitsMap, + Map properties) { + super(xTags, xUnits, yUnits, properties); + if (yValuesMap == null ^ yUnitsMap == null) { + throw new IllegalArgumentException("yValuesMap == null ^ yUnitsMap == null"); + } + if ( yValuesMap!=null ) { + if (!yValuesMap.keySet().equals(yUnitsMap.keySet())) { + throw new IllegalArgumentException("mismatched keySets for yValuesMap and yUnitsMap"); + } + for (Iterator it = yValuesMap.keySet().iterator(); it.hasNext();) { + if (!(it.next() instanceof String)) { + throw new IllegalArgumentException("Non-String key found in yValuesMap"); + } + } + } + int planeCount = 1 + (yValuesMap == null ? 0 : yValuesMap.size()); + this.yValues = new double[planeCount][]; + this.yValues[0] = (double[])yValues.clone(); + this.planeIDs = new String[planeCount]; + this.planeIDs[0] = ""; + this.yUnits = new Units[planeCount]; + this.yUnits[0] = yUnits; + if (yValuesMap != null) { + int index = 1; + for (Iterator it = new TreeMap(yValuesMap).entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry)it.next(); + String id = (String)entry.getKey(); + double[] values = (double[])entry.getValue(); + planeIDs[index] = id; + this.yValues[index] = (double[])values.clone(); + this.yUnits[index] = (Units)yUnitsMap.get(id); + } + } + } + + /** Creates a DefaultVectorData set without copying or verifying the + * data and units supplied. + */ + DefaultVectorDataSet(double[] xTags, Units xUnits, + double[][] yValues, Units[] yUnits, + String[] planeIDs, Map properties) { + super(xTags, xUnits, yUnits[0], properties); + this.yValues = yValues; + this.yUnits = yUnits; + this.planeIDs = planeIDs; + } + + /** Returns the Y value for the given index into the x tags as a + * Datum. + * @param i index of the x tag for the requested value. + * @return the value at index location i as a Datum + */ + public Datum getDatum(int i) { + return Datum.create(yValues[0][i], getYUnits()); + } + + /** Returns the Y value for the given index into the x tags as a + * double with the given units. + * @param i index of the x tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location i as a double. + */ + public double getDouble(int i, Units units) { + if (yUnits[0].isFill(yValues[0][i])) { + return units.getFillDouble(); + } + return getYUnits().getConverter(units).convert(yValues[0][i]); + } + + /** Returns the Y value for the given index into the x tags as a + * int with the given units. + * @param i index of the x tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location i as a int. + */ + public int getInt(int i, Units units) { + return (int)Math.round(getDouble(i, units)); + } + + /** Returns a DataSet with the specified view as the primary + * view. + * @param planeID the String id of the requested plane. + * @return the specified view, as a DataSet + */ + public DataSet getPlanarView(String planeID) { + int index = -1; + for (int i = 0; i < planeIDs.length; i++) { + if (planeIDs[i].equals(planeID)) { + index = i; + break; + } + } + if (index == -1) { + return null; + } + else { + return new PlanarViewDataSet(index); + } + } + + public String[] getPlaneIds() { + String[] result= new String[planeIDs.length]; + System.arraycopy( planeIDs, 0, result, 0, planeIDs.length ); + return result; + } + + private class PlanarViewDataSet extends AbstractDataSet.ViewDataSet implements VectorDataSet { + + private final int index; + + private PlanarViewDataSet(int index) { + DefaultVectorDataSet.this.super(); + this.index = index; + } + + public Datum getDatum(int i) { + return Datum.create(yValues[index][i], yUnits[index]); + } + + public double getDouble(int i, Units units) { + return yUnits[index].getConverter(units).convert(yValues[index][i]); + } + + public int getInt(int i, Units units) { + return (int)Math.round(getDouble(i, units)); + } + + public Units getYUnits() { + return yUnits[index]; + } + + public DataSet getPlanarView(String planeID) { + DataSet result= planeID.equals("") ? this : DefaultVectorDataSet.this.getPlanarView(planeID); + if (result==null ) return DefaultVectorDataSet.this.getPlanarView( planeIDs[index] + "."+planeID ); + return result; + } + + public String[] getPlaneIds() { + return new String[0] ; + } + + public Object getProperty(String name) { + String planeProp = planeIDs[index] + "." + name; + if (DefaultVectorDataSet.this.hasProperty(name)) { + return DefaultVectorDataSet.this.getProperty(planeProp); + } + else { + return DefaultVectorDataSet.this.getProperty(name); + } + } + + // TODO: this appears to have different logic than in ViewDataSet. This needs to be resolved. + + // public Map getProperties() { + // throw new IllegalStateException("unimplemented"); + // //return DefaultVectorDataSet.this.getProperty(planeIDs[index] + "." + name); + // } + + public String toString() { + return "DefaultVectorDataSet("+DefaultVectorDataSet.this.planeIDs[index]+") "+VectorUtil.toString(this); + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/ExecutableDataSetDescriptor.java b/dasCore/src/main/java/org/das2/dataset/ExecutableDataSetDescriptor.java new file mode 100644 index 000000000..f8dfed2aa --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/ExecutableDataSetDescriptor.java @@ -0,0 +1,88 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.dataset; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.das2.DasException; +import org.das2.DasIOException; +import org.das2.client.DataSetStreamHandler; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import org.das2.util.DasProgressMonitorInputStream; +import org.das2.util.StreamTool; +import org.das2.util.monitor.ProgressMonitor; + +/** DataSetDescriptor implementation + * + * @author eew + */ +public class ExecutableDataSetDescriptor extends DataSetDescriptor { + + private String exePath; + private String[] commandFmts; + + private static final Logger logger= DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG); + + public ExecutableDataSetDescriptor(String exePath, String[] commandFmts) { + this.exePath = exePath; + this.commandFmts = Arrays.copyOf(commandFmts, commandFmts.length); + } + + @Override + protected DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + InputStream in; + DataSet result; + + String[] command = new String[commandFmts.length+1]; + command[0] = exePath; + for (int iParam = 0; iParam < commandFmts.length; iParam++) { + String sParam = commandFmts[iParam]; + if (sParam.contains("%{start}")) { + sParam = sParam.replace("%{start}", start.toString()); + } + else if (sParam.contains("%{end}")) { + sParam = sParam.replace("%{end}", end.toString()); + } + else if (sParam.contains("%{resolution}")) { + sParam = sParam.replace("%{resolution}", + Double.toString(resolution.doubleValue(Units.seconds))); + } + command[iParam+1] = sParam; + } + ProcessBuilder builder = new ProcessBuilder(command); + builder.redirectError(ProcessBuilder.Redirect.INHERIT); + try { + Process p = builder.start(); + in = p.getInputStream(); + final DasProgressMonitorInputStream mpin = new DasProgressMonitorInputStream(in, monitor); + ReadableByteChannel channel = Channels.newChannel(mpin); + + DataSetStreamHandler handler = new DataSetStreamHandler(properties, monitor); + + StreamTool.readStream(channel, handler); + return handler.getDataSet(); + + } catch (IOException ex) { + throw new DasIOException(ex); + } + + } + + @Override + public Units getXUnits() { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/FastTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/FastTableDataSet.java new file mode 100644 index 000000000..d96cca901 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/FastTableDataSet.java @@ -0,0 +1,17 @@ +/* + * FastTableDataSet.java + * + * Created on July 11, 2005, 3:30 PM + * + * + */ + +package org.das2.dataset; + +/** + * + * @author Jeremy + */ +public class FastTableDataSet { + +} diff --git a/dasCore/src/main/java/org/das2/dataset/GapListDouble.java b/dasCore/src/main/java/org/das2/dataset/GapListDouble.java new file mode 100755 index 000000000..f0594e17f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/GapListDouble.java @@ -0,0 +1,196 @@ +/* File: DoubleList.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 30, 2003, 4:26 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * List implementation that allows for efficient insertion into the middle of the list. + * @author Edward West + */ +class GapListDouble { + + private static final int INITIAL_ARRAY_SIZE = 128; + private double[] array = new double[INITIAL_ARRAY_SIZE]; + private int gapStart = 0; + private int gapEnd = INITIAL_ARRAY_SIZE; + + /** + * Constructor for the class, which is only used within the package. + */ + GapListDouble() { + } + + /** + * inserts the double into the list. + * @param d the double to be added. + * @return the index of the element within the list. + */ + public int add(double d) { + int index = indexOf(d); + if (index < 0) { + index = ~index; + } + if (isFull()) { + resizeArray(); + } + if (index != gapStart) { + moveGap(index); + } + array[gapStart] = d; + gapStart++; + return index; + } + + /** + * Returns the element at the specified position in this list. + * @param index index of element to return. + * @return the element at the specified position in this list. + */ + public double get(int index) { + if (index < gapStart) { + return array[index]; + } + else { + return array[index + (gapEnd - gapStart)]; + } + } + + /** + * Find the index of the number, or insertion index if the number is not in the list. + * @param d number to look for + * @return index of the search key, if it is contained in the list; otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which the key would be inserted into the list: the index of the first element greater than the key, or list.size(), if all elements in the list are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found. + */ + public int indexOf(double d) { + if (gapStart != 0 && array[gapStart - 1] >= d) { + return binarySearch(d, array, 0, gapStart); + } + else if (gapEnd != array.length && array[gapEnd] < d) { + int index = binarySearch(d, array, gapEnd, array.length); + if (index >= 0) { + return index - (gapEnd - gapStart); + } + else { + return ~( ~index - (gapEnd - gapStart)); + } + } + else { + return ~gapStart; + } + } + + /** + * true is the list is empty + * @return true if the list is empty + */ + public boolean isEmpty() { + return gapStart == 0 && gapEnd == array.length; + } + + private boolean isFull() { + return gapStart == gapEnd; + } + + /** + * Returns the number of elements in this collection. + * @return the number of elements in the list. + */ + public int size() { + return gapStart + array.length - gapEnd; + } + + /** + * returns the list in an array. + * @return double array of length size(), containing the list. + */ + public double[] toArray() { + double[] out = new double[size()]; + System.arraycopy(array, 0, out, 0, gapStart); + System.arraycopy(array, gapEnd, out, gapStart, array.length - gapEnd); + return out; + } + + /** + * returns a string representation of the object. + * @return a string representation of the object. + */ + public String toString() { + if (isEmpty()) { + return "[]"; + } + StringBuffer buffer = new StringBuffer("["); + int size = size(); + for (int i = 0; i < size-1; i++) { + buffer.append(get(i)).append(", "); + } + buffer.append(get(size - 1)).append("]"); + return buffer.toString(); + } + + private void resizeArray() { + double[] temp = new double[array.length << 1]; + System.arraycopy(array, 0, temp, 0, gapStart); + int l2 = array.length - gapEnd; + System.arraycopy(array, gapEnd, temp, temp.length - l2, l2); + array = temp; + gapEnd = temp.length - l2; + } + + private void moveGap(int position) { + if (position < gapStart) { + int chunkSize = gapStart - position; + int gapSize = gapEnd - gapStart; + System.arraycopy(array, position, array, position + gapSize, chunkSize); + gapStart = position; + gapEnd = gapStart + gapSize; + } + else if (position > gapStart) { + int chunkSize = position - gapStart; + int gapSize = gapEnd - gapStart; + System.arraycopy(array, gapEnd, array, gapStart, chunkSize); + gapStart = position; + gapEnd = gapStart + gapSize; + } + } + + /** + * index of the search key, if it is contained in the list; otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which the key would be inserted into the list: the index of the first element greater than the key, or list.size(), if all elements in the list are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found. + */ + private static int binarySearch(final double d, final double[] array, final int start, final int end) { + int low = start; + int high = end-1; + while (low <= high) { + int mid = (low + high) >> 1; + if (array[mid] < d) { + low = mid + 1; + } + else if (array[mid] > d) { + high = mid - 1; + } + else { + return mid; + } + } + return ~low; + } + +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/dataset/GenericQernalFactory.java b/dasCore/src/main/java/org/das2/dataset/GenericQernalFactory.java new file mode 100644 index 000000000..ac900b1bf --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/GenericQernalFactory.java @@ -0,0 +1,75 @@ +/* + * GenericQernalFactory.java + * + * Created on October 7, 2005, 1:58 PM + * + * + */ + +package org.das2.dataset; + +import org.das2.dataset.QernalTableRebinner.Qernal; +import org.das2.datum.Datum; + +/** + * + * @author Jeremy + */ +public class GenericQernalFactory implements QernalTableRebinner.QernalFactory { + class GenericQernal implements QernalTableRebinner.Qernal { + double[][] qernal; + int dx0,dx1; + int dy0,dy1; + int nx, ny; + GenericQernal( double[][] qernal, int dx0, int dy0, int nx, int ny ) { + this.qernal= qernal; + this.dx0= dx0; + this.dy0= dy0; + this.dy1= qernal[0].length-dy0-1; + this.dx1= qernal.length-dx0-1; + this.nx= nx; + this.ny= ny; + } + + public void apply( int x, int y, double value, double weight, double[][]ss, double[][]ww ) { + int x0,x1; + int y0,y1; + x0= x-dx0; + x1= x+dx1+1; + y0= y-dy0; + y1= y+dy1+1; + if (x0<0) x0=0; else if (x0>nx) x0=nx; // trim to visible portion + if (x1<0) x1=0; else if (x1>nx) x1=nx; + if (y0<0) y0=0; else if (y0>ny) y0=ny; + if (y1<0) y1=0; else if (y1>ny) y1=ny; + + for ( int i=x0; i ww[i][j] ) { + try { + double w= weight * qernal[i-x+dx0][j-y+dy0]; + ss[i][j]+= value * w; + ww[i][j]+= w; + } catch ( ArrayIndexOutOfBoundsException e ) { + throw new RuntimeException(e); + } + } + } + } + + } + } + + public QernalTableRebinner.Qernal getQernal( RebinDescriptor ddx, RebinDescriptor ddy, Datum xTagWidth, Datum yTagWidth ) { + double[][] qernal= new double[5][5]; + qernal[1][4]= 1.0; + qernal[3][4]= 1.0; + qernal[0][1]= 1.0; + qernal[4][1]= 1.0; + qernal[1][0]= 1.0; + qernal[2][0]= 1.0; + qernal[3][0]= 1.0; + return new GenericQernal( qernal, 2, 2, ddx.numberOfBins(), ddy.numberOfBins() ); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/LimitCountDataSetCache.java b/dasCore/src/main/java/org/das2/dataset/LimitCountDataSetCache.java new file mode 100644 index 000000000..9875a510a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/LimitCountDataSetCache.java @@ -0,0 +1,123 @@ +/* File: DataSetCache.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.DasApplication; +/** + * + * @author jbf + */ +public class LimitCountDataSetCache extends AbstractDataSetCache { + + protected Entry[] buffer; + + /** Creates a new instance of StandardDataStreamCache */ + public LimitCountDataSetCache( int count ) { + buffer= new Entry[count]; + } + + public void store( DataSetDescriptor dsd, CacheTag cacheTag, DataSet data ) { + Entry entry= new Entry( dsd, cacheTag, data ); + + int iMin=-1; + for (int i=buffer.length-1; i>=0; i--) { + if (buffer[i]==null) { + iMin= i; + } + } + if ( iMin==-1 ) { + long oldestAccess= Long.MAX_VALUE; + int oldest= -1; + for (int i=buffer.length-1; i>=0; i--) { + if ( buffer[i].lastAccess < oldestAccess ) { + oldest= i; + oldestAccess= buffer[i].lastAccess; + } + } + iMin= oldest; + } + + buffer[iMin]= entry; + }; + + private int findStored( DataSetDescriptor dsd, CacheTag cacheTag ) { + Entry entry= new Entry( dsd, cacheTag, null ); + + int iHit=-1; + for (int i=0; i this.totalSizeLimit ) return; + + synchronized (entries) { + while ( sizeBytes + this.totalSize > this.totalSizeLimit ) { + Entry e= leastValuableEntry(); + long s= DataSetUtil.guessSizeBytes( e.data ); + entries.remove(e); + totalSize-= s; + } + + entries.add( new Entry( dsd, cacheTag, data ) ); + totalSize+= sizeBytes; + } + } + + public Entry[] getEntries() { + return (Entry[]) entries.toArray( new Entry[ entries.size() ] ); + } + + public Entry getEntries( int i ) { + return (Entry)entries.get(i); + } + + public Datum getTotalSize() { + return Units.kiloBytes.createDatum( this.totalSize/1000., 0.1 ); + } + + public Datum getTotalSizeLimit() { + return Units.kiloBytes.createDatum( this.totalSizeLimit/1000., 0.1 ); + } + + public void setTotalSizeLimit( Datum d ) { + this.totalSizeLimit= (long)( d.doubleValue( Units.kiloBytes ) * 1000 ); + } + + public Datum getHitRate() { + //return ( this.hits + this.misses == 0 ) ? Units.percent.getFillDatum() : Units.percent.createDatum( this.hits * 100. / ( this.hits + this.misses ), 0.1 ); + return Units.percent.createDatum( this.hits * 100. / ( this.hits + this.misses ), 0.1 ); + } + + public String getDisplayString() { + StringBuffer result= new StringBuffer( "LimitSizeBytesDataSetCache with "+entries.size()+" datasets" ); + for ( int i=0; inx) x0=nx; // trim to visible portion + if (x1<0) x1=0; else if (x1>nx) x1=nx; + if (y0<0) y0=0; else if (y0>ny) y0=ny; + if (y1<0) y1=0; else if (y1>ny) y1=ny; + for ( int i=x0; i ww[i][j] ) { + ss[i][j]= value * weight; + ww[i][j]= weight; + } + } + } + } + } + + // special case for 1-pixel Quernal + class NNQernalOne implements QernalTableRebinner.Qernal { + int nx, ny; + private NNQernalOne( int nx, int ny ) { + this.nx= nx; + this.ny= ny; + } + public void apply( int x, int y, double value, double weight, double[][]ss, double[][]ww ) { + if ( x>=0 && x=0 && y ww[x][y] ) { + ss[x][y]= value * weight; + ww[x][y]= weight; + } + } + } + + public Qernal getQernal( RebinDescriptor ddx, RebinDescriptor ddy, Datum xTagWidth, Datum yTagWidth ) { + Datum d= ddx.binCenter(0); + int i= ddx.whichBin( d.add(xTagWidth).doubleValue(d.getUnits()), d.getUnits() ); + int dx0= i/2; + int dx1= i/2; + int dy0,dy1; + if ( UnitsUtil.isRatiometric(yTagWidth.getUnits()) ) { + if (!ddy.isLog() ) throw new IllegalArgumentException("need log axis"); + d= ddy.binCenter(0); + double f= yTagWidth.doubleValue( Units.log10Ratio ); + i= ddy.whichBin( d.multiply( DasMath.exp10(f) ).doubleValue(d.getUnits()), d.getUnits() ); + dy0= i/2; + dy1= (i+1)/2; + } else { + d= ddy.binCenter(0); + i= ddy.whichBin( d.add(yTagWidth).doubleValue(d.getUnits()), d.getUnits() ); + dy0= i/2; + dy1= (i+1)/2; + } + if ( dx0==0 && dx1==0 && dy0==0 && dy1==0 ) { + return new NNQernalOne( ddx.numberOfBins(), ddy.numberOfBins() ); + } else { + return new NNQernal( dx0, dx1, dy0, dy1, ddx.numberOfBins(), ddy.numberOfBins() ); + } + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java new file mode 100755 index 000000000..3b174863a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java @@ -0,0 +1,319 @@ +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import org.das2.datum.UnitsUtil; +import java.util.*; + +public class NearestNeighborTableDataSet implements TableDataSet { + + TableDataSet source; + + Map m_override; /* Have to keep track of your overrides for getProperty */ + + int[] imap; + + int[][] jmap; + + int[] itableMap; + + RebinDescriptor ddX; + + RebinDescriptor ddY; + + NearestNeighborTableDataSet( + TableDataSet source, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) { + imap= new int[ddX.numberOfBins()]; + if ( ddY==null ) { + if ( source.tableCount()>1 ) { + throw new IllegalArgumentException(); + } + jmap= new int[source.tableCount()][source.getYLength(0)]; + } else { + jmap= new int[source.tableCount()][ddY.numberOfBins()]; + } + itableMap= new int[ddX.numberOfBins()]; + + this.ddX= ddX; + this.ddY= ddY; + this.source= source; + + if(override == null) + m_override = new HashMap<>(); + else + m_override = override; + + if ( source.getXLength()==0 ) { + for ( int i=0; i xTagWidth.doubleValue(xunits)/1.90 ) { + imap[i]=-1; + } else { + int itable= source.tableOfIndex(imap[i]); + itableMap[i]= itable; + if ( itable0!=itable ) { + if ( ddY==null ) { + for ( int j=0; j yTagWidth.doubleValue(Units.logERatio)/1.90 ) jmap[itable][j]=-1; + } else { + Datum yclose= source.getYTagDatum( itable, jmap[itable][j] ); + if ( Math.abs( yclose.subtract(yy[j],ddY.getUnits()).doubleValue(yunits)) > yTagWidth.doubleValue(yunits)/1.90 ) { + jmap[itable][j]= -1; + } + } + } + } + itable0= itable; + } + } + } + } + } + + @Override + public Datum getDatum(int i, int j) { + if ( imap[i]!=-1 && jmap[itableMap[i]][j]!=-1 ) { + return source.getDatum(imap[i], jmap[itableMap[i]][j]); + } else { + return source.getZUnits().createDatum(source.getZUnits().getFillDouble()); + } + } + + @Override + public double getDouble(int i, int j, Units units) { + try { + if ( imap[i]!=-1 && jmap[itableMap[i]][j]!=-1 ) { + return source.getDouble(imap[i], jmap[itableMap[i]][j], units); + } else { + return source.getZUnits().getFillDouble(); + } + } catch ( ArrayIndexOutOfBoundsException e ) { + System.err.println("here: "+e); + throw new RuntimeException(e); + } + } + + @Override + public int getInt(int i, int j, Units units) { + if ( imap[i]!=-1 && jmap[itableMap[i]][j]!=-1 ) { + return source.getInt(imap[i], jmap[itableMap[i]][j],units); + } else { + return source.getZUnits().getFillDatum().intValue(source.getZUnits()); + } + } + + @Override + public DataSet getPlanarView(String planeID) { + TableDataSet ds = (TableDataSet)source.getPlanarView(planeID); + if (ds != null) { + return new NearestNeighborTableDataSet(ds,ddX,ddY, null); + } else { + return null; + } + } + + @Override + public String[] getPlaneIds() { + return source.getPlaneIds(); + } + + @Override + public Object getProperty(String name) { + Object ret = m_override.get(name); + if(ret != null) return source.getProperty(name); + return ret; + } + + @Override + public Map getProperties() { + if(m_override.isEmpty()) return source.getProperties(); + + // Fun, now we get to merge + HashMap mRet = new HashMap<>(m_override); + Map srcProps = source.getProperties(); + for(Object key: srcProps.keySet()){ + mRet.put(key, srcProps.get(key)); + } + return mRet; + } + + @Override + public int getXLength() { + return imap.length; + } + + @Override + public VectorDataSet getXSlice(int i) { + return new XSliceDataSet(this,i); + } + + @Override + public VectorDataSet getYSlice(int j, int table) { + return new YSliceDataSet(this, j, table); + } + + @Override + public Datum getXTagDatum(int i) { + return ddX.getUnits().createDatum(getXTagDouble(i,ddX.getUnits())); + } + + @Override + public double getXTagDouble(int i, Units units) { + return ddX.binCenter(i,units); + } + + @Override + public int getXTagInt(int i, Units units) { + return (int)getXTagDouble(i,units); + } + + @Override + public Units getXUnits() { + return ddX.getUnits(); + } + + @Override + public int getYLength(int table) { + if ( ddY==null ) { + return source.getYLength(table); + } else { + return ddY.numberOfBins(); + } + } + + @Override + public Datum getYTagDatum(int table, int j) { + if ( ddY==null ) { + return source.getYTagDatum( table, j ); + } else { + return ddY.getUnits().createDatum(getYTagDouble(table,j,ddY.getUnits())); + } + } + + @Override + public double getYTagDouble(int table, int j, Units units) { + if ( ddY==null ) { + return source.getYTagDouble( table, j, units ); + } else { + return ddY.binCenter(j,units); + } + } + + @Override + public int getYTagInt(int table, int j, Units units) { + return (int)getYTagDouble( table, j, units); + } + + @Override + public Units getYUnits() { + if ( ddY==null ) { + return source.getYUnits(); + } else { + return ddY.getUnits(); + } + } + + @Override + public Units getZUnits() { + return source.getZUnits(); + } + + @Override + public int tableCount() { + return 1; + } + + @Override + public int tableEnd(int table) { + return ddX.numberOfBins(); + } + + @Override + public int tableOfIndex(int i) { + return 0; + } + + @Override + public int tableStart(int table) { + return 0; + } + + @Override + public String toString() { + return "NearestNeighborTableDataSet " + TableUtil.toString(this); + } + + @Override + public double[] getDoubleScan(int i, Units units) { + int yLength = getYLength(tableOfIndex(i)); + double[] array = new double[yLength]; + for (int j = 0; j < yLength; j++) { + array[j] = getDouble(i, j, units); + } + return array; + } + + @Override + public DatumVector getScan(int i) { + Units zUnits = getZUnits(); + return DatumVector.newDatumVector(getDoubleScan(i, zUnits), zUnits); + } + + @Override + public DatumVector getYTags(int table) { + double[] tags = new double[getYLength(table)]; + Units yUnits = getYUnits(); + for (int j = 0; j < tags.length; j++) { + tags[j] = getYTagDouble(table, j, yUnits); + } + return DatumVector.newDatumVector(tags, yUnits); + } + + @Override + public Object getProperty(int table, String name) { + return getProperty(name); + } + +} + diff --git a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableRebinner.java new file mode 100755 index 000000000..2f4673ed7 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableRebinner.java @@ -0,0 +1,49 @@ +/* File: TableAverageRebinner.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 5, 2003, 10:31 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import java.util.Map; + +/** + * + * @author Jeremy Faden + */ +public class NearestNeighborTableRebinner implements DataSetRebinner { + + /** Creates a new instance of TableAverageRebinner */ + public NearestNeighborTableRebinner() { + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException(); + } + + return new NearestNeighborTableDataSet((TableDataSet)ds, ddX, ddY, override ); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/NewAverageTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/NewAverageTableRebinner.java new file mode 100644 index 000000000..07ed4f7c0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/NewAverageTableRebinner.java @@ -0,0 +1,486 @@ +/* File: TableAverageRebinner.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 5, 2003, 10:31 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.UnitsUtil; +import org.das2.DasException; +import org.das2.system.DasLogger; +import java.util.*; +import java.util.logging.*; + +/** + * not thread safe!!! + * @author Jeremy Faden + */ +public class NewAverageTableRebinner implements DataSetRebinner { + + private static final Logger logger = DasLogger.getLogger(DasLogger.DATA_OPERATIONS_LOG); + + /** + * Holds value of property interpolate. + */ + private boolean interpolate= true; + private boolean enlargePixels = true; + + /* not thread safe--ny is set in rebin and then used by the other routines. */ + private final int ny; + private final int nx; + TableDataSet tds; + RebinDescriptor ddX, ddY; + + /** Creates a new instance of TableAverageRebinner */ + public NewAverageTableRebinner( DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY ) { + if (ds == null) { + throw new NullPointerException("null data set"); + } + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException("Data set must be an instanceof TableDataSet: " + ds.getClass().getName()); + } + this.tds = (TableDataSet)ds; + this.ddX= ddX; + this.ddY= ddY; + nx= (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, DasException { + if ( ds!=this.tds ) throw new IllegalArgumentException("already set for another dataset"); + if ( ddX!=this.ddX ) throw new IllegalArgumentException("already set for another X rebin descriptor"); + if ( ddY!=this.ddY ) throw new IllegalArgumentException("already set for another Y rebin descriptor"); + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + + TableDataSet weights = (TableDataSet)tds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + if (ddX != null && tds.getXLength() > 0) { + double start = tds.getXTagDouble(0, ddX.getUnits()); + double end = tds.getXTagDouble(tds.getXLength() - 1, ddX.getUnits()); + if (start > ddX.end ) { + throw new NoDataInIntervalException("data starts after range"); + } else if ( end < ddX.start ) { + throw new NoDataInIntervalException("data ends before range"); + } + } + + long timer= System.currentTimeMillis(); + + Units xunits= ddX.getUnits(); + + + + logger.finest("Allocating rebinData and rebinWeights: " + nx + " x " + ny); + + double[] rebinData= new double[nx*ny]; // Y's are adjacent + double[] rebinWeights= new double[nx*ny]; + + average(tds, weights, rebinData, rebinWeights, ddX, ddY); + + double[] xTags; + if (ddX != null) { + xTags = ddX.binCenters(); + } else { + xTags = new double[nx]; + for (int i = 0; i < nx; i++) { + xTags[i] = tds.getXTagDouble(i, tds.getXUnits()); + } + } + + double[] yTags; + if (ddY != null) { + yTags = ddY.binCenters(); + } else { + yTags = new double[ny]; + for (int j = 0; j < ny; j++) { + yTags[j] = tds.getYTagDouble(0, j, tds.getYUnits()); + } + } + + /* TODO: handle xTagWidth yTagWidth properties. Pass on unrelated properties on to the + * new dataset. + */ + Units resultXUnits= ddX==null ? tds.getXUnits() : ddX.getUnits(); + Units resultYUnits= ddY==null ? tds.getYUnits() : ddY.getUnits(); + + if ( this.interpolate ) { + Datum xTagWidth= (Datum)ds.getProperty("xTagWidth"); + if ( xTagWidth==null ) { + xTagWidth= DataSetUtil.guessXTagWidth(tds); + } + double xTagWidthDouble= xTagWidth.doubleValue(ddX.getUnits().getOffsetUnits()); + + Datum yTagWidth= (Datum)ds.getProperty("yTagWidth"); + + if ( ddX!=null ) fillInterpolateX(rebinData, rebinWeights, xTags, xTagWidthDouble ); + if ( ddY!=null ) fillInterpolateY(rebinData, rebinWeights, ddY, yTagWidth ); + } else if (enlargePixels) { + enlargePixels( rebinData, rebinWeights ); + } + + TableDataSet weightsTDS= new SimpleTableDataSet( xTags, yTags, rebinWeights, resultXUnits, resultYUnits, Units.dimensionless ); + TableDataSet result= new SimpleTableDataSet( xTags, yTags, rebinData, resultXUnits, resultYUnits, Units.dimensionless, DataSet.PROPERTY_PLANE_WEIGHTS, weightsTDS ); + + return result; + } + + private final int indexOf( int i, int j ) { + return i*ny + j; + } + + void average(TableDataSet tds, TableDataSet weights, double[] rebinData, double[] rebinWeights, RebinDescriptor ddX, RebinDescriptor ddY) { + double[] ycoordinate; + int nTables; + Units xUnits, zUnits; + + double[][] hInterpData, hInterpWeights, vInterpData, vInterpWeights; + int[][] hInterpIndex, vInterpIndex; + + xUnits = tds.getXUnits(); + zUnits= tds.getZUnits(); + + hInterpData = new double[nx][2]; + hInterpWeights = new double[nx][2]; + hInterpIndex = new int[nx][2]; + vInterpData = new double[2][ny]; + vInterpWeights = new double[2][ny]; + vInterpIndex = new int[2][ny]; + + for (int i = 0; i < hInterpIndex.length; i++) { + Arrays.fill(hInterpIndex[i], -1); + } + for (int i = 0; i < vInterpIndex.length; i++) { + Arrays.fill(vInterpIndex[i], -1); + } + + if (ddY != null) { + ycoordinate = ddY.binCenters(); + } else { + ycoordinate = new double[tds.getYLength(0)]; + for (int j = 0; j < ycoordinate.length; j++) { + ycoordinate[j] = tds.getDouble(0, j, zUnits ); + } + } + + nTables = tds.tableCount(); + for (int iTable = 0; iTable < nTables; iTable++) { + int yLength= tds.getYLength(iTable); + int [] ibiny= new int[tds.getYLength(iTable)]; + for (int j=0; j < ibiny.length; j++) { + if (ddY != null) { + ibiny[j]= ddY.whichBin(tds.getYTagDouble(iTable, j, tds.getYUnits()), tds.getYUnits()); + } else { + ibiny[j] = j; + } + } + for (int i=tds.tableStart(iTable); i < tds.tableEnd(iTable); i++) { + int ibinx; + if (ddX != null) { + ibinx= ddX.whichBin(tds.getXTagDouble(i, xUnits), xUnits); + } else { + ibinx = i; + } + + if (ibinx < 0) { + for (int j = 0; j < yLength; j++) { + if (ibiny[j] < 0 || ibiny[j] >= ny) { + continue; + } + double z = tds.getDouble(i, j, zUnits); + double w = weights == null + ? (zUnits.isFill(z) ? 0. : 1.) + : weights.getDouble(i, j, Units.dimensionless); + if (vInterpIndex[0][ibiny[j]] == -1 + || ibinx > vInterpIndex[0][ibiny[j]]) { + vInterpData[0][ibiny[j]] = z * w; + vInterpWeights[0][ibiny[j]] = w; + vInterpIndex[0][ibiny[j]] = ibinx; + } else if (ibinx == vInterpIndex[0][ibiny[j]]) { + vInterpData[0][ibiny[j]] += z * w; + vInterpWeights[0][ibiny[j]] += w; + } + } + } else if (ibinx >= nx) { + for (int j = 0; j < yLength; j++) { + if (ibiny[j] < 0 || ibiny[j] >= ny) { + continue; + } + double z = tds.getDouble(i, j, zUnits); + double w = weights == null + ? (zUnits.isFill(z) ? 0. : 1.) + : weights.getDouble(i, j, Units.dimensionless); + if (vInterpIndex[1][ibiny[j]] == -1 + || ibinx < vInterpIndex[1][ibiny[j]]) { + vInterpData[1][ibiny[j]] = z * w; + vInterpWeights[1][ibiny[j]] = w; + vInterpIndex[1][ibiny[j]] = ibinx; + } else { + vInterpData[1][ibiny[j]] += z * w; + vInterpWeights[1][ibiny[j]] += w; + } + } + } else { //if (ibinx>=0 && ibinx hInterpIndex[ibinx][0]) { + hInterpData[ibinx][0] = z * w; + hInterpWeights[ibinx][0] = w; + hInterpIndex[ibinx][0] = ibiny[j]; + } else if (ibiny[j] == hInterpIndex[ibinx][0]) { + hInterpData[ibinx][0] += z * w; + hInterpWeights[ibinx][0] += w; + } + } else if (ibiny[j] >= ny) { + if (hInterpIndex[ibinx][1] == -1 + || ibiny[j] < hInterpIndex[ibinx][1]) { + hInterpData[ibinx][1] = z * w; + hInterpWeights[ibinx][1] = w; + hInterpIndex[ibinx][1] = ibiny[j]; + } else if (ibiny[j] == hInterpIndex[ibinx][1]) { + hInterpData[ibinx][1] += z * w; + hInterpWeights[ibinx][1] += w; + } + } else { //if (ibiny[j] >= 0 && ibiny[j] < ny) { + rebinData[ indexOf( ibinx,ibiny[j] ) ] += z * w; + rebinWeights[ indexOf(ibinx,ibiny[j] ) ] += w; + } + } + } + } + } + + multiplyWeights(rebinData, rebinWeights, zUnits); + } + + private final double linearlyInterpolate(int i0, double z0, int i1, double z1, int i) { + double r = ((double)(i - i0))/(i1 - i0); + return z0 + r * (z1 - z0); + } + + private final void multiplyWeights(double[] data, double[] weights, Units zUnits) { + for (int index = 0; index < data.length; index++) { + if (weights[index] > 0.0) { + data[index] = data[index] / weights[index]; + } else { + data[index] = zUnits.getFillDouble(); + } + } + } + + void fillInterpolateX(final double[] data, final double[] weights, final double[] xTags, final double xSampleWidth) { + + final int[] i1= new int[nx]; + final int[] i2= new int[nx]; + double a1; + double a2; + + for (int j = 0; j < ny; j++) { + int ii1 = -1; + int ii2 = -1; + for (int i = 0; i < nx; i++) { + if (weights[ indexOf(i,j) ] > 0. && ii1 == (i-1)) { // ho hum another valid point + i1[i] = -1; + i2[i] = -1; + ii1 = i; + } else if (weights[ indexOf(i,j) ] > 0. && ii1 == -1) { // first valid point + i1[i] = -1; + i2[i] = -1; + ii1 = i; + } else if (weights[indexOf(i,j)] > 0. && ii1 < (i-1)) { // bracketed a gap, interpolate + if (ii1 > -1) { + i1[i] = -1; + i2[i] = -1; + for (int ii = i - 1; ii >= ii1; ii--) { + ii2 = i; + i1[ii] = ii1; + i2[ii] = ii2; + } + ii1 = i; + } + } else { + i1[i] = -1; + i2[i] = -1; + } + } + + for (int i = 0; i < nx; i++) { + + if ((i1[i] != -1) && (xTags[i2[i]] - xTags[i1[i]]) < xSampleWidth * 1.5 ) { + a2 = (float)((xTags[i] - xTags[i1[i]]) / (xTags[i2[i]] - xTags[i1[i]])); + a1 = 1.f - a2; + data[indexOf(i,j)] = data[indexOf(i1[i],j)] * a1 + data[indexOf(i2[i],j)] * a2; + weights[indexOf(i,j)] = weights[indexOf(i1[i],j)] * a1 + weights[indexOf(i2[i],j)] * a2; //approximate + } + } + } + } + + void fillInterpolateY(final double[] data, final double[] weights, RebinDescriptor ddY, Datum yTagWidth ) { + + final int[] i1= new int[ny]; + final int[] i2= new int[ny]; + final double [] y_temp= new double[ddY.numberOfBins()]; + float a1; + float a2; + + final double[] yTags= ddY.binCenters(); + final Units yTagUnits= ddY.getUnits(); + final boolean log= ddY.isLog(); + + if (log) { + for (int j=0; j 0. && ii1 == (j-1)) { // ho hum another valid point + i1[j] = -1; + i2[j] = -1; + ii1 = j; + } else if (weights[indexOf(i,j)] > 0. && ii1 == -1) { // first valid point + i1[j] = -1; + i2[j] = -1; + ii1=j; + } else if (weights[indexOf(i,j)] > 0. && ii1 < (j-1)) { // bracketed a gap, interpolate + if ((ii1 > -1)) { // need restriction on Y gap size + i1[j] = -1; + i2[j] = -1; + for (int jj=j-1; jj>=ii1; jj--) { + ii2 = j; + i1[jj] = ii1; + i2[jj] = ii2; + } + ii1 = j; + } + } else { + i1[j] = -1; + i2[j] = -1; + } + } + + + for (int j = 0; j < ny; j++) { + if ( (i1[j] != -1) && ( ( yTags[i2[j]] - yTags[i1[j]] ) < ySampleWidth[j] ) ) { + a2 = (float)((y_temp[j] - y_temp[i1[j]]) / (y_temp[i2[j]] - y_temp[i1[j]])); + a1 = 1.f - a2; + data[indexOf(i,j)] = data[indexOf(i,i1[j])] * a1 + data[indexOf(i,i2[j])] * a2; + weights[indexOf(i,j)] = weights[indexOf(i,i1[j])] * a1 + weights[indexOf(i,i2[j])] * a2; //approximate + } + } + } + } + + private void enlargePixels( double[] rebinData, double[] rebinWeights ) { + for ( int ii=0; ii0; ii-- ) { + for ( int jj=0; jj0; jj-- ) { + for ( int ii=0; ii + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * + * @author jbf + */ +public class NoDataInIntervalException extends org.das2.DasException { + + public NoDataInIntervalException(String msg) { + super(msg); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/NoInterpolateQernalFactory.java b/dasCore/src/main/java/org/das2/dataset/NoInterpolateQernalFactory.java new file mode 100644 index 000000000..28b4e5034 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/NoInterpolateQernalFactory.java @@ -0,0 +1,73 @@ +/* + * NNQernalFactory.java + * + * Created on October 7, 2005, 12:06 PM + * + * + */ + +package org.das2.dataset; + +import org.das2.dataset.QernalTableRebinner.Qernal; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.util.DasMath; + +/** + * + * @author Jeremy + */ +public class NoInterpolateQernalFactory implements QernalTableRebinner.QernalFactory { + + class EnlargeQernal implements QernalTableRebinner.Qernal { + int dx0,dx1; + int dy0,dy1; + int nx,ny; // number of elements in each dimension + private EnlargeQernal( int dx0, int dx1, int dy0, int dy1, int nx, int ny ) { + this.dx0= dx0; + this.dx1= dx1; + this.dy0= dy0; + this.dy1= dy1; + this.nx= nx; + this.ny= ny; + } + public void apply( int x, int y, double value, double weight, double[][]ss, double[][]ww ) { + int x0,x1; + int y0,y1; + x0= x-dx0; + x1= x+dx1+1; + y0= y-dy0; + y1= y+dy1+1; + if (x0<0) x0=0; else if (x0>nx) x0=nx; // trim to visible portion + if (x1<0) x1=0; else if (x1>nx) x1=nx; + if (y0<0) y0=0; else if (y0>ny) y0=ny; + if (y1<0) y1=0; else if (y1>ny) y1=ny; + for ( int i=x0; i=0 && x=0 && y + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * + * @author jbf + */ +public class NoKeyProvidedException extends org.das2.DasException { + //The requested data set requires a key, but none was provided. + + /** Creates a new instance of NoKeyProvidedException */ + public NoKeyProvidedException(String msg) { + super(msg); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/NullDataSetCache.java b/dasCore/src/main/java/org/das2/dataset/NullDataSetCache.java new file mode 100644 index 000000000..76404442b --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/NullDataSetCache.java @@ -0,0 +1,37 @@ +/* + * NullDataSetCache.java + * + * Created on November 13, 2006, 11:45 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.dataset; + +/** + * DataSetCache that does no caching at all. This is useful for batch + * mode or when dataset caching is undesirable. + * + * @author jbf + */ +public class NullDataSetCache implements DataSetCache { + + public NullDataSetCache() { + } + + public void store(DataSetDescriptor dsd, CacheTag cacheTag, DataSet data) { + } + + public boolean haveStored(DataSetDescriptor dsd, CacheTag cacheTag) { + return false; + } + + public DataSet retrieve(DataSetDescriptor dsd, CacheTag cacheTag) { + throw new IllegalArgumentException("not found in cache"); + } + + public void reset() { + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/PeakTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/PeakTableRebinner.java new file mode 100755 index 000000000..afa1fe07e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/PeakTableRebinner.java @@ -0,0 +1,132 @@ +/* File: TablePeakRebinner.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 5, 2003, 10:31 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import java.util.Map; +import org.das2.datum.Units; + +/** + * + * @author Edward West + */ +public class PeakTableRebinner implements DataSetRebinner { + + public PeakTableRebinner() { + } + + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException { + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException(); + } + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + + TableDataSet tds = (TableDataSet)ds; + long timer= System.currentTimeMillis(); + + int nx= (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + int ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + double[][] rebinData= new double[nx][ny]; + double[][] rebinWeights= new double[nx][ny]; + + peaks(tds, rebinData, ddX, ddY); + + double[] xTags; + if (ddX != null) { + xTags = ddX.binCenters(); + } + else { + xTags = new double[tds.getXLength()]; + for (int i = 0; i < xTags.length; i++) { + xTags[i] = tds.getXTagDouble(i, tds.getXUnits()); + } + } + double[][] yTags; + if (ddY != null) { + yTags = new double[][]{ddY.binCenters()}; + } + else { + yTags = new double[0][tds.getYLength(0)]; + for (int j = 0; j < yTags[0].length; j++) { + yTags[0][j] = tds.getYTagDouble(0, j, tds.getYUnits()); + } + } + double[][][] zValues = {rebinData}; + int[] tableOffsets = {0}; + Units[] zUnits = {tds.getZUnits()}; + String[] planeIDs = {""}; + + return new DefaultTableDataSet(xTags, tds.getXUnits(), yTags, tds.getYUnits(), zValues, zUnits, planeIDs, tableOffsets, java.util.Collections.EMPTY_MAP); + } + + static void peaks(TableDataSet tds, double[][] rebinData, RebinDescriptor ddX, RebinDescriptor ddY) { + + int nx= (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + int ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + for (int i = 0; i < rebinData.length; i++) { + java.util.Arrays.fill(rebinData[i], Double.NaN); + } + + int [] ibiny= new int[tds.getYLength(0)]; + for (int j=0; j < ibiny.length; j++) { + if (ddY != null) { + ibiny[j]= ddY.whichBin(tds.getYTagDouble(0, j, tds.getYUnits()), tds.getYUnits()); + } + else { + ibiny[j] = j; + } + } + + for (int i=0; i < tds.getXLength(); i++) { + int ibinx; + if (ddX != null) { + ibinx= ddX.whichBin(tds.getXTagDouble(i, tds.getXUnits()), tds.getXUnits()); + } + else { + ibinx = i; + } + if (ibinx>=0 && ibinx= 0 && ibiny[j] < ny) { + double value = tds.getDouble(i, j, tds.getZUnits()); + if (Double.isNaN(rebinData[ibinx][ibiny[j]])) { + rebinData[ibinx][ibiny[j]] = value; + } + else { + rebinData[ibinx][ibiny[j]] = Math.max(value, rebinData[ibinx][ibiny[j]]); + } + } + } + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/QernalTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/QernalTableRebinner.java new file mode 100644 index 000000000..456690b1e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/QernalTableRebinner.java @@ -0,0 +1,168 @@ +/* + * QernalTableRebinner.java + * + * Created on October 7, 2005, 11:34 AM + * + * + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + + + +/** + * + * @author Jeremy + */ +public class QernalTableRebinner implements DataSetRebinner { + + interface QernalFactory { + Qernal getQernal( RebinDescriptor ddx, RebinDescriptor ddy, Datum xBinWidth, Datum yBinWidth ); + } + + interface Qernal { + void apply( int x, int y, double value, double weight, double[][] s, double[][]w ); + } + + Logger logger= DasLogger.getLogger(DasLogger.DATA_OPERATIONS_LOG); + QernalFactory factory; + + public QernalTableRebinner( QernalFactory factory ) { + this.factory= factory; + } + + public DataSet rebin( + DataSet ds, RebinDescriptor ddX, RebinDescriptor ddY, Map override + ) throws IllegalArgumentException, org.das2.DasException { + logger.finest("enter QernalTableRebinner.rebin"); + + if (ds == null) { + throw new NullPointerException("null data set"); + } + if (!(ds instanceof TableDataSet)) { + throw new IllegalArgumentException("Data set must be an instanceof TableDataSet: " + ds.getClass().getName()); + } + + if(override != null) + throw new UnsupportedOperationException("This rebinner does not "+ + "yet know how to override dataset properties."); + + TableDataSet tds = (TableDataSet)ds; + TableDataSet weights = (TableDataSet)ds.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + if (ddX != null && tds.getXLength() > 0) { + double start = tds.getXTagDouble(0, ddX.getUnits()); + double end = tds.getXTagDouble(tds.getXLength() - 1, ddX.getUnits()); + if (start > ddX.end ) { + throw new NoDataInIntervalException("data starts after range"); + } else if ( end < ddX.start ) { + throw new NoDataInIntervalException("data ends before range"); + } + } + + Datum xBinWidth= DataSetUtil.guessXTagWidth(tds); + + long timer= System.currentTimeMillis(); + + Units xUnits= ddX.getUnits(); + Units zUnits= tds.getZUnits(); + + int nx= (ddX == null ? tds.getXLength() : ddX.numberOfBins()); + int ny= (ddY == null ? tds.getYLength(0) : ddY.numberOfBins()); + + logger.finest("Allocating rebinData and rebinWeights: " + nx + " x " + ny); + + double[][] rebinData= new double[nx][ny]; + double[][] rebinWeights= new double[nx][ny]; + + int nTables = tds.tableCount(); + for (int iTable = 0; iTable < nTables; iTable++) { + Datum yBinWidth= TableUtil.guessYTagWidth(tds,iTable); + + Qernal qernal= factory.getQernal( ddX, ddY, xBinWidth, yBinWidth ); + + int [] ibiny= new int[tds.getYLength(iTable)]; + for (int j=0; j < ibiny.length; j++) { + if (ddY != null) { + ibiny[j]= ddY.whichBin(tds.getYTagDouble(iTable, j, tds.getYUnits()), tds.getYUnits()); + } else { + ibiny[j] = j; + } + } + + for (int i=tds.tableStart(iTable); i < tds.tableEnd(iTable); i++) { + int ibinx; + if (ddX != null) { + ibinx= ddX.whichBin( tds.getXTagDouble(i, xUnits), xUnits ); + } else { + ibinx = i; + } + + for (int j = 0; j < tds.getYLength(iTable); j++) { + double z = tds.getDouble(i,j,zUnits); + double w = weights == null + ? (zUnits.isFill(z) ? 0. : 1.) + : weights.getDouble(i, j, Units.dimensionless); + qernal.apply( ibinx, ibiny[j], z, w, rebinData, rebinWeights ); + } + } + } + + logger.finest("normalize sums by weights"); + for ( int i=0; i 0. ) { + rebinData[i][j]/= rebinWeights[i][j]; + } else { + rebinData[i][j]= zUnits.getFillDouble(); + } + } + } + + logger.finest( "create new DataSet" ); + + double[] xTags; + if (ddX != null) { + xTags = ddX.binCenters(); + } else { + xTags = new double[nx]; + for (int i = 0; i < nx; i++) { + xTags[i] = tds.getXTagDouble(i, tds.getXUnits()); + } + } + double[][] yTags; + if (ddY != null) { + yTags = new double[][]{ddY.binCenters()}; + } else { + yTags = new double[1][ny]; + for (int j = 0; j < ny; j++) { + yTags[0][j] = tds.getYTagDouble(0, j, tds.getYUnits()); + } + } + + Units resultXUnits= ddX==null ? tds.getXUnits() : ddX.getUnits(); + Units resultYUnits= ddY==null ? tds.getYUnits() : ddY.getUnits(); + + double[][][] zValues = {rebinData,rebinWeights}; + + int[] tableOffsets = {0}; + Units[] newZUnits = {tds.getZUnits(), Units.dimensionless}; + String[] planeIDs = {"", DataSet.PROPERTY_PLANE_WEIGHTS}; + + Map properties= new HashMap(ds.getProperties()); + + if ( ddX!=null ) properties.put( DataSet.PROPERTY_X_TAG_WIDTH, ddX.binWidthDatum() ); + if ( ddY!=null ) properties.put( DataSet.PROPERTY_Y_TAG_WIDTH, ddY.binWidthDatum() ); + + TableDataSet result= new DefaultTableDataSet( xTags, resultXUnits, yTags, resultYUnits, zValues, newZUnits, planeIDs, tableOffsets, properties ); + logger.finest("done, QernalTableRebinner.rebin"); + return result; + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/QuickVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/QuickVectorDataSet.java new file mode 100644 index 000000000..4ae00621b --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/QuickVectorDataSet.java @@ -0,0 +1,64 @@ +/* + * EZVectorDataSet.java + * + * Created on April 15, 2005, 2:59 PM + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.Units; + +/** + * Abstract VectorDataSet that allows for defining a vector dataset by + * implementing a minimal portion of the api. + * @author Jeremy + */ +public abstract class QuickVectorDataSet implements VectorDataSet { + java.util.Map properties= new java.util.HashMap(); + String[] planeIds= new String[0]; + + public Datum getDatum(int i) { + Units yUnits= getYUnits(); + return yUnits.createDatum( getDouble(i,yUnits) ); + } + + abstract public double getDouble(int i, Units units ); + + public int getInt(int i, Units units) { + return (int) getDouble( i,units ); + } + + public DataSet getPlanarView(String planeID) { + return null; + } + + public String[] getPlaneIds() { + return planeIds; + } + + public java.util.Map getProperties() { + return null; + } + + public Object getProperty(String name) { + return null; + } + + abstract public int getXLength(); + + public Datum getXTagDatum(int i) { + Units xUnits= getXUnits(); + return xUnits.createDatum( getXTagDouble(i,xUnits) ); + } + + abstract public double getXTagDouble(int i, Units units); + + public int getXTagInt(int i, Units units) { + return (int)getXTagDouble( i, units ); + } + + abstract public Units getXUnits(); + + abstract public Units getYUnits(); +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/dataset/RebinDescriptor.java b/dasCore/src/main/java/org/das2/dataset/RebinDescriptor.java new file mode 100755 index 000000000..b273d9243 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/RebinDescriptor.java @@ -0,0 +1,251 @@ +/* File: RebinDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.DatumVector; +import org.das2.datum.UnitsConverter; +import org.das2.datum.Datum; +import org.das2.datum.Units; + +/** Nice Documentation... + * + * @author jbf + */ +public class RebinDescriptor { + + Units units; + protected double start; + protected double end; + protected int nBin; + protected boolean isLog = false; + + public static final int FIRSTORLAST=-2; // return the closest valid bin, first or last + public static final int MINUSONE= -3; // return sentinel -1. + public static final int EXTRAPOLATE= -4; // return negative or >nBin. + + private int outOfBoundsAction= EXTRAPOLATE; + + /** Creates a new instance of RebinDescriptor */ + private RebinDescriptor() { + } + + public RebinDescriptor(double start, double end, Units units, int nBin, boolean isLog) { + this.units= units; + if (isLog) { + this.start= Math.log(start); + this.end= Math.log(end); + } else { + this.start= start; + this.end= end; + } + this.nBin= nBin; + this.isLog= isLog; + } + + public RebinDescriptor( Datum start, Datum end, int nBin, boolean isLog) { + this(start.doubleValue(start.getUnits()),end.doubleValue(end.getUnits()),start.getUnits(),nBin,isLog); + if (start.getUnits()!=end.getUnits()) throw new IllegalArgumentException("start and end units differ"); + } + + public int numberOfBins() { + return nBin; + } + + public int whichBin( double x, Units units ) { + if ( units!=this.units ) { + x= Units.getConverter(units,this.units).convert(x); + } + int result=0; + if (isLog) x= Math.log(x); + if ((x=end) && outOfBoundsAction!=EXTRAPOLATE) { + switch (outOfBoundsAction) { + case FIRSTORLAST: + result= x= numberOfBins() ) { + throw new IllegalArgumentException("bin "+ibin+" is out of bounds"); + } + } + double result= start+((ibin)/(double)(nBin)*(end-start)); + UnitsConverter uc= this.units.getConverter(units); + if ( isLog ) { + return uc.convert(Math.exp(result)); + } else { + return uc.convert(result); + } + } + + public Datum binStop( int ibin ) { + return Datum.create( binStop( ibin, units ), units ); + } + + public double binStop( int ibin, Units units ) { + if ( this.outOfBoundsAction!=RebinDescriptor.EXTRAPOLATE ) { + if ( ibin<0 || ibin >= numberOfBins() ) { + throw new IllegalArgumentException("bin "+ibin+" is out of bounds"); + } + } + double result= start+((ibin+1)/(double)(nBin)*(end-start)); + UnitsConverter uc= this.units.getConverter(units); + if ( isLog ) { + return uc.convert(Math.exp(result)); + } else { + return uc.convert(result); + } + } + + /** So what freaking units are the return value in??? + * Anyone? Buler? + * @return a number in who knows what coordinate system + */ + public double[] binStarts() { + double [] result= new double[nBin]; + for (int i=0; i0 ) { + i0= 0; + ymin= units.createDatum(ddY.binStart(0, units)); + } + if ( i0< -10000000 ) { + throw new IllegalArgumentException( "ymin would result in impossibly large rebin descriptor (ymin="+ymin+" falls in bin number "+i0+")" ); + } + + int i1= dd.whichBin( ymax.doubleValue(units), units ); + if ( i1 10000000 ) { + throw new IllegalArgumentException( "ymax would result in impossibly large rebin descriptor (ymax="+ymax+" falls in bin number "+i0+")" ); + } + + int nbins= i1-i0+1; + + return new RebinDescriptor( units.createDatum(dd.binStart(i0,units)), units.createDatum(dd.binStop(i1,units)), nbins, dd.isLog() ); + } + + public double binWidth() { + return (end-start)/(double)nBin; + } + + public Datum binWidthDatum() { + return Datum.create( binWidth(), getUnits().getOffsetUnits() ); + } + + public boolean isLog() { + return isLog; + } + + public Units getUnits() { + return units; + } + + public String toString() { + return "["+units.createDatum(start)+" - "+units.createDatum(end)+" in "+nBin+" bins "+(isLog?"Log":"")+"]"; + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/SimpleDataSetCache.java b/dasCore/src/main/java/org/das2/dataset/SimpleDataSetCache.java new file mode 100644 index 000000000..eb8245cf3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/SimpleDataSetCache.java @@ -0,0 +1,90 @@ +/* File: DataSetCache.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.DasApplication; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +/** + * simply cache data by storing one per DataSetDescriptor. + * + * @author jbf + */ +public class SimpleDataSetCache extends AbstractDataSetCache { + + protected Map buffer; + + /** Creates a new instance of StandardDataStreamCache */ + public SimpleDataSetCache() { + buffer= new HashMap(); + } + + public void store( DataSetDescriptor dsd, CacheTag cacheTag, DataSet data ) { + Entry entry= new Entry( dsd, cacheTag, data ); + buffer.put( dsd, entry ); + }; + + public boolean haveStoredImpl( DataSetDescriptor dsd, CacheTag cacheTag ) { + Entry haveEntry= (Entry)buffer.get(dsd); + if ( haveEntry==null ) { + return false; + } else { + Entry entry= new Entry( dsd, cacheTag, null ); + return haveEntry.satifies(entry); + } + } + + public DataSet retrieveImpl( DataSetDescriptor dsd, CacheTag cacheTag ) { + Entry haveEntry= (Entry)buffer.get(dsd); + if ( haveEntry==null ) { + throw new IllegalArgumentException("Data not found in cache"); + } else { + Entry entry= new Entry( dsd, cacheTag, null ); + if ( haveEntry.satifies(entry) ) { + return haveEntry.getData(); + } else { + throw new IllegalArgumentException("Data not found in cache"); + } + } + } + + public void reset() { + buffer= new HashMap(); + } + + public String toString() { + StringBuffer result= new StringBuffer("\n---SimpleDataSetCache---\n"); + for ( Iterator i= buffer.keySet().iterator(); i.hasNext(); ) { + Object key= i.next(); + result.append( " " ); + result.append( buffer.get(key).toString() ); + result.append( "\n" ); + } + result.append( "------------------------\n" ); + return result.toString(); + } + + +} diff --git a/dasCore/src/main/java/org/das2/dataset/SimpleTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/SimpleTableDataSet.java new file mode 100644 index 000000000..84abe1969 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/SimpleTableDataSet.java @@ -0,0 +1,176 @@ +/* + * WritableTableDataSet.java + * + * Created on September 2, 2004, 11:58 AM + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import java.util.*; +import java.util.Map; + +/** + * optimized TableDataSet where only 1-table data set is supported, + * and is backed by a 1-D array. This is to house the result of the rebin method + * used for spectrograms. + * + * @author Jeremy + */ +public class SimpleTableDataSet implements TableDataSet { + + double[] z; + double[] x; + double[] y; + final int nx; + final int ny; + Units xunits; + Units yunits; + Units zunits; + Map properties; + TableDataSet auxPlane; + String auxPlaneName; + + /* z must be stored with the y's adjacent */ + public SimpleTableDataSet( double[] x, double[]y, double[] z, Units xunits, Units yunits, Units zunits ) { + this.z= z; + this.x= x; + this.y= y; + this.nx= x.length; + this.ny= y.length; + this.xunits= xunits; + this.yunits= yunits; + this.zunits= zunits; + auxPlaneName= null; + } + + public SimpleTableDataSet( double[] x, double[]y, double[] z, Units xunits, Units yunits, Units zunits, String planeName, TableDataSet planeData ) { + this( x, y, z, xunits, yunits, zunits ); + auxPlaneName= planeName; + auxPlane= planeData; // TODO: check units and dimensions + } + + + private final int indexOf( int i, int j ) { + return i*ny + j; + } + + public Datum getDatum(int i, int j) { + return Datum.create( z[indexOf(i,j)], zunits ); + } + + public double getDouble(int i, int j, Units units) { + return zunits.convertDoubleTo(units,z[indexOf(i,j)]); + } + + public double[] getDoubleScan(int i, Units units) { + throw new UnsupportedOperationException(); + } + + public int getInt(int i, int j, Units units) { + throw new UnsupportedOperationException(); + } + + public DataSet getPlanarView(String planeID) { + if ( planeID.equals( auxPlaneName ) ) return auxPlane; else return null; + } + + public String[] getPlaneIds() { + return new String[0]; + } + + public Object getProperty(String name) { + return properties.get(name); + } + + public Object getProperty( int table, String name) { + return getProperty(name); + } + + public DatumVector getScan(int i) { + throw new UnsupportedOperationException(); + } + + public int getXLength() { + return x.length; + } + + public VectorDataSet getXSlice(int i) { + return new XSliceDataSet( this, i ); + } + + public Datum getXTagDatum(int i) { + return Datum.create( x[i], xunits ); + } + + public double getXTagDouble(int i, Units units) { + return xunits.convertDoubleTo( units, x[i] ); + } + + public int getXTagInt(int i, Units units) { + throw new UnsupportedOperationException(); + } + + public Units getXUnits() { + return xunits; + } + + public int getYLength(int table) { + return y.length; + } + + public VectorDataSet getYSlice(int j, int table) { + return new YSliceDataSet( this, j, table ); + } + + public Datum getYTagDatum(int table, int j) { + return Datum.create( y[j], yunits ); + } + + public double getYTagDouble(int table, int j, Units units) { + return yunits.convertDoubleTo(units,y[j]); + } + + public void setYTagDouble( int table, int j, double yvalue, Units units ) { + y[j]= units.convertDoubleTo( yunits, yvalue ); + } + + public int getYTagInt(int table, int j, Units units) { + throw new UnsupportedOperationException(); + } + + public DatumVector getYTags(int table) { + return DatumVector.newDatumVector(y,yunits); + } + + public Units getYUnits() { + return yunits; + } + + public Units getZUnits() { + return zunits; + } + + public int tableCount() { + return 1; + } + + public int tableEnd(int table) { + return x.length; + } + + public int tableOfIndex(int i) { + return 0; + } + + public int tableStart(int table) { + return 0; + } + + public Map getProperties() { + return new HashMap(properties); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/SingleVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/SingleVectorDataSet.java new file mode 100644 index 000000000..3c2eeb06e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/SingleVectorDataSet.java @@ -0,0 +1,81 @@ +/* + * SingleVectorDataSet.java + * + * Created on November 3, 2005, 1:29 PM + * + * + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import java.util.HashMap; + +/** + * + * @author Jeremy + */ +public class SingleVectorDataSet implements VectorDataSet { + Datum x; + Datum y; + HashMap properties; + + public SingleVectorDataSet( Datum x, Datum y, HashMap properties ) { + this.x= x; + this.y= y; + this.properties= new HashMap(properties); + } + + public org.das2.datum.Datum getDatum(int i) { + return y; + } + + public double getDouble(int i, org.das2.datum.Units units) { + return y.doubleValue(units); + } + + public int getInt(int i, org.das2.datum.Units units) { + return y.intValue(units); + } + + public DataSet getPlanarView(String planeID) { + return null; + } + + public String[] getPlaneIds() { + return new String[] { "" }; + } + + public int getXLength() { + return 1; + } + + public Datum getXTagDatum(int i) { + return x; + } + + public double getXTagDouble( int i, Units units ) { + return x.doubleValue(units); + } + + public int getXTagInt( int i, Units units ) { + return x.intValue(units); + } + + public java.util.Map getProperties() { + return new HashMap( properties ); + } + + public Object getProperty(String name) { + return properties.get(name); + } + + public Units getXUnits() { + return x.getUnits(); + } + + public Units getYUnits() { + return y.getUnits(); + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/SyncUtil.java b/dasCore/src/main/java/org/das2/dataset/SyncUtil.java new file mode 100755 index 000000000..8f8186f0f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/SyncUtil.java @@ -0,0 +1,166 @@ +/* + * SyncUtil.java + * + * Created on April 7, 2004, 1:11 PM + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import java.util.Map; + +/** + * + * @author Jeremy + */ +public class SyncUtil { + + private static int[] calculateImap( DataSet source, DataSet target ) { + int[] imap= new int[target.getXLength()]; + Units xunits= source.getXUnits(); + + Datum xTagWidth= (Datum)source.getProperty("xTagWidth"); + if ( xTagWidth==null ) xTagWidth= DataSetUtil.guessXTagWidth(source); + + for ( int i=0; i xTagWidth.doubleValue(xunits)/2. ) { + imap[i]=-1; + } + } + return imap; + } + + /* calculates imap when width tags are irregular, and possibly overlapping. + */ + private static int[] calculateImapForWidthTags( DataSet source, DataSet target ) { + int[] imap= new int[target.getXLength()]; + Units xunits= source.getXUnits(); + Units xoffsetUnits= xunits.getOffsetUnits(); + String widthsPlane= "xTagWidth"; + + VectorDataSet widthsDs= (VectorDataSet)source.getPlanarView(widthsPlane); + + int sourceLength= source.getXLength(); + + for ( int i=0; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; + +/** A DataSet implementation for 3 dimensional z(x,y) data sets + * where the data is arranged in a sequence of tables. Each table will have + * a set of monotonically increasing x tags and y tags. The x tags for all + * the tables, when taken together in the order that the table are in, will + * be monotonically increasing over the whole data set. The y tags are constant + * over the y scans of each table, but may change, either in number or value, + * from table to table. + * + * @author Edward West + */ +public interface TableDataSet extends DataSet { + + /** Returns the Units object representing the unit type of the y values for + * this data set. + * @return the x units + */ + Units getZUnits(); + + /** Returns the Z value for the given indices into the x and y tags as a + * Datum. + * @param i index of the x tag for the requested value. + * @param j index of the y tag for the requested value. + * @return the value at index location (i, j) as a Datum + */ + Datum getDatum(int i, int j); + + /** Returns the Z value for the given indices into the x and y tags as a + * double with the given units. + * @param j index of the x tag for the requested value. + * @param i index of the y tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location (i, j) as a double. + */ + double getDouble(int i, int j, Units units); + + DatumVector getScan(int i); + + double[] getDoubleScan(int i, Units units); + + /** Returns the Z value for the given indices into the x and y tags as a + * int with the given units. + * @param i index of the x tag for the requested value. + * @param j index of the y tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location (i, j) as a int. + */ + int getInt(int i, int j, Units units); + + /** Returns the yTags for this data set as a DatumVector + * @return the yTags for this data set as a DatumVector + */ + DatumVector getYTags(int table); + + /** Returns the value of the y tag at the given index j as a + * Datum. + * @param j the index of the requested y tag + * @return the value of the y tag at the given index j as a + * Datum. + */ + Datum getYTagDatum(int table, int j); + + /** Returns the value of the y tag at the given index j as a + * double in the given units. YTags must be + * monotonically increasing with j. + * @return the value of the y tag at the given index j as a + * double. + * @param units the units of the returned value + * @param j the index of the requested y tag + */ + double getYTagDouble(int table, int j, Units units); + + /** Returns the value of the y tag at the given index j as an + * int in the given units. YTags must be + * monotonically increasing with j. + * @return the value of the y tag at the given index j as an + * int. + * @param units the units of the returned value + * @param j the index of the requested y tag + */ + int getYTagInt(int table, int j, Units units); + + /** Returns the number of y tags in the specified table for this data set. + * YTags must be monotonically increasing with j. + * @param table index of the table + * @return the number of x tags in this data set. + */ + int getYLength(int table); + + /** Returns the first x tag index of the specified table. + * @param table the index of the table. + * @return the first x tag index of the specified table + */ + int tableStart(int table); + + /** Returns the index after the last x tag index of the specified table + * @param table the index of the table + * @return the index after the last x tag index of the specified table + */ + int tableEnd(int table); + + /** Returns the number of tables in this data set + * @return the number of tables in this data set + */ + int tableCount(); + + /** Returns the table number that the specified index is in. + * @param i x tag index + * @return the table number that the specified index is in + */ + int tableOfIndex(int i); + + /** Returns a slice view of this data set for a specific x value + */ + VectorDataSet getXSlice(int i); + + /** Returns a slice view of this data set for a specific y value + */ + VectorDataSet getYSlice(int j, int table); + + /** + * Return the property value attached to the table. This should + * simply return DataSet.getProperty() if the table has no special + * value for the table. + * @param table + * @param name + * @return + */ + Object getProperty( int table, String name ); +} diff --git a/dasCore/src/main/java/org/das2/dataset/TableDataSetBuilder.java b/dasCore/src/main/java/org/das2/dataset/TableDataSetBuilder.java new file mode 100755 index 000000000..5cb67c9e3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/TableDataSetBuilder.java @@ -0,0 +1,431 @@ +/* File: TableDataSetBuilder.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 10, 2003, 4:54 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import java.util.*; + +/** Build an X-Y data grid from input vectors. + * Here's an example of using this class. + *
    + * {@code
    + *		TableDataSet
    + * }
    + *
    + * @author Edward West + */ +public class TableDataSetBuilder { + + private static final double[] EMPTY = new double[0]; + + private GapListDouble xTags = new GapListDouble(); + + private List zValues = new ArrayList(); + + private List planeIDs = new ArrayList(); + + private Units xUnits = Units.dimensionless; + private Units yUnits = Units.dimensionless; + + private Map zUnitsMap = new HashMap(); + + private SortedSet yTagSet = new TreeSet(new DoubleArrayComparator()); + + private Map properties = new HashMap(); + private List tableProperties= new ArrayList<>(); + + /** Creates a new instance of TableDataSetBuilder + * This version assigns a 0-length string to the name of the first plane, and + * assumes that the zUnits are dimensionless. + */ + public TableDataSetBuilder( Units xUnits, Units yUnits) { + setXUnits(xUnits); + setYUnits(yUnits); + + zUnitsMap.put("", Units.dimensionless); + planeIDs.add(""); + } + + /** Creates a new instance of TableDataSetBuilder + * This version assigns a 0-length string to the name of the first plane. + */ + public TableDataSetBuilder( Units xUnits, Units yUnits, Units zUnits ) { + setXUnits(xUnits); + setYUnits(yUnits); + + zUnitsMap.put("", zUnits); + planeIDs.add(""); + } + + /** Create a new instance of TableDataSetBuilder + * + * @param xUnits The X Axis Units + * @param yUnits The Y Axis Units + * @param zUnits The Z Axis Units + * @param sPlane0Name A name for the 0th plane of the dataset + */ + public TableDataSetBuilder( Units xUnits, Units yUnits, Units zUnits, String sPlane0Name ) + { + setXUnits(xUnits); + setYUnits(yUnits); + zUnitsMap = new HashMap(); + zUnitsMap.put(sPlane0Name, zUnits); + planeIDs.add(sPlane0Name); + } + + public void setProperty(String name, Object value) { + properties.put(name, value); + } + + public void setProperty( int table, String name, Object value ) { + if ( tableProperties.size() plane in the dataset. + * + * @param units + */ + public void setZUnits(Units units) { + + setZUnits(units, ""); + } + + public void setZUnits(Units units, String planeID) { + if (units == null) { + throw new NullPointerException(); + } + zUnitsMap.put(planeID, units); + } + + public String toString() { + int index = 0; + return "TableDataSetBuilder ["+xTags.size()+" xtags, "+getTableCount(zValues)+"tables]"; + } + + public TableDataSet toTableDataSet() { + int count = getTableCount(zValues); + int[] tableOffsets = getTableOffsets(zValues, count); + double[][] collapsedYTags = collapseYTags(zValues, count); + double[][][] collapsedZValues = collapseZValues(zValues, planeIDs, zUnitsMap); + Units[] zUnitsArray = getUnitsArray(planeIDs, zUnitsMap); + return new DefaultTableDataSet(xTags.toArray(), xUnits, + collapsedYTags, yUnits, + collapsedZValues, zUnitsArray, + (String[])planeIDs.toArray(new String[planeIDs.size()]), tableOffsets, + properties, tableProperties ); + } + + public int getXLength() { + return xTags.size(); + } + + public double getXTag(int i) { + return xTags.get(i); + } + + private static double[] insert(double[] array, double value, int index) { + double[] result = new double[array.length + 1]; + System.arraycopy(array, 0, result, 0, index); + result[index] = value; + System.arraycopy(array, index, result, index + 1, array.length - index); + return result; + } + + private static double[][] insert(double[][] array, double[] values, int index) { + double[][] result = new double[array.length + 1][]; + System.arraycopy(array, 0, result, 0, index); + result[index] = values; + System.arraycopy(array, index, result, index + 1, array.length - index); + return result; + } + + private static String toString(double[] array) { + return toString(array, 0, array.length); + } + + private static String toString(double[] array, int startIndex, int endIndex) { + if (array.length == 0) return "[]"; + StringBuffer buffer = new StringBuffer("["); + for (int i = startIndex; i < endIndex-1; i++) { + buffer.append(array[i]).append(", "); + } + buffer.append(array[endIndex - 1]).append(']'); + return buffer.toString(); + } + + private static int getTableCount(List list) { + int count = 0; + double[] previous = null; + for (Iterator i = list.iterator(); i.hasNext();) { + MultiYScan scan = (MultiYScan)i.next(); + if (!Arrays.equals(previous, scan.getYTags())) { + previous = scan.getYTags(); + count++; + } + } + return count; + } + + private static int[] getTableOffsets(List list, int count) { + double[] previous = null; + int index = 0; + int offset = 0; + int[] tableOffsets = new int[count]; + for (Iterator i = list.iterator(); i.hasNext();) { + MultiYScan scan = (MultiYScan)i.next(); + if (!Arrays.equals(previous, scan.getYTags())) { + tableOffsets[index] = offset; + previous = scan.getYTags(); + index++; + } + offset++; + } + return tableOffsets; + } + + private static double[][] collapseYTags(List list, int count) { + double[] previous = null; + int index = 0; + double[][] result = new double[count][]; + for (Iterator i = list.iterator(); i.hasNext();) { + MultiYScan scan = (MultiYScan)i.next(); + if (!Arrays.equals(previous, scan.getYTags())) { + result[index] = scan.getYTags(); + previous = scan.getYTags(); + index++; + } + } + return result; + } + + private static double[][][] collapseZValues(List list, List planeIDs, Map unitsMap) { + double[][][] zValues = new double[planeIDs.size()][list.size()][]; + int index = 0; + for (Iterator i = list.iterator(); i.hasNext();) { + MultiYScan scan = (MultiYScan)i.next(); + for (int plane = 0; plane < planeIDs.size(); plane++) { + double[] z = scan.get((String)planeIDs.get(plane)); + if (z == null) { + z = new double[scan.getYTags().length]; + Units units = (Units)unitsMap.get(planeIDs.get(plane)); + Arrays.fill(z, units.getFillDouble()); + } + zValues[plane][index] = z; + } + index++; + } + return zValues; + } + + private static Units[] getUnitsArray(List planeIDs, Map unitsMap) { + Units[] units = new Units[planeIDs.size()]; + for (int i = 0; i < units.length; i++) { + units[i] = (Units)unitsMap.get(planeIDs.get(i)); + } + return units; + } + + private class DoubleArrayComparator implements Comparator { + + public int compare(Object o1, Object o2) { + double[] d1 = (double[])o1; + double[] d2 = (double[])o2; + if (d1.length != d2.length) { + return d1.length - d2.length; + } + for (int i = 0; i < d1.length; i++) { + double delta = d1[i] - d2[i]; + if (delta < 0.0) { + return -1; + } + else if (delta > 0.0) { + return 1; + } + } + return 0; + } + + } + + private class MultiYScan { + private HashMap map = new HashMap(); + double[] yTags; + public void put(String name, double[] scan) { + map.put(name, scan); + } + public double[] get(String name) { + return (double[])map.get(name); + } + public double[] getYTags() { + return yTags; + } + public void setYTags(double[] yTags) { + this.yTags = yTags; + } + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/TableDataSetConsumer.java b/dasCore/src/main/java/org/das2/dataset/TableDataSetConsumer.java new file mode 100644 index 000000000..443ce1609 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/TableDataSetConsumer.java @@ -0,0 +1,37 @@ +/* File: TableDataSetConsumer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +/** + * + * @author jbf + */ + +import org.das2.graph.DasAxis; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetConsumer; + +public interface TableDataSetConsumer extends DataSetConsumer { + public DasAxis getZAxis(); +} diff --git a/dasCore/src/main/java/org/das2/dataset/TableDataSetDecorator.java b/dasCore/src/main/java/org/das2/dataset/TableDataSetDecorator.java new file mode 100644 index 000000000..98ed2891e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/TableDataSetDecorator.java @@ -0,0 +1,17 @@ +/* + * TableDataSetDecorator.java + * + * Created on December 15, 2005, 8:55 AM + * + * + */ + +package org.das2.dataset; + +/** + * + * @author Jeremy + */ +public class TableDataSetDecorator { + +} diff --git a/dasCore/src/main/java/org/das2/dataset/TableDataSetGridder.java b/dasCore/src/main/java/org/das2/dataset/TableDataSetGridder.java new file mode 100644 index 000000000..94385b0ed --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/TableDataSetGridder.java @@ -0,0 +1,107 @@ +/* + * TableDataSetGridder.java + * + * Created on July 15, 2005, 4:56 PM + * + * + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.datum.UnitsUtil; +import org.das2.util.DasMath; + +/** + * calculate TableDataSets with tables that are gridded in linear or log space. + * @author Jeremy + */ +public class TableDataSetGridder { + + private static Datum yTagGcd( TableDataSet tds, int itable, Datum error ) { + Units units= tds.getYUnits(); + double[] ytag= tds.getYTags(itable).toDoubleArray(units); + double gcd= DasMath.gcd( ytag, error.doubleValue( units.getOffsetUnits() ) ); + return units.getOffsetUnits().createDatum( gcd ); + } + + private static Datum yTagGcdLog( TableDataSet tds, int itable, Datum error ) { + Units units= tds.getYUnits(); + double[] ytag= new double[ tds.getYLength(itable) -1 ] ; + + if ( ! UnitsUtil.isRatiometric(error.getUnits()) ) throw new IllegalArgumentException("error units must be ratiometric"); + for ( int i=0; i1 ) throw new IllegalArgumentException("only simple tables for now"); + + int itable=0; + Datum xTagGcd= xTagGcd( tds, itable, xerror ); + Datum yTagGcd= yTagGcdLog( tds, itable, yerror ); + + Units xunits= tds.getXUnits(); + Units yunits= tds.getYUnits(); + + double tagWidth= DataSetUtil.guessXTagWidth(tds).doubleValue(xunits.getOffsetUnits()); + double dx= xTagGcd.doubleValue(xunits.getOffsetUnits()); + double xbase= tds.getXTagDouble( tds.tableStart(itable), xunits ) -tagWidth/2 - dx / 2.; + + int nx= (int)(( tds.getXTagDouble(tds.tableEnd(itable)-1, xunits ) + tagWidth/2 - xbase ) / dx + 1 ); + + int[] imap= new int[nx]; + + + for ( int i=tds.tableStart(itable); i(-(insertion point) - 1). (See Arrays.binarySearch) + */ + public static int yTagBinarySearch( TableDataSet ds, int table, Datum datum, int low, int high ) { + Units units= datum.getUnits(); + double key= datum.doubleValue(units); + while (low <= high) { + int mid = (low + high) >> 1; + double midVal = ds.getYTagDouble(table,mid,units); + int cmp; + if (midVal < key) { + cmp = -1; // Neither val is NaN, thisVal is smaller + } else if (midVal > key) { + cmp = 1; // Neither val is NaN, thisVal is larger + } else { + long midBits = Double.doubleToLongBits(midVal); + long keyBits = Double.doubleToLongBits(key); + cmp = (midBits == keyBits ? 0 : // Values are equal + (midBits < keyBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) + 1)); // (0.0, -0.0) or (NaN, !NaN) + } + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + + /** Searches the ytags of the specified table of the specified data set for + * the specified datum using the binary search algorithm. + * + * @param ds the data set to search + * @param table the table to search + * @param datum the key to search for + * @return index of the search datum if it exists as an ytag in the + * data set; otherwise the insertion point. The insertion point is + * is defined as -1.0 if the datum is less that the first ytag + * in the data set, ds.getYLength(table) if the datum + * is larger than the last ytag, or + * (i + (datum - y0) / (y1 - y0) ) + * where y0 is the largest ytag smaller than datum, + * y1 is the smallest ytag larger than datum, and + * i is the index of y0. + */ + public static double rowFindex( TableDataSet ds, int table, Datum datum ) { + int result = yTagBinarySearch( ds, table, datum, 0, ds.getXLength()-1); + Units u = datum.getUnits(); + + if (result >= -1) { + return result; + } + + result = ~result; + + if (result == ds.getYLength(table)) { + return result; + } + else { + int lowerIndex = result-1; + double lower = ds.getYTagDouble(table, lowerIndex, u); + double upper = ds.getYTagDouble(table, result, u); + double key = datum.doubleValue(u); + return lowerIndex + (key - lower) / (upper - lower); + } + } + + public static Datum closestDatum( TableDataSet table, Datum x, Datum y ) { + int i= DataSetUtil.closestColumn( table, x ); + int j= closestRow( table, table.tableOfIndex(i), y ); + return table.getDatum(i,j); + } + + public static int tableIndexAt( TableDataSet table, int i ) { + int itable=0; + while ( table.tableEnd(itable)<=i ) itable++; + return itable; + } + + public static Datum guessYTagWidth( TableDataSet table ) { + return guessYTagWidth( table, 0 ); + } + + /** + * guess the y tag cadence by returning the difference of the first two tags. + * If the tags appear to be log spaced, then a ratiometric unit (e.g. percentIncrease) + * is returned. monotonically decreasing is handled, in which case a positive tag cadence + * is returned. + * @param table + * @param the table index. + * @return the norminal cadence of the tags. + */ + public static Datum guessYTagWidth( TableDataSet table, int itable ) { + // cheat and check for logarithmic scale. If logarithmic, then return YTagWidth as percent. + double y0= table.getYTagDouble( itable, 0, table.getYUnits()); + double y1= table.getYTagDouble( itable, 1, table.getYUnits()); + int n= table.getYLength(itable)-1; + double yn= table.getYTagDouble( itable, n, table.getYUnits() ); + double cycles= (yn-y0) / ( (y1-y0 ) * n ); + if ( y1 10. ) { + return Units.log10Ratio.createDatum( DasMath.log10(y1/y0) ); + } else { + return table.getYUnits().createDatum(y1-y0); + } + } + public static double tableMax( TableDataSet tds, Units units ) { + double result= Double.NEGATIVE_INFINITY; + + for ( int itable=0; itable result ) { + result= tds.getDouble(i,j,units); + } + } + } + } + return result; + } + + public static void checkForNaN( TableDataSet tds ) { + for ( int i=0; i"); + pout.println("Stream creation date: "+TimeUtil.now().toString()+""); + pout.print(""); + + if ( tds.getXUnits() instanceof LocationUnits ) { + base= xmin; + offsetUnits= ((LocationUnits)base.getUnits()).getOffsetUnits(); + if ( offsetUnits==Units.microseconds ) { + offsetUnits= Units.seconds; + } + } + + pout.print("[01]\n"); + pout.print(""); + + String yTagsString= ""+tds.getYTagDatum(0,0); + for ( int j=1; j"); + pout.print(""); + + NumberFormat xnf= new DecimalFormat("00000.000"); + NumberFormat ynf= new DecimalFormat("0.00E00"); + + double dx= xmax.subtract(xmin).doubleValue(offsetUnits); + for (int i=0; i=0 && x0 && ( dir * dd > 0 ) ) { + return i-1; + } else { + return i; + } + } + + /** + * return the first row after the datum. Handles mono decreasing. + * @return the row which is greater than or equal to the datum + */ + public static int getNextRow( TableDataSet ds, int itable, Datum datum ) { + int i= closestRow( ds, itable, datum ); + Units units= ds.getYUnits(); + double dir= ds.getYTagDouble(itable, 1, units ) - ds.getYTagDouble(itable, 0, units ); + double dd= ds.getYTagDouble(itable,i,units) - datum.doubleValue(units); + if ( iitable ) { + tableStartList.add( new Integer(i) ); + tableEndList.add( new Integer(i) ); + } + } + tableEndList.add( new Integer(itableMap.length) ); + + tableCount= tableEndList.size(); + tableStart= new int[tableCount]; + tableEnd= new int[tableCount]; + for ( int i=0; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; + +/** Interface definition for datasets comprised of a y value + * for each x tag such that y(x). + * + * @author eew + */ +public interface VectorDataSet extends DataSet { + + /** Returns the Y value for the given index into the x tags as a + * Datum. + * @param i index of the x tag for the requested value. + * @return the value at index location i as a Datum + */ + Datum getDatum(int i); + + /** Returns the Y value for the given index into the x tags as a + * double with the given units. + * @param i index of the x tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location i as a double. + */ + double getDouble(int i, Units units); + + /** Returns the Y value for the given index into the x tags as a + * int with the given units. + * @param i index of the x tag for the requested value. + * @param units the units the returned value should be coverted to. + * @return the value at index location i as a int. + */ + int getInt(int i, Units units); +} diff --git a/dasCore/src/main/java/org/das2/dataset/VectorDataSetBuilder.java b/dasCore/src/main/java/org/das2/dataset/VectorDataSetBuilder.java new file mode 100755 index 000000000..4c3978ebb --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/VectorDataSetBuilder.java @@ -0,0 +1,270 @@ +/* File: VectorDataSetBuilder.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 23, 2003, 8:58 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import org.das2.datum.Datum; + +import java.util.*; + +/** + * + * @author Edward West + */ +public class VectorDataSetBuilder { + + private GapListDouble xTags = new GapListDouble(); + private List yValues = new ArrayList(); + + private List planeIDs = new ArrayList(); + { + planeIDs.add(""); + } + + private Units xUnits = Units.dimensionless; + + private Map yUnitsMap = new HashMap(); + { + yUnitsMap.put("", Units.dimensionless); + } + + private Map properties = new HashMap(); + + /** Creates a new instance of VectorDataSetBuilder */ + public VectorDataSetBuilder( Units xUnits, Units yUnits ) { + this.xUnits= xUnits; + setYUnits(yUnits); + } + + public void setProperty(String name, Object value) { + properties.put(name, value); + } + + public Object getProperty(String name) { + return properties.get(name); + } + + public void addProperties(Map map) { + properties.putAll(map); + } + + public void addPlane(String name, Units yUnits ) { + if (!planeIDs.contains(name)) { + planeIDs.add(name); + yUnitsMap.put(name, yUnits ); + } + } + + public void insertY(double x, double y) { + insertY(x, y, ""); + } + + public void insertY(double x, double y, String planeID) { + if ( !planeIDs.contains(planeID) ) { + throw new IllegalArgumentException( "invalid planeID: "+planeID+", have "+planeIDs ); + } + if ( Double.isInfinite(x) || Double.isNaN(x) ) { + throw new IllegalArgumentException( "x is not finite" ); + } + int insertionIndex = xTags.indexOf(x); + if (planeID == null) { + planeID = ""; + } + if (insertionIndex < 0) { + insertionIndex = ~insertionIndex; + xTags.add(x); + MultiY scan = new MultiY(); + scan.put(planeID, y); + yValues.add(insertionIndex, scan); + } else { + MultiY scan = (MultiY)yValues.get(insertionIndex); + scan.put(planeID, y); + } + } + + /** + * Insert method favored when there is a default and one additional plane, + * because it's less prone to error when the + * one forgets the planeId. (And it's slightly more efficient because + * the index search need only be done once.) + */ + public void insertY( double x, double y, String planeId1, double planeValue1 ) { + if ( Double.isInfinite(x) || Double.isNaN(x) ) { + throw new IllegalArgumentException( "x is not finite" ); + } + int insertionIndex = xTags.indexOf(x); + if (insertionIndex < 0) { + insertionIndex = ~insertionIndex; + xTags.add(x); + MultiY scan = new MultiY(); + scan.put( "", y ); + scan.put( planeId1, planeValue1 ); + yValues.add(insertionIndex, scan); + } else { + //throw new IllegalArgumentException("already got value at this index"); + MultiY scan = (MultiY)yValues.get(insertionIndex); + scan.put( "", y ); + scan.put( planeId1, planeValue1 ); + } + } + + /** + * insert a datum for the default plane + */ + public void insertY(Datum x, Datum y) { + insertY(x, y, ""); + } + + /** + * insert a datum for the plane. + */ + public void insertY(Datum x, Datum y, String planeID) { + if (!planeIDs.contains(planeID)) { + throw new IllegalArgumentException("invalid planeID: "+planeID+", have "+planeIDs); + } + double xd = x.doubleValue(xUnits); + double yd = y.doubleValue((Units)yUnitsMap.get(planeID)); + insertY(xd, yd, planeID); + } + + public void append(VectorDataSet vds) { + String[] planeIds= DataSetUtil.getAllPlaneIds(vds); + for ( int iplane=0; iplane0 && xx[result]>x ) result--; + if ( result"); + pout.println("Stream creation date: "+TimeUtil.now().toString()+""); + pout.print(""); + + if ( vds.getXUnits() instanceof LocationUnits ) { + base= xmin; + offsetUnits= ((LocationUnits)base.getUnits()).getOffsetUnits(); + if ( offsetUnits==Units.microseconds ) { + offsetUnits= Units.seconds; + } + } + + + pout.print("[01]\n"); + pout.print(""); + + List planeIDs; + if ( vds.getProperty("plane-list")!=null ) { + planeIDs= (List)vds.getProperty("plane-list"); + } else { + planeIDs= new ArrayList(); + planeIDs.add(""); + } + + for ( int i=0; i"); + } + pout.print(""); + + NumberFormat xnf= new DecimalFormat("00000.000"); + NumberFormat ynf= new DecimalFormat("0.00E00"); + + double dx= xmax.subtract(xmin).doubleValue(offsetUnits); + for (int i=0; i=0 && xname represents + * @param name String name of the property requested + * @return the value of the property that name represents + * + */ + public Object getProperty(String name) { + return source.getProperty(name); + } + + public java.util.Map getProperties() { + return source.getProperties(); + } + + public int getXLength() { + return source.getXLength(); + } + + public Datum getXTagDatum(int i) { + return source.getXTagDatum(i); + } + + public double getXTagDouble(int i, Units units) { + return source.getXTagDouble(i,units); + } + + public int getXTagInt(int i, Units units) { + return source.getXTagInt(i,units); + } + + /** Returns the Units object representing the unit type of the x tags + * for this data set. + * @return the x units + * + */ + public Units getXUnits() { + return source.getXUnits(); + } + + /** Returns the Units object representing the unit type of the y tags + * or y values for this data set. + * @return the y units + * + */ + public Units getYUnits() { + return source.getYUnits(); + } + +} + diff --git a/dasCore/src/main/java/org/das2/dataset/WeightsTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/WeightsTableDataSet.java new file mode 100644 index 000000000..faffe7e8e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/WeightsTableDataSet.java @@ -0,0 +1,154 @@ +/* + * WeightsTableDataSet.java + * + * Created on November 29, 2005, 3:42 PM + * + * + */ + +package org.das2.dataset; + +import org.das2.datum.Units; +import java.util.HashMap; + +/** + * WeightsTableDataSet wraps a TableDataSet and returns 0.0 if the data point is + * not valid, and non-zero (generally one) otherwise. + * + * This is intended to provide a consistent way to get the weights without having + * to handle the case where the weights plane doesn't exist. + * + * @author Jeremy + */ +public class WeightsTableDataSet implements TableDataSet { + TableDataSet source; + Units sourceUnits; + double fill; + + public static TableDataSet create( TableDataSet source ) { + if ( source.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS)!=null ) { + return (TableDataSet)source.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + } else { + return new WeightsTableDataSet( source ); + } + } + + private WeightsTableDataSet( TableDataSet source ) { + this.source= source; + this.sourceUnits= source.getZUnits(); + this.fill= source.getZUnits().getFillDouble(); + } + + public org.das2.datum.Datum getDatum(int i, int j) { + return Units.dimensionless.createDatum( getDouble( i, j, Units.dimensionless ) ); + } + + public double getDouble(int i, int j, org.das2.datum.Units units) { + return ( sourceUnits.isFill(source.getDouble( i, j, sourceUnits )) ) ? 0.0 : 1.0; + } + + public double[] getDoubleScan(int i, org.das2.datum.Units units) { + throw new IllegalStateException("not implemented"); + } + + public int getInt(int i, int j, org.das2.datum.Units units) { + return ( source.getDouble( i, j, sourceUnits ) != fill ) ? 1 : 0; + } + + public DataSet getPlanarView(String planeID) { + return this; + } + + public String[] getPlaneIds() { + return new String[] { "" }; + } + + public java.util.Map getProperties() { + return new HashMap(); + } + + public Object getProperty(String name) { + return null; + } + + public org.das2.datum.DatumVector getScan(int i) { + throw new IllegalStateException("not implemented"); + } + + public int getXLength() { + return source.getXLength(); + } + + public VectorDataSet getXSlice(int i) { + throw new IllegalStateException("not implemented"); + } + + public org.das2.datum.Datum getXTagDatum(int i) { + return source.getXTagDatum(i); + } + + public double getXTagDouble(int i, org.das2.datum.Units units) { + return source.getXTagDouble( i, units ); + } + + public int getXTagInt(int i, org.das2.datum.Units units) { + return source.getXTagInt(i, units); + } + + public org.das2.datum.Units getXUnits() { + return source.getXUnits(); + } + + public int getYLength(int table) { + return source.getYLength(table); + } + + public VectorDataSet getYSlice(int j, int table) { + throw new IllegalStateException("not implemented"); + } + + public org.das2.datum.Datum getYTagDatum(int table, int j) { + return source.getYTagDatum(table, j ); + } + + public double getYTagDouble(int table, int j, org.das2.datum.Units units) { + return source.getYTagDouble(table, j, units); + } + + public int getYTagInt(int table, int j, org.das2.datum.Units units) { + return source.getYTagInt(table, j, units); + } + + public org.das2.datum.DatumVector getYTags(int table) { + return source.getYTags(table); + } + + public org.das2.datum.Units getYUnits() { + return source.getYUnits(); + } + + public org.das2.datum.Units getZUnits() { + return Units.dimensionless; + } + + public int tableCount() { + return source.tableCount(); + } + + public int tableEnd(int table) { + return source.tableEnd(table); + } + + public int tableOfIndex(int i) { + return source.tableOfIndex(i); + } + + public int tableStart(int table) { + return source.tableStart(table); + } + + public Object getProperty(int table, String name) { + return source.getProperty(table,name); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/WritableTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/WritableTableDataSet.java new file mode 100644 index 000000000..03d4f18bc --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/WritableTableDataSet.java @@ -0,0 +1,232 @@ +/* + * WritableTableDataSet.java + * + * Created on September 2, 2004, 11:58 AM + */ + +package org.das2.dataset; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import java.util.*; +import java.util.Map; + +/** + * + * @author Jeremy + */ +public class WritableTableDataSet implements TableDataSet { + + double[] z; + double[] x; + double[] y; + final int nx; + final int ny; + Units xunits; + Units yunits; + Units zunits; + Map properties; + + public static WritableTableDataSet newSimple( int nx, Units xunits, int ny, Units yunits, Units zunits ) { + double [] z= new double[nx*ny]; + double [] x= new double[nx]; + double [] y= new double[ny]; + return new WritableTableDataSet( x, xunits, y, yunits, z, zunits, new HashMap() ); + } + + public static WritableTableDataSet newEmpty( TableDataSet tds ) { + if ( tds.tableCount() > 1 ) throw new IllegalArgumentException("only supported for simple tables"); + int nx= tds.tableEnd(0); + int ny= tds.getYLength(0); + WritableTableDataSet result= newSimple( nx, tds.getXUnits(), ny, tds.getYUnits(), tds.getZUnits() ); + for ( int i=0; i 1 ) throw new IllegalArgumentException("only supported for simple tables"); + int nx= tds.tableEnd(0); + int ny= tds.getYLength(0); + WritableTableDataSet result= newEmpty( tds ); + for ( int i=0; i +

    Provides classes and interfaces for combining Datums into structured DataSets, and operators + for working with DataSets. The DataSet interface is the base for all DataSets, which all + contain a set of monotonically-increasing xtags. DataSets also contain a set of arbitary + properties, which are String->Object mappings. These are used to store metadata such as + axis labels. DataSets can have auxiliary "planes" attached to them. This + mechanism was first introduced as a means to keep track of the weights + after averaging, but we also use them for peaks-and-averages plots and + orbits.

    +

    DataSetDescriptors are used to provide access to datasets that are parametric over a long interval + (generally time), + such as Voyager 1 power spectrum. Clients request data from a DataSetDescriptor for a given time + interval and resolution. The base class DataSetDescriptor is abstract and implements DataSet + caching. +

    +

    Rebinners are DataSet operators that rebin data to a precisely-controlled set of X and Y tags. Various + methods for rebinning data such as bin averaging and nearest neighbor sampling are provided.

    +

    Lastly, objects for caching datasets are provided.

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/dataset/parser/VectorDataSetParser.java b/dasCore/src/main/java/org/das2/dataset/parser/VectorDataSetParser.java new file mode 100644 index 000000000..3d8351690 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/parser/VectorDataSetParser.java @@ -0,0 +1,210 @@ +/* + * VectorDataSetParser.java + * + * Created on December 4, 2004, 12:54 PM + */ + +package org.das2.dataset.parser; + +import org.das2.datum.Units; +import java.io.*; +import java.util.regex.*; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; + +/** + * Class for reading ascii tables into a VectorDataSet. This parses a + * file by looking at each line to see if it matches one of + * two Patterns: one for properties and one for records. If a record matched, + * then the record is matched and fields pulled out, parsed and insered a + * VectorDataSetBuilder. If a property is matched, then the builder property + * is set. Two Patterns are provided NAME_COLON_VALUE_PATTERN and + * NAME_EQUAL_VALUE_PATTERN for convenience. The record pattern is currently + * the number of fields identified with whitespace in between. Note the X + * tags are just the record numbers. + * + * @author Jeremy + */ +public class VectorDataSetParser { + + Pattern propertyPattern; + String commentRegex; + Pattern recordPattern; + String[] fieldNames; + + final static String numberPart= "[\\d\\.eE\\+\\-]+"; + final static String decimalRegex= numberPart; + int skipLines; + int recordCountLimit; + int fieldCount; + + public final static Pattern NAME_COLON_VALUE_PATTERN= Pattern.compile("\\s*(.+?)\\s*\\:\\s*(.+)\\s*"); + public final static Pattern NAME_EQUAL_VALUE_PATTERN= Pattern.compile("\\s*(.+?)\\s*\\=\\s*(.+)\\s*"); + + private VectorDataSetParser( String[] fieldNames ) { + this.fieldCount= fieldNames.length; + this.fieldNames= fieldNames; + StringBuffer regexBuf= new StringBuffer(); + regexBuf.append("\\s*"); + for ( int i=0; i max ) { + imax=j; + max= recCount[j]; + } + } + + return imax; + } + + /** + * creates a parser with @param fieldCount fields, named "field0,...,fieldN" + */ + public static VectorDataSetParser newParser( int fieldCount ) { + String[] fieldNames= new String[ fieldCount ]; + for ( int i=0; i +Package containing parsers for creating data sets. + diff --git a/dasCore/src/main/java/org/das2/dataset/scratchPad.txt b/dasCore/src/main/java/org/das2/dataset/scratchPad.txt new file mode 100755 index 000000000..098d8268f --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/scratchPad.txt @@ -0,0 +1,70 @@ +jbf> TableDataSetConsumer should be renamed ZAxisDataSetComsumer or something like that + DataSet.hasZAxis()? + +jbf> Suggested Properties for DataSets: + spacecraft Spacecraft enumeration object + xRange DataRange object suggestion + yRange DataRange object suggestion + defaultRenderer Renderer Object + xOffset offsets to be applied to yscan + mouseModules list of mouseModules that should be installed + +jbf> suggested logic for per-plane properties for das2Stream client-side DataSets: + the data of the primary plane is merged with the stream and packet properties. + name clashes to be resolved by using plane overrides packet overrides stream. + +jbf> suggest introduce getPropertyNames() method and policy that rebinners + propogate the properties listed. Alternatively, this could be expressed as + an optional property. + +eew> Agreed proeprties for DataSets + xTagWidth Datum the extent of relevance of the x tags + yTagWidth Datum " " " of the y tags + introduce Units.percent, which is useful for specifying log width. + rebinner Rebinner Object + cacheTag Description of the start, end and resolution satisfied by this + data set. + plane-list java.util.List + +eew> Agreed plane identifiers + peaks - peaks + weights - rebinning weights (always dimensionless) + uncertainty - errors plane + + +Vector + get(i)->Datum + getDouble(i,Units)->double + getUnits()->Units + +Table + get(i,j)->Datum + getDouble(i,j,Units)->double + length(index)->int + getUnits()->Units + +HyperTable // arbitrary rank + rank()->int + get(i,j,k,l)->Datum + getDouble(i,j,k,l,Units)->double + length(index)->int + getUnits()->Units + slice(index,idim)->HyperTable + collapse(offset,length,idim)->HyperTable + +TableDataSet + tableCount()->int + getData(i)->Table + getDepend(index)->String[] + getTags(String,i)->Vector + getPlane(name)->DataSet + +VectorDataSet + getData->Vector + getDepend()->String[] + getTags(String)->Vector + getPlane(name)->DataSet + +HyperTableDataSet + slice or collapse to reduce to TableDataSet + diff --git a/dasCore/src/main/java/org/das2/dataset/test/BigVectorDataSet.java b/dasCore/src/main/java/org/das2/dataset/test/BigVectorDataSet.java new file mode 100644 index 000000000..f67ff0081 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/test/BigVectorDataSet.java @@ -0,0 +1,43 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.dataset.test; + +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.Units; +import org.das2.util.monitor.ProgressMonitor; +import java.util.Random; + +/** + * + * @author jbf + */ +public class BigVectorDataSet { + + public static VectorDataSet getDataSet( int size, ProgressMonitor mon ) { + double dsize= (double)size; + + System.err.println("enter getDataSet"); + long t0 = System.currentTimeMillis(); + + Random random = new Random(0); + + VectorDataSetBuilder vbd = new VectorDataSetBuilder(Units.dimensionless, Units.dimensionless); + double y = 0; + for (int i = 0; i < size; i += 1) { + y += random.nextDouble() - 0.5; + if (i % 100 == 10) { + vbd.insertY( i / dsize, Units.dimensionless.getFillDouble()); + } else { + vbd.insertY( i / dsize, y); + } + } + vbd.setProperty(DataSet.PROPERTY_X_MONOTONIC, Boolean.TRUE); + VectorDataSet vds = vbd.toVectorDataSet(); + System.err.println("done getDataSet in " + (System.currentTimeMillis() - t0) + " ms"); + return vds; + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/test/ContourMeDataSet.java b/dasCore/src/main/java/org/das2/dataset/test/ContourMeDataSet.java new file mode 100644 index 000000000..9b646f506 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/test/ContourMeDataSet.java @@ -0,0 +1,34 @@ +/* + * ContourMeDataSet.java + * + * Created on June 24, 2004, 9:23 PM + */ + +package org.das2.dataset.test; + +/** + * + * @author jbf + */ +public class ContourMeDataSet extends FunctionTableDataSet { + + /** Creates a new instance of ContourMeDataSet */ + public ContourMeDataSet() { + //xtags=101; + //ytags=101; + super(31,31); + xtags=31; + ytags=31; + fillCache(); + } + + public double getDoubleImpl(int i, int j, org.das2.datum.Units units) { + i= i - this.xtags / 2; + j= j - this.ytags / 2; + double d= (i*i+j*j); + //double a= Math.atan2(j,i); + //return d + xtags/40*Math.cos( a * 20 ); + return d; + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/test/DistTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/test/DistTableDataSet.java new file mode 100644 index 000000000..5a509e519 --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/test/DistTableDataSet.java @@ -0,0 +1,54 @@ +/* + * DistTableDataSet.java + * + * Created on May 21, 2004, 2:58 PM + */ + +package org.das2.dataset.test; + +/** + * + * @author Jeremy + */ +public class DistTableDataSet extends FunctionTableDataSet { +/* on_error,2 ;Return to caller if an error occurs +x=findgen(n) ;Make a row +x = (x < (n-x)) ^ 2 ;column squares +if n_elements(m) le 0 then m = n + +a = FLTARR(n,m,/NOZERO) ;Make array + +for i=0L, m/2 do begin ;Row loop + y = sqrt(x + i^2) ;Euclidian distance + a[0,i] = y ;Insert the row + if i ne 0 then a[0, m-i] = y ;Symmetrical + endfor +return,a +end */ + + /** Creates a new instance of DistTableDataSet */ + public DistTableDataSet( int size ) { + super( size,size ); + fillCache(); + } + + public double getDoubleImpl(int i, int j, org.das2.datum.Units units) { + int m= xtags/2; + int n= ytags/2; + if ( i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.dataset.test; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import java.util.*; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.XSliceDataSet; +import org.das2.dataset.YSliceDataSet; + +/** + * + * @author jbf + */ +public abstract class FunctionTableDataSet implements TableDataSet { + + Units zUnits= Units.dimensionless; + Units yUnits= Units.dimensionless; + Units xUnits= Units.dimensionless; + protected int ytags; + protected int xtags; + + double[] data; + + HashMap properties; + + public abstract double getDoubleImpl(int i, int j, Units units); + + public FunctionTableDataSet( int nx, int ny ) { + xtags= nx; + ytags= ny; + data= new double[nx*ny]; + for ( int i=0; i0 ) throw new IllegalArgumentException("table doesn't exist: "+table); + return yUnits.convertDoubleTo(units, (double)j); + } + + public int getYTagInt(int table, int j, Units units) { + return (int)getYTagDouble(table, j, units); + } + + public Units getYUnits() { + return yUnits; + } + + public Units getZUnits() { + return zUnits; + } + + public int tableCount() { + return 1; + } + + public int tableEnd(int table) { + return xtags; + } + + public int tableOfIndex(int i) { + return 0; + } + + public int tableStart(int table) { + return 0; + } + + public DatumVector getYTags(int table) { + double[] tags = new double[getYLength(table)]; + for (int j = 0; j < tags.length; j++) { + tags[j] = getYTagDouble(table, j, yUnits); + } + return DatumVector.newDatumVector(tags, yUnits); + } + + public String toString() { + return TableUtil.toString(this); + } +} diff --git a/dasCore/src/main/java/org/das2/dataset/test/MendelbrotDataLoader.java b/dasCore/src/main/java/org/das2/dataset/test/MendelbrotDataLoader.java new file mode 100644 index 000000000..e0c6aba3e --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/test/MendelbrotDataLoader.java @@ -0,0 +1,208 @@ +/* + * MendelbrotDataSetDescriptor.java + * + * Created on May 11, 2004, 10:26 AM + */ + +package org.das2.dataset.test; + +import org.das2.DasException; +import org.das2.dataset.DataSet; +import org.das2.datum.Units; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.graph.DataLoader; +import org.das2.graph.Renderer; +import org.das2.system.DasLogger; +import org.das2.system.RequestProcessor; +import org.das2.util.monitor.ProgressMonitor; +import java.util.logging.Logger; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.WritableTableDataSet; + + +/** + * + * @author Jeremy + */ +public class MendelbrotDataLoader extends DataLoader { + /* we store the yaxis to kludge in the yresolution, range */ + + int limit=200; + int overSampleFactor= 1; + + Request currentRequest; + Request completedRequest; + + Logger logger= DasLogger.getLogger(DasLogger.GRAPHICS_LOG, "MendelBrotDataLoader" ); + + /** Creates a new instance of MendelbrotDataSetDescriptor */ + public MendelbrotDataLoader( Renderer r ) { + super(r); + } + + private final float punktfarbe(double xwert, double ywert) {// color value from 0.0 to 1.0 by iterations + double r = 0.0, i = 0.0, m = 0.0; + int j = 0; + final int MAX=limit; + + while ((j < MAX) && (m < 4.0)) { + j++; + m = r * r - i * i; + i = 2.0 * r * i + ywert; + r = m + xwert; + } + if ( j==MAX ) j=0; + return (float)j; + } + + public void update( ) { + if ( isActive() ) { + DasPlot p= getRenderer().getParent(); + + if ( p==null ) return; + + DasAxis xAxis= p.getXAxis(); + DasAxis yAxis= p.getYAxis(); + + if ( xAxis.valueIsAdjusting() || yAxis.valueIsAdjusting() ) return; + + final RebinDescriptor xRebinDescriptor = new RebinDescriptor( + xAxis.getDataMinimum(), xAxis.getDataMaximum(), + xAxis.getColumn().getWidth(), + xAxis.isLog()); + + final RebinDescriptor yRebinDescriptor = new RebinDescriptor( + yAxis.getDataMinimum(), yAxis.getDataMaximum(), + yAxis.getRow().getHeight(), + yAxis.isLog()); + + if ( currentRequest!=null ) { + if ( ! ( xAxis.getMemento().equals( currentRequest.xmem ) || yAxis.getMemento().equals( currentRequest.ymem ) ) ) { + logger.fine( "cancel old request" ); + currentRequest.monitor.cancel(); + } else { + logger.fine( "ignore repeat request" ); + return; // ignore the repeated request + } + } + + final String taskDescription= "mendelbrot x:"+xAxis.getMemento()+" y:"+ yAxis.getMemento(); + + if ( completedRequest!=null ) { + if ( ( xAxis.getMemento().equals( completedRequest.xmem ) && yAxis.getMemento().equals( completedRequest.ymem ) ) ) { + logger.fine( "ignore satisfied request "+taskDescription ); + return; + } + } + + currentRequest= new DataLoader.Request( getMonitor(taskDescription), xAxis.getMemento(), yAxis.getMemento() ); + + Runnable run= new Runnable() { + public void run( ) { + try { + logger.fine( "calculate dataset for "+taskDescription ); + DataSet result= getDataSet( xRebinDescriptor, yRebinDescriptor, getMonitor(taskDescription) , taskDescription ); + System.err.println( result.getProperty("TaskDescription") ); + getRenderer().setDataSet( result ); + completedRequest= currentRequest; + logger.fine( "completed "+taskDescription ); + currentRequest= null; + } catch ( DasException e ) { + getRenderer().setException( e ); + } + + } + }; + RequestProcessor.invokeAfter( run, this.getRenderer() ); + } + } + + private DataSet getDataSet( RebinDescriptor ddx, RebinDescriptor ddy, ProgressMonitor monitor, String desc) throws DasException { + + double xstart, xend, xresolution; + xstart= ddx.binCenter(0, Units.dimensionless ); + xend= ddx.binCenter( ddx.numberOfBins()-1, Units.dimensionless ); + xresolution= ddx.binWidth() / overSampleFactor; + + double ystart, yend, yresolution; + ystart= ddy.binCenter(0, Units.dimensionless ); + yend= ddy.binCenter( ddy.numberOfBins()-1, Units.dimensionless ); + yresolution= ddy.binWidth() / overSampleFactor; + + int ny= (int)(1.5+((yend-ystart)/yresolution)); + int nx= (int)(1.5+((xend-xstart)/xresolution)); + + WritableTableDataSet result= WritableTableDataSet.newSimple( nx, Units.dimensionless, ny, Units.dimensionless, Units.dimensionless ); + + double[][] z= new double[nx][ny]; + + monitor.setTaskSize(ny); + monitor.started(); + for ( int iy=0; iy + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.dataset.test; + +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Units; +import org.das2.datum.Datum; + +/** + * + * @author jbf + */ +public class OrbitVectorDataSet implements VectorDataSet { + + static double[][] data; + private double[][] idata; // pixel space + + private static OrbitVectorDataSet xview= new OrbitVectorDataSet(0); + private static OrbitVectorDataSet yview= new OrbitVectorDataSet(1); + + Units xunits; + Units yunits; + Units tunits; + int view; + + private OrbitVectorDataSet(int view) { + data= new double[3][40]; + for ( int i=0; iymin ) builder.insertY(x, eval(x)); + } + if ( eval(x1)>ymin ) builder.insertY(x1, eval(x1)); + return builder.toVectorDataSet(); + } + + private double eval(double x) { + double y = 0; + for (int ic = c.length - 1; ic >= 0; ic--) { + y = y = c[ic] + x * y; + } + return y; + } + + public Units getXUnits() { + return xUnits; + } + + @SuppressWarnings("deprecation") + public static void main(String[] args) { + + double[] c = {90.0, 3.0, -1.0}; + PolynomialDataSetDescriptor dsd = new PolynomialDataSetDescriptor(c, Units.dimensionless, Units.dimensionless, Datum.create(1.0)); + + DasAxis xAxis = new DasAxis(Datum.create(-10.0), Datum.create(10.0), DasAxis.BOTTOM); + DasAxis yAxis = new DasAxis(Datum.create(0.0), Datum.create(100.0), DasAxis.LEFT); + DasPlot plot = new DasPlot(xAxis, yAxis); + plot.addRenderer(new SymbolLineRenderer(dsd)); + DasCanvas canvas = new DasCanvas(400, 400); + canvas.add(plot, new DasRow(canvas, 0.1, 0.9), new DasColumn(canvas, 0.1, 0.9)); + + JFrame frame = new JFrame("Polynomial"); + frame.setContentPane(canvas); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + +} diff --git a/dasCore/src/main/java/org/das2/dataset/test/RipplesDataSet.java b/dasCore/src/main/java/org/das2/dataset/test/RipplesDataSet.java new file mode 100755 index 000000000..6ec279b5a --- /dev/null +++ b/dasCore/src/main/java/org/das2/dataset/test/RipplesDataSet.java @@ -0,0 +1,81 @@ +/* File: RipplesDataSet.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 18, 2003, 12:52 PM by __FULLNAME__ <__EMAIL__> + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.dataset.test; + +import org.das2.dataset.TableDataSet; +import org.das2.datum.Units; + +/** + * + * @author jbf + */ +public final class RipplesDataSet extends FunctionTableDataSet implements TableDataSet { + + double x1,y1,p1; + double x2,y2,p2; + + public RipplesDataSet( ) { + this( 2, 3, 1, 13, 15, 2, 30, 30 ); + } + + /** + * creates a dataset that is the sum of two rippley functions that look appealling + * and are useful for testing. + * @param x1 the x coordinate of the first ripple source + * @param y1 the y coordinate of the first ripple source + * @param p1 the radius of the first ripple + * @param x2 the x coordinate of the first ripple source + * @param y2 the y coordinate of the first ripple source + * @param p2 the radius of the first ripple + * @param xlength the number of columns in the dataset. + * @param ylength the number of rows in the dataset. + */ + public RipplesDataSet( double x1, double y1, double p1, double x2, double y2, double p2, int xlength, int ylength ) { + super(xlength,ylength); + this.x1= x1; + this.y1= y1; + this.p1= p1; + this.x2= x2; + this.y2= y2; + this.p2= p2; + fillCache(); + } + + public double getDoubleImpl(int i, int j, Units units) { + double x= getXTagDouble(i,xUnits); + double y= getYTagDouble(0,j,yUnits); + if (12. + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.dataset.test; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.DasException; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.DefaultTableDataSet; +import org.das2.util.monitor.ProgressMonitor; + +/** + * + * @author jbf + */ +public class RipplesDataSetDescriptor extends DataSetDescriptor { + + double x1,y1,p1; + double x2,y2,p2; + int nx,ny; + + public RipplesDataSetDescriptor( ) { + this( 14, 17, 10, 20, 60, 15 , 100, 100 ); + } + + /** + * creates a DataSetDescriptor (note the range and resolution is ignored--an unneccessary use + * since Render now has setDataSet method) that is the sum of two ripples. + * @param x1 the x coordinate of the first ripple source + * @param y1 the y coordinate of the first ripple source + * @param p1 the radius of the first ripple + * @param x2 the x coordinate of the first ripple source + * @param y2 the y coordinate of the first ripple source + * @param p2 the radius of the first ripple + * @param nx the number of columns in the dataset. + * @param ny the number of rows in the dataset. + */ + public RipplesDataSetDescriptor( double x1, double y1, double p1, double x2, double y2, double p2 , int nx, int ny ) { + this.x1= x1; + this.y1= y1; + this.p1= p1; + this.x2= x2; + this.y2= y2; + this.p2= p2; + this.nx= nx; + this.ny= ny; + } + + public Units getXUnits() { + return Units.dimensionless; + } + + public org.das2.datum.Units getYUnits() { + return Units.dimensionless; + } + + public org.das2.datum.Units getZUnits() { + return Units.dimensionless; + } + + public DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + + double[] x= new double[nx]; + double[] y= new double[ny]; + double[][] z= new double[nx][ny]; + + monitor.setTaskSize(x.length); + monitor.started(); + + for (int i=0; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.dataset.test; + +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.DasException; +import org.das2.util.monitor.ProgressMonitor; +import java.util.*; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.DefaultVectorDataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.DataSet; + +/** + * + * @author jbf + */ +public class SineWaveDataSetDescriptor extends DataSetDescriptor { + + Datum amplitude; + Datum period; + Datum phase; + + /** + * Creates a new instance of SineWaveDataSetDescriptor with arbitrary phase. + * @param amplitude the amplitude of the signal. + * @param period is in the offset units of phase. + * + */ + public SineWaveDataSetDescriptor( Datum amplitude, Datum period ) { + this( amplitude, period, null ); + } + + /** + * Creates a new instance of SineWaveDataSetDescriptor + * @param amplitude the amplitude of the signal. + * @param period is in the offset units of phase. + * @param phase datum in the same units as period. null indicates that the phase is arbitrary and will change based on the data request. + * + */ + public SineWaveDataSetDescriptor( Datum amplitude, Datum period, Datum phase ) { + super(null); + if ( 0. == period.doubleValue(period.getUnits() ) ) { + throw new IllegalArgumentException( "period is zero" ); + } + + this.amplitude= amplitude; + this.period= period; + this.phase= phase; + } + + public DataSet getDataSetImpl(Datum start, Datum end, Datum resolution, ProgressMonitor monitor) throws DasException { + if ( resolution==null ) resolution= end.subtract( start ).divide(1000); + int nstep= 2 + (int)(end.subtract(start).doubleValue(resolution.getUnits()) / resolution.doubleValue(resolution.getUnits())); + int stepSize= 1; /* not sure what this is useful for jbf */ + nstep= nstep / stepSize; + + if ( phase==null ) phase= start; + + double[] yvalues= new double[nstep]; + double[] xtags= new double[nstep]; + Units xunits= phase.getUnits(); + Units offsetUnits= period.getUnits(); + Units yunits= amplitude.getUnits(); + + for ( int i=0; i +Classes for creating test data sets. + diff --git a/dasCore/src/main/java/org/das2/datum/Basis.java b/dasCore/src/main/java/org/das2/datum/Basis.java new file mode 100644 index 000000000..602714c72 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/Basis.java @@ -0,0 +1,87 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.datum; + +import java.util.IdentityHashMap; + +/** + * Model a basis vector that defines a dimension. For example, Units.us2000 has the + * getOffsetUnits() -> Units.microseconds and getBasis() -> "since 2000-01-01T00:00". + * + * @author jbf + */ +public class Basis { + + public static final Basis fahrenheit= new Basis( "fahrenheit", "fahrenheit", Basis.physicalZero, 255.370, Units.celciusDegrees ); + public static final Basis kelvin= new Basis( "kelvin", "kelvin", Basis.physicalZero, 0, Units.celciusDegrees ); + public static final Basis centigrade= new Basis( "centigrade", "centigrade", Basis.physicalZero, 273.15, Units.celciusDegrees ); + + public static final Basis since2000= new Basis( "since2000", "since 2000-01-01T00:00Z", null, 0, null ); + public static final Basis since2010= new Basis( "since2010", "since 2010-01-01T00:00Z", since2000, 315619200., Units.seconds ); + public static final Basis since1980= new Basis( "since1980", "since 1980-01-01T00:00Z", since2000, -631152000., Units.seconds ); + public static final Basis since1970= new Basis( "since1970", "since 1970-01-01T00:00Z", since2000, -938044800., Units.seconds ); + public static final Basis since1958= new Basis( "since1958", "since 1958-01-01T00:00Z", since2000, -1325376000., Units.seconds ); + public static final Basis modifiedJulian= new Basis( "modifiedJulian", "since 1858-11-17T00:00Z", since2000, 4453401600., Units.seconds ); + public static final Basis since0000= new Basis( "since0000", "since 01-Jan-0000T00:00Z", since2000, 63113904000., Units.seconds ); + + /** + * special basis representing physical zero for all combinations of physical units. + */ + public static final Basis physicalZero= new Basis( "", "physical zero", null, 0, null ); + + private IdentityHashMap bases; + + private String id; + private String description; + private Basis parent; + + public Basis( String id, String description, Basis parent, double d, Units offsetUnits ) { + this.id= id; + this.description= description; + this.parent= parent; + if ( parent!=null ) { + parent.bases.put( this, offsetUnits.createDatum(d) ); + } else { + bases= new IdentityHashMap(); + } + } + + /** + * return the location of this basis in given basis, in the given units. + * @param basis + * @param u + * @return + */ + double getOffset( Basis basis, Units u ) { + if ( parent==null ) { + return bases.get(basis).doubleValue(u); + } else { + double d0= parent.bases.get(this).doubleValue(u); + double d1= parent.bases.get(basis).doubleValue(u); + return d0 - d1; + } + } + + /** + * specify offset to another basis. Register to + * @param toBasis + * @param d + * @param u + */ + public void registerConverter( Basis toBasis, double d, Units u ) { + bases.put( toBasis, u.createDatum(d) ); + } + + + public static void main( String[] args ) { + Basis since2010= new Basis( "since2010", "since 2010-01-01T00:00Z", Basis.since2000, 315619200000000.0, Units.microseconds ); + Basis since2011= new Basis( "since2011", "since 2011-01-01T00:00Z", Basis.since2000, 347155200000000.0, Units.microseconds ); + System.err.println( since2011.getOffset(since2010, Units.days )); + + System.err.println( centigrade.getOffset( fahrenheit, Units.fahrenheitDegrees ) ); + + } +} diff --git a/dasCore/src/main/java/org/das2/datum/BestFormatter.txt b/dasCore/src/main/java/org/das2/datum/BestFormatter.txt new file mode 100644 index 000000000..56219da55 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/BestFormatter.txt @@ -0,0 +1,37 @@ +Best Formatter + +Purpose: for a group of numbers, select a formatter that correctly and + efficiently represents each number of the set. + +Cases: + 1. linearly spaced. + 2. logarithmically spaced. + 3. arbitary set of similar magnitude + 4. arbitary set of differing magnitudes + 3a. similar, large magnitude + 3b. similar, order-one magnitude + +Is (1) an instance of (3), and (2) an instance of (4)? + +Case 3b solution: + Find the greatest common divisor of the numbers, and let this +set the number of fractional decimal places. + +Case 3a solution: + Identify the base exponent to make each number order one, specifically +so the mantissas of the numbers vary from 1-100. Then use gcd to determine +number of digimal places, and express each number with the common exponent +pulled out. + +Case 4 solution: +??? + + + +Case 3 examples: +dv= DatumVector.newDatumVector( new double[] { 100000, 125000, 105000 }, Units.dimensionless ); + +Case 4 examples: +dv= DatumVector.newDatumVector( new double[] { 0.002, 2.001, 105.001 }, Units.dimensionless ); +dv= DatumVector.newDatumVector( new double[] { 1e3, 1e6, 1e9, 2.345e7 }, Units.dimensionless ); +dv= DatumVector.newDatumVector( new double[] { 0.001, 1.0, 1000.00 }, Units.dimensionless ); diff --git a/dasCore/src/main/java/org/das2/datum/CalendarTime.java b/dasCore/src/main/java/org/das2/datum/CalendarTime.java new file mode 100644 index 000000000..db6161068 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/CalendarTime.java @@ -0,0 +1,1016 @@ +package org.das2.datum; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.StringTokenizer; +import java.util.TimeZone; + +/** Represents a point in time, over thousands of years to nano-second resolution. + * The Gegorian calendar is extended in both directions, which makes little sense + * for dates prior to it's adoption. + * + * @author ljg, eew, jbf, cwp + */ +public class CalendarTime implements Comparable{ + + /** The time point's year number. + * Note: that year 1 BC is represented year 0 in this field. + */ + protected int m_nYear; + + /** The time point's month of year, normalized range is 1 to 12 */ + protected int m_nMonth; + + /** The time point's day of month, normalized range is 1 up to 31 + * depending on the month rValue. + */ + protected int m_nDom; + + // Cash the day of year calculation after a normalize. + protected int m_nDoy; + + /** The time point's hour of day, normalized range is 0 to 23 */ + protected int m_nHour; + + /** The time point's minute of hour, normalized range is 0 to 59 */ + protected int m_nMinute; + + /** The time point's second of minute, normalized range is 0 to 59. + * Note that leap seconds are not handled by this class, though it + * wouldn't be hard to do so. + */ + protected int m_nSecond; + + /** The time point's nanosecond of second, normalized range is 0 to 999,999,999 */ + protected long m_nNanoSecond; + + + //////////////////////////////////////////////////////////////////////////////////// + /** Empty constructor */ + + public CalendarTime(){ + m_nYear = 1; + m_nMonth = 1; + m_nDom = 1; + m_nDoy = 1; + m_nHour = 0; + m_nMinute = 0; + m_nSecond = 0; + m_nNanoSecond = 0; + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Static method to create a calender time set to now */ + static public CalendarTime now(){ + + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + + + CalendarTime ct = new CalendarTime(); + ct.m_nYear = cal.get(Calendar.YEAR); + ct.m_nMonth = cal.get(Calendar.MONTH) + 1; + ct.m_nDom = cal.get(Calendar.DAY_OF_MONTH); + ct.m_nDoy = cal.get(Calendar.DAY_OF_YEAR); + ct.m_nHour = cal.get(Calendar.HOUR_OF_DAY); + ct.m_nMinute = cal.get(Calendar.MINUTE); + ct.m_nSecond = cal.get(Calendar.SECOND); + ct.m_nNanoSecond = cal.get(Calendar.MILLISECOND) * 1000000L; + + return ct; + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Tuple constructor + * @param lFields an array of integer fields. Values are assumed to be in largest + * time span to smallest time span. The array may be null. Up to 7 items will + * be used from the array in the order: year, month, day, hour, min, sec, + * nanosecond. + */ + public CalendarTime(int... lFields){ + m_nYear = 1; + m_nMonth = 1; + m_nDom = 1; + m_nDoy = 1; + m_nHour = 0; + m_nMinute = 0; + m_nSecond = 0; + m_nNanoSecond = 0; + + if(lFields == null) return; + if(lFields.length > 0) m_nYear = lFields[0]; + if(lFields.length > 1) m_nMonth = lFields[1]; + if(lFields.length > 2) m_nDom = lFields[2]; + if(lFields.length > 3) m_nHour = lFields[3]; + if(lFields.length > 4) m_nMinute = lFields[4]; + if(lFields.length > 5) m_nSecond = lFields[5]; + if(lFields.length > 6) m_nNanoSecond = lFields[6]; + + normalize(); + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Copy constructor */ + public CalendarTime(CalendarTime other){ + m_nYear = other.m_nYear; + m_nMonth = other.m_nMonth; + m_nDom = other.m_nDom; + m_nDoy = other.m_nDoy; + m_nHour = other.m_nHour; + m_nMinute = other.m_nMinute; + m_nSecond = other.m_nSecond; + m_nNanoSecond = other.m_nNanoSecond; + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Constructing a calendar time from a time string. + * Taken from Larry Granroth's Das1 lib. + */ + public CalendarTime(String s) throws ParseException{ + + final int DATE = 0; + final int YEAR = 1; + final int MONTH = 2; + final int DAY = 3; + final int HOUR = 4; + final int MINUTE = 5; + final int SECOND = 6; + + final String DELIMITERS = " \t/-:,_;\r\n"; + final String PDSDELIMITERS = " \t/-T:,_;\r\n"; + + final String[] months = { + "january", "febuary", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december" + }; + final String[] mons = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + // Starting values for this object, does not default to current year. + m_nYear = 0; + m_nMonth = 0; + m_nDom = 0; + m_nDoy = 0; + m_nHour = 0; + m_nMinute = 0; + m_nSecond = 0; + m_nNanoSecond = 0; + + String[] lToks = new String[10]; + + int[] format = new int[7]; + + int ptr; + + int hold; + + int tokIndex; + + /* handle ISO8601 time format */ + String delimiters = DELIMITERS; + int iC; + if((iC = s.indexOf((int) 'Z')) != -1){ + s = s.substring(0, iC); + } + int end_of_date = s.indexOf((int) 'T'); + if(end_of_date != -1){ + iC = end_of_date - 1; + if(Character.isDigit(s.charAt(iC))){ + delimiters = PDSDELIMITERS; + } + else{ + end_of_date = -1; + } + } + + /* if not PDS then count out 3 non-space delimiters */ + int nTokens = 0; + if(end_of_date == -1){ + nTokens = 0; + int nLen = s.length(); + for(int i = 0; i < nLen; i++){ + if((iC = (delimiters.substring(2)).indexOf(s.charAt(i))) != -1){ + nTokens++; + } + if(nTokens == 3){ + end_of_date = i; + break; + } + } + } + + /* tokenize the time string */ + StringTokenizer st = new StringTokenizer(s, delimiters); + + if(!st.hasMoreTokens()) + throw new java.text.ParseException("No tokens in '" + s + "'", 0); + + for(nTokens = 0; nTokens < 10 && st.hasMoreTokens(); nTokens++) + lToks[nTokens] = st.nextToken(); + + boolean[] lWant = new boolean[]{false, false, false, false, false, false, false}; + lWant[DATE] = lWant[YEAR] = lWant[MONTH] = lWant[DAY] = true; + hold = 0; + + tokIndex = -1; + + // The big token parser loop, each iteration handles one token from the input string + for(int i = 0; i < nTokens; i++){ + tokIndex = s.indexOf(lToks[i], tokIndex + 1); + if((end_of_date != -1) && lWant[DATE] && tokIndex > end_of_date){ + lWant[DATE] = false; + lWant[HOUR] = lWant[MINUTE] = lWant[SECOND] = true; + } + + int nTokLen = lToks[i].length(); + double rValue; + + // skip 3-digit day-of-year values in parenthesis + if ((nTokLen == 5) && lToks[i].startsWith("(") && lToks[i].endsWith(")")) { + try{ + rValue = Double.parseDouble(lToks[i].substring(1, nTokLen-2)); + } + catch(NumberFormatException e){ + throw new ParseException("Error in token '"+lToks[i]+"'", 1); + } + if ((rValue > 0) && (rValue < 367)) continue; + } + + try{ + rValue = Double.parseDouble(lToks[i]); + } + catch(NumberFormatException e){ + if(nTokLen < 3 || !lWant[DATE]){ + throw new ParseException("Error at token '"+lToks[i]+"' in '"+s+"'", 0); + } + for(int j = 0; j < 12; j++){ + if(lToks[i].equalsIgnoreCase(months[j]) || + lToks[i].equalsIgnoreCase(mons[j])) { + m_nMonth = j + 1; + lWant[MONTH] = false; + if(hold > 0){ + if(m_nDom > 0) + throw new ParseException("Ambiguous dates in token '" + lToks[i] + + "' in '" + s + "'", 0); + m_nDom = hold; + hold = 0; + lWant[DAY] = false; + } + break; + } + } + if(lWant[MONTH]) + throw new ParseException("Error at token '"+lToks[i]+"' in '"+s+"'", 0); + continue; + } + + if(Math.IEEEremainder(rValue, 1.0) != 0.0){ + if(lWant[SECOND]){ + //Round normally to nearest nanosecond + long nTmp = Math.round( rValue * 1.0e+9); + m_nSecond = (int) ((long)nTmp / 1000000000L); + m_nNanoSecond = (long)nTmp % 1000000000L; + break; + } + else{ + throw new ParseException("Error at token '"+lToks[i]+"' in '"+s+"'", 0); + } + } + + int number = (int) rValue; + if(number < 0){ + throw new ParseException("Error at token '"+lToks[i]+"' in '"+s+"'", 0); + } + + if(lWant[DATE]){ + + if(number == 0){ + throw new ParseException("m,d, or y can't be 0 in '" + s + "'", 0); + } + + if(number >= 10000000 && lWant[YEAR]){ // %Y%m%d + m_nYear = number / 10000; + lWant[YEAR] = false; + m_nMonth = number / 100 % 100; + lWant[MONTH] = false; + m_nDom = number % 100; + m_nDoy = 0; + lWant[DAY] = false; + } + else if(number >= 1000000 && lWant[YEAR]){ //%Y%j + m_nYear = number / 1000; + lWant[YEAR] = false; + m_nDoy = number % 1000; + m_nMonth = 0; + lWant[MONTH] = false; + lWant[DAY] = false; + + } + else if(number > 31){ + + if(lWant[YEAR]){ + m_nYear = number; + if(m_nYear < 1000){ + m_nYear += 1900; + } + lWant[YEAR] = false; + } + else if(lWant[MONTH]){ + lWant[MONTH] = false; + m_nMonth = 0; + m_nDoy = number; + lWant[DAY] = false; + } + else{ + throw new ParseException("Error at token '"+lToks[i]+"' in '"+s+"'", 0); + } + + } + else if(number > 12){ + + if(lWant[DAY]){ + if(hold > 0){ + m_nMonth = hold; + lWant[MONTH] = false; + } + if(nTokLen == 3){ + if(m_nMonth > 0){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nDoy = number; + m_nDom = 0; + lWant[MONTH] = false; + } + else{ + m_nDom = number; + } + lWant[DAY] = false; + } + else{ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + + } + else if(!lWant[MONTH]){ + + if(m_nMonth > 0){ + m_nDom = number; + m_nDoy = 0; + } + else{ + m_nDoy = number; + m_nDom = 0; + } + lWant[DAY] = false; + + } + else if(!lWant[DAY]){ + + if(m_nDoy > 0){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nMonth = number; + lWant[MONTH] = false; + + } + else if(!lWant[YEAR]){ + + if(nTokLen == 3){ + if(m_nMonth > 0){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nDoy = number; + m_nDom = 0; + lWant[DAY] = false; + } + else{ + if(m_nDoy > 0){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nMonth = number; + if(hold > 0){ + m_nDom = hold; + lWant[DAY] = false; + } + } + lWant[MONTH] = false; + + } + else if(hold > 0){ + + m_nMonth = hold; + hold = 0; + lWant[MONTH] = false; + m_nDom = number; + lWant[DAY] = false; + + } + else{ + hold = number; + } + + if(!(lWant[YEAR] || lWant[MONTH] || lWant[DAY])){ + lWant[DATE] = false; + lWant[HOUR] = lWant[MINUTE] = lWant[SECOND] = true; + } + + } + else if(lWant[HOUR]){ + + if(nTokLen == 4){ + hold = number / 100; + // TODO: handle times like Jan-1-2001T24:00 --> Jan-2-2001T00:00, for ease of modifying times + if(hold > 23){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nHour = hold; + hold = number % 100; + if(hold > 59){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nMinute = hold; + lWant[MINUTE] = false; + } + else{ + if(number > 23){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nHour = number; + } + lWant[HOUR] = false; + + } + else if(lWant[MINUTE]){ + // TODO: handle times like 0:90 --> 1:30, for ease of modifying times + if(number > 59){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nMinute = number; + lWant[MINUTE] = false; + + } + else if(lWant[SECOND]){ + + if(number > 61){ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + m_nSecond = number; + lWant[SECOND] = false; + + } + else{ + throw new ParseException("Error at token '" + lToks[i] + "' in '" + s + "'", 0); + } + + } + // End of token parsing loop + + if(m_nMonth > 12){ + throw new ParseException("Month is greater than 12 in '" + s + "'", 0); + } + if(m_nMonth > 0 && m_nDom <= 0){ + m_nDom = 1; + } + + int iLeap = ((m_nYear % 4) != 0 ? 0 : ((m_nYear % 100) > 0 ? 1 : ((m_nYear % 400) > 0 ? 0 : 1))); + + if((m_nMonth > 0) && (m_nDom > 0) && (m_nDoy == 0)){ + if(m_nDom > TimeUtil.daysInMonth[iLeap][m_nMonth]){ + throw new java.text.ParseException("day of month too high in '" + s + "'", 0); + } + m_nDoy = TimeUtil.dayOffset[iLeap][m_nMonth] + m_nDom; + } + else if((m_nDoy > 0) && (m_nMonth == 0) && (m_nDom == 0)){ + if(m_nDoy > (365 + iLeap)){ + throw new java.text.ParseException("day of year too high in '" + s + "'", 0); + } + int i = 2; + while(i < 14 && m_nDoy > TimeUtil.dayOffset[iLeap][i]) i++; + i--; + m_nMonth = i; + m_nDom = m_nDoy - TimeUtil.dayOffset[iLeap][i]; + } + else{ + if(m_nMonth == 0){ + m_nMonth = 1; + } + m_nDom = 1; + } + + // Okay, hit nomalize, looking at the code above, we know that the seconds + // field can be way over value, others may be too. + normalize(); + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Construct from a datum. + * splits the time location datum into y,m,d,etc components. Note that + * seconds is a double, and micros will be 0. + * + * @param datum with time location units + */ + public CalendarTime(Datum datum){ + double microseconds = TimeUtil.getMicroSecondsSinceMidnight(datum); + Datum sansMicros = datum.subtract(microseconds, Units.microseconds); + + int jd = TimeUtil.getJulianDay(sansMicros); + if(jd < 0) + throw new IllegalArgumentException("julian day is negative."); + + int[] lDate = TimeUtil.julianToGregorian(jd); + m_nYear = lDate[0]; + m_nMonth = lDate[1]; + m_nDom = lDate[2]; + m_nNanoSecond = Math.round( microseconds * 1000); + normalize(); + } + + //////////////////////////////////////////////////////////////////////////////////// + @Override + public String toString(){ + return m_nYear + "/" + m_nMonth + "/" + m_nDom + " " + m_nHour + ":" + m_nMinute + ":" + m_nSecond + + "." + m_nNanoSecond; + } + + public boolean isLeapYear(){ return TimeUtil.isLeapYear(m_nYear); } + + @Override + public int compareTo(CalendarTime o){ + if(m_nYear != o.m_nYear) return m_nYear - o.m_nYear; + if(m_nMonth != o.m_nMonth) return m_nMonth - o.m_nMonth; + if(m_nDom != o.m_nDom) return m_nDom - o.m_nDom; + if(m_nHour != o.m_nHour) return m_nHour - o.m_nHour; + if(m_nMinute != o.m_nMinute) return m_nMinute - o.m_nMinute; + if(m_nSecond != o.m_nSecond) return m_nSecond - o.m_nSecond; + if(m_nNanoSecond < o.m_nNanoSecond) return -1; + if(m_nNanoSecond > o.m_nNanoSecond) return 1; + + return 0; + } + + @Override + public boolean equals(Object o){ + if(! (o instanceof CalendarTime) ) return false; + + CalendarTime ctO = (CalendarTime)o; + + if(m_nYear != ctO.m_nYear) return false; + if(m_nMonth != ctO.m_nMonth) return false; + if(m_nDom != ctO.m_nDom) return false; + if(m_nHour != ctO.m_nHour) return false; + if(m_nMinute != ctO.m_nMinute) return false; + if(m_nSecond != ctO.m_nSecond) return false; + if(m_nNanoSecond != ctO.m_nNanoSecond) return false; + + return true; + } + + @Override + public int hashCode(){ + int hash = 7; + hash = 71 * hash + this.m_nYear; + hash = 71 * hash + this.m_nMonth; + hash = 71 * hash + this.m_nDom; + hash = 71 * hash + this.m_nHour; + hash = 71 * hash + this.m_nMinute; + hash = 71 * hash + this.m_nSecond; + hash = 71 * hash + (int) (this.m_nNanoSecond ^ (this.m_nNanoSecond >>> 32)); + return hash; + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Resolution flags to use when requesting time point as a string */ + public static enum Resolution { + YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISEC, MICROSEC, NANOSEC + }; + + //////////////////////////////////////////////////////////////////////////////////// + /** Produce an ISO 8601 Date-Time string with up-to nanosecond resolution. + * The primary ISO format uses YYYY-MM-DD style dates. + * + * @param res The resolution of the returned string. + * @return + */ + public String toISO8601(Resolution res){ + switch(res){ + case YEAR: + return String.format("%04d", m_nYear); + case MONTH: + return String.format("%04d-%02d", m_nYear, m_nMonth); + case DAY: + return String.format("%04d-%02d-%02d", m_nYear, m_nMonth, m_nDom); + case HOUR: + return String.format("%04d-%02d-%02dT%02d", m_nYear, m_nMonth, m_nDom, m_nHour); + case MINUTE: + return String.format("%04d-%02d-%02dT%02d:%02d", m_nYear, m_nMonth, m_nDom, m_nHour, m_nMinute); + case SECOND: + return String.format("%04d-%02d-%02dT%02d:%02d:%02d", m_nYear, m_nMonth, m_nDom, m_nHour, + m_nMinute, m_nSecond); + case MILLISEC: + // Let string.format handle rounding for me. + return String.format("%04d-%02d-%02dT%02d:%02d:%02d.%03.0f", m_nYear, m_nMonth, m_nDom, + m_nHour, m_nMinute, m_nSecond, m_nNanoSecond / 1000000.0); + case MICROSEC: + // Let string.format handle rounding for me. + return String.format("%04d-%02d-%02dT%02d:%02d:%02d.%06.0f", m_nYear, m_nMonth, m_nDom, + m_nHour, m_nMinute, m_nSecond, m_nNanoSecond / 1000.0); + case NANOSEC: + // Let string.format handle rounding for me. + return String.format("%04d-%02d-%02dT%02d:%02d:%02d.%09d", m_nYear, m_nMonth, m_nDom, + m_nHour, m_nMinute, m_nSecond, m_nNanoSecond); + } + return null; // added to make the compilier happy. + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Produce an alternate ISO 8601 Date-Time string with up-to nanosecond resolution. + * The alternate format ISO format uses ordinal day-of-year style dates, i.e. + * YYYY-DDD. + * + * @param res The resolution of the returned string. + * @return + */ + public String toAltISO8601(Resolution res){ + switch(res){ + case YEAR: + return String.format("%04d", m_nYear); + case MONTH: + throw new IllegalArgumentException("Alternate ISO time point format doesn't " + + "contain a month number."); + case DAY: + return String.format("%04d-%03d", m_nYear, m_nDoy); + case HOUR: + return String.format("%04d-%03dT%02d", m_nYear, m_nDoy, m_nHour); + case MINUTE: + return String.format("%04d-%03dT%02d:%02d", m_nYear, m_nDoy, m_nHour, m_nMinute); + case SECOND: + return String.format("%04d-%03dT%02d:%02d:%02d", m_nYear, m_nDoy, m_nHour, + m_nMinute, m_nSecond); + case MILLISEC: + // Let string.format handle rounding for me. + return String.format("%04d-%03dT%02d:%02d:%02d.%03.0f", m_nYear, m_nDoy, m_nHour, + m_nMinute, m_nSecond, m_nNanoSecond / 1000000.0); + case MICROSEC: + // Let string.format handle rounding for me. + return String.format("%04d-%03dT%02d:%02d:%02d.%06.0f", m_nYear, m_nDoy, m_nHour, + m_nMinute, m_nSecond, m_nNanoSecond / 1000.0); + case NANOSEC: + // Let string.format handle rounding for me. + return String.format("%04d-%03dT%02d:%02d:%02d.%09d", m_nYear, m_nDoy, m_nHour, + m_nMinute, m_nSecond, m_nNanoSecond); + } + return null; // added to make the compilier happy. + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Normalize date and time components for the Gregorian calendar ignoring leap seconds + * + * From Larry Granroth's original Das1 libs. + */ + private void normalize(){ + + // month is required input -- first adjust month + if( m_nMonth > 12 || m_nMonth < 1){ + // temporarily make month zero-based + m_nMonth--; + m_nYear += m_nMonth / 12; + m_nMonth %= 12; + if(m_nMonth < 0){ + m_nMonth += 12; + m_nYear--; + } + m_nMonth++; + } + + // index for leap year + int iLeap = TimeUtil.isLeapYear(m_nYear)?1:0; + + // day of year is output only -- calculate it + m_nDoy = TimeUtil.dayOffset[iLeap][m_nMonth] + m_nDom; + + // now adjust other items . . . + + // New addition, handle nanoseconds + if(m_nNanoSecond >= 1000000000 || m_nNanoSecond < 0){ + m_nSecond += m_nNanoSecond / 1000000000; + m_nNanoSecond = m_nNanoSecond % 1000000000; + if(m_nNanoSecond < 0){ + m_nNanoSecond += 1000000000; + m_nSecond--; + } + } + + // again, we're ignoring leap seconds + if( m_nSecond >= 60 || m_nSecond < 0){ + m_nMinute += m_nSecond / 60; + m_nSecond = m_nSecond % 60; + if(m_nSecond < 0){ + m_nSecond += 60; + m_nMinute--; + } + } + + if(m_nMinute >= 60 || m_nMinute < 0){ + m_nHour += m_nMinute / 60; + m_nMinute %= 60; + if(m_nMinute < 0){ + m_nMinute += 60; + m_nHour--; + } + } + + if(m_nHour >= 24 ||m_nHour < 0){ + m_nDoy += m_nHour / 24; + m_nHour %= 24; + if(m_nHour < 0){ + m_nHour += 24; + m_nDoy--; + } + } + + /* final adjustments for year and day of year */ + int ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + if(m_nDoy > ndays || m_nDoy < 1){ + while(m_nDoy > ndays){ + m_nYear++; + m_nDoy -= ndays; + ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + } + while(m_nDoy < 1){ + m_nYear--; + ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + m_nDoy += ndays; + } + } + + /* and finally convert day of year back to month and day */ + iLeap = TimeUtil.isLeapYear(m_nYear)?1:0; + while(m_nDoy <= TimeUtil.dayOffset[iLeap][m_nMonth]){ + m_nMonth--; + } + while(m_nDoy > TimeUtil.dayOffset[iLeap][m_nMonth + 1]){ + m_nMonth++; + } + m_nDom = m_nDoy - TimeUtil.dayOffset[iLeap][m_nMonth]; + } + + //////////////////////////////////////////////////////////////////////////////////// + + /** Set the year field. Use set() if you have multiple fields to set. */ + public void setYear(int nYear){ + m_nYear = nYear; + normalize(); + } + /** Set the month field. Use set() if you have multiple fields to set. */ + public void setMonth(int nMonth){ + m_nMonth = nMonth; + normalize(); + } + /** Set the day of month field. Use set() if you have multiple fields to set. */ + public void setDay(int nDay){ + m_nDom = nDay; + normalize(); + } + + /** Set the day of year, and recompute the month and day of month. + * @param nDoy the new day of year. + */ + public void setDayOfYear(int nDoy){ + m_nDoy = nDoy; + + /* final adjustments for year and day of year */ + int ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + if(m_nDoy > ndays || m_nDoy < 1){ + while(m_nDoy > ndays){ + m_nYear++; + m_nDoy -= ndays; + ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + } + while(m_nDoy < 1){ + m_nYear--; + ndays = TimeUtil.isLeapYear(m_nYear) ? 366 : 365; + m_nDoy += ndays; + } + } + + /* and finally convert day of year back to month and day */ + int iLeap = TimeUtil.isLeapYear(m_nYear)?1:0; + while(m_nDoy <= TimeUtil.dayOffset[iLeap][m_nMonth]){ + m_nMonth--; + } + while(m_nDoy > TimeUtil.dayOffset[iLeap][m_nMonth + 1]){ + m_nMonth++; + } + m_nDom = m_nDoy - TimeUtil.dayOffset[iLeap][m_nMonth]; + } + + /** Set the hour field. Use set() if you have multiple fields to set. */ + public void setHour(int nHour){ + m_nHour = nHour; + normalize(); + } + /** Set the minute field. Use set() if you have multiple fields to set. */ + public void setMinute(int nMinute){ + m_nMinute = nMinute; + normalize(); + } + /** Set the second field. Use set() if you have multiple fields to set. */ + public void setSecond(int nSecond){ + m_nSecond = nSecond; + normalize(); + } + /** Set the nanosecond field. Use set() if you have multiple fields to set. */ + public void setNanoSecond(long nNano){ + m_nNanoSecond = nNano; + normalize(); + } + + /** Set (upto) all fields of a calendar time + * + * @param lFields An array of 1 to 7 items whose values will be assigned to the + * year, month, day, hour, minute, second and nanosecond respectively. Also + * integers can be specified one at a time in var-args fashion + */ + public void set(int... lFields){ + + if(lFields.length < 1) return; + + // Cool case fall through (good idea Ed!) + switch(lFields.length){ + default: m_nNanoSecond = lFields[6]; + case 6: m_nSecond = lFields[5]; + case 5: m_nMinute = lFields[4]; + case 4: m_nHour = lFields[3]; + case 3: m_nDom = lFields[2]; + case 2: m_nMonth = lFields[1]; + case 1: m_nYear = lFields[0]; + } + + normalize(); + } + + ///////////////////////////////////////////////////////////////////////////////////// + + public int year(){return m_nYear;} + public int month(){return m_nMonth;} + public int day(){return m_nDom;} + public int dayOfYear(){ return m_nDoy; } + public int hour(){return m_nHour;} + public int minute(){return m_nMinute;} + public int second(){return m_nSecond;} + public int nanosecond(){return (int)m_nNanoSecond;} + + public int[] get(){ + return new int[]{m_nYear, m_nMonth, m_nDom, m_nHour, m_nMinute, m_nSecond, + (int)m_nNanoSecond}; + } + + + //////////////////////////////////////////////////////////////////////////////////// + + /** Used to step add to a calendar time by 1 or more integer units. */ + public enum Step { + YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, NANOSEC, HALF_YEAR, QUARTER, + MILLISEC, MICROSEC; + + public static Step HigerStep(Step step){ + switch(step){ + case DAY: return MONTH; + case HOUR: return DAY; + case MINUTE: return HOUR; + case SECOND: return MINUTE; + case MILLISEC: return SECOND; + case MICROSEC: return MILLISEC; + case NANOSEC: return MICROSEC; + default: return YEAR; + } + } + + public static Step LowerStep(Step step){ + switch(step){ + case YEAR: return MONTH; + case MONTH: return DAY; + case DAY: return HOUR; + case HOUR: return MINUTE; + case MINUTE: return SECOND; + case SECOND: return MILLISEC; + case MILLISEC: return MICROSEC; + default: return NANOSEC; + } + } + } + + /** A convenience method for handling multiple steps at once. + * + * @param steps An array of upto 7 items, each one will step succeedingly smaller + * time fields if present. So the array items are taken to be: + * [year, month, day, hour, minute, second, nanosecond]. + * + * @return A new calendar time stepped as specified. + */ + public CalendarTime step(int lSteps[]){ + CalendarTime ct = new CalendarTime(this); + + Step fields[] = {Step.YEAR, Step.MONTH, Step.DAY, Step.HOUR, Step.MINUTE, + Step.SECOND, Step.NANOSEC}; + for(int i = 0; i < 7; i++){ + if((lSteps.length > i)&&(lSteps[i] != 0)) + ct = ct.step(fields[i], lSteps[i]); + } + + return ct; + } + + //////////////////////////////////////////////////////////////////////////////////// + /** Introduced as a way to increase the efficiency of the time axis tick calculation. + * Step to the next higher ordinal. If the calendar time is already at the ordinal, + * then step by one unit. + * + * @param field The time field to change. Integers for this value are defined in + * TimeUtil + * @param steps number of positive or negative steps to take + * @return + */ + public CalendarTime step(Step field, int steps) { + + CalendarTime ct = new CalendarTime(this); + + if(steps == 0) return ct; + + // First change the relavent field + switch(field){ + case NANOSEC: ct.m_nNanoSecond += steps; break; + case MICROSEC: ct.m_nNanoSecond += 1000*steps; break; + case MILLISEC: ct.m_nNanoSecond += 1000000*steps; break; + case SECOND: ct.m_nSecond += steps; break; + case MINUTE: ct.m_nMinute += steps; break; + case HOUR: ct.m_nHour += steps; break; + case DAY: ct.m_nDom += steps; break; + case MONTH: ct.m_nMonth += steps; break; + case QUARTER: ct.m_nMonth += steps*3; break; + case HALF_YEAR: ct.m_nMonth += steps*6; break; + case YEAR: ct.m_nYear += steps*1; break; + default: + throw new IllegalArgumentException("Unknown time field designator: "+field); + } + + + // Handle zeroing out lower level fields (case fall throught can be handy) + switch(field){ + case YEAR: + ct.m_nMonth = 1; + case HALF_YEAR: + //Map months to a 0-1 half year scale + double dHalfYears = (ct.m_nMonth - 1)/6.0; + ct.m_nMonth = (((int)dHalfYears) * 6) + 1; //Truncates towards zero + case QUARTER: + //Map months to a 0-3 quarterly scale + double dQuarters = (ct.m_nMonth - 1)/3.0; + ct.m_nMonth = (((int)dQuarters) * 3) + 1; //Truncates towards zero + case MONTH: + ct.m_nDom = 1; + case DAY: + ct.m_nHour = 0; + case HOUR: + ct.m_nMinute = 0; + case MINUTE: + ct.m_nSecond = 0; + case MILLISEC: + ct.m_nNanoSecond = (ct.m_nNanoSecond / 1000000) * 1000000; + case MICROSEC: + ct.m_nNanoSecond = (ct.m_nNanoSecond / 1000) * 1000; + } + + ct.normalize(); + return ct; + } + + /** Special handler for changing the nanoseconds, as this field is a long */ + public CalendarTime stepNano(long steps) { + CalendarTime ct = new CalendarTime(this); + ct.m_nNanoSecond += steps; + ct.normalize(); + return ct; + } + + + /////////////////////////////////////////////////////////////////////////////////// + /** Get a time datum in us2000 units. + * + * @return A datum whose value is the number of milliseconds since midnight + * 2000-01-01, ignoring leap seconds. + */ + public Datum toDatum(){ + int jd = 367 * m_nYear - 7 * (m_nYear + (m_nMonth + 9) / 12) / 4 + - 3 * ((m_nYear + (m_nMonth - 9) / 7) / 100 + 1) / 4 + + 275 * m_nMonth / 9 + m_nDom + 1721029; + + double us2000 = (jd - 2451545) * 86400e6; // TODO: leap seconds + + return Datum.create(m_nHour*3600.0e6 + m_nMinute*60e6 + m_nSecond*1e6 + + m_nNanoSecond/1000 + us2000, Units.us2000); + } + + +} diff --git a/dasCore/src/main/java/org/das2/datum/Datum.java b/dasCore/src/main/java/org/das2/datum/Datum.java new file mode 100644 index 000000000..f88f28def --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/Datum.java @@ -0,0 +1,576 @@ +/* File: Datum.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import org.das2.datum.format.DatumFormatter; + + +/** + *

    A Datum is a number in the context of a Unit, for example "15 microseconds.". + * A Datum object has methods for formatting itself as a String, representing + * itsself as a double in the context of another Unit, and mathematical + * operators that allow simple calculations to be made at the physical quantities. + * Also a Datum's precision can be limited to improve formatting.

    + *

    + * @author jbf + */ +public class Datum implements Comparable { + + private Units units; + private Number value; + private double resolution; + private DatumFormatter formatter; + + /** + * class backing Datums with a double. + */ + public static class Double extends Datum { + + Double( Number value, Units units ) { + super( value, units, 0. ); + } + + Double( double value, Units units ) { + super( new java.lang.Double(value), units, 0. ); + } + Double( double value ) { + super( new java.lang.Double(value), Units.dimensionless, 0. ); + } + Double( double value, Units units, double resolution ) { + super( new java.lang.Double(value), units, units.getDatumFormatterFactory().defaultFormatter(), resolution ); + } + + } + + private Datum(Number value, Units units, double resolution ) { + this( value, units, units.getDatumFormatterFactory().defaultFormatter(), resolution ); + } + + private Datum(Number value, Units units, DatumFormatter formatter, double resolution ) { + if ( value==null ) throw new IllegalArgumentException("value is null"); + this.value = value; + this.units = units; + this.resolution= resolution; + this.formatter = formatter; + } + + /** + * returns the datum's double value. This protected method allows subclasses and classes + * within the package to peek at the double value. + * + * @return the double value of the datum in the context of its units. + */ + protected double doubleValue() { + return this.getValue().doubleValue(); + } + + /** + * returns a double representing the datum in the context of units. + * + * @param units the Units in which the double should be returned + * @return a double in the context of the provided units. + */ + public double doubleValue(Units units) { + if ( units!=getUnits() ) { + return getUnits().getConverter(units).convert(this.getValue()).doubleValue(); + } else { + return this.getValue().doubleValue(); + } + } + + /** + * returns the resolution (or precision) of the datum. This is metadata for the datum, used + * primarily to limit the number of decimal places displayed in a string representation, + * but operators like add and multiply will propogate errors through the calculation. + * + * @param units the Units in which the double resolution should be returned. Note + * the units must be convertable to this.getUnits().getOffsetUnits(). + * @return the double resolution of the datum in the context of units. + */ + public double getResolution( Units units ) { + Units offsetUnits= getUnits().getOffsetUnits(); + if ( units!=offsetUnits ) { + return offsetUnits.getConverter(units).convert(this.resolution); + } else { + return this.resolution; + } + } + + /** + * returns the datum's int value. This protected method allows subclasses and classes + * within the package to peek at the value as an integer. (The intent was that a + * Datum might be backed by an integer instead of a double, so that numerical + * round-off issues can be avoided.) + * + * @return the integer value of the datum in the context of its units. + */ + protected int intValue() { + return this.getValue().intValue(); + } + + /** + * returns a int representing the datum in the context of units. + * + * @param units the Units in which the int should be returned + * @return a double in the context of the provided units. + */ + public int intValue(Units units) { + if ( units!=getUnits() ) { + return getUnits().getConverter(units).convert(this.getValue()).intValue(); + } else { + return this.getValue().intValue(); + } + } + + /** + * returns the datum's units. For example, UT times might have the units + * Units.us2000. + * + * @return the datum's units. + */ + public Units getUnits() { + return this.units; + } + + /** + * returns the Number representing the datum's location in the space indentified by its units. + * This protected method allows subclasses and classes + * within the package to peek at the value. (The intent was that a + * Datum might be backed by an integer, float, or double, depending on the application.) + * @return a Number in the context of the provided units. + */ + protected Number getValue() { + return this.value; + } + + /** + * convenience method for checking to see if a datum is a fill datum. + * @return true if the value is fill as defined by the Datum's units. + */ + public boolean isFill() { + return getUnits().isFill(getValue()); + } + + /** + * returns a Datum whose value is the sum of this and datum, in this.getUnits(). + * @return a Datum that is the sum of the two values in this Datum's units. + * @param datum Datum to add, that is convertable to this.getUnits(). + */ + public Datum add( Datum datum ) { + Datum result= add( datum.getValue(), datum.getUnits() ); + result.resolution= Math.sqrt( datum.resolution * datum.resolution + this.resolution * this.resolution ); + return result; + } + + /** + * returns a Datum whose value is the sum of this and value in + * the context of units, in this.getUnits(). + * @param value a Number to add in the context of units. + * @param units units defining the context of value. There should be a converter from + * units to this Datum's units. + * @return value Datum that is the sum of the two values in this Datum's units. + */ + public Datum add( Number value, Units units ) { return getUnits().add( getValue(), value, units ); } + + /** + * returns a Datum whose value is the sum of this and value in + * the context of units, in this.getUnits(). + * @param d a Number to add in the context of units. + * @param units units defining the context of value. There should be a converter from + * units to this Datum's units. + * @return value Datum that is the sum of the two values in this Datum's units. + */ + public Datum add( double d, Units units ) { return add( new java.lang.Double(d), units ); } + + /** + * returns a Datum whose value is the difference of this and value. + * The returned Datum will have units according to the type of units subtracted. + * For example, "1979-01-02T00:00" - "1979-01-01T00:00" = "24 hours" (this datum's unit's offset units), + * while "1979-01-02T00:00" - "1 hour" = "1979-01-01T23:00" (this datum's units.) + * + * Note also the resolution of the result is calculated. + * + * @return a Datum that is the sum of the two values in this Datum's units. + * @param datum Datum to add, that is convertable to this.getUnits() or offset units. + */ + public Datum subtract( Datum datum ) { + Datum result= subtract( datum.getValue(), datum.getUnits() ); + result.resolution= Math.sqrt( datum.resolution * datum.resolution + this.resolution * this.resolution ); + return result; + } + + /** + * returns a Datum whose value is the difference of this and value in + * the context of units. + * The returned Datum will have units according to the type of units subtracted. + * For example, "1979-01-02T00:00" - "1979-01-01T00:00" = "24 hours" (this datum's unit's offset units), + * while "1979-01-02T00:00" - "1 hour" = "1979-01-01T23:00" (this datum's units.) + * + * @param a a Number to add in the context of units. + * @param units units defining the context of value. There should be a converter from + * units to this Datum's units or offset units. + * @return value Datum that is the difference of the two values in this Datum's units. + */ + public Datum subtract( Number a, Units units ) { + Datum result= getUnits().subtract( getValue(), a, units ); + return result; + } + + /** + * returns a Datum whose value is the difference of this and value in + * the context of units. + * The returned Datum will have units according to the type of units subtracted. + * For example, "1979-01-02T00:00" - "1979-01-01T00:00" = "24 hours" (this datum's unit's offset units), + * while "1979-01-02T00:00" - "1 hour" = "1979-01-01T23:00" (this datum's units.) + * + * @param d a Number to add in the context of units. + * @param units units defining the context of value. There should be a converter from + * units to this Datum's units or offset units. + * @return value Datum that is the difference of the two values in this Datum's units. + */ + public Datum subtract( double d, Units units ) { return subtract( new java.lang.Double(d), units ); } + + private static double relativeErrorMult( double x, double dx, double y, double dy ) { + return Math.sqrt( dx/x * dx/x + dy/y * dy/y ); + } + + /** + * divide this by the datum a. Currently, only division is only supported:

    +     *   between convertable units, resulting in a Units.dimensionless quantity, or
    +     *   by a Units.dimensionless quantity, and a datum with this datum's units is returned.
    + * This may change, as a generic SI units class is planned. + * + * @param a the datum divisor. + * @return the quotient. + */ + public Datum divide( Datum a ) { + Datum result= divide( a.getValue(), a.getUnits() ); + result.resolution= Math.abs( result.doubleValue() ) * relativeErrorMult( doubleValue(), resolution, a.doubleValue(), a.resolution ); + return result; + } + + /** + * divide this by the Number provided in the context of units. Currently, only division is only supported:
    +     *   between convertable units, resulting in a Units.dimensionless quantity, or
    +     *   by a Units.dimensionless quantity, and a datum with this datum's units is returned.
    + * This may change, as a generic SI units class is planned. + * @param a the magnitude of the divisor. + * @param units the units of the divisor. + * @return the quotient. + */ + public Datum divide( Number a, Units units ) { return getUnits().divide( getValue(), a, units ); } + + /** + * divide this by the dimensionless double. + * @param d the magnitude of the divisor. + * @return the quotient. + */ + public Datum divide( double d ) { return divide( new java.lang.Double(d), Units.dimensionless ); } + + /** + * multiply this by the datum a. Currently, only multiplication is only supported:
    +     *   by a dimensionless datum, or when this is dimensionless.
    +     * This may change, as a generic SI units class is planned.
    +     *
    +     * This should also throw an IllegalArgumentException if the units are LocationUnits (e.g. UT time), but doesn't.  This may
    +     * change.
    +     *   
    +     * @param a the datum to multiply
    +     * @return the product.
    +     */
    +    public Datum multiply( Datum a ) { 
    +        Datum result= multiply( a.getValue(), a.getUnits() );         
    +        result.resolution= result.doubleValue() * relativeErrorMult( doubleValue(), resolution, a.doubleValue(), a.resolution );
    +        return result;
    +    }
    +    
    +    /**
    +     * multiply this by the Number provided in the context of units.  Currently, only multiplication is only supported:
    +     *   by a dimensionless datum, or when this is dimensionless.
    +     * This may change, as a generic SI units class is planned.
    +     *
    +     * This should also throw an IllegalArgumentException if the units are LocationUnits (e.g. UT time), but doesn't.  This may
    +     * change.
    +     *
    +     * @param a the magnitude of the multiplier.
    +     * @param units the units of the multiplier.
    +     * @return the product.
    +     */
    +    public Datum multiply( Number a, Units units ) { return getUnits().multiply( getValue(), a, units ); }
    +    
    +    /**
    +     * multiply by a dimensionless number.
    +     *
    +     * This should also throw an IllegalArgumentException if the units are LocationUnits (e.g. UT time), but doesn't.  This may
    +     * change.
    +     *
    +     * @param d the multiplier.
    +     * @return the product.
    +     */
    +    public Datum multiply( double d ) {  return multiply( new java.lang.Double(d), Units.dimensionless ); }
    +    
    +    /**
    +     * creates an equivalent datum using a different unit.  For example,
    +     *  x= Datum.create( 5, Units.seconds );
    +     *  System.err.println( x.convertTo( Units.seconds ) );
    +     * 
    +     * @param units the new Datum's units
    +     * @throws java.lang.IllegalArgumentException if the datum cannot be converted to the given units.
    +     * @return a datum with the new units, that is equal to the original datum.
    +     */
    +    public Datum convertTo( Units units ) throws IllegalArgumentException {
    +        UnitsConverter muc= this.units.getConverter(units);
    +        Datum result= units.createDatum( muc.convert( this.getValue() ) );
    +        if ( this.resolution!=0. ) {
    +            muc= this.units.getOffsetUnits().getConverter(units.getOffsetUnits());
    +            result.resolution= muc.convert(this.resolution);
    +        }
    +        return result;
    +    }
    +    
    +    /**
    +     * returns a hashcode that is a function of the value and the units.
    +     * @return a hashcode for the datum
    +     */
    +    public int hashCode() {
    +        long bits = (long) getValue().hashCode();
    +        int doubleHash= (int)(bits ^ (bits >>> 32));
    +        int unitsHash= units.hashCode();
    +        return doubleHash ^ unitsHash;
    +    }
    +    
    +    /**
    +     * returns true if the two datums are equal.  That is, their double values are equal when converted to the same units.
    +     * @param a the Object to compare to.
    +     * @throws java.lang.IllegalArgumentException if the Object is not a datum or the units are not convertable.
    +     * @return true if the datums are equal.
    +     */
    +    public boolean equals( Object a ) throws IllegalArgumentException {
    +        return ( a!=null && (a instanceof Datum) && this.equals( (Datum)a ) );
    +    }
    +    
    +    /**
    +     * returns true if the two datums are equal.  That is, their double values are equal when converted to the same units.
    +     * @param a the datum to compare
    +     * @throws java.lang.IllegalArgumentException if the units are not convertable.
    +     * @return true if the datums are equal.
    +     */
    +    public boolean equals( Datum a ) throws IllegalArgumentException {
    +        return ( a!=null && this.getUnits().isConvertableTo( a.getUnits() ) && this.compareTo(a)==0 );
    +    }
    +    
    +    /**
    +     * returns true if this is less than a.
    +     * @param a a datum convertable to this Datum's units.
    +     * @throws java.lang.IllegalArgumentException if the two don't have convertable units.
    +     * @return true if this is less than a.
    +     */
    +    public boolean lt( Datum a ) throws IllegalArgumentException {
    +        return (this.compareTo(a)<0);
    +    }
    +    
    +    /**
    +     * returns true if this is greater than a.
    +     * @param a a datum convertable to this Datum's units.
    +     * @throws java.lang.IllegalArgumentException if the two don't have convertable units.
    +     * @return true if this is greater than a.
    +     */
    +    public boolean gt( Datum a ) throws IllegalArgumentException {
    +        return (this.compareTo(a)>0);
    +    }
    +    
    +    /**
    +     * returns true if this is less than or equal to a.
    +     * @param a a datum convertable to this Datum's units.
    +     * @throws java.lang.IllegalArgumentException if the two don't have convertable units.
    +     * @return true if this is less than or equal to a.
    +     */
    +    public boolean le( Datum a ) throws IllegalArgumentException {
    +        return (this.compareTo(a)<=0);
    +    }
    +    
    +    /**
    +     * returns true if this is greater than or equal to a.
    +     * @param a a datum convertable to this Datum's units.
    +     * @throws java.lang.IllegalArgumentException if the two don't have convertable units.
    +     * @return true if this is greater than or equal to a.
    +     */
    +    public boolean ge( Datum a ) throws IllegalArgumentException {
    +        return (this.compareTo(a)>=0);
    +    }
    +    
    +    /**
    +     * compare this to another datum.
    +     * @return an int <0 if this comes before Datum a in this Datum's units space,
    +     * 0 if they are equal, and >0 otherwise.
    +     * @param a the Datum to compare this datum to.
    +     * @throws IllegalArgumentException if a is not convertable to this Datum's
    +     * units.
    +     */
    +    public int compareTo( Datum a ) throws IllegalArgumentException {
    +        if ( this.units != a.units ) {
    +            a= a.convertTo(this.units);
    +        }
    +        
    +        double d= this.getValue().doubleValue() - a.getValue().doubleValue();
    +        
    +        if (d==0.) {
    +            return 0;
    +        } else if ( d<0. ) {
    +            return -1;
    +        } else {
    +            return 1;
    +        }
    +    }
    +    
    +    /**
    +     * returns true if the value is non NaN.
    +     * @return true if the value is non NaN.
    +     * @deprecated Use isFinite instead, or getValue.
    +     */
    +    public boolean isValid() {
    +        return (value.doubleValue()!=java.lang.Double.NaN);
    +    }
    +    
    +    /**
    +     * returns true if the value is finite, that is not INFINITY or NaN.
    +     * @return true if the value is finite, that is not INFINITY or NaN.
    +     */
    +    public boolean isFinite() {
    +        return ( value.doubleValue()!=java.lang.Double.POSITIVE_INFINITY )
    +        && ( value.doubleValue()!=java.lang.Double.NEGATIVE_INFINITY )
    +        && ( value.doubleValue()!=java.lang.Double.NaN );
    +    }
    +    
    +    /**
    +     * returns a human readable String representation of the Datum, which should also be parseable with
    +     * Units.parse()
    +     * @return a human readable String representation of the Datum, which should also be parseable with
    +     * Units.parse()
    +     */
    +    public String toString() {
    +        if (formatter==null) {
    +            return units.getDatumFormatterFactory().defaultFormatter().format(this);
    +        } else {
    +            return formatter.format(this);
    +        }
    +    }
    +    
    +    /**
    +     * convenient method for creating a dimensionless Datum with the given value.
    +     * @param value the magnitude of the datum.
    +     * @return a dimensionless Datum with the given value.
    +     */
    +    public static Datum create(double value) {
    +        return Units.dimensionless.createDatum(value);
    +    }
    +    
    +    /**
    +     * creates a datum with the given units and value, for example,
    +     * Datum.create( 54, Units.milliseconds )
    +     * @param value the magnitude of the datum.
    +     * @param units the units of the datum.
    +     * @return a Datum with the given units and value.
    +     */
    +    public static Datum create( double value, Units units ) {
    +        return units.createDatum( value );
    +    }
    +    
    +    /**
    +     * Returns a Datum with a specific DatumFormatter attached to
    +     * it.  This was was used to limit resolution before limited resolution
    +     * Datums were introduced.
    +     *
    +     * @param value the magnitude of the datum.
    +     * @param units the units of the datum.
    +     * @param formatter the DatumFormatter that should be used to format this datum, which will be
    +     *   returned by getFormatter().
    +     * @return a Datum with the given units and value, that should return the given formatter when asked.  
    +     */
    +    public static Datum create( double value, Units units, DatumFormatter formatter ) {
    +        Datum result= create( value, units);
    +        result.formatter= formatter;
    +        return result;
    +    }
    +    
    +    /**
    +     * Returns a Datum with the given value and limited to the given resolution.
    +     * When formatted, the formatter should use this resolution to limit the 
    +     * precision displayed.
    +     * @param value the magnitude of the datum, or value to be interpreted in the context of units.
    +     * @param units the units of the datum.
    +     * @param resolution the limit to which the datum's precision is known.
    +     * @return a Datum with the given units and value.
    +     */
    +    public static Datum create( double value, Units units, double resolution ) {
    +        Datum result= units.createDatum( value, resolution );
    +        result.formatter= units.getDatumFormatterFactory().defaultFormatter();
    +        return result;
    +    }
    +    
    +    /**
    +     * Returns a Datum with the given value and limited to the given resolution.
    +     * When formatted, the formatter should use this resolution to limit the 
    +     * precision displayed.
    +     * @param value the magnitude of the datum, or value to be interpreted in the context of units.
    +     * @param units the units of the datum.
    +     * @param resolution the limit to which the datum's precision is known.
    +     * @param formatter the DatumFormatter that should be used to format this datum, which will be
    +     *   returned by getFormatter().
    +     * @return a Datum with the given units and value.
    +     */
    +    public static Datum create( double value, Units units, double resolution, DatumFormatter formatter ) {
    +        Datum result= units.createDatum( value, resolution );
    +        result.formatter= formatter;
    +        return result;
    +    }
    +    
    +    /**
    +     * creates a dimensionless datum backed by an int.
    +     * @return a dimensionless Datum with the given value.
    +     * @param value the magnitude of the dimensionless datum.
    +     */
    +    public static Datum create( int value ) {
    +        return Units.dimensionless.createDatum( value );
    +    }
    +    
    +    /**
    +     * creates a datum backed by an int with the given units.
    +     * @return a Datum with the given units and value.
    +     * @param value the magnitude of the datum
    +     * @param units the units of the datum
    +     */
    +    public static Datum create( int value, Units units ) {
    +        return units.createDatum( value );
    +    }
    +    
    +    /**
    +     * returns a formatter suitable for formatting this datum as a string.
    +     * @return a formatter to be used to format this Datum into a String.
    +     */
    +    public DatumFormatter getFormatter() {
    +        return this.formatter;
    +    }
    +    
    +}
    diff --git a/dasCore/src/main/java/org/das2/datum/DatumRange.java b/dasCore/src/main/java/org/das2/datum/DatumRange.java
    new file mode 100644
    index 000000000..6e57b9b9f
    --- /dev/null
    +++ b/dasCore/src/main/java/org/das2/datum/DatumRange.java
    @@ -0,0 +1,293 @@
    +package org.das2.datum;
    +
    +/**
    + * A DatumRange is provided as a means to carry an ordered pair of Datums
    + * representing an interval.  This sort of data structure comes up often in
    + * processing, and it's useful to define once and get the operators
    + * implemented correctly.  Consider using this object whenever you see
    + * pairs of Datums in interfaces and codes (e.g. tbegin,tend), they are probably
    + * a DatumRange!
    + */
    +
    +public class DatumRange implements Comparable {
    +    
    +    Datum s1;
    +    Datum s2;
    +    
    +    /**
    +     * Creates valid DatumRange from two Datums.
    +     * @param s1 the start or smaller value of the range.
    +     * @param s2 the stop or bigger value of the range.
    +     */
    +    public DatumRange(Datum s1, Datum s2) {
    +        if ( s2.lt(s1) ) {
    +            throw new IllegalArgumentException( "s2 [0,1).include(2)->[0,2)  (note this is exclusive of 2 since it's the end).
    +     * [0,1).include(-1)->[-1,1).
    +     * [0,1).include(0.5)->[0,1]  (and returns the same DatumRange object)
    +     * 
    + * Also, including a fill Datum returns the same DatumRange as well. + */ + public DatumRange include(Datum d) { + if ( d.isFill() ) return this; + if ( this.contains(d) || this.max().equals(d) ) return this; + Datum min= ( this.min().le(d) ? this.min() : d ); + Datum max= ( this.max().ge(d) ? this.max() : d ); + return new DatumRange( min, max ); + } + + /** + * return the units of the DatumRange. + */ + public Units getUnits() { + return this.s1.getUnits(); + } + + /** + * creates a new DatumRange object with the range specified in the space + * identified by units. Note that min must be <= max. + */ + public static DatumRange newDatumRange(double min, double max, Units units) { + return new DatumRange( Datum.create(min,units), Datum.create(max,units) ); + } + +} + diff --git a/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java b/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java new file mode 100644 index 000000000..04a2fc6ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java @@ -0,0 +1,1067 @@ +/* + * DatumRangeUtil.java + * + * Created on September 16, 2004, 2:35 PM + */ + +package org.das2.datum; + +import org.das2.util.DasMath; +import org.das2.system.DasLogger; +import java.text.*; +import java.util.*; +import java.util.logging.*; +import java.util.regex.*; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.TimeDatumFormatter; + +/** + * + * @author Jeremy + */ +public class DatumRangeUtil { + + private static final int DATEFORMAT_USA= 1; + private static final int DATEFORMAT_EUROPE= 0; + private static final int DATEFORMAT_YYYY_DDD= 2; + + private static final boolean DEBUG=false; + + + // this pattern is always a year + private static boolean isYear( String string ) { + return string.length()==4 && Pattern.matches("\\d{4}",string); + } + + // this pattern is always a day of year + private static boolean isDayOfYear( String string ) { + return string.length()==3 && Pattern.matches("\\d{3}",string); + } + + private static int monthNumber( String string ) throws ParseException { + if ( Pattern.matches("\\d+", string) ) { + return parseInt(string); + } else { + int month= monthNameNumber(string); + if ( month==-1 ) throw new ParseException("hoping for month at, got "+string, 0); + return month; + } + } + + private static int monthNameNumber( String string ) { + if ( string.length() < 3 ) return -1; + String[] monthNames= new String[] { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; + string= string.substring(0,3).toLowerCase(); + int r=-1; + for ( int i=0; i 100 ) { + return year; + } else { + if ( year < 70 ) { + return 2000+year; + } else { + return 1900+year; + } + } + } + + public static class DateDescriptor { + String date; + String year; + String month; + String day; + String delim; + int dateformat; + } + + private int stregex( String string, String regex ) { + Matcher matcher= Pattern.compile(regex).matcher(string); + if ( matcher.find() ) { + return matcher.start(); + } else { + return -1; + } + } + + private static void caldat( int julday, DateDescriptor dateDescriptor ) { + int jalpha, j1, j2, j3, j4, j5; + + jalpha = (int)(((double)(julday - 1867216) - 0.25)/36524.25); + j1 = julday + 1 + jalpha - jalpha/4; + j2 = j1 + 1524; + j3 = 6680 + (int)(((j2-2439870)-122.1)/365.25); + j4 = 365*j3 + j3/4; + j5 = (int)((j2-j4)/30.6001); + + int day = j2 - j4 - (int)(30.6001*j5); + int month = j5-1; + month = ((month - 1) % 12) + 1; + int year = j3 - 4715; + year = year - (month > 2 ? 1 : 0); + year = year - (year <= 0 ? 1 : 0); + + dateDescriptor.day= ""+day; + dateDescriptor.month= ""+month; + dateDescriptor.year= ""+year; + + } + + private static int julday( int month, int day, int year ) { + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + return jd; + } + + private static void printGroups( Matcher matcher ) { + for ( int i=0; i<=matcher.groupCount(); i++ ) { + System.out.println(" "+i+": "+matcher.group(i) ); + } + System.out.println(" " ); + } + + private static int parseInt( String s ) throws ParseException { + try { + return Integer.parseInt(s); + } catch ( NumberFormatException e ) { + throw new ParseException( "failed attempt to parse int in "+s, 0 ); + } + } + + /*;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + //;; + //;; papco_parse_timerange, string -> timeRange + //;; + //;; parses a timerange from a string. Valid strings include: + // ;; "2001" + // ;; "2001-2004" + // ;; "2003-004" + // ;; "12/31/2001" + // ;; "Jan 2001" + // ;; "Jan-Feb 2004" + // ;; "2004-004 - 2003-007" + // ;; "JAN to MAR 2004" + // ;; "2004/feb-2004/mar" + // ;; "2004/004-008 + // ;; "1979-03-01T20:58:45.000Z span 17.5 s" + // ;; keeps track of format(e.g. %Y-%j) for debugging, and perhaps to reserialize + // ;; + // ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + // if an element is trivially identifiable, as in "mar", then it is required + // that the corresponding range element be of the same format or not specified. + */ + + static class TimeRangeParser { + String token; + String delim=""; + + String string; + int ipos; + + final int YEAR=0; + final int MONTH=1; + final int DAY=2; + final int HOUR=3; + final int MINUTE=4; + final int SECOND=5; + final int NANO=6; + + final int STATE_OPEN=89; + final int STATE_TS1TIME=90; + final int STATE_TS2TIME=91; + + int state= STATE_OPEN; + + String delimRegEx= "\\s|-|/|\\.|:|to|through|span|T|Z|\u2013|,"; + Pattern delimPattern= Pattern.compile( delimRegEx ); + int[] ts1= new int[] { -1, -1, -1, -1, -1, -1, -1 }; + int[] ts2= new int[] { -1, -1, -1, -1, -1, -1, -1 }; + int[] ts= null; + + boolean beforeTo; + + private Pattern yyyymmddPattern= Pattern.compile("((\\d{4})(\\d{2})(\\d{2}))( |to|t|-|$)"); + + /* groups= group numbers: { year, month, day, delim } (0 is all) */ + private boolean tryPattern( Pattern regex, String string, int[] groups, DateDescriptor dateDescriptor ) throws ParseException { + Matcher matcher= regex.matcher( string.toLowerCase() ); + if ( matcher.find() && matcher.start()==0 ) { + // printGroups(matcher); + int posDate= matcher.start(); + int length= matcher.end()-matcher.start(); + dateDescriptor.delim= matcher.group(groups[3]); + dateDescriptor.date= string.substring( matcher.start(), matcher.end()-dateDescriptor.delim.length() ); + String month; + String day; + String year; + dateDescriptor.day= matcher.group(groups[2]); + dateDescriptor.month= matcher.group(groups[1]); + dateDescriptor.year= matcher.group(groups[0]); + return true; + } else { + return false; + } + } + + public boolean isTime( String string, int[] timearr ) throws ParseException { + Matcher m; + Pattern hhmmssmmPattern= Pattern.compile( "(\\d+):(\\d\\d+):(\\d\\d+).(\\d+) )" ); + Pattern hhmmssPattern= Pattern.compile( "(\\d+):(\\d\\d+):(\\d\\d+)" ); + Pattern hhmmPattern= Pattern.compile( "(\\d+):(\\d\\d+)" ); + Pattern hhPattern= Pattern.compile( "(\\d+):" ); + + if ( (m=hhmmssmmPattern.matcher(string)).matches() ) { + timearr[HOUR]= Integer.parseInt( m.group(1) ); + timearr[MINUTE]= Integer.parseInt( m.group(2) ); + timearr[SECOND]= Integer.parseInt( m.group(3) ); + timearr[NANO]= (int)( Integer.parseInt( m.group(4) ) * ( 100000 / DasMath.exp10( m.group(4).length() ) )); + throw new RuntimeException("working on this"); + } else if (( m=hhmmssPattern.matcher(string)).matches() ) { + } else if (( m=hhmmPattern.matcher(string)).matches() ) { + } else if (( m=hhPattern.matcher(string)).matches() ) { + } + return false; + } + + public boolean isDate( String string, DateDescriptor dateDescriptor ) throws ParseException { + // this is introduced because mm/dd/yy is so ambiguous, the parser + // has trouble with these dates. Check for these as a group. + + if ( string.length()<6 ) return false; + + int[] groups; + String dateDelimRegex= "( |to|t|-)"; + String yearRegex= "(\\d{2}(\\d{2})?)"; // t lower case because tryPattern folds case + + if ( tryPattern( yyyymmddPattern, string, new int[] { 2,3,4,5 }, dateDescriptor ) ) { + dateDescriptor.dateformat= DATEFORMAT_USA; + return true; + } + + String delim; + + String delims="(/|\\.|-| )"; + Matcher matcher= Pattern.compile(delims).matcher(string); + + if ( matcher.find() ) { + int posDelim= matcher.start(); + delim= string.substring(matcher.start(),matcher.end()); + } else { + return false; + } + + if ( delim.equals(".") ) { + delim="\\."; + } + + String monthNameRegex= "(jan[a-z]*|feb[a-z]*|mar[a-z]*|apr[a-z]*|may|june?|july?|aug[a-z]*|sep[a-z]*|oct[a-z]*|nov[a-z]*|dec[a-z]*)"; + String monthRegex= "((\\d?\\d)|"+monthNameRegex+")"; + String dayRegex= "(\\d?\\d)"; + + String euroDateRegex; + + if ( delim.equals("\\.") ) { + euroDateRegex= "(" + dayRegex + delim + monthRegex + delim + yearRegex + dateDelimRegex + ")"; + groups= new int [] { 6, 3, 2, 8 }; + } else { + euroDateRegex= "(" + dayRegex + delim + monthNameRegex + delim + yearRegex + dateDelimRegex + ")"; + groups= new int [] { 4, 3, 2, 6 }; + } + if ( tryPattern( Pattern.compile( euroDateRegex ), string, groups, dateDescriptor ) ) { + dateDescriptor.dateformat= DATEFORMAT_EUROPE; + return true; + } + + String usaDateRegex= monthRegex + delim + dayRegex + delim + yearRegex + dateDelimRegex ; + if ( tryPattern( Pattern.compile( usaDateRegex ), string, new int[] { 5,1,4,7 }, dateDescriptor ) ) { + dateDescriptor.dateformat= DATEFORMAT_USA; + return true; + } + + // only works for four-digit years + String lastDateRegex= "(\\d{4})" + delim + monthRegex + delim + dayRegex + dateDelimRegex; + if ( tryPattern( Pattern.compile( lastDateRegex ), string, new int[] { 1,2,5,6 }, dateDescriptor ) ) { + dateDescriptor.dateformat= DATEFORMAT_USA; + return true; + } + + String doyRegex= "(\\d{3})"; + String dateRegex= doyRegex+"(-|/)" + yearRegex + dateDelimRegex; + + if ( tryPattern( Pattern.compile( dateRegex ), string, new int[] { 3,1,1,5 }, dateDescriptor ) ) { + int doy= parseInt(dateDescriptor.day); + if ( doy>366 ) return false; + int year= parseInt(dateDescriptor.year); + caldat( julday( 12, 31, year-1 ) + doy, dateDescriptor ); + dateDescriptor.dateformat= DATEFORMAT_YYYY_DDD; + + return true; + } + + dateRegex= yearRegex +"(-|/)" + doyRegex + dateDelimRegex; + if ( tryPattern( Pattern.compile( dateRegex ), string, new int[] { 1,4,4,5 }, dateDescriptor ) ) { + int doy= parseInt(dateDescriptor.day); + if ( doy>366 ) return false; + int year= parseInt(dateDescriptor.year); + caldat( julday( 12, 31, year-1 ) + doy, dateDescriptor ); + dateDescriptor.dateformat= DATEFORMAT_YYYY_DDD; + + return true; + } + return false; + } + + + private void nextToken( ) { + Matcher matcher= delimPattern.matcher( string.substring(ipos) ); + if ( matcher.find() ) { + int r= matcher.start(); + int length= matcher.end()-matcher.start(); + token= string.substring( ipos, ipos+r ); + delim= string.substring( ipos+r, ipos+r+length ); + ipos= ipos + r + length; + } else { + token= string.substring(ipos); + delim= ""; + ipos= string.length(); + } + } + + private void setBeforeTo( boolean v ) { + beforeTo= v; + if ( beforeTo ) { + ts= ts1; + } else { + ts= ts2; + } + } + + /* identify and make the "to" delimiter unambiguous */ + public String normalizeTo( String s ) throws ParseException { + + int minusCount= 0; + for ( int i=0; i2 ) { + result= ss[0]; + for ( int i=1; i 0 ) { + ArrayList unload; + String formatUn; + int idx=0; + + if ( beforeToUnresolved.size() < afterToUnresolved.size() ) { + if ( beforeToUnresolved.size()>0 ) { + for ( int i=0; i0 ) { + for ( int i=0; i=0; i-- ) { + while ( ts[lsd]!=-1 && lsd>0 ) lsd--; + if ( ts[lsd]!=-1 ) { + throw new ParseException( "can't resolve these tokens in \""+stringIn+"\": "+unload+ " ("+format+")", 0 ); + } + ts[lsd]= parseInt((String)unload.get(i)); + String[] s= format.split(formatUn+(i+1)); + format= s[0]+formatCodes[lsd]+s[1]; + } + + } // unresolved entities + + { + StringBuffer stringBuffer= new StringBuffer("ts1: "); + for ( int i=0; i<7; i++ ) stringBuffer.append(""+ts1[i]+" "); + logger.fine( stringBuffer.toString() ); + stringBuffer= new StringBuffer("ts2: "); + for ( int i=0; i<7; i++ ) stringBuffer.append(""+ts2[i]+" "); + logger.fine( stringBuffer.toString() ); + logger.fine( format ); + } + + /* contextual fill. Copy over digits that were specified in one time but + * not the other. + */ + for ( int i=YEAR; i<=DAY; i++ ) { + if ( ts2[i] == -1 && ts1[i] != -1 ) ts2[i]= ts1[i]; + if ( ts1[i] == -1 && ts2[i] != -1 ) ts1[i]= ts2[i]; + } + + int i= NANO; + int[] implicit_timearr= new int[] { -1, 1, 1, 0, 0, 0, 0 }; + int ts1lsd= -1; + int ts2lsd= -1; + while (i>=0) { + if ( ts2[i] != -1 && ts2lsd == -1 ) ts2lsd=i; + if ( ts2lsd == -1 ) ts2[i]= implicit_timearr[i]; + if ( ts2[i] == -1 && ts2lsd != -1 ) { + throw new ParseException("not specified in stop time: "+digitIdentifiers[i]+" in "+stringIn+" ("+format+")",ipos); + } + if ( ts1[i] != -1 && ts1lsd == -1 ) ts1lsd=i; + if ( ts1lsd == -1 ) ts1[i]= implicit_timearr[i]; + if ( ts1[i] == -1 && ts1lsd != -1 ) { + throw new ParseException("not specified in start time:"+digitIdentifiers[i]+" in "+stringIn+" ("+format+")",ipos); + } + i= i-1; + } + + + if ( ts1lsd != ts2lsd && ( ts1lsd3; idigit-- ) { + if ( arr[idigit]>0 ) break; + } + stopRes= Math.max( stopRes, idigit ); + } + + int[] arr= TimeUtil.toTimeArray(time); + if ( stopRes>3 ) { + timeString+= ( arr[4] < 10 ? "0" : "" ) + arr[4]; + if ( stopRes>4 ) { + int second= arr[5]; + timeString+=":"+ ( second < 10 ? "0" : "" ) + second; + if ( stopRes>5 ) { + int millis= arr[6]; + DecimalFormat nf= new DecimalFormat("000"); + timeString+="."+nf.format(millis); + if ( stopRes>6 ) { + int micros= arr[7]; + timeString+=nf.format(micros); + } + } + } + } + + return timeString; + } + + public static String formatTimeRange( DatumRange self ) { + + String[] monthStr= new String[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec" }; + + double seconds= self.width().doubleValue(Units.seconds); + + CalendarTime ts1= new CalendarTime(self.min()); + CalendarTime ts2= new CalendarTime(self.max()); + + // ts1= [ year1, month1, dom1, hour1, minute1, second1, nanos1 ] + // ts2= [ year2, month2, dom2, hour2, minute2, second2, nanos2 ] + + boolean isMidnight1= TimeUtil.getSecondsSinceMidnight( self.min() ) == 0.; + boolean isMidnight2= TimeUtil.getSecondsSinceMidnight( self.max() ) == 0.; + + boolean isMonthBoundry1= isMidnight1 && ts1.m_nDom == 1; + boolean isMonthBoundry2= isMidnight2 && ts2.m_nDom == 1; + + boolean isYearBoundry1= isMonthBoundry1 && ts1.m_nMonth == 1; + boolean isYearBoundry2= isMonthBoundry2 && ts2.m_nMonth == 1; + + //String toDelim= " \u2013 "; + String toDelim= " through "; + if ( isYearBoundry1 && isYearBoundry2 ) { // no need to indicate month + if ( ts2.m_nYear-ts1.m_nYear == 1 ) { + return "" + ts1.m_nYear; + } else { + return "" + ts1.m_nYear + toDelim + (ts2.m_nYear-1); + } + } else if ( isMonthBoundry1 && isMonthBoundry2 ) { // no need to indicate day of month + if ( ts2.m_nMonth == 1 ) { + ts2.m_nMonth=13; + ts2.m_nYear--; + } + if ( ts2.m_nYear == ts1.m_nYear ) { + if ( ts2.m_nMonth-ts1.m_nMonth == 1 ) { + return monthStr[ts1.m_nMonth-1] + " " + ts1.m_nYear; + } else { + return monthStr[ts1.m_nMonth-1]+ toDelim + monthStr[ts2.m_nMonth-1-1] + " " + ts1.m_nYear; + } + } else { + return monthStr[ts1.m_nMonth-1] + " " + ts1.m_nYear + toDelim + + monthStr[ts2.m_nMonth-1-1] + " " + ts2.m_nYear; + } + } + + if ( isMidnight1 && isMidnight2 ) { // no need to indicate HH:MM + if ( TimeUtil.getJulianDay( self.max() ) - TimeUtil.getJulianDay( self.min() ) == 1 ) { + return TimeDatumFormatter.DAYS.format( self.min() ); + } else { + Datum endtime= self.max().subtract( Datum.create( 1, Units.days ) ); + return TimeDatumFormatter.DAYS.format( self.min() ) + toDelim + + TimeDatumFormatter.DAYS.format( endtime ); + } + + } else { + DatumFormatter timeOfDayFormatter; + + if ( seconds<1. ) timeOfDayFormatter= TimeDatumFormatter.MILLISECONDS; + else if ( seconds<60. ) timeOfDayFormatter= TimeDatumFormatter.MILLISECONDS; + else if ( seconds<3600. ) timeOfDayFormatter= TimeDatumFormatter.SECONDS; + else timeOfDayFormatter= TimeDatumFormatter.MINUTES; + + int maxDay= TimeUtil.getJulianDay(self.max()); + if ( TimeUtil.getSecondsSinceMidnight(self.max())==0 ) maxDay--; // want to have 24:00, not 00:00 + if ( maxDay== TimeUtil.getJulianDay(self.min()) ) { + return TimeDatumFormatter.DAYS.format(self.min()) + + " " + efficientTime( self.min(), self.max(), self ) + + " to " + efficientTime( self.max(), self.min(), self ); + } else { + String t1str= efficientTime( self.min(), self.max(), self ); + String t2str= efficientTime( self.max(), self.min(), self ); + return TimeDatumFormatter.DAYS.format( self.min() ) + " " + t1str + + " to " + TimeDatumFormatter.DAYS.format( self.max() ) + " " + t2str; + } + } + } + + /** + * return a list of DatumRanges that together cover the space identified + * by bounds. The list should contain one DatumRange that is equal to + * element, which should define the phase and period of the list elements. + * For example, + *
     DatumRange bounds= DatumRangeUtil.parseTimeRangeValid( '2006' );
    +     * DatumRange first= DatumRangeUtil.parseTimeRangeValid( 'Jan 2006' );
    +     * List list= generateList( bounds, first );
    + * Note the procedure calls element.previous until bound.min() is contained, + * then calls bound.max until bound.max() is contained. + * + * @param bounds range to be covered. + * @param element range defining the width and phase of each list DatumRange. + * + */ + public static List generateList( DatumRange bounds, DatumRange element ) { + + ArrayList result= new ArrayList(); + DatumRange dr= element; + while ( dr.max().gt(bounds.min()) ) { + result.add(0,dr); + dr= dr.previous(); + } + dr= element.next(); + while( dr.min().lt(bounds.max() ) ) { + result.add(dr); + dr= dr.next(); + } + return result; + } + + + public static DatumRange newDimensionless(double lower, double upper) { + return new DatumRange( Datum.create(lower), Datum.create(upper) ); + } + + public static DatumRange parseDatumRange( String str, Units units ) throws ParseException { + if ( units instanceof TimeLocationUnits ) { + return parseTimeRange( str ); + } else { + // consider Patterns -- dash not handled because of negative sign. + String[] ss= str.split("to"); + if ( ss.length==1 ) { + ss= str.split("\u2013"); + } + if ( ss.length != 2 ) { + if ( ss.length==3 ) { + ss[0]= "-"+ss[1]; + ss[1]= ss[2]; + } else { + throw new IllegalArgumentException("failed to parse: "+str); + } + } + + // TODO: handle "124.0 to 140.0 kHz" when units= Units.hertz + Datum d2; + try { + d2= DatumUtil.parse(ss[1]); + if ( d2.getUnits()==Units.dimensionless ) d2= units.parse( ss[1] ); + } catch ( ParseException e ) { + d2= units.parse( ss[1] ); + } + Datum d1= d2.getUnits().parse( ss[0] ); + + if ( d1.getUnits().isConvertableTo(units) ) { + return new DatumRange( d1.convertTo(units), d2.convertTo(units) ); + } else { + throw new ParseException( "Can't convert parsed unit ("+d1.getUnits()+") to "+units, 0 ); + } + } + } + + public static DatumRange parseDatumRange( String str, DatumRange orig ) throws ParseException { + return parseDatumRange( str, orig.getUnits() ); + } + + /** + * returns DatumRange relative to this, where 0. is the minimum, and 1. is the maximum. + * For example rescale(1,2) is scanNext, rescale(0.5,1.5) is zoomOut. + * @param dr a DatumRange with nonzero width. + * @param min the new min normalized with respect to this range. 0. is this range's min, 1 is this range's max, 0 is + * min-width. + * @param max the new max with normalized wrt this range. 0. is this range's min, 1 is this range's max, 0 is + * min-width. + * @return new DatumRange. + */ + public static DatumRange rescale( DatumRange dr, double min, double max ) { + Datum w= dr.width(); + if ( !w.isFinite() ) { + throw new RuntimeException("width is not finite"); + } + if ( w.doubleValue( w.getUnits() )==0. ) { + // condition that might cause an infinate loop! For now let's check for this and throw RuntimeException. + throw new RuntimeException("width is zero!"); + } + return new DatumRange( dr.min().add( w.multiply(min) ), dr.min().add( w.multiply(max) ) ); + } + + /** + * returns DatumRange relative to this, where 0. is the minimum, and 1. is the maximum, but the + * scaling is done in the log space. + * For example, rescaleLog( [0.1,1.0], -1, 2 )-> [ 0.01, 10.0 ] + * @param dr a DatumRange with nonzero width. + * @param min the new min normalized with respect to this range. 0. is this range's min, 1 is this range's max, 0 is + * min-width. + * @param max the new max with normalized wrt this range. 0. is this range's min, 1 is this range's max, 0 is + * min-width. + * @return new DatumRange. + */ + public static DatumRange rescaleLog( DatumRange dr, double min, double max ) { + Units u= dr.getUnits(); + double s1= DasMath.log10( dr.min().doubleValue(u) ); + double s2= DasMath.log10( dr.max().doubleValue(u) ); + double w= s2 - s1; + if ( w==0. ) { + // condition that might cause an infinate loop! For now let's check for this and throw RuntimeException. + throw new RuntimeException("width is zero!"); + } + s2= DasMath.exp10( s1 + max * w ); // danger + s1= DasMath.exp10( s1 + min * w ); + return new DatumRange( s1, s2, u ); + } + + /** + * returns the position within dr, where 0. is the dr.min(), and 1. is dr.max() + * @param dr a datum range with non-zero width. + * @param d a datum to normalize with respect to the range. + * @return a double indicating the normalized datum. + */ + public static double normalize( DatumRange dr, Datum d ) { + return d.subtract(dr.min()).divide(dr.width()).doubleValue(Units.dimensionless); + } + + /** + * returns the position within dr, where 0. is the dr.min(), and 1. is dr.max() + * @param dr a datum range with non-zero width. + * @param d a datum to normalize with respect to the range. + * @return a double indicating the normalized datum. + */ + public static double normalizeLog( DatumRange dr, Datum d ) { + Units u= dr.getUnits(); + double d0= Math.log( dr.min().doubleValue( u ) ); + double d1= Math.log( dr.max().doubleValue( u ) ); + double dd= Math.log( d.doubleValue( u ) ); + return (dd-d0) / ( d1-d0 ); + } + + /** + * Like DatumRange.intesects, but returns a zero-width range when the two do + * not intersect. When they do not intersect, the min or max of the first range + * is returned, depending on whether or not the second range is above or below + * the first range. Often this allows for simpler code. + * @see DatumRange.intersection. + */ + public static DatumRange sloppyIntersection( DatumRange range, DatumRange include ) { + Units units= range.getUnits(); + double s11= range.min().doubleValue(units); + double s12= include.min().doubleValue(units); + double s21= range.max().doubleValue(units); + if ( range.intersects(include) ) { + double s1= Math.max( s11, s12 ); + double s22= include.max().doubleValue(units); + double s2= Math.min( s21, s22 ); + return new DatumRange( s1, s2, units ); + } else { + if ( s11 + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import org.das2.system.DasLogger; +import org.das2.util.DasMath; +import java.text.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.DatumFormatterFactory; +import org.das2.datum.format.DefaultDatumFormatterFactory; +import org.das2.datum.format.EnumerationDatumFormatterFactory; +import org.das2.datum.format.ExponentialDatumFormatter; +import org.das2.datum.format.TimeDatumFormatter; + +/** + * + * @author Edward West + */ +public final class DatumUtil { + + /** Creates a new instance of DatumUtil */ + private DatumUtil() { + } + + public static DatumFormatter bestFormatter( DatumVector datums ) { + double[] array; + Units units; + + if ( datums.getUnits() instanceof EnumerationUnits ) { + return EnumerationDatumFormatterFactory.getInstance().defaultFormatter(); + } + + if ( datums.getUnits() instanceof TimeLocationUnits ) { + Datum t1= datums.get(0); + int nticks= datums.getLength(); + Datum t2= datums.get(nticks-1); + return DatumUtil.bestTimeFormatter(t1,t2,nticks-1); + } + + if ( datums.getUnits() instanceof LocationUnits ) { + array= new double[ datums.getLength() ]; + units= ((LocationUnits)datums.get(0).getUnits()).getOffsetUnits(); + array[0]= 0.; + for ( int i=1; i(gcd*0.1) ) { // don't look at fuzzy zero + int ee= (int)Math.floor(0.05+DasMath.log10(Math.abs(d))); + if ( ee(discernable*0.1) ) { // don't look at fuzzy zero + int ee= (int)Math.floor(0.05+DasMath.log10(Math.abs(d))); + if ( ee 60 ) { + return DefaultDatumFormatterFactory.getInstance().defaultFormatter(); + } else if ( smallestExp < -3 || smallestExp > 3 ) { + return new ExponentialDatumFormatter( smallestExp - (-1*fracDigits) +1 , smallestExp ); + } else { + int nFraction= -1 * (int)Math.floor(0.05+DasMath.log10(discernable)); + nFraction= nFraction<0 ? 0 : nFraction; + String formatString = zeros(nFraction); + return factory.newFormatter(formatString); + } + } + catch (java.text.ParseException pe) { + Logger logger = DasLogger.getLogger(); + //Should not happen under normal circumstances, so bail. + RuntimeException re = new RuntimeException(pe); + logger.log(Level.SEVERE, pe.getMessage(), re); + throw re; + } + } + + private static String exp(int power) { + StringBuffer buffer = new StringBuffer(power+4); + for (int i = 0; i < power - 1; i++) { + buffer.append('#'); + } + buffer.append("0.#E0"); + return buffer.toString(); + } + + private static final String zeros100= "0.00000000000000000000" + + "0000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000"; + public static String zeros(int count) { + if ( count < 0 ) return "0"; + else if ( count <= 100 ) return zeros100.substring(0,count+2); + else { + StringBuffer buff = new StringBuffer(count+2).append("0."); + for (int index = 0; index < count; index++) { + buff.append('0'); + } + return buff.toString(); + } + } + + public static DatumFormatter bestTimeFormatter(Datum minimum, Datum maximum, int nsteps) { + double secondsPerStep = maximum.subtract(minimum).doubleValue(Units.seconds) / ( nsteps ); + double daysPerStep= secondsPerStep/86400; + if (secondsPerStep < 1.) { + return TimeDatumFormatter.MILLISECONDS; + } + else if (secondsPerStep < 60.) { + return TimeDatumFormatter.SECONDS; + } + else if (secondsPerStep < 3600.) { + return TimeDatumFormatter.MINUTES; + } + else if (secondsPerStep < 86400. ) { + return TimeDatumFormatter.HOURS; + } + else if ( secondsPerStep < 28*86400.0 ) { + return TimeDatumFormatter.DAYS; + } + else if ( secondsPerStep < 31557600.0 ) { + return TimeDatumFormatter.MONTHS; + } + else { + return TimeDatumFormatter.YEARS; + } + } + + /** + * attempt to parse the string as a datum. Note that if the + * units aren't specified, then of course the Datum will be + * assumed to be dimensionless. + * @throws ParseException when the double can't be parsed or the units aren't recognized. + */ + public static Datum parse(java.lang.String s) throws ParseException { + String[] ss= s.trim().split("\\s"); + Units units; + double value; + if ( ss.length==1 ) { + units= Units.dimensionless; + } else { + try { + units= Units.lookupUnits(ss[1]); + } catch ( IllegalArgumentException e ) { + throw new ParseException( e.getMessage(), 0 ); + } + } + return Datum.create( Double.parseDouble(ss[0]), units ); + } + + public static Datum parseValid(java.lang.String s) { + try { + return parse( s ); + } catch ( ParseException e ) { + throw new RuntimeException(e); + } + } + + public static Datum createValid( String s ) { + return Datum.create( Double.parseDouble(s), Units.dimensionless ); + } + + public static double[] doubleValues( Datum[] datums, Units units ) { + double[] result= new double[datums.length]; + for (int j=0; j 20) + score = 20/nn; + else + score = nn; + + if (score > bestScore) { + bestScore = score; + bestDatum = dd; + } + } + return bestDatum; + } + + +} diff --git a/dasCore/src/main/java/org/das2/datum/DatumVector.java b/dasCore/src/main/java/org/das2/datum/DatumVector.java new file mode 100644 index 000000000..93ab70b45 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/DatumVector.java @@ -0,0 +1,190 @@ +/* File: DatumVector.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 18, 2004, 10:40 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +/** + * + * @author eew + */ +public final class DatumVector { + + private final Units units; + private final Object store; + private final double resolution; + private final int offset; + private final int length; + + /** T0DO: check offset and length for out of bounds condition */ + private DatumVector(double[] array, int offset, int length, Units units) { + this(array, offset, length, units, 0.0, true); + } + + /** T0DO: check offset and length for out of bounds condition */ + private DatumVector(double[] array, int offset, int length, Units units, double resolution, boolean copy) { + // + if (array == null) { + throw new NullPointerException("array is null"); + } + if (units == null) { + throw new NullPointerException("units is null"); + } + // + if (copy) { + this.store = new double[length]; + for (int i = 0; i < length; i++) { + ((double[])store)[i] = array[offset + i]; + } + offset = 0; + } + else { + this.store = array; + } + this.offset = offset; + this.units = units; + this.resolution= resolution; + this.length = length; + } + + /** T0DO: check start and end for out of bounds condition */ + public DatumVector getSubVector(int start, int end) { + if (start == 0 && end == length) { + return this; + } + else return new DatumVector((double[])store, offset + start, end - start, units, resolution, false); + } + + public Datum get(int index) { + return Datum.create( ((double[])store)[index + offset], units, resolution ); + } + + public Units getUnits() { + return this.units; + } + + public double doubleValue(int index, Units toUnits) { + return units.convertDoubleTo(toUnits, ((double[])store)[index + offset]); + } + + public double[] toDoubleArray(Units units) { + return toDoubleArray(null, units); + } + + public double[] toDoubleArray(double[] array, Units units) { + if (array == null || array.length < length) { + array = new double[length]; + } + if (units == this.units) { + System.arraycopy(store, offset, array, 0, length); + } + else { + double[] store = (double[])this.store; + for (int i = 0; i < length; i++) { + array[i] = this.units.convertDoubleTo(units, store[i]); + } + } + return array; + } + + public static DatumVector newDatumVector(Datum[] array, Units units) { + double[] dArray = new double[array.length]; + for (int i = 0; i < array.length; i++) { + dArray[i] = array[i].doubleValue(units); + } + return newDatumVector(dArray, units); + } + + public static DatumVector newDatumVector(double[] array, Units units) { + return newDatumVector(array, 0, array.length, units); + } + + public static DatumVector newDatumVector(double[] array, double resolution, Units units) { + return new DatumVector( array, 0, array.length, units, resolution, true ); + } + + public static DatumVector newDatumVector(double[] array, int offset, int length, Units units) { + return new DatumVector(array, offset, length, units); + } + + public int getLength() { + return length; + } + + public DatumVector add( Datum d ) { + double[] dd= new double[getLength()]; + Units newUnits; + if ( d.getUnits() instanceof LocationUnits ) { + newUnits= d.getUnits(); + for ( int i=0; i0 ) result.append(", "); + Datum d= get(i); + result.append(d.getFormatter().format(d,units)); + } + if ( getLength()>4 ) result.append(", ..."); + result.append(" "+units.toString()+" ]"); + return result.toString(); + } +} diff --git a/dasCore/src/main/java/org/das2/datum/EnumerationUnits.java b/dasCore/src/main/java/org/das2/datum/EnumerationUnits.java new file mode 100755 index 000000000..3449b2158 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/EnumerationUnits.java @@ -0,0 +1,228 @@ +/* File: EnumerationUnits.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.datum; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.das2.datum.format.DatumFormatterFactory; +import org.das2.datum.format.EnumerationDatumFormatterFactory; + +/** + * Units class for mapping arbitary objects to Datums. Nothing about the contract + * for a Datum requires that they correspond to physical quanities, and we can + * assign a mapping from numbers to objects using this class. This allows + * information such as "Cluster 1" or "Spin Flip" to be encoded. + * + * This is used to model ordinal or nominal data, as described in + * http://en.wikipedia.org/wiki/Level_of_measurement + * + * @author Jeremy + */ +public class EnumerationUnits extends Units { + + private HashMap ordinals; // maps from ordinal to Datum.Integer + private int highestOrdinal; // highest ordinal for each Units type + private HashMap objects; // maps from object to Datum.Integer + private HashMap invObjects; // maps from Datum.Integer to object + public static HashMap unitsInstances; + + public EnumerationUnits(String id) { + this(id, ""); + } + + public EnumerationUnits(String id, String description) { + super(id, description); + highestOrdinal = 0; + ordinals = new HashMap(); + objects = new HashMap(); + invObjects = new HashMap(); + } + + public static Datum createDatumAndUnits(Object object) { + return create(object).createDatum(object); + } + + /** + * creates the datum, explicitly setting the ordinal. Use with caution. + * @param ival + * @param sval + * @throws IllegalArgumentException if this ordinal is already taken by a different value. + */ + public Datum createDatum(int ival, Object object) { + if (objects.containsKey(object)) { + return objects.get(object); + } else { + if (highestOrdinal < ival) { + highestOrdinal = ival; + } + Integer ordinal = new Integer(ival); + Datum result = new Datum.Double(ordinal, this); + if ( ordinals.containsKey(ordinal) ) { + Datum d= ordinals.get(ordinal); + if ( ! invObjects.get( d ).equals(object) ) { + throw new IllegalArgumentException("value already exists for this ordinal!"); + } + } + ordinals.put(ordinal, result); + invObjects.put(result, object); + objects.put(object, result); + return result; + } + + } + + public DatumVector createDatumVector(Object[] objects) { + double[] doubles = new double[objects.length]; + for (int i = 0; i < objects.length; i++) { + doubles[i] = createDatum(objects[i]).doubleValue(this); + } + return DatumVector.newDatumVector(doubles, this); + } + + public synchronized Datum createDatum(Object object) { + if (objects.containsKey(object)) { + return objects.get(object); + } else { + highestOrdinal++; + Integer ordinal = new Integer(highestOrdinal); + Datum result = new Datum.Double(ordinal, this); + ordinals.put(ordinal, result); + invObjects.put(result, object); + objects.put(object, result); + return result; + } + } + + /** + * provides access to map of all values. + * @return + */ + public Map getValues() { + return Collections.unmodifiableMap(ordinals); + } + + public Datum createDatum(int value) { + Integer key = new Integer(value); + if (ordinals.containsKey(key)) { + return ordinals.get(key); + } else { + throw new IllegalArgumentException("No Datum exists for this ordinal: " + value); + } + } + + public Datum createDatum(long value) { + return createDatum((int) value); + } + + public Datum createDatum(Number value) { + return createDatum(value.intValue()); + } + + public Object getObject(Datum datum) { + if (invObjects.containsKey(datum)) { + return invObjects.get(datum); + } else { + throw new IllegalArgumentException("This Datum doesn't map back to an object! This shouldn't happen!"); + } + } + + public static synchronized EnumerationUnits create(Object o) { + if (unitsInstances == null) + unitsInstances = new HashMap(); + Class c = o.getClass(); + if (unitsInstances.containsKey(c)) { + return unitsInstances.get(c); + } else { + Units u= null; + try { + u= Units.getByName(c.toString() + "Unit"); + } catch ( IllegalArgumentException ex ) { + EnumerationUnits result = new EnumerationUnits(c.toString() + "Unit"); + unitsInstances.put(c, result); + return result; + } + if ( u instanceof EnumerationUnits ) { + return (EnumerationUnits)u; + } else { + throw new IllegalArgumentException("unit already exists: "+u); + } + } + } + + public Datum createDatum(double d) { + return createDatum((int) d); + } + + public Datum createDatum(double d, double resolution) { + return createDatum((int) d); + } + + public DatumFormatterFactory getDatumFormatterFactory() { + return EnumerationDatumFormatterFactory.getInstance(); + } + + public Datum subtract(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("subtract on EnumerationUnit"); + } + + public Datum add(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("add on EnumerationUnit"); + } + + public Datum divide(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("divide on EnumerationUnit"); + } + + public Datum multiply(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("multiply on EnumerationUnit"); + } + + public Datum parse(String s) throws java.text.ParseException { + Datum result = null; + for (Iterator i = objects.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = objects.get(key); + if (key.toString().equals(s)) { // if the look the same, they are the same + if (result == null) { + result = (Datum) objects.get(key); + } else { + throw new IllegalStateException("Multiple Objects' string representations match"); + } + } + } + if (result == null) { + throw new java.text.ParseException("no objects match \"" + s + "\"", 0); + } + return result; + } + + public int getHighestOrdinal() { + return this.highestOrdinal; + } + + public String toString() { + return this.getId() + "(ordinal)"; + } +} diff --git a/dasCore/src/main/java/org/das2/datum/InconvertibleUnitsException.java b/dasCore/src/main/java/org/das2/datum/InconvertibleUnitsException.java new file mode 100644 index 000000000..214e90c77 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/InconvertibleUnitsException.java @@ -0,0 +1,25 @@ +/* + * InconvertibleUnitsException.java + * + * Created on December 1, 2007, 6:31 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.datum; + +/** + * introduced so that clients can more precisely catch this exception. + * @author jbf + */ +public class InconvertibleUnitsException extends IllegalArgumentException { + + /** Creates a new instance of InconvertibleUnitsException */ + public InconvertibleUnitsException( Units fromUnits, Units toUnits ) { + super( ( fromUnits==Units.dimensionless ? "(dimensionless)" : fromUnits.toString() ) + + " -> " + + ( toUnits==Units.dimensionless ? "(dimensionless)" : toUnits.toString() ) ) ; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/LocationUnits.java b/dasCore/src/main/java/org/das2/datum/LocationUnits.java new file mode 100644 index 000000000..ddf15e443 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/LocationUnits.java @@ -0,0 +1,95 @@ +/* File: LocationUnits.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +/** + * + * @author jbf + */ +public class LocationUnits extends NumberUnits { + + Units offsetUnits; + Basis basis; + + /** Creates a new instance of LocationUnit */ + public LocationUnits( String id, String description, Units offsetUnits, Basis basis ) { + super( id, description ); + this.offsetUnits= offsetUnits; + this.basis= basis; + } + + /** + * return the physical units of the basis vector, such as "microseconds" or "degrees" + * @return + */ + @Override + public Units getOffsetUnits() { + return this.offsetUnits; + } + + /** + * return the basis for the unit, such as "since 2000-01-01T00:00Z" or "north of Earth's equator" + * @return + */ + @Override + public Basis getBasis() { + return this.basis; + } + + public Datum add(Number a, Number b, Units bUnits) { + if ( bUnits instanceof LocationUnits ) { + throw new IllegalArgumentException("You can't add "+this+" to "+bUnits+", they both identify a location in a space"); + } else { + Units offsetUnits= getOffsetUnits(); + if ( bUnits!=offsetUnits) { + UnitsConverter uc= Units.getConverter( bUnits, offsetUnits ); + b= uc.convert(b); + } + return createDatum( add( a, b ) ); + } + } + + public Datum divide(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("multiplication of locationUnits"); + } + + public Datum multiply(Number a, Number b, Units bUnits) { + throw new IllegalArgumentException("division of locationUnits"); + } + + public Datum subtract( Number a, Number b, Units bUnits) { + if ( bUnits instanceof LocationUnits ) { + if ( this != bUnits ) { + b= bUnits.getConverter(this).convert(b); + } + return getOffsetUnits().createDatum(subtract( a, b )); + } else { + if ( bUnits != getOffsetUnits()) { + b= bUnits.getConverter( getOffsetUnits() ).convert(b); + } + return createDatum( subtract( a, b ) ); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/MonthDatumRange.java b/dasCore/src/main/java/org/das2/datum/MonthDatumRange.java new file mode 100644 index 000000000..e544eca32 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/MonthDatumRange.java @@ -0,0 +1,82 @@ +/* + * MonthDatumRange.java + * + * Created on November 15, 2004, 4:28 PM + */ + +package org.das2.datum; + +/** + * + * @author Jeremy + */ +public class MonthDatumRange extends DatumRange { + + int width; + int widthDigit; + int[] start; + int[] end; + + public MonthDatumRange( int[] start, int[] end ) { + super( TimeUtil.toDatum( start ), + TimeUtil.toDatum( end ) ); + widthDigit= -1; + int[] widthArr= new int[7]; + boolean haveNonZeroDigit= false; + for ( int i=0; i<7; i++ ) { + widthArr[i]= end[i]-start[i]; + } + while( widthArr[1]<0 ) { + widthArr[1]+= 12; + widthArr[0]--; + } + for ( int i=0; i<7; i++ ) { + if ( widthArr[i]!=0 ) { + if ( widthDigit!=-1 ) { + throw new IllegalArgumentException("MonthDatumRange must only vary in month or year, not both"); + } else { + widthDigit=i; + width= widthArr[widthDigit]; + } + } + } + this.start= start; + this.end= end; + } + + public DatumRange next() { + int[] end1= new int[7]; + for ( int i=0; i<7; i++ ) { + end1[i]= this.end[i]; + } + end1[widthDigit]= end1[widthDigit]+this.width; + switch ( widthDigit ) { + case 1: while( end1[1]>12 ) { + end1[1]-= 12; + end1[0]++; + } + case 0: break; + default: throw new IllegalArgumentException("not implemented"); + } + return new MonthDatumRange( this.end, end1 ); + } + + public DatumRange previous() { + int[] start1= new int[7]; + for ( int i=0; i<7; i++ ) { + start1[i]= this.start[i]; + } + start1[widthDigit]= start1[widthDigit]-this.width; + switch ( widthDigit ) { + case 1: while( start1[1]<1 ) { + start1[1]+= 12; + start1[0]--; + } + case 0: break; + default: throw new IllegalArgumentException("not implemented"); + } + + return new MonthDatumRange( start1, this.start ); + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/NumberUnits.java b/dasCore/src/main/java/org/das2/datum/NumberUnits.java new file mode 100755 index 000000000..c9f06e1e1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/NumberUnits.java @@ -0,0 +1,276 @@ +/* File: Units.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import org.das2.util.DasMath; +import java.math.*; +import java.text.ParseException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.das2.datum.format.DefaultDatumFormatterFactory; +import org.das2.datum.format.DatumFormatterFactory; + +/** + * + * @author jbf + */ +public class NumberUnits extends Units { + + public NumberUnits(String id) { + this(id,""); + } + + public NumberUnits(String id, String description) { + super(id,description); + } + + public Datum createDatum( double value ) { + return new Datum.Double( value, this, 0. ); + } + + public Datum createDatum( double value, double resolution ) { + return new Datum.Double( value, this, resolution ); + } + + public Datum createDatum( int value ) { + return new Datum.Double( value, this ); + } + + public Datum createDatum( long value ) { + return new Datum.Double( value, this ); + } + + public Datum createDatum( Number value ) { + return new Datum.Double( value, this ); + } + + + public DatumFormatterFactory getDatumFormatterFactory() { + return DefaultDatumFormatterFactory.getInstance(); + } + + /* + * @returns double[2], [0] is number, [1] is the resolution + */ + private double[] parseDecimal( String s ) { + s= s.trim(); + BigDecimal bd= new BigDecimal(s); + + if ( bd.scale()>0 ) { + double resolution= DasMath.exp10( -1*bd.scale() ); + return new double[] { Double.parseDouble(s), resolution }; + } else { + int ie= s.indexOf( 'E' ); + if ( ie==-1 ) ie= s.indexOf('e'); + String mant; + if ( ie==-1 ) { + int id= s.indexOf('.'); + double[] dd= new double[2]; + dd[0]= Double.parseDouble(s); + if ( id==-1 ) { + dd[1]= 1.; + } else { + int scale= s.length()-id-1; + dd[1]= DasMath.exp10(-1*scale); + } + return dd; + } else { + mant= s.substring(0,ie); + double[] dd= parseDecimal( mant ); + double exp= DasMath.exp10( Double.parseDouble( s.substring(ie+1) ) ); + dd[0]= dd[0] * exp; + dd[1]= dd[1] * exp; + return dd; + } + } + } + + // note + and - are left out because of ambiguity with sign. + private static Pattern expressionPattern= Pattern.compile( "(.+)([\\*/])(.+)" ); + + private Datum parseExpression( String s ) throws ParseException { + Matcher m= expressionPattern.matcher(s); + if ( !m.matches() ) throw new IllegalArgumentException("not an expression"); + String operator= m.group(2); + Datum operand1; + try { + operand1= Units.dimensionless.parse( m.group(1) ); + } catch ( IllegalArgumentException e ) { + operand1= this.parse( m.group(1) ); + } + Datum operand2; + try { + operand2= Units.dimensionless.parse( m.group(3) ); + } catch ( IllegalArgumentException e ) { + operand2= this.parse( m.group(3) ); + } + Datum result; + if ( operator.equals("*") ) { + result= operand1.multiply(operand2); + } else if ( operator.equals("/") ) { + result= operand1.divide(operand2); + } else { + throw new IllegalArgumentException("Bad operator: "+operator+" of expression "+s); + } + return result; + } + + /* + * parse the string in the context of this. If units are not + * specified, then assume units are this. Otherwise, parse the + * unit and attempt to convert to this before creating the unit. + */ + public Datum parse(String s) throws ParseException { + expressionPattern= Pattern.compile( "(.+)([\\*/])(.+)" ); + if ( expressionPattern.matcher(s).matches() ) { + Datum result= parseExpression( s ); + if ( result.getUnits()==Units.dimensionless ) { + result= this.createDatum( result.doubleValue() ); + } else { + // throw exception if it's not convertable + result= result.convertTo(this); + } + return result; + } else { + try { + s= s.trim(); + if ( s.endsWith(this.getId()) ) { + s= s.substring(0,s.length()-this.getId().length()); + } + String[] ss= s.split("\\s+"); + double[] dd= parseDecimal(ss[0]); + if ( ss.length==1 ) { + return Datum.create( dd[0], this, dd[1] ); + } else { + String unitsString= ss[1]; + for ( int i=2; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import org.das2.datum.Units; + +/** + * + * @author jbf + */ +public class TimeContext { + + public static TimeContext MILLISECONDS= new TimeContext("milliseconds",1/86400000.); + public static TimeContext SECONDS= new TimeContext("seconds", 1/86400.); + public static TimeContext MINUTES = new TimeContext("minutes", 1/1440.); + public static TimeContext HOURS = new TimeContext("hours",1/24.); + public static TimeContext DAYS = new TimeContext("days",1); + public static TimeContext WEEKS = new TimeContext("weeks",7); + public static TimeContext MONTHS = new TimeContext("months",30); + public static TimeContext YEARS = new TimeContext("years",365); + public static TimeContext DECADES = new TimeContext("decades",3650); + + String s; + double ordinal; + + public TimeContext(String s, double ordinal ) { + this.s= s; + this.ordinal= ordinal; + } + + public boolean gt( TimeContext tc ) { + return ordinal>tc.ordinal; + } + + public boolean le( TimeContext tc ) { + return ordinal<=tc.ordinal; + } + + public String toString() { + return s; + } + + public static TimeContext getContext( Datum t1, Datum t2) { + TimeContext context; + double seconds= t2.subtract(t1).doubleValue(Units.seconds); + if (seconds<1) { context=MILLISECONDS; } + else if (seconds<60) { context=SECONDS; } + else if (seconds<3600) { context=MINUTES; } + else if (seconds<86400) { context=HOURS; } + else if (seconds<=864000) { context=DAYS; } + else { context=DAYS; } + return context; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/TimeLocationUnits.java b/dasCore/src/main/java/org/das2/datum/TimeLocationUnits.java new file mode 100644 index 000000000..8b3ad6d51 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/TimeLocationUnits.java @@ -0,0 +1,58 @@ +/* File: TimeLocationUnits.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import org.das2.datum.format.DatumFormatterFactory; +import org.das2.datum.format.TimeDatumFormatterFactory; + +/** + * + * @author jbf + */ +public class TimeLocationUnits extends LocationUnits { + + /* TimeLocationUnits class is introduced because it is often necessary to + * easily identify a time quantity, for instance when deciding whether to + * use a timeAxis or not. (TimeAxis is no longer a class, but we use a + * special tickV for time units.) + */ + + public TimeLocationUnits( String id, String description, Units offsetUnits, Basis basis ) { + super(id,description,offsetUnits,basis); + } + + public DatumFormatterFactory getDatumFormatterFactory() { + return TimeDatumFormatterFactory.getInstance(); + } + + public Datum parse(String s) throws java.text.ParseException { + CalendarTime ct = new CalendarTime(s); + return ct.toDatum(); + } + + public String getTimeZone() { + return "UT"; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/TimeUtil.java b/dasCore/src/main/java/org/das2/datum/TimeUtil.java new file mode 100755 index 000000000..df962f885 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/TimeUtil.java @@ -0,0 +1,400 @@ +/* File: TimeUtil.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 22, 2003, 11:00 AM by Jeremy Faden + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import java.text.ParseException; +import java.util.Map; +import java.util.HashMap; +import org.das2.datum.format.TimeDatumFormatter; + +/** + * Various time utilities + * @author jbf + */ +public final class TimeUtil { + + private TimeUtil() { + } + + // One of the few times package private is useful + final static int[][] daysInMonth = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0} + }; + + // One of the few times package private is useful + final static int[][] dayOffset = { + {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, + {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} + }; + + public static int daysInMonth(int month, int year) { + return daysInMonth[isLeapYear(year)?1:0][month]; + } + + public static int julday( int month, int day, int year ) { + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + return jd; + } + + public static int dayOfYear( int month, int day, int year ) { + return day + dayOffset[isLeapYear(year)?1:0][month]; + } + + // intruduced to aid in debugging + public static class TimeDigit{ + CalendarTime.Step ordinal; // YEAR, MONTH, etc. + String label; + int divisions; // approximate + private static Map digits = + new HashMap(); + @Override + public String toString(){ + return label; + } + private TimeDigit(CalendarTime.Step ordinal, String label, int divisions){ + this.ordinal = ordinal; + this.label = label; + this.divisions = divisions; + digits.put(ordinal, this); + } + public CalendarTime.Step getOrdinal(){ + return ordinal; + } + public int divisions(){ + return divisions; + } + public static TimeDigit fromOrdinal(CalendarTime.Step ordinal){ + return digits.get(ordinal); + } + } + + public static final TimeDigit TD_YEAR = new TimeDigit( CalendarTime.Step.YEAR, "YEAR", 12 ); + public static final TimeDigit TD_MONTH = new TimeDigit( CalendarTime.Step.MONTH, "MONTH", 30 ); + public static final TimeDigit TD_DAY = new TimeDigit( CalendarTime.Step.DAY, "DAY", 24 ); + public static final TimeDigit TD_HOUR = new TimeDigit( CalendarTime.Step.HOUR, "HOUR", 60 ); + public static final TimeDigit TD_MINUTE = new TimeDigit( CalendarTime.Step.MINUTE, "MINUTE", 60 ); + public static final TimeDigit TD_SECOND = new TimeDigit( CalendarTime.Step.SECOND, "SECOND", 1000 ); + public static final TimeDigit TD_MILLI= new TimeDigit( CalendarTime.Step.MILLISEC, "MILLISECONDS", 1000 ); + public static final TimeDigit TD_MICRO = new TimeDigit( CalendarTime.Step.MICROSEC, "MICROSECONDS", 1000 ); + public static final TimeDigit TD_NANO = new TimeDigit( CalendarTime.Step.NANOSEC, "NANOSECONDS", 1000 ); + + public static double getSecondsSinceMidnight(Datum datum) { + double xx= datum.doubleValue(Units.t2000); + if (xx<0) { + xx= xx % 86400; + if (xx==0) { + return 0; + } else { + return 86400+xx; + } + } else { + return xx % 86400; + } + } + + public static double getMicroSecondsSinceMidnight(Datum datum) { + double xx= datum.doubleValue( Units.us2000 ); + if (xx<0) { + xx= xx % 86400e6; + if (xx==0) { + return 0; + } else { + return 86400e6+xx; + } + } else { + return xx % 86400e6; + } + } + + /** + * return the the integer number of days that have elapsed since roughly Monday, January 1, 4713 BC. Julian Day + * is defined as starting at noon UT, here is is defined starting at midnight. + * @param datum + * @return + */ + public static int getJulianDay( Datum datum ) { + double xx= datum.doubleValue(Units.mj1958); + return (int)Math.floor( xx ) + 2436205; + } + + /** + *Break the Julian day apart into month, day year. This is based on + *http://en.wikipedia.org/wiki/Julian_day (GNU Public License), and + *was introduced when toTimeStruct failed when the year was 1886. + *@param julian the (integer) number of days that have elapsed since the initial epoch at noon Universal Time (UT) Monday, January 1, 4713 BC + *@return a CalendarTime with the month, day and year fields set. + */ + public static int[] julianToGregorian( int julian ) { + + int[] lRet = {0,0,0}; + + int j = julian + 32044; + int g = j / 146097; + int dg = j % 146097; + int c = (dg / 36524 + 1) * 3 / 4; + int dc = dg - c * 36524; + int b = dc / 1461; + int db = dc % 1461; + int a = (db / 365 + 1) * 3 / 4; + int da = db - a * 365; + int y = g * 400 + c * 100 + b * 4 + a; + int m = (da * 5 + 308) / 153 - 2; + int d = da - (m + 4) * 153 / 5 + 122; + int Y = y - 4800 + (m + 2) / 12; + int M = (m + 2) % 12 + 1; + int D = d + 1; + + lRet[0] = Y; + lRet[1] = M; + lRet[2] = D; + return lRet; + } + + /** Here millis are the number of milliseconds after the second, and micros are + * the number of micro seconds after the millisecond not the second. + * + * returns int[] { year, month, day, hour, minute, second, millis, micros } + */ + public static int[] toTimeArray( Datum time ) { + + CalendarTime ts= new CalendarTime( time ); + int millis = (int) (ts.m_nNanoSecond / 1000000); + int micros = (int) ((ts.m_nNanoSecond % 1000000)/ 1000); + + return new int[] { ts.m_nYear, ts.m_nMonth, ts.m_nDom, ts.m_nHour, ts.m_nMinute, ts.m_nSecond, + millis, micros }; + } + + public static Datum toDatum( int[] timeArray ) { + int year = timeArray[0]; + int month = timeArray[1]; + int day = timeArray[2]; + if ( timeArray[1]<1 ) { + throw new IllegalArgumentException(""); + } + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + int hour = (int)timeArray[3]; + int minute = (int)timeArray[4]; + double seconds = timeArray[5] + hour*(float)3600.0 + minute*(float)60.0 + timeArray[6]/1e9; + double us2000= UnitsConverter.getConverter(Units.mj1958,Units.us2000).convert(( jd - 2436205 ) + seconds / 86400. ); + return Datum.create( us2000, Units.us2000 ); + } + + /** Is year a leap year. + * Warning: Not tested for years prior to 1. + */ + public static boolean isLeapYear( int year ) { + if(year % 4 != 0) return false; + if(year % 400 == 0) return true; + if(year % 100 == 0) return false; + else return true; + } + + + public static Datum next( TimeDigit td, int count, Datum datum ) { + if ( td==TD_NANO ) throw new IllegalArgumentException("not supported nanos"); + CalendarTime ct = new CalendarTime(datum).step(td.getOrdinal(), count); + Datum result= ct.toDatum(); + return result; + } + + public static Datum next(CalendarTime.Step step, Datum datum ) { + CalendarTime ct = new CalendarTime(datum).step(step, 1); + return ct.toDatum(); + } + + + /** step down the previous ordinal. If the datum is already at an ordinal + * boundry, then step down by one ordinal. + * @param step + * @param datum + * @return + */ + public static Datum prev(CalendarTime.Step step, Datum datum ) { + CalendarTime ct= new CalendarTime(datum).step(step, -1); + return ct.toDatum(); + } + + public static Datum now() { + double us2000= ( System.currentTimeMillis() - 946684800e3 ) * 1000; + return Units.us2000.createDatum(us2000); + } + + /** + * @param year the year + * @param month the month + * @param day the day of month, unless month==0, then day is day of year. + * @param hour additional hours + * @param minute additional minutes + * @param second additional seconds + * @param units the Units in which to return the result. + * @return a double in the context of units. + */ + public static double convert(int year, int month, int day, int hour, int minute, + double second, TimeLocationUnits units) { + // if month==0, then day is doy (day of year). + int jd; + if ( month>0 ) { + jd = julday(month,day,year); + } else { + // if month==0 then day is doy + int month1= 1; + int day1= 1; + jd = julday(month1,day1,year); + jd+= ( day - 1 ); + } + + second+= hour*3600.0 + minute*60.0; + + double us2000 = (jd-2451545)*86400000000. + second * 1000000; + + if ( units==Units.us2000 ) { + return us2000; + } else { + return Units.us2000.convertDoubleTo(units, us2000); + } + } + + private final static String[] mons= { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + /** + * returns 1..12 for the English month name + * + * @throws ParseException if the name isn't recognized + */ + public static int monthNumber( String s ) throws ParseException { + s= s.substring(0,3); + for ( int i=0; i<12; i++ ) { + if ( s.equalsIgnoreCase( mons[i] ) ) return i+1; + } + throw new ParseException("Unable to parse month", 0 ); + } + + /** + * returns "Jan", "Feb", ... for month number (1..12). + * @param mon integer from 1 to 12. + * @return three character English month name. + */ + public static String monthNameAbbrev( int mon ) { + if ( mon<1 || mon>12 ) throw new IllegalArgumentException("invalid month number: "+mon); + return mons[mon-1]; + } + + /** Creates a datum from a string + * @param s + * @throws ParseException + * @return + */ + public static Datum create(String s) throws java.text.ParseException { + CalendarTime ts= new CalendarTime(s); + return ts.toDatum(); + } + + /** creates a Datum from a string which is known to contain + * a valid time format. Throws a RuntimeException if the + * string is not valid. + * @param validString + * @return + */ + public static Datum createValid(String validString ) { + try { + return create( validString ); + } catch ( java.text.ParseException ex ) { + throw new RuntimeException( ex ); + } + } + + public static boolean isValidTime( String string ) { + try { + create( string ); + return true; + } catch ( java.text.ParseException ex ) { + return false; + } + } + + public static Datum prevMidnight(Datum datum) { + //return datum.subtract(getMicroSecondsSinceMidnight(datum), Units.microseconds); + return datum.subtract(getSecondsSinceMidnight(datum), Units.seconds); + } + + /** + * returns the next midnight, or this datum if we are already on midnight. + * @param datum + * @return + */ + public static Datum nextMidnight( Datum datum ) { + CalendarTime ct = new CalendarTime(datum).step(CalendarTime.Step.DAY, 1); + return ct.toDatum(); + } + /** + * creates a Datum representing the time given in integer years, months, ..., seconds, nanoseconds. The year + * must be at least 1960, and must be a four-digit year. A double in Units.us2000 is used to represent the + * Datum, so resolution will drop as the year drops away from 2000. + * + * @param year four digit year >= 1960. + * @param month integer month, 1..12. + * @param day integer day of month. + * @param hour additional hours + * @param minute additional minutes + * @param second additional seconds + * @param nano additional nanoseconds + * @return a Datum with units Units.us2000. + */ + public static Datum createTimeDatum( int year, int month, int day, int hour, int minute, int second, int nano ) { + //if ( year<1960 ) throw new IllegalArgumentException("year must not be < 1960, and no 2 digit years (year="+year+")"); + if ( year<100 ) throw new IllegalArgumentException("year must not be < 100, and no 2 digit years (year="+year+")"); + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + double microseconds = second*1e6 + hour*3600e6 + minute*60e6 + nano/1e3; + double us2000= UnitsConverter.getConverter(Units.mj1958,Units.us2000).convert(( jd - 2436205 )) + microseconds; + return Datum.create( us2000, Units.us2000 ); + } + + public static void main(String[] args) throws Exception { + System.out.println( TimeUtil.now() ); + System.out.println( Datum.create( TimeUtil.convert(2000,1,2, 0, 0, 0, Units.us2000 ), Units.us2000 )); + Datum x=create( "2000-1-1 0:00:33.45" ); + System.out.println( x ); + + CalendarTime ts= new CalendarTime(x); + System.out.println( ts.toDatum() ); + + TimeDatumFormatter tf = TimeDatumFormatter.DEFAULT; + + for ( int i=0; i<44; i++ ) { + System.out.println(tf.format(x)+"\t"+(long)x.doubleValue(Units.us2000)); + x= TimeUtil.prev(CalendarTime.Step.SECOND,x); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/Units.java b/dasCore/src/main/java/org/das2/datum/Units.java new file mode 100755 index 000000000..87b2a196f --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/Units.java @@ -0,0 +1,821 @@ +/* File: Units.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.das2.datum.format.DatumFormatterFactory; + +/** + * Class for indicating physical units, and other random units. + * @author jbf + */ +public abstract class Units { + + private static final Logger logger= Logger.getLogger("datum.units"); + + private static Map unitsMap = new HashMap(); + + public static final Units dimensionless= new NumberUnits("","dimensionless quantities"); + + public static final Units radians= new NumberUnits("radian"); + public static final Units degrees= new NumberUnits("degrees"); + public static final Units deg= new NumberUnits("deg"); + static { + degrees.registerConverter(radians, new UnitsConverter.ScaleOffset(Math.PI/180.0,0.0) ); + degrees.registerConverter(deg, UnitsConverter.IDENTITY); + } + + /** + * + */ + public static final Units rgbColor= new NumberUnits("rgbColor","256*256*red+256*green+blue"); + + /** + * this is left in in case legacy code needs to see the conversion from dB to dimensionless offset. + */ + private static final class dBConverter extends UnitsConverter { + @Override + public double convert(double value) { + return 10 * Math.log10(value); + } + @Override + public UnitsConverter getInverse() { + if (inverse == null) { + inverse = new UnitsConverter() { + @Override + public double convert(double value) { + return Math.pow(10.0, value / 10.0); + } + @Override + public UnitsConverter getInverse() { + return dBConverter.this; + } + }; + } + return inverse; + } + } + + public static final Units celciusDegrees= new NumberUnits("celcius degrees"); // disambiguate from "deg C" which is the temperature scale + public static final Units fahrenheitDegrees= new NumberUnits("fahrenheit degrees"); // disambiguate from "deg F" which is the temperature scale + + public static final Units hours= new NumberUnits("hr"); + public static final Units minutes= new NumberUnits("min"); + public static final Units seconds= new NumberUnits("s"); + public static final Units seconds2= new NumberUnits("sec"); + //public static final Units seconds3= new NumberUnits("seconds"); // note s was not convertible to seconds. + public static final Units milliseconds= new NumberUnits("ms","milliseconds"); + public static final Units milliseconds2= new NumberUnits("msec"); + public static final Units microseconds= new NumberUnits("microseconds"); + public static final Units microseconds2= new NumberUnits("\u00B5s"); + + public static final Units nanoseconds= new NumberUnits("nanoseconds"); + public static final Units ns= new NumberUnits("ns","nanoseconds"); + public static final Units picoseconds= new NumberUnits("picoseconds"); + public static final Units days= new NumberUnits("days"); + static { + seconds.registerConverter(milliseconds, UnitsConverter.MILLI); + seconds.registerConverter(microseconds, UnitsConverter.MICRO); + seconds.registerConverter(nanoseconds,UnitsConverter.NANO); + seconds.registerConverter(ns,UnitsConverter.NANO); + nanoseconds.registerConverter( ns, UnitsConverter.IDENTITY ); + seconds.registerConverter(picoseconds,UnitsConverter.PICO); + seconds.registerConverter(seconds2,UnitsConverter.IDENTITY); + microseconds.registerConverter(nanoseconds, UnitsConverter.MILLI); // to support time formatting, often from us2000 to microseconds offset. + microseconds.registerConverter(microseconds2, UnitsConverter.IDENTITY); + milliseconds.registerConverter(milliseconds2, UnitsConverter.IDENTITY); + hours.registerConverter(seconds, new UnitsConverter.ScaleOffset( 3600.,0.0)); + minutes.registerConverter(seconds, new UnitsConverter.ScaleOffset( 60.,0.0)); + days.registerConverter(seconds, new UnitsConverter.ScaleOffset(8.64e4, 0.0)); + } + + public static final Units bytesPerSecond= new NumberUnits("bytes/s"); + public static final Units kiloBytesPerSecond= new NumberUnits("KBytes/s"); + public static final Units bytes= new NumberUnits( "bytes" ); + public static final Units kiloBytes= new NumberUnits( "KBytes" ); + static { + bytesPerSecond.registerConverter( kiloBytesPerSecond, UnitsConverter.KILO ); + bytes.registerConverter( kiloBytes, UnitsConverter.KILO ); + } + + public static final Units hertz= new NumberUnits("Hz"); + public static final Units kiloHertz = new NumberUnits("kHz"); // I verified that this should be lower case k. I wonder why... + public static final Units megaHertz = new NumberUnits("MHz"); + public static final Units gigaHertz = new NumberUnits("GHz"); + static { + hertz.registerConverter(kiloHertz, UnitsConverter.KILO); + hertz.registerConverter(megaHertz, UnitsConverter.MEGA); + hertz.registerConverter(gigaHertz, UnitsConverter.GIGA); + } + + public static final Units eV= new NumberUnits("eV"); + public static final Units ev= new NumberUnits("ev"); // Mike at LANL had run into these... + public static final Units keV= new NumberUnits("keV"); + public static final Units MeV= new NumberUnits("MeV"); + static { + eV.registerConverter(Units.ev, UnitsConverter.IDENTITY); + eV.registerConverter(Units.keV, UnitsConverter.KILO); + eV.registerConverter(Units.MeV, UnitsConverter.MEGA); + } + + /** + * 1 / cm3 + */ + public static final Units pcm3= new NumberUnits("cm!a-3!n"); + + public static final Units kelvin= new NumberUnits("K"); + public static final Units cmps= new NumberUnits("cm/s"); + + public static final Units cm_2s_1keV_1= new NumberUnits( "cm!U-2!N s!U-1!N keV!U-1!N" ); + public static final Units cm_2s_1MeV_1= new NumberUnits( "cm!U-2!N s!U-1!N MeV!U-1!N" ); + static { + cm_2s_1keV_1.registerConverter( Units.cm_2s_1MeV_1, UnitsConverter.KILO ); + } + /** + * Volts 2 m-2 Hz-1 + */ + public static final Units v2pm2Hz= new NumberUnits("V!a2!nm!a-2!nHz!a-1"); + static { + unitsMap.put("V**2 m**-2 Hz**-1", v2pm2Hz); + } + public static final Units V_per_m = new NumberUnits("V m**-1"); + static { + unitsMap.put("V/m", V_per_m); + } + + /** + * Watts / m2 + */ + public static final Units wpm2= new NumberUnits("W/m!a-2!n"); + public static final Units W_per_m2_Hz = new NumberUnits("W m**-2 Hz**-1"); + + + public static final Units meters = new NumberUnits("m"); + public static final Units millimeters = new NumberUnits("mm"); + public static final Units centimeters = new NumberUnits("cm"); + public static final Units kiloMeters = new NumberUnits("km"); + public static final Units inches = new NumberUnits("inch"); + public static final Units typographicPoints = new NumberUnits("points"); + static { + meters.registerConverter(kiloMeters, UnitsConverter.KILO); + meters.registerConverter(centimeters, UnitsConverter.CENTI ); + meters.registerConverter(millimeters, UnitsConverter.MILLI ); + inches.registerConverter( meters, new UnitsConverter.ScaleOffset(0.0254,0.0) ); + inches.registerConverter( typographicPoints, new UnitsConverter.ScaleOffset(72,0.0) ); + } + + /**** begin of LocationUnits. These must be defined after the physical units to support Basis. ****/ + + public static final Units centigrade= new LocationUnits( "centigrade", "centigrade", Units.celciusDegrees, Basis.centigrade ); + public static final Units fahrenheitScale= new LocationUnits("deg F", "deg F", Units.fahrenheitDegrees, Basis.fahrenheit ); + + static { + centigrade.registerConverter(fahrenheitScale, new UnitsConverter.ScaleOffset(1.8, 32)); + celciusDegrees.registerConverter(fahrenheitDegrees, new UnitsConverter.ScaleOffset(1.8,0) ); + } + + /** + * Microseconds since midnight Jan 1, 2000, excluding those within a leap second. Differences across leap + * second boundaries do not represent the number of microseconds elapsed. + */ + public static final TimeLocationUnits us2000= new TimeLocationUnits("us2000", "Microseconds since midnight Jan 1, 2000.", + Units.microseconds, Basis.since2000); + + /** + * Microseconds since midnight Jan 1, 1980, excluding those within a leap second. + */ + public static final TimeLocationUnits us1980= new TimeLocationUnits("us1980", "Microseconds since midnight Jan 1, 1980.", + Units.microseconds, Basis.since1980 ); + + /** + * Seconds since midnight Jan 1, 2010, excluding leap seconds. + */ + public static final TimeLocationUnits t2010= new TimeLocationUnits("t2010","Seconds since midnight Jan 1, 2010.", + Units.seconds, Basis.since2010 ); + + /** + * Seconds since midnight Jan 1, 2000, excluding leap seconds. + */ + public static final TimeLocationUnits t2000= new TimeLocationUnits("t2000","Seconds since midnight Jan 1, 2000.", + Units.seconds, Basis.since2000 ); + + /** + * seconds since midnight Jan 1, 1970, excluding leap seconds. + */ + public static final TimeLocationUnits t1970= new TimeLocationUnits("t1970","Seconds since midnight Jan 1, 1970", + Units.seconds, Basis.since1970 ); + + /** + * milliseconds since midnight Jan 1, 1970, excluding leap seconds. + */ + public static final TimeLocationUnits ms1970= new TimeLocationUnits("ms1970","Milliseconds since midnight Jan 1, 1970", + Units.milliseconds, Basis.since1970 ); + + /** + * roughly days since noon on some day in 1958, Julian - 2436204.5 to be more precise. + */ + public static final TimeLocationUnits mj1958= new TimeLocationUnits("mj1958","Julian - 2436204.5", + Units.days, Basis.since1958 ); + + /** + * The Modified Julian Day (MJD) is the number of days (with decimal fraction of the day) that have elapsed since midnight at the beginning of Wednesday November 17, 1858. + * Julian - 2400000.5 + */ + public static final TimeLocationUnits mjd= new TimeLocationUnits("mjd", "days since midnight November 17, 1858.", + Units.days , Basis.modifiedJulian ); + + static { + ((Units)t2000).registerConverter(us2000, UnitsConverter.MICRO); + ((Units)us1980).registerConverter(us2000, new UnitsConverter.ScaleOffset(1.0, -631152000000000L ) ); + ((Units)t2000).registerConverter(t1970, new UnitsConverter.ScaleOffset(1.0, 9.466848e8)); + ((Units)t1970).registerConverter(ms1970, UnitsConverter.MILLI ); + ((Units)t2000).registerConverter(t2010, new UnitsConverter.ScaleOffset(1.0, -3.1561920e+8 )); + ((Units)t2000).registerConverter(mj1958, new UnitsConverter.ScaleOffset(1.0/8.64e4, 15340 )); + ((Units)t2000).registerConverter(mjd, new UnitsConverter.ScaleOffset(1.0/8.64e4, 51544 )); + } + + /**** ratiometric units ***********/ + + public static final Units percent= new NumberUnits("%",""); + + /** + * Define a set of units to describe ratiometric (logarithmic) spacing. Note that Units.percent + * is no longer the defacto ratiometric spacing, and Units.percentIncrease takes its place. + * Note the log10Ratio is the preferred method for expressing spacing, but all are convertible + * See logERatio, log10Ratio and google for "fold change." + */ + + /* percentIncrease is defined as ( b-a )*100. / a. So { 1,2,4,8 } has a spacing of 100 % diff. */ + public static final Units dB = new NumberUnits("dB","decibels"); + public static final Units ampRatio= new NumberUnits("ampratio","amplitude ratio"); + public static final Units percentIncrease= new NumberUnits("% diff","Special dimensionless number, useful for expressing on logarithmic scale. 100% indicates a doubling"); + public static final Units log10Ratio= new NumberUnits("log10Ratio", "Special dimensionless number, useful for expressing distances on a log10 scale" ); + public static final Units logERatio= new NumberUnits("logERatio", "Special dimensionless number, useful for expressing distances on a logE scale" ); + private static class PercentRatioConverter extends UnitsConverter { + @Override + public double convert(double value) { + return ( Math.exp(value) - 1.0 ) * 100; + } + @Override + public UnitsConverter getInverse() { + if (inverse == null) { + inverse = new UnitsConverter() { + @Override + public double convert(double value) { + return Math.log( value / 100 + 1. ); + } + @Override + public UnitsConverter getInverse() { + return PercentRatioConverter.this; + } + }; + } + return inverse; + } + } + + /** + * see http://en.wikipedia.org/wiki/Decibel + */ + private static class AmpRatioConverter extends UnitsConverter { + @Override + public double convert(double value) { + return ( Math.pow(10,value/20.) ); + } + @Override + public UnitsConverter getInverse() { + if (inverse == null) { + inverse = new UnitsConverter() { + @Override + public double convert(double value) { + return 20 * Math.log10( value ); + } + @Override + public UnitsConverter getInverse() { + return AmpRatioConverter.this; + } + }; + } + return inverse; + } + } + + static { + log10Ratio.registerConverter( logERatio, new UnitsConverter.ScaleOffset( Math.log(10), 0. ) ); + logERatio.registerConverter( percentIncrease, new PercentRatioConverter() ); + dB.registerConverter( log10Ratio, new UnitsConverter.ScaleOffset( 10, 0 ) ); + dB.registerConverter( ampRatio, new AmpRatioConverter() ); + } + + /* static { + unitsMap.put("mj1958", Units.mj1958); + unitsMap.put("t1970", Units.t1970); + unitsMap.put("t2000", Units.t2000); + unitsMap.put("us2000", Units.us2000); + unitsMap.put("seconds", Units.seconds); + unitsMap.put("s", Units.seconds); + unitsMap.put("days", Units.days); + unitsMap.put("microseconds", Units.microseconds); + unitsMap.put("", Units.dimensionless); + unitsMap.put("dB", Units.dB); + + unitsMap.put("Hz", Units.hertz); + unitsMap.put("kHz", Units.kiloHertz); + unitsMap.put("MHz", Units.megaHertz); + }*/ + + private String id; + private String description; + private final Map conversionMap = new ConcurrentHashMap(); + + protected Units( String id ) { + this( id, "" ); + }; + + protected Units( String id, String description ) { + this.id= id; + this.description= description; + unitsMap.put( id, this ); + }; + + /** + * get the id uniquely identifying the units. Note the id may contain + * special tokens, like "since" for time locations. + * @return the id. + */ + public String getId() { + return this.id; + } + + /** + * register a converter between the units. Note these converters can be + * changed together to derive conversions. (A to B, B to C defines A to C.) + * @param toUnits the target units + * @param converter the converter that goes from this unit to target units. + */ + public void registerConverter(Units toUnits, UnitsConverter converter) { + conversionMap.put(toUnits, converter); + UnitsConverter inverse = (UnitsConverter)toUnits.conversionMap.get(this); + if (inverse == null || inverse.getInverse() != converter) { + toUnits.registerConverter(this, converter.getInverse()); + } + } + + /** + * return the units to which this unit is convertible. + * @return the units to which this unit is convertible. + */ + public Units[] getConvertableUnits() { + Set result= new HashSet(); + LinkedList queue = new LinkedList(); + queue.add(this); + while (!queue.isEmpty()) { + Units current = (Units)queue.removeFirst(); + for (Map.Entry entry : current.conversionMap.entrySet()) { + Units next = (Units)entry.getKey(); + if (!result.contains(next)) { + queue.add(next); + result.add(next); + } + } + } + return (Units[])result.toArray( new Units[result.size()] ); + } + + /** + * return true if the unit can be converted to toUnits. + * @deprecated use isConvertibleTo (which does not contain spelling error) + * @param toUnits Units object. + * @return true if the unit can be converted to toUnits. + */ + public boolean isConvertableTo( Units toUnits ) { + UnitsConverter result= getConverterInternal(this, toUnits); + return result!=null; + } + + /** + * return true if the unit can be converted to toUnits. + * @param toUnits Units object. + * @return true if the unit can be converted to toUnits. + */ + public boolean isConvertibleTo( Units toUnits ) { + UnitsConverter result= getConverterInternal(this, toUnits); + return result!=null; + } + + /** + * lookup the UnitsConverter object that takes numbers from fromUnits to toUnits. + * This will chain together UnitsConverters registered via units.registerConverter. + * @param fromUnits units instance that is the source units. + * @param toUnits units instance that is the target units. + * @return UnitsConverter object + * @throws InconvertibleUnitsException when the conversion is not possible. + */ + public static UnitsConverter getConverter( final Units fromUnits, final Units toUnits ) { + logger.log(Level.FINER, "getConverter( {0} to {1} )", new Object[]{fromUnits, toUnits}); //TODO: THIS IS CALLED WITH EVERY REPAINT!!! + UnitsConverter result= getConverterInternal(fromUnits, toUnits); + if ( result==null ) { + throw new InconvertibleUnitsException( fromUnits, toUnits ); + } + return result; + } + + /** + * lookup the UnitsConverter object that takes numbers from fromUnits to toUnits. + * This will chain together UnitsConverters registered via units.registerConverter. + * @param fromUnits + * @param toUnits + * @return UnitsConverter object + * @throws InconvertibleUnitsException when the conversion is not possible. + */ + private static UnitsConverter getConverterInternal( final Units fromUnits, final Units toUnits ) { + logger.log(Level.FINE, "fromUnits={0} {1} toUnits={2} {3}", new Object[]{fromUnits,fromUnits.hashCode(), toUnits,toUnits.hashCode()}); + if (fromUnits == toUnits) { + return UnitsConverter.IDENTITY; + } + + UnitsConverter o = fromUnits.conversionMap.get(toUnits); + if ( o != null) { + return o; + } + + Map visited = new HashMap(); + visited.put(fromUnits, null); + LinkedList queue = new LinkedList(); + queue.add(fromUnits); + while (!queue.isEmpty()) { + Units current = (Units)queue.removeFirst(); + for ( Map.Entry entry : current.conversionMap.entrySet() ) { + Units next = (Units)entry.getKey(); + if (!visited.containsKey(next)) { + visited.put(next, current); + queue.add(next); + if (next == toUnits) { + logger.log(Level.FINE, "build conversion from {0} to {1}", new Object[]{fromUnits, toUnits}); + return buildConversion(fromUnits, toUnits, visited); + } + } + } + } + return null; + } + + private static UnitsConverter buildConversion(Units fromUnits, Units toUnits, Map parentMap) { + ArrayList list = new ArrayList(); + Units current = toUnits; + while (current != null) { + list.add(current); + current = (Units)parentMap.get(current); + } + UnitsConverter converter = UnitsConverter.IDENTITY; + for (int i = list.size() - 1; i > 0; i--) { + Units a = (Units)list.get(i); + Units b = (Units)list.get(i - 1); + UnitsConverter c = (UnitsConverter)a.conversionMap.get(b); + converter = converter.append(c); + } + fromUnits.registerConverter(toUnits, converter); + return converter; + } + + /** + * Get the converter that goes from this Unit to toUnits. E.g. + * Units.meters.getConverter(Units.centimeters) yields a converter that + * multiplies by 100. + * @param toUnits + * @return a converter from this unit to toUnits. + * @throws IllegalArgumentException if conversion between units is not possible + */ + public UnitsConverter getConverter( Units toUnits ) { + return getConverter( this, toUnits ); + } + + /** + * convert the double in this units' space to toUnits' space. + * @param toUnits the units. + * @param value the value in toUnits. + * @return the double in the new units system. + */ + public double convertDoubleTo( Units toUnits, double value ) { + if ( this==toUnits ) { + return value; + } else { + return getConverter(this,toUnits).convert(value); + } + } + + @Override + public String toString() { + return id; + } + + /** + * return the units from the Basis for the unit, such as "seconds" in + * "seconds since midnight, Jan 1, 1970" + * @return this units offsets. + */ + public Units getOffsetUnits() { + return this; + } + + /** + * return the Basis which defines the meaning of zero and the direction of positive values, such as + * "since midnight, Jan 1, 1970" + * @return the Basis object, which simply identifies a basis. + */ + public Basis getBasis() { + return Basis.physicalZero; + } + + public abstract Datum createDatum( double value ); + public abstract Datum createDatum( int value ); + public abstract Datum createDatum( long value ); + public abstract Datum createDatum( Number value ); + + public abstract Datum createDatum( double value, double resolution ); + + private final static double FILL_DOUBLE= -1e31; + private final static float FILL_FLOAT= -1e31f; + private final static int FILL_INT= Integer.MAX_VALUE; + private final static long FILL_LONG= Long.MAX_VALUE; + + public double getFillDouble() { return FILL_DOUBLE; } + public float getFillFloat() { return FILL_FLOAT; } + public int getFillInt() { return FILL_INT; } + public long getFillLong() { return FILL_LONG; } + public Datum getFillDatum() { return this.createDatum(FILL_DOUBLE); } + + public boolean isFill( double value ) { return valueFILL_DOUBLE/10 ; + } + + public abstract DatumFormatterFactory getDatumFormatterFactory(); + + public abstract Datum parse(String s) throws ParseException; + public String format( Datum datum ) { + return getDatumFormatterFactory().defaultFormatter().format(datum); + } + public String grannyFormat( Datum datum ) { + return getDatumFormatterFactory().defaultFormatter().grannyFormat(datum); + } + + public abstract Datum add( Number a, Number b, Units bUnits ); + public abstract Datum subtract( Number a, Number b, Units bUnits ); + public abstract Datum multiply( Number a, Number b, Units bUnits ); + public abstract Datum divide( Number a, Number b, Units bUnits ); + + /** + * return all the known units. + * @return list of all the known units. + */ + public static List getAllUnits() { + return new ArrayList(unitsMap.keySet()); + } + + /** + * returns a Units object with the given string representation that is stored in the unitsMap. + * + * @param s units identifier + * @return units object + * @throws IllegalArgumentException if the unit is not recognized. + */ + public static Units getByName(String s) { + Units units = (Units)unitsMap.get(s); + if (units == null) { + throw new IllegalArgumentException("Unrecognized units: "+s); + } else return units; + } + + /** + * return canonical das2 unit for colloquial time. + * @param s string containing time unit like s, sec, millisec, etc. + * @return + */ + public static Units lookupTimeLengthUnit(String s) throws ParseException { + s= s.toLowerCase().trim(); + if ( s.startsWith("sec") || s.equals("s") ) { + return Units.seconds; + } else if ( s.startsWith("ms") || s.startsWith("millisec") || s.startsWith("milliseconds") ) { + return Units.milliseconds; + } else if ( s.equals("hr") || s.startsWith("hour") ) { + return Units.hours; + } else if ( s.equals("mn") || s.startsWith("min") ) { + return Units.minutes; + } else if ( s.startsWith("us") || s.startsWith("\u00B5s" ) || s.startsWith("micros")) { + return Units.microseconds; + } else if ( s.startsWith("ns") || s.startsWith("nanos" ) ) { + return Units.nanoseconds; + } else if ( s.startsWith("d") ) { //TODO: yikes... + return Units.days; + } else { + throw new ParseException("failed to identify unit: "+s,0); + } + } + + /** + * lookupUnits canonical units object, or allocate one. If one is + * allocated, then parse for "<unit> since <datum>" and add conversion to + * "microseconds since 2000-001T00:00." Note leap seconds are ignored! + * @param base the base time, for example 2000-001T00:00. + * @param offsetUnits the offset units for example microseconds. Positive values of the units will be since the base time. + * @return the unit. + */ + public static synchronized Units lookupTimeUnits( Datum base, Units offsetUnits ) { + Units result; + String canonicalName = "" + offsetUnits + " since "+ base; + try { + result= Units.getByName(canonicalName); + return result; + } catch ( IllegalArgumentException ex ) { + Basis basis= new Basis( "since "+ base, "since "+ base, Basis.since2000, base.doubleValue(Units.us2000), Units.us2000.getOffsetUnits() ); + result= new TimeLocationUnits( canonicalName, canonicalName, offsetUnits, basis ); + result.registerConverter( Units.us2000, + new UnitsConverter.ScaleOffset( + offsetUnits.convertDoubleTo(Units.microseconds, 1.0), + base.doubleValue(Units.us2000) ) ); + return result; + } + } + + /** + * lookupUnits canonical units object, or allocate one. If one is + * allocated, then parse for "<unit> since <datum>" and add conversion to + * "microseconds since 2000-001T00:00" (us2000). Note leap seconds are ignored + * in the returned units, so each day is 86400 seconds long, and differences in + * times should not include leap seconds. Note this contains a few kludges + * as this for datasets encountered by Autoplot. + * @param units string like "microseconds since 2000-001T00:00" which will be the id. + * @return a units object that implements. + * @throws java.text.ParseException if the time cannot be parsed, etc. + */ + public static synchronized Units lookupTimeUnits( String units ) throws ParseException { + + Units result; + + //see if it's already registered. + try { + result= Units.getByName(units); + return result; + } catch ( IllegalArgumentException ex ) { + //do nothing until later + } + + if(units.trim().equalsIgnoreCase("UTC")) return us2000; + + String[] ss= units.split("since"); + Units offsetUnits= lookupTimeLengthUnit(ss[0]); + Datum datum; + + if ( ss[1].equals(" 1-1-1 00:00:00" ) ) { // make this into something that won't crash. + //datum= Units.mj1958.createDatum(-714779); + ss[1]= "1901-01-01 00:00:00"; // /media/mini/data.backup/examples/netcdf/sst.ltm.1961-1990.nc + } + if ( ss[1].contains("1970-01-01 00:00:00.0 0:00") ) { + ss[1]= "1970-01-01 00:00:00"; + } + if ( ss[1].endsWith(" UTC") ) { // http://www.ngdc.noaa.gov/stp/satellite/dmsp/f16/ssj/2011/01/f16_20110101_ssj.h5?TIME + ss[1]= ss[1].substring(0,ss[1].length()-4); + } + datum= TimeUtil.create(ss[1]); + return lookupTimeUnits( datum, offsetUnits ); + } + + /** + * lookupUnits canonical units object, or allocate one. + * Examples include: + * "nT" where it's already allocated, + * "apples" where it allocates a new one, and + * "seconds since 2011-12-21T00:00" where it uses lookupTimeUnits. + * @param sunits string identifier. + * @return canonical units object. + */ + public static synchronized Units lookupUnits(String sunits) { + Units result; + sunits= sunits.trim(); + try { + result= Units.getByName(sunits); + + } catch ( IllegalArgumentException ex ) { + if ( sunits.contains(" since ") || sunits.equalsIgnoreCase("UTC") ) { + try { + result = lookupTimeUnits(sunits); + } catch (ParseException ex1) { + result= new NumberUnits( sunits ); + } + } else if ( sunits.equals("sec") ) { // begin, giant table of kludges + result= Units.seconds; + } else if ( sunits.equals("msec") ) { // CDF + result= Units.milliseconds; + } else if ( sunits.contains("(All Qs)")) { //themis files have this annotation on the units. Register a converter. TODO: solve this in a nice way. The problem is I wouldn't want to assume nT(s) doesn't mean nT * sec. + result= new NumberUnits( sunits ); + Units targetUnits= lookupUnits( sunits.replace("(All Qs)","").trim() ); + result.registerConverter( targetUnits, UnitsConverter.IDENTITY ); + } else { + Pattern multPattern= Pattern.compile("([.0-9]+)\\s*([a-zA-Z]+)"); + Matcher m= multPattern.matcher(sunits); + if ( m.matches() ) { // kludge for ge_k0_mgf which has "0.1nT" for units. We register a converter when we see these. Note this is going to need more attention + try { + Units convTo; + convTo = lookupUnits(m.group(2)); + if ( convTo!=null ) { + double fact= Double.parseDouble(m.group(1)); + result= new NumberUnits( sunits ); + result.registerConverter( convTo, new UnitsConverter.ScaleOffset(fact,0.0) ); + } else { + result= lookupUnits(sunits); + } + } catch ( NumberFormatException ex2 ) { + result= lookupUnits(sunits); + } + } else { + result= new NumberUnits( sunits ); + } + } + } + + // look to see if there is a standard unit for this and register a converter if so. E.g. [ms]<-->ms + String stdunits= sunits; + if ( stdunits.startsWith("[") && stdunits.endsWith("]") ) { // we can't just pop these off. Hudson has case where this causes problems. We need to make units in vap files canonical as well. + stdunits= stdunits.substring(1,stdunits.length()-1); + } + if ( stdunits.startsWith("(") && stdunits.endsWith(")") ) { // often units get [] or () put around them. Pop these off. + stdunits= stdunits.substring(1,stdunits.length()-1); + } + if ( !stdunits.equals(sunits) ) { + Units stdUnit= lookupUnits(stdunits); // we need to register "foo" when "[foo]" so that order doesn't matter. + if ( !stdUnit.isConvertibleTo(result) ) { + logger.log(Level.FINE, "registering identity converter {0} -> {1}", new Object[]{stdUnit, result}); + stdUnit.registerConverter( result, UnitsConverter.IDENTITY ); + stdUnit.getConverter(result); + } + } + return result; + } + + public static void main( String[] args ) throws java.text.ParseException { + //Datum ratio = Datum.create(100); + Datum ratio = Units.ampRatio.createDatum(100); + Datum db = ratio.convertTo(dB); + System.out.println("ratio: " + ratio); + System.out.println("dB: " + db); + + Datum Hz = Datum.create(1000000.0, hertz); + Datum kHz = Hz.convertTo(kiloHertz); + Datum MHz = kHz.convertTo(megaHertz); + System.out.println("Hz: " + Hz); + System.out.println("kHz: " + kHz); + System.out.println("MHz: " + MHz); + + System.err.println( Units.ms1970.createDatum(1000) ); + } +} diff --git a/dasCore/src/main/java/org/das2/datum/UnitsConverter.java b/dasCore/src/main/java/org/das2/datum/UnitsConverter.java new file mode 100644 index 000000000..cf44b15ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/UnitsConverter.java @@ -0,0 +1,227 @@ +/* File: UnitsConverter.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum; + +/** + * + * @author jbf + */ +public abstract class UnitsConverter { + + public static final UnitsConverter IDENTITY = new UnitsConverter() { + public UnitsConverter getInverse() { + return this; + } + public double convert(double value) { + return value; + } + public String toString() { + return "IDENTITY UnitsConverter"; + } + }; + + public static final UnitsConverter TERA = new ScaleOffset(1e-12, 0.0); + public static final UnitsConverter GIGA = new ScaleOffset(1e-9, 0.0); + public static final UnitsConverter MEGA = new ScaleOffset(1e-6, 0.0); + public static final UnitsConverter KILO = new ScaleOffset(1e-3, 0.0); + public static final UnitsConverter MILLI = new ScaleOffset(1e3, 0.0); + public static final UnitsConverter CENTI = new ScaleOffset(1e2, 0.0); + public static final UnitsConverter MICRO = new ScaleOffset(1e6, 0.0); + public static final UnitsConverter NANO = new ScaleOffset(1e9, 0.0); + public static final UnitsConverter PICO = new ScaleOffset(1e12, 0.0); + + protected UnitsConverter inverse; + + protected UnitsConverter() { + } + + protected UnitsConverter(UnitsConverter inverse) { + this.inverse = inverse; + } + + public abstract UnitsConverter getInverse(); + + public abstract double convert(double value); + + public Number convert( Number number ) { + double value = number.doubleValue(); + value = convert(value); + if (number instanceof Integer) { + return new Integer((int)value); + } + else if (number instanceof Long) { + return new Long((long)value); + } + else { + return new Double(value); + } + } + + public UnitsConverter append(UnitsConverter that) { + return new Appended(this, that); + } + + public static class ScaleOffset extends UnitsConverter { + private final double offset; + private final double scale; + private final int hashCode; + + /** + * Creates a new UnitsConverter.ScaleOffset. This + * converter multiplies by scale and adds offset, so + * offset is in the target Units. For example, + * deg C to deg F would be + *
    new UnitsConverter.ScaleOffset( 9./5, 32 )
    . + * + */ + public ScaleOffset(double scale, double offset) { + this(scale, offset, null); + } + + private ScaleOffset(double scale, double offset, UnitsConverter inverse) { + super(inverse); + this.scale = scale; + this.offset = offset; + hashCode = computeHashCode(); + } + + private int computeHashCode() { + long scaleBits = Double.doubleToLongBits(scale); + long offsetBits = Double.doubleToLongBits(offset); + long code = (11 * 13 * 13) + (13 * scaleBits) + offsetBits; + int a = (int)(code >> 32); + int b = (int)(0xFFFFFFFFL & code); + return a + b; + } + + public UnitsConverter getInverse() { + if (((UnitsConverter)this).inverse == null) { + ((UnitsConverter)this).inverse = new ScaleOffset(1.0 / scale, -(offset / scale), this); + } + return ((UnitsConverter)this).inverse; + } + + public double convert( double value ) { + return scale * value + offset; + } + + public UnitsConverter append(UnitsConverter that) { + if (this.equals(IDENTITY)) { + return that; + } + else if (that.equals(IDENTITY)) { + return this; + } + else if (that instanceof ScaleOffset) { + ScaleOffset so = (ScaleOffset)that; + double aScale = this.scale * so.scale; + double aOffset = this.offset * so.scale + so.offset; + return new ScaleOffset(aScale, aOffset); + } + else { + return super.append(that); + } + } + + public boolean equals(Object o) { + if (!(o instanceof ScaleOffset)) { + return false; + } + ScaleOffset that = (ScaleOffset)o; + return this.scale == that.scale && this.offset == that.offset; + } + + public String toString() { + return getClass().getName() + "[scale=" + scale + ",offset=" + offset + "]"; + } + + public int hashCode() { + return hashCode; + } + + } + + public static class Appended extends UnitsConverter{ + + UnitsConverter[] converters; + + public Appended(UnitsConverter uc1, UnitsConverter uc2) { + UnitsConverter[] a1 = ucToArray(uc1); + UnitsConverter[] a2 = ucToArray(uc2); + converters = new UnitsConverter[a1.length + a2.length]; + for (int i = 0; i < a1.length; i++) { + converters[i] = a1[i]; + } + for (int i = 0; i < a2.length; i++) { + converters[i + a1.length] = a2[i]; + } + } + + private Appended(UnitsConverter[] array, UnitsConverter inverse) { + super(inverse); + converters = array; + } + + public double convert(double value) { + for (int i = 0; i < converters.length; i++) { + value = converters[i].convert(value); + } + return value; + } + + public Number convert(Number value) { + for (int i = 0; i < converters.length; i++) { + value = converters[i].convert(value); + } + return value; + } + + public UnitsConverter getInverse() { + if (inverse == null) { + int length = converters.length; + UnitsConverter[] inverseArray = new UnitsConverter[length]; + for (int i = 0; i < length; i++) { + inverseArray[i] = converters[length - i - 1].getInverse(); + } + inverse = new Appended(inverseArray, this); + } + return inverse; + } + + private static UnitsConverter[] ucToArray(UnitsConverter uc) { + if (uc instanceof Appended) { + return ((Appended)uc).converters; + } + else { + return new UnitsConverter[] {uc}; + } + } + + } + + public static UnitsConverter getConverter(Units fromUnits, Units toUnits) { + return Units.getConverter(fromUnits,toUnits); + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/UnitsUtil.java b/dasCore/src/main/java/org/das2/datum/UnitsUtil.java new file mode 100644 index 000000000..69abf1d73 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/UnitsUtil.java @@ -0,0 +1,153 @@ +/* + * UnitsUtil.java + * + * Created on December 1, 2004, 10:25 PM + */ + +package org.das2.datum; + +/** + * + * @author Jeremy + */ +public class UnitsUtil { + + /** + * returns true if the unit is used to measure distance in a logarithmic + * space, such as decibels or percent increase. Note Units.dimensionless + * are not considered ratiometric. (Of course, all ratiometic + * units are dimensionless...) + */ + public static final boolean isRatiometric( Units unit ) { + return unit!=Units.dimensionless && unit.isConvertableTo(Units.logERatio); + } + + /** + * returns true if the unit describes a location in time, as in us2000. + */ + public static final boolean isTimeLocation( Units unit ) { + return unit.isConvertableTo(Units.us2000); + } + + /** + * returns true if the unit is a ratio measurement, meaning there is a physical zero + * and you can make meaningful ratios between arbitary numbers. All operations + * like add, multiply and divide are allowed. (What about negative numbers? We + * need a statistician!) + * Examples include "5 km" or "0.2/cc" and "15 counts" + * See http://en.wikipedia.org/wiki/Level_of_measurement + * @param unit + * @return + */ + public static final boolean isRatioMeasurement( Units unit ) { + return !(unit instanceof EnumerationUnits) && unit.getOffsetUnits()==unit; + } + + /** + * returns true if the unit is a interval measurement, meaning the choice of + * zero is arbitrary. Subtraction and comparison are allowed, but addition, + * multiplication and division are invalid operators. + * Examples include "2008-04-09T14:27:00Z" and 15 deg W Longitude. + * See http://en.wikipedia.org/wiki/Level_of_measurement + * @param unit + * @return + */ + public static final boolean isIntervalMeasurement( Units unit ) { + return !(unit instanceof EnumerationUnits) && unit.getOffsetUnits()!=unit; + } + /** + * returns true if the unit is nominal, meaning that Datums with this unit + * can only be equal or not equal. Currently all nominal data is stored + * as ordinal data, so this always returns false. + * Examples include "Iowa City", and "Voyager 1". + * See http://en.wikipedia.org/wiki/Level_of_measurement + * @param unit + * @return true if the unit is nominal. + */ + public static final boolean isNominalMeasurement( Units unit ) { + return false; + } + + /** + * returns true if the unit is ordinal, meaning that Datums with this unit + * can only be equal or not equal, or compared. subtract, add, multiply, + * divide are invalid. + * Examples include energy bin labels and quality measures. + * See http://en.wikipedia.org/wiki/Level_of_measurement + * @param unit + * @return true if the unit is ordinal. + */ + public static final boolean isOrdinalMeasurement( Units unit ) { + return unit instanceof EnumerationUnits; + } + + /** + * returns the unit whose product with the parameter unit is + * unity. + */ + public static Units getInverseUnit( Units unit ) { + if ( unit==Units.seconds ) { + return Units.hertz; + } else if ( unit==Units.hertz ) { + return Units.seconds; + } else if ( unit==Units.dimensionless ) { + return Units.dimensionless; + } else if ( unit==Units.milliseconds ) { + return Units.kiloHertz; + } else if ( unit==Units.microseconds ) { + return Units.megaHertz; + } else { + throw new IllegalArgumentException( "units not supported: "+unit ); + } + } + + /** + * Special division operation that either does the Datum division if + * possible, or returns the division of the magitude parts of the + * Datums plus the unit names "A/B", suitable for human consumption. + */ + public static String divideToString( Datum aDatum, Datum bDatum ) { + try { + Datum result= divide( aDatum, bDatum ); + return String.valueOf(result); + } catch ( IllegalArgumentException e ) { + Units aUnits= aDatum.getUnits(); + Units bUnits= bDatum.getUnits(); + double a= aDatum.doubleValue(aUnits); + double b= bDatum.doubleValue(bUnits); + return ""+(a/b)+" "+aUnits+" / " +bUnits; + } + } + + /** + * attempt to perform the division of two Datums by looking for + * convertable units or dimensionless. + */ + public static Datum divide( Datum aDatum, Datum bDatum ) { + Units bUnits= bDatum.getUnits(); + Units aUnits= aDatum.getUnits(); + + Units bInvUnits; + try{ + bInvUnits= getInverseUnit(bUnits); + } catch ( IllegalArgumentException e ) { + bInvUnits= null; + } + + double a= aDatum.doubleValue(aUnits); + double b= bDatum.doubleValue(bUnits); + + if ( bUnits==Units.dimensionless ) { + return aUnits.createDatum( a/b ); + } else if ( aUnits==Units.dimensionless ) { + return bInvUnits.createDatum(a/b); + } else { + if ( !bUnits.isConvertableTo(aUnits) ) { + throw new IllegalArgumentException("unable to calculate, b units not convertable to a"); + } else { + UnitsConverter uc= bUnits.getConverter(aUnits); + return Units.dimensionless.createDatum( a / uc.convert(b) ); + } + } + } +} diff --git a/dasCore/src/main/java/org/das2/datum/format/DatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/DatumFormatter.java new file mode 100644 index 000000000..9b0047534 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/DatumFormatter.java @@ -0,0 +1,99 @@ +/* File: DatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 24, 2003, 4:45 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; + +/** Formats Datum objects for printing and parses strings to Datum objects. + * + * @author Edward West + */ +public abstract class DatumFormatter { + //TODO: consider the following api: + /* + * String format( Datum datum ) returns fully-qualified datum e.g. "12 days" + * String format( Datum datum, Units units ) returns formatted in the context of units (e.g.for axis) + * + * we've considered this and it needs to be implemented. + */ + + /** Available for use by subclasses */ + protected DatumFormatter() {} + + /* + * format the Datum so that it is understood out-of-context. For + * example, "4.5 seconds" + */ + public abstract String format( Datum datum ); + + /* + * format the Datum in the context of a given unit. For example, + * "4.5". It is acceptable to return the fully-qualified Datum, which + * is also the default class behavior. This will give somewhat undesirable + * results on axes. + */ + public String format( Datum datum, Units units ) { + return format( datum ); + } + + + + /** Returns the datum formatted as a String with special formatting + * characters. As with format, this should be out-of-context and should + * be tagged with the Units. + * + * The default implementation just returns the result of + * {@link #format(org.das2.datum.Datum)} + */ + public String grannyFormat(Datum datum) { + return format(datum); + } + + + /** formats the Datum in the context of the units. + */ + public String grannyFormat( Datum datum, Units units ) { + return format( datum, units ); + } + + /** + * format the set of Datums using a consistent and optimized format. + * First introduced to support DasAxis, where tighter coupling between + * the two is required to efficiently provide context. + * @param datums + * @param context visible range, context should be provided. + * @return + */ + public String[] axisFormat( DatumVector datums, DatumRange context ) { + String [] result= new String[datums.getLength()]; + for ( int i=0; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +/** + * + * @author Edward West + */ +public abstract class DatumFormatterFactory { + + /** provided for use by subclasses */ + protected DatumFormatterFactory() {} + + public abstract DatumFormatter newFormatter(String format) throws java.text.ParseException; + + public abstract DatumFormatter defaultFormatter(); +} diff --git a/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatter.java new file mode 100644 index 000000000..52edbe79d --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatter.java @@ -0,0 +1,185 @@ +/* File: DefaultDatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 1, 2003, 4:45 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.datum.format; + +import org.das2.util.NumberFormatUtil; +import org.das2.util.DasMath; + +import java.text.*; +import java.util.Locale; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; + +/** Formats Datum objects for printing and parses strings to Datum objects. + * + * @author Edward West + */ +public class DefaultDatumFormatter extends DatumFormatter { + + private String formatString; + private NumberFormat format; + + /** Available for use by subclasses */ + protected DefaultDatumFormatter() { + } + + /** Creates a new instance of DatumFormatter */ + public DefaultDatumFormatter(String formatString) throws ParseException { + if (formatString.equals("")) { + this.formatString = ""; + format = null; + } else { + this.formatString = formatString; + format = NumberFormatUtil.getDecimalFormat(formatString); + } + } + + public String format(Datum datum) { + return format(datum, datum.getUnits()) + " " + datum.getUnits(); + } + + public String format(Datum datum, Units units) { + double d = datum.doubleValue(units); + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "" + d; + } + String result; + if (format == null) { + double resolution = datum.getResolution(units.getOffsetUnits()); + result = formatLimitedResolution(d, resolution); + } else { + result = format.format(datum.doubleValue(units)); + } + return result; + } + + @Override + public String grannyFormat(Datum datum, Units units) { + String formt = format(datum, units); + if (formt.indexOf("E") != -1) { + int iE = formt.indexOf("E"); + StringBuffer granny = new StringBuffer(formt.length() + 4); + String mant = formt.substring(0, iE); + if (Double.parseDouble(mant) != 1.0) { + granny.append(mant).append("\u00d7"); + } + granny.append("10").append("!A").append(formt.substring(iE + 1)).append("!N"); + formt = granny.toString(); + } + return formt; + } + + @Override + public String[] axisFormat(DatumVector datums, DatumRange context ) { + Units units= context.getUnits(); + String[] result = new String[datums.getLength()]; + for (int i = 0; i < result.length; i++) { + result[i] = format(datums.get(i), units); + } + boolean hasMant = false; + for (int i = 0; i < result.length; i++) { + String res1 = result[i]; + if (res1.indexOf("E") != -1) { + int iE = res1.indexOf("E"); + String mant = res1.substring(0, iE); + if (Double.parseDouble(mant) != 1.0) { + hasMant = true; + } + } + } + for (int i = 0; i < result.length; i++) { + String res1 = result[i]; + if (res1.indexOf("E") != -1) { + int iE = res1.indexOf("E"); + StringBuffer granny = new StringBuffer(res1.length() + 4); + String mant = res1.substring(0, iE); + + if (hasMant) { + granny.append(mant).append("\u00d7"); + } + + granny.append("10").append("!A").append(res1.substring(iE + 1)).append("!N"); + result[i] = granny.toString(); + } + + } + return result; + } + + public String grannyFormat(Datum datum) { + return grannyFormat(datum, datum.getUnits()) + " " + datum.getUnits(); + } + + public String toString() { + return formatString; + } + + private String formatLimitedResolution(double d, double resolution) { + String result; + if (resolution == 0. && Double.toString(d).length() > 7) { + // make the default resolution be 0.01%. + resolution = d / 10000; + } + if (resolution > 0) { + // 28 --> scale = -1 + // 2.8 --> scale = 0 + int scale = (int) Math.ceil(-1 * DasMath.log10(resolution) - 0.00001); + int exp; + if (d != 0.) { + exp = (int) DasMath.log10(Math.abs(d)); + } else { + exp = 0; + } + if (scale >= 0) { + DecimalFormat f; + if (exp <= -5 || exp >= 5) { + f = NumberFormatUtil.getDecimalFormat("0E0"); + f.setMinimumFractionDigits(scale + exp - 1); + f.setMaximumFractionDigits(scale + exp - 1); + } else { + f = NumberFormatUtil.getDecimalFormat("0"); + f.setMinimumFractionDigits(scale); + f.setMaximumFractionDigits(scale); + } + result = f.format(d); + } else { + double round = DasMath.exp10(-1 * scale); + d = Math.round(d / round) * round; + DecimalFormat f; + if (exp <= -5 || exp >= 5) { + f = NumberFormatUtil.getDecimalFormat("0E0"); + f.setMinimumFractionDigits(scale + exp + 1); + f.setMaximumFractionDigits(scale + exp + 1); + } else { + f = NumberFormatUtil.getDecimalFormat("0"); + } + result = f.format(d); + } + } else { + result = Double.toString(d); + } + return result; + } +} diff --git a/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatterFactory.java b/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatterFactory.java new file mode 100644 index 000000000..4566068a1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/DefaultDatumFormatterFactory.java @@ -0,0 +1,61 @@ +/* File: DatumFormatterFactory.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 25, 2003, 3:35 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +/** + * + * @author Edward West + */ +public final class DefaultDatumFormatterFactory extends DatumFormatterFactory { + + private static DatumFormatterFactory factory; + + /** provided for use by subclasses */ + protected DefaultDatumFormatterFactory() { + } + + public DatumFormatter newFormatter(String format) throws java.text.ParseException { + return new DefaultDatumFormatter(format); + } + + /** Get an instance of this factory. */ + public static DatumFormatterFactory getInstance() { + //This isn't thread safe, but who cares. Instances are small and + //functionally identical. + if (factory == null) { + factory = new DefaultDatumFormatterFactory(); + } + return factory; + } + + public DatumFormatter defaultFormatter() { + try { + return newFormatter(""); + } + catch (java.text.ParseException pe) { + throw new RuntimeException(pe); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatter.java new file mode 100644 index 000000000..d9cb23c6c --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatter.java @@ -0,0 +1,47 @@ +/* File: EnumerationDatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 29, 2003, 4:34 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +import org.das2.datum.Datum; +import org.das2.datum.EnumerationUnits; + +/** + * + * @author Edward West + */ +public class EnumerationDatumFormatter extends DatumFormatter { + + /** Creates a new instance of EnumerationDatumFormatter */ + public EnumerationDatumFormatter() { + } + + public String toString() { + return getClass().getName(); + } + + public String format(Datum datum) { + return ((EnumerationUnits)datum.getUnits()).getObject(datum).toString(); + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatterFactory.java b/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatterFactory.java new file mode 100644 index 000000000..5d3c88370 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/EnumerationDatumFormatterFactory.java @@ -0,0 +1,51 @@ +/* File: EnumerationDatumFormatterFactory.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 29, 2003, 4:34 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +/** + * + * @author Edward West + */ +public class EnumerationDatumFormatterFactory extends DatumFormatterFactory { + + private final static EnumerationDatumFormatterFactory INSTANCE + = new EnumerationDatumFormatterFactory(); + + /** Creates a new instance of EnumerationDatumFormatterFactory */ + private EnumerationDatumFormatterFactory() { + } + + public DatumFormatter defaultFormatter() { + return new EnumerationDatumFormatter(); + } + + public DatumFormatter newFormatter(String format) throws java.text.ParseException { + return defaultFormatter(); + } + + public static EnumerationDatumFormatterFactory getInstance() { + return INSTANCE; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/ExponentialDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/ExponentialDatumFormatter.java new file mode 100644 index 000000000..6ed5ea25d --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/ExponentialDatumFormatter.java @@ -0,0 +1,107 @@ +/* File: DefaultDatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 1, 2003, 4:45 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +import org.das2.util.NumberFormatUtil; +import org.das2.util.DasMath; + +import java.text.*; +import org.das2.datum.Datum; +import org.das2.datum.Units; + +/** Formats Datums forcing a given exponent and number of decimal places. + * This is useful for axes where each of the labels should have the same + * exponent. Zero is treated specially, just "0" is returned. This helps + * one to quickly identify zero on the axis. + * + * @author Jeremy Faden + */ +public class ExponentialDatumFormatter extends DatumFormatter { + + private DecimalFormat format; + + int digits; + int exponent; + NumberFormat mantFormat; + String mantFormatString; + + /* print with digits in the mantissa, use exponent for the exponent */ + /* mEe */ + public ExponentialDatumFormatter(int digits, int exponent) { + this.digits= digits; + this.exponent= exponent; + StringBuffer buff = new StringBuffer(digits+2).append("0"); + if ( digits>1 ) buff.append('.'); + for (int i = 1; i< digits; i++) { + buff.append('0'); + } + mantFormatString= buff.toString(); + this.mantFormat= NumberFormatUtil.getDecimalFormat( buff.toString() ); + } + + public String format(Datum datum) { + return format( datum, datum.getUnits() ) + " " + datum.getUnits(); + } + + public String format( Datum datum, Units units ) { + double x= datum.doubleValue(datum.getUnits()); + if ( x == 0. ) return "0."; + double exp= DasMath.exp10(exponent); + double mant= x/exp; + double tenToN= DasMath.exp10(digits); + mant= Math.round( mant * tenToN ) / tenToN; + return mantFormat.format(mant)+"E"+exponent; + } + + public String grannyFormat( Datum datum, Units units ) { + String format= format(datum,units); + if ( format.indexOf("E")!=-1 ) { + int iE= format.indexOf("E"); + StringBuffer granny = new StringBuffer(format.length() + 4); + String mant= format.substring(0,iE); + granny.append(mant).append("\u00d7"); + granny.append("10").append("!A").append(format.substring(iE+1)).append("!N"); + format = granny.toString(); + } + return format; + } + + public String grannyFormat( Datum datum ) { + String format= format(datum,datum.getUnits()); + if ( format.indexOf("E")!=-1 ) { + int iE= format.indexOf("E"); + StringBuffer granny = new StringBuffer(format.length() + 4); + String mant= format.substring(0,iE); + granny.append(mant).append("\u00d7"); + granny.append("10").append("!A").append(format.substring(iE+1)).append("!N"); + format = granny.toString(); + } + return format + " " +datum.getUnits(); + } + + public String toString() { + return mantFormatString + "E"+exponent; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/LatinPrefixDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/LatinPrefixDatumFormatter.java new file mode 100644 index 000000000..60c643c0a --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/LatinPrefixDatumFormatter.java @@ -0,0 +1,98 @@ +/* File: DefaultDatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 1, 2003, 4:45 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +import org.das2.util.NumberFormatUtil; +import org.das2.util.DasMath; + +import java.text.*; +import java.util.Locale; +import org.das2.datum.Datum; +import org.das2.datum.Units; + +/** Formats Datums using K and M, etc labels and a specified precision. + * + * @author Jeremy Faden + */ +public class LatinPrefixDatumFormatter extends DatumFormatter { + + private DecimalFormat format; + + int digits; + int exponent; + + /* print with digits in the mantissa */ + /* m.mmK */ + public LatinPrefixDatumFormatter( int digits ) { + this.digits= digits; + } + + public String format(Datum datum) { + return format( datum, datum.getUnits() ) + " " + datum.getUnits(); + } + + public String format( Datum datum, Units units ) { + double x= datum.doubleValue(units); + if ( x == 0. ) return "0."; + int exponent= (int) DasMath.log10(1.000001*Math.abs(x)) / 3 * 3; + + String expString; + switch (exponent) { + case -18: expString="a"; break; + case -15: expString="f"; break; + case -12: expString="p"; break; + case -9: expString="n";break; + case -6: expString="\u03BC";break; // micro + case -3: expString="m";break; + case 0: expString="";break; + case 3: expString="k";break; + case 6: expString="M";break; + case 9: expString="G";break; + case 12: expString="T"; break; + default: expString=""; exponent=0; break; + } + + int sign= x < 0 ? -1 : 1; + + double exp= DasMath.exp10(exponent); + double mant= x / exp; + + int mantFracDigits= digits - (int)DasMath.log10(mant); + + StringBuffer buff = new StringBuffer(digits+2).append("0"); + if ( digits>1 ) buff.append('.'); + for (int i = 0; i< mantFracDigits; i++) { + buff.append('0'); + } + String mantFormatString= buff.toString(); + NumberFormat mantFormat= NumberFormatUtil.getDecimalFormat(buff.toString()); + + return mantFormat.format(mant) + expString; + } + + public String toString() { + return "EngineeringFormatter("+digits+" sig fig)"; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/TimeDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/format/TimeDatumFormatter.java new file mode 100644 index 000000000..6d55255c2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/TimeDatumFormatter.java @@ -0,0 +1,350 @@ +/* File: TimeDatumFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 25, 2003, 1:47 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +import java.text.*; +import java.util.regex.*; +import org.das2.datum.CalendarTime; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.TimeUtil; + +/** + * + * @author Edward West + */ +public class TimeDatumFormatter extends DatumFormatter { + + /** Private constants for referencing an array of timestamp fields */ + private static final int YEAR_FIELD_INDEX = 0; + private static final int MONTH_FIELD_INDEX = 1; + private static final int DAY_FIELD_INDEX = 2; + private static final int DOY_FIELD_INDEX = 3; + private static final int HOUR_FIELD_INDEX = 4; + private static final int MINUTE_FIELD_INDEX = 5; + private static final int SECONDS_FIELD_INDEX = 6; + private static final int TIMESTAMP_FIELD_COUNT = 7; + + /** + * yyyy-MM-dd'T'HH:mm:ss.SSS'Z + */ + public static final TimeDatumFormatter DEFAULT; + /** + * yyyy-MM-dd + */ + public static final TimeDatumFormatter DAYS; + /** + * yyyy + */ + public static final TimeDatumFormatter YEARS; + /** + * yyyy-MM + */ + public static final TimeDatumFormatter MONTHS; + /** + * yyyy-MM-dd HH:'00' + */ + public static final TimeDatumFormatter HOURS; + /** + * HH:mm + */ + public static final TimeDatumFormatter MINUTES; + /** + * HH:mm:ss + */ + public static final TimeDatumFormatter SECONDS; + /** + * HH:mm:ss.SSS + */ + public static final TimeDatumFormatter MILLISECONDS; + /** + * HH:mm:ss.SSSSSS + */ + public static final TimeDatumFormatter MICROSECONDS; + /** + * HH:mm:ss.SSSSSSSSS + */ + public static final TimeDatumFormatter NANOSECONDS; + + //Initialize final constants + static { + try { + DEFAULT = new TimeDatumFormatter("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + YEARS= new TimeDatumFormatter("yyyy"); + MONTHS= new TimeDatumFormatter("yyyy-MM"); + DAYS = new TimeDatumFormatter("yyyy-MM-dd"); + HOURS = new TimeDatumFormatter("yyyy-MM-dd HH:'00'"); + MINUTES = new TimeDatumFormatter("HH:mm"); + SECONDS = new TimeDatumFormatter("HH:mm:ss"); + MILLISECONDS = new TimeDatumFormatter("HH:mm:ss.SSS"); + MICROSECONDS = new TimeDatumFormatter("HH:mm:ss.SSSSSS"); + NANOSECONDS = new TimeDatumFormatter("HH:mm:ss.SSSSSSSSS"); + } catch (ParseException pe) { + throw new RuntimeException(pe); + } + } + + private String formatString; + + private MessageFormat format; + + private int[] scaleSeconds; + + /** Creates a new instance of TimeDatumFormatter */ + public TimeDatumFormatter(String formatString) throws ParseException { + this.formatString = formatString; + if ( formatString.contains( "%" ) ) { + format = new MessageFormat(parseTimeFormatStringPercent(formatString)); + } else { + format = new MessageFormat(parseTimeFormatString(formatString)); + } + } + + /** + * returns a TimeDatumFormatter suitable for the specified scale and context. + * Context may be null to indicate that the formatted string will be interpreted + * outside of any context. + * @param scale the length we wish to represent, such as TimeUtil.HOUR + * @param context the context for the formatter, or null if the formatted string + * will be interpreted outside of any context. + * @throws IllegalArgumentException if the scale is TimeUtil.NANOS or is not found in TimeUtil. + */ + public static TimeDatumFormatter formatterForScale(CalendarTime.Step scale, DatumRange context ) { + try { + if ( context!=null ) { + switch ( scale ) { + case YEAR: return YEARS; + case MONTH: return MONTHS; + case DAY: return DAYS; + case HOUR: return MINUTES; + case MINUTE: return MINUTES; + case SECOND: return SECONDS; + case MILLISEC: return MILLISECONDS; + case MICROSEC: return MICROSECONDS; + case NANOSEC: return NANOSECONDS; + default: throw new IllegalArgumentException("unsupported scale: "+scale); + } + } else { + switch ( scale ) { + case YEAR: return YEARS; + case MONTH: return MONTHS; + case DAY: return DAYS; + case HOUR: return HOURS; + case MINUTE: return new TimeDatumFormatter("yyyy-MM-dd HH:mm"); + case SECOND: return new TimeDatumFormatter("yyyy-MM-dd HH:mm:ss"); + case MILLISEC: return new TimeDatumFormatter("yyyy-MM-dd HH:mm:ss.SSS"); + case MICROSEC: return new TimeDatumFormatter("yyyy-MM-dd HH:mm:ss.SSSSSS"); + case NANOSEC: return new TimeDatumFormatter("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"); + default: throw new IllegalArgumentException("unsupported scale: "+scale); + } + } + } catch ( ParseException e ) { + throw new RuntimeException(e); + } + } + + public String toString() { + return formatString; + } + + public String format(Datum datum) { + if ( datum.isFill() ) return "fill"; + CalendarTime ct = new CalendarTime(datum); + Number[] array = timeStructToArray(ct); + return format.format(array); + } + + protected Format getFormat() { + return format; + } + + protected String parseTimeFormatString(String input) throws ParseException { + final String formatPattern = "(([yMDdHmsS])\\2*)"; + final String delimiterPattern = "([-/:.,_ \t]+)"; + final String literalPattern = "('(?:[^']|'')*')"; + Pattern token = Pattern.compile( + formatPattern + "|" + delimiterPattern + "|" + literalPattern + ); + int from = 0; + StringBuffer formatString = new StringBuffer(); + Matcher matcher = token.matcher(input); + while (matcher.find(from)) { + int start = matcher.start(); + if (start > from) { + char[] dots = new char[start + 1]; + java.util.Arrays.fill(dots, from, start, '.'); + dots[from] = '^'; + dots[start] = '^'; + StringBuffer errorString = new StringBuffer("Unrecognized sub-pattern\n"); + errorString.append(input).append("\n"); + errorString.append(dots); + throw new ParseException(errorString.toString(), from); + } + String format = matcher.group(1); + String delimiter = matcher.group(3); + String literal = matcher.group(4); + if (format != null) { + switch (format.charAt(0)) { + case 'y': { + appendSubFormat(formatString, YEAR_FIELD_INDEX, format.length()); + } break; + case 'M': { + appendSubFormat(formatString, MONTH_FIELD_INDEX, format.length()); + } break; + case 'D': { + appendSubFormat(formatString, DOY_FIELD_INDEX, format.length()); + } break; + case 'd': { + appendSubFormat(formatString, DAY_FIELD_INDEX, format.length()); + } break; + case 'H': { + appendSubFormat(formatString, HOUR_FIELD_INDEX, format.length()); + } break; + case 'm': { + appendSubFormat(formatString, MINUTE_FIELD_INDEX, format.length()); + } break; + case 's': { + appendSubFormat(formatString, SECONDS_FIELD_INDEX, format.length()); + }break; + case 'S': { + int digitCount = format.length(); + int fieldIndex = addScaleFactor(digitCount); + appendSubFormat(formatString, fieldIndex, digitCount); + } + break; + default: break; + } + } else if (delimiter != null) { + formatString.append(delimiter); + } else if (literal != null) { + literal = literal.substring(1, literal.length() - 1); + literal = literal.replaceAll("''", "'"); + formatString.append(literal); + } + from = matcher.end(); + } + return formatString.toString(); + } + + /** + * create the message format, based on %Y, %m, %d format specification. + * @param input + * @return + * @throws java.text.ParseException + */ + protected String parseTimeFormatStringPercent(String format) throws ParseException { + StringBuffer formatString= new StringBuffer(); + String[] ss= format.split("%"); + formatString.append(ss[0]); + int offset= ss[0].length(); + + for ( int i=1; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.datum.format; + +/** + * + * @author Edward West + */ +public class TimeDatumFormatterFactory extends DatumFormatterFactory { + + private static TimeDatumFormatterFactory factory; + + /** Creates a new instance of TimeDatumFormatterFactory */ + protected TimeDatumFormatterFactory() {} + + public DatumFormatter defaultFormatter() { + return TimeDatumFormatter.DEFAULT; + } + + public DatumFormatter newFormatter(String format) throws java.text.ParseException { + return new TimeDatumFormatter(format); + } + + /** Get an instance of this factory. */ + public static TimeDatumFormatterFactory getInstance() { + //This isn't thread safe, but who cares. Instances are small and + //functionally identical. + if (factory == null) { + factory = new TimeDatumFormatterFactory(); + } + return factory; + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/format/package.html b/dasCore/src/main/java/org/das2/datum/format/package.html new file mode 100644 index 000000000..0197255f2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/format/package.html @@ -0,0 +1,19 @@ + +

    + Classes for formatting a Datum to a String. The DatumFormatter +base class identifies four methods: format(Datum), format( Datum, Units ), +grannyFormat( Datum ), and grannyFormat( Datum, Units ). format(Datum) +should return a string accurately representing the datum out-of-context, +generally meaning the datum's units are displayed along with the double +value. format(Datum,Units) means that the string will be used in the context +of the given units. For example, say you want to output an ascii table +with column headers. Each column header indicates a title and a unit, for +example, "delay(sec)." Then to format a datum for displaying in this column +format( datum, Units.seconds ) would return an appropriate number. The +grannyFormat methods work the same way, except the formatter may return +a "granny string" (see GrannyTextRenderer) like "cm!e-3!n" +

    format(Datum) is the +only abstract class, and by default all other methods simply return the result +of the format(Datum) method.

    + + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/datum/package.html b/dasCore/src/main/java/org/das2/datum/package.html new file mode 100644 index 000000000..295af20da --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/package.html @@ -0,0 +1,18 @@ + +

    Provides classes representing physical quanties. The Units class +defines an enumeration of physical units such as Units.hertz and Units.microseconds. It +also contains a convention for identifying locations in time with units like +Units.t1970, the number of microseconds elapsed since midnight, January 1, 1970. +The Units class also contains a registry of Units and the conversions between them. +

    +

    A Datum is a number in the context of a Unit, for example "15 microseconds.". + A Datum object has methods for formatting itself as a String, representing + itsself as a double in the context of another Unit, and mathematical +operators that allow simple calculations to be made at the physical quantities. +Also a Datum's precision can be limited to improve formatting. +

    +

    A DatumRange is a pair of Datums that identify a range in the physical space +of a Unit. An example of a formatted DatumRange is "January, 2005" or "0 to 50Hz."

    +

    Also there are utility classes for parsing and formatting times.

    + +

    \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/datum/swing/DatumJFormatterFactory.java b/dasCore/src/main/java/org/das2/datum/swing/DatumJFormatterFactory.java new file mode 100644 index 000000000..35fdd878a --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/swing/DatumJFormatterFactory.java @@ -0,0 +1,69 @@ +/* + * DatumJFormatter.java + * + * Created on June 12, 2007, 9:11 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.datum.swing; + +import java.text.ParseException; +import javax.swing.JFormattedTextField; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.DefaultDatumFormatterFactory; + +/** + * + * @author eew + */ +public class DatumJFormatterFactory extends JFormattedTextField.AbstractFormatterFactory { + + DatumFormatter format; + SwingFormatter wrapper; + Units units; + Units implicitUnits = Units.dimensionless; + + public DatumJFormatterFactory() { + this(DefaultDatumFormatterFactory.getInstance().defaultFormatter()); + } + + /** Creates a new instance of DatumJFormatter */ + public DatumJFormatterFactory(DatumFormatter format) { + this.format = format; + this.wrapper = new SwingFormatter(); + } + + public void explicitUnits(Units u) { + units = u; + } + + public JFormattedTextField.AbstractFormatter getFormatter(JFormattedTextField tf) { + return wrapper; + } + + private class SwingFormatter extends JFormattedTextField.AbstractFormatter { + public Object stringToValue(String text) throws ParseException { + Units u = units != null ? units : implicitUnits; + return u.parse(text); + } + + public String valueToString(Object value) throws ParseException { + Datum datum = (Datum)value; + if (datum == null) { + throw new ParseException("Null values not allowed.", -1); + } + implicitUnits = datum.getUnits(); + if (units != null) { + return format.format(datum, units); + } + else { + return format.format(datum); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/datum/swing/SwingDatumFormatter.java b/dasCore/src/main/java/org/das2/datum/swing/SwingDatumFormatter.java new file mode 100644 index 000000000..4f1d4c217 --- /dev/null +++ b/dasCore/src/main/java/org/das2/datum/swing/SwingDatumFormatter.java @@ -0,0 +1,61 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.datum.swing; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.DefaultDatumFormatterFactory; +import java.text.ParseException; +import javax.swing.JFormattedTextField.AbstractFormatter; +import javax.swing.JFormattedTextField.AbstractFormatterFactory; +import javax.swing.text.DefaultFormatterFactory; + +/** + * + * @author eew + */ +public class SwingDatumFormatter extends AbstractFormatter { + + private Units units; + + private DatumFormatter formatter; + + public SwingDatumFormatter() { + formatter = DefaultDatumFormatterFactory.getInstance().defaultFormatter(); + } + + @Override + public Object stringToValue(String text) throws ParseException { + if (units == null) { + units = Units.dimensionless; + } + return units.parse(text); + } + + @Override + public String valueToString(Object value) throws ParseException { + Datum d = (Datum)value; + if (d == null) { + return ""; + } + units = d.getUnits(); + return formatter.format(d, units); + } + + public Units getUnits() { + return units; + } + + public void setUnits(Units units) { + this.units = units; + } + + public static final AbstractFormatterFactory newFactory() { + return new DefaultFormatterFactory(new SwingDatumFormatter()); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/AngleSelectionDragRenderer.java b/dasCore/src/main/java/org/das2/event/AngleSelectionDragRenderer.java new file mode 100644 index 000000000..ff1edb801 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/AngleSelectionDragRenderer.java @@ -0,0 +1,41 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.event; + +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; + +/** + * + * @author jbf + */ +public class AngleSelectionDragRenderer implements DragRenderer{ + + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { + g.drawLine( p1.x, p1.y, p2.x, p2.y ); + Rectangle r= new Rectangle(p1); + r.add(p2); + return new Rectangle[] { r }; + } + + public void clear(Graphics g) { + + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return new MouseBoxEvent( source, p1, p2, isModified ); + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return true; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/AngleSlicerMouseModule.java b/dasCore/src/main/java/org/das2/event/AngleSlicerMouseModule.java new file mode 100644 index 000000000..72fb3827d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/AngleSlicerMouseModule.java @@ -0,0 +1,83 @@ +/* File: HorizontalSlicerMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.DataSetConsumer; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.graph.Renderer; +/** + * + * @author jbf + */ +public class AngleSlicerMouseModule extends MouseModule { + + private DasAxis xaxis; + private DasAxis yaxis; + + private TableDataSetConsumer dataSetConsumer; + + private DataPointSelectionEvent de; + + private javax.swing.event.EventListenerList listenerList = null; + + public AngleSlicerMouseModule( DasPlot parent, TableDataSetConsumer dataSetConsumer, DasAxis xaxis, DasAxis yaxis) { + this( parent, (DataSetConsumer)dataSetConsumer, xaxis, yaxis ); + } + + protected AngleSlicerMouseModule(DasPlot parent, DataSetConsumer dataSetConsumer, DasAxis xaxis, DasAxis yaxis) { + super( parent, new AngleSelectionDragRenderer(), "Angle Slice" ); + + if (!(dataSetConsumer instanceof TableDataSetConsumer)) { + throw new IllegalArgumentException("dataSetConsumer must be an XTaggedYScanDataSetConsumer"); + } + this.dataSetConsumer= ( TableDataSetConsumer)dataSetConsumer; + this.xaxis= xaxis; + this.yaxis= yaxis; + this.de= new DataPointSelectionEvent(this,null,null); + + } + + public static AngleSlicerMouseModule create(DasPlot parent) { + DasAxis xaxis= parent.getXAxis(); + DasAxis yaxis= parent.getYAxis(); + return new AngleSlicerMouseModule(parent,parent,xaxis,yaxis); + } + + public static AngleSlicerMouseModule create(Renderer renderer) + { + DasPlot parent= renderer.getParent(); + DasAxis xaxis= parent.getXAxis(); + DasAxis yaxis= parent.getYAxis(); + return new AngleSlicerMouseModule(parent,renderer,xaxis,yaxis); + } + + @Override + public void mouseRangeSelected(MouseDragEvent e0) { + MouseBoxEvent e= (MouseBoxEvent)e0; + + } + +} diff --git a/dasCore/src/main/java/org/das2/event/AnnotatorMouseModule.java b/dasCore/src/main/java/org/das2/event/AnnotatorMouseModule.java new file mode 100644 index 000000000..f9a04be3a --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/AnnotatorMouseModule.java @@ -0,0 +1,54 @@ +/* + * AnnotatorMouseModule.java + * + * Created on March 25, 2005, 1:07 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasColumn; +import org.das2.graph.DasAnnotation; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasRow; + +/** + * + * @author Jeremy + */ +public class AnnotatorMouseModule extends MouseModule { + + DasCanvas canvas; + + /** Creates a new instance of AnnotatorMouseModule */ + public AnnotatorMouseModule( DasCanvasComponent parent ) { + super( parent, new BoxRenderer(parent), "Annotate" ); + this.canvas= (DasCanvas)parent.getParent(); + } + + public DasCanvas getCanvas() { + return canvas; + } + + public void mouseRangeSelected(MouseDragEvent e) { + super.mouseRangeSelected(e); + + System.out.println(e); + MouseBoxEvent me= (MouseBoxEvent) e; + + double n; + n= canvas.getHeight(); + DasRow row= new DasRow( canvas, me.getYMinimum()/n, me.getYMaximum()/n ); + + n= canvas.getWidth(); + DasColumn col= new DasColumn( canvas, me.getXMinimum()/n, me.getXMaximum()/n ); + + DasAnnotation anno= new DasAnnotation("right click"); + + canvas.add( anno, row, col ); + canvas.revalidate(); + } + + + +} diff --git a/dasCore/src/main/java/org/das2/event/ArrowDragRenderer.java b/dasCore/src/main/java/org/das2/event/ArrowDragRenderer.java new file mode 100644 index 000000000..22ad28252 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/ArrowDragRenderer.java @@ -0,0 +1,51 @@ +/* + * ArrowDragRenderer.java + * + * Created on February 13, 2007, 3:50 PM + * + * + */ + +package org.das2.event; + +import org.das2.graph.Arrow; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; + +/** + * + * @author Jeremy + */ +public class ArrowDragRenderer implements DragRenderer { + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { + g.setClip( null ); + Arrow.paintArrow( (Graphics2D)g, p2, p1, 12 , Arrow.HeadStyle.DRAFTING ); + Rectangle result= new Rectangle(p1); + result.add(p2); + result.x-= 6; + result.y-= 6; + result.width+= 12; + result.height+= 12; + + return new Rectangle[] { result }; + } + + public void clear(Graphics g) { + + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return null; + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return true; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/BoxGesturesRenderer.java b/dasCore/src/main/java/org/das2/event/BoxGesturesRenderer.java new file mode 100644 index 000000000..4bef8abd8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxGesturesRenderer.java @@ -0,0 +1,82 @@ +/* File: BoxRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author eew + */ +public class BoxGesturesRenderer extends BoxRenderer { + + GesturesRenderer gr; + + public BoxGesturesRenderer(DasCanvasComponent parent) { + super(parent); + gr= new GesturesRenderer(parent); + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + Graphics2D g= (Graphics2D) g1; + + if ( gr.isGesture( p1, p2 ) ) { + Rectangle[] rr= gr.renderDrag( g, p1, p2 ); + dirtyBounds= rr[0]; + } else { + Rectangle r = new Rectangle(p1); + r.add(p2); + + Color color0= g.getColor(); + g.setColor(new Color(255,255,255,100)); + g.setStroke(new BasicStroke( 3.0f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND )); + + g.drawRect(r.x, r.y, r.width, r.height); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + g.drawRect(r.x, r.y, r.width, r.height); + + dirtyBounds.setLocation(r.x-2,r.y-3); + dirtyBounds.add(r.x+r.width+2,r.y+r.height+3); + } + return new Rectangle[] { dirtyBounds }; + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + if ( gr.isGesture(p1,p2) ) { + return gr.getMouseDragEvent( source, p1, p2, isModified ); + } else { + return super.getMouseDragEvent( source, p1, p2, isModified ); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java new file mode 100755 index 000000000..6b2cff539 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java @@ -0,0 +1,113 @@ +/* File: BoxRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.DataSetConsumer; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import javax.swing.event.EventListenerList; + +/** + * //@deprecated use BoxSelectorMouseModule + * @author jbf + */ +public class BoxRangeSelectorMouseModule extends MouseModule { + + protected DasAxis xAxis; + protected DasAxis yAxis; + private DataSetConsumer consumer; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = new javax.swing.event.EventListenerList(); + + /** + * @param consumer is the source context of the data set selection + */ + public BoxRangeSelectorMouseModule(DasCanvasComponent parent, DataSetConsumer consumer, DasAxis xAxis, DasAxis yAxis) { + super( parent, new BoxGesturesRenderer(parent), "Box Selection" ); + if (!xAxis.isHorizontal()) { + throw new IllegalArgumentException("X Axis orientation is not horizontal"); + } + if (yAxis.isHorizontal()) { + throw new IllegalArgumentException("Y Axis orientation is not vertical"); + } + this.xAxis= xAxis; + this.yAxis= yAxis; + this.consumer = consumer; + } + + public static BoxRangeSelectorMouseModule create(DasPlot parent) { + BoxRangeSelectorMouseModule result= + new BoxRangeSelectorMouseModule(parent, null, parent.getXAxis(),parent.getYAxis()); + return result; + } + + public void mouseRangeSelected(MouseDragEvent e0) { + if (e0 instanceof MouseBoxEvent) { + MouseBoxEvent e= (MouseBoxEvent)e0; + + Datum xMin = xAxis.invTransform(e.getXMinimum()); + Datum xMax = xAxis.invTransform(e.getXMaximum()); + Datum yMin = yAxis.invTransform(e.getYMaximum()); + Datum yMax = yAxis.invTransform(e.getYMinimum()); + BoxSelectionEvent evt = new BoxSelectionEvent(this, new DatumRange( xMin, xMax ), new DatumRange( yMin, yMax) ); + if (consumer != null) { + evt.setDataSet(consumer.getConsumedDataSet()); + } + fireBoxSelected(evt); + } + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public void addBoxSelectionListener(BoxSelectionListener listener) { + listenerList.add(BoxSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public void removeBoxSelectionListener(BoxSelectionListener listener) { + listenerList.remove(BoxSelectionListener.class, listener); + } + + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + protected void fireBoxSelected(BoxSelectionEvent event) { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i] == BoxSelectionListener.class) { + ((BoxSelectionListener)listeners[i+1]).BoxSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/BoxRenderer.java b/dasCore/src/main/java/org/das2/event/BoxRenderer.java new file mode 100644 index 000000000..cc9cc3f9d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxRenderer.java @@ -0,0 +1,104 @@ +/* File: BoxRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * Draws a box + * @author eew + */ +public class BoxRenderer implements DragRenderer { + + Rectangle dirtyBounds; + DasCanvasComponent parent; + + boolean updating; + + /** use this value for P1 instead of the one sent by the client. This + * allows the box to used to render tweak of corner. + */ + Point overrideP1; + + public BoxRenderer(DasCanvasComponent parent, boolean updating ) { + this.parent= parent; + dirtyBounds= new Rectangle(); + this.updating= updating; + } + + public BoxRenderer( DasCanvasComponent parent ) { + this( parent, false ); + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public Rectangle[] renderDrag( Graphics g1, Point p1, Point p2 ) { + Graphics2D g= (Graphics2D) g1; + + if ( overrideP1!=null ) p1= overrideP1; + + Rectangle r = new Rectangle(p1); + r.add(p2); + + Color color0= g.getColor(); + g.setColor(new Color(255,255,255,100)); + g.setStroke(new BasicStroke( 3.0f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND )); + + g.drawRect(r.x, r.y, r.width, r.height); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + g.drawRect(r.x, r.y, r.width, r.height); + + dirtyBounds.setLocation(r.x-2,r.y-3); + dirtyBounds.add(r.x+r.width+2,r.y+r.height+3); + return new Rectangle[] { dirtyBounds }; + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + if ( overrideP1!=null ) p1= overrideP1; + return new MouseBoxEvent(source,p1,p2,isModified); + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return updating; + } + + /** + * set this to override + * @param p1 + */ + public void setDragStart( Point p1 ) { + this.overrideP1= p1; + } +} diff --git a/dasCore/src/main/java/org/das2/event/BoxSelectionEvent.java b/dasCore/src/main/java/org/das2/event/BoxSelectionEvent.java new file mode 100755 index 000000000..f2ed22a2e --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxSelectionEvent.java @@ -0,0 +1,155 @@ +/* File: BoxSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.DataSet; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import java.util.HashMap; + +/** + * This is the range anolog to the DataPointSelectionEvent. The DPSE is a point, + * and this is a box. + * + * Note that it's acceptible to have null xrange and yrange, so that the same + * code can support a variety of applications. It's left to the programmer to + * see that these are used consistently. + * + * @author jbf + */ +public class BoxSelectionEvent extends DasEvent { + + private DatumRange xrange; + private DatumRange yrange; + private Datum finishx, finishy; + private Datum startx, starty; + private DataSet ds; + private HashMap planes; + + /** + * @deprecated use BoxSelectionEvent( Object, DatumRange, DatumRange ); + */ + public BoxSelectionEvent(Object source, org.das2.datum.Datum xMin, org.das2.datum.Datum xMax, org.das2.datum.Datum yMin, org.das2.datum.Datum yMax) { + this( source, xMin.le(xMax) ? new DatumRange( xMin, xMax ) : new DatumRange( xMax, xMin ), + yMin.le(yMax) ? new DatumRange( yMin, yMax ) : new DatumRange( yMax, yMin ) ); + } + + public BoxSelectionEvent( Object source, DatumRange xrange, DatumRange yrange ) { + this( source, xrange, yrange, null ); + } + + public BoxSelectionEvent( Object source, DatumRange xrange, DatumRange yrange, HashMap planes ) { + super( source ); + this.xrange= xrange; + this.yrange= yrange; + this.planes= planes; + } + + public void setFinish( Datum x, Datum y ) { + this.finishx = x; + this.finishy = y; + } + + public Datum getFinishX() { + return this.finishx; + } + + public Datum getFinishY() { + return this.finishy; + } + + public void setStart( Datum x, Datum y ) { + this.startx = x; + this.starty = y; + } + + public Datum getStartX() { + return this.startx; + } + + public Datum getStartY() { + return this.starty; + } + + /** + * @deprecated use getXRange().min(); + */ + public org.das2.datum.Datum getXMinimum() { + if ( xrange!=null ) return xrange.min(); else return null; + } + + /** + * @deprecated use getXRange().max(); + */ + public org.das2.datum.Datum getXMaximum() { + if ( xrange!=null ) return xrange.max(); else return null; + } + + /** + * @deprecated use getYRange().min(); + */ + public org.das2.datum.Datum getYMinimum() { + if ( yrange!=null ) return yrange.min(); else return null; + } + + /** + * @deprecated use getYRange().max(); + */ + public org.das2.datum.Datum getYMaximum() { + if ( yrange!=null ) return yrange.max(); else return null; + } + + public DatumRange getXRange() { + return xrange; + } + + public DatumRange getYRange() { + return yrange; + } + + public Object getPlane( String plane ) { + return planes==null ? null : planes.get(plane); + } + + public String[] getPlaneIds() { + if ( planes==null ) { + return new String[0]; + } else { + return (String[])planes.keySet().toArray( new String[ planes.keySet().size() ] ); + } + } + + public void setDataSet(DataSet ds) { + this.ds = ds; + } + + public DataSet getDataSet() { + return ds; + } + + public String toString() { + return "[BoxSelectionEvent x: "+xrange+", y: "+yrange+"]"; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/BoxSelectionListener.java b/dasCore/src/main/java/org/das2/event/BoxSelectionListener.java new file mode 100644 index 000000000..309a89772 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxSelectionListener.java @@ -0,0 +1,32 @@ +/* File: BoxSelectionListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public abstract interface BoxSelectionListener extends java.util.EventListener { + public void BoxSelected(BoxSelectionEvent e); +} diff --git a/dasCore/src/main/java/org/das2/event/BoxSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/BoxSelectorMouseModule.java new file mode 100644 index 000000000..f18e545f3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxSelectorMouseModule.java @@ -0,0 +1,300 @@ +/* + * DataPointSelectorMouseModule.java + * + * Created on November 3, 2005, 2:53 PM + * + * + */ +package org.das2.event; + +import org.das2.dataset.DataSetConsumer; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import java.awt.Cursor; +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.HashMap; + +/** + * General purpose mouse module for getting data point selections. The client + * provides the DragRenderer, generally a vertical line, horizontal line or a + * crosshair. + * + * Three properties control when BoxSelectionEvents are to be fired: + * dragEvents as the mouse is dragged, + * keyEvents when a key is pressed. (The key is the "keyChar" plane of the event) + * releaseEvents when the mouse is released. (false by default) + * + * @see BoxRenderer + * @author Jeremy + */ +public class BoxSelectorMouseModule extends MouseModule { + + DasAxis xaxis, yaxis; + DataSetConsumer dataSetConsumer; + javax.swing.event.EventListenerList listenerList = null; + MouseDragEvent lastMouseEvent; + /** when true, box selections are remembered, and tweaks to corners are allowed. */ + boolean tweakable = false; + BoxSelectionEvent lastSelectionEvent = null; + + public static BoxSelectorMouseModule create( DasPlot parent, String label ) { + return new BoxSelectorMouseModule( parent, parent.getXAxis(), parent.getYAxis(), null, new BoxRenderer(parent), label ); + } + + public BoxSelectorMouseModule(DasCanvasComponent parent, DasAxis xAxis, DasAxis yAxis, + DataSetConsumer consumer, + DragRenderer dragRenderer, String label) { + super(parent, dragRenderer, label); + this.xaxis = xAxis; + this.yaxis = yAxis; + this.dataSetConsumer = consumer; + } + + /** + * allow the last selection to be tweaked. It's the client's responsibility + * to draw the current selection. + * @param b + */ + public void setTweakable(boolean b) { + this.tweakable = b; + } + + private Datum[] checkTweak(Point p) { + double nx = DatumRangeUtil.normalize(lastSelectionEvent.getXRange(), xaxis.invTransform(p.getX())); + double ny = DatumRangeUtil.normalize(lastSelectionEvent.getYRange(), yaxis.invTransform(p.getY())); + + System.err.println("" + nx + " " + ny); + Datum otherx = null; + Datum othery = null; + + if (nx >= 0.0 && nx < 0.1) { + otherx = lastSelectionEvent.getXRange().max(); + } else if (nx > 0.9 && nx < 1.0) { + otherx = lastSelectionEvent.getXRange().min(); + } + + if (ny >= 0.0 && ny < 0.1) { + othery = lastSelectionEvent.getYRange().max(); + } else if (ny > 0.9 && ny < 1.0) { + othery = lastSelectionEvent.getYRange().min(); + } + + Datum[] otherCorner = new Datum[2]; + + otherCorner[0] = otherx; + otherCorner[1] = othery; + + return otherCorner; + + } + + @Override + public void mousePressed(MouseEvent e) { + + if (tweakable && lastSelectionEvent != null) { + Point p = new Point(e.getPoint()); + p.translate(e.getComponent().getX(), e.getComponent().getY()); + + Datum[] otherCorner= checkTweak( p ); + if (otherCorner[0] != null && otherCorner[1] != null) { // we're tweaking + double p1x = xaxis.transform(otherCorner[0]); + double p2x = yaxis.transform(otherCorner[1]); + Point p1 = new Point((int) p1x, (int) p2x); + ((BoxRenderer) dragRenderer).setDragStart(p1); + } else { + ((BoxRenderer) dragRenderer).setDragStart(null); + } + } + } + + @Override + public void mouseMoved(MouseEvent e) { + Cursor c= null; + if (tweakable && lastSelectionEvent != null) { + Point p = new Point(e.getPoint()); + p.translate(e.getComponent().getX(), e.getComponent().getY()); + + Datum[] otherCorner= checkTweak( p ); + if (otherCorner[0] != null && otherCorner[1] != null) { // we're tweaking + c= new Cursor( Cursor.MOVE_CURSOR ); + } + } + parent.getCanvas().setCursor(c); + } + + private BoxSelectionEvent getBoxSelectionEvent(MouseDragEvent mde) { + + MouseBoxEvent e = (MouseBoxEvent) mde; + + DatumRange xrange = null; + DatumRange yrange = null; + + Datum x=null, y=null; + Datum sx= null, sy=null; + + if (xaxis != null) { + Datum min = xaxis.invTransform(e.getXMinimum()); + Datum max = xaxis.invTransform(e.getXMaximum()); + if (min.gt(max)) { + Datum t = min; + min = max; + max = t; + } + xrange = new DatumRange(min, max); + x= xaxis.invTransform(e.getPoint().x); + sx= xaxis.invTransform(e.getPressPoint().x); + } + + if (yaxis != null) { + Datum min = yaxis.invTransform(e.getYMinimum()); + Datum max = yaxis.invTransform(e.getYMaximum()); + if (min.gt(max)) { + Datum t = min; + min = max; + max = t; + } + yrange = new DatumRange(min, max); + y= yaxis.invTransform( e.getPoint().y ); + sy= yaxis.invTransform( e.getPressPoint().y ); + } + + BoxSelectionEvent evt = new BoxSelectionEvent(this, xrange, yrange); + evt.setStart( sx,sy ); + evt.setFinish( x,y ); + + + this.lastSelectionEvent = evt; + return evt; + } + + public void mouseRangeSelected(MouseDragEvent e) { + lastMouseEvent = e; + if (keyEvents) { + parent.requestFocus(); + } + if (dragEvents) { + fireBoxSelectionListenerBoxSelected(getBoxSelectionEvent(e)); + } + } + + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + + if (lastMouseEvent != null) { + BoxSelectionEvent dpse = getBoxSelectionEvent(lastMouseEvent); + HashMap planes = new HashMap(); + planes.put("keyChar", String.valueOf(e.getKeyChar())); + dpse = new BoxSelectionEvent(this, dpse.getXRange(), dpse.getYRange(), planes); + fireBoxSelectionListenerBoxSelected(dpse); + } + } + + /** Registers BoxSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addBoxSelectionListener(org.das2.event.BoxSelectionListener listener) { + if (listenerList == null) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.BoxSelectionListener.class, listener); + } + + /** Removes BoxSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeBoxSelectionListener(org.das2.event.BoxSelectionListener listener) { + listenerList.remove(org.das2.event.BoxSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) { + if (listenerList == null) { + return; + } + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == org.das2.event.BoxSelectionListener.class) { + ((org.das2.event.BoxSelectionListener) listeners[i + 1]).BoxSelected(event); + } + } + } + /** + * Holds value of property dragEvents. + */ + private boolean dragEvents = false; + + /** + * Getter for property dragEvents. + * @return Value of property dragEvents. + */ + public boolean isDragEvents() { + return this.dragEvents; + } + + /** + * Setter for property dragEvents. + * @param dragEvents New value of property dragEvents. + */ + public void setDragEvents(boolean dragEvents) { + this.dragEvents = dragEvents; + } + /** + * Holds value of property keyEvents. + */ + private boolean keyEvents = false; + + /** + * Getter for property keyEvents. + * @return Value of property keyEvents. + */ + public boolean isKeyEvents() { + + return this.keyEvents; + } + + /** + * Setter for property keyEvents. + * @param keyEvents New value of property keyEvents. + */ + public void setKeyEvents(boolean keyEvents) { + this.keyEvents = keyEvents; + } + + public void mouseReleased(java.awt.event.MouseEvent e) { + super.mouseReleased(e); + if (releaseEvents) { + fireBoxSelectionListenerBoxSelected(getBoxSelectionEvent(lastMouseEvent)); + } + } + /** + * Holds value of property releaseEvents. + */ + private boolean releaseEvents = true; + + /** + * Getter for property releaseEvents. + * @return Value of property releaseEvents. + */ + public boolean isReleaseEvents() { + + return this.releaseEvents; + } + + /** + * Setter for property releaseEvents. + * @param releaseEvents New value of property releaseEvents. + */ + public void setReleaseEvents(boolean releaseEvents) { + + this.releaseEvents = releaseEvents; + } +} diff --git a/dasCore/src/main/java/org/das2/event/BoxZoomDialog.form b/dasCore/src/main/java/org/das2/event/BoxZoomDialog.form new file mode 100644 index 000000000..7f2e3f840 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxZoomDialog.form @@ -0,0 +1,165 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/event/BoxZoomDialog.java b/dasCore/src/main/java/org/das2/event/BoxZoomDialog.java new file mode 100644 index 000000000..635067620 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxZoomDialog.java @@ -0,0 +1,229 @@ +/* + * BoxZoomDialog.java + * + * Created on May 16, 2007, 7:36 AM + */ + +package org.das2.event; + +import javax.swing.JOptionPane; + +/** + * + * @author jbf + */ +public class BoxZoomDialog extends javax.swing.JPanel { + + BoxZoomMouseModule module; + + /** Creates new form BoxZoomDialog */ + public BoxZoomDialog( BoxZoomMouseModule module ) { + initComponents(); + this.module= module; + zoomYButton.setAction( module.getZoomYAction() ); + zoomXButton.setAction( module.getZoomXAction() ); + zoomBoxButton.setAction( module.getZoomBoxAction() ); + } + + String getXRange( ) { + return xRangeTextField.getText(); + } + + String getYRange( ) { + return yRangeTextField.getText(); + } + + public boolean isAutoBoxZoom() { + return autoCheckBox.isSelected(); + } + + public boolean isConstrainProportions() { + return constrainProportionsCheckBox.isSelected(); + } + + public boolean isDisablePopup() { + return disablePopupCheckBox.isSelected(); + } + + public void setDisablePopup( boolean v ) { + disablePopupCheckBox.setSelected(v); + } + + public void setConstrainProportions( boolean v ) { + constrainProportionsCheckBox.setSelected(v); + } + + public void setAutoBoxZoom( boolean v ) { + autoCheckBox.setSelected(v); + } + + public void setXRange( String s ) { + this.xRangeTextField.setText(s); + } + + public void setYRange( String s ) { + this.yRangeTextField.setText(s); + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + jLabel1 = new javax.swing.JLabel(); + xRangeTextField = new javax.swing.JTextField(); + jLabel2 = new javax.swing.JLabel(); + yRangeTextField = new javax.swing.JTextField(); + zoomYButton = new javax.swing.JButton(); + zoomBoxButton = new javax.swing.JButton(); + zoomXButton = new javax.swing.JButton(); + autoCheckBox = new javax.swing.JCheckBox(); + disablePopupCheckBox = new javax.swing.JCheckBox(); + constrainProportionsCheckBox = new javax.swing.JCheckBox(); + + jLabel1.setText("X:"); + + jLabel2.setText("Y:"); + + zoomYButton.setText("z
    o
    o
    m
    Y"); + zoomYButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); + + zoomBoxButton.setText("Zoom
    Box"); + + zoomXButton.setText("zoom X"); + + autoCheckBox.setText("auto box zoom"); + autoCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); + autoCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0)); + autoCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + autoCheckBoxActionPerformed(evt); + } + }); + + disablePopupCheckBox.setText("disable popup"); + disablePopupCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); + disablePopupCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0)); + disablePopupCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + disablePopupCheckBoxActionPerformed(evt); + } + }); + + constrainProportionsCheckBox.setText("constrain proportions"); + constrainProportionsCheckBox.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 0, 0, 0)); + constrainProportionsCheckBox.setMargin(new java.awt.Insets(0, 0, 0, 0)); + constrainProportionsCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + constrainProportionsCheckBoxActionPerformed(evt); + } + }); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(jLabel1) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(xRangeTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 155, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(layout.createSequentialGroup() + .add(jLabel2) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(yRangeTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(layout.createSequentialGroup() + .add(10, 10, 10) + .add(zoomYButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 21, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(zoomXButton) + .add(zoomBoxButton, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 109, Short.MAX_VALUE))) + .add(constrainProportionsCheckBox) + .add(autoCheckBox) + .add(layout.createSequentialGroup() + .add(17, 17, 17) + .add(disablePopupCheckBox))))) + .addContainerGap()) + ); + + layout.linkSize(new java.awt.Component[] {xRangeTextField, yRangeTextField}, org.jdesktop.layout.GroupLayout.HORIZONTAL); + + layout.linkSize(new java.awt.Component[] {zoomBoxButton, zoomXButton}, org.jdesktop.layout.GroupLayout.HORIZONTAL); + + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(jLabel1) + .add(xRangeTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(jLabel2) + .add(yRangeTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(zoomBoxButton) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(zoomXButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 31, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(zoomYButton, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 80, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(constrainProportionsCheckBox) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(autoCheckBox) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(disablePopupCheckBox) + .addContainerGap()) + ); + }//
    //GEN-END:initComponents + + private void disablePopupCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_disablePopupCheckBoxActionPerformed + if ( disablePopupCheckBox.isSelected() ) { + int result= JOptionPane.showConfirmDialog(this, + "Hitting OK will disable this popup and will perform the zoom. " + + "Popup may be re-enabled via the plot's property editor.", + "hiding box zoom popup", + JOptionPane.OK_CANCEL_OPTION ); + if ( result==JOptionPane.OK_OPTION ) { + module.setAutoUpdate(true); + module.zoomBox(); + this.module.dialog.setVisible(false); + } else { + disablePopupCheckBox.setSelected(false); + } + } + module.guiChanged(); + }//GEN-LAST:event_disablePopupCheckBoxActionPerformed + + private void constrainProportionsCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_constrainProportionsCheckBoxActionPerformed + module.guiChanged(); + }//GEN-LAST:event_constrainProportionsCheckBoxActionPerformed + + private void autoCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoCheckBoxActionPerformed + disablePopupCheckBox.setEnabled( autoCheckBox.isSelected() ); + if ( !autoCheckBox.isSelected() ) disablePopupCheckBox.setSelected(false); + module.guiChanged(); + }//GEN-LAST:event_autoCheckBoxActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox autoCheckBox; + private javax.swing.JCheckBox constrainProportionsCheckBox; + private javax.swing.JCheckBox disablePopupCheckBox; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JTextField xRangeTextField; + private javax.swing.JTextField yRangeTextField; + private javax.swing.JButton zoomBoxButton; + private javax.swing.JButton zoomXButton; + private javax.swing.JButton zoomYButton; + // End of variables declaration//GEN-END:variables + +} diff --git a/dasCore/src/main/java/org/das2/event/BoxZoomMouseModule.java b/dasCore/src/main/java/org/das2/event/BoxZoomMouseModule.java new file mode 100644 index 000000000..1666b184d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/BoxZoomMouseModule.java @@ -0,0 +1,209 @@ +/* + * BoxZoomMouseModule.java + * + * Created on May 20, 2005, 12:21 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.GraphUtil; +import org.das2.graph.DasAxis; +import org.das2.dataset.DataSetConsumer; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.DatumRangeUtil; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** + * + * @author Jeremy + */ +public class BoxZoomMouseModule extends BoxRangeSelectorMouseModule { + + DatumRange xrange, yrange; + JDialog dialog; + JLabel xrangeLabel, yrangeLabel; + JCheckBox autoUpdateCB, constrainProportionsCB; + BoxZoomDialog bzdialog; + + boolean autoUpdate= true; + boolean constrainProportions= false; + + /** Creates a new instance of BoxZoomMouseModule */ + public BoxZoomMouseModule( DasCanvasComponent parent, DataSetConsumer consumer, DasAxis xAxis, DasAxis yAxis ) { + super( parent, consumer, xAxis, yAxis ); + setLabel("Box Zoom"); + } + + JDialog getDialog() { + if ( dialog==null ) { + dialog= new JDialog( (Frame)null ); + dialog.setLocationRelativeTo( parent ); + + Container content= dialog.getContentPane(); + bzdialog= new BoxZoomDialog( this ); + content.add( bzdialog ); + + dialog.pack(); + } + + bzdialog.setAutoBoxZoom( autoUpdate ); + bzdialog.setDisablePopup( popupDisabled ); + bzdialog.setConstrainProportions( constrainProportions ); + + if ( !popupDisabled || !autoUpdate ) dialog.setVisible(true); + + return dialog; + } + + protected void guiChanged() { + autoUpdate= bzdialog.isAutoBoxZoom( ); + popupDisabled= bzdialog.isDisablePopup( ); + constrainProportions = bzdialog.isConstrainProportions( ); + } + + Action getZoomYAction() { + return new AbstractAction("
    z
    o
    o
    m
    Y
    ") { + public void actionPerformed( ActionEvent e ) { + if ( yrange!=null ) yAxis.setDatumRange(yrange); + } + }; + } + + Action getZoomXAction() { + return new AbstractAction("zoom X") { + public void actionPerformed( ActionEvent e ) { + if ( xrange!=null ) xAxis.setDatumRange(xrange); + } + }; + } + + Action getZoomBoxAction() { + return new AbstractAction("
    Zoom
    Box
    ") { + public void actionPerformed( ActionEvent e ) { + zoomBox(); + } + }; + } + + protected void zoomBox() { + if ( yrange!=null ) yAxis.setDatumRange(yrange); + if ( xrange!=null ) xAxis.setDatumRange(xrange); + } + + @Override + public void mouseRangeSelected(MouseDragEvent e0) { + if ( e0 instanceof MouseBoxEvent ) { + MouseBoxEvent e= (MouseBoxEvent)e0; + + xrange= GraphUtil.invTransformRange( xAxis, e.getXMinimum(), e.getXMaximum() ); + yrange= GraphUtil.invTransformRange( yAxis, e.getYMinimum(), e.getYMaximum() ); + + if ( constrainProportions ) { + double aspect= yAxis.getHeight() / (double)xAxis.getWidth(); + DatumRange mx= new DatumRange( e.getXMinimum(), e.getXMaximum(), Units.dimensionless ); + DatumRange my= new DatumRange( e.getYMinimum(), e.getYMaximum(), Units.dimensionless ); + double mouseAspect= my.width().divide(mx.width()).doubleValue(Units.dimensionless); + if ( mouseAspect > aspect ) { + double f= mouseAspect / aspect; + mx= DatumRangeUtil.rescale(my, 0.5-f/2, 0.5+f/2 ); + } else { + double f= aspect / mouseAspect; + my= DatumRangeUtil.rescale(my, 0.5-f/2, 0.5+f/2 ); + } + xrange= GraphUtil.invTransformRange( xAxis, mx.min().doubleValue(Units.dimensionless), + mx.max().doubleValue(Units.dimensionless) ); + yrange= GraphUtil.invTransformRange( yAxis, my.max().doubleValue(Units.dimensionless), + my.min().doubleValue(Units.dimensionless) ); + } else { + xrange= GraphUtil.invTransformRange( xAxis, e.getXMinimum(), e.getXMaximum() ); + yrange= GraphUtil.invTransformRange( yAxis, e.getYMaximum(), e.getYMinimum() ); + } + + if ( ! autoUpdate ) { + getDialog(); + bzdialog.setXRange( xrange.toString() ); + bzdialog.setYRange(yrange.toString()); + } else { + zoomBox(); + } + + } else if ( e0.isGesture() ) { + if ( e0.getGesture()==Gesture.ZOOMOUT ) { + xAxis.setDataRangeZoomOut(); + yAxis.setDataRangeZoomOut(); + } else if ( e0.getGesture()==Gesture.BACK ) { + xAxis.setDataRangePrev(); + yAxis.setDataRangePrev(); + } else if ( e0.getGesture()==Gesture.FORWARD ) { + xAxis.setDataRangeForward(); + yAxis.setDataRangeForward(); + } + } + + } + + /** + * Getter for property autoUpdate. + * @return Value of property autoUpdate. + */ + public boolean isAutoUpdate() { + return this.autoUpdate; + } + + /** + * Setter for property autoUpdate. + * @param autoUpdate New value of property autoUpdate. + */ + public void setAutoUpdate(boolean autoUpdate) { + if ( bzdialog!=null ) bzdialog.setAutoBoxZoom( autoUpdate ); + this.autoUpdate = autoUpdate; + } + + /** + * Getter for property constrainProportions. + * @return Value of property constrainProportions. + */ + public boolean isConstrainProportions() { + return this.constrainProportions; + } + + /** + * Setter for property constrainProportions. + * @param constrainProportions New value of property constrainProportions. + */ + public void setConstrainProportions(boolean constrainProportions) { + if ( bzdialog!=null ) bzdialog.setConstrainProportions(constrainProportions); + this.constrainProportions = constrainProportions; + } + + /** + * Holds value of property popupDisabled. + */ + private boolean popupDisabled; + + /** + * Getter for property popupDisabled. + * @return Value of property popupDisabled. + */ + public boolean isPopupDisabled() { + return this.popupDisabled; + } + + /** + * Setter for property popupDisabled. + * @param popupDisabled New value of property popupDisabled. + */ + public void setPopupDisabled(boolean popupDisabled) { + if ( bzdialog!=null ) bzdialog.setDisablePopup(popupDisabled); + this.popupDisabled = popupDisabled; + } + + + + + +} diff --git a/dasCore/src/main/java/org/das2/event/ColorBarRepaletteMouseModule.java b/dasCore/src/main/java/org/das2/event/ColorBarRepaletteMouseModule.java new file mode 100644 index 000000000..15bc4b104 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/ColorBarRepaletteMouseModule.java @@ -0,0 +1,114 @@ +/* File: VerticalRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.graph.DasColorBar; +import org.das2.graph.DasRow; +import org.das2.graph.Renderer; +import java.awt.event.MouseEvent; +import javax.swing.event.EventListenerList; + +/** + * + * @author jbf + */ +public class ColorBarRepaletteMouseModule extends MouseModule { + + DasColorBar colorBar; + Renderer parent; + DatumRange range0; + boolean animated0; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + public String getLabel() { return "Repalette"; }; + + public ColorBarRepaletteMouseModule( Renderer parent, DasColorBar colorBar ) { + if (colorBar.isHorizontal()) { + throw new IllegalArgumentException("Axis orientation is not vertical"); + } + this.parent= parent; + // this.dragRenderer= (DragRenderer)HorizontalRangeRenderer.renderer; + this.dragRenderer= new HorizontalSliceSelectionRenderer(parent.getParent()); + this.colorBar= colorBar; + } + + private void setColorBar( int y ) { + DatumRange dr; + DasRow row= colorBar.getRow(); + double alpha= ( row.getDMaximum() - y ) / (1.*row.getHeight()); + dr= DatumRangeUtil.rescale(range0, 0, alpha ); + colorBar.setDatumRange(dr); + parent.update(); + } + + public void mouseReleased( MouseEvent e ) { + colorBar.setAnimated(animated0); + } + + public void mousePointSelected(MousePointSelectionEvent e) { + setColorBar( e.y ); + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + + public void mousePressed(java.awt.event.MouseEvent e) { + super.mousePressed(e); + animated0= colorBar.isAnimated(); + colorBar.setAnimated(false); + range0= colorBar.getDatumRange(); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/CommentDataPointSelectionEvent.java b/dasCore/src/main/java/org/das2/event/CommentDataPointSelectionEvent.java new file mode 100755 index 000000000..8a627e150 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/CommentDataPointSelectionEvent.java @@ -0,0 +1,36 @@ +/* + * DataTupleSelectionEvent.java + * + * Created on October 13, 2003, 11:27 AM + */ + +package org.das2.event; + +import org.das2.datum.Datum; +import org.das2.event.DataPointSelectionEvent; + +/** + * @deprecated use DataPointSelectionEvent planes hashmap "comment" key + * @author jbf + */ +public class CommentDataPointSelectionEvent extends DataPointSelectionEvent { + + String comment; + + public static CommentDataPointSelectionEvent create( DataPointSelectionEvent e, String comment ) { + CommentDataPointSelectionEvent ce = new CommentDataPointSelectionEvent( e.getSource(), e.getX(), e.getY(), comment ); + ce.setDataSet(e.getDataSet()); + return ce; + } + + /** Creates a new instance of DataTupleSelectionEvent */ + private CommentDataPointSelectionEvent( Object source, Datum x, Datum y, String comment ) { + super( source, x, y ); + this.comment= comment; + } + + public String getComment() { + return comment; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/CrossHairMouseModule.java b/dasCore/src/main/java/org/das2/event/CrossHairMouseModule.java new file mode 100755 index 000000000..52817ac3a --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/CrossHairMouseModule.java @@ -0,0 +1,146 @@ +/* File: CrossHairMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; +import org.das2.dataset.DataSet; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.graph.Renderer; + + +/** + * + * @author Owner + */ +public class CrossHairMouseModule extends MouseModule { + + public static abstract class InfoItem { + public abstract void label(org.das2.datum.Datum x, org.das2.datum.Datum y, StringBuilder out); + public org.das2.datum.Datum[] snap(org.das2.datum.Datum x, org.das2.datum.Datum y) { + return null; + } + } + + private DasAxis xaxis; + private DasAxis yaxis; + private DasPlot plot; + + protected DataPointSelectionEvent de; + + org.das2.dataset.DataSetConsumer dataSetConsumer; + + /** Utility field used by event firing mechanism. */ + private javax.swing.event.EventListenerList listenerList = null; + + public CrossHairMouseModule(DasPlot parent, DasAxis xaxis, DasAxis yaxis) { + this( parent, parent, xaxis, yaxis ); + } + + public CrossHairMouseModule( DasPlot parent, org.das2.dataset.DataSetConsumer dataSetConsumer, DasAxis xAxis, DasAxis yAxis ) { + super(parent,new CrossHairRenderer(parent,dataSetConsumer,xAxis,yAxis),"Crosshair Digitizer"); + this.plot= parent; + this.dataSetConsumer= dataSetConsumer; + this.xaxis= xAxis; + this.yaxis= yAxis; + this.de= new DataPointSelectionEvent(this,null,null); + } + + public static CrossHairMouseModule create( DasPlot parent ) { + DasAxis xaxis= null; + DasAxis yaxis= null; + return new CrossHairMouseModule(parent,null,xaxis,yaxis); + } + + public void addInfoItem(InfoItem i) { + DragRenderer dr = getDragRenderer(); + if (dr instanceof CrossHairRenderer) { + ((CrossHairRenderer)dr).addInfoItem(i); + } + } + + public void removeInfoItem(InfoItem i) { + DragRenderer dr = getDragRenderer(); + if (dr instanceof CrossHairRenderer) { + ((CrossHairRenderer)dr).removeInfoItem(i); + } + } + + private DataSet getContextDataSet() { + DataSet ds; + if ( dataSetConsumer!=null ) { + ds = dataSetConsumer.getConsumedDataSet(); + } else { + Renderer[] rends= ((DasPlot)this.parent).getRenderers(); + if ( rends.length>0 ) { + ds= rends[0].getConsumedDataSet(); + } else { + ds= null; + } + } + return ds; + } + protected DataPointSelectionEvent getDataPointSelectionEvent(MousePointSelectionEvent e) { + de.setDataSet( getContextDataSet() ); + DasAxis xa, ya; + xa= ( this.xaxis==null ) ? plot.getXAxis() : xaxis; + ya= ( this.yaxis==null ) ? plot.getYAxis() : yaxis; + de.set(xa.invTransform(e.getX()),ya.invTransform(e.getY())); + return de; + } + + public void mousePointSelected(MousePointSelectionEvent e) { + fireDataPointSelectionListenerDataPointSelected(getDataPointSelectionEvent(e)); + } + + /** Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + listenerList.remove(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + protected void fireDataPointSelectionListenerDataPointSelected(DataPointSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener)listeners[i+1]).dataPointSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/CrossHairRenderer.java b/dasCore/src/main/java/org/das2/event/CrossHairRenderer.java new file mode 100755 index 000000000..421a15c6a --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/CrossHairRenderer.java @@ -0,0 +1,460 @@ +/* File: CrossHairRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.event; + +import org.das2.components.propertyeditor.Editable; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.format.DefaultDatumFormatterFactory; +import org.das2.datum.format.DatumFormatter; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; + +import org.das2.datum.Datum; + +import org.das2.graph.Renderer; +import java.awt.*; +import java.awt.geom.Point2D; +import java.text.*; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author eew + */ +public class CrossHairRenderer extends LabelDragRenderer implements DragRenderer, Editable { + + protected int xInitial; + protected int yInitial; + protected DasAxis XAxis; + protected DasAxis YAxis; + protected DasPlot parent; + private int ix = 0; // store the current position within the dataset object + private int iy = 0; + private int context; + private DatumFormatter nfx; + private DatumFormatter nfy; + private DatumFormatter nfz; + private FontMetrics fm; + private int dxMax = -999999; + private Rectangle hDirtyBounds; + private Rectangle vDirtyBounds; + private Point crossHairLocation = null; + private DataSetConsumer dataSetConsumer; + + private List infoItems = new ArrayList(); + + /** + * Holds value of property allPlanesReport. + */ + private boolean allPlanesReport; + /** + * Holds value of property debugging. + */ + private boolean debugging; + /** + * snapping = true means that the cross-hair digitizer will only + * display x and y values that are valid tags in the data set. + */ + private boolean snapping; + + public CrossHairRenderer(DasPlot parent, DataSetConsumer dataSetConsumer, DasAxis xAxis, DasAxis yAxis) { + super(parent); + this.XAxis = xAxis; + this.YAxis = yAxis; + this.parent = parent; + this.dataSetConsumer = dataSetConsumer; + hDirtyBounds = new Rectangle(); + vDirtyBounds = new Rectangle(); + } + + void addInfoItem(CrossHairMouseModule.InfoItem item) { + infoItems.add(item); + } + + void removeInfoItem(CrossHairMouseModule.InfoItem item) { + infoItems.remove(item); + } + + private DatumFormatter addResolutionToFormat(DatumFormatter nfz) throws ParseException { + String formatString = nfz.toString(); + String result; + if (formatString.indexOf('E') == -1) { + result = formatString + "00"; + } else { + String[] ss = formatString.split("E"); + if (ss[0].indexOf('.') == -1) { + result = ss[0] + ".00" + "E0"; + } else { + result = ss[0] + "00" + "E0"; + } + } + return DefaultDatumFormatterFactory.getInstance().newFormatter(result); + } + + private String getZString(TableDataSet tds, Datum x, Datum y, int[] ij) { + int i = DataSetUtil.closestColumn(tds, x); + int j = TableUtil.closestRow(tds, tds.tableOfIndex(i), y); + Datum zValue = tds.getDatum(i, j); + + if (ij != null) { + ij[0] = i; + ij[1] = j; + } + + try { + if (dataSetConsumer instanceof TableDataSetConsumer) { + nfz = ((TableDataSetConsumer) dataSetConsumer).getZAxis().getDatumFormatter(); + nfz = addResolutionToFormat(nfz); + } else { + nfz = DefaultDatumFormatterFactory.getInstance().newFormatter("0.000"); + } + } catch (java.text.ParseException pe) { + org.das2.DasProperties.getLogger().severe("failure to create formatter"); + DasAxis axis = ((TableDataSetConsumer) dataSetConsumer).getZAxis(); + axis.getUnits().getDatumFormatterFactory().defaultFormatter(); + } + + String result; + if (zValue.isFill()) { + result = "fill"; + } else { + result = nfz.grannyFormat(zValue); + } + if (allPlanesReport) { + if (debugging) { + result += "!c" + tds.toString(); + } + String[] planeIds = tds.getPlaneIds(); + for (int iplane = 0; iplane < planeIds.length; iplane++) { + if (!planeIds[iplane].equals("")) { + result = result + "!c"; + result += planeIds[iplane] + ":" + nfz.grannyFormat(((TableDataSet) tds.getPlanarView(planeIds[iplane])).getDatum(i, j)); + if (debugging) { + result += " " + ((TableDataSet) tds.getPlanarView(planeIds[iplane])).toString(); + } + } + } + if (debugging) { + result += "!ci:" + i + " j:" + j; + } + } + return result; + } + + private int closestPointVector(VectorDataSet ds, Datum x, Datum y) { + + Boolean xmono = (Boolean) ds.getProperty(DataSet.PROPERTY_X_MONOTONIC); + + DasAxis xa, ya; + xa = (this.XAxis == null) ? parent.getXAxis() : XAxis; + ya = (this.YAxis == null) ? parent.getYAxis() : YAxis; + + int start, end; + Point2D.Double me = new Point2D.Double(xa.transform(x), ya.transform(y)); + if (xmono != null && xmono.equals(Boolean.TRUE)) { + start = DataSetUtil.getPreviousColumn(ds, xa.getDataMinimum()); + end = DataSetUtil.getNextColumn(ds, xa.getDataMaximum()); + } else { + start = 0; + end = ds.getXLength(); + } + + int bestIndex = -1; + double bestXDist = Double.POSITIVE_INFINITY; + double bestDist = Double.POSITIVE_INFINITY; + int comparisons = 0; + + // prime the best dist comparison by scanning decimated dataset + for (int i = start; i < end; i += 100) { + double x1 = xa.transform(ds.getXTagDatum(i)); + double dist = Math.abs(x1 - me.getX()); + if (dist < bestXDist) { + bestXDist = dist; + } + } + + for (int i = start; i < end; i++) { + double x1 = xa.transform(ds.getXTagDatum(i)); + if (Math.abs(x1 - me.getX()) <= bestXDist) { + Point2D them = new Point2D.Double(x1, ya.transform(ds.getDatum(i))); + double dist = me.distance(them); + comparisons++; + if (dist < bestDist) { + bestIndex = i; + bestDist = dist; + bestXDist = Math.abs(x1 - me.getX()); + } + } + } + + return bestIndex; + + + } + + @Override + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + Graphics2D g = (Graphics2D) g1; + g.setRenderingHints((RenderingHints) org.das2.DasProperties.getRenderingHints()); + + DataSet ds; + + if ( dataSetConsumer!=null ) { + ds = dataSetConsumer.getConsumedDataSet(); + } else { + Renderer[] rends= ((DasPlot)this.parent).getRenderers(); + if ( rends.length>0 ) { + ds= rends[0].getConsumedDataSet(); + } else { + ds= null; + } + } + + Rectangle[] superDirty = null; + + Datum x = null; + Datum y = null; + + DasAxis xa, ya; + xa = (this.XAxis == null) ? parent.getXAxis() : XAxis; + ya = (this.YAxis == null) ? parent.getYAxis() : YAxis; + + if (crossHairLocation == null) { + + x = xa.invTransform(p2.x); + y = ya.invTransform(p2.y); + + nfy = y.getFormatter(); + + String xAsString; + nfx = x.getFormatter(); + xAsString = nfx.format(x); + + + String yAsString; + yAsString = nfy.format(y); + + String report; + + String nl = multiLine ? "!c" : " "; + + report = "x:" + xAsString + nl + "y:" + yAsString; + + if (ds != null) { + if (ds instanceof TableDataSet) { + TableDataSet tds = (TableDataSet) ds; + String zAsString; + if (tds != null && snapping) { + int[] ij = new int[2]; + zAsString = getZString(tds, x, y, ij); + x = tds.getXTagDatum(ij[0]); + xAsString = nfx.format(x); + y = tds.getYTagDatum(tds.tableOfIndex(ij[0]), ij[1]); + yAsString = nfy.format(y); + } else { + zAsString = getZString(tds, x, y, null); + } + report = "x:" + xAsString + nl + "y:" + yAsString + nl + "z:" + zAsString; + } else { + if (ds == null && dataSetConsumer instanceof DasPlot) { + if (((DasPlot) dataSetConsumer).getRenderers().length > 0) { + ds = ((DasPlot) dataSetConsumer).getRenderer(0).getDataSet(); + } + } + if (ds != null && snapping) { + VectorDataSet vds = (VectorDataSet) ds; + if (vds.getXLength() == 0) { + yAsString = "(empty dataset)"; + } else { + int i = closestPointVector(vds, x, y); + x = vds.getXTagDatum(i); + y = vds.getDatum(i); + xAsString = nfx.format(x); + yAsString = nfy.format(y); + if (allPlanesReport) { + String result = yAsString; + String[] planeIds = vds.getPlaneIds(); + for (int iplane = 0; iplane < planeIds.length; iplane++) { + if (!planeIds[iplane].equals("")) { + result = result + "!c"; + result += planeIds[iplane] + ":" + nfy.grannyFormat(((VectorDataSet) vds.getPlanarView(planeIds[iplane])).getDatum(i)); + if (debugging) { + result += " " + ((VectorDataSet) vds.getPlanarView(planeIds[iplane])).toString(); + } + } + } + yAsString = result; + } + } + } + report = "x:" + xAsString + nl + "y:" + yAsString; + } + } + + StringBuilder builder = new StringBuilder(report); + for (CrossHairMouseModule.InfoItem item : infoItems) { + builder.append("!C"); + item.label(x, y, builder); + } + report = builder.toString(); + + setLabel(report); + super.renderDrag(g, p1, p2); + } + + if (snapping && x != null && y != null) { + Point p3 = new Point((int) xa.transform(x), (int) ya.transform(y)); + drawCrossHair(g, p3); + /* + //p2= GraphUtil.moveTowards( p2, p3, 4 ); + g.drawLine( p2.x, p2.y, p3.x, p3.y ); + dirtyBounds.add( p3 ); + dirtyBounds.add( p2 );*/ + } else { + drawCrossHair(g, p2); + } + + + return new Rectangle[]{this.hDirtyBounds, + this.vDirtyBounds, + dirtyBounds + }; + } + + private void drawCrossHair(Graphics g0, Point p) { + + Graphics2D g = (Graphics2D) g0.create(); + g.setClip(null); + + Color color0 = Color.black; + + g.setColor(color0); + + Dimension d = parent.getCanvas().getSize(); + hDirtyBounds.setBounds(0, p.y - 1, d.width, 3); + + Stroke stroke0 = g.getStroke(); + + g.setColor(ghostColor); + g.setStroke(new BasicStroke(3.0f)); + g.drawLine(0, p.y, d.width, p.y); + g.drawLine(p.x, 0, p.x, d.height); + + g.setColor(color0); + g.setStroke(stroke0); + + g.drawLine(0, p.y, d.width, p.y); + vDirtyBounds.setBounds(p.x - 1, 0, 3, d.height); + g.drawLine(p.x, 0, p.x, d.height); + + g.dispose(); + + } + + public void clear(Graphics g) { + super.clear(g); + parent.paintImmediately(hDirtyBounds); + parent.paintImmediately(vDirtyBounds); + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return false; + } + + /** + * Getter for property allPlanesReport. + * @return Value of property allPlanesReport. + */ + public boolean isAllPlanesReport() { + return this.allPlanesReport; + } + + /** + * Setter for property allPlanesReport. + * @param allPlanesReport New value of property allPlanesReport. + */ + public void setAllPlanesReport(boolean allPlanesReport) { + this.allPlanesReport = allPlanesReport; + } + + /** + * Getter for property debugging. + * @return Value of property debugging. + */ + public boolean isDebugging() { + return this.debugging; + } + + /** + * Setter for property debugging. + * @param debugging New value of property debugging. + */ + public void setDebugging(boolean debugging) { + this.debugging = debugging; + } + + public Rectangle[] getDirtyBounds() { + return new Rectangle[]{super.dirtyBounds, this.hDirtyBounds, this.vDirtyBounds}; + } + + public boolean isSnapping() { + return snapping; + } + + public void setSnapping(boolean b) { + snapping = b; + } + /** + * Holds value of property multiLine. + */ + private boolean multiLine = false; + + /** + * Getter for property multiLine. + * @return Value of property multiLine. + */ + public boolean isMultiLine() { + + return this.multiLine; + } + + /** + * Setter for property multiLine. + * @param multiLine New value of property multiLine. + */ + public void setMultiLine(boolean multiLine) { + + this.multiLine = multiLine; + } +} diff --git a/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java b/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java new file mode 100644 index 000000000..214364252 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java @@ -0,0 +1,725 @@ +/* + * Cutoff2MouseModule.java + * + * Created on November 10, 2005, 1:41 PM + * + * + */ + +package org.das2.event; + +import java.text.ParseException; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.ClippedTableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.DataSetUpdateEvent; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.DefaultVectorDataSet; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.SingleVectorDataSet; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import org.das2.graph.SymbolLineRenderer; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.Color; +import java.beans.PropertyChangeEvent; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JFrame; + +/** + * + * @author Jeremy + */ +public class CutoffMouseModule extends BoxSelectorMouseModule { + + DasAxis xaxis, yaxis; + DataSetConsumer dataSetConsumer; + DatumRange xrange; + DatumRange yrange; + String lastComment; + CutoffSlicer cutoffSlicer; + DasApplication application; + + public CutoffMouseModule( DasPlot parent, DataSetConsumer consumer ) { + super( parent, parent.getXAxis(), parent.getYAxis(), consumer, new BoxRenderer(parent,true), "Cutoff" ); + application= parent.getCanvas().getApplication(); + this.dataSetConsumer= consumer; + } + + public static final String CONFIG_VOYAGER_HR_LOWER= "Ondrej: min=-4. slopeMin=0.26 nave=3 cutoff=lower xres=1s"; + public static final String CONFIG_GALILEO_LOWER= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=120s"; + public static final String CONFIG_GALILEO_LOWER_60= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=60s"; + public static final String CONFIG_GALILEO_LOWER_30= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=30s"; + + /** + * see CONFIG_VOYAGER_HR_LOWER, etc. + * @param config + */ + public void setConfig( String config ) throws ParseException { + if ( !config.startsWith("Ondrej:") ) { + throw new IllegalArgumentException("config must start with Ondrej"); + } + + Pattern p= Pattern.compile("(\\S+)=(\\S+)"); + + Matcher m= p.matcher(config.substring(7) ); + + while ( m.find() ) { + String name= m.group(1); + String sval= m.group(2); + if ( name.equals("min") ) { + setLevelMin(Units.dimensionless.parse(sval)); + } else if ( name.equals("slopeMin") ) { + setSlopeMin(Units.dimensionless.parse(sval)); + } else if ( name.equals("nave") ) { + setNave( Integer.parseInt(sval) ); + } else if (name.equals("cutoff") ) { + setLowCutoff( "lower".equals(sval) ); + } else if (name.equals("xres") ) { + setXResolution( Units.seconds.parse(sval) ); + } + } + } + + protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) { + + DatumRange xrange0= xrange; + DatumRange yrange0= yrange; + + xrange= event.getXRange(); + yrange= event.getYRange(); + if ( event.getPlane("keyChar")!=null ) { + lastComment= (String)event.getPlane("keyChar"); + } else { + lastComment= null; + } + + try { + recalculateSoon( ); + } catch ( RuntimeException ex ) { + xrange= xrange0; + yrange= yrange0; + throw ex; + } + } + + /** + * return RebinDescriptor that is on descrete, repeatable boundaries. + * get us2000, divide by resolution, truncate, multiply by resolution. + */ + private RebinDescriptor getRebinDescriptor( DatumRange range ) { + double res= xResolution.doubleValue(Units.microseconds); + double min= range.min().doubleValue(Units.us2000); + min= Math.floor( min / res ); + double max= range.max().doubleValue(Units.us2000); + max= Math.ceil( max / res ); + int nbin= (int)(max-min); + + RebinDescriptor ddx= new RebinDescriptor( min*res, max*res, Units.us2000, nbin, false ); + return ddx; + } + + private void recalculateSoon( ) { + Runnable run= new Runnable() { + public void run() { + ProgressMonitor mon= application.getMonitorFactory().getMonitor( parent, "calculating cutoffs", "calculating cutoffs" ); + recalculate( mon ); + } + }; + new Thread( run, "digitizer recalculate" ).start(); + } + + private synchronized void recalculate( ProgressMonitor mon) { + TableDataSet tds= (TableDataSet)dataSetConsumer.getConsumedDataSet(); + if ( tds==null ) return; + if ( xrange==null ) return; + + tds= new ClippedTableDataSet( tds, xrange, yrange ); + + // average the data down to xResolution + DataSetRebinner rebinner= new AverageTableRebinner(); + + DatumRange range= DataSetUtil.xRange( tds ); + RebinDescriptor ddx= getRebinDescriptor( range ); + + try { + //TODO: why does rebin throw DasException? + tds= (TableDataSet)rebinner.rebin( tds, ddx, null, null ); + } catch ( DasException e ) { + throw new RuntimeException(e); + } + + VectorDataSetBuilder builder= new VectorDataSetBuilder( tds.getXUnits(), tds.getYUnits() ); + + mon.setTaskSize( tds.getXLength() ); + mon.started(); + + for ( int i=0; i-1 ) { + builder.insertY( tds.getXTagDatum(i), tds.getYTagDatum( tds.tableOfIndex(i), icutoff ) ); + } else { + Units yunits=tds.getYUnits(); + builder.insertY( tds.getXTagDatum(i), yunits.createDatum(yunits.getFillDouble()) ); + } + } + + mon.finished(); + + if ( mon.isCancelled() ) return; + + String comment= "Ondrej:"+levelMin+":"+slopeMin+":"+nave; + if ( lastComment!=null ) { + comment= lastComment + " "+comment; + } + builder.setProperty("comment",comment); + builder.setProperty( DataSet.PROPERTY_X_TAG_WIDTH, this.xResolution ); + VectorDataSet vds= builder.toVectorDataSet(); + + fireDataSetUpdateListenerDataSetUpdated( new DataSetUpdateEvent( this,vds ) ); + + } + + /** + * slopeMin in the y units of ds. + * levelMin in the y units of ds. + * mult=-1 high cutoff, =1 low cutoff + */ + public int cutoff( VectorDataSet ds, Datum slopeMin, int nave, int mult, Datum levelMin ) { + int nfr= ds.getXLength(); + if ( nfr < (nave+1) ) { + throw new IllegalArgumentException("DataSet doesn't contain enough elements"); + } + double[] cumul= new double[nfr]; + Units units= ds.getYUnits(); + double level= levelMin.doubleValue( units ); + double slope= slopeMin.doubleValue( units ); + + cumul[0]= ds.getDouble(0, units); + for ( int i=1; i0 ? ave[j+k] : ave[j]; + if ( uave <= level ) icof[j]=false; + icofBuilder.insertY( ds.getXTagDatum(j), icof[j] ? units.dimensionless.createDatum(1) : units.dimensionless.createDatum(0) ); + } + } + + if ( cutoffSlicer!=null ) { + cutoffSlicer.slopeRenderer.setDataSet( slopeBuilder.toVectorDataSet() ); + cutoffSlicer.levelRenderer.setDataSet( levelBuilder.toVectorDataSet() ); + cutoffSlicer.icofRenderer.setDataSet( icofBuilder.toVectorDataSet() ); + } + + int icutOff=-1; + + for ( int j= ( mult<0 ? nfr-1 : 0 ); j>=0 && j= 0; i -= 2) { + if (listeners[i]==java.beans.PropertyChangeListener.class) { + ((java.beans.PropertyChangeListener)listeners[i+1]).propertyChange(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DasEvent.java b/dasCore/src/main/java/org/das2/event/DasEvent.java new file mode 100644 index 000000000..3fa0a79bd --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasEvent.java @@ -0,0 +1,37 @@ +/* File: DasEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public class DasEvent extends java.util.EventObject { + + /** Creates a new instance of DasEvent */ + public DasEvent(Object source) { + super(source); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DasEventMulticaster.java b/dasCore/src/main/java/org/das2/event/DasEventMulticaster.java new file mode 100644 index 000000000..869407099 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasEventMulticaster.java @@ -0,0 +1,122 @@ +/* File: DasEventMulticaster.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import java.util.EventListener; + +/** + * + * @author eew + */ +public class DasEventMulticaster extends java.awt.AWTEventMulticaster + implements DataPointSelectionListener, DataRangeSelectionListener, + TimeRangeSelectionListener { + + /** Creates a new instance of DasEventMultiCaster */ + protected DasEventMulticaster(EventListener a, EventListener b) { + super(a, b); + } + + public void dataPointSelected(DataPointSelectionEvent e) { + ((DataPointSelectionListener)a).dataPointSelected(e); + ((DataPointSelectionListener)b).dataPointSelected(e); + } + + public void dataRangeSelected(DataRangeSelectionEvent e) { + ((DataRangeSelectionListener)a).dataRangeSelected(e); + ((DataRangeSelectionListener)b).dataRangeSelected(e); + } + + public void timeRangeSelected(TimeRangeSelectionEvent e) { + ((TimeRangeSelectionListener)a).timeRangeSelected(e); + ((TimeRangeSelectionListener)b).timeRangeSelected(e); + } + + public static DataPointSelectionListener add(DataPointSelectionListener a, DataPointSelectionListener b) { + if (a == null) return b; + if (b == null) return a; + return new DasEventMulticaster(a, b); + } + + public static DataRangeSelectionListener add(DataRangeSelectionListener a, DataRangeSelectionListener b) { + if (a == null) return b; + if (b == null) return a; + return new DasEventMulticaster(a, b); + } + + public static TimeRangeSelectionListener add(TimeRangeSelectionListener a, TimeRangeSelectionListener b) { + if (a == null) return b; + if (b == null) return a; + return new DasEventMulticaster(a, b); + } + + public static DataPointSelectionListener remove(DataPointSelectionListener a, DataPointSelectionListener b) { + if (a instanceof DasEventMulticaster) { + return (DataPointSelectionListener)((DasEventMulticaster)a).remove(b); + } + return (a == b ? null : a); + } + + public static DataRangeSelectionListener remove(DataRangeSelectionListener a, DataRangeSelectionListener b) { + if (a instanceof DasEventMulticaster) { + return (DataRangeSelectionListener)((DasEventMulticaster)a).remove(b); + } + return (a == b ? null : a); + } + + public static TimeRangeSelectionListener remove(TimeRangeSelectionListener a, TimeRangeSelectionListener b) { + if (a instanceof DasEventMulticaster) { + return (TimeRangeSelectionListener)((DasEventMulticaster)a).remove(b); + } + return (a == b ? null : a); + } + + protected EventListener remove(EventListener listener) { + if (listener == a) return b; + if (listener == b) return a; + EventListener aa; + EventListener bb; + if (a instanceof DasEventMulticaster) { + aa = ((DasEventMulticaster)a).remove(listener); + } + else { + aa = a; + } + if (b instanceof DasEventMulticaster) { + bb = ((DasEventMulticaster)b).remove(listener); + } + else { + bb = b; + } + if (bb == b && aa == a) return this; + return new DasEventMulticaster(aa, bb); + } + + public String toString() { + + return "[" + a + "," + b + "]"; + + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DasMouseEvent.java b/dasCore/src/main/java/org/das2/event/DasMouseEvent.java new file mode 100644 index 000000000..2781530b1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasMouseEvent.java @@ -0,0 +1,37 @@ +/* File: DasMouseEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public class DasMouseEvent extends DasEvent { + + /** Creates a new instance of DasMouseEvent */ + public DasMouseEvent(Object o) { + super(o); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DasMouseInputAdapter.java b/dasCore/src/main/java/org/das2/event/DasMouseInputAdapter.java new file mode 100755 index 000000000..99121d0e3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasMouseInputAdapter.java @@ -0,0 +1,1157 @@ +/* File: DasMouseInputAdapter.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.event; + +import org.das2.graph.DasColumn; +import org.das2.graph.DasRow; +import org.das2.system.DasLogger; +import org.das2.DasApplication; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasCanvasComponent; +import org.das2.util.DasExceptionHandler; +import java.awt.*; + +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import java.awt.event.*; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.util.*; +import java.util.logging.Logger; +import org.das2.components.propertyeditor.Editable; + +/** + * DasMouseInputAdapter delegates mouse and key events to mouse modules, which + * do something with the events. Also, mouse events are promoted to MouseDragEvents + * which conveniently store information about the entire drag gesture. + * + * The base class of MouseModule has do-nothing stubs for KeyListener, MouseListener, + * MouseMotionListener, and MouseWheelListener, which can be implemented if the + * module wants to do something with these events. Also MouseDragEvents will be + * sent to the module as its DragRenderer has requested: after the mouse release, + * during the drag, or when keys are pressed. + * + * The module will first receive the low-level events before receiving the MouseDragEvents. + * + * @author jbf + */ +public class DasMouseInputAdapter extends MouseInputAdapter implements Editable, MouseWheelListener { + + private MouseModule primary = null; + private MouseModule secondary = null; + /* + * array of active modules. This will be removed, as the idea was + * that a few modules could be used together simultaneously, but this implementation + * only allows for one to be active at a time. + */ + private Vector active = null; + private boolean pinned = false; + private Vector modules; + private HashMap primaryActionButtonMap; + private HashMap secondaryActionButtonMap; + protected JPopupMenu primaryPopup; + protected JPopupMenu secondaryPopup; + private Point primaryPopupLocation; + private Point secondaryPopupLocation; + private JPanel pngFileNamePanel; + private JTextField pngFileTextField; + private JFileChooser pngFileChooser; + JCheckBoxMenuItem primarySelectedItem; + JCheckBoxMenuItem secondarySelectedItem; // must be non-null, but may contain null elements + Rectangle[] dirtyBoundsList; + Logger log = DasLogger.getLogger(DasLogger.GUI_LOG); + /** + * number of additional inserted popup menu items to the primary menu. + */ + int numInserted; + + /** + * number of additional inserted popup menu items to the secondary menu. + * Components can be added to the primary menu, but not the secondary. + */ + int numInsertedSecondary; + + protected ActionListener popupListener; + protected DasCanvasComponent parent = null; + //private Point selectionStart; // in component frame + //private Point selectionEnd; // in component frame + private Point dSelectionStart; // in DasCanvas device frame + private Point dSelectionEnd; // in DasCanvas device frame + private MousePointSelectionEvent mousePointSelection; + private int xOffset; // parent to canvas offset + private int yOffset; // parent to canvas offset + private int button = 0; // current depressed button + private MouseMode mouseMode = MouseMode.idle; + private boolean drawControlPoints = false; + private DragRenderer resizeRenderer = null; + private Point resizeStart = null; + /* + *this will be removed, and the component can add its own popup buttons. + */ + Vector hotSpots = null; + Rectangle dirtyBounds = null; + private boolean hasFocus = false; + private Point pressPosition; // in the component frame + private boolean headless; + + private static class MouseMode { + + String s; + boolean resizeTop = false; + boolean resizeBottom = false; + boolean resizeRight = false; + boolean resizeLeft = false; + Point moveStart = null; // in the DasCanvas frame + static MouseMode idle = new MouseMode("idle"); + static MouseMode resize = new MouseMode("resize"); + static MouseMode move = new MouseMode("move"); + static MouseMode moduleDrag = new MouseMode("moduleDrag"); + + MouseMode(String s) { + this.s = s; + } + + public String toString() { + return s; + } + } + + /** Creates a new instance of dasMouseInputAdapter */ + public DasMouseInputAdapter(DasCanvasComponent parent) { + + this.parent = parent; + + modules = new Vector(); + + primaryActionButtonMap = new HashMap(); + secondaryActionButtonMap = new HashMap(); + + this.headless = DasApplication.getDefaultApplication().isHeadless(); + if (!headless) { + primaryPopup= new JPopupMenu(); + numInserted = createPopup(primaryPopup); + secondaryPopup= new JPopupMenu(); + numInsertedSecondary = createPopup(secondaryPopup); + } + + active = null; + + mousePointSelection = new MousePointSelectionEvent(this, 0, 0); + + resizeRenderer = new BoxRenderer(parent); + + + dirtyBoundsList = new Rectangle[0]; + } + + public void replaceMouseModule(MouseModule oldModule, MouseModule newModule) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(oldModule); + primaryActionButtonMap.put(newModule, j); + primaryActionButtonMap.remove(oldModule); + secondaryActionButtonMap.put(newModule, secondaryActionButtonMap.get(oldModule)); + secondaryActionButtonMap.remove(oldModule); + modules.removeElement(oldModule); + modules.addElement(newModule); + } + + /** + * add a mouse module to the list of available modules. If a module with the same + * label exists already, it will be replaced. + */ + public void addMouseModule(MouseModule module) { + + if (headless) { + DasLogger.getLogger(DasLogger.GUI_LOG).fine("not adding module since headless is true"); + + } else { + MouseModule preExisting = getModuleByLabel(module.getLabel()); + if (preExisting != null) { + DasLogger.getLogger(DasLogger.GUI_LOG).fine("Replacing mouse module " + module.getLabel() + "."); + replaceMouseModule(preExisting, module); + + } else { + + modules.add(module); + + String name = module.getLabel(); + + JCheckBoxMenuItem primaryNewItem = new JCheckBoxMenuItem(name); + JCheckBoxMenuItem secondaryNewItem = new JCheckBoxMenuItem(name); + + primaryNewItem.addActionListener(popupListener); + primaryNewItem.setActionCommand("primary"); + secondaryNewItem.addActionListener(popupListener); + secondaryNewItem.setActionCommand("secondary"); + + primaryActionButtonMap.put(module, primaryNewItem); + secondaryActionButtonMap.put(module, secondaryNewItem); + + try { + // insert the check box after the separator, and at the end of the actions list. + primaryPopup.add(primaryNewItem, numInserted + 1 + primaryActionButtonMap.size() - 1); + secondaryPopup.add(secondaryNewItem, numInsertedSecondary + 1 + secondaryActionButtonMap.size() - 1); + } catch ( IllegalArgumentException ex ) { + ex.printStackTrace(); + } + + } + } + } + + public KeyAdapter getKeyAdapter() { + return new KeyAdapter() { + + public void keyPressed(KeyEvent ev) { + log.finest("keyPressed "); + if (ev.getKeyCode() == KeyEvent.VK_ESCAPE & active != null) { + active = null; + getGlassPane().setDragRenderer(null, null, null); + parent.getCanvas().paintImmediately(0, 0, parent.getCanvas().getWidth(), parent.getCanvas().getHeight()); + refresh(); + ev.consume(); + } else if (ev.getKeyCode() == KeyEvent.VK_SHIFT) { + drawControlPoints = true; + parent.repaint(); + } else if (ev.getKeyChar() == 'p') { + pinned = true; + ev.consume(); + } else { + if (active == null) { + return; + } + for (int i = 0; i < active.size(); i++) { + ((MouseModule) active.get(i)).keyPressed(ev); + } + } + } + + public void keyReleased(KeyEvent ev) { + if (ev.getKeyCode() == KeyEvent.VK_SHIFT) { + drawControlPoints = false; + parent.repaint(); + } + if (active == null) { + return; + } + for (int i = 0; i < active.size(); i++) { + ((MouseModule) active.get(i)).keyReleased(ev); + } + } + + public void keyTyped(KeyEvent ev) { + if (active == null) { + return; + } + for (int i = 0; i < active.size(); i++) { + ((MouseModule) active.get(i)).keyTyped(ev); + } + } + }; + } + + public MouseModule getPrimaryModule() { + ArrayList activ = new ArrayList(); + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + activ.add(modules.get(i)); + } + } + return (MouseModule) activ.get(0); // at one time we allowed multiple modules at once. + } + + public MouseModule getSecondaryModule() { + ArrayList activ = new ArrayList(); + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) secondaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + activ.add(modules.get(i)); + } + } + return (MouseModule) activ.get(0); // at one time we allowed multiple modules at once. + } + + /** + * set the primary module, the module receiving left-button events, to the + * module provided. If the module is not already loaded, implicitly addMouseModule + * is called. + */ + public void setPrimaryModule(MouseModule module) { + if (headless) { + return; + } + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(module); + if (j == null) { + addMouseModule(module); + } + for (Iterator i = primaryActionButtonMap.entrySet().iterator(); i.hasNext();) { + try { + Object ii = ((Map.Entry) i.next()).getValue(); + ((JCheckBoxMenuItem) ii).setSelected(false); + } catch (RuntimeException e) { + e.printStackTrace(); + throw e; + } + } + + j = (JCheckBoxMenuItem) primaryActionButtonMap.get(module); + if (j != null) { + j.setSelected(true); + } + primarySelectedItem = j; + primary = module; + parent.setCursor(primary.getCursor()); + } + + /** + * set the secondary module, the module receiving middle-button events, to the + * module provided. If the module is not already loaded, implicitly addMouseModule + * is called. + */ + public void setSecondaryModule(MouseModule module) { + if (headless) { + return; + } + JCheckBoxMenuItem j = (JCheckBoxMenuItem) secondaryActionButtonMap.get(module); + if (j == null) { + addMouseModule(module); + } + for (Iterator i = secondaryActionButtonMap.entrySet().iterator(); i.hasNext();) { + try { + Object ii = ((Map.Entry) i.next()).getValue(); + ((JCheckBoxMenuItem) ii).setSelected(false); + } catch (RuntimeException e) { + e.printStackTrace(); + throw e; + } + } + + j = (JCheckBoxMenuItem) secondaryActionButtonMap.get(module); + if (j != null) { + j.setSelected(true); + } + secondarySelectedItem = j; + secondary = module; + } + + /** + * create the popup for the component. This popup has three + * sections: + *
    1. component actions
    +     *2. mouse modules
    +     *3. canvas actions
    + * The variable numInserted is the number of actions inserted, and + * is used to calculate the position of inserted mouse modules. + */ + private int createPopup(JPopupMenu popup) { + + popupListener = createPopupMenuListener(); + + Action[] componentActions = parent.getActions(); + for (int iaction = 0; iaction < componentActions.length; iaction++) { + JMenuItem item = new JMenuItem(); + item.setAction(componentActions[iaction]); + popup.add(item); + } + int numInsert = componentActions.length; + + popup.addSeparator(); + // mouse modules go here + popup.addSeparator(); + + Action[] canvasActions = DasCanvas.getActions(); + for (int iaction = 0; iaction < canvasActions.length; iaction++) { + JMenuItem item = new JMenuItem(); + item.setAction(canvasActions[iaction]); + popup.add(item); + } + + return numInsert; + } + + private ActionListener createPopupMenuListener() { + return new ActionListener() { + + public void actionPerformed(ActionEvent e) { + DasMouseInputAdapter outer = DasMouseInputAdapter.this; // useful for debugging + String command = e.getActionCommand(); + if (command.equals("properties")) { + parent.showProperties(); + } else if (command.equals("print")) { + Printable p = ((DasCanvas) parent.getParent()).getPrintable(); + PrinterJob pj = PrinterJob.getPrinterJob(); + pj.setPrintable(p); + if (pj.printDialog()) { + try { + pj.print(); + } catch (PrinterException pe) { + Object[] message = {"Error printing", pe.getMessage()}; + JOptionPane.showMessageDialog(null, message, "ERROR", JOptionPane.ERROR_MESSAGE); + } + } + } else if (command.equals("toPng")) { + if (pngFileNamePanel == null) { + pngFileNamePanel = new JPanel(); + pngFileNamePanel.setLayout(new BoxLayout(pngFileNamePanel, BoxLayout.X_AXIS)); + pngFileTextField = new JTextField(32); + pngFileTextField.setMaximumSize(pngFileTextField.getPreferredSize()); + pngFileChooser = new JFileChooser(); + pngFileChooser.setApproveButtonText("Select File"); + pngFileChooser.setDialogTitle("Write to PNG"); + JButton b = new JButton("Browse"); + b.setActionCommand("pngBrowse"); + b.addActionListener(this); + pngFileNamePanel.add(pngFileTextField); + pngFileNamePanel.add(b); + } + pngFileTextField.setText(pngFileChooser.getCurrentDirectory().getPath()); + String[] options = {"Write to PNG", "Cancel"}; + int choice = JOptionPane.showOptionDialog(parent, + pngFileNamePanel, + "Write to PNG", + 0, + JOptionPane.QUESTION_MESSAGE, + null, + options, + "Ok"); + if (choice == 0) { + DasCanvas canvas = (DasCanvas) parent.getParent(); + try { + canvas.writeToPng(pngFileTextField.getText()); + } catch (java.io.IOException ioe) { + org.das2.util.DasExceptionHandler.handle(ioe); + } + } + } else if (command.equals("pngBrowse")) { + int choice = pngFileChooser.showDialog(parent, "Select File"); + if (choice == JFileChooser.APPROVE_OPTION) { + pngFileTextField.setText(pngFileChooser.getSelectedFile().getPath()); + } + } else if (command.equals("close")) { + } else if (command.equals("primary")) { + if (primarySelectedItem != null) { + primarySelectedItem.setSelected(false); + } + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + primarySelectedItem = j; + break; + } + } + primarySelectedItem.setSelected(true); // for case when selection wasn't changed. + //primaryPopup.show( parent, l.x, l.y ); + } else if (command.equals("secondary")) { + if (secondarySelectedItem != null) { + secondarySelectedItem.setSelected(false); + } + Point l = secondaryPopupLocation; + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) secondaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + secondarySelectedItem = j; + break; + } + } + //secondaryPopup.show( parent, l.x, l.y ); + } else { + org.das2.util.DasDie.println("" + command); + } + } + }; + } + + /** + * call the renderDrag method of the active module's dragRenderer. This method + * returns an array of Rectangles, or null, indicating the affected regions. + * It's also permisable for a array element to be null. + */ + private void renderSelection(Graphics2D g2d) { + try { + //DasCanvas canvas = parent.getCanvas(); + //selectionStart = SwingUtilities.convertPoint(canvas, dSelectionStart, parent); + //selectionEnd = SwingUtilities.convertPoint(canvas, dSelectionEnd, parent); + + for (int i = 0; i < active.size(); i++) { + DragRenderer dr = ((MouseModule) active.get(i)).getDragRenderer(); + + //Rectangle[] dd = dr.renderDrag( getGlassPane().getGraphics(), dSelectionStart, dSelectionEnd); + //dirtyBoundsList = new Rectangle[dd.length]; + //for (i = 0; i < dd.length; i++) { + // dirtyBoundsList[i] = new Rectangle(dd[i]); + //} + getGlassPane().setDragRenderer(dr, dSelectionStart, dSelectionEnd); + } + } catch (RuntimeException e) { + DasExceptionHandler.handle(e); + } + } + + /* This attempts to redraw just the affected portions of parent. Presently it + * needs to call the parent's paintImmediately twice, because we don't know what + * the dragRenderer's dirty bounds will be. + */ + private synchronized void refresh() { + if (dirtyBoundsList.length > 0) { + Rectangle[] dd = new Rectangle[dirtyBoundsList.length]; + for (int i = 0; i < dd.length; i++) { + if (dirtyBoundsList[i] != null) { + dd[i] = new Rectangle(dirtyBoundsList[i]); + } + } + for (int i = 0; i < dd.length; i++) { + if (dd[i] != null) { + parent.getCanvas().paintImmediately(dd[i]); + } + } + for (int i = 0; i < dirtyBoundsList.length; i++) { + if (dirtyBoundsList[i] != null) { + parent.getCanvas().paintImmediately(dirtyBoundsList[i]); + } + } + } else { + if (active != null) { + parent.getCanvas().paintImmediately(0, 0, parent.getCanvas().getWidth(), parent.getCanvas().getHeight()); + } + } + if (active == null) { + dirtyBoundsList = new Rectangle[0]; + } + } + + /* + * Paint the drag renderer on top of parent. Graphics g1 should be in + * the parent's coordinate frame. + */ + public void paint(Graphics g1) { + Graphics2D g = (Graphics2D) g1.create(); + //g= (Graphics2D)getGlassPane().getGraphics(); + //g.translate(parent.getX(),parent.getY()); + + g.translate(-parent.getX(), -parent.getY()); + + if (active != null) { + renderSelection(g); + } + if (hasFocus && hoverHighlite) { + g.setColor(new Color(255, 0, 0, 10)); + g.setStroke(new BasicStroke(10)); + g.draw(parent.getBounds()); + return; + } + if (hasFocus && drawControlPoints) { + drawControlPoints(g); + } + } + + private void drawControlPoints(Graphics2D g) { + if (parent.getRow() != DasRow.NULL && parent.getColumn() != DasColumn.NULL) { + int xLeft = parent.getColumn().getDMinimum(); + int xRight = parent.getColumn().getDMaximum(); + int yTop = parent.getRow().getDMinimum(); + int yBottom = parent.getRow().getDMaximum(); + + Graphics2D gg = (Graphics2D) g.create(); + + //gg.translate(-parent.getX(),-parent.getY()); + gg.setColor(new Color(0, 0, 0, 255)); + + int ss = 9; + gg.fillRect(xLeft + 1, yTop + 1, ss - 2, ss - 2); + gg.fillRect(xRight - ss + 1, yTop + 1, ss - 2, ss - 2); + gg.fillRect(xLeft + 1, yBottom - ss + 1, ss - 2, ss - 2); + gg.fillRect(xRight - ss + 1, yBottom - ss + 1, ss - 2, ss - 2); + + gg.setColor(new Color(255, 255, 255, 100)); + gg.drawRect(xLeft, yTop, ss, ss); + gg.drawRect(xRight - ss, yTop, ss, ss); + gg.drawRect(xLeft, yBottom - ss, ss, ss); + gg.drawRect(xRight - ss, yBottom - ss, ss, ss); + + int xmid = (xLeft + xRight) / 2; + int ymid = (yTop + yBottom) / 2; + + int rr = 4; + g.setColor(new Color(255, 255, 255, 100)); + gg.fillOval(xmid - rr - 1, ymid - rr - 1, rr * 2 + 3, rr * 2 + 3); + + gg.setColor(new Color(0, 0, 0, 255)); + + gg.drawOval(xmid - rr, ymid - rr, rr * 2, rr * 2); + gg.fillOval(xmid - 1, ymid - 1, 3, 3); + + gg.dispose(); + } + } + + private MouseMode activateMouseMode(MouseEvent e) { + + boolean xLeftSide = false; + boolean xRightSide = false; + boolean xMiddle = false; + boolean yTopSide = false; + boolean yBottomSide = false; + boolean yMiddle = false; + + Point mousePoint = e.getPoint(); + mousePoint.translate(parent.getX(), parent.getY());// canvas coordinate system + + if (parent.getRow() != DasRow.NULL && parent.getColumn() != DasColumn.NULL) { + int xLeft = parent.getColumn().getDMinimum(); + int xRight = parent.getColumn().getDMaximum(); + int yTop = parent.getRow().getDMinimum(); + int yBottom = parent.getRow().getDMaximum(); + int xmid = (xLeft + xRight) / 2; + int ymid = (yTop + yBottom) / 2; + + xLeftSide = mousePoint.getX() < xLeft + 10; + xRightSide = mousePoint.getX() > xRight - 10; + xMiddle = Math.abs(mousePoint.getX() - xmid) < 4; + yTopSide = (mousePoint.getY() < yTop + 10) && (mousePoint.getY() >= yTop); + yBottomSide = mousePoint.getY() > (yBottom - 10); + yMiddle = Math.abs(mousePoint.getY() - ymid) < 4; + + } + + MouseMode result = MouseMode.idle; + Cursor cursor = new Cursor(Cursor.DEFAULT_CURSOR); + + if (!(parent instanceof DasAxis)) { + if ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK) { + if (xLeftSide) { + if (yTopSide) { + result = MouseMode.resize; + cursor = new Cursor(Cursor.NW_RESIZE_CURSOR); + } else if (yBottomSide) { + result = MouseMode.resize; + cursor = new Cursor(Cursor.SW_RESIZE_CURSOR); + } + } else if (xRightSide) { + if (yTopSide) { + result = MouseMode.resize; + cursor = new Cursor(Cursor.NE_RESIZE_CURSOR); + } else if (yBottomSide) { + result = MouseMode.resize; + cursor = new Cursor(Cursor.SE_RESIZE_CURSOR); + } + } else if (xMiddle && yMiddle) { + result = MouseMode.move; + cursor = new Cursor(Cursor.MOVE_CURSOR); + } + } + + } + + if (result == MouseMode.resize) { + result.resizeBottom = yBottomSide; + result.resizeTop = yTopSide; + result.resizeRight = xRightSide; + result.resizeLeft = xLeftSide; + } else if (result == MouseMode.move) { + result.moveStart = e.getPoint(); + result.moveStart.translate(-parent.getX(), -parent.getY()); + } + + if (result != mouseMode) { + getGlassPane().setCursor(cursor); + } + return result; + } + + @Override + public void mouseMoved(MouseEvent e) { + log.finest("mouseMoved"); + Point l = parent.getLocation(); + xOffset = l.x; + yOffset = l.y; + + boolean drawControlPoints0 = this.drawControlPoints; + if ((e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) == MouseEvent.SHIFT_DOWN_MASK) { + drawControlPoints = true; + } else { + drawControlPoints = false; + } + + if (drawControlPoints0 != drawControlPoints) { + parent.repaint(); + } + + MouseMode m; + if ((m = activateMouseMode(e)) != null) { + mouseMode = m; + } else { + mouseMode = MouseMode.idle; + } + } + + private void showPopup(JPopupMenu menu, MouseEvent ev) { + log.finest("showPopup"); + HashMap map = null; + if (menu == primaryPopup) { + map = primaryActionButtonMap; + } else if (menu == secondaryPopup) { + map = secondaryActionButtonMap; + } else { + throw new IllegalArgumentException("menu must be primary or secondary popup menu"); + } + for (Iterator i = modules.iterator(); i.hasNext();) { + MouseModule mm = (MouseModule) i.next(); + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(mm); + j.setText(mm.getLabel()); + } + menu.show(ev.getComponent(), ev.getX(), ev.getY()); + } + + public void setPinned(boolean b) { + pinned = b; + } + + public boolean getPinned() { + return pinned; + } + + @Override + public void mousePressed(MouseEvent e) { + log.finer("mousePressed " + mouseMode); + if (pinned) { + active = null; + refresh(); + } + pinned = false; + Point l = parent.getLocation(); + parent.requestFocus(); + xOffset = l.x; + yOffset = l.y; + pressPosition = e.getPoint(); + + Point cp = new Point(e.getPoint()); + cp.translate(xOffset, yOffset); + if (!parent.acceptContext(cp.x, cp.y)) { + return; + } + if (mouseMode == MouseMode.resize) { + resizeStart = new Point(0, 0); + if (mouseMode.resizeRight) { + resizeStart.x = 0 + xOffset; + } else if (mouseMode.resizeLeft) { + resizeStart.x = parent.getWidth() + xOffset; + } + if (mouseMode.resizeTop) { + resizeStart.y = parent.getHeight() + yOffset; + } else if (mouseMode.resizeBottom) { + resizeStart.y = 0 + yOffset; + } + + } else if (mouseMode == MouseMode.move) { + mouseMode.moveStart = e.getPoint(); + mouseMode.moveStart.translate(xOffset, yOffset); + + } else { + if (active == null) { + button = e.getButton(); + //selectionStart = e.getPoint(); + dSelectionStart = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), parent.getCanvas()); + //selectionEnd = e.getPoint(); + dSelectionEnd = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), parent.getCanvas()); + //graphics = (Graphics2D) parent.getGraphics(); + + if (e.isControlDown() || button == MouseEvent.BUTTON3) { + if (button == MouseEvent.BUTTON1 || button == MouseEvent.BUTTON3) { + showPopup(primaryPopup, e); + } else { + showPopup(secondaryPopup, e); + } + } else { + + active = new Vector(); + + if (button == MouseEvent.BUTTON1 || button == MouseEvent.BUTTON3) { + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) primaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + active.add(modules.get(i)); + } + } + } else { + for (int i = 0; i < modules.size(); i++) { + JCheckBoxMenuItem j = (JCheckBoxMenuItem) secondaryActionButtonMap.get(modules.get(i)); + if (j.isSelected()) { + active.add(modules.get(i)); + } + } + } + + mouseMode = MouseMode.moduleDrag; + + mousePointSelection.set(e.getX() + xOffset, e.getY() + yOffset); + for (int i = 0; i < active.size(); i++) { + MouseModule j = (MouseModule) active.get(i); + j.mousePressed(e); + if (j.dragRenderer.isPointSelection()) { + mouseDragged(e); + } + } + } + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + log.finest("mouseDragged in " + mouseMode); + if (mouseMode == MouseMode.resize) { + Point p = e.getPoint(); + p.translate(parent.getX(), parent.getY()); + getGlassPane().setDragRenderer(resizeRenderer, resizeStart, p); + getGlassPane().repaint(); + + } else if (mouseMode == MouseMode.move) { + Point moveEnd = e.getPoint(); + moveEnd.translate(xOffset, yOffset); + int dx = moveEnd.x - mouseMode.moveStart.x; + int dy = moveEnd.y - mouseMode.moveStart.y; + + int xmin = parent.getColumn().getDMinimum(); + int xmax = parent.getColumn().getDMaximum(); + + int ymin = parent.getRow().getDMinimum(); + int ymax = parent.getRow().getDMaximum(); + Point p1 = new Point(xmin + dx, ymin + dy); + Point p2 = new Point(xmax + dx, ymax + dy); + + getGlassPane().setDragRenderer(resizeRenderer, p1, p2); + getGlassPane().repaint(); + //resizeRenderer.clear(graphics); + //resizeRenderer.renderDrag(graphics, p1, p2); + } else { + if (active != null) { + //clearSelection(graphics); + //selectionEnd = e.getPoint(); + dSelectionEnd = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), parent.getCanvas()); + + mousePointSelection.set((int) dSelectionEnd.getX(), (int) dSelectionEnd.getY()); + for (int i = 0; i < active.size(); i++) { + try { + MouseModule j = (MouseModule) active.get(i); + if (j.dragRenderer.isPointSelection()) { + log.finest("mousePointSelected"); + j.mousePointSelected(mousePointSelection); + } + if (j.dragRenderer.isUpdatingDragSelection()) { + // Really it should be the DMM that indicates it wants updates...whoops... + MouseDragEvent de = j.dragRenderer.getMouseDragEvent(parent, dSelectionStart, dSelectionEnd, e.isShiftDown()); + log.finest("mouseRangeSelected"); + j.mouseRangeSelected(de); + } + j.mouseDragged(e); + } catch (RuntimeException except) { + DasExceptionHandler.handle(except); + } + } + refresh(); + } + } + } + + private void performResize(MouseEvent e) { + int dxLeft = parent.getColumn().getDMinimum(); + int dxRight = parent.getColumn().getDMaximum(); + int dyTop = parent.getRow().getDMinimum(); + int dyBottom = parent.getRow().getDMaximum(); + + int dx = e.getX() + xOffset; + int dy = e.getY() + yOffset; + if (mouseMode.resizeRight) { + dxRight = dx; + } else if (mouseMode.resizeLeft) { + dxLeft = dx; + } + if (mouseMode.resizeTop) { + dyTop = dy; + } else if (mouseMode.resizeBottom) { + dyBottom = dy; + } + + parent.getColumn().setDPosition(dxLeft, dxRight); + parent.getRow().setDPosition(dyTop, dyBottom); + + xOffset += dx; + yOffset += dy; + + parent.resize(); + getGlassPane().setDragRenderer(null, null, null); + getGlassPane().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); + } + + public void mouseReleased(MouseEvent e) { + log.finest("mouseReleased"); + if (mouseMode == MouseMode.resize) { + performResize(e); + getGlassPane().setDragRenderer(null, null, null); + parent.getCanvas().paintImmediately(0, 0, parent.getCanvas().getWidth(), parent.getCanvas().getHeight()); + refresh(); + } else if (mouseMode == MouseMode.move) { + performMove(e); + getGlassPane().setDragRenderer(null, null, null); + parent.getCanvas().paintImmediately(0, 0, parent.getCanvas().getWidth(), parent.getCanvas().getHeight()); + refresh(); + + } else { + if (e.getButton() == button) { + if (active != null) { + //clearSelection(graphics); + int x = e.getX(); + int y = e.getY(); + for (int i = 0; i < active.size(); i++) { + MouseModule j = (MouseModule) active.get(i); + try { + MouseDragEvent de = + j.dragRenderer.getMouseDragEvent(parent, dSelectionStart, dSelectionEnd, e.isShiftDown()); + j.mouseRangeSelected(de); + } catch (RuntimeException ex) { + DasExceptionHandler.handle(ex); + } finally { + button = 0; + try { + j.mouseReleased(e); + } catch (RuntimeException ex2) { + DasExceptionHandler.handle(ex2); + } + } + } + if (!pinned) { + active = null; + getGlassPane().setDragRenderer(null, null, null); + parent.getCanvas().paintImmediately(0, 0, parent.getCanvas().getWidth(), parent.getCanvas().getHeight()); + refresh(); + } + } + } + } + + } + + public void removeMouseModule(MouseModule module) { + // not implemented yet + } + + /** + * Getter for property mouseModules. + * @return Value of property mouseModules. + */ + public MouseModule getMouseModule(int i) { + return (MouseModule) modules.get(i); + } + + public MouseModule[] getMouseModules() { + MouseModule[] result = new MouseModule[modules.size()]; + modules.copyInto(result); + return result; + } + + /** + * @deprecated use getPrimaryModuleByLabel + * @return + */ + public String getPrimaryModuleLabel() { + MouseModule primary = getPrimaryModule(); + return primary == null ? "" : primary.getLabel(); + } + + public String getPrimaryModuleByLabel() { + MouseModule primary = getPrimaryModule(); + return primary == null ? "" : primary.getLabel(); + } + + public void setPrimaryModuleByLabel(String label) { + MouseModule mm = getModuleByLabel(label); + if (mm != null) { + setPrimaryModule(mm); + } + } + + /** + * @deprecated use getSecondaryModuleByLabel + * @return + */ + public String getSecondaryModuleLabel() { + MouseModule secondary = getPrimaryModule(); + return secondary == null ? "" : secondary.getLabel(); + } + + public String getSecondaryModuleByLabel() { + MouseModule secondary = getPrimaryModule(); + return secondary == null ? "" : secondary.getLabel(); + } + + public void setSecondaryModuleByLabel(String label) { + MouseModule mm = getModuleByLabel(label); + if (mm != null) { + setSecondaryModule(mm); + } + } + + /** + * //TODO: check this + * Setter for property mouseModules. + * @param mouseModule the new mouseModule to use. + */ + public void setMouseModule(int i, MouseModule mouseModule) { + this.modules.set(i, mouseModule); + } + + public void mouseEntered(MouseEvent e) { + hasFocus = true; + if (e.isShiftDown()) { + parent.repaint(); + } + if (primary != null) { + getGlassPane().setCursor(primary.getCursor()); + } + } + + public void mouseExited(MouseEvent e) { + hasFocus = false; + if (e.isShiftDown()) { + parent.repaint(); + } + getGlassPane().setCursor(Cursor.getDefaultCursor()); + } + + /** + * hack to provide way to get rid of "Dump Data". + * @param label string to search for. + */ + public synchronized void removeMenuItem(String label) { + if (headless) { + return; + } + MenuElement[] ele = primaryPopup.getSubElements(); + int index = -1; + for (int i = 0; i < numInserted; i++) { + if (ele[i] instanceof JMenuItem) { + if (((JMenuItem) ele[i]).getText().contains(label)) { + index = i; + break; + } + } + } + if (index != -1) { + primaryPopup.remove(index); + numInserted--; + } + + } + + public synchronized void addMenuItem(final Component b) { + if (headless) { + return; + } + if (numInserted == 0) { + primaryPopup.insert(new JPopupMenu.Separator(), 0); + } + primaryPopup.insert(b, numInserted); + numInserted++; + + } + + /** + * return a menu with font to match LAF. + * @param label + * @return + */ + public JMenu addMenu(String label) { + JMenu result = new JMenu(label); + //result.setFont(primaryPopup.getFont()); + addMenuItem(result); + return result; + } + + private DasCanvas.GlassPane getGlassPane() { + DasCanvas.GlassPane r = (DasCanvas.GlassPane) ((DasCanvas) parent.getParent()).getGlassPane(); + if (r.isVisible() == false) { + r.setVisible(true); + } + return r; + } + + public MouseModule getModuleByLabel(java.lang.String label) { + MouseModule result = null; + for (int i = 0; i < modules.size(); i++) { + if (label.equals(((MouseModule) modules.get(i)).getLabel())) { + result = (MouseModule) modules.get(i); + } + } + return result; + } + /** + * Draws a faint box around the border when the mouse enters the component, + * to help indicate what's going on. + */ + private boolean hoverHighlite = false; + + public boolean isHoverHighlite() { + return this.hoverHighlite; + } + + public void setHoverHighlite(boolean value) { + this.hoverHighlite = value; + } + + /** + * returns the position of the last mouse press. This is a hack so that + * the mouse position can be obtained to get the context of the press. + * The result point is in the parent's coordinate system. + */ + public Point getMousePressPosition() { + return this.pressPosition; + } + + private void performMove(MouseEvent e) { + Point moveEnd = e.getPoint(); + moveEnd.translate(xOffset, yOffset); + int dx = moveEnd.x - mouseMode.moveStart.x; + int dy = moveEnd.y - mouseMode.moveStart.y; + + this.xOffset += dx; + this.yOffset += dy; + + int min = parent.getColumn().getDMinimum(); + int max = parent.getColumn().getDMaximum(); + parent.getColumn().setDPosition(min + dx, max + dx); + + min = + parent.getRow().getDMinimum(); + max = + parent.getRow().getDMaximum(); + parent.getRow().setDPosition(min + dy, max + dy); + } + + public void mouseWheelMoved(MouseWheelEvent e) { + if (secondary != null) { + secondary.mouseWheelMoved(e); + } + } +} diff --git a/dasCore/src/main/java/org/das2/event/DasSelectionEvent.java b/dasCore/src/main/java/org/das2/event/DasSelectionEvent.java new file mode 100644 index 000000000..038279f91 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasSelectionEvent.java @@ -0,0 +1,148 @@ +/* File: DasSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import java.awt.*; +import java.util.EventObject; + +/** + * + * @author eew + */ +public class DasSelectionEvent extends EventObject +{ + + /** Type-safe enumeration class for selection type contants. */ + public static class Type + { + public static final Type AREA_SELECTION = + new Type("AREA_SELECTION", false); + public static final Type POINT_SELECTION = + new Type("POINT_SELECTION", true); + public static final Type VERTICAL_SLICE_SELECTION = + new Type("VERTICAL_SLICE_SELECTION", true); + public static final Type HORIZONTAL_SLICE_SELECTION = + new Type("HORIZONTAL_SLICE_SELECTION", true); + public static final Type VERTICAL_RANGE_SELECTION = + new Type("VERTICAL_RANGE_SELECTION", false); + public static final Type HORIZONTAL_RANGE_SELECTION = + new Type("HORIZONTAL_RANGE_SELECTION", false); + public static final Type NO_SELECTION = + new Type("NO_SELECTION", false); + + private String type; + private boolean single; + + private Type(String type, boolean single) + { this.type = type; this.single = single; } + public String toString() { return type; } + public boolean isSingleSelection() { return single; } + public boolean equals(Object o) { return this==o; } + } + + protected Point dot; + protected Point mark; + protected boolean isShiftDown; + protected boolean clearSelection; + protected DasSelectionEvent.Type selectionType; + + private Point selectionEnd; + + private DasSelectionEvent.Type selectionMode = DasSelectionEvent.Type.POINT_SELECTION; + + private Point selectionStart; + + /** Creates a new instance of DasSelectionEvent + * + * @param source The source of the event. + * @param selectionType The type of selection. + * @param isShiftDown true if the shift buttons was + * down when the selection was made, false otherwise. + * @param dot The point at which the selection started. + * @param mark The point at which the selection ended, or the point + * of the selection for single selections. + */ + public DasSelectionEvent(Object source, + DasSelectionEvent.Type selectionType, + boolean isShiftDown, + Point dot, Point mark) + { + super(source); + this.selectionType = selectionType; + this.isShiftDown = isShiftDown; + this.dot = new Point(dot); + this.mark = new Point(mark); + this.clearSelection = false; + } + + public Point getDot() + { + return new Point(dot); + } + + public Point getMark() + { + return new Point(mark); + } + + public int getDotX() + { + return dot.x; + } + + public int getDotY() + { + return dot.y; + } + + public int getMarkX() + { + return mark.x; + } + + public int getMarkY() + { + return mark.y; + } + + public boolean isShiftDown() + { + return isShiftDown; + } + + public boolean shouldClearSelection() + { + return clearSelection; + } + + public void clearSelection() + { + clearSelection = true; + } + + public DasSelectionEvent.Type getSelectionType() + { + return selectionType; + } +} diff --git a/dasCore/src/main/java/org/das2/event/DasUpdateEvent.java b/dasCore/src/main/java/org/das2/event/DasUpdateEvent.java new file mode 100644 index 000000000..4c78bbbc8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DasUpdateEvent.java @@ -0,0 +1,39 @@ +/* File: DasUpdateEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author eew + */ +public class DasUpdateEvent extends java.awt.AWTEvent { + + public static final int DAS_UPDATE_EVENT_ID = java.awt.AWTEvent.RESERVED_ID_MAX + 0xAD01; + + /** Creates a new instance of DasUpdateEvent */ + public DasUpdateEvent(org.das2.graph.DasCanvasComponent source) { + super(source, DAS_UPDATE_EVENT_ID); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DataPointSelectionEvent.java b/dasCore/src/main/java/org/das2/event/DataPointSelectionEvent.java new file mode 100644 index 000000000..795f436c8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DataPointSelectionEvent.java @@ -0,0 +1,104 @@ +/* File: DataPointSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; +import org.das2.dataset.DataSet; +import org.das2.datum.Datum; +import java.util.Map; + +/** + * This is the general-purpose "a data point was selected" event. Note that + * auxillary data is supported, such as a keystroke that triggered the event. + * + * The X and Y Datums may be null, so that code may be reused. + * + * @author jbf + */ +public class DataPointSelectionEvent extends DasEvent { + + private Datum x; + private Datum y; + private Map planes; + + public long birthMilli; + + private DataSet ds=null; + + private Object source; + + /** Creates a new instance of DataPointSelectionEvent */ + public DataPointSelectionEvent(Object source, + Datum x, + Datum y, + Map planes ) { + super(source); + this.birthMilli= System.currentTimeMillis(); + this.x= x; + this.y= y; + this.ds= null; + this.planes= planes; + } + + public DataPointSelectionEvent(Object source, + Datum x, + Datum y ) { + this( source, x, y, null ); + } + + public org.das2.datum.Datum getX() { + return x; + } + + public org.das2.datum.Datum getY() { + return y; + } + + public Object getPlane( String plane ) { + return planes==null ? null : planes.get(plane); + } + + public String[] getPlaneIds() { + if ( planes==null ) { + return new String[0]; + } else { + return (String[])planes.keySet().toArray( new String[ planes.keySet().size() ] ); + } + } + + public void set( Datum x, Datum y) { + this.x= x; + this.y= y; + } + + public void setDataSet(org.das2.dataset.DataSet ds) { + this.ds= ds; + } + + public org.das2.dataset.DataSet getDataSet() { + return this.ds; + } + + public String toString() { + return "[DataPointSelectionEvent x:"+x+" y:"+y+"]"; + } +} diff --git a/dasCore/src/main/java/org/das2/event/DataPointSelectionListener.java b/dasCore/src/main/java/org/das2/event/DataPointSelectionListener.java new file mode 100644 index 000000000..c44e047b3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DataPointSelectionListener.java @@ -0,0 +1,32 @@ +/* File: DataPointSelectionListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public abstract interface DataPointSelectionListener extends java.util.EventListener { + public void dataPointSelected(DataPointSelectionEvent e); +} diff --git a/dasCore/src/main/java/org/das2/event/DataPointSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/DataPointSelectorMouseModule.java new file mode 100644 index 000000000..dbd489661 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DataPointSelectorMouseModule.java @@ -0,0 +1,204 @@ +/* + * DataPointSelectorMouseModule.java + * + * Created on November 3, 2005, 2:53 PM + * + * + */ + +package org.das2.event; +import org.das2.dataset.DataSetConsumer; +import org.das2.datum.Datum; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashMap; + +/** + * General purpose mouse module for getting data point selections. The client + * provides the DragRenderer, generally a vertical line, horizontal line or a + * crosshair. + * + * Three properties control when DataPointSelectionEvents are to be fired: + * dragEvents as the mouse is dragged, + * keyEvents when a key is pressed. (The key is the "keyChar" plane of the event) + * releaseEvents when the mouse is released. (false by default) + * + * @see CrossHairRenderer + * @author Jeremy + */ +public class DataPointSelectorMouseModule extends MouseModule { + DasAxis xaxis, yaxis; + DataSetConsumer dataSetConsumer; + javax.swing.event.EventListenerList listenerList = null; + MousePointSelectionEvent lastMousePoint; + + public DataPointSelectorMouseModule( DasPlot parent, + DataSetConsumer consumer, + DragRenderer dragRenderer, String label ) { + super( parent, dragRenderer, label ); + this.xaxis= parent.getXAxis(); + this.yaxis= parent.getYAxis(); + this.dataSetConsumer= consumer; + } + + private DataPointSelectionEvent getDataPointSelectionEvent(MousePointSelectionEvent e) { + Datum x= xaxis.invTransform(e.getX()); + Datum y= yaxis.invTransform(e.getY()); + DataPointSelectionEvent de= new DataPointSelectionEvent( this, x, y ); + return de; + } + + public void mousePointSelected(MousePointSelectionEvent e) { + lastMousePoint= e; + if ( keyEvents ) parent.requestFocus(); + if ( dragEvents ) fireDataPointSelectionListenerDataPointSelected(getDataPointSelectionEvent(e)); + } + + public void keyPressed(KeyEvent e) { + int keyCode= e.getKeyCode(); + + if ( lastMousePoint!=null ) { + if ( keyCode==KeyEvent.VK_LEFT || keyCode==KeyEvent.VK_RIGHT || keyCode==KeyEvent.VK_UP || keyCode==KeyEvent.VK_DOWN ) { + int x=0; + int y=0; + try { + int xOff= parent.getLocationOnScreen().x-parent.getX(); + int yOff= parent.getLocationOnScreen().y-parent.getY(); + final java.awt.Robot robot= new java.awt.Robot(); + switch ( keyCode ) { + case KeyEvent.VK_LEFT: + robot.mouseMove(lastMousePoint.getX()+xOff-1, lastMousePoint.getY()+yOff); + break; + case KeyEvent.VK_RIGHT: + robot.mouseMove(lastMousePoint.getX()+xOff+1, lastMousePoint.getY()+yOff); + break; + case KeyEvent.VK_UP: + robot.mouseMove(lastMousePoint.getX()+xOff, lastMousePoint.getY()+yOff-1); + break; + case KeyEvent.VK_DOWN: + robot.mouseMove(lastMousePoint.getX()+xOff, lastMousePoint.getY()+yOff+1); + break; + } + } catch ( java.awt.AWTException e1 ) { + org.das2.util.DasDie.println(e1.getMessage()); + } + + } else { + + DataPointSelectionEvent dpse= getDataPointSelectionEvent(lastMousePoint); + HashMap planes= new HashMap(); + planes.put( "keyChar", String.valueOf( e.getKeyChar() ) ); + dpse= new DataPointSelectionEvent( this, dpse.getX(), dpse.getY(), planes ); + fireDataPointSelectionListenerDataPointSelected( dpse ); + } + } + } + + /** Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + listenerList.remove(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + protected void fireDataPointSelectionListenerDataPointSelected(DataPointSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener)listeners[i+1]).dataPointSelected(event); + } + } + } + + /** + * Holds value of property dragEvents. + */ + private boolean dragEvents= true; + + /** + * Getter for property dragEvents. + * @return Value of property dragEvents. + */ + public boolean isDragEvents() { + return this.dragEvents; + } + + /** + * Setter for property dragEvents. + * @param dragEvents New value of property dragEvents. + */ + public void setDragEvents(boolean dragEvents) { + this.dragEvents = dragEvents; + } + + /** + * Holds value of property keyEvents. + */ + private boolean keyEvents= true; + + /** + * Getter for property keyEvents. + * @return Value of property keyEvents. + */ + public boolean isKeyEvents() { + + return this.keyEvents; + } + + /** + * Setter for property keyEvents. + * @param keyEvents New value of property keyEvents. + */ + public void setKeyEvents(boolean keyEvents) { + + this.keyEvents = keyEvents; + } + + public void mouseReleased(java.awt.event.MouseEvent e) { + super.mouseReleased(e); + if ( releaseEvents ) { + fireDataPointSelectionListenerDataPointSelected(getDataPointSelectionEvent(lastMousePoint)); + } + } + + /** + * Holds value of property releaseEvents. + */ + private boolean releaseEvents= false; + + /** + * Getter for property releaseEvents. + * @return Value of property releaseEvents. + */ + public boolean isReleaseEvents() { + + return this.releaseEvents; + } + + /** + * Setter for property releaseEvents. + * @param releaseEvents New value of property releaseEvents. + */ + public void setReleaseEvents(boolean releaseEvents) { + + this.releaseEvents = releaseEvents; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DataRangeSelectionEvent.java b/dasCore/src/main/java/org/das2/event/DataRangeSelectionEvent.java new file mode 100755 index 000000000..ee6292e20 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DataRangeSelectionEvent.java @@ -0,0 +1,87 @@ +/* File: DataRangeSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.DataSet; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; + +/** + * + * @author jbf + */ +public class DataRangeSelectionEvent extends DasEvent { + + private DataSet ds=null; + + Datum min; + Datum max; + + Datum reference; // this is where the selection was made at (perpendicular axis) + + public DataRangeSelectionEvent(Object source, Datum min, Datum max) { + super(source); + if (min.gt(max)) { + Datum t=min; + min=max; + max=t; + } + this.min= min; + this.max= max; + reference= null; + } + + public Datum getMinimum() { + return min; + } + + public Datum getMaximum() { + return max; + } + + public DatumRange getDatumRange() { + return new DatumRange( min, max ); + } + + public void setDataSet(DataSet ds) { + this.ds= ds; + } + + public DataSet getDataSet() { + return this.ds; + } + + public void setReference(Datum reference) { + this.reference= reference; + } + + public Datum getReference() { + return this.reference; + } + + public String toString() { + return "[DataRangeSelectionEvent min:"+min+" max:"+max+"]"; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/DataRangeSelectionListener.java b/dasCore/src/main/java/org/das2/event/DataRangeSelectionListener.java new file mode 100644 index 000000000..97547a8cb --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DataRangeSelectionListener.java @@ -0,0 +1,32 @@ +/* File: DataRangeSelectionListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public abstract interface DataRangeSelectionListener extends java.util.EventListener { + public void dataRangeSelected(DataRangeSelectionEvent e); +} diff --git a/dasCore/src/main/java/org/das2/event/DisplayDataMouseModule.java b/dasCore/src/main/java/org/das2/event/DisplayDataMouseModule.java new file mode 100644 index 000000000..f8cbd81fc --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DisplayDataMouseModule.java @@ -0,0 +1,272 @@ +/* + * DisplayDataMouseModule.java + * + * Created on October 23, 2007, 7:08 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.das2.event; + +import org.das2.dataset.ClippedTableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.format.DatumFormatter; +import org.das2.graph.DasPlot; +import org.das2.graph.Renderer; +import org.das2.util.DasExceptionHandler; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.das2.datum.UnitsUtil; + +/** + * + * @author jbf + */ +public class DisplayDataMouseModule extends MouseModule { + + final static String LABEL = "Display Data"; + DasPlot plot; + static JFrame myFrame; + static JPanel myPanel; + static JEditorPane myEdit; + + /** Creates a new instance of DisplayDataMouseModule */ + public DisplayDataMouseModule(DasPlot parent) { + super(parent, new BoxGesturesRenderer(parent), LABEL); + this.plot = parent; + } + + private void maybeCreateFrame() { + if (myFrame == null) { + myFrame = new JFrame(LABEL); + myPanel = new JPanel(); + myPanel.setPreferredSize(new Dimension(300, 300)); + myPanel.setLayout(new BorderLayout()); + myEdit = new JEditorPane(); + myEdit.setFont(Font.decode("fixed-10")); + JScrollPane scrollPane = new JScrollPane(myEdit, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + myPanel.add(scrollPane, BorderLayout.CENTER); + myFrame.getContentPane().add(myPanel); + myFrame.pack(); + } + return; + } + + private String unitsStr(Units u) { + return u == Units.dimensionless ? "" : "(" + u.toString() + ")"; + } + + @Override + public void mouseRangeSelected(MouseDragEvent e0) { + + if ( e0.isGesture() ) return; + + maybeCreateFrame(); + myFrame.setVisible(true); + + MouseBoxEvent e = (MouseBoxEvent) e0; + + DatumRange xrange; + DatumRange yrange; + + if (plot.getXAxis().isFlipped()) { + xrange = new DatumRange(plot.getXAxis().invTransform(e.getXMaximum()), plot.getXAxis().invTransform(e.getXMinimum())); + } else { + xrange = new DatumRange(plot.getXAxis().invTransform(e.getXMinimum()), plot.getXAxis().invTransform(e.getXMaximum())); + } + if (plot.getYAxis().isFlipped()) { + yrange = new DatumRange(plot.getYAxis().invTransform(e.getYMinimum()), plot.getYAxis().invTransform(e.getYMaximum())); + } else { + yrange = new DatumRange(plot.getYAxis().invTransform(e.getYMaximum()), plot.getYAxis().invTransform(e.getYMinimum())); + } + + Renderer[] rends = plot.getRenderers(); + + Document doc = myEdit.getDocument(); + + try { + + AttributeSet attrSet = null; + + doc.remove(0, doc.getLength()); // erase all + + for (int irend = 0; irend < rends.length; irend++) { + + doc.insertString(doc.getLength(), "Renderer #" + irend + "\n", attrSet); + + DataSet ds = rends[irend].getDataSet(); + + if (ds == null) { + doc.insertString(doc.getLength(), "(no dataset)\n", attrSet); + } + + // copy for this renderer. + DatumRange rx1 = xrange; + DatumRange ry1 = yrange; + + if (ds != null) { + if (!rx1.getUnits().isConvertableTo(ds.getXUnits())) { + rx1 = DataSetUtil.xRange(ds); + } + if (!ry1.getUnits().isConvertableTo(ds.getYUnits())) { + ry1 = DataSetUtil.yRange(ds); + } + } + + DataSet outds; + if (ds instanceof TableDataSet) { + TableDataSet tds = (TableDataSet) ds; + + TableDataSet toutds = new ClippedTableDataSet(tds, rx1, ry1); + + StringBuffer buf = new StringBuffer(); + + Units zunits = tds.getZUnits(); + DatumFormatter df; + df = (DatumFormatter) tds.getProperty(DataSet.PROPERTY_FORMATTER); + if (df == null) { + df = tds.getDatum(0, 0).getFormatter(); + } + buf.append("TableDataSet " + toutds.getXLength() + "x" + toutds.getYLength(0) + " " + unitsStr(zunits) + "\n"); + for (int i = 0; i < toutds.getXLength(); i++) { + for (int j = 0; j < toutds.getYLength(0); j++) { + try { + buf.append(df.format(toutds.getDatum(i, j), zunits)); + buf.append(" "); + } catch (IndexOutOfBoundsException ex) { + System.err.println("here"); + } + } + buf.append("\n"); + } + doc.insertString(doc.getLength(), buf.toString(), attrSet); + + } else if (ds instanceof VectorDataSet) { + VectorDataSet vds = (VectorDataSet) ds; + + Units units = vds.getYUnits(); + Units xunits = vds.getXUnits(); + + StringBuffer buf = new StringBuffer(); + DatumFormatter df = vds.getDatum(0).getFormatter(); + DatumFormatter xdf = vds.getXTagDatum(0).getFormatter(); + + String[] planes = vds.getPlaneIds(); + VectorDataSet[] vdss = new VectorDataSet[planes.length]; + for (int j = 0; j < planes.length; j++) { + vdss[j] = (VectorDataSet) vds.getPlanarView(planes[j]); + } + + if (planes.length > 1) { + buf.append("X" + unitsStr(xunits) + "\t"); + buf.append("Y" + unitsStr(units) + "\t"); + for (int j = 0; j < vdss.length; j++) { + if (!planes[j].equals("")) { + buf.append("" + planes[j] + "" + unitsStr(vdss[j].getYUnits()) + "\t"); + } + } + buf.append("\n"); + } + + VectorDataSetBuilder builder = new VectorDataSetBuilder(vds.getXUnits(), vds.getYUnits()); + for (int i = 0; i < vds.getXLength(); i++) { + if (xrange.contains(vds.getXTagDatum(i)) && (!yclip || yrange.contains(vds.getDatum(i)))) { + buf.append(xdf.format(vds.getXTagDatum(i), xunits) + "\t" + df.format(vds.getDatum(i), units)); + for (int j = 0; j < planes.length; j++) { + if (!planes[j].equals("")) { + Units u= vdss[j].getYUnits(); + if ( UnitsUtil.isIntervalMeasurement(u) || UnitsUtil.isRatioMeasurement(u) ) { + buf.append("\t" + df.format(vdss[j].getDatum(i), vdss[j].getYUnits())); + } else { + buf.append("\t" + vdss[j].getDatum(i) ); + } + } + } + + buf.append("\n"); + } + } + doc.insertString(doc.getLength(), buf.toString(), attrSet); + + } + + } + } catch (BadLocationException ex) { + DasExceptionHandler.handle(ex); + } + } + + public String getListLabel() { + return getLabel(); + } + + @Override + public Icon getListIcon() { + ImageIcon icon; + icon = new ImageIcon(this.getClass().getResource("/images/icons/showDataMouseModule.png")); + return icon; + } + + @Override + public String getLabel() { + return LABEL; + } + /** + * Holds value of property yclip. + */ + private boolean yclip = false; + /** + * Utility field used by bound properties. + */ + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + /** + * Getter for property yclip. + * @return Value of property yclip. + */ + public boolean isYclip() { + return this.yclip; + } + + /** + * Setter for property yclip. + * @param yclip New value of property yclip. + */ + public void setYclip(boolean yclip) { + boolean oldYclip = this.yclip; + this.yclip = yclip; + propertyChangeSupport.firePropertyChange("yclip", new Boolean(oldYclip), new Boolean(yclip)); + } +} diff --git a/dasCore/src/main/java/org/das2/event/DragRenderer.java b/dasCore/src/main/java/org/das2/event/DragRenderer.java new file mode 100755 index 000000000..288605144 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DragRenderer.java @@ -0,0 +1,62 @@ +/* File: DragRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import java.awt.*; + +/** A DragRenderer provides the feedback to the human operator + * of what his or her mousing is doing. It applies constraints to the + * drag as well. It promotes the awt mouse events into events + * that represent the operation, implementing for example mouse + * gestures. + * + * @author eew + */ +public interface DragRenderer +{ + /* use this color when drawing ghostly backgrounds for contrast */ + public Color ghostColor= new Color(255,255,255,100); + + /* draws the drag for mousing from p1 to p2, and returns an array of + * Rectangles covering the rendering. If nothing is drawn, then an + * array of length zero should be returned, and nulls are allowed in the + * array. p1 and p2, and g are in the canvas frame of reference. + */ + public abstract Rectangle[] renderDrag(Graphics g, Point p1, Point p2); + + /* clears whatever renderDrag rendered. This is not used by the DasMouseInputAdapter, + * but must still be supported for now. + */ + public abstract void clear(Graphics g); + + /* promotes the drag begin and end into a mouseDragEvent */ + public abstract MouseDragEvent getMouseDragEvent( Object source, Point p1, Point p2, boolean isModified ); + + /* indicates that MM.mousePointSelected() should called as new mouse events come in */ + public boolean isPointSelection(); + + /* range selection events should be fired during drag */ + public boolean isUpdatingDragSelection(); + +} diff --git a/dasCore/src/main/java/org/das2/event/DumpToFileMouseModule.java b/dasCore/src/main/java/org/das2/event/DumpToFileMouseModule.java new file mode 100644 index 000000000..1c41d3d5f --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/DumpToFileMouseModule.java @@ -0,0 +1,115 @@ +/* + * DumpToFileMouseModule.java + * + * Created on December 1, 2004, 10:31 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.VectorUtil; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.ClippedTableDataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.DatumRange; +import org.das2.util.DasExceptionHandler; +import org.das2.system.UserMessageCenter; +import java.io.*; +import java.nio.channels.*; +import javax.swing.*; + +/** + * + * @author Jeremy + */ +public class DumpToFileMouseModule extends MouseModule { + DasAxis xAxis; + DasAxis yAxis; + DataSetConsumer dsConsumer; + + public DumpToFileMouseModule(DasCanvasComponent parent, DataSetConsumer dsConsumer, DasAxis xAxis, DasAxis yAxis) { + super( parent, new BoxRenderer(parent), "Dump to File" ); + if (!xAxis.isHorizontal()) { + throw new IllegalArgumentException("X Axis orientation is not horizontal"); + } + if (yAxis.isHorizontal()) { + throw new IllegalArgumentException("Y Axis orientation is not vertical"); + } + this.xAxis= xAxis; + this.yAxis= yAxis; + this.dsConsumer= dsConsumer; + } + + public static DumpToFileMouseModule create( DasPlot parent, DataSetConsumer dsConsumer ) { + DumpToFileMouseModule result= + new DumpToFileMouseModule(parent, dsConsumer, parent.getXAxis(),parent.getYAxis()); + return result; + } + + public void mouseRangeSelected(MouseDragEvent e0) { + MouseBoxEvent e= (MouseBoxEvent)e0; + + DatumRange xrange; + DatumRange yrange; + + xrange= new DatumRange( xAxis.invTransform(e.getXMinimum()), xAxis.invTransform(e.getXMaximum()) ); + yrange= new DatumRange( yAxis.invTransform(e.getYMaximum()), yAxis.invTransform(e.getYMinimum()) ); + + DataSet ds= dsConsumer.getConsumedDataSet(); + + if ( ds==null ) { + UserMessageCenter.getDefault().notifyUser( this, "This renderer doesn't have a dataset loaded" ); + return; + } + + DataSet outds; + if ( ds instanceof TableDataSet ) { + TableDataSet tds= (TableDataSet)ds; + outds= new ClippedTableDataSet( tds, xrange, yrange ); + + } else { + VectorDataSet vds= (VectorDataSet)ds; + VectorDataSetBuilder builder= new VectorDataSetBuilder(vds.getXUnits(),vds.getYUnits()); + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import java.awt.*; + +/** + * + * @author eew + */ +public class EmptyDragRenderer implements DragRenderer +{ + public static final EmptyDragRenderer renderer = new EmptyDragRenderer(); + private EmptyDragRenderer(){} + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { return new Rectangle[0]; } + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return null; + } + + public void clear(Graphics g) { + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/FrequencyDragRenderer.java b/dasCore/src/main/java/org/das2/event/FrequencyDragRenderer.java new file mode 100644 index 000000000..527bc5990 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/FrequencyDragRenderer.java @@ -0,0 +1,198 @@ +/* + * FrequencyDragRenderer.java + * + * Created on September 6, 2005, 4:55 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasAxis; +import org.das2.datum.Datum; +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.text.*; + +/** + * + * @author Jeremy + * @author eew + */ +public class FrequencyDragRenderer extends LabelDragRenderer implements DragRenderer, KeyListener { + + DasCanvasComponent parent; + + DasAxis axis; + int ncycles; + private boolean horizontal; + double period; + + private PropertyChangeSupport pcs; + + /** Creates a new instance of HorizontalFrequencyDragRenderer */ + public FrequencyDragRenderer( DasCanvasComponent parent, DasAxis axis ) { + super( parent ); + this.parent= parent; + parent.addKeyListener(this); + this.axis= axis; + this.dirtyBounds= new Rectangle(); + ncycles=1; + } + + public Rectangle[] renderDrag(java.awt.Graphics g1, java.awt.Point p1, java.awt.Point p2) { + + Rectangle myDirtyBounds= new Rectangle(); + + //Probably only need to do this once. + horizontal = axis.isHorizontal(); + + Graphics2D g= (Graphics2D) g1; + int x1= p1.x; + int x2 = p2.x; + int y1 = p1.y; + int y2 = p2.y; + if (horizontal&&x2 6 ) + g.drawLine(x1+3, y2, x2-3, y2); + g.drawLine(x1, y2+2, x1, y2-2 ); //serifs + g.drawLine(x2, y2+2, x2, y2-2 ); + + dirtyBounds.setRect(x1-2,y2-5,4,10); + } + else { + if ( width > 6 ) + g.drawLine(x2, y1+3, x2, y2-3); + g.drawLine(x2+2, y1, x2-2, y1 ); //serifs + g.drawLine(x2+2, y2, x2-2, y2 ); + + dirtyBounds.setRect(x2-5,y1-2,10,4); + } + + double rwidth= width / (double) ncycles; + if ( width>3*ncycles ) { + double start = horizontal ? x2 + rwidth : y2 + rwidth; + int limit = horizontal ? axis.getColumn().getWidth() : axis.getRow().getHeight(); + for ( double ii= start; ii0; ii-= rwidth ) { + if(horizontal) { + g.drawLine( (int)ii, y2+2, (int)ii, y2-2 ); + dirtyBounds.add((int)ii-2,y2-5); + } + else { + g.drawLine( x2+2, (int)ii, x2, (int)ii ); + dirtyBounds.add(x2-5,(int)ii-2); + } + } + } + } + + public boolean isPointSelection() { + return false; + } + + public void clear(java.awt.Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isUpdatingDragSelection() { + return false; + } + + public MouseDragEvent getMouseDragEvent(Object source, java.awt.Point p1, java.awt.Point p2, boolean isModified) { + return null; + } + + public void keyPressed(KeyEvent e) { + int keyCode= e.getKeyCode(); + System.out.println(e); + if ( e.getKeyChar()=='1' ) { + ncycles= 1; + } else if ( e.getKeyChar()=='2' ) { + ncycles= 2; + } else if ( e.getKeyChar()=='3' ) { + ncycles= 3; + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + private void fireChange(final double oldPeriod, final double newPeriod) { + if (pcs != null) { + EventQueue.invokeLater(new Runnable() { + public void run() { + pcs.firePropertyChange("period", new Double(oldPeriod), new Double(newPeriod)); + } + }); + } + } + + public void addPropertyChangeListener(String p, PropertyChangeListener l) { + if (pcs == null) { + pcs = new PropertyChangeSupport(this); + } + pcs.addPropertyChangeListener(p, l); + } + + public void removePropertyChangeListener(String p, PropertyChangeListener l) { + if (pcs != null) { + pcs.removePropertyChangeListener(p, l); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/Gesture.java b/dasCore/src/main/java/org/das2/event/Gesture.java new file mode 100755 index 000000000..999d3eac6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/Gesture.java @@ -0,0 +1,46 @@ +/* File: Gesture.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.event; + +public class Gesture { + + public static final Gesture NONE = new Gesture("GestureNONE"); + public static final Gesture BACK = new Gesture("GestureBACK"); + public static final Gesture FORWARD = new Gesture("GestureFORWARD"); + public static final Gesture ZOOMOUT = new Gesture("GestureZOOMOUT"); + public static final Gesture UNDEFINED = new Gesture("GestureUNDEFINED"); + public static final Gesture SCANNEXT = new Gesture("GestureSCANNEXT"); + public static final Gesture SCANPREV = new Gesture("GestureSCANPREV"); + + String name; + + Gesture(String name) { + this.name= name; + } + + public String toString() { + return name; + } + +} + diff --git a/dasCore/src/main/java/org/das2/event/GesturesRenderer.java b/dasCore/src/main/java/org/das2/event/GesturesRenderer.java new file mode 100644 index 000000000..5fb423c6d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/GesturesRenderer.java @@ -0,0 +1,138 @@ +/* File: GesturesRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author jbf + */ +public class GesturesRenderer implements DragRenderer { + + DasCanvasComponent parent; + Rectangle dirtyBounds; + + /** Creates a new instance of GesturesRenderer */ + public GesturesRenderer(DasCanvasComponent parent) { + this.parent= parent; + dirtyBounds= new Rectangle(); + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + Gesture g=null; + double dx= p2.x-p1.x; + double dy= -1* ( p2.y-p1.y ); + double angle= Math.atan2(dy, dx) * 180 / Math.PI; + double radius= Math.sqrt(dy*dy+dx*dx); + int width= ((Component)source).getWidth(); + int xOffset= ((Component)source).getLocation().x; + if ( radius<20 && radius>4) { + if ( (p1.x-xOffset<10) && (p1.x-xOffset)>=0 && (p2.x-xOffset)<0 ) { + g= Gesture.SCANPREV; + } else if ((p1.x-xOffset)>(width-10) && (p1.x-xOffset)=width ) { + g= Gesture.SCANNEXT; + } else if (Math.abs(angle)>160) { + g= Gesture.BACK; + } else if (-1104) { + + Color color0= g.getColor(); + + for (int i=0; i<2; i++) { + if (i==0) { + g.setColor(new Color(255,255,255,100)); + g.setStroke(new BasicStroke( 3.0f, + BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND )); + } else { + g.setColor(color0); + g.setStroke(new BasicStroke()); + } + + if (Math.abs(angle)>160) { + g.drawLine(p1.x,p1.y,p1.x-5,p1.y); + g.drawLine(p1.x-5,p1.y,p1.x-3,p1.y-2); + g.drawLine(p1.x-5,p1.y,p1.x-3,p1.y+2); + } else if (-110 + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author eew + */ +public class HorizontalDragRangeRenderer implements DragRenderer { + + private Rectangle dirtyBounds; + DasCanvasComponent parent; + boolean updating; + + public HorizontalDragRangeRenderer(DasCanvasComponent parent) { + this.parent= parent; + dirtyBounds= new Rectangle(); + updating= true; + } + + public HorizontalDragRangeRenderer(DasCanvasComponent parent, boolean updating ) { + this( parent ); + this.updating= updating; + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + + Graphics2D g= (Graphics2D) g1; + int x2 = p2.x; + int x1= p1.x; + if (x2 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + if ( width > 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + dirtyBounds.setLocation(x1-2,y+3); + dirtyBounds.add(x2+2,y-3); + + return new Rectangle[] { dirtyBounds }; + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + MouseRangeSelectionEvent me= new MouseRangeSelectionEvent(source,p1.x,p2.x,isModified); + return me; + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return updating; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalDragRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/HorizontalDragRangeSelectorMouseModule.java new file mode 100755 index 000000000..1e10d1a2d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalDragRangeSelectorMouseModule.java @@ -0,0 +1,111 @@ +/* File: HorizontalDragRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.datum.Datum; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; + +/** + * + * @author jbf + */ +public class HorizontalDragRangeSelectorMouseModule extends MouseModule { + + DasAxis axis; + + int start; + + /** Utility field used by event firing mechanism. */ + private javax.swing.event.EventListenerList listenerList = null; + + private org.das2.dataset.DataSetConsumer dataSetConsumer; + + public HorizontalDragRangeSelectorMouseModule(DasPlot parent, org.das2.dataset.DataSetConsumer dataSetConsumer, DasAxis axis) { + super( parent, new HorizontalDragRangeRenderer(parent), "Horizontal Drag Range" ); + if (!axis.isHorizontal()) { + throw new IllegalArgumentException("Axis orientation is not horizontal"); + } + this.dataSetConsumer= dataSetConsumer; + this.axis= axis; + + } + + public static HorizontalDragRangeSelectorMouseModule create(DasPlot parent) { + DasAxis axis= parent.getXAxis(); + HorizontalDragRangeSelectorMouseModule result= + new HorizontalDragRangeSelectorMouseModule(parent,parent,parent.getXAxis()); + return result; + } + + public void mouseRangeSelected(MouseDragEvent e0) { + MouseRangeSelectionEvent e= (MouseRangeSelectionEvent)e0; + org.das2.datum.Datum min; + org.das2.datum.Datum max; + + min= axis.invTransform(e.getMinimum()); + max= axis.invTransform(e.getMaximum()); + DataRangeSelectionEvent te= + new DataRangeSelectionEvent(parent,min,max); + fireDataRangeSelectionListenerDataRangeSelected(te); + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if ( dataSetConsumer instanceof TableDataSetConsumer ) { + event.setDataSet(dataSetConsumer.getConsumedDataSet()); + } + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalDragRenderer.java b/dasCore/src/main/java/org/das2/event/HorizontalDragRenderer.java new file mode 100644 index 000000000..69886cf05 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalDragRenderer.java @@ -0,0 +1,36 @@ +/* File: HorizontalDragRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author Owner + */ +public class HorizontalDragRenderer { + + /** Creates a new instance of HorizontalDragRenderer */ + public HorizontalDragRenderer() { + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalFrequencyDragRenderer.java b/dasCore/src/main/java/org/das2/event/HorizontalFrequencyDragRenderer.java new file mode 100755 index 000000000..27299fc64 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalFrequencyDragRenderer.java @@ -0,0 +1,170 @@ +/* + * HorizontalFrequencyDragRenderer.java + * + * Created on February 17, 2004, 6:06 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasAxis; +import org.das2.datum.Datum; +import java.awt.*; +import java.awt.event.*; +import java.text.*; + +/** + * + * @author Jeremy + */ +public class HorizontalFrequencyDragRenderer implements DragRenderer, KeyListener { + + private Rectangle dirtyBounds; + DasCanvasComponent parent; + + DasAxis axis; + int ncycles; + + /** Creates a new instance of HorizontalFrequencyDragRenderer */ + public HorizontalFrequencyDragRenderer( DasCanvasComponent parent, DasAxis axis ) { + this.parent= parent; + parent.addKeyListener(this); + this.axis= axis; + this.dirtyBounds= new Rectangle(); + ncycles=1; + } + + public void renderLabel( java.awt.Graphics g1, java.awt.Point p1, java.awt.Point p2, String report ) { + int dxMax= Integer.MIN_VALUE; + + Graphics2D g= ( Graphics2D ) g1; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); + + FontMetrics fm= parent.getGraphics().getFontMetrics(); + + Color color0= g.getColor(); + g.setColor(new Color(255,255,255,200)); + + Dimension d= parent.getSize(); + + int dx= fm.stringWidth(report)+6; + if (dxMaxd.width-3) && (p2.x-3-dx>0) ) { + xp= p2.x-3-dx; + } + + if (yp<13) { + yp= p2.y+3; + } + + Rectangle bg= new Rectangle(xp,yp,dx,dy); + g.fill(bg); + + g.setColor(new Color(20,20,20)); + g.drawString(report,xp+3,yp+fm.getAscent()); + g.setColor(color0); + + dirtyBounds.add(bg); + } + + public Rectangle[] renderDrag(java.awt.Graphics g1, java.awt.Point p1, java.awt.Point p2) { + + Graphics2D g= (Graphics2D) g1; + int x2 = p2.x; + int x1= p1.x; + if (x2 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + */ + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + internalRender(g, dirtyBounds, x1, x2, y); + + Datum periodDatum= axis.invTransform( x2 ) . subtract( axis.invTransform( x1 ) ); + double period= periodDatum.doubleValue( periodDatum.getUnits() ); + double freq= ncycles / period; + + DecimalFormat df= new DecimalFormat("0.00"); + renderLabel(g1, p1, p2, "T:"+df.format(period)+" f:"+df.format(freq) ); + + return new Rectangle[] { dirtyBounds }; + } + + private void internalRender(Graphics2D g, Rectangle dirtyBounds, int x1, int x2, int y) { + double width = x2 - x1; + if ( width > 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + dirtyBounds.setRect(x1-2,y-5,4,10); + + double rwidth= width / (double) ncycles; + if ( width>3*ncycles ) { + + for ( double ii= x2 + rwidth; ii0; ii-= rwidth ) { + g.drawLine( (int)ii, y+2, (int)ii, y-2 ); + dirtyBounds.add((int)ii-2,y-5); + } + } + } + + public boolean isPointSelection() { + return false; + } + + public void clear(java.awt.Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isUpdatingDragSelection() { + return false; + } + + public MouseDragEvent getMouseDragEvent(Object source, java.awt.Point p1, java.awt.Point p2, boolean isModified) { + return null; + } + + public void keyPressed(KeyEvent e) { + int keyCode= e.getKeyCode(); + System.out.println(e); + if ( e.getKeyChar()=='1' ) { + ncycles= 1; + } else if ( e.getKeyChar()=='2' ) { + ncycles= 2; + } else if ( e.getKeyChar()=='3' ) { + ncycles= 3; + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalRangeGesturesRenderer.java b/dasCore/src/main/java/org/das2/event/HorizontalRangeGesturesRenderer.java new file mode 100644 index 000000000..a9cd4e82b --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalRangeGesturesRenderer.java @@ -0,0 +1,125 @@ +/* File: HorizontalRangeGesturesRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author eew + */ +public class HorizontalRangeGesturesRenderer implements DragRenderer { + + protected int xInitial; + protected int yInitial; + + private Rectangle dirtyBounds; + DasCanvasComponent parent; + GesturesRenderer gr; + + public HorizontalRangeGesturesRenderer(DasCanvasComponent parent) { + this.parent= parent; + dirtyBounds= new Rectangle(); + gr= new GesturesRenderer(parent); + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + + Graphics2D g= (Graphics2D) g1; + + double dx= p2.x-p1.x; + double dy= -1* ( p2.y-p1.y ); + double radius= Math.sqrt(dy*dy+dx*dx); + + if ( radius<20 ) { + gr.renderDrag( g, p1, p2 ); + dirtyBounds.setBounds(gr.getDirtyBounds()); + + } else { + + int x2 = p2.x; + int x1= p1.x; + if (x2 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + if ( width > 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + // see if drawing again fixes WebStart bug // + if ( width > 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + dirtyBounds.setLocation(x1-2,y+3); + dirtyBounds.add(x2+2,y-3); + } + return new Rectangle[] { dirtyBounds }; + } + + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + + double dx= p2.x-p1.x; + double dy= -1* ( p2.y-p1.y ); + double radius= Math.sqrt(dy*dy+dx*dx); + if ( radius<20 ) { + return gr.getMouseDragEvent(source,p1,p2,isModified); + } else { + return new MouseRangeSelectionEvent( source,p1.x,p2.x, isModified ); + } + + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalRangeRenderer.java b/dasCore/src/main/java/org/das2/event/HorizontalRangeRenderer.java new file mode 100644 index 000000000..81933431f --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalRangeRenderer.java @@ -0,0 +1,95 @@ +/* File: HorizontalRangeRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author eew + */ +public class HorizontalRangeRenderer implements DragRenderer { + + protected int xInitial; + protected int yInitial; + + private Rectangle dirtyBounds; + DasCanvasComponent parent; + + public HorizontalRangeRenderer(DasCanvasComponent parent) { + this.parent= parent; + dirtyBounds= new Rectangle(); + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + + Graphics2D g= (Graphics2D) g1; + int x2 = p2.x; + int x1= p1.x; + if (x2 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + if ( width > 6 ) + g.drawLine(x1+3, y, x2-3, y); + g.drawLine(x1, y+2, x1, y-2 ); //serifs + g.drawLine(x2, y+2, x2, y-2 ); + + dirtyBounds.setLocation(x1-2,y+3); + dirtyBounds.add(x2+2,y-3); + return new Rectangle[] { dirtyBounds }; + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return new MouseRangeSelectionEvent(source,p1.x,p2.x,isModified); + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/HorizontalRangeSelectorMouseModule.java new file mode 100755 index 000000000..843be3c38 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalRangeSelectorMouseModule.java @@ -0,0 +1,117 @@ +/* File: HorizontalRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import javax.swing.event.EventListenerList; + + +/** + * + * @author jbf + */ +public class HorizontalRangeSelectorMouseModule extends MouseModule { + + DasAxis axis; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + public HorizontalRangeSelectorMouseModule(DasCanvasComponent parent, DasAxis axis) { + super(parent,new HorizontalRangeGesturesRenderer(parent),"Zoom X"); + if (!axis.isHorizontal()) { + throw new IllegalArgumentException("Axis orientation is not horizontal"); + } + this.axis= axis; + } + + public static HorizontalRangeSelectorMouseModule create(DasPlot parent) { + DasAxis axis= parent.getXAxis(); + HorizontalRangeSelectorMouseModule result= + new HorizontalRangeSelectorMouseModule(parent,parent.getXAxis()); + return result; + } + + public void mouseRangeSelected(MouseDragEvent e0) { + if (!e0.isGesture()) { + Datum min; + Datum max; + MouseRangeSelectionEvent e= (MouseRangeSelectionEvent)e0; + min= axis.invTransform(e.getMinimum()); + max= axis.invTransform(e.getMaximum()); + DatumRange dr= new DatumRange( min, max ); + DatumRange nndr= axis.getTickV().enclosingRange(dr, true); + DataRangeSelectionEvent te= + new DataRangeSelectionEvent(e0.getSource(),nndr.min(),nndr.max()); + fireDataRangeSelectionListenerDataRangeSelected(te); + } else if ( e0.getGesture()==Gesture.BACK ) { + axis.setDataRangePrev(); + } else if ( e0.getGesture()==Gesture.ZOOMOUT ) { + axis.setDataRangeZoomOut(); + } else if ( e0.getGesture()==Gesture.FORWARD ) { + axis.setDataRangeForward(); + } else if ( e0.getGesture()==Gesture.SCANPREV) { + axis.scanPrevious(); + } else if ( e0.getGesture()==Gesture.SCANNEXT) { + axis.scanNext(); + } + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalRangeTorsionMouseModule.java b/dasCore/src/main/java/org/das2/event/HorizontalRangeTorsionMouseModule.java new file mode 100644 index 000000000..bcc177e21 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalRangeTorsionMouseModule.java @@ -0,0 +1,109 @@ +/* + * HorizontalRangeTorsionMouseModule.java + * + * Created on February 12, 2004, 6:00 PM + */ + +package org.das2.event; + +import org.das2.graph.DasAxis; +import org.das2.datum.Units; +import java.awt.event.*; +import javax.swing.*; +/** + * + * @author Jeremy + */ +public class HorizontalRangeTorsionMouseModule extends MouseModule { + + + // this dumb idea was abandoned. + DasAxis axis; + double min; + double max; + Units units; + int x0; + int y0; + long tt; + + double inOutVelocity; + double inOutPosition; + double nextPrevVelocity; + double nextPrevPosition; + + static final double inOutFactor= 1e-6; + static final double nextPrevFactor= 100000; + + boolean mouseButtonPressed; + + /** Creates a new instance of HorizontalRangeTorsionMouseModule */ + public HorizontalRangeTorsionMouseModule( DasAxis axis ) { + //super( axis, new PointSlopeDragRenderer(axis), "AxisDriver" ); + this.axis= axis; + } + + public void mousePressed(java.awt.event.MouseEvent e) { + super.mousePressed(e); + units= axis.getUnits(); + min= axis.getDataMinimum(units); + max= axis.getDataMaximum(units); + x0= e.getX(); + y0= e.getY(); + tt= System.currentTimeMillis(); + inOutPosition=0.; + nextPrevPosition=0.; + mouseButtonPressed= true; + } + + void timerTask() { + if ( mouseButtonPressed ) { + long dt= System.currentTimeMillis() - tt; + inOutPosition+= inOutVelocity * dt; + nextPrevPosition+= nextPrevVelocity * dt; + tt+= dt; + reportPosition(); + startTimer(); + + } + } + + public void startTimer() { + int delay = 100; //milliseconds + ActionListener taskPerformer = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + timerTask(); + } + }; + new Timer(delay, taskPerformer).start(); + } + + public void mouseDragged(java.awt.event.MouseEvent e) { + super.mouseDragged(e); + int dx= e.getX()-x0; + int dy= e.getY()-y0; + long dt= System.currentTimeMillis() - tt; + inOutVelocity= dy * inOutFactor; + nextPrevVelocity= dx * nextPrevFactor; + timerTask(); + } + + private void reportPosition() { + double dd= max - min; + + double offset= nextPrevPosition; + double scale= 1 + inOutPosition; + + double newMin= min + offset ; + double newMax= min + offset + dd * scale; + + //System.out.println( "min: " + newMin + "offset: " + offset + " scale: " + scale ); + //System.out.println( "" + units.createDatum(newMin) + " " + units.createDatum(newMax) ); + axis.setDataRange( units.createDatum(newMin), units.createDatum(newMax) ); + } + + public void mouseReleased(java.awt.event.MouseEvent e) { + super.mouseReleased(e); + mouseButtonPressed= false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalSliceSelectionRenderer.java b/dasCore/src/main/java/org/das2/event/HorizontalSliceSelectionRenderer.java new file mode 100644 index 000000000..130d78dc1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalSliceSelectionRenderer.java @@ -0,0 +1,86 @@ +/* File: HorizontalSliceSelectionRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasColumn; + +import java.awt.*; + + +/** + * + * @author eew + */ +public class HorizontalSliceSelectionRenderer implements DragRenderer { + + DasCanvasComponent parent; + Rectangle dirtyBounds; + + /** Creates a new instance of HorizontalSliceSelectionRenderer */ + public HorizontalSliceSelectionRenderer( DasCanvasComponent parent ) { + this.parent = parent; + dirtyBounds= new Rectangle(); + } + + private void drawCrossHair(Graphics g0, Point p) { + + Graphics g= g0.create(); + + g.setColor(new Color(0,0,0)); + g.setXORMode(Color.white); + + DasColumn col= parent.getColumn(); + g.drawLine( col.getDMinimum(), (int)p.y, col.getDMaximum(), (int)p.y); + + g.dispose(); + + } + + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { + //g.drawLine(0, p2.y, parent.getWidth(), p2.y); + DasColumn col= parent.getColumn(); + + dirtyBounds.setRect(col.getDMinimum(),p2.y,col.getDMaximum(),1); + drawCrossHair(g,p2); + return new Rectangle[] { dirtyBounds }; + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return null; + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/HorizontalSlicerMouseModule.java b/dasCore/src/main/java/org/das2/event/HorizontalSlicerMouseModule.java new file mode 100755 index 000000000..75bb74d2a --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/HorizontalSlicerMouseModule.java @@ -0,0 +1,119 @@ +/* File: HorizontalSlicerMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetConsumer; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.graph.Renderer; +/** + * + * @author jbf + */ +public class HorizontalSlicerMouseModule extends MouseModule { + + private DasAxis xaxis; + private DasAxis yaxis; + + private TableDataSetConsumer dataSetConsumer; + + /** Creates a new instance of VerticalSlicerMouseModule */ + + private DataPointSelectionEvent de; + + /** Utility field used by event firing mechanism. */ + private javax.swing.event.EventListenerList listenerList = null; + + public HorizontalSlicerMouseModule(DasPlot parent, TableDataSetConsumer dataSetConsumer, DasAxis xaxis, DasAxis yaxis) { + this( parent, (DataSetConsumer)dataSetConsumer, xaxis, yaxis ); + } + + protected HorizontalSlicerMouseModule(DasPlot parent, DataSetConsumer dataSetConsumer, DasAxis xaxis, DasAxis yaxis) { + super( parent, new HorizontalSliceSelectionRenderer(parent), "Horizontal Slice" ); + + if (!(dataSetConsumer instanceof TableDataSetConsumer)) { + throw new IllegalArgumentException("dataSetConsumer must be an XTaggedYScanDataSetConsumer"); + } + this.dataSetConsumer= ( TableDataSetConsumer)dataSetConsumer; + this.xaxis= xaxis; + this.yaxis= yaxis; + this.de= new DataPointSelectionEvent(this,null,null); + } + + public static HorizontalSlicerMouseModule create(DasPlot parent) { + DasAxis xaxis= parent.getXAxis(); + DasAxis yaxis= parent.getYAxis(); + return new HorizontalSlicerMouseModule(parent,parent,xaxis,yaxis); + } + + public static HorizontalSlicerMouseModule create(Renderer renderer) + { + DasPlot parent= renderer.getParent(); + DasAxis xaxis= parent.getXAxis(); + DasAxis yaxis= parent.getYAxis(); + return new HorizontalSlicerMouseModule(parent,renderer,xaxis,yaxis); + } + + public void mousePointSelected(MousePointSelectionEvent e) { + org.das2.dataset.DataSet ds= dataSetConsumer.getConsumedDataSet(); + de.setDataSet(ds); + de.set(xaxis.invTransform(e.getX()),yaxis.invTransform(e.getY())); + + fireDataPointSelectionListenerDataPointSelected(de); + } + + /** Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener( DataPointSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener( DataPointSelectionListener listener) { + listenerList.remove( DataPointSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataPointSelectionListenerDataPointSelected(DataPointSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener)listeners[i+1]).dataPointSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/LabelDragRenderer.java b/dasCore/src/main/java/org/das2/event/LabelDragRenderer.java new file mode 100644 index 000000000..be541bf5c --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/LabelDragRenderer.java @@ -0,0 +1,333 @@ +/* + * LabelDragRenderer.java + * + * Created on October 5, 2004, 1:25 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.util.GrannyTextRenderer; +import org.das2.system.DasLogger; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.logging.Logger; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JWindow; +import javax.swing.SwingUtilities; +import org.das2.graph.EventsRenderer; + +/** + * + * @author Jeremy + */ +public class LabelDragRenderer implements DragRenderer { + + String label="Label not set"; + GrannyTextRenderer gtr; + DasCanvasComponent parent; + InfoLabel infoLabel; + + int labelPositionX=1; // 1=right, -1=left + int labelPositionY=-1; // 1=below, -1=above + + /* the implementing class is responsible for setting this */ + Rectangle dirtyBounds; + + Logger logger= DasLogger.getLogger(DasLogger.GUI_LOG); + + int maxLabelWidth; + + public void clear(Graphics g) { + if ( dirtyBounds!=null ) parent.paintImmediately(dirtyBounds); + dirtyBounds= null; + } + + public LabelDragRenderer( DasCanvasComponent parent ) { + this.parent= parent; + this.dirtyBounds= new Rectangle(); + gtr= new GrannyTextRenderer(); + } + + /** + * This method is called by the DMIA on mouse release. We use this to infer the mouse release + * and hide the Window. Note this assumes isUpdatingDragSelection is false! + * TODO: DMIA should call clear so this is more explicit. + */ + public MouseDragEvent getMouseDragEvent(Object source, java.awt.Point p1, java.awt.Point p2, boolean isModified) { + maxLabelWidth= 0; + if ( tooltip ) { + if ( infoLabel!=null ) infoLabel.hide(); + } + return null; + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return false; + } + + public void setLabel( String s ) { + this.label= s; + } + + private class InfoLabel { + JWindow window; + JPanel label; + GrannyTextRenderer gtr; + + JPanel containedPanel; + JComponent glassPane; + + boolean contained= true; + + void init() { + Window root= (Window)SwingUtilities.getRoot( parent ); + window= new JWindow( root ); + label= new JPanel() { + public void paintComponent( Graphics g ) { + g.clearRect(0,0, getWidth(), getHeight() ); + gtr.draw( g, 0, (int)gtr.getAscent() ); + } + }; + label.setOpaque( true ); + label.setPreferredSize( new Dimension( 300,20 ) ); + window.getContentPane().add( label ); + window.pack(); + gtr= new GrannyTextRenderer(); + + glassPane= (JComponent)parent.getCanvas().getGlassPane(); + containedPanel= new JPanel() { + public void paintComponent( Graphics g ) { + g.clearRect(0,0, getWidth(), getHeight() ); + gtr.draw( g, 0, (int)gtr.getAscent() ); + } + }; + containedPanel.setVisible(false); + glassPane.add(containedPanel); + contained= true; + + } + + void setText( String text, Point p ) { + if ( window==null ) init(); + if ( text!=null ) { + gtr.setString( containedPanel.getFont(), text ); + Rectangle rect= gtr.getBounds(); + + int posx= p.x + labelPositionX * 3 + Math.min( labelPositionX, 0 ) * rect.width; + int posy= p.y + labelPositionY * 3 + Math.min( labelPositionY, 0 ) * rect.height; + + Rectangle bounds= gtr.getBounds(); + + Point p2= new Point( posx, posy ); + SwingUtilities.convertPointFromScreen( p2, glassPane ); + + bounds.translate( p2.x, p2.y ); + + contained= glassPane.getBounds().contains( bounds ); + + if ( contained ) { + + containedPanel.setSize( new Dimension( rect.width, rect.height ) ); + containedPanel.setLocation( p2.x, p2.y ); + window.setVisible(false); + containedPanel.setVisible(true); + containedPanel.repaint(); + + } else { + + gtr.setString(label.getFont(),text); + rect= gtr.getBounds(); + window.setSize( new Dimension( rect.width, rect.height ) ); + + posx= p.x + labelPositionX * 3 + Math.min( labelPositionX, 0 ) * rect.width; + posy= p.y + labelPositionY * 3 + Math.min( labelPositionY, 0 ) * rect.height; + + containedPanel.setVisible(false); + window.setLocation( posx, posy ); + window.setVisible(true); + window.repaint(); + } + } else { + hide(); + } + } + + void hide() { + if ( window==null ) init(); + if ( contained ) { + containedPanel.setVisible(false); + } else { + window.setVisible(false); + } + } + + } + + private Rectangle paintLabel( Graphics g1, java.awt.Point p2 ) { + + if ( label==null ) return null; + + Graphics2D g= (Graphics2D)g1; + g.setClip(null); + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + + Dimension d= parent.getCanvas().getSize(); + + gtr.setString( g1, label); + + int dx= (int)gtr.getWidth()+6; + + int dy= (int)gtr.getHeight(); + + if (maxLabelWidth d.width ) && (p2.x-3-dx>0) ) { + labelPositionX= -1; + } else { + labelPositionX= 1; + } + + int xp; + if ( labelPositionX==1 ) { + xp= p2.x+3; + } else { + xp= p2.x-3-dx; + } + + int yp; + if ( p2.y-3-dy < 13) { + labelPositionY= -1; + } else { + labelPositionY= 1; + } + + if ( labelPositionY==1 ) { + yp= p2.y-3-dy; + } else { + yp= p2.y+3; + } + + dirtyBounds= new Rectangle();; + + Color color0= g.getColor(); + + // draw the translucent background + g.setColor(new Color(255,255,255,200)); + dirtyBounds.setRect(xp,yp,dx,dy); + g.fill(dirtyBounds); + + // draw the label + g.setColor(new Color(20,20,20)); + gtr.draw( g, xp+3, (float)(yp+gtr.getAscent()) ); + + g.setColor(color0); + + return dirtyBounds; + } + + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { + logger.finest("renderDrag "+p2); + Rectangle[] result; + if ( tooltip ) { + if ( infoLabel==null ) infoLabel= new InfoLabel(); + Point p= (Point)p2.clone(); + SwingUtilities.convertPointToScreen( p, parent.getCanvas() ); + infoLabel.setText( label, p ); + result= new Rectangle[0]; + } else { + if ( label==null ) { + result= new Rectangle[0]; + } else { + Rectangle r= paintLabel( g, p2 ); + result= new Rectangle[] { r }; + } + } + return result; + } + + /** + * added to more conveniently keep track of dirty bounds when subclassing. + */ + ArrayList newDirtyBounds; + + protected void resetDirtyBounds( ) { + newDirtyBounds= new ArrayList(); + } + + protected void addDirtyBounds( Rectangle[] dirty ) { + if ( dirty!=null && dirty.length>0 ) newDirtyBounds.addAll( Arrays.asList( dirty ) ); + } + + protected void addDirtyBounds( Rectangle dirty ) { + if ( dirty!=null ) newDirtyBounds.add( dirty ); + } + + protected Rectangle[] getDirtyBounds() { + try { + return (Rectangle[]) newDirtyBounds.toArray( new Rectangle[newDirtyBounds.size()] ); + } catch ( RuntimeException e ) { + throw e; + } + } + + /* public void keyPressed(KeyEvent e) { + int keyCode= e.getKeyCode(); + + if ( keyCode==KeyEvent.VK_LEFT || keyCode==KeyEvent.VK_RIGHT || keyCode==KeyEvent.VK_UP || keyCode==KeyEvent.VK_DOWN ) { + int x=0; + int y=0; + try { + int xOff= parent.getLocationOnScreen().x-parent.getX(); + int yOff= parent.getLocationOnScreen().y-parent.getY(); + final java.awt.Robot robot= new java.awt.Robot(); + switch ( keyCode ) { + case KeyEvent.VK_LEFT: + robot.mouseMove(lastMousePoint.getX()+xOff-1, lastMousePoint.getY()+yOff); + break; + case KeyEvent.VK_RIGHT: + robot.mouseMove(lastMousePoint.getX()+xOff+1, lastMousePoint.getY()+yOff); + break; + case KeyEvent.VK_UP: + robot.mouseMove(lastMousePoint.getX()+xOff, lastMousePoint.getY()+yOff-1); + break; + case KeyEvent.VK_DOWN: + robot.mouseMove(lastMousePoint.getX()+xOff, lastMousePoint.getY()+yOff+1); + break; + } + } catch ( java.awt.AWTException e1 ) { + org.das2.util.DasDie.println(e1.getMessage()); + } + + } else { + + DataPointSelectionEvent dpse= getDataPointSelectionEvent(lastMousePoint); + HashMap planes= new HashMap(); + planes.put( "keyChar", String.valueOf( e.getKeyChar() ) ); + dpse= new DataPointSelectionEvent( this, dpse.getX(), dpse.getY(), planes ); + fireDataPointSelectionListenerDataPointSelected( dpse ); + } + } + }*/ + + boolean tooltip= false; + public boolean isTooltip() { + return tooltip; + } + + public void setTooltip( boolean tooltip ) { + this.tooltip= tooltip; + if ( tooltip ) { + labelPositionX= 1; + labelPositionY= -1; + } + } +} diff --git a/dasCore/src/main/java/org/das2/event/LengthDragRenderer.java b/dasCore/src/main/java/org/das2/event/LengthDragRenderer.java new file mode 100644 index 000000000..bbdfc614f --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/LengthDragRenderer.java @@ -0,0 +1,196 @@ +/* + * PointSlopeDragRenderer.java + * + * Created on February 19, 2004, 11:31 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.DatumUtil; +import org.das2.datum.UnitsUtil; +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.text.*; + +/** + * + * @author Owner + */ +public class LengthDragRenderer extends LabelDragRenderer { + + private DasAxis xaxis, yaxis; + private DasPlot plot; + + NumberFormat nf; + + /** Creates a new instance of PointSlopeDragRenderer */ + public LengthDragRenderer(DasCanvasComponent parent, DasAxis xaxis, DasAxis yaxis) { + super( parent ); + this.plot= (DasPlot)parent; + this.xaxis= xaxis; + this.yaxis= yaxis; + } + + // kludge to handle the fact that datums are not always displayed with their units!!! + private String datumString( Datum d ) { + String result= d.toString(); + /*if ( d.getUnits()!= Units.dimensionless && !d.getUnits().isConvertableTo(Units.us2000) ) { + result+= " "+d.getUnits(); + }*/ + return result; + } + + public Rectangle[] renderDrag(java.awt.Graphics g1, java.awt.Point p1, java.awt.Point p2) { + Graphics2D g= ( Graphics2D ) g1; + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + double atan= Math.atan2( p2.y-p1.y, p2.x-p1.x ); + + Line2D line= new Line2D.Double( p1.x + (int)(4.0 * Math.cos(atan)), (int)(p1.y + 4.0*Math.sin(atan)), p2.x, p2.y ); + g.draw( line ); + g.draw( new Ellipse2D.Double( p1.x-4, p1.y-4, 8, 8 ) ); + + Rectangle myDirtyBounds= new Rectangle(); + + myDirtyBounds.setRect( p1.x-3, p1.y-3, 7, 7 ); + myDirtyBounds.add(p2.x-2,p2.y-2); + myDirtyBounds.add(p2.x+2,p2.y+2); + + DasAxis xa= xaxis == null ? plot.getXAxis() : xaxis; + DasAxis ya= yaxis == null ? plot.getYAxis() : yaxis; + + if ( !p1.equals(p2) ) { + Datum x1= xa.invTransform(p2.x); + Datum x0= xa.invTransform(p1.x); + Datum run= x1.subtract(x0); + run= DatumUtil.asOrderOneUnits(run); + String runString; + if ( x1.getUnits()==run.getUnits() ) { + runString= x1.getFormatter().format(run); + } else { + runString= datumString(run); + } + + Datum y1= ya.invTransform(p2.y); + Datum y0= ya.invTransform(p1.y); + Datum rise= y1.subtract(y0); + + String riseString; + riseString= datumString(rise); + + String radString; + if ( rise.getUnits().isConvertableTo(run.getUnits()) ) { + Units u= run.getUnits(); + double rised= rise.doubleValue(u); + double rund= run.doubleValue(u); + double rad= Math.sqrt( rised * rised + rund * rund ); + double srised= rise.getResolution(u); + double srund= run.getResolution(u); + double res= rad * Math.sqrt( + Math.pow( srised / Math.max( Math.abs(rised), srised ), 2 ) + + Math.pow( srund / Math.max( Math.abs( rund ), srund ), 2 ) ); + Datum radDatum= Datum.create( rad, u, res/100. ); + + radString= "!cR:" + radDatum; + } else { + radString= ""; + } + + String label= "\u0394x: " + runString + " \u0394y: " + riseString + radString ; + + if ( showSlope ) { + label += "!c m: "+ UnitsUtil.divideToString( rise, run ); + } + + if ( showFit ) { + // show y= m * ( x - x0 ) + y0 + Datum slope= rise.divide(run); + + String fit; + if ( yaxis.isLog() && xaxis.isLog() ) { + double ycycles= Math.log10( y1.divide(y0).doubleValue(Units.dimensionless) ); + double xcycles= Math.log10( x1.divide(x0).doubleValue(Units.dimensionless) ); + NumberFormat nf= new DecimalFormat("0.00"); + String sslope= nf.format( ycycles / xcycles ); + fit = "y= ( x/" +x1 +" )!A"+sslope + "!n * " + y1 ; + } else if ( yaxis.isLog() && !xaxis.isLog() ) { + NumberFormat nf= new DecimalFormat("0.00"); + Units u= run.getUnits(); + double drise= Math.log10(y1.divide(y0).doubleValue(Units.dimensionless) ); + double drun= x1.subtract(x0).doubleValue(u); + String sslope= nf.format( drise/drun ); + String su; + if ( u.isConvertableTo(Units.seconds) ) { + su= UnitsUtil.divideToString( Units.dimensionless.createDatum(drise), run ); + } else if ( u==Units.dimensionless ) { + su= sslope; + } else { + su= sslope + "/("+u+")"; + } + + fit= "!Cy="+ "10!A( x-("+x1+") )*"+su+"!n * " + y1; + + } else if ( !yaxis.isLog() && xaxis.isLog() ) { + fit = "n/a"; + } else { + fit= "y="+ slope + " * ( x - ("+x1+") ) + "+ y1; + } + label+= "!c" + fit; + } + + + setLabel( label ); + } else { + setLabel( "" ); + } + super.renderDrag( g, p1, p2 ); + return new Rectangle[] { dirtyBounds, myDirtyBounds }; + } + + /** + * Holds value of property showSlope. + */ + private boolean showSlope= false; + + /** + * Getter for property showSlope. + * @return Value of property showSlope. + */ + public boolean isShowSlope() { + return this.showSlope; + } + + /** + * Setter for property showSlope. + * @param showSlope New value of property showSlope. + */ + public void setShowSlope(boolean showSlope) { + this.showSlope = showSlope; + } + + protected boolean showFit = false; + + /** + * Get the value of showFit + * + * @return the value of showFit + */ + public boolean isShowFit() { + return showFit; + } + + /** + * Set the value of showFit + * + * @param showFit new value of showFit + */ + public void setShowFit(boolean showFit) { + this.showFit = showFit; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/MouseBoxEvent.java b/dasCore/src/main/java/org/das2/event/MouseBoxEvent.java new file mode 100644 index 000000000..92784bf58 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MouseBoxEvent.java @@ -0,0 +1,64 @@ +/* File: MouseBoxEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import java.awt.*; + +public class MouseBoxEvent extends MouseDragEvent { + + private Point pressPoint; + private Point releasePoint; + + /** Creates a new instance of MouseBoxEvent */ + public MouseBoxEvent(Object source, Point pressPoint, Point releasePoint, boolean isModified) { + super(source); + this.pressPoint= pressPoint; + this.releasePoint= releasePoint; + } + + public Point getPressPoint() { + return pressPoint; + } + + public Point getPoint() { + return releasePoint; + } + + public int getXMinimum() { + return ( pressPoint.x < releasePoint.x ) ? pressPoint.x : releasePoint.x ; + } + + public int getXMaximum() { + return ( pressPoint.x > releasePoint.x ) ? pressPoint.x : releasePoint.x ; + } + + public int getYMinimum() { + return ( pressPoint.y < releasePoint.y ) ? pressPoint.y : releasePoint.y ; + } + + public int getYMaximum() { + return ( pressPoint.y > releasePoint.y ) ? pressPoint.y : releasePoint.y ; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/MouseDragEvent.java b/dasCore/src/main/java/org/das2/event/MouseDragEvent.java new file mode 100644 index 000000000..30ccf9d2d --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MouseDragEvent.java @@ -0,0 +1,55 @@ +/* File: MouseDragEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public class MouseDragEvent extends DasMouseEvent { + + Gesture gesture; + + public MouseDragEvent(Object source) { + super(source); + gesture= Gesture.NONE; + } + + public MouseDragEvent(Object source, Gesture gesture ) { + super(source); + this.gesture= gesture; + } + + public boolean isGesture() { + return gesture!=Gesture.NONE; + } + + public Gesture getGesture() { + return gesture; + } + + public String toString() { + return isGesture() ? gesture.toString() : "MouseDragEvent source: "+source; + } +} diff --git a/dasCore/src/main/java/org/das2/event/MouseModule.java b/dasCore/src/main/java/org/das2/event/MouseModule.java new file mode 100755 index 000000000..f93ed5587 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MouseModule.java @@ -0,0 +1,154 @@ +/* File: MouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.Editable; +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; + +/** A MouseModule is a pluggable unit that promotes simple + * mouse events into human events or actions that are useful + * for science analysis. Each component has a mouseInputAdapter + * that manages a set of mouseModules, one is active at any + * given time. + * + * The DasMouseInputAdapter will delegate mouse events, key events, and mouse wheel + * events to the active + * @author jbf + */ +public class MouseModule implements Editable, Displayable, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener { + + //protected DasCanvasComponent parent; + protected DragRenderer dragRenderer; + private String label; + protected DasCanvasComponent parent; + + protected MouseModule() { + label= "unlabelled MM"; + dragRenderer= EmptyDragRenderer.renderer; + } + + public MouseModule(DasCanvasComponent parent) { + this( parent, EmptyDragRenderer.renderer, "unlabelled MM" ); + setLabel(this.getClass().getName()); + } + + public MouseModule(DasCanvasComponent parent, DragRenderer dragRenderer, String label) { + this.parent= parent; + this.dragRenderer= dragRenderer; + this.label= label; + } + + /** + * returns a string that identifies the module + */ + public String getLabel() { + return label; + } + + /** + * No longer used + * @deprecated No longer supported + */ + public Vector getHotSpots() { + return null; + } + + /** return a cursor that indicates the selected module. */ + public Cursor getCursor() { + return new Cursor(Cursor.DEFAULT_CURSOR); + } + + public void hotSpotPressed(Shape s) { + } + + public DragRenderer getDragRenderer() { + return dragRenderer; + } + + /** Action to take when a mouse range (click, drag, release) has been selected. */ + public void mouseRangeSelected(MouseDragEvent e) { + } + + /** Action to take when a point (click or drag) is selected. */ + public void mousePointSelected(MousePointSelectionEvent e) { + } + + public void setLabel(java.lang.String label) { + this.label= label; + } + + public javax.swing.Icon getListIcon() { + return null; + } + + public String getListLabel() { + return getLabel(); + } + + public void keyPressed(KeyEvent keyEvent) { + } + + public void keyReleased(KeyEvent keyEvent) { + } + + public void keyTyped(KeyEvent keyEvent) { + } + + public void mouseReleased(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseDragged(MouseEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mouseMoved(MouseEvent e) { + } + + public void mouseWheelMoved(MouseWheelEvent e) { + } + + /** + * this should only be called from the mouse module constructor. (Until + * it is verified that it is okay to call it elsewhere.) + */ + protected void setDragRenderer(DragRenderer dragRenderer) { + this.dragRenderer = dragRenderer; + } +} diff --git a/dasCore/src/main/java/org/das2/event/MousePointSelectionEvent.java b/dasCore/src/main/java/org/das2/event/MousePointSelectionEvent.java new file mode 100644 index 000000000..2f81ab4a9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MousePointSelectionEvent.java @@ -0,0 +1,57 @@ +/* File: MousePointSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public class MousePointSelectionEvent extends DasMouseEvent { + int x; + int y; + /** Creates a new instance of MousePointSelectionEvent */ + + public MousePointSelectionEvent(Object source, int x, int y ) { + super(source); + this.x= x; + this.y= y; + } + + public void set(int x, int y) { + this.x= x; + this.y= y; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public String toString() { + return "[MousePointSelectionEvent x:"+x+" y:"+y+"]"; + } +} diff --git a/dasCore/src/main/java/org/das2/event/MouseRangeGestureSelectionEvent.java b/dasCore/src/main/java/org/das2/event/MouseRangeGestureSelectionEvent.java new file mode 100644 index 000000000..a20abccbf --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MouseRangeGestureSelectionEvent.java @@ -0,0 +1,60 @@ +/* File: MouseRangeGestureSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author Owner + */ +public class MouseRangeGestureSelectionEvent extends MouseRangeSelectionEvent { + + private Gesture gesture; + + /** Creates a new instance of MouseRangeGestureSelectionEvent */ + public MouseRangeGestureSelectionEvent(Object source, int min, int max, Gesture g) { + super( source, min, max, false ); + this.gesture= g; + } + + public boolean isGesture() { + return gesture!=Gesture.NONE; + } + + public boolean isBack() { + return gesture==Gesture.BACK; + } + + public boolean isForward() { + return gesture==Gesture.FORWARD; + } + + public boolean isZoomOut() { + return gesture==Gesture.ZOOMOUT; + } + + public Gesture getGesture() { + return gesture; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/MouseRangeSelectionEvent.java b/dasCore/src/main/java/org/das2/event/MouseRangeSelectionEvent.java new file mode 100644 index 000000000..c47638293 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MouseRangeSelectionEvent.java @@ -0,0 +1,62 @@ +/* File: MouseRangeSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + + + +/** + * + * @author eew + */ +public class MouseRangeSelectionEvent extends MouseDragEvent +{ + + private int min, max; + private boolean isModified; + + /** Creates a new instance of DasDevicePositionEvent */ + public MouseRangeSelectionEvent(Object source, int min, int max, boolean isModified) { + super(source); + if (min>max) { + int t= min; + min= max; + max= t; + } + + this.min= min; + this.max= max; + this.isModified= isModified; + } + + public int getMinimum() { + return min; + } + + public int getMaximum() { + return max; + } + public boolean isModified() { + return isModified; + } +} diff --git a/dasCore/src/main/java/org/das2/event/MoveComponentMouseModule.java b/dasCore/src/main/java/org/das2/event/MoveComponentMouseModule.java new file mode 100644 index 000000000..0e1bcd445 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/MoveComponentMouseModule.java @@ -0,0 +1,147 @@ +/* + * MoveComponentMouseModule.java + * + * Created on April 21, 2007, 7:10 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasColumn; +import org.das2.graph.DasRow; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.image.BufferedImage; + +/** + * + * @author jbf + */ +public class MoveComponentMouseModule extends MouseModule { + + Point p0; + + static class MoveRenderer implements DragRenderer { + + DasCanvasComponent c; + BufferedImage i; + + MoveRenderer(DasCanvasComponent c) { + this.c = c; + } + + private void refreshImage() { // this doesn't work. + Rectangle bounds = c.getActiveRegion().getBounds(); + i = new BufferedImage( bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB ); + Graphics g= i.getGraphics(); + g.translate( c.getX(), c.getY() ); + c.paint( g ); + + } + + private Point center(Shape s) { + long avgX = 0, avgY = 0; + double[] coords = new double[6]; + long count = 0; + for (PathIterator i = s.getPathIterator(null); !i.isDone(); i.next()) { + int type = i.currentSegment(coords); + if (type == PathIterator.SEG_LINETO) { + avgX += coords[0]; + avgY += coords[1]; + count++; + } + } + + avgX /= count; + avgY /= count; + + return new Point((int) avgX, (int) avgY); + } + + private Shape enlarge(Shape s, double scale) { + Point center = center(s); + AffineTransform at = new AffineTransform(); + + at.translate( +center.x, +center.y); + at.scale(scale, scale); + GeneralPath gp = new GeneralPath(s); + gp.transform(at); + at = new AffineTransform(); + at.translate(-center.x*scale, -center.y*scale); + gp.transform(at); + + return gp; + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + Rectangle bounds = c.getActiveRegion().getBounds(); + bounds.translate(p2.x - p1.x, p2.y - p1.y); + Graphics2D g = (Graphics2D) g1; + + //g.drawImage( i, p2.x - p1.x, p2.y-p1.y, c ); + + g.setClip(null); + g.setColor(Color.BLACK); + g.draw(bounds); + Point p = center(bounds); + //g.drawOval(p.x - 2, p.y - 2, 5, 5); + //g.draw(enlarge(bounds.getBounds(), 1.2)); + //System.err.println("draw " + bounds.getBounds()); + return new Rectangle[] { enlarge(bounds.getBounds(), 1.2 ).getBounds()}; + } + + public void clear(Graphics g) { + } + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + return null; + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return true; + } + } + + /** Creates a new instance of MoveComponentMouseModule */ + public MoveComponentMouseModule(DasCanvasComponent parent) { + super(parent, new MoveRenderer(parent), "Move Component"); + } + + @Override + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + Point p = e.getPoint(); + int dx = p.x - p0.x; + int dy = p.y - p0.y; + + DasRow row = this.parent.getRow(); + row.setDPosition(row.getDMinimum() + dy, row.getDMaximum() + dy); + + DasColumn col = this.parent.getColumn(); + col.setDPosition(col.getDMinimum() + dx, col.getDMaximum() + dx); + + p0 = null; + } + + @Override + public void mousePressed(MouseEvent e) { + super.mousePressed(e); + ((MoveRenderer)this.dragRenderer).refreshImage(); + p0 = e.getPoint(); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/PeakDetectorMouseModule.java b/dasCore/src/main/java/org/das2/event/PeakDetectorMouseModule.java new file mode 100644 index 000000000..24caf880c --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/PeakDetectorMouseModule.java @@ -0,0 +1,599 @@ +/* + * Cutoff2MouseModule.java + * + * Created on November 10, 2005, 1:41 PM + * + * + */ + +package org.das2.event; + +import org.das2.DasException; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.ClippedTableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.DataSetUpdateEvent; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.NoDataInIntervalException; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.SingleVectorDataSet; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.test.PolynomialDataSetDescriptor; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import org.das2.graph.Psym; +import org.das2.graph.PsymConnector; +import org.das2.graph.SymColor; +import org.das2.graph.SymbolLineRenderer; +import org.das2.math.QuadFitUtil; +import org.das2.system.DasLogger; +import org.das2.util.DasMath; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.util.Arrays; +import java.util.HashMap; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.Box; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; + +/** + * + * @author Jeremy + */ +public class PeakDetectorMouseModule extends BoxSelectorMouseModule { + + DasAxis xaxis, yaxis; + DataSetConsumer dataSetConsumer; + DatumRange xrange; + DatumRange yrange; + String lastComment; + PeakSlicer peakSlicer; + + + static Logger logger= DasLogger.getLogger( DasLogger.GUI_LOG ); + + public PeakDetectorMouseModule( DasPlot parent, DataSetConsumer consumer ) { + super( parent, parent.getXAxis(), parent.getYAxis(), consumer, new BoxRenderer(parent,true), "Peak Detector" ); + this.dataSetConsumer= consumer; + } + + @Override + protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) { + + final DatumRange xrange0= xrange; + final DatumRange yrange0= yrange; + + xrange= event.getXRange(); + yrange= event.getYRange(); + if ( event.getPlane("keyChar")!=null ) { + lastComment= (String)event.getPlane("keyChar"); + } else { + lastComment= null; + } + + Runnable run= new Runnable() { + public void run() { + try { + recalculate(); + } catch ( RuntimeException ex ) { + xrange= xrange0; + yrange= yrange0; + throw ex; + } + } + }; + new Thread(run).start(); + } + + /** + * return RebinDescriptor that is on descrete, repeatable boundaries. + * get us2000, divide by resolution, truncate, multiply by resolution. + */ + private RebinDescriptor getRebinDescriptor( DatumRange range ) { + double res= xResolution.doubleValue(Units.microseconds); + double min= range.min().doubleValue(Units.us2000); + min= Math.floor( min / res ); + double max= range.max().doubleValue(Units.us2000); + max= Math.ceil( max / res ); + int nbin= (int)(max-min); + + RebinDescriptor ddx= new RebinDescriptor( min*res, max*res, Units.us2000, nbin, false ); + return ddx; + } + + private VectorDataSet toDb( VectorDataSet ds, Datum reference ) { + Units refUnits= reference.getUnits(); + double refValue= reference.doubleValue(refUnits); + Units yunits= Units.dB; + Units xunits= ds.getXUnits(); + VectorDataSetBuilder builder= new VectorDataSetBuilder( xunits, yunits ); + + for ( int i=0; i=xtag>max, or -1 if no peak is found. + */ + private static int peakIndex(VectorDataSet ds, Datum min, Datum max) { + Datum yMax = ds.getYUnits().createDatum(Double.NEGATIVE_INFINITY); + int iMax = -1; + int i0 = -1; //The first x index in range + int i1 = -1; //The last x index in range + for (int i = 0; i < ds.getXLength(); i++) { + Datum y = ds.getDatum(i); + Datum x = ds.getXTagDatum(i); + if ( x.ge(min) && x.lt(max)) { + if (i0 == -1) i0 = i; + i1 = i; + if (y.gt(yMax)) { + yMax = y; + iMax = i; + } + } + } + + // We don't want to record this value if it is the first or + // last within range, since this inidicates that the value + // probably isn't really a maximum. + if (iMax == i0 || iMax == i1) { + iMax = -1; + } + return iMax; + } + + private static int[] findFive(VectorDataSet ds, int peakIndex) { + int[] indices, result; + int nIndex; + double yLowPrev, yHighPrev; + Units yUnits = ds.getYUnits(); + + indices = new int[7]; + indices[0] = peakIndex; + nIndex = 1; + + yHighPrev = yLowPrev = ds.getDouble(peakIndex, yUnits); + + for (int i = 1; i <= 3 && nIndex < 5; i++) { + double yLow = (peakIndex - i) >= 0 ? ds.getDouble(peakIndex - i, yUnits) : Double.POSITIVE_INFINITY; + double yHigh = (peakIndex + i) < ds.getXLength() ? ds.getDouble(peakIndex + i, yUnits) : Double.POSITIVE_INFINITY; + if (yLow < yLowPrev) { + indices[nIndex++] = peakIndex - i; + yLowPrev = yLow; + } else { + yLowPrev = Double.NEGATIVE_INFINITY; + } + if (yHigh < yHighPrev) { + indices[nIndex++] = peakIndex + i; + yHighPrev = yHigh; + } else { + yHighPrev = Double.NEGATIVE_INFINITY; + } + } + + Arrays.sort(indices, 0, nIndex); + result = new int[nIndex]; + System.arraycopy(indices, 0, result, 0, nIndex); + + return result; + } + + /** + * returns a double[2] if a fit is possible, null otherwise. + * @param slice a VectorDataSet with yUnits convertable to Units.dB. + */ + private double[] getFit( VectorDataSet slice, int jMax) { + double[] px, py, w; + double[] c; + int [] indices; + Datum y = slice.getXTagDatum(jMax); + Datum z = slice.getDatum(jMax); + Units xUnits = slice.getXUnits(); + Units yUnits = Units.dB; + + indices = findFive(slice, jMax); + int peakOfFive= Arrays.binarySearch(indices, jMax); + px = new double[indices.length]; + py = new double[indices.length]; + w = new double[indices.length]; + for (int iIndex = 0; iIndex < indices.length; iIndex++) { + px[iIndex] = slice.getXTagDouble(indices[iIndex], xUnits); + py[iIndex] = slice.getDouble(indices[iIndex], yUnits); + w[iIndex] = Units.dB.convertDoubleTo(Units.dimensionless, py[iIndex]); + } + + double threeDown = py[peakOfFive] - levelMin.doubleValue(Units.dB); + if (threeDown < py[0] && threeDown < py[py.length - 1]) { + return null; + } + + //Arrays.fill(w, 1.0); + c = QuadFitUtil.quadfit(px, py, w); + + if (c[2] >= -0.0) { + return null; + } + + double peak = QuadFitUtil.quadPeak(c); + if (peak < slice.getXTagDouble(0, xUnits) || peak > slice.getXTagDouble(slice.getXLength() - 1, xUnits)) { + return null; + } + + return c; + } + + + private class PeakSlicer implements DataPointSelectionListener { + + DataPointSelectionEvent lastSelectedPoint; + FitDescriptor fit; + Datum yValue; + Datum xValue; + int selectedRecord; // + + SymbolLineRenderer levelRenderer; + SymbolLineRenderer fitRenderer; + SymbolLineRenderer fitPointRenderer; + + DasPlot topPlot; + JFrame frame; + + Action prevAction= new AbstractAction("<< Prev") { + @Override + public void actionPerformed( ActionEvent e ) { + Datum xnew= xValue.subtract( xResolution ); + DataPointSelectionEvent evNew= new DataPointSelectionEvent( this, xnew, yValue ); + PeakSlicer.this.dataPointSelected(evNew); + } + } ; + + Action nextAction= new AbstractAction("Next >>") { + @Override + public void actionPerformed( ActionEvent e ) { + Datum xnew= xValue.add( xResolution ); + DataPointSelectionEvent evNew= new DataPointSelectionEvent( this, xnew, yValue ); + PeakSlicer.this.dataPointSelected(evNew); + } + } ; + + + PeakSlicer( DasPlot parent, DasAxis xaxis ) { + frame= new JFrame("Peak Slice"); + JPanel contentPanel= new JPanel(); + contentPanel.setLayout( new BorderLayout() ); + DasCanvas canvas= new DasCanvas( 300, 300 ); + contentPanel.add( canvas, BorderLayout.CENTER ); + Box npBox= Box.createHorizontalBox(); + npBox.add( new JButton( prevAction ) ); + npBox.add( new JButton( nextAction ) ); + contentPanel.add( npBox, BorderLayout.NORTH ); + + frame.getContentPane().add( contentPanel ); + frame.pack(); + frame.setVisible(false); + frame.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE ); + + DasColumn col= DasColumn.create( canvas ); + DasRow row1= DasRow.create( canvas, 0, 1 ); + + DasAxis yaxis= new DasAxis( new DatumRange( 0.001, 1,Units.dimensionless ), DasAxis.VERTICAL ) ; + yaxis.setLog(true); + DasPlot plot= new PeakDasPlot( xaxis, yaxis ); + plot.getYAxis().setLabel("level"); + plot.getXAxis().setTickLabelsVisible(false); + levelRenderer= new SymbolLineRenderer(); + + fitRenderer= new SymbolLineRenderer(); + fitRenderer = new SymbolLineRenderer(); + fitRenderer.setColor(SymColor.blue); + + fitPointRenderer = new SymbolLineRenderer(); + fitPointRenderer.setColor(SymColor.red); + fitPointRenderer.setPsymConnector(PsymConnector.NONE); + fitPointRenderer.setPsym(Psym.TRIANGLES); + fitPointRenderer.setSymSize(3.0); + + plot.addRenderer(fitRenderer); + plot.addRenderer(levelRenderer); + + topPlot= plot; + + DataPointSelectorMouseModule tweakSlicer= + new DataPointSelectorMouseModule( topPlot, levelRenderer, + new VerticalSliceSelectionRenderer(topPlot), "tweak cutoff" ) { + @Override + public void keyPressed( KeyEvent event ) { + System.err.print(event); + if ( event.getKeyCode()==KeyEvent.VK_DOWN ) { + } else if ( event.getKeyCode()==KeyEvent.VK_UP ) { + Datum xnew= xValue.subtract( xResolution ); + DataPointSelectionEvent evNew= new DataPointSelectionEvent( this, xnew, yValue ); + PeakSlicer.this.dataPointSelected(evNew); + } + } + }; + tweakSlicer.setDragEvents(true); // only key events fire + tweakSlicer.addDataPointSelectionListener( new DataPointSelectionListener() { + @Override + public void dataPointSelected( DataPointSelectionEvent e ) { + Datum x= e.getX(); + HashMap properties= new HashMap(); + if ( e.getPlane("keyChar")!=null ) { + properties.put("comment",e.getPlane("keyChar")); + } else { + properties.put("comment","tweak"); + } + fireDataSetUpdateListenerDataSetUpdated( + new DataSetUpdateEvent(this, + new SingleVectorDataSet( xValue, e.getX(), properties ) ) ); + } + } ); + topPlot.addMouseModule( tweakSlicer ); + topPlot.getDasMouseInputAdapter().setPrimaryModule(tweakSlicer); + + DataPointSelectorMouseModule levelSlicer= + new DataPointSelectorMouseModule( topPlot, levelRenderer, + new HorizontalSliceSelectionRenderer(topPlot), "peak S/N level" ); + levelSlicer.addDataPointSelectionListener( new DataPointSelectionListener() { + @Override + public void dataPointSelected( DataPointSelectionEvent e ) { + Datum y= e.getY(); + PeakDetectorMouseModule.this.setLevelMin( y ); + } + } ); + levelSlicer.setDragEvents(false); + levelSlicer.setKeyEvents(false); + levelSlicer.setReleaseEvents(true); + topPlot.addMouseModule( levelSlicer ); + + canvas.add( plot, row1, col ); + + } + + + @Override + public void dataPointSelected(org.das2.event.DataPointSelectionEvent event) { + logger.fine("got DataPointSelectionEvent: "+event.getX() ); + this.lastSelectedPoint= event; + + TableDataSet tds= (TableDataSet)dataSetConsumer.getConsumedDataSet(); + + this.xValue= event.getX(); + this.yValue= event.getY(); + + if ( xrange==null ) return; + + DatumRange range= new DatumRange( event.getX(), event.getX() ); + try { + tds= conditionData( tds, range ); + } catch (NoDataInIntervalException ex) { + return; + } + + logger.fine("find closest column " ); + int i= DataSetUtil.closestColumn( tds, event.getX() ); + + + this.xValue= tds.getXTagDatum(i); + topPlot.setTitle( "" + xValue ); + + logger.fine("doDigitize"); + fit= doDigitize( tds, i ); + if ( fit!=null ) { + levelRenderer.setDataSet( fit.digitizedDataSet ); + Datum resLimit= topPlot.getXAxis().invTransform(1) .subtract( topPlot.getXAxis().invTransform(0) ); + PolynomialDataSetDescriptor dsd= new PolynomialDataSetDescriptor( fit.fitCoef, fit.peakX.getUnits(), + fit.digitizedDataSet.getYUnits(), resLimit ) ; + dsd.setYMin(fit.digitizedDataSet.getYUnits().createDatum( -5 )); + fitRenderer.setDataSetDescriptor( dsd ); + } + + showPopup(); + } + + private void showPopup() { + if ( !frame.isVisible() ) frame.setVisible(true); + } + + class PeakDasPlot extends DasPlot { + PeakDasPlot( DasAxis x, DasAxis y ) { + super(x,y); + } + @Override + protected void drawContent(java.awt.Graphics2D g) { + super.drawContent(g); + + if ( fit!=null ) { + g.setColor( Color.GRAY ); + int ix= (int)this.getXAxis().transform( fit.peakX ); + g.drawLine( ix, 0, ix, getHeight() ); + int iy= (int)this.getYAxis().transform( Units.dB.createDatum(-3) ); + g.drawLine( 0, iy, getWidth(), iy ); + + g.setColor( Color.pink ); + ix= (int)getXAxis().transform( yValue ); + g.drawLine( ix, 0, ix, getHeight() ); + } + + } + } + } + + public DataPointSelectionListener getSlicer( DasPlot plot, TableDataSetConsumer consumer ) { + DasAxis sourceYAxis = plot.getYAxis(); + DasAxis sourceZAxis = consumer.getZAxis(); + + DatumRange range= sourceYAxis.getDatumRange(); + DasAxis xAxis = sourceYAxis.createAttachedAxis( DasAxis.HORIZONTAL ); + peakSlicer= new PeakSlicer( plot, xAxis ); + return peakSlicer; + + } + + + private transient java.util.ArrayList dataSetUpdateListenerList; + + public synchronized void addDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + if (dataSetUpdateListenerList == null ) { + dataSetUpdateListenerList = new java.util.ArrayList(); + } + dataSetUpdateListenerList.add(listener); + } + + public synchronized void removeDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + if (dataSetUpdateListenerList != null ) { + dataSetUpdateListenerList.remove(listener); + } + } + + private void fireDataSetUpdateListenerDataSetUpdated(org.das2.dataset.DataSetUpdateEvent event) { + java.util.ArrayList list; + synchronized (this) { + if (dataSetUpdateListenerList == null) return; + list = (java.util.ArrayList)dataSetUpdateListenerList.clone(); + } + for (int i = 0; i < list.size(); i++) { + ((org.das2.dataset.DataSetUpdateListener)list.get(i)).dataSetUpdated(event); + } + } + + /** + * Holds value of property levelMin. + */ + private Datum levelMin= Units.dB.createDatum(-3.); + + /** + * Getter for property levelMin. + * @return Value of property levelMin. + */ + public Datum getLevelMin() { + return this.levelMin; + } + + /** + * Setter for property levelMin. + * @param levelMin New value of property levelMin. + */ + public void setLevelMin(Datum levelMin) { + this.levelMin = levelMin; + recalculate(); + } + + private Datum xResolution= Units.milliseconds.createDatum(500); + + public Datum getXResolution() { + return this.xResolution; + } + + public void setXResolution(Datum xResolution) { + this.xResolution = xResolution; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/PointSlopeDragRenderer.java b/dasCore/src/main/java/org/das2/event/PointSlopeDragRenderer.java new file mode 100644 index 000000000..a1cda6f5e --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/PointSlopeDragRenderer.java @@ -0,0 +1,61 @@ +/* + * PointSlopeDragRenderer.java + * + * Created on February 19, 2004, 11:31 PM + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasAxis; +import org.das2.datum.Datum; +import org.das2.util.GrannyTextRenderer; +import java.awt.*; +import java.text.*; + +/** + * + * @author Owner + */ +public class PointSlopeDragRenderer extends LabelDragRenderer { + + + DasAxis xaxis, yaxis; + NumberFormat nf; + + /** Creates a new instance of PointSlopeDragRenderer */ + public PointSlopeDragRenderer(DasCanvasComponent parent, DasAxis xaxis, DasAxis yaxis ) { + super( parent ); + this.parent= parent; + this.xaxis= xaxis; + this.yaxis= yaxis; + + gtr= new GrannyTextRenderer(); + nf= new DecimalFormat( "0.00E0" ); + } + + public Rectangle[] renderDrag(java.awt.Graphics g1, java.awt.Point p1, java.awt.Point p2) { + Graphics2D g= ( Graphics2D ) g1; + g1.drawLine( p1.x, p1.y, p2.x, p2.y ); + g1.drawOval(p1.x-1, p1.y-1, 3, 3 ) ; + + Rectangle myDirtyBounds= new Rectangle( p1.x-2, p1.y-2, 5, 5 ); + + myDirtyBounds.add(p2.x-2,p2.y-2); + myDirtyBounds.add(p2.x+2,p2.y+2); + + Datum run= xaxis.invTransform(p2.x).subtract(xaxis.invTransform(p1.x)); + Datum rise= yaxis.invTransform(p2.y).subtract(yaxis.invTransform(p1.y)); + + if ( !p1.equals(p2) ) { + Datum slope= rise.divide(run); + setLabel( "m="+slope ); + } else { + setLabel( "" ); + } + super.renderDrag( g, p1, p2 ); + return new Rectangle[] { dirtyBounds, myDirtyBounds } ; + + } + +} diff --git a/dasCore/src/main/java/org/das2/event/RangeAnnotatorMouseModule.java b/dasCore/src/main/java/org/das2/event/RangeAnnotatorMouseModule.java new file mode 100644 index 000000000..4e370c3c3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/RangeAnnotatorMouseModule.java @@ -0,0 +1,34 @@ +/* + * RangeAnnotatorMouseModule.java + * + * Created on November 1, 2005, 4:30 PM + * + * + */ + +package org.das2.event; + +import org.das2.graph.DasPlot; +import java.awt.event.MouseEvent; + +/** + * + * @author Jeremy + */ +public class RangeAnnotatorMouseModule extends HorizontalRangeSelectorMouseModule { + public RangeAnnotatorMouseModule( DasPlot parent ) { + super( parent, parent.getXAxis() ); + setLabel("Range Annotator"); + } + + public void mouseRangeSelected(MouseDragEvent e) { + super.mouseRangeSelected(e); + System.out.println(e); + } + + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + System.out.println(e); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/TimeRangeSelectionEvent.java b/dasCore/src/main/java/org/das2/event/TimeRangeSelectionEvent.java new file mode 100644 index 000000000..b5ac30fa8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/TimeRangeSelectionEvent.java @@ -0,0 +1,51 @@ +/* File: TimeRangeSelectionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; + +/** + * + * @author jbf + */ +public class TimeRangeSelectionEvent extends DasEvent { + + private DatumRange range = null; + + /** Creates a new instance of TimeRangeSelectionEvent */ + public TimeRangeSelectionEvent(Object source, DatumRange range ) { + super(source); + this.range= range; + } + + public DatumRange getRange() { + return range; + } + + public String toString() { + return "["+range+"]"; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/TimeRangeSelectionListener.java b/dasCore/src/main/java/org/das2/event/TimeRangeSelectionListener.java new file mode 100644 index 000000000..be0aa5028 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/TimeRangeSelectionListener.java @@ -0,0 +1,32 @@ +/* File: TimeRangeSelectionListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +/** + * + * @author jbf + */ +public abstract interface TimeRangeSelectionListener extends java.util.EventListener { + public void timeRangeSelected(TimeRangeSelectionEvent e); +} diff --git a/dasCore/src/main/java/org/das2/event/TimeRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/TimeRangeSelectorMouseModule.java new file mode 100644 index 000000000..1f17d0055 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/TimeRangeSelectorMouseModule.java @@ -0,0 +1,137 @@ +/* File: TimeRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.datum.DatumRange; +import org.das2.DasApplication; +import org.das2.datum.Datum; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import org.das2.system.DasLogger; +import javax.swing.event.EventListenerList; + + +/** + * + * @author jbf + * @deprecated Use HorizontalRangeSelectorMouseModule. + */ +public class TimeRangeSelectorMouseModule extends MouseModule { + + DasAxis timeAxis; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + public String getLabel() { return "X Time Zoom"; } + + public TimeRangeSelectorMouseModule(DasCanvasComponent parent, DasAxis timeAxis) { + this.parent= parent; + this.dragRenderer= new HorizontalRangeGesturesRenderer(parent); + this.timeAxis= timeAxis; + } + + public static TimeRangeSelectorMouseModule create(DasPlot parent) { + DasAxis axis= parent.getXAxis(); + TimeRangeSelectorMouseModule result=null; + result= new TimeRangeSelectorMouseModule(parent, parent.getXAxis()); + return result; + } + + public void mouseRangeSelected(MouseRangeSelectionEvent e0) { + Datum tmin; + Datum tmax; + + if (!e0.isGesture()) { + MouseRangeSelectionEvent e= (MouseRangeSelectionEvent)e0; + Datum min= timeAxis.invTransform(e.getMinimum()); + Datum max= timeAxis.invTransform(e.getMaximum()); + + Datum nnMin= timeAxis.findTick(min,0,true); // nearest neighbor + Datum nnMax= timeAxis.findTick(max,0,true); + if (nnMin.equals(nnMax)) { + min= timeAxis.findTick(min,-1,true); + max= timeAxis.findTick(max,1,true); + } else { + min= nnMin; + max= nnMax; + } + TimeRangeSelectionEvent te= new TimeRangeSelectionEvent(parent,new DatumRange( min,max ) ); + fireTimeRangeSelectionListenerTimeRangeSelected(te); + } else if (e0.getGesture()==Gesture.BACK) { + timeAxis.setDataRangePrev(); + } else if (e0.getGesture()==Gesture.ZOOMOUT) { + timeAxis.setDataRangeZoomOut(); + } else if (e0.getGesture()==Gesture.FORWARD) { + timeAxis.setDataRangeForward(); + } else if (e0.getGesture()==Gesture.SCANPREV) { + DatumRange range0= timeAxis.getDatumRange(); + TimeRangeSelectionEvent te= new TimeRangeSelectionEvent(parent, range0.previous() ); + fireTimeRangeSelectionListenerTimeRangeSelected(te); + } else if (e0.getGesture()==Gesture.SCANNEXT) { + DatumRange range0= timeAxis.getDatumRange(); + TimeRangeSelectionEvent te= new TimeRangeSelectionEvent(parent, range0.next() ); + fireTimeRangeSelectionListenerTimeRangeSelected(te); + } else { + throw new RuntimeException("unrecognized gesture: "+e0.getGesture()); + } + + } + + /** Registers TimeRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addTimeRangeSelectionListener(org.das2.event.TimeRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new EventListenerList(); + } + listenerList.add(org.das2.event.TimeRangeSelectionListener.class, listener); + } + + /** Removes TimeRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeTimeRangeSelectionListener(org.das2.event.TimeRangeSelectionListener listener) { + listenerList.remove(org.das2.event.TimeRangeSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + + private void fireTimeRangeSelectionListenerTimeRangeSelected(TimeRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.TimeRangeSelectionListener.class) { + String logmsg= "fire event: "+this.getClass().getName()+"-->"+listeners[i+1].getClass().getName()+" "+event; + DasLogger.getLogger( DasLogger.GUI_LOG ).fine(logmsg); + ((org.das2.event.TimeRangeSelectionListener)listeners[i+1]).timeRangeSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/VerticalRangeGesturesRenderer.java b/dasCore/src/main/java/org/das2/event/VerticalRangeGesturesRenderer.java new file mode 100644 index 000000000..8fce3fe2e --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/VerticalRangeGesturesRenderer.java @@ -0,0 +1,114 @@ +/* File: VerticalRangeGesturesRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; + +import java.awt.*; + +/** + * + * @author eew + */ +public class VerticalRangeGesturesRenderer implements DragRenderer { + + protected int xInitial; + protected int yInitial; + private GesturesRenderer gr; + private Rectangle dirtyBounds; + private DasCanvasComponent parent; + + public VerticalRangeGesturesRenderer(DasCanvasComponent parent) { + gr= new GesturesRenderer(parent); + this.parent= parent; + dirtyBounds= new Rectangle(); + } + + public Rectangle[] renderDrag(Graphics g1, Point p1, Point p2) { + + Graphics2D g= (Graphics2D) g1; + + double dx= p2.x-p1.x; + double dy= -1* ( p2.y-p1.y ); + double angle= Math.atan2(dy, dx) * 180 / Math.PI; + double radius= Math.sqrt(dy*dy+dx*dx); + if ( radius<20 ) { + gr.renderDrag( g, p1, p2 ); + dirtyBounds.setBounds(gr.getDirtyBounds()); + } else { + + int y2 = p2.y; + int y1= p1.y; + if (y2 6 ) + g.drawLine(x, y1+3, x, y2-3); + g.drawLine(x+2, y1, x-2, y1 ); //serifs + g.drawLine(x+2, y2, x-2, y2 ); + + g.setStroke(new BasicStroke()); + g.setColor(color0); + + if ( height > 6 ) + g.drawLine(x, y1+3, x, y2-3); + g.drawLine(x+2, y1, x-2, y1 ); //serifs + g.drawLine(x+2, y2, x-2, y2 ); + dirtyBounds.setLocation(x-4,y1-2); + dirtyBounds.add(x+4,y2+2); + } + return new Rectangle[] { dirtyBounds }; + } + + + public MouseDragEvent getMouseDragEvent(Object source, Point p1, Point p2, boolean isModified) { + double dx= p2.x-p1.x; + double dy= -1* ( p2.y-p1.y ); + double radius= Math.sqrt(dy*dy+dx*dx); + if ( gr.isGesture(p1,p2) ) { + return gr.getMouseDragEvent(source,p1,p2,isModified); + } else { + return new MouseRangeSelectionEvent(source,p1.y,p2.y, isModified ); + } + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return false; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java new file mode 100644 index 000000000..9601fe39f --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java @@ -0,0 +1,118 @@ +/* File: VerticalRangeSelectorMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import javax.swing.event.EventListenerList; + +/** + * + * @author jbf + */ +public class VerticalRangeSelectorMouseModule extends MouseModule { + + DasAxis axis; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + public String getLabel() { return "Zoom Y"; }; + + public VerticalRangeSelectorMouseModule(DasCanvasComponent parent, DasAxis axis) { + if (axis.isHorizontal()) { + throw new IllegalArgumentException("Axis orientation is not vertical"); + } + this.parent= parent; + // this.dragRenderer= (DragRenderer)HorizontalRangeRenderer.renderer; + this.dragRenderer= new VerticalRangeGesturesRenderer(parent); + this.axis= axis; + } + + public static VerticalRangeSelectorMouseModule create(DasPlot parent) { + DasAxis axis= parent.getYAxis(); + VerticalRangeSelectorMouseModule result= + new VerticalRangeSelectorMouseModule(parent,parent.getYAxis()); + return result; + } + + public void mouseRangeSelected(MouseDragEvent e0) { + if (!e0.isGesture()) { + Datum min; + Datum max; + Datum nnMin; + Datum nnMax; + MouseRangeSelectionEvent e= (MouseRangeSelectionEvent)e0; + min= axis.invTransform(e.getMaximum()); + max= axis.invTransform(e.getMinimum()); + DatumRange dr= new DatumRange( min, max ); + DatumRange nndr= axis.getTickV().enclosingRange(dr, true); + DataRangeSelectionEvent te= + new DataRangeSelectionEvent(parent,nndr.min(),nndr.max()); + fireDataRangeSelectionListenerDataRangeSelected(te); + } else if (e0.getGesture()==Gesture.BACK) { + axis.setDataRangePrev(); + } else if (e0.getGesture()==Gesture.ZOOMOUT) { + axis.setDataRangeZoomOut(); + } else if (e0.getGesture()==Gesture.FORWARD) { + axis.setDataRangeForward(); + } else { + } + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/VerticalSliceSelectionRenderer.java b/dasCore/src/main/java/org/das2/event/VerticalSliceSelectionRenderer.java new file mode 100644 index 000000000..50b6547f6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/VerticalSliceSelectionRenderer.java @@ -0,0 +1,84 @@ +/* File: VerticalSliceSelectionRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; + +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasRow; + +import java.awt.*; +/** + * + * @author eew + */ +public class VerticalSliceSelectionRenderer implements DragRenderer { + + DasCanvasComponent parent; + Rectangle dirtyBounds; + + /** Creates a new instance of VerticalLineSelectionRenderer */ + public VerticalSliceSelectionRenderer(DasCanvasComponent parent) { + this.parent= parent; + dirtyBounds= new Rectangle(); + } + + private void drawCrossHair(Graphics g0, Point p) { + + Graphics g= g0.create(); + + g.setColor(new Color(0,0,0)); + g.setXORMode(Color.white); + + DasRow row= parent.getRow(); + + g.drawLine( p.x, row.getDMinimum(), p.x, row.getDMaximum() ); + + g.dispose(); + + } + + public Rectangle[] renderDrag(Graphics g, Point p1, Point p2) { + drawCrossHair(g,p2); + //g.drawLine(p2.x, 0, p2.x, parent.getHeight()); + dirtyBounds.setRect(p2.x,0,1,parent.getHeight()); + return new Rectangle[] { dirtyBounds }; + } + + + public MouseDragEvent getMouseDragEvent(Object o,Point p1,Point p2,boolean isModified) { + return null; + } + + public void clear(Graphics g) { + parent.paintImmediately(dirtyBounds); + } + + public boolean isPointSelection() { + return true; + } + + public boolean isUpdatingDragSelection() { + return false; + } + +} diff --git a/dasCore/src/main/java/org/das2/event/VerticalSlicerMouseModule.java b/dasCore/src/main/java/org/das2/event/VerticalSlicerMouseModule.java new file mode 100755 index 000000000..0347f90e3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/VerticalSlicerMouseModule.java @@ -0,0 +1,111 @@ +/* File: VerticalSlicerMouseModule.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.event; +import org.das2.dataset.DataSetConsumer; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasPlot; +import org.das2.graph.Renderer; +/** + * + * @author jbf + */ +public class VerticalSlicerMouseModule extends MouseModule { + + private org.das2.dataset.DataSet ds; + double offset; + private DasAxis xaxis; + private DasAxis yaxis; + + private org.das2.dataset.DataSetConsumer dataSetConsumer; + /** Creates a new instance of VerticalSlicerMouseModule */ + + private DataPointSelectionEvent de; + + /** Utility field used by event firing mechanism. */ + private javax.swing.event.EventListenerList listenerList = null; + + public VerticalSlicerMouseModule( DasCanvasComponent parent, + DataSetConsumer dataSetConsumer, DasAxis xaxis, DasAxis yaxis ) { + super( parent, new VerticalSliceSelectionRenderer(parent), "Vertical Slice" ); + this.dataSetConsumer= dataSetConsumer; + this.xaxis= xaxis; + this.yaxis= yaxis; + //TODO: this is silly, just create a new one each time... + this.de= new DataPointSelectionEvent(this,null,null); + } + + public static VerticalSlicerMouseModule create(DasPlot parent) { + DasAxis xaxis= parent.getXAxis(); + DasAxis yaxis= parent.getYAxis(); + return new VerticalSlicerMouseModule(parent,parent,xaxis,yaxis); + } + + public static VerticalSlicerMouseModule create( Renderer renderer ) { + DasPlot parent= renderer.getParent(); + return new VerticalSlicerMouseModule(parent,renderer,parent.getXAxis(),parent.getYAxis()); + } + + public void mousePointSelected(MousePointSelectionEvent e) { + de.birthMilli= System.currentTimeMillis(); + ds= dataSetConsumer.getConsumedDataSet(); + de.set(xaxis.invTransform(e.getX()),yaxis.invTransform(e.getY())); + + de.setDataSet(ds); + + fireDataPointSelectionListenerDataPointSelected(de); + } + + /** Registers DataPointSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + if (listenerList == null ) { + listenerList = new javax.swing.event.EventListenerList(); + } + listenerList.add(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Removes DataPointSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataPointSelectionListener(org.das2.event.DataPointSelectionListener listener) { + listenerList.remove(org.das2.event.DataPointSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataPointSelectionListenerDataPointSelected(DataPointSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataPointSelectionListener.class) { + ((org.das2.event.DataPointSelectionListener)listeners[i+1]).dataPointSelected(event); + } + } + } + +} diff --git a/dasCore/src/main/java/org/das2/event/ZoomOutMouseModule.java b/dasCore/src/main/java/org/das2/event/ZoomOutMouseModule.java new file mode 100644 index 000000000..e55c62833 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/ZoomOutMouseModule.java @@ -0,0 +1,62 @@ +/* + * ZoomOutMouseModule.java + * + * Created on June 29, 2007, 3:50 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.event; + +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.graph.DasAxis; + +/** + * + * @author jbf + */ +public class ZoomOutMouseModule extends BoxSelectorMouseModule { + + DasAxis parent; + + BoxSelectionListener createBoxSelectionListener() { + return new BoxSelectionListener() { + public void BoxSelected( BoxSelectionEvent event ) { + DatumRange outerRange= parent.getDatumRange(); + DatumRange range= parent.isHorizontal() ? event.getXRange() : event.getYRange(); + range= parent.getTickV().enclosingRange( range, true ); + DatumRange newRange; + if ( parent.isLog() ) { + double nmin= DatumRangeUtil.normalizeLog( range, outerRange.min() ); + double nmax= DatumRangeUtil.normalizeLog( range, outerRange.max() ); + nmin= nmin < -3 ? -3 : nmin; + nmax= nmax > 3 ? 3 : nmax; + newRange= DatumRangeUtil.rescaleLog( outerRange, nmin, nmax ); + } else { + double nmin= DatumRangeUtil.normalize( range, outerRange.min() ); + double nmax= DatumRangeUtil.normalize( range, outerRange.max() ); + nmin= nmin < -3 ? -3 : nmin; + nmax= nmax > 3 ? 3 : nmax; + newRange= DatumRangeUtil.rescale( outerRange, nmin, nmax ); + } + parent.setDatumRange( newRange ); + } + + }; + } + + /** Creates a new instance of ZoomOutMouseModule */ + public ZoomOutMouseModule( DasAxis axis ) { + super( axis, + axis.isHorizontal() ? axis : null, + axis.isHorizontal() ? null : axis, + null, + new BoxRenderer(axis), + "Zoom Out" ); + this.parent= axis; + addBoxSelectionListener( createBoxSelectionListener() ); + } + +} diff --git a/dasCore/src/main/java/org/das2/event/ZoomPanMouseModule.java b/dasCore/src/main/java/org/das2/event/ZoomPanMouseModule.java new file mode 100644 index 000000000..23db8ca0b --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/ZoomPanMouseModule.java @@ -0,0 +1,256 @@ +/* + * ZoomPanMouseModule.java + * + * Created on August 7, 2007, 8:53 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.das2.event; + +import java.awt.Component; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.TimeLocationUnits; +import org.das2.datum.UnitsUtil; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.TickVDescriptor; +import java.awt.Cursor; +import java.awt.Point; +import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import org.das2.graph.DasDevicePosition; + +/** + * + * @author jbf + */ +public class ZoomPanMouseModule extends MouseModule { + + DasAxis xAxis; + DasAxis yAxis; + DasAxis.Lock xAxisLock; + DasAxis.Lock yAxisLock; + Point p0; + DatumRange xAxisRange0; + DatumRange yAxisRange0; + long t0, tbirth; + + /** Creates a new instance of ZoomPanMouseModule */ + public ZoomPanMouseModule(DasCanvasComponent parent, DasAxis horizontalAxis, DasAxis verticalAxis) { + super(parent); + setLabel("Zoom Pan"); + this.xAxis = horizontalAxis; + this.yAxis = verticalAxis; + t0 = System.nanoTime(); + tbirth = System.nanoTime(); + } + + private boolean axisIsAdjustable(DasAxis axis) { + return axis != null && (UnitsUtil.isIntervalMeasurement(axis.getUnits()) || UnitsUtil.isRatioMeasurement(axis.getUnits())); + } + + private enum Pos { + _null, beyondMin, min, middle, max, beyondMax + + + + }; + + private Pos position(DasDevicePosition ddp, int pos, int threshold) { + int max = ddp.getDMaximum(); + int min = ddp.getDMinimum(); + if (((max - min) / threshold) < 3) threshold = (max - min) / 3; + if (pos < min) { + return Pos.beyondMin; + } else if (pos < min + threshold ) { + return Pos.min; + } else if (pos <= max - threshold) { + return Pos.middle; + } else if (pos <= max) { + return Pos.max; + } else { + return Pos.beyondMax; + } + } + + /** + * mouse wheel events zoom or pan rapidly. With a physical wheel, I (jbf) found + * that I get 17ms per click, and this is managable. With a touchpad on a mac, + * these events come much faster, like 10ms per click, which can disorient the + * operator. So we limit the speed to 20ms per click, for now by dropping + * rapid clicks. + * + * @param e + */ + public void mouseWheelMoved(MouseWheelEvent e) { + double nmin, nmax; + + double xshift = 0., yshift = 0.; + + if ((e.isControlDown() || e.isShiftDown())) { + if (xAxis != null && yAxis != null) return; // this happens when mouse drifts onto plot during xaxis pan. + if (e.getWheelRotation() < 0) { + nmin = -0.20; // pan left on xaxis + nmax = +0.80; + } else { + nmin = +0.20; // pan right on xaxis + nmax = +1.20; + } + } else { + Point ep= SwingUtilities.convertPoint( e.getComponent(), e.getPoint(), parent.getCanvas() ); + + //ep.translate( e.getComponent().getX(), e.getComponent().getY() ); + Pos xpos = xAxis == null ? Pos._null : position(xAxis.getColumn(), ep.x, 20); + Pos ypos = yAxis == null ? Pos._null : position(yAxis.getRow(), ep.y, 20); + + if (e.getWheelRotation() < 0) { + nmin = 0.20; // zoom in + nmax = 0.80; + } else { + nmin = -0.25; // zoom out + nmax = 1.25; + } + switch (xpos) { + case min: + xshift = -nmin; + break; + case max: + xshift = nmin; + break; + } + switch (ypos) { + case min: + yshift = nmin; + break; + case max: + yshift = -nmin; + break; + } + } + + //int clickMag= Math.abs(e.getWheelRotation()); + int clickMag = 1; + final long t1 = System.nanoTime(); + long limitNanos = (long) 20e6; + if ((t1 - t0) / clickMag < limitNanos) { + clickMag = (int) Math.floor((t1 - t0) / limitNanos); + } + + if (clickMag == 0) return; + t0 = System.nanoTime(); + + //System.err.println(":ns: "+(System.nanoTime()-tbirth)+" "+clickMag); + if (axisIsAdjustable(xAxis)) { + DatumRange dr = xAxis.getDatumRange(); + for (int i = 0; i < clickMag; i++) { + if (xAxis.isLog()) { + dr = DatumRangeUtil.rescaleLog(dr, nmin+xshift, nmax+xshift); + } else { + dr = DatumRangeUtil.rescale(dr, nmin+xshift, nmax+xshift); + } + } + xAxis.setDatumRange(dr); + } + if (axisIsAdjustable(yAxis)) { + DatumRange dr = yAxis.getDatumRange(); + for (int i = 0; i < clickMag; i++) { + + if (yAxis.isLog()) { + dr = DatumRangeUtil.rescaleLog(dr, nmin+yshift, nmax+yshift); + } else { + dr = DatumRangeUtil.rescale(dr, nmin+yshift, nmax+yshift); + } + } + yAxis.setDatumRange(dr); + } + + super.mouseWheelMoved(e); + } + + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + if (xAxis != null) { + xAxisLock.unlock(); + xAxisLock = null; + } + if (yAxis != null) { + yAxisLock.unlock(); + yAxisLock = null; + } + doPan(e, false); + parent.getCanvas().getGlassPane().setCursor(null); + } + + /** + * round to a nice boundaries. + */ + private static DatumRange doRound(DatumRange dr, DasAxis axis) { + TickVDescriptor ticks; + if (dr.getUnits() instanceof TimeLocationUnits) { + ticks = TickVDescriptor.bestTickVTime(dr.min(), dr.max(), axis.getDLength() / 2, axis.getDLength(), true); + } else if (axis.isLog()) { + ticks = TickVDescriptor.bestTickVLogNew(dr.min(), dr.max(), axis.getDLength() / 2, axis.getDLength(), true); + } else { + ticks = TickVDescriptor.bestTickVLinear(dr.min(), dr.max(), axis.getDLength() / 2, axis.getDLength(), true); + } + return ticks.enclosingRange(dr, true); + } + + private void doPan(final MouseEvent e, boolean round) { + Point p2 = e.getPoint(); + if (axisIsAdjustable(xAxis)) { + DatumRange dr; + if (xAxis.isLog()) { + Datum delta = xAxis.invTransform(p0.getX()).divide(xAxis.invTransform(p2.getX())); + dr = new DatumRange(xAxisRange0.min().multiply(delta), xAxisRange0.max().multiply(delta)); + } else { + Datum delta = xAxis.invTransform(p0.getX()).subtract(xAxis.invTransform(p2.getX())); + dr = new DatumRange(xAxisRange0.min().add(delta), xAxisRange0.max().add(delta)); + } + if (round) { + dr = doRound(dr, xAxis); + } + xAxis.setDatumRange(dr); + } + if (axisIsAdjustable(yAxis)) { + DatumRange dr; + if (yAxis.isLog()) { + Datum ydelta = yAxis.invTransform(p0.getY()).divide(yAxis.invTransform(p2.getY())); + dr = new DatumRange(yAxisRange0.min().multiply(ydelta), yAxisRange0.max().multiply(ydelta)); + } else { + Datum ydelta = yAxis.invTransform(p0.getY()).subtract(yAxis.invTransform(p2.getY())); + dr = new DatumRange(yAxisRange0.min().add(ydelta), yAxisRange0.max().add(ydelta)); + } + if (round) { + dr = doRound(dr, yAxis); + } + yAxis.setDatumRange(dr); + } + } + + public void mouseDragged(MouseEvent e) { + super.mouseDragged(e); + doPan(e, false); + } + + public void mousePressed(MouseEvent e) { + super.mousePressed(e); + p0 = e.getPoint(); + if (xAxis != null) { + xAxisRange0 = xAxis.getDatumRange(); + xAxisLock = xAxis.mutatorLock(); + xAxisLock.lock(); + } + if (yAxis != null) { + yAxisRange0 = yAxis.getDatumRange(); + yAxisLock = yAxis.mutatorLock(); + yAxisLock.lock(); + } + parent.getCanvas().getGlassPane().setCursor(new Cursor(Cursor.HAND_CURSOR)); + } +} diff --git a/dasCore/src/main/java/org/das2/event/package.html b/dasCore/src/main/java/org/das2/event/package.html new file mode 100644 index 000000000..12b4a49c2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/package.html @@ -0,0 +1,18 @@ + +

    Classes for adding interactivity to the application components. +Each DasCanvasComponent has a DasMouseInputAdapter that dispatches mouse +events. MouseModules are plug into a DasMouseInputAdapter and receive the +mouse events and perform a function based on the input. For example, the +CrossHairMouseModule looks up the X and Y coodinates of the pointer during a +mouse drag and displays them in a box. A VerticalSlicerMouseModule looks up +the TableDataSet column under the click and plots it in a popup window. +The MouseModules use DragRenderers to provide visual feedback about the pending +operation. +

    +

    A set of science abstraction level events is defined as well for communication +between application components. These include, for example, TimeRangeSelectionEvent +and BoxSelectionEvent. For example, a MouseModule might create a BoxSelectionEvent, +then send the event off to another module that begins an analysis for the data within +the selected box. +

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/event/scratchPad.txt b/dasCore/src/main/java/org/das2/event/scratchPad.txt new file mode 100755 index 000000000..d93f55c3c --- /dev/null +++ b/dasCore/src/main/java/org/das2/event/scratchPad.txt @@ -0,0 +1,18 @@ +Single Key MouseModule Activation: + X zoom X + Y zoom Y + V slice Vertical + H slice Horizontal + D crosshair digitizer + B bookmarks + E edge detector + F frequency measurer + + +Should DragRenderers only be allowed to communicate science events? If so, +then what is the purpose of a MouseModule? + +2007-01-29: eew + Ed suggests the mouseModules have a hook that is called when the MM is selected, so it can pop up a dialog, etc. + + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/fsm/FileStorageModel.java b/dasCore/src/main/java/org/das2/fsm/FileStorageModel.java new file mode 100644 index 000000000..704f5a812 --- /dev/null +++ b/dasCore/src/main/java/org/das2/fsm/FileStorageModel.java @@ -0,0 +1,774 @@ +/* + * FileStorageModel.java + * + * Created on March 31, 2004, 9:52 AM + */ + +package org.das2.fsm; + +import org.das2.datum.CalendarTime; +import org.das2.util.filesystem.FileObject; +import org.das2.util.filesystem.FileSystem; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.TimeUtil; +import org.das2.datum.Units; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.monitor.SubTaskMonitor; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.*; +import java.util.*; +import java.util.regex.*; + +/** + * Represents a method for storing data sets in a set of files by time. The + * client provides a regex for the files and how each group of the regex is + * interpreted as a time digit. The model can then be used to provide the set + * of files that cover a time range, etc. + * + * @author Jeremy + */ +public class FileStorageModel { + + private Pattern pattern; + private Pattern absPattern; + private String regex; + + private FieldHandler[] fieldHandlers; + + private CalendarTime.Step timeWidth; /* in TimeUtil enum */ + private int timeWidthMultiplier; /* 7 days */ + private Datum timePhase= null; /* a file boundary */ + + private boolean[] copyToEndTime; /* indexed by TimeUtil enum */ + private boolean useEndTime; + FileStorageModel parent; + FileSystem root; + + public static final int StartYear4=100; + public static final int StartYear2=101; + public static final int StartMonth=102; + public static final int StartMonthName=108; + public static final int StartDay=103; + public static final int StartDoy=104; + public static final int StartHour=105; + public static final int StartMinute=106; + public static final int StartSecond=107; + + public static final int EndYear4=200; + public static final int EndYear2=201; + public static final int EndMonth=202; + public static final int EndMonthName=208; + public static final int EndDay=203; + public static final int EndDoy=204; + public static final int EndHour=205; + public static final int EndMinute=206; + public static final int EndSecond=207; + + public static final int Ignore=300; + + HashMap fileNameMap=null; + + /* need to map back to TimeUtil's enums, note that we have an extra for the 2 digit year */ + private CalendarTime.Step toTimeUtilEnum( int i ) { + if( i<100 || i > 300 ) + throw new IllegalArgumentException( "enumeration is not of the correct type"); + + switch(i % 100){ + case 0: + case 1: return CalendarTime.Step.YEAR; + case 2: return CalendarTime.Step.MONTH; + // Day handled below + case 5: return CalendarTime.Step.HOUR; + case 6: return CalendarTime.Step.MINUTE; + case 7: + case 8: return CalendarTime.Step.SECOND; + } + return CalendarTime.Step.DAY; + } + + //TODO: add + // public string format( DatumRange dr ); + // + public interface FieldHandler { + public void handle( String s, CalendarTime ts1, CalendarTime ts2 ); + public String format( CalendarTime ts1, CalendarTime ts2 ); + } + + public static abstract class IntegerFieldHandler implements FieldHandler { + public void handle( String s, CalendarTime ts1, CalendarTime ts2 ) { + handleInt( Integer.parseInt(s), ts1, ts2 ); + } + abstract void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ); + public abstract String format( CalendarTime ts1, CalendarTime ts2 ); + } + + static final NumberFormat nf4= new DecimalFormat("0000"); + static final NumberFormat nf3= new DecimalFormat("000"); + static final NumberFormat nf2= new DecimalFormat("00"); + + private final static String[] mons= new String [] { + "", "jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec" } ; + private static final FieldHandler StartMonthNameHandler= new FieldHandler() { + public void handle( String s, CalendarTime ts1, CalendarTime ts2 ) { + try { + ts1.setMonth(TimeUtil.monthNumber( s )); + } catch ( ParseException e ) { + DasExceptionHandler.handle(e); + } + } + public String format(CalendarTime ts1, CalendarTime ts2) { + return mons[ ts1.month() ]; + } + }; + + private static final FieldHandler EndMonthNameHandler= new FieldHandler() { + public void handle( String s, CalendarTime ts1, CalendarTime ts2 ) { + try { + ts2.setMonth( TimeUtil.monthNumber( s ) ); + } catch ( ParseException e ) { + DasExceptionHandler.handle(e); + } + } + public String format(CalendarTime ts1, CalendarTime ts2) { + return mons[ ts2.month() ]; + } + + }; + + + private static final FieldHandler StartYear4Handler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setYear(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf4.format(ts1.year()); } + }; + + private static final FieldHandler StartYear2Handler= new IntegerFieldHandler() { + @Override + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setYear( i<58 ? i+2000 : i+1900); } + @Override + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.year() % 100 ); } + }; + + private static final FieldHandler StartMonthHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setMonth(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.month() ); } + }; + + private static final FieldHandler StartDayHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setDay(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.day() ); } + }; + private static final FieldHandler StartDoyHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setDayOfYear(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf3.format( ts1.dayOfYear() ); } + }; + private static final FieldHandler StartHourHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setHour(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.hour() ); } + }; + private static final FieldHandler StartMinuteHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setMinute(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.minute() ); } + }; + private static final FieldHandler StartSecondHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts1.setSecond(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts1.second() ); } + }; + + private static final FieldHandler EndYear4Handler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setYear(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf4.format( ts2.year() ); } + }; + private static final FieldHandler EndYear2Handler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setYear( i<58 ? i+2000 : i+1900); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.year() ); } + }; + + private static final FieldHandler EndMonthHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setMonth(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.month() ); } + }; + private static final FieldHandler EndDayHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setDay(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.day() ); } + }; + private static final FieldHandler EndDoyHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setDayOfYear(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf3.format( ts2.dayOfYear() ); } + }; + private static final FieldHandler EndHourHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setHour(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.hour() ); } + }; + private static final FieldHandler EndMinuteHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setMinute(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.minute() ); } + }; + private static final FieldHandler EndSecondHandler= new IntegerFieldHandler() { + public void handleInt( int i, CalendarTime ts1, CalendarTime ts2 ) { ts2.setSecond(i); } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return nf2.format( ts2.second() ); } + }; + + private static final FieldHandler IgnoreHandler= new FieldHandler() { + public void handle( String s, CalendarTime ts1, CalendarTime ts2 ) { } + public String format( CalendarTime ts1, CalendarTime ts2 ) { return "*"; } + }; + + + private void checkArgs( String regex, int[] digitList ) { + int startLsd=0, endLsd=0; + int[] startDigits= new int[7]; + int[] endDigits= new int[7]; + copyToEndTime= new boolean[8]; /* indexed by TimeUtil enum */ + int startBase=100; + int endBase=200; + int ignoreBase=300; + for ( int i=0; istartLsd ) startLsd= 103; + } else if ( digitList[i]==EndDoy ) { + endDigits[1]=1; endDigits[2]=1; + if ( 203>endLsd ) endLsd= 203; + } else if ( digitList[i]>=startBase && digitList[i]startLsd ) startLsd= digitList[i]; + } else if ( digitList[i]>=endBase && digitList[i]endLsd ) endLsd= digitList[i]; + } + } + if ( startDigits[StartYear2-startBase]==1 ) startDigits[StartYear4-startBase]=1; + if ( startDigits[StartYear4-startBase]==1 ) startDigits[StartYear2-startBase]=1; + + if ( startDigits[StartDoy-startBase]==1 ) { + startDigits[StartMonth-startBase]=1; + startDigits[StartDay-startBase]=1; + } + if ( endDigits[EndYear2-endBase]==1 ) endDigits[EndYear4-endBase]=1; + if ( startDigits[EndYear4-endBase]==1 ) startDigits[EndYear2-endBase]=1; + if ( endDigits[EndDoy-endBase]==1 ) { + endDigits[EndMonth-endBase]=1; + endDigits[EndDay-endBase]=1; + } + for ( int i=0; i0 && startDigits[i]==1 && startDigits[i-1]!=1 ) { + throw new IllegalArgumentException( "more significant digits missing in startTime"); + } + if ( i>0 && startDigits[i]==0 && startDigits[i-1]==1 ) { + timeWidth= toTimeUtilEnum( startLsd ); + timeWidthMultiplier= 1; + } + } + + boolean canUse= true; + for ( int i=startLsd-startBase; i>=0; i-- ) { + if ( endDigits[i]==0 ) canUse=false; + if ( !canUse ) endDigits[i]= 0; + } + + for ( int i=0; i=100 && digitList[i]<200 ) { + fieldHandlerList.add(i,startHandlers[digitList[i]-100]); + } else if (digitList[i]>=200 && digitList[i]<300 ) { + fieldHandlerList.add(i,endHandlers[digitList[i]-200]); + } else if ( digitList[i]==300 ) { + fieldHandlerList.add(i,IgnoreHandler); + } else { + throw new IllegalArgumentException("unknown field handler: "+digitList[i]); + } + } + return (FieldHandler[])fieldHandlerList.toArray( new FieldHandler[fieldHandlerList.size()] ); + } + + + /* + * extract time range for file or directory from its name. + * The least significant time digit is considered to be the implicitTimeWidth, + * and if the width is not stated explicitly, it will be used. When + * a set timeDigits are encountered twice, then the second occurrence + * is considered be the end time. + * + * .../FULL1/T8709_12/T871118.DAT + *'.../FULL1/T'YYMM_MM/TYYMMDD'.DAT' + */ + private DatumRange getDatumRangeFor( String filename ) { + + if ( fieldHandlers.length==0 ) { + // e.g. FULL1 doesn't constrain time + return DatumRange.newDatumRange( -1e30, 1e30, Units.mj1958 ); + } + + CalendarTime ts1= new CalendarTime(new int[]{1,1,1,0,0,0,0}); + + CalendarTime ts2= new CalendarTime(); + if ( File.separatorChar=='\\' ) filename= filename.replaceAll("\\\\", "/"); + + Matcher m= pattern.matcher(filename); + if ( m.matches() ) { + for ( int i=0; i0 ) { + String ff= names[i].equals("") ? files1[0] : names[i]+"/"+files1[0]; + if ( ff.endsWith("/") ) ff=ff.substring(0,ff.length()-1); + result= ff; + } + } + + return result; + } + + public File[] getFilesFor( final DatumRange targetRange ) throws IOException { + return getFilesFor( targetRange, new NullProgressMonitor() ); + } + + public DatumRange getRangeFor( String name ) { + return getDatumRangeFor( name ); + } + + /** + * returns true if the file came (or could come) from this FileStorageModel. + */ + public boolean containsFile( File file ) { + maybeCreateFileNameMap(); + if ( !fileNameMap.containsKey(file) ) { + return false; + } else { + String result= (String)fileNameMap.get(file); + String name= getNameFor( file ); + Matcher m= pattern.matcher( name ); + return m.matches(); + } + } + + /** + * Need a way to recover the model name of a file. The returned File from getFilesFor can be anywhere, + * so it would be good to provide a way to get it back into a FSM name. + */ + public String getNameFor( File file ) { + String result= (String)fileNameMap.get(file); + if ( result==null ) { + throw new IllegalArgumentException( "File didn't come from this FileStorageModel" ); + } else { + return result; + } + } + + private synchronized void maybeCreateFileNameMap() { + if ( fileNameMap==null ) fileNameMap= new HashMap(); + } + + /** + * retrieve the file for the name. + * @throws IOException if the file cannot be transferred. + */ + public File getFileFor( String name, ProgressMonitor monitor ) throws FileNotFoundException, IOException { + FileObject o= root.getFileObject( name ); + File file= o.getFile( monitor ); + + maybeCreateFileNameMap(); + + fileNameMap.put( file, name ); + return file; + + } + + /** + * returns a list of files that can be used + */ + public File[] getFilesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { + String[] names= getNamesFor( targetRange ); + File[] files= new File[names.length]; + + maybeCreateFileNameMap(); + + if ( names.length>0 ) monitor.setTaskSize( names.length * 10 ); + for ( int i=0; i1 ) { + dirRegex= s[0]; + for ( int i=1; i 300 ) { + throw new IllegalArgumentException( "enumeration is not of the correct type"); + } + i= i % 100; + if ( i==0 ) i=1; + return i; + } + + + /** + * extract time range for file or directory from its name. + * The least significant time digit is considered to be the implicitTimeWidth, + * and if the width is not stated explicitly, it will be used. When + * a set timeDigits are encountered twice, then the second occurrence + * is considered be the end time. + * + * .../FULL1/T8709_12/T871118.DAT + *'.../FULL1/T'YYMM_MM/TYYMMDD'.DAT' + */ + private DatumRange getDatumRangeFor( String filename ) { + try { + if ( pattern.matcher(filename).matches() ) { + timeParser.parse( filename ); + return timeParser.getTimeRange(); + } else { + throw new IllegalArgumentException( "file name ("+filename+") doesn't match model specification ("+regex+")"); + } + } catch ( ParseException e ) { + IllegalArgumentException e2=new IllegalArgumentException( "file name ("+filename+") doesn't match model specification ("+regex+"), parse error in field",e); + throw e2; + } catch ( NumberFormatException e ) { + IllegalArgumentException e2=new IllegalArgumentException( "file name ("+filename+") doesn't match model specification ("+regex+"), parse error in field",e); + throw e2; + } + } + + public String getFilenameFor( Datum start, Datum end ) { + return timeParser.format( start, end ); + } + + public String[] getNamesFor( final DatumRange targetRange ) throws IOException { + return getNamesFor( targetRange, new NullProgressMonitor() ); + } + + public String[] getNamesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { + + String listRegex; + + FileSystem[] fileSystems; + String[] names; + + if ( parent!=null ) { + names= parent.getNamesFor(targetRange); + fileSystems= new FileSystem[names.length]; + for ( int i=0; i0 ) monitor.setTaskSize( names.length * 10 ); + monitor.started(); + for ( int i=0; i1 ) { + dirRegex= s[0]; + for ( int i=1; i +fsm contains objects that model files that are stored in a FileSystem with the +filename parametric in time. For example, suppose there is a store of data +in http://cdaweb.gsfc.nasa.gov/istp_public/data/ace/swe/, where the data files are +stored by year and then in each yearly folder, the files contain the date encoded +within the filename. We can then model the storage with the template specification +%Y/ac_k0_swe_%Y%m%d_v01.cdf. A FileStorageModel object can be queried as to what +is available for a time range, and will use the FileSystem object to provide +local copies of the file. + +FileStorageModel is the production object providing this functionality. FileStorageModelNew +is an attempt to unify codes by treating filenames as formatted time range strings, which +should reduce code and run more efficiently. This code is not yet proven and should be used +with caution. FileStorageModelAvailabilityDataSetDescriptor provides a DataSetDescriptor +that can be used to get DataSet views describing when data is available. + + diff --git a/dasCore/src/main/java/org/das2/graph/Arrow.java b/dasCore/src/main/java/org/das2/graph/Arrow.java new file mode 100644 index 000000000..3cd8e5351 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/Arrow.java @@ -0,0 +1,90 @@ +/* + * Arrow.java + * + * Created on September 28, 2004, 2:13 PM + */ + +package org.das2.graph; + +import java.awt.*; +import java.awt.geom.*; + +/** + * + * @author Jeremy + */ +public class Arrow extends DasCanvasComponent { + + Point head, tail; + Stroke stroke; + double em=24; //pixels; + + public enum HeadStyle { + DRAFTING, FAT_TRIANGLE, THIN_TRIANGLE, + } + + public Arrow( DasCanvas c, Point head, Point tail ) { + this.head= head; + this.tail= tail; + setRow( new DasRow( c, head.getX(), tail.getX() ) ); + setColumn( new DasColumn( c, head.getY(), tail.getY() ) ); + } + + public void resize() { + Rectangle bounds= new Rectangle(); + bounds.add( head.x - em, head.y-em ); // account for stroke width + bounds.add( tail.x + em, tail.y+em ); + setBounds(bounds); + } + + public static void paintArrow( Graphics2D g, Point head, Point tail, double em, HeadStyle style ) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + + Line2D line= new Line2D.Double( head, tail ); + double dx= - ( head.getX() - tail.getX() ); + double dy= - ( head.getY() - tail.getY() ); + double dd= Math.sqrt( dx*dx + dy*dy ); + dx= dx * em / 4 / dd; + dy= dy * em / 4 / dd; + + double hx= head.getX(); + double hy= head.getY(); + + g.setStroke( new BasicStroke( (float)(em/8), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) ); + + g.draw( line ); + + GeneralPath p= new GeneralPath(); + p.moveTo( (float)hx, (float)hy ); + + if ( style==HeadStyle.DRAFTING ) { + p.lineTo( (float)(hx+2*dx+0.5*dy), (float)(hy+2*dy-0.5*dx) ); + p.lineTo( (float)(hx+3*dx+dy), (float)(hy+3*dy-dx) ); + p.lineTo( (float)(hx+3*dx-dy), (float)(hy+3*dy+dx) ); + p.lineTo( (float)(hx+2*dx-0.5*dy), (float)(hy+2*dy+0.5*dx) ); + p.lineTo( (float)hx, (float)hy ); + } else if ( style==HeadStyle.FAT_TRIANGLE ) { + p.lineTo( (float)(hx+3*dx+1.5*dy), (float)(hy+3*dy-1.5*dx) ); + p.lineTo( (float)(hx+3*dx-1.5*dy), (float)(hy+3*dy+1.5*dx) ); + p.lineTo( (float)hx, (float)hy ); + } else if ( style==HeadStyle.THIN_TRIANGLE ) { + p.lineTo( (float)(hx+3*dx+dy), (float)(hy+3*dy-dx) ); + p.lineTo( (float)(hx+3*dx-dy), (float)(hy+3*dy+dx) ); + p.lineTo( (float)hx, (float)hy ); + } + + g.fill( p ); + + g.draw( p ); + + } + + protected void paintComponent(Graphics g1) { + Graphics2D g= (Graphics2D) g1.create(); + g.translate(-getX(),-getY()); + paintArrow( g, head, tail, em , HeadStyle.DRAFTING ); + getDasMouseInputAdapter().paint(g1); + } + + +} diff --git a/dasCore/src/main/java/org/das2/graph/AttachedLabel.java b/dasCore/src/main/java/org/das2/graph/AttachedLabel.java new file mode 100644 index 000000000..ae1ebc4ed --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/AttachedLabel.java @@ -0,0 +1,551 @@ +/* File: DasAxis.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.util.GrannyTextRenderer; +import java.awt.*; + +/** + * A canvas component for labeling things that is positioned just outside of a row,column box. + * + * @author eew + */ +public class AttachedLabel extends DasCanvasComponent implements Cloneable { + + + /* + * PUBLIC CONSTANT DECLARATIONS + */ + + /** This value indicates that the axis should be located at the top of its cell */ + public static final int TOP = 1; + + /** This value indicates that the axis should be located at the bottom of its cell */ + public static final int BOTTOM = 2; + + /** This value indicates that the axis should be located to the left of its cell */ + public static final int LEFT = 3; + + /** This value indicateds that the axis should be located to the right of its cell */ + public static final int RIGHT = 4; + + /** This value indicates that the axis should be oriented horizontally */ + public static final int HORIZONTAL = BOTTOM; + + /** This value indicates that the axis should be oriented vertically */ + public static final int VERTICAL = LEFT; + + /* GENERAL LABEL INSTANCE MEMBERS */ + private int orientation; + protected String axisLabel = ""; + + /* Rectangles representing different areas of the axis */ + private Rectangle blTitleRect; + private Rectangle trTitleRect; + + private double emOffset; + + private boolean flipLabel = false; + + /* DEBUGGING INSTANCE MEMBERS */ + private static final boolean DEBUG_GRAPHICS = false; + private static final Color[] DEBUG_COLORS; + static { + if (DEBUG_GRAPHICS) { + DEBUG_COLORS = new Color[] { + Color.BLACK, Color.RED, Color.GREEN, Color.BLUE, + Color.GRAY, Color.CYAN, Color.MAGENTA, Color.YELLOW, + }; + } else { + DEBUG_COLORS = null; + } + } + private int debugColorIndex = 0; + + /** constructs an AttachedLabel. + * @param label The granny string to be displayed. + * @param orientation identifies the side of the box. See TOP, BOTTOM, LEFT, RIGHT. + * @param emOffset The offset from the edge of the box to the label, in "ems"-- the roughly the width of a letter "M," and + * more precisely the size of the current font. + */ + public AttachedLabel( String label, int orientation, double emOffset) { + super(); + this.orientation = orientation; + this.emOffset = emOffset; + this.axisLabel = label; + setOpaque(false); + } + + /** Sets the side of the row,column box to locate the label. + * @param orientation should be one of AttachedLabel.TOP, AttachedLabel.BOTTOM, AttachedLabel.LEFT, AttachedLabel.RIGHT + */ + public void setOrientation(int orientation) { + boolean oldIsHorizontal = isHorizontal(); + setOrientationInternal(orientation); + } + + /* This is a private internal implementation for + * {@link #setOrientation(int)}. This method is provided + * to avoid calling a non-final non-private instance method + * from a constructor. Doing so can create problems if the + * method is overridden in a subclass. + */ + private void setOrientationInternal(int orientation) { + this.orientation = orientation; + } + + /** Mutator method for the title property of this axis. + * + * The title for this axis is displayed below the ticks for horizontal axes + * or to left of the ticks for vertical axes. + * @param t The new title for this axis + */ + public void setLabel(String t) { + if (t == null) throw new NullPointerException("axis label cannot be null"); + Object oldValue = axisLabel; + axisLabel = t; + update(); + firePropertyChange("label", oldValue, t); + } + + /** + * Accessor method for the title property of this axis. + * + * @return A String instance that contains the title displayed + * for this axis, or null if the axis has no title. + */ + public String getLabel() { + return axisLabel; + } + + /** + * @return the pixel position of the label. + * @deprecated It's not clear how this should be used, and it does not appear to be used within dasCore and dasApps. + */ + public final int getDevicePosition() { + if (orientation == BOTTOM) { + return getRow().getDMaximum(); + } else if (orientation == TOP) { + return getRow().getDMinimum(); + } else if (orientation == LEFT) { + return getColumn().getDMinimum(); + } else { + return getColumn().getDMaximum(); + } + } + + /** + * @return returns the length in pixels of the axis. + */ + public int getDLength() { + if (isHorizontal()) + return getColumn().getWidth(); + else + return getRow().getHeight(); + } + + /** + * paints the axis component. The tickV's and bounds should be calculated at this point + * @param graphics + */ + protected void paintComponent(Graphics graphics) { + + /* This was code was keeping axes from being printed on PC's + Shape saveClip = null; + if (getCanvas().isPrintingThread()) { + saveClip = graphics.getClip(); + graphics.setClip(null); + } + */ + + Graphics2D g = (Graphics2D)graphics.create(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.translate(-getX(), -getY()); + g.setColor(getForeground()); + + /* Debugging code */ + /* The compiler will optimize it out if DEBUG_GRAPHICS == false */ + if (DEBUG_GRAPHICS) { + g.setStroke(new BasicStroke(3f, BasicStroke.CAP_BUTT, BasicStroke.CAP_BUTT, 1f, new float[]{3f, 3f}, 0f)); + g.setColor(Color.BLUE); + g.setColor(Color.LIGHT_GRAY); + if (trTitleRect != null) g.draw(trTitleRect); + g.setStroke(new BasicStroke(1f)); + g.setColor(DEBUG_COLORS[debugColorIndex]); + debugColorIndex++; + if (debugColorIndex >= DEBUG_COLORS.length) { debugColorIndex = 0; }; + } + /* End debugging code */ + + if (isHorizontal()) { + paintHorizontalLabel(g); + } else { + paintVerticalLabel(g); + } + + g.dispose(); + getDasMouseInputAdapter().paint(graphics); + } + + /** Paint the axis if it is horizontal */ + protected void paintHorizontalLabel(Graphics2D g) { + Rectangle clip = g.getClipBounds(); + if (clip == null) { + clip = new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + boolean bottomLabel = ((orientation == BOTTOM && !axisLabel.equals("")) && blTitleRect != null && blTitleRect.intersects(clip)); + boolean topLabel = ((orientation == TOP && !axisLabel.equals("")) && trTitleRect != null && trTitleRect.intersects(clip)); + + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + int DMax= getColumn().getDMaximum(); + int DMin= getColumn().getDMinimum(); + + Font labelFont = getLabelFont(); + + int tickLengthMajor = labelFont.getSize() * 2 / 3; + int tickLengthMinor = tickLengthMajor / 2; + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D)g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g, axisLabel); + int titleWidth = (int)gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (bottomLabel) { + leftEdge = DMin + (DMax-DMin - titleWidth)/2; + baseline = bottomPosition + titlePositionOffset; + gtr.draw(g2, (float)leftEdge, (float)baseline); + } + if (topLabel) { + leftEdge = DMin + (DMax-DMin - titleWidth)/2; + baseline = topPosition - titlePositionOffset; + gtr.draw(g2, (float)leftEdge, (float)baseline); + } + g2.dispose(); + } + } + + /** Paint the axis if it is vertical */ + protected void paintVerticalLabel(Graphics2D g) { + Rectangle clip = g.getClipBounds(); + if (clip == null) { + clip = new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + boolean leftLabel = ((orientation == LEFT && !axisLabel.equals("")) && blTitleRect != null && blTitleRect.intersects(clip)); + boolean rightLabel = ((orientation == RIGHT && !axisLabel.equals("")) && trTitleRect != null && trTitleRect.intersects(clip)); + + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax= getRow().getDMaximum(); + int DMin= getRow().getDMinimum(); + + Font labelFont = getLabelFont(); + + int tickLengthMajor= labelFont.getSize()*2/3; + int tickLengthMinor = tickLengthMajor / 2; + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D)g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g, axisLabel); + int titleWidth = (int)gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (leftLabel) { + g2.rotate(-Math.PI/2.0); + leftEdge = -DMax + (DMax-DMin - titleWidth)/2; + baseline = leftPosition - titlePositionOffset; + gtr.draw(g2, (float)leftEdge, (float)baseline); + //leftEdge = DMin + (DMax-DMin - titleWidth)/2; + //baseline = topPosition - titlePositionOffset; + //gtr.draw(g2, (float)leftEdge, (float)baseline); + } + if (rightLabel) { + if (flipLabel) { + g2.rotate(-Math.PI/2.0); + leftEdge = -DMax + (DMax-DMin - titleWidth)/2; + baseline = rightPosition + titlePositionOffset + + (int)Math.ceil(gtr.getHeight()) + - 2*(int)Math.ceil(gtr.getDescent()); + gtr.draw(g2, (float)leftEdge, (float)baseline); + //leftEdge = DMin + (DMax-DMin - titleWidth)/2; + //baseline = bottomPosition + titlePositionOffset; + //gtr.draw(g2, (float)leftEdge, (float)baseline); + } + else { + g2.rotate(Math.PI/2.0); + leftEdge = DMin + (DMax-DMin - titleWidth)/2; + baseline = - rightPosition - titlePositionOffset; + gtr.draw(g2, (float)leftEdge, (float)baseline); + } + } + g2.dispose(); + } + } + + /** calculates the distance from the box to the label. + * @return integer distance in pixels. + */ + protected int getTitlePositionOffset() { + Font labelFont = getLabelFont(); + + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, axisLabel); + + int offset = (int)Math.ceil(emOffset * labelFont.getSize()); + + if (orientation == BOTTOM) { + offset += gtr.getAscent(); + } + return offset; + } + + public boolean isLabelFlipped() { + return flipLabel; + } + + public void setLabelFlipped(boolean flipLabel) { + this.flipLabel = flipLabel; + update(); + } + + /** get the current font of the component. + * @return Font of the component. + */ + public Font getLabelFont() { + return this.getFont(); + } + + /** set the font of the label. + * @param labelFont Font for the component. Currently this is ignored. + */ + public void setLabelFont(Font labelFont) { + // TODO: whah?--jbf + } + + /** clones the component + * @return clone of this. + */ + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error("Assertion failure"); + } + } + + /** + * revalidate component after resize. + */ + public void resize() { + setBounds(getLabelBounds()); + invalidate(); + validate(); + } + + /** get the Rectangle precisely enclosing the label. + * @return Rectangle in canvas space. + */ + protected Rectangle getLabelBounds() { + Rectangle bounds; + if (isHorizontal()) { + bounds = getHorizontalLabelBounds(); + } else { + bounds = getVerticalLabelBounds(); + } + return bounds; + } + + private Rectangle getHorizontalLabelBounds() { + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + DasDevicePosition range = getColumn(); + int DMax = range.getDMaximum(); + int DMin = range.getDMinimum(); + + boolean bottomLabel = (orientation == BOTTOM && !axisLabel.equals("")); + boolean topLabel = (orientation == TOP && !axisLabel.equals("")); + + Rectangle bounds; + + //Add room for the axis label + Font labelFont = getLabelFont(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, getLabel()); + int offset = getTitlePositionOffset(); + if (bottomLabel) { + int x = 0; + int y = getRow().getDMaximum() + offset - (int)Math.ceil(gtr.getAscent()); + int width = getCanvas().getWidth(); + int height = (int)Math.ceil(gtr.getHeight()); + blTitleRect = setRectangleBounds(blTitleRect, x, y, width, height); + } + if (topLabel) { + int x = 0; + int y = getRow().getDMinimum() - offset - (int)Math.ceil(gtr.getAscent()); + int width = getCanvas().getWidth(); + int height = (int)Math.ceil(gtr.getHeight()); + trTitleRect = setRectangleBounds(trTitleRect, x, y, width, height); + } + + bounds = new Rectangle((orientation == BOTTOM) ? blTitleRect : trTitleRect); + + return bounds; + } + + private Rectangle getVerticalLabelBounds() { + boolean leftLabel = (orientation == LEFT && !axisLabel.equals("")); + boolean rightLabel = (orientation == RIGHT && !axisLabel.equals("")); + + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax= getRow().getDMaximum(); + int DMin= getRow().getDMinimum(); + + Rectangle bounds; + + int offset = getTitlePositionOffset(); + + //Add room for the axis label + Font labelFont = getLabelFont(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, getLabel()); + if (leftLabel) { + int x = getColumn().getDMinimum() - offset - (int)Math.ceil(gtr.getAscent()); + int y = 0; + int width = (int)Math.ceil(gtr.getHeight()); + int height = getCanvas().getHeight(); + blTitleRect = setRectangleBounds(blTitleRect, x, y, width, height); + } + if (rightLabel) { + int x = getColumn().getDMaximum() + offset - (int)Math.ceil(gtr.getDescent()); + int y = 0; + int width = (int)Math.ceil(gtr.getHeight()); + int height = getCanvas().getHeight(); + trTitleRect = setRectangleBounds(trTitleRect, x, y, width, height); + } + + bounds = new Rectangle((orientation == LEFT) ? blTitleRect : trTitleRect); + return bounds; + } + + private static Rectangle setRectangleBounds(Rectangle rc, int x, int y, int width, int height) { + if (rc == null) { + return new Rectangle(x, y, width, height); + } else { + rc.setBounds(x, y, width, height); + return rc; + } + } + + /** return orientation int + * @return AttachedLabel.TOP, etc. + */ + public int getOrientation() { + return orientation; + } + + + /** true if the label is horizontal + * @return true if the label is horizontal + */ + public boolean isHorizontal() { + return orientation == BOTTOM || orientation == TOP; + } + + /** return a string for the int orientation encoding. + * @param i + * @return "top", "right", etc. + */ + protected static String orientationToString(int i) { + switch (i) { + case TOP: + return "top"; + case BOTTOM: + return "bottom"; + case LEFT: + return "left"; + case RIGHT: + return "right"; + default: throw new IllegalStateException("invalid orienation: " + i); + } + } + + /** + * @param orientationString left, right, horizontal, etc. + * @return the int encoding for the orientation parameter, + */ + protected static int parseOrientationString(String orientationString) { + if (orientationString.equals("horizontal")) { + return HORIZONTAL; + } else if (orientationString.equals("vertical")) { + return VERTICAL; + } else if (orientationString.equals("left")) { + return LEFT; + } else if (orientationString.equals("right")) { + return RIGHT; + } else if (orientationString.equals("top")) { + return TOP; + } else if (orientationString.equals("bottom")) { + return BOTTOM; + } else { + throw new IllegalArgumentException("Invalid orientation: " + orientationString); + } + } + + /** + * @return the bounds of the label + */ + public Shape getActiveRegion() { + return getLabelBounds(); + } + + /** + * Getter for property emOffset. + * @return Value of property emOffset. + */ + public double getEmOffset() { + + return this.emOffset; + } + + /** + * Setter for property emOffset. + * @param emOffset New value of property emOffset. + */ + public void setEmOffset(double emOffset) { + + this.emOffset = emOffset; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/Auralizor.java b/dasCore/src/main/java/org/das2/graph/Auralizor.java new file mode 100644 index 000000000..b14705593 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/Auralizor.java @@ -0,0 +1,90 @@ +/* + * Auralizor.java + * + * Created on April 2, 2004, 3:31 PM + */ + +package org.das2.graph; + +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import javax.sound.sampled.*; + +/** + * + * @author Owner + */ +public class Auralizor { + + private static final int EXTERNAL_BUFFER_SIZE = 1000; + byte[] buffer; + SourceDataLine line = null; + int bufferInputIndex; + double min; + double max; + Units yUnits; + + VectorDataSet ds; + + void setDataSet( VectorDataSet ds ) { + this.ds= ds; + } + + public void playSound() { + float sampleRate= (float) ( 1. / ds.getXTagDatum(1).subtract(ds.getXTagDatum(0)).doubleValue(Units.seconds) ) ; + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("sampleRate= "+sampleRate); + AudioFormat audioFormat= new AudioFormat( sampleRate, 8, 1, true, false ); + + DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); + try { + line = (SourceDataLine) AudioSystem.getLine(info); + line.open(audioFormat); + line.addLineListener(getLineListener()); + } + catch (Exception e) { + throw new RuntimeException(e); + } + buffer = new byte[EXTERNAL_BUFFER_SIZE]; + bufferInputIndex= 0; + line.start(); + + int i=0; + int ibuf=0; + while ( i xhigh2 ) return; + + GeneralPath gp= new GeneralPath(); + GeneralPath fillPath= new GeneralPath(); + + gp.moveTo( xlow1,y1-hlen ); fillPath.moveTo( xlow1,y1-hlen ); + gp.lineTo( xlow1,y1 ); fillPath.lineTo( xlow1,y1 ); + gp.lineTo( xlow2,y2 ); fillPath.lineTo( xlow2,y2 ); + gp.lineTo( xlow2,y3 ); fillPath.lineTo( xlow2,y3 ); + gp.moveTo( xhigh2, y3 ); + fillPath.lineTo( xhigh2,y3 ); + gp.lineTo( xhigh2,y2 ); fillPath.lineTo( xhigh2,y2 ); + gp.lineTo( xhigh1,y1 ); fillPath.lineTo( xhigh1,y1 ); + gp.lineTo( xhigh1,y1-hlen ); fillPath.lineTo( xhigh1,y1-hlen ); + + if ( fill ) { + g.setColor( fillColor ); + g.fill(fillPath); + } + + g.setColor( getForeground() ); + + g.draw( gp ); + + if ( bottomCurtain && topPlot.getYAxis().getUnits().isConvertableTo( bottomPlot.getYAxis().getUnits() ) ) { + + DatumRange drtop= topPlot.getYAxis().getDatumRange(); + DatumRange yaxisRange= bottomPlot.getYAxis().getDatumRange(); + + drtop= DatumRangeUtil.sloppyIntersection( yaxisRange, drtop ); + + int y5,y6; + + y5= (int)bottomPlot.getYAxis().transform( drtop.max() ); + y6= (int)bottomPlot.getYAxis().transform( drtop.min() ); + + if ( curtainOpacityPercent > 0 ) { + int xLeft= (int)topPlot.getXAxis().getColumn().getDMinimum(); + int xRight= (int)bottomPlot.getXAxis().getColumn().getDMaximum(); + Color canvasColor= getCanvas().getBackground(); + Color curtainColor= new Color( canvasColor.getRed(), canvasColor.getGreen(), canvasColor.getBlue(), + curtainOpacityPercent * 255 / 100 ); + + GeneralPath gpfill= new GeneralPath( DasRow.toRectangle(bottomPlot.getRow(),bottomPlot.getColumn() ) ); + gpfill.append( new Rectangle( xlow2, y5, xhigh2-xlow2, y6-y5 ), false ); + gpfill.setWindingRule( GeneralPath.WIND_EVEN_ODD ); + + g.setColor( curtainColor ); + g.fill( gpfill ); + //g.fillRect( xLeft, y3+1, xlow2-xLeft, y4-y3-1 ); + //g.fillRect( xhigh2+1, y3+1, xRight-xhigh2-1, y4-y3-1 ); + g.setColor( getForeground() ); + } + + if ( yaxisRange.contains(drtop.max()) )g.drawLine( xlow2, y5, xhigh2, y5 ); + if ( yaxisRange.contains(drtop.min()) && drtop.min().gt( yaxisRange.min() ) ) g.drawLine( xlow2, y6, xhigh2, y6 ); + g.drawLine( xlow2, y3, xlow2, y4 ); + g.drawLine( xhigh2, y3, xhigh2, y4 ); + + + } + + g.dispose(); + + getDasMouseInputAdapter().paint(g1); + } + + public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) { + markDirty(); + update(); + } + + /** + * Holds value of property fillColor. + */ + private Color fillColor= new Color( 240,240,240,255 ); + + /** + * Utility field used by bound properties. + */ + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + /** + * Getter for property fillColor. + * @return Value of property fillColor. + */ + public Color getFillColor() { + return this.fillColor; + } + + /** + * Setter for property fillColor. + * @param fillColor New value of property fillColor. + */ + public void setFillColor(Color fillColor) { + Color oldFillColor = this.fillColor; + this.fillColor = fillColor; + repaint(); + propertyChangeSupport.firePropertyChange("fillColor", oldFillColor, fillColor); + } + + /** + * Holds value of property fill. + */ + private boolean fill=false; + + /** + * Getter for property fill. + * @return Value of property fill. + */ + public boolean isFill() { + return this.fill; + } + + /** + * Setter for property fill. + * @param fill New value of property fill. + */ + public void setFill(boolean fill) { + boolean oldFill = this.fill; + this.fill = fill; + repaint(); + propertyChangeSupport.firePropertyChange("fill", new Boolean(oldFill), new Boolean(fill)); + } + + /** + * Holds value of property bottomCurtain. + */ + private boolean bottomCurtain; + + /** + * Getter for property bottomCurtain. + * @return Value of property bottomCurtain. + */ + public boolean isBottomCurtain() { + return this.bottomCurtain; + } + + /** + * Setter for property bottomCurtain. + * @param bottomCurtain New value of property bottomCurtain. + */ + public void setBottomCurtain(boolean bottomCurtain) { + boolean oldBottomCurtain = this.bottomCurtain; + this.bottomCurtain = bottomCurtain; + repaint(); + propertyChangeSupport.firePropertyChange("bottomCurtain", new Boolean(oldBottomCurtain), new Boolean(bottomCurtain)); + } + + /** + * Holds value of property curtainOpacityPercent. + */ + private int curtainOpacityPercent= 40; + + /** + * Getter for property curtainOpacityPercent. + * @return Value of property curtainOpacityPercent. + */ + public int getCurtainOpacityPercent() { + return this.curtainOpacityPercent; + } + + /** + * Setter for property curtainOpacityPercent. + * @param curtainOpacityPercent New value of property curtainOpacityPercent. + */ + public void setCurtainOpacityPercent(int curtainOpacityPercent) { + int oldCurtainOpacityPercent = this.curtainOpacityPercent; + this.curtainOpacityPercent = Math.max( 0, Math.min( 100, curtainOpacityPercent ) ); + repaint(); + propertyChangeSupport.firePropertyChange("curtainOpacityPercent", new Integer(oldCurtainOpacityPercent), new Integer(curtainOpacityPercent)); + } + +} + diff --git a/dasCore/src/main/java/org/das2/graph/ContoursRenderer.java b/dasCore/src/main/java/org/das2/graph/ContoursRenderer.java new file mode 100755 index 000000000..bc3a89f49 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/ContoursRenderer.java @@ -0,0 +1,455 @@ +/* + * ContoursRenderer.java + * + * Created on December 7, 2007, 2:47 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ +package org.das2.graph; + +import org.das2.DasException; +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.ClippedTableDataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import org.das2.math.Contour; +import java.awt.BasicStroke; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Renderer for making contour plots + * @author jbf + */ +public class ContoursRenderer extends Renderer implements Displayable { + + /** Creates a new instance of ContoursRenderer */ + public ContoursRenderer() { + } + GeneralPath[] paths; + String[] pathLabels; + + public synchronized void render(Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + Graphics2D g = (Graphics2D) g1; + + if (parent.getCanvas().isAntiAlias()) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + if (paths == null) { + return; + } + g.setColor(color); + g.setStroke( new BasicStroke((float)lineThick) ); + + if (drawLabels) { + Area labelClip = paintLabels(g); + + Shape rclip = g.getClip() == null ? new Rectangle(parent.getX(), parent.getY(), parent.getWidth(), parent.getHeight()) : g.getClip(); + Area clip = new Area(rclip); + clip.subtract(labelClip); + g.setClip(clip); + //g.draw( labelClip ); + + } + + for (int i = 0; i < paths.length; i++) { + if (paths[i] != null) { + g.draw(paths[i]); + } + } + + } + + /** + * returns clip, in the canvas reference frame + */ + private Area paintLabels(final Graphics2D g) { + + Area clip = new Area(); + + Font font0 = g.getFont(); + + // do labels + AffineTransform at0 = g.getTransform(); + + Font font = font0.deriveFont(8f); + + g.setFont(font); + + String[] cons = this.contours.trim().split(","); + + double minLength= 20; + for (int i = 0; i < paths.length; i++) { + if (paths[i] == null) { + continue; + } + String label = pathLabels[i]; + GeneralPath p = paths[i]; + + if (p != null) { + + PathIterator it1 = p.getPathIterator(null); + PathIterator it2 = p.getPathIterator(null); + + while (!it1.isDone()) { + + double len = GraphUtil.pointsAlongCurve(it1, null, null, null, true); + + int nlabel = 1 + (int) Math.floor( len / this.labelCadence ); + + double phase = (len - ( nlabel-1 ) * labelCadence ) / 2; + + if ( len < minLength ) { + //advance it2. + GraphUtil.pointsAlongCurve(it2, null, null, null, true); + + } else { + double[] lens = new double[nlabel*2]; + double labelWidth=10; // approx. + if ( labelWidth > labelCadence ) labelWidth= labelCadence * 0.99; + for (int ilabel = 0; ilabel < nlabel; ilabel++) { + lens[ilabel*2] = phase + labelCadence * ilabel; + lens[ilabel*2+1] = phase + labelCadence * ilabel + labelWidth; + } + Point2D.Double[] points = new Point2D.Double[nlabel*2]; + double[] orient = new double[nlabel*2]; + + //advance it2. + GraphUtil.pointsAlongCurve(it2, lens, points, orient, true); + + for (int ilabel = 0; ilabel < nlabel; ilabel++) { + AffineTransform at = new AffineTransform(); + at.translate(points[ilabel*2].x, points[ilabel*2].y); + //double dx= points[ilabel*2+1].x - points[ilabel*2].x; + //double dy= points[ilabel*2+1].y - points[ilabel*2].y; + //double orient1= Math.atan2(dy,dx); + at.rotate(orient[ilabel*2]); + //at.rotate(orient1); + + + Rectangle2D sbounds = g.getFontMetrics().getStringBounds(label, g); + double w = sbounds.getWidth(); + + GeneralPath rect = new GeneralPath(sbounds); + rect.transform(AffineTransform.getTranslateInstance(-w / 2, 0)); + rect.transform(at); + clip.add(new Area(rect)); + + AffineTransform gat= new AffineTransform(at0); + gat.concatenate(at); + + g.setTransform( gat ); + g.setColor(color); + g.drawString(label, (int) (-w / 2), 0); + } + } + } + } + } + g.setTransform(at0); + + return clip; + } + + protected void installRenderer() { + } + + protected void uninstallRenderer() { + } + + protected Element getDOMElement(Document document) { + return null; + } + + public Icon getListIcon() { + return new ImageIcon(SpectrogramRenderer.class.getResource("/images/icons/contoursRenderer.png")); + } + + public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor) throws DasException { + super.updatePlotImage(xAxis, yAxis, monitor); + + TableDataSet tds = (TableDataSet) getDataSet(); + + if (tds == null) { + return; + } + tds = new ClippedTableDataSet(tds, xAxis.getDatumRange(), yAxis.getDatumRange()); + + Units units = tds.getZUnits(); + + String[] cons = this.contours.trim().split(","); + double[] dcons = new double[cons.length]; + for (int i = 0; i < cons.length; i++) { + if (cons[i].trim().equals("")) { + continue; + } + double c = Double.parseDouble(cons[i]); + dcons[i] = c; + } + DatumVector dv = DatumVector.newDatumVector(dcons, tds.getZUnits()); + + final boolean rebin= false; + if (rebin) { + RebinDescriptor xRebinDescriptor; + xRebinDescriptor = new RebinDescriptor( + xAxis.getDataMinimum(), xAxis.getDataMaximum(), + xAxis.getWidth() / 2, + xAxis.isLog()); + + RebinDescriptor yRebinDescriptor = new RebinDescriptor( + yAxis.getDataMinimum(), yAxis.getDataMaximum(), + yAxis.getHeight() / 2, + yAxis.isLog()); + + if (DataSetUtil.guessXTagWidth(tds).gt(xRebinDescriptor.binWidthDatum())) { + xRebinDescriptor = null; + } + if (TableUtil.guessYTagWidth(tds).gt(yRebinDescriptor.binWidthDatum())) { + yRebinDescriptor = null; + } + AverageTableRebinner rebinner = new AverageTableRebinner(); + rebinner.setInterpolate(false); + + if (xRebinDescriptor != null || yRebinDescriptor != null) { + tds = (TableDataSet) rebinner.rebin(tds, xRebinDescriptor, yRebinDescriptor, null); + } + } + + VectorDataSet vds = Contour.contour(tds, dv); + + paths = new GeneralPath[dv.getLength()]; + + double d0 = units.getFillDouble(); + int ii = -1; + + VectorDataSet xds = (VectorDataSet) vds.getPlanarView(Contour.PLANE_X); + VectorDataSet yds = (VectorDataSet) vds.getPlanarView(Contour.PLANE_Y); + + Units xunits = xAxis.getUnits(); + Units yunits = yAxis.getUnits(); + + ArrayList list = new ArrayList(); + ArrayList labels = new ArrayList(); + + GeneralPath currentPath = null; + + double n0 = 0; // node counter. Breaks are indicated by increment, so keep track of the last node. + + double slen = 0.; // path length + + float fx0 = 0f, fy0 = 0f; // for calculating path length + + NumberFormat nf = new DecimalFormat("0.00"); + + for (int i = 0; i < vds.getXLength(); i++) { + double d = vds.getDouble(i, units); + int n = (int) vds.getXTagDouble(i, Units.dimensionless); + + float fx = (float) xAxis.transform(xds.getDatum(i)); + float fy = (float) yAxis.transform(yds.getDatum(i)); + + if (d != d0) { + ii++; + + if ( currentPath!=null && simplifyPaths ) { + GeneralPath newPath= new GeneralPath(); + newPath= GraphUtil.reducePath( currentPath.getPathIterator(null), newPath ); + list.set( list.indexOf(currentPath), newPath ); + } + + currentPath = new GeneralPath(); + list.add(currentPath); + labels.add(nf.format(d)); + + d0 = d; + currentPath.moveTo(fx, fy); + slen = 0.; + fx0 = fx; + fy0 = fy; + } + if (n != (n0 + 1)) { + currentPath.moveTo(fx, fy); + fx0 = fx; + fy0 = fy; + slen = 0.; + } else { + currentPath.lineTo(fx, fy); + } + n0 = n; + } + + paths = (GeneralPath[]) list.toArray(new GeneralPath[list.size()]); + pathLabels = (String[]) labels.toArray(new String[labels.size()]); + } + /** + * Holds value of property contours. + */ + private String contours = "-.7,-.6,-.5,-.4,-.3,-.2,-.1,0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9"; + + /** + * Getter for property contours. + * @return Value of property contours. + */ + public String getContours() { + return this.contours; + } + + /** + * Setter for property contours. + * @param contours New value of property contours. + */ + public void setContours(String contours) { + String oldContours = this.contours; + this.contours = contours; + update(); + propertyChangeSupport.firePropertyChange("contours", oldContours, contours); + } + /** + * property labelCadence, the inter-label distance, in ems. + */ + private double labelCadence = 100; + + /** + * Getter for property labelCadence. + * @return Value of property labelCadence. + */ + public double getLabelCadence() { + return this.labelCadence; + } + + /** + * Setter for property labelCadence. + * @param labelCadence New value of property labelCadence. + */ + public void setLabelCadence(double labelCadence) { + double oldLabelCadence = this.labelCadence; + this.labelCadence = labelCadence; + update(); + propertyChangeSupport.firePropertyChange("labelCadence", new Double(oldLabelCadence), new Double(labelCadence)); + } + + public boolean acceptContext(int x, int y) { + if (paths == null) { + return false; + } + for (int i = 0; i < paths.length; i++) { + if (paths[i] != null) { + if (paths[i].intersects(x - 2, y - 2, 5, 5)) { + return true; + } + } + } + return false; + } + /** + * Holds value of property drawLabels. + */ + private boolean drawLabels; + + /** + * Getter for property drawLabels. + * @return Value of property drawLabels. + */ + public boolean isDrawLabels() { + return this.drawLabels; + } + + /** + * Setter for property drawLabels. + * @param drawLabels New value of property drawLabels. + */ + public void setDrawLabels(boolean drawLabels) { + boolean oldDrawLabels = this.drawLabels; + this.drawLabels = drawLabels; + update(); + propertyChangeSupport.firePropertyChange("drawLabels", new Boolean(oldDrawLabels), new Boolean(drawLabels)); + } + /** + * Holds value of property color. + */ + private Color color = Color.BLACK; + + /** + * Getter for property color. + * @return Value of property color. + */ + public Color getColor() { + return this.color; + } + + /** + * Setter for property color. + * @param color New value of property color. + */ + public void setColor(Color color) { + Color oldColor = this.color; + this.color = color; + update(); + propertyChangeSupport.firePropertyChange("color", oldColor, color); + } + + public String getListLabel() { + return "Contours Renderer"; + } + + + private boolean simplifyPaths = true; + + public static final String PROP_SIMPLIFYPATHS = "simplifyPaths"; + + public boolean isSimplifyPaths() { + return this.simplifyPaths; + } + + public void setSimplifyPaths(boolean newsimplifyPaths) { + boolean oldsimplifyPaths = simplifyPaths; + this.simplifyPaths = newsimplifyPaths; + update(); + propertyChangeSupport.firePropertyChange(PROP_SIMPLIFYPATHS, oldsimplifyPaths, newsimplifyPaths); + } + + + private double lineThick = 1.0; + + public static final String PROP_LINETHICK = "lineThick"; + + public double getLineThick() { + return this.lineThick; + } + + public void setLineThick(double newlineThick) { + double oldlineThick = lineThick; + this.lineThick = newlineThick; + propertyChangeSupport.firePropertyChange(PROP_LINETHICK, oldlineThick, newlineThick); + } + + +} diff --git a/dasCore/src/main/java/org/das2/graph/CurveRenderer.java b/dasCore/src/main/java/org/das2/graph/CurveRenderer.java new file mode 100644 index 000000000..ccb6bbfa2 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/CurveRenderer.java @@ -0,0 +1,199 @@ +/* File: TickCurveRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 3, 2003, 11:43 AM by __FULLNAME__ <__EMAIL__> + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Units; +import org.das2.DasException; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.*; +import java.awt.geom.*; + +/** + * + * @author jbf + */ +public class CurveRenderer extends Renderer { + + private String xplane; + private String yplane; + + private Units xunits; // xUnits of the axis + private Units yunits; // yUnits of the axis + private double[][] idata; // data transformed to pixel space + + private boolean antiAliased= true; + private SymColor color= SymColor.black; + private PsymConnector psymConnector = PsymConnector.SOLID; + private Psym psym = Psym.NONE; + private double symSize = 1.0; // radius in pixels + private float lineWidth = 1.5f; // width in pixels + + private GeneralPath path; + + /** The dataset descriptor should return a Vector data set with planes identified + * by xplane and yplane. + */ + public CurveRenderer( DataSetDescriptor dsd, String xplane, String yplane ) { + super(dsd); + + setLineWidth( 1.0f ); + + this.xplane= xplane; + this.yplane= yplane; + } + + protected void uninstallRenderer() { + } + + protected void installRenderer() { + } + + public void render(java.awt.Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + long timer0= System.currentTimeMillis(); + + VectorDataSet dataSet= (VectorDataSet)getDataSet(); + + if (dataSet == null || dataSet.getXLength() == 0) { + return; + } + + VectorDataSet xds= (VectorDataSet)dataSet.getPlanarView(xplane); + VectorDataSet yds= (VectorDataSet)dataSet.getPlanarView(yplane); + + Graphics2D graphics= (Graphics2D) g1.create(); + + RenderingHints hints0= graphics.getRenderingHints(); + if ( antiAliased ) { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } else { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + + graphics.setColor(color.toColor()); + + if (path != null) { + psymConnector.draw(graphics, path, (float)lineWidth); + } + + Dimension d; + + double xmin, xmax, ymin, ymax; + + org.das2.datum.Units xUnits= xAxis.getUnits(); + org.das2.datum.Units yUnits= yAxis.getUnits(); + + Rectangle r= g1.getClipBounds(); + + if ( r==null ) { + xmax= xAxis.getDataMaximum().doubleValue(xUnits); + xmin= xAxis.getDataMinimum().doubleValue(xUnits); + ymax= yAxis.getDataMaximum().doubleValue(yUnits); + ymin= yAxis.getDataMinimum().doubleValue(yUnits); + } else { + xmin= xAxis.invTransform((int)r.getX()).doubleValue(xUnits); + xmax= xAxis.invTransform((int)(r.getX()+r.getWidth())).doubleValue(xUnits); + ymin= yAxis.invTransform((int)r.getY()).doubleValue(yUnits); + ymax= yAxis.invTransform((int)(r.getY()+r.getHeight())).doubleValue(yUnits); + } + + + for (int index = 0; index < xds.getXLength(); index++) { + if ( ! yUnits.isFill(yds.getDouble(index,yUnits)) ) { + double i = xAxis.transform(xds.getDouble(index,xUnits),xUnits); + double j = yAxis.transform(yds.getDouble(index,yUnits),yUnits); + if ( Double.isNaN(j) ) { + //DasApplication.getDefaultApplication().getDebugLogger().warning("got NaN"); + } else { + psym.draw( g1, i, j, (float)symSize ); + } + } + } + + graphics.setRenderingHints(hints0); + } + + public void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor) throws DasException { + super.updatePlotImage( xAxis, yAxis, monitor ); + + VectorDataSet dataSet= (VectorDataSet)getDataSet(); + + if (dataSet == null || dataSet.getXLength() == 0) { + return; + } + + VectorDataSet xds= (VectorDataSet)dataSet.getPlanarView(xplane); + VectorDataSet yds= (VectorDataSet)dataSet.getPlanarView(yplane); + + path= GraphUtil.getPath( xAxis, yAxis, xds, yds, false ); + } + + /** Getter for property lineWidth. + * @return Value of property lineWidth. + * + */ + public double getLineWidth() { + return this.lineWidth; + } + + /** Setter for property lineWidth. + * @param lineWidth New value of property lineWidth. + * + */ + public void setLineWidth(double lineWidth) { + this.lineWidth = (float)lineWidth; + } + + protected org.w3c.dom.Element getDOMElement(org.w3c.dom.Document document) { + throw new UnsupportedOperationException(); + } + + public PsymConnector getPsymConnector() { + return psymConnector; + } + + public void setPsymConnector(PsymConnector p) { + psymConnector = p; + refreshImage(); + } + + /** Getter for property psym. + * @return Value of property psym. + */ + public Psym getPsym() { + return this.psym; + } + + + /** Setter for property psym. + * @param psym New value of property psym. + */ + public void setPsym(Psym psym) { + if (psym == null) throw new NullPointerException("psym cannot be null"); + Object oldValue = this.psym; + this.psym = psym; + refreshImage(); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasAnnotation.java b/dasCore/src/main/java/org/das2/graph/DasAnnotation.java new file mode 100644 index 000000000..9a87cbf86 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasAnnotation.java @@ -0,0 +1,377 @@ +/* + * DasAnnotation.java + * + * Created on December 20, 2004, 2:32 PM + */ +package org.das2.graph; + +import org.das2.util.GrannyTextRenderer; +import org.das2.datum.Datum; +import org.das2.event.ArrowDragRenderer; +import org.das2.event.MouseModule; +import org.das2.event.MoveComponentMouseModule; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenuItem; + +/** + * This component-izes a GrannyTextRenderer, and composes with an Arrow. + * @author Jeremy + */ +public class DasAnnotation extends DasCanvasComponent { + + String templateString; + GrannyTextRenderer gtr; + /** + * point at this thing + */ + private DasAnnotation.PointDescriptor pointAt; + private MouseModule arrowToMouseModule; + + /** Creates a new instance of DasAnnotation */ + public DasAnnotation(String string) { + super(); + this.gtr = new GrannyTextRenderer(); + this.templateString = string; + + Action removeArrowAction = new AbstractAction("remove arrow") { + + public void actionPerformed(ActionEvent e) { + pointAt = null; + repaint(); + } + }; + + this.getDasMouseInputAdapter().addMenuItem(new JMenuItem(removeArrowAction)); + + Action removeMeAction = new AbstractAction("remove") { + + public void actionPerformed(ActionEvent e) { + DasCanvas canvas = getCanvas(); + // TODO: confirm dialog + canvas.remove(DasAnnotation.this); + canvas.revalidate(); + } + }; + + this.getDasMouseInputAdapter().addMenuItem(new JMenuItem(removeMeAction)); + + MouseModule mm = new MoveComponentMouseModule(this); + this.getDasMouseInputAdapter().setPrimaryModule(mm); + + arrowToMouseModule = createArrowToMouseModule(this); + this.getDasMouseInputAdapter().setSecondaryModule(arrowToMouseModule); + } + + public static class DatumPairPointDescriptor implements PointDescriptor { + + DasPlot p; + Datum x; + Datum y; + + public DatumPairPointDescriptor(DasPlot p, Datum x, Datum y) { + this.x = x; + this.y = y; + this.p = p; + } + + public Point getPoint() { + int ix = (int) (p.getXAxis().transform(x)); + int iy = (int) (p.getYAxis().transform(y)); + return new Point(ix, iy); + } + + public String getLabel() { + return "" + x + "," + y; + } + } + + private MouseModule createArrowToMouseModule(final DasAnnotation anno) { + return new MouseModule(DasAnnotation.this, new ArrowDragRenderer(), "Point At") { + + Point head; + Point tail; + + @Override + public void mousePressed(MouseEvent e) { + super.mousePressed(e); + tail= e.getPoint(); + tail.translate(anno.getX(), anno.getY()); + Rectangle r= DasAnnotation.this.getActiveRegion().getBounds(); + if ( !r.contains(tail) ) { + tail= null; + } + } + + @Override + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + if ( tail==null ) return; + head = e.getPoint(); + head.translate(anno.getX(), anno.getY()); + DasCanvasComponent c = parent.getCanvas().getCanvasComponentAt(head.x, head.y); + if (c instanceof DasPlot) { + final DasPlot p = (DasPlot) c; + final Datum x = p.getXAxis().invTransform(head.x); + final Datum y = p.getYAxis().invTransform(head.y); + anno.setPointAt(new DatumPairPointDescriptor(p, x, y)); + setBounds(calcBounds()); + } + + } + }; + } + + public void setText(String string) { + this.templateString = string; + if ( this.getGraphics()!=null ) { + gtr.setString( this.getGraphics(), getString() ); + calcBounds(); + } + + repaint(); + + } + + public String getText() { + return templateString; + } + + @Override + public void resize() { + super.resize(); + this.gtr.setString(this.getGraphics(), getString() ); + Rectangle r= calcBounds(); + setBounds(r); + } + + @Override + public Shape getActiveRegion() { + Rectangle r = gtr.getBounds(); + int em = (int) getEmSize() / 2; + r = new Rectangle(r.x, r.y + (int) gtr.getAscent(), r.width + 2 * em + 3, r.height + 2 * em + 3); + r.translate(getColumn().getDMinimum(), getRow().getDMinimum()); + return r; + } + + @Override + public boolean acceptContext(int x, int y) { + if ( getActiveRegion().contains( x, y ) ) { + return true; + } else if ( pointAt!=null ) { + if ( pointAt.getPoint().distance(x,y) < 5 ) { + return true; + } + } + return false; + } + + + private Rectangle calcBounds() { + Rectangle r = (Rectangle)getActiveRegion(); + int em = (int) getEmSize() / 2; + if (pointAt != null) { + Point head = pointAt.getPoint(); + r.add(head); + } + r.x-= em; + r.y-= em; + r.width+= em*2; + r.height+= em*2; + + return r; + } + + @Override + public void paintComponent(Graphics g1) { + + // TODO: need to draw based on row, col, not on bounds which may move with arrow. + + Graphics2D g = (Graphics2D) g1.create(); //SVG bug + + g.translate( getColumn().getDMinimum()-getX(), getRow().getDMinimum()-getY() ); + + Color fore = g.getColor(); + + Color canvasColor = getCanvas().getBackground(); + Color back = new Color(canvasColor.getRed(), canvasColor.getGreen(), canvasColor.getBlue(), + 80 * 255 / 100); + if ( fontSize>0 ) g.setFont( getFont().deriveFont(fontSize) ); + + int em = (int) getEmSize() / 2; + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + gtr.setString( g, getString() ); + Rectangle r = gtr.getBounds(); + + r.x = em; + r.y = em; + + r = new Rectangle(r.x - em + 1, r.y - em + 1, r.width + 2 * em - 1, r.height + 2 * em - 1); + + //r.translate( em, em + (int) gtr.getAscent()); + g.setColor(back); + + if (borderType == BorderType.RECTANGLE || borderType == BorderType.NONE) { + g.fill(r); + } else if (borderType == BorderType.ROUNDED_RECTANGLE) { + g.fillRoundRect(r.x, r.y, r.width, r.height, em * 2, em * 2); + } + + g.setColor(fore); + + gtr.draw(g, em, em + (float) gtr.getAscent()); + + if (pointAt != null) { + double em2 = getCanvas().getFont().getSize(); + g.setStroke(new BasicStroke((float) (em2 / 8))); + //g.drawLine( r.x, r.y+r.height, r.x+r.width, r.y+r.height ); + + Point head = pointAt.getPoint(); + head.translate(-getColumn().getDMinimum(), -getRow().getDMinimum()); + int tx = Math.min(head.x, r.x + r.width * 2 / 3); + tx = Math.max(tx, r.x + r.width * 1 / 3); + Point tail = new Point(tx, r.y + r.height); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setClip(null); + //Arrow.paintArrow(g2, head, tail, em2); + + Point2D tail2d= new Point2D.Double( r.x + r.width/2, r.y + r.y + r.height/2 ); + Point2D head2d= new Point2D.Double( head.x, head.y ); + Rectangle2D rect2d= new Rectangle2D.Double(r.x, r.y, r.width, r.height ); + Point2D p2d= GraphUtil.lineRectangleIntersection( tail2d, head2d, rect2d ); + Point p= p2d==null ? head : new Point( (int)p2d.getX(), (int)p2d.getY() ); + Arrow.paintArrow(g2, head, p, em2, this.arrowStyle ); + + } + + if (borderType != BorderType.NONE) { + if (borderType == BorderType.RECTANGLE) { + g.draw(r); + } else if (borderType == BorderType.ROUNDED_RECTANGLE) { + g.drawRoundRect(r.x, r.y, r.width, r.height, em * 2, em * 2); + } + + } + + /*r= DasColumn.toRectangle( getRow(), getColumn() ); + r.translate( -getX(), -getY() ); + r.width--; + r.height--; + ((Graphics2D)g1).draw( r ); + */ + + g.dispose(); + + getDasMouseInputAdapter().paint(g1); + + } + + public interface PointDescriptor { + + Point getPoint(); + + String getLabel(); + } + + public void setPointAt(PointDescriptor p) { + this.pointAt = p; + repaint(); + } + + private String getString() { + String s = templateString; + if (this.templateString != null && this.templateString.contains("%") && pointAt!=null ) { + s = templateString.replace("%p", pointAt.getLabel() ); + } + return s; + } + + public PointDescriptor getPointAt() { + return this.pointAt; + } + + @Override + protected void installComponent() { + super.installComponent(); + this.gtr.setString( this.getFont(), getString() ); + } + + float fontSize= 0; + + /** + * Getter for property fontSize. + * @return Value of property fontSize. + */ + public float getFontSize() { + return fontSize; + } + + /** + * override the canvas font size. If zero, then use the canvas size, otherwise, + * use this size. + * + * @param fontSize New value of property fontSize. + */ + public void setFontSize(float fontSize) { + this.fontSize= fontSize; + Font f = getFont(); + if (f == null) { + f = getCanvas().getBaseFont(); + } + Font newFont= f; + if ( fontSize>0 ) f= f.deriveFont(fontSize); + Graphics g= this.getGraphics(); + if ( g==null ) return; + g.setFont(newFont); + gtr.setString( g, getString() ); + setBounds(calcBounds()); + repaint(); + + } + + + + public enum BorderType { + NONE, RECTANGLE, ROUNDED_RECTANGLE + } + private BorderType borderType = BorderType.NONE; + public static final String PROP_BORDERTYPE = "borderType"; + + public BorderType getBorderType() { + return this.borderType; + } + + public void setBorderType(BorderType newborderType) { + BorderType oldborderType = borderType; + this.borderType = newborderType; + repaint(); + + firePropertyChange(PROP_BORDERTYPE, oldborderType, newborderType); + } + + + + private Arrow.HeadStyle arrowStyle = Arrow.HeadStyle.DRAFTING; + + public static final String PROP_ARROWSTYLE = "arrowStyle"; + + public Arrow.HeadStyle getArrowStyle() { + return this.arrowStyle; + } + + public void setArrowStyle( Arrow.HeadStyle newarrowStyle) { + Arrow.HeadStyle oldarrowStyle = arrowStyle; + this.arrowStyle = newarrowStyle; + repaint(); + firePropertyChange(PROP_ARROWSTYLE, oldarrowStyle, newarrowStyle); + } + + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasAnnotationBeanInfo.java b/dasCore/src/main/java/org/das2/graph/DasAnnotationBeanInfo.java new file mode 100644 index 000000000..4a1200659 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasAnnotationBeanInfo.java @@ -0,0 +1,82 @@ +/* File: DasColorBarBeanInfo.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.beans.DasCanvasComponentBeanInfo; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.beans.SimpleBeanInfo; +import org.das2.components.propertyeditor.EnumerationEditor; + +/** + * BeanInfo class for DasColorBar + * + * @author Edward West + */ +public class DasAnnotationBeanInfo extends SimpleBeanInfo { + + private static PropertyDescriptor[] properties; + static { + try { + properties = new PropertyDescriptor[] { + new PropertyDescriptor("text", DasAnnotation.class), + new PropertyDescriptor("borderType", DasAnnotation.class), + new PropertyDescriptor("arrowStyle", DasAnnotation.class ), + new PropertyDescriptor("fontSize", DasAnnotation.class ), + }; + properties[1].setPropertyEditorClass( EnumerationEditor.class ); + properties[2].setPropertyEditorClass( EnumerationEditor.class ); + System.err.println("yeah!!!"); + } catch ( IntrospectionException e) { + e.printStackTrace(); + properties= null; + } + + } + + + @Override + public PropertyDescriptor[] getPropertyDescriptors() { + return properties; + } + + @Override + public BeanInfo[] getAdditionalBeanInfo() { + BeanInfo[] additional = { + new DasCanvasComponentBeanInfo(), + }; + return additional; + + /*try { + BeanInfo[] additional = { + Introspector.getBeanInfo( DasAxis.class ), + }; + return additional; + } catch ( IntrospectionException e ) { + throw new RuntimeException(e); + }*/ + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasAxis.java b/dasCore/src/main/java/org/das2/graph/DasAxis.java new file mode 100755 index 000000000..33906eb5e --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasAxis.java @@ -0,0 +1,3399 @@ +/* File: DasAxis.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.*; +import java.util.*; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.*; + +import javax.swing.*; +import javax.swing.border.*; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasNameException; +import org.das2.DasProperties; +import org.das2.DasPropertyException; +import org.das2.NameContext; +import org.das2.dasml.FormBase; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.DataSetUpdateEvent; +import org.das2.dataset.DataSetUpdateListener; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.DatumVector; +import org.das2.datum.InconvertibleUnitsException; +import org.das2.datum.TimeLocationUnits; +import org.das2.datum.TimeUtil; +import org.das2.datum.Units; +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.format.DatumFormatterFactory; +import org.das2.datum.format.DefaultDatumFormatterFactory; +import org.das2.datum.format.TimeDatumFormatterFactory; +import org.das2.event.DataRangeSelectionEvent; +import org.das2.event.DataRangeSelectionListener; +import org.das2.event.HorizontalRangeSelectorMouseModule; +import org.das2.event.MouseModule; +import org.das2.event.TimeRangeSelectionEvent; +import org.das2.event.TimeRangeSelectionListener; +import org.das2.event.VerticalRangeSelectorMouseModule; +import org.das2.event.ZoomPanMouseModule; +import org.das2.system.DasLogger; +import org.das2.util.DasExceptionHandler; +import org.das2.util.DasMath; +import org.das2.util.Entities; +import org.das2.util.GrannyTextRenderer; +import org.das2.util.monitor.NullProgressMonitor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * One dimensional axis component that transforms data to device space and back, + * and provides controls for nagivating the 1-D data space. + * @author eew + */ +public class DasAxis extends DasCanvasComponent + implements DataRangeSelectionListener, TimeRangeSelectionListener, Cloneable { + + public static final String PROP_LABEL = "label"; + public static final String PROP_Y_LABEL = "yLabel"; + public static final String PROP_LOG = "log"; + public static final String PROP_OPPOSITE_AXIS_VISIBLE = "oppositeAxisVisible"; + public static final String PROP_BOUNDS = "bounds"; + + /* + * PUBLIC CONSTANT DECLARATIONS + */ + /** This value indicates that the axis should be located at the top of its cell */ + public static final int TOP = 1; + /** This value indicates that the axis should be located at the bottom of its cell */ + public static final int BOTTOM = 2; + /** This value indicates that the axis should be located to the left of its cell */ + public static final int LEFT = 3; + /** This value indicateds that the axis should be located to the right of its cell */ + public static final int RIGHT = 4; + /** This value indicates that the axis should be oriented horizontally */ + public static final int HORIZONTAL = BOTTOM; + /** This value indicates that the axis should be oriented vertically */ + public static final int VERTICAL = LEFT; + /** */ + public static final int UP = 995; + /** */ + public static final int DOWN = 996; + /* Constants defining the action commands and labels for the scan buttons. */ + private static final String SCAN_PREVIOUS_LABEL = "<< scan"; + private static final String SCAN_NEXT_LABEL = "scan >>"; + + public static String PROP_UNITS= "units"; + + /* GENERAL AXIS INSTANCE MEMBERS */ + protected DataRange dataRange; + public static String PROPERTY_TICKS = "ticks"; + + public DatumFormatter getUserDatumFormatter() { + return userDatumFormatter; + } + + public void setUserDatumFormatter(DatumFormatter userDatumFormatter) { + this.userDatumFormatter = userDatumFormatter; + updateTickV(); + repaint(); + } + /** + * if non-null, try to use this formatter. + */ + private DatumFormatter userDatumFormatter = null; + + /** + * until we switch to java 1.5, use this lock object instead of + * java.util.concurrent.lock + */ + public interface Lock { + + public void lock(); + + public void unlock(); + } + /* Affine Transform, dependent on min, max and axis position + * pixel= at_m * data + at_b + * where data is data point in linear space (i.e. log property implemented) + */ + double at_m; + double at_b; + private int orientation; + private int tickDirection = 1; // 1=down or left, -1=up or right + protected String axisLabel = ""; + protected TickVDescriptor tickV; + protected boolean autoTickV = true; + private boolean ticksVisible = true; + private boolean tickLabelsVisible = true; + private boolean oppositeAxisVisible = false; + protected DatumFormatter datumFormatter = DefaultDatumFormatterFactory.getInstance().defaultFormatter(); + private MouseModule zoom = null; + private PropertyChangeListener dataRangePropertyListener; + protected JPanel primaryInputPanel; + protected JPanel secondaryInputPanel; + private ScanButton scanPrevious; + private ScanButton scanNext; + private boolean animated = ("on".equals(DasProperties.getInstance().get("visualCues"))); + /* Rectangles representing different areas of the axis */ + private Rectangle blLineRect; + private Rectangle trLineRect; + private Rectangle blTickRect; + private Rectangle trTickRect; + private Rectangle blLabelRect; + private Rectangle trLabelRect; + private Rectangle blTitleRect; + private Rectangle trTitleRect; + /** TODO: Currently under implemented! */ + private boolean flipped; + /* TIME LOCATION UNITS RELATED INSTANCE MEMBERS */ + private javax.swing.event.EventListenerList timeRangeListenerList = null; + private TimeRangeSelectionEvent lastProcessedEvent = null; + /* TCA RELATED INSTANCE MEMBERS */ + private DataSetDescriptor dsd; + private VectorDataSet[] tcaData = new VectorDataSet[0]; + private DatumFormatter[] tcaFormatters = new DatumFormatter[0]; + private String dataset = ""; + private boolean drawTca; + private static DatumFormatterFactory formatterFactory + = DefaultDatumFormatterFactory.getInstance(); + private static DatumFormatterFactory timeFormatterFactory + = TimeDatumFormatterFactory.getInstance(); + + public static String PROPERTY_DATUMRANGE = "datumRange"; + + /* DEBUGGING INSTANCE MEMBERS */ + private static boolean DEBUG_GRAPHICS = false; + private static final Color[] DEBUG_COLORS; + + static { + if (DEBUG_GRAPHICS) { + DEBUG_COLORS = new Color[]{ + Color.BLACK, Color.RED, Color.GREEN, Color.BLUE, + Color.GRAY, Color.CYAN, Color.MAGENTA, Color.YELLOW, + }; + } else { + DEBUG_COLORS = null; + } + } + private int debugColorIndex = 0; + private DasPlot dasPlot; + private JMenu favoritesMenu; + private JMenu backMenu; + private static final Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + + /** TODO + * @param min + * @param max + * @param orientation DasAxis.VERTICAL, DasAxis.HORIZONTAL, DasAxis.RIGHT, etc. + */ + public DasAxis(Datum min, Datum max, int orientation) { + this(min, max, orientation, false); + } + + /** TODO Tell us if these are screen or data coordinates + * @param min + * @param max + * @param orientation + * @param log + */ + public DasAxis(Datum min, Datum max, int orientation, boolean log) { + this(orientation); + dataRange = new DataRange(this, min, max, log); + addListenersToDataRange(dataRange, dataRangePropertyListener); + copyFavorites(); + copyHistory(); + } + + /** TODO + * @param range + * @param orientation + */ + protected DasAxis(DataRange range, int orientation) { + this(orientation); + dataRange = range; + addListenersToDataRange(range, dataRangePropertyListener); + copyFavorites(); + copyHistory(); + } + + private void addListenersToDataRange(DataRange range, PropertyChangeListener listener) { + range.addPropertyChangeListener(PROP_LOG, listener); + range.addPropertyChangeListener("minimum", listener); + range.addPropertyChangeListener("maximum", listener); + range.addPropertyChangeListener(DataRange.PROPERTY_DATUMRANGE, listener); + range.addPropertyChangeListener("history", listener); + range.addPropertyChangeListener("favorites", listener); + } + + public DasAxis(DatumRange range, int orientation) { + this(range.min(), range.max(), orientation); + } + + private DasAxis(int orientation) { + super(); + setOpaque(false); + setOrientationInternal(orientation); + installMouseModules(); + if (!DasApplication.getDefaultApplication().isHeadless()) { + backMenu = new JMenu("Back"); + mouseAdapter.addMenuItem(backMenu); + favoritesMenu = new JMenu("Favorites"); + mouseAdapter.addMenuItem(favoritesMenu); + } + dataRangePropertyListener = createDataRangePropertyListener(); + setLayout(new AxisLayoutManager()); + maybeInitializeInputPanels(); + maybeInitializeScanButtons(); + add(primaryInputPanel); + add(secondaryInputPanel); + } + + public void addToFavorites(final DatumRange range) { + dataRange.addToFavorites(range); + copyFavorites(); + } + + private void copyFavorites() { + if (DasApplication.getDefaultApplication().isHeadless()) { + return; + } + favoritesMenu.removeAll(); + List favorites = dataRange.getFavorites(); + for (Iterator i = favorites.iterator(); i.hasNext();) { + final DatumRange r = (DatumRange) i.next(); // copied code from addToFavorites + Action action = new AbstractAction(r.toString()) { + + public void actionPerformed(ActionEvent e) { + DasAxis.this.setDatumRange(r); + } + }; + JMenuItem menuItem = new JMenuItem(action); + favoritesMenu.add(menuItem); + } + Action action = new AbstractAction("add to favorites") { + + public void actionPerformed(ActionEvent e) { + DasAxis.this.addToFavorites(DasAxis.this.getDatumRange()); + } + }; + JMenuItem addItem = new JMenuItem(action); + favoritesMenu.add(addItem); + } + + private void copyHistory() { + if (DasApplication.getDefaultApplication().isHeadless()) { + return; + } + backMenu.removeAll(); + List history = dataRange.getHistory(); + int ii = 0; + for (Iterator i = history.iterator(); i.hasNext();) { + final int ipop = ii; + final DatumRange r = (DatumRange) i.next(); // copied code from addToFavorites + Action action = new AbstractAction(r.toString()) { + + public void actionPerformed(ActionEvent e) { + dataRange.popHistory(ipop); + DasAxis.this.setDataRangePrev(); + } + }; + JMenuItem menuItem = new JMenuItem(action); + backMenu.add(menuItem); + ii++; + } + } + + /* PRIVATE INITIALIZATION FUNCTIONS */ + private void maybeInitializeInputPanels() { + if (primaryInputPanel == null) { + primaryInputPanel = new JPanel(); + primaryInputPanel.setOpaque(false); + } + if (secondaryInputPanel == null) { + secondaryInputPanel = new JPanel(); + secondaryInputPanel.setOpaque(false); + } + } + + private void maybeInitializeScanButtons() { + if (!DasApplication.getDefaultApplication().isHeadless()) { + scanPrevious = new DasAxis.ScanButton(SCAN_PREVIOUS_LABEL); + scanNext = new DasAxis.ScanButton(SCAN_NEXT_LABEL); + ActionListener al = createScanActionListener(); + scanPrevious.addActionListener(al); + scanNext.addActionListener(al); + add(scanPrevious); + add(scanNext); + } + } + + private ActionListener createScanActionListener() { + return new ActionListener() { + + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + DasLogger.getLogger(DasLogger.GUI_LOG).fine("event " + command); + if (command.equals(SCAN_PREVIOUS_LABEL)) { + scanPrevious(); + } else if (command.equals(SCAN_NEXT_LABEL)) { + scanNext(); + } + } + }; + } + + private PropertyChangeListener createDataRangePropertyListener() { + return new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent e) { + String propertyName = e.getPropertyName(); + Object oldValue = e.getOldValue(); + Object newValue = e.getNewValue(); + if (propertyName.equals(PROP_LOG)) { + update(); + firePropertyChange(PROP_LOG, oldValue, newValue); + } else if (propertyName.equals("minimum")) { + update(); + firePropertyChange("dataMinimum", oldValue, newValue); + } else if (propertyName.equals("maximum")) { + update(); + firePropertyChange("dataMaximum", oldValue, newValue); + } else if (propertyName.equals("favorites")) { + copyFavorites(); + } else if (propertyName.equals(DataRange.PROPERTY_DATUMRANGE)) { + update(); + firePropertyChange(PROPERTY_DATUMRANGE, oldValue, newValue); + } else if (propertyName.equals("history")) { + if (!dataRange.valueIsAdjusting()) { + copyHistory(); + } + } + markDirty(); + } + }; + } + + private void installMouseModules() { + if (zoom instanceof HorizontalRangeSelectorMouseModule) { + ((HorizontalRangeSelectorMouseModule) zoom).removeDataRangeSelectionListener(this); + mouseAdapter.removeMouseModule(zoom); + } else if (zoom instanceof VerticalRangeSelectorMouseModule) { + ((VerticalRangeSelectorMouseModule) zoom).removeDataRangeSelectionListener(this); + mouseAdapter.removeMouseModule(zoom); + } + if (isHorizontal()) { + zoom = new HorizontalRangeSelectorMouseModule(this, this); + ((HorizontalRangeSelectorMouseModule) zoom).addDataRangeSelectionListener(this); + mouseAdapter.addMouseModule(zoom); + mouseAdapter.setPrimaryModule(zoom); + + MouseModule zoomPan = new ZoomPanMouseModule(this, this, null); + mouseAdapter.addMouseModule(zoomPan); + mouseAdapter.setSecondaryModule(zoomPan); + } else { + zoom = new VerticalRangeSelectorMouseModule(this, this); + ((VerticalRangeSelectorMouseModule) zoom).addDataRangeSelectionListener(this); + mouseAdapter.addMouseModule(zoom); + mouseAdapter.setPrimaryModule(zoom); + + MouseModule zoomPan = new ZoomPanMouseModule(this, null, this); + mouseAdapter.addMouseModule(zoomPan); + mouseAdapter.setSecondaryModule(zoomPan); + } + } + + /** TODO + * @param orientation + */ + public void setOrientation(int orientation) { + boolean oldIsHorizontal = isHorizontal(); + setOrientationInternal(orientation); + if (oldIsHorizontal != isHorizontal()) { + installMouseModules(); + } + } + + /* This is a private internal implementation for + * {@link #setOrientation(int)}. This method is provided + * to avoid calling a non-final non-private instance method + * from a constructor. Doing so can create problems if the + * method is overridden in a subclass. + */ + private void setOrientationInternal(int orientation) { + this.orientation = orientation; + if (orientation == TOP) { + setTickDirection(UP); + } else if (orientation == BOTTOM) { + setTickDirection(DOWN); + } else if (orientation == LEFT) { + setTickDirection(RIGHT); + } else if (orientation == RIGHT) { + setTickDirection(LEFT); + } else { + throw new IllegalArgumentException("Invalid value for orientation"); + } + } + + public void setDatumRange(DatumRange dr) { + if ( getUnits().isConvertableTo(dr.getUnits()) ) { + this.setDataRange(dr.min(), dr.max()); + } else { + Units oldUnits= getUnits(); + this.resetRange(dr); + firePropertyChange( PROP_UNITS, oldUnits, dr.getUnits() ); + } + + } + + public DatumRange getDatumRange() { + return dataRange.getDatumRange(); + } + + /* + * @returns true is the range is acceptible, false otherwise. This method + * is overriden by DasLabelAxis. + */ + protected boolean rangeIsAcceptable(DatumRange dr) { + return dr.min().lt(dr.max()); + } + + /** TODO + * @param minimum + * @param maximum + */ + public void setDataRange(Datum minimum, Datum maximum) { + + Units units = dataRange.getUnits(); + if (minimum.getUnits() != units) { + minimum = minimum.convertTo(units); + maximum = maximum.convertTo(units); + } + + DatumRange newRange = new DatumRange(minimum, maximum); + logger.fine("enter dasAxis.setDataRange( " + newRange + " )"); + + if (!rangeIsAcceptable(newRange)) { + logger.warning("invalid range ignored"); + return; + } + + double min, max, min0, max0; + + min0 = dataRange.getMinimum(); + max0 = dataRange.getMaximum(); + + if (dataRange.isLog()) { + min = DasMath.log10(minimum.doubleValue(getUnits())); + max = DasMath.log10(maximum.doubleValue(getUnits())); + } else { + min = minimum.doubleValue(getUnits()); + max = maximum.doubleValue(getUnits()); + } + + if (!valueIsAdjusting()) { + animateChange(min0, max0, min, max); + } + DatumRange oldRange = dataRange.getDatumRange(); + dataRange.setRange(newRange); + + update(); + createAndFireRangeSelectionEvent(); + firePropertyChange(PROPERTY_DATUMRANGE, oldRange, newRange); + } + + public void clearHistory() { + dataRange.clearHistory(); + } + + private void createAndFireRangeSelectionEvent() { + if (getUnits() instanceof TimeLocationUnits) { + logger.fine("firing rangeSelectionEvent"); + TimeRangeSelectionEvent e = new TimeRangeSelectionEvent(this, new DatumRange(this.getDataMinimum(), this.getDataMaximum())); + fireTimeRangeSelectionListenerTimeRangeSelected(e); + } + } + + /** TODO */ + public void setDataRangePrev() { + logger.fine("enter dasAxis.setDataRangePrev()"); + DatumRange oldRange = dataRange.getDatumRange(); + double min0 = dataRange.getMinimum(); + double max0 = dataRange.getMaximum(); + dataRange.setRangePrev(); + DatumRange newRange = dataRange.getDatumRange(); + double min1 = dataRange.getMinimum(); + double max1 = dataRange.getMaximum(); + animateChange(min0, max0, min1, max1); + update(); + createAndFireRangeSelectionEvent(); + firePropertyChange(PROPERTY_DATUMRANGE, oldRange, newRange); + } + + /** TODO */ + public void setDataRangeForward() { + logger.fine("enter dasAxis.setDataRangeForward()"); + double min0 = dataRange.getMinimum(); + double max0 = dataRange.getMaximum(); + DatumRange oldRange = dataRange.getDatumRange(); + dataRange.setRangeForward(); + DatumRange newRange = dataRange.getDatumRange(); + double min1 = dataRange.getMinimum(); + double max1 = dataRange.getMaximum(); + animateChange(min0, max0, min1, max1); + update(); + createAndFireRangeSelectionEvent(); + firePropertyChange(PROPERTY_DATUMRANGE, oldRange, newRange); + } + + /** TODO */ + public void setDataRangeZoomOut() { + logger.fine("enter dasAxis.setDataRangeZoomOut()"); + double t1 = dataRange.getMinimum(); + double t2 = dataRange.getMaximum(); + double delta = t2 - t1; + double min = t1 - delta; + double max = t2 + delta; + animateChange(t1, t2, min, max); + DatumRange oldRange = dataRange.getDatumRange(); + dataRange.setRange(min, max); + DatumRange newRange = dataRange.getDatumRange(); + update(); + createAndFireRangeSelectionEvent(); + firePropertyChange(PROPERTY_DATUMRANGE, oldRange, newRange); + } + + /** TODO + * @return + */ + public DataRange getDataRange() { + return this.dataRange; + } + + /** TODO */ + protected void deviceRangeChanged() { + } + + /** TODO + * @return + */ + public Datum getDataMinimum() { + return dataRange.getDatumRange().min(); + } + + /** TODO + * @return + */ + public Datum getDataMaximum() { + return dataRange.getDatumRange().max(); + } + + /* + * + */ + /** + * This is the preferred method for getting the range of the axis. + * @return a DatumRange indicating the range of the axis. + * @deprecated use getDatumRange instead. + */ + public DatumRange getRange() { + return dataRange.getDatumRange(); + } + + /** TODO + * @param units + * @return + */ + public double getDataMaximum(Units units) { + return getDataMaximum().doubleValue(units); + } + + /** TODO + * @param units + * @return + */ + public double getDataMinimum(Units units) { + return getDataMinimum().doubleValue(units); + } + + /** TODO + * @param max + */ + public void setDataMaximum(Datum max) { + dataRange.setMaximum(max); + update(); + } + + /** TODO + * @param min + */ + public void setDataMinimum(Datum min) { + dataRange.setMinimum(min); + update(); + } + + /** TODO + * @return + */ + public boolean isLog() { + return dataRange.isLog(); + } + + /** + * Set the axis to be log or linear. If necessary, axis range will be + * adjusted to make the range valid. + * @param log + */ + public void setLog(boolean log) { + boolean oldLog = isLog(); + DatumRange range= getDatumRange(); + dataRange.setLog(log); + update(); + if (log != oldLog) { + firePropertyChange(PROP_LOG, oldLog, log); + } + // switching log can change the axis range. + if ( ! range.equals(getDatumRange()) ) { + firePropertyChange( PROPERTY_DATUMRANGE, range, getDatumRange() ); + } + } + + /** TODO + * @return + */ + public Units getUnits() { + return dataRange.getUnits(); + } + + public void setUnits(Units newUnits) { + dataRange.setUnits(newUnits); + } + + /** + * changes the units of the axis to a new unit. + */ + public synchronized void resetRange(DatumRange range) { + if (range.getUnits() != this.getUnits()) { + if (dasPlot != null) { + dasPlot.invalidateCacheImage(); + } + logger.finest("replaceRange(" + range + ")"); + dataRange.resetRange(range); + } else { + dataRange.setRange(range); + } + updateTickV(); + markDirty(); + update(); + } + + /** TODO + * @param visible + */ + public void setOppositeAxisVisible(boolean visible) { + if (visible == oppositeAxisVisible) { + return; + } + boolean oldValue = oppositeAxisVisible; + oppositeAxisVisible = visible; + revalidate(); + repaint(); + firePropertyChange(PROP_OPPOSITE_AXIS_VISIBLE, oldValue, visible); + } + + /** TODO + * @return + */ + public boolean isOppositeAxisVisible() { + return oppositeAxisVisible; + } + + /** Mutator method for the title property of this axis. + * + * The title for this axis is displayed below the ticks for horizontal axes + * or to left of the ticks for vertical axes. + * @param t The new title for this axis + */ + public void setLabel(String t) { + if (t == null) { + throw new NullPointerException("axis label cannot be null"); + } + Object oldValue = axisLabel; + axisLabel = t; + update(); + firePropertyChange(PROP_LABEL, oldValue, t); + } + + /** + * Accessor method for the title property of this axis. + * + * @return A String instance that contains the title displayed + * for this axis, or null if the axis has no title. + */ + public String getLabel() { + return axisLabel; + } + + /** Getter for property animated. + * @return Value of property animated. + */ + public boolean isAnimated() { + return this.animated; + } + + /** Setter for property animated. + * @param animated new value of property animated. + */ + public void setAnimated(boolean animated) { + this.animated = animated; + } + + public boolean getDrawTca() { + return drawTca; + } + + public void setDrawTca(boolean b) { + boolean oldValue = drawTca; + if (b && getOrientation() != BOTTOM) { + throw new IllegalArgumentException("Vertical time axes cannot have annotations"); + } + if (drawTca == b) { + return; + } + drawTca = b; + markDirty(); + update(); + firePropertyChange("showTca", oldValue, b); + } + + public String getDataPath() { + return dataset; + } + + /** + * + * @param dataset The URL identifier string of a TCA data set, or "" for no TCAs. + */ + public void setDataPath(String dataset) { + if (dataset == null) { + throw new NullPointerException("null dataPath string not allowed"); + } + Object oldValue = this.dataset; + if (dataset.equals(this.dataset)) { + return; + } + this.dataset = dataset; + if (dataset.equals("")) { + dsd = null; + } else { + try { + dsd = DataSetDescriptor.create(dataset); + } catch ( org.das2.DasException de) { + DasExceptionHandler.handle(de); + } + } + markDirty(); + update(); + firePropertyChange("dataPath", oldValue, dataset); + } + + /** Add auxiliary data to an axis (usually OA data for a time axis). + * This function does the same thing as setDataPath, but with a different interface. + * dsdAux must return a non-empty string from getDataSetID() + * @param dsdAux will be called upon to generate auxiliary data sets. To avoid nonsensical + * graphs the X axis for this dataset must be the same as the that handed to the + * renderer. + */ + public void setDataSetDescriptor(DataSetDescriptor dsdAux) { + if (dsdAux == null) { + throw new NullPointerException("null DataSetDescriptor not allowed"); + } + DataSetDescriptor oldVal = dsd; + dsd = dsdAux; + markDirty(); + update(); + + String oldDataset = dataset; + dataset = dsd.getDataSetID(); + firePropertyChange("dataSetDescriptor", oldVal, dsd); + firePropertyChange("dataPath", oldDataset, dataset); + } + + public DataSetDescriptor getDataSetDescriptor() { + return dsd; + } + + public void setTcaData(VectorDataSet ds) { + logger.fine("got TCADataSet"); + List itemList = (List) ds.getProperty("plane-list"); + VectorDataSet[] newData = new VectorDataSet[itemList.size()]; + DatumFormatter[] newFormatters = new DatumFormatter[itemList.size()]; + newData[0] = ds; + for (int i = 1; i < itemList.size(); i++) { + newData[i] = (VectorDataSet) ds.getPlanarView((String) itemList.get(i)); + } + for (int i = 0; i < newData.length; i++) { + String format = (String)newData[i].getProperty("format"); + newFormatters[i] = getFormatter(format, newData[i].getYUnits()); + } + tcaData = newData; + tcaFormatters = newFormatters; + update(); + } + + private DatumFormatter getFormatter(String format, Units u) { + if (format == null) return null; + try { + if (u.isConvertableTo(Units.t2000)) { + return timeFormatterFactory.newFormatter(format); + } + return formatterFactory.newFormatter(format); + } + catch (ParseException ex) { + return null; + } + catch (IllegalArgumentException ex) { + return null; + } + } + + private final DataSetUpdateListener tcaListener = new DataSetUpdateListener() { + + @Override + public void dataSetUpdated(DataSetUpdateEvent e) { + VectorDataSet ds = (VectorDataSet) e.getDataSet(); + if (ds == null) { + logger.warning(String.valueOf(e.getException())); + return; + } + setTcaData(ds); + } + }; + + private void updateTCADataSet() { + logger.fine("updateTCADataSet"); + double[] tickV = getTickV().tickV.toDoubleArray(getUnits()); + Datum data_minimum; + Datum data_maximum; + Datum iinterval; + if (tickV.length == 1) { + data_minimum = Datum.create(tickV[0], getTickV().units); + data_maximum = Datum.create(tickV[0], getTickV().units); + iinterval = data_maximum.subtract(data_minimum); + } else { + data_minimum = Datum.create(tickV[0], getTickV().units); + data_maximum = Datum.create(tickV[tickV.length - 1], getTickV().units); + iinterval = (data_maximum.subtract(data_minimum)).divide(tickV.length - 1); + } + data_maximum = data_maximum.add(iinterval); + final Datum interval = iinterval; + tcaData = null; + tcaFormatters = null; + + this.dsd.requestDataSet(data_minimum, data_maximum.add(Datum.create(1.0, Units.seconds)), interval, new NullProgressMonitor(), getCanvas(), tcaListener); + + } + + /** TODO + * @return + */ + public final int getDevicePosition() { + if (orientation == BOTTOM) { + return getRow().getDMaximum(); + } else if (orientation == TOP) { + return getRow().getDMinimum(); + } else if (orientation == LEFT) { + return getColumn().getDMinimum(); + } else { + return getColumn().getDMaximum(); + } + } + + /** + * @return returns the length in pixels of the axis. + */ + public int getDLength() { + if (isHorizontal()) { + return getColumn().getWidth(); + } else { + return getRow().getHeight(); + } + } + + /** TODO + * @return + */ + public DasAxis getMasterAxis() { + return dataRange.getCreator(); + } + + /** TODO + * @param axis + */ + public void attachTo(DasAxis axis) { + DataRange oldRange = dataRange; + dataRange = axis.dataRange; + oldRange.removePropertyChangeListener(PROP_LOG, dataRangePropertyListener); + oldRange.removePropertyChangeListener("minimum", dataRangePropertyListener); + oldRange.removePropertyChangeListener("maximum", dataRangePropertyListener); + oldRange.removePropertyChangeListener(DataRange.PROPERTY_DATUMRANGE, dataRangePropertyListener); + dataRange.addPropertyChangeListener(PROP_LOG, dataRangePropertyListener); + dataRange.addPropertyChangeListener("minimum", dataRangePropertyListener); + dataRange.addPropertyChangeListener("maximum", dataRangePropertyListener); + dataRange.addPropertyChangeListener(DataRange.PROPERTY_DATUMRANGE, dataRangePropertyListener); + if (oldRange.isLog() != dataRange.isLog()) { + firePropertyChange(PROP_LOG, oldRange.isLog(), dataRange.isLog()); + } + firePropertyChange("minimum", oldRange.getMinimum(), dataRange.getMinimum()); + firePropertyChange("maximum", oldRange.getMaximum(), dataRange.getMaximum()); + copyFavorites(); + copyHistory(); + } + + /** TODO */ + public void detach() { + dataRange.removePropertyChangeListener(PROP_LOG, dataRangePropertyListener); + dataRange.removePropertyChangeListener("minimum", dataRangePropertyListener); + dataRange.removePropertyChangeListener("maximum", dataRangePropertyListener); + dataRange.removePropertyChangeListener(DataRange.PROPERTY_DATUMRANGE, dataRangePropertyListener); + DataRange newRange = new DataRange(this, Datum.create(dataRange.getMinimum(), dataRange.getUnits()), + Datum.create(dataRange.getMaximum(), dataRange.getUnits()), + dataRange.isLog()); + dataRange = newRange; + dataRange.addPropertyChangeListener(PROP_LOG, dataRangePropertyListener); + dataRange.addPropertyChangeListener("minimum", dataRangePropertyListener); + dataRange.addPropertyChangeListener("maximum", dataRangePropertyListener); + dataRange.addPropertyChangeListener(DataRange.PROPERTY_DATUMRANGE, dataRangePropertyListener); + copyFavorites(); + copyHistory(); + } + + /** TODO + * @return + */ + public boolean isAttached() { + return this != getMasterAxis(); + } + + /** TODO + * @return + */ + public TickVDescriptor getTickV() { + if (tickV == null) { + updateTickV(); + } + return tickV; + } + + /** + * Sets the TickVDescriptor for this axis. If null is passed in, the + * axis will put into autoTickV mode, where the axis will attempt to + * determine ticks using an appropriate algortithm. + * + * @param tickV the new ticks for this axis, or null + */ + public void setTickV(TickVDescriptor tickV) { + checkTickV(tickV); + this.tickV = tickV; + if (tickV == null) { + autoTickV = true; + updateTickV(); + } else { + autoTickV = false; + } + update(); + } + + /** TODO: implement this + */ + private void checkTickV(TickVDescriptor tickV) throws IllegalArgumentException { + } + + private void updateTickVLog() { + + double min = getDataMinimum().doubleValue(getUnits()); + double max = getDataMaximum().doubleValue(getUnits()); + + double dMinTick = DasMath.roundNFractionalDigits(DasMath.log10(min), 4); + int minTick = (int) Math.ceil(dMinTick); + double dMaxTick = DasMath.roundNFractionalDigits(DasMath.log10(max), 4); + int maxTick = (int) Math.floor(dMaxTick); + + GrannyTextRenderer idlt = new GrannyTextRenderer(); + idlt.setString(this.getTickLabelFont(), "10!U-10"); + + int nTicksMax; + if (isHorizontal()) { + nTicksMax = (int) Math.floor(getColumn().getWidth() / (idlt.getWidth())); + } else { + nTicksMax = (int) Math.floor(getRow().getHeight() / (idlt.getHeight())); + } + + nTicksMax = (nTicksMax < 7) ? nTicksMax : 7; + + tickV = TickVDescriptor.bestTickVLogNew(getDataMinimum(), getDataMaximum(), 3, nTicksMax, true); + datumFormatter = resolveFormatter(tickV); + + return; + + } + + private void updateTickVLinear() { + int nTicksMax; + int axisSize; + if (isHorizontal()) { + int tickSizePixels = (int) (getFontMetrics(getTickLabelFont()).stringWidth("0.0000") * 1.5); + axisSize = getColumn().getWidth(); + nTicksMax = axisSize / tickSizePixels; + } else { + int tickSizePixels = getFontMetrics(getTickLabelFont()).getHeight() + 6; + axisSize = getRow().getHeight(); + nTicksMax = axisSize / tickSizePixels; + } + + nTicksMax = (nTicksMax < 7) ? nTicksMax : 7; + + this.tickV = TickVDescriptor.bestTickVLinear(getDataMinimum(), getDataMaximum(), 3, nTicksMax, false); + + DatumFormatter tdf = resolveFormatter(tickV); + + boolean once = true; + + while (once) { + + Rectangle maxBounds = getMaxBounds(tdf, tickV); + + if (isHorizontal()) { + int tickSizePixels = (int) (maxBounds.width + getEmSize() * 2); + nTicksMax = axisSize / tickSizePixels; + } else { + int tickSizePixels = (int) (maxBounds.height); + nTicksMax = axisSize / tickSizePixels; + } + + this.tickV = TickVDescriptor.bestTickVLinear(getDataMinimum(), getDataMaximum(), 3, nTicksMax, true); + datumFormatter = resolveFormatter(tickV); + + once = false; + } + + return; + + } + + private DatumFormatter resolveFormatter(TickVDescriptor tickV) { + return getUserDatumFormatter() == null ? tickV.getFormatter() : getUserDatumFormatter(); + } + + private Rectangle getMaxBounds(DatumFormatter tdf, TickVDescriptor tickV) { + String[] granny = tdf.axisFormat(tickV.tickV, getDatumRange()); + GrannyTextRenderer idlt = new GrannyTextRenderer(); + Rectangle bounds = new Rectangle(); + for (int i = 0; i < granny.length; i++) { + idlt.setString(this.getTickLabelFont(), granny[i]); + bounds.add(idlt.getBounds()); + } + return bounds; + } + + private void updateTickVTime() { + + int nTicksMax; + + DatumRange dr = getDatumRange(); + Datum pixel = dr.width().divide(getDLength()); + DatumFormatter tdf; + + if (isHorizontal()) { + // two passes to avoid clashes -- not guarenteed + tickV = TickVDescriptor.bestTickVTime(dr.min().subtract(pixel), dr.max().add(pixel), 3, 8, false); + + tdf = resolveFormatter(tickV); + Rectangle bounds = getMaxBounds(tdf, tickV); + + int tickSizePixels = (int) (bounds.width + getEmSize() * 2); + + if (drawTca) { + FontMetrics fm = getFontMetrics(getTickLabelFont()); + String item = format(99999.99, "(f8.2)"); + int width = fm.stringWidth(item) + (int) (getEmSize() * 2); + if (width > tickSizePixels) { + tickSizePixels = width; + } + } + + int axisSize = getColumn().getWidth(); + nTicksMax = Math.max(2, axisSize / tickSizePixels); + + tickV = TickVDescriptor.bestTickVTime(getDataMinimum(), getDataMaximum(), 2, nTicksMax, false); + tdf = resolveFormatter(tickV); + + bounds = getMaxBounds(tdf, tickV); + tickSizePixels = (int) (bounds.getWidth() + getEmSize() * 2); + + if (drawTca) { + FontMetrics fm = getFontMetrics(getTickLabelFont()); + String item = format(99999.99, "(f8.2)"); + int width = fm.stringWidth(item); + if (width > tickSizePixels) { + tickSizePixels = width; + } + } + //nTicksMax = (int) Math.floor( 0.2 + 1.* axisSize / tickSizePixels ); + + nTicksMax = (nTicksMax > 1 ? nTicksMax : 2); + nTicksMax = (nTicksMax < 10 ? nTicksMax : 10); + + boolean overlap = true; + while (overlap && nTicksMax > 2) { + + tickV = TickVDescriptor.bestTickVTime(getDataMinimum(), getDataMaximum(), 2, nTicksMax, false); + + if (tickV.getMajorTicks().getLength() <= 1) { + // we're about to have an assertion error, time to debug; + System.err.println("about to assert error: " + tickV.getMajorTicks()); + } + assert (tickV.getMajorTicks().getLength() > 1); + + tdf = resolveFormatter(tickV); + + bounds = getMaxBounds(tdf, tickV); + tickSizePixels = (int) (bounds.getWidth() + getEmSize() * 2); + + double x0 = transform(tickV.getMajorTicks().get(0)); + double x1 = transform(tickV.getMajorTicks().get(1)); + + if (x1 - x0 > tickSizePixels) { + overlap = false; + } else { + nTicksMax = nTicksMax - 1; + } + } + tickV = TickVDescriptor.bestTickVTime(getDataMinimum(), getDataMaximum(), 2, nTicksMax, true); + + } else { + int tickSizePixels = getFontMetrics(getTickLabelFont()).getHeight(); + int axisSize = getRow().getHeight(); + nTicksMax = axisSize / tickSizePixels; + + nTicksMax = (nTicksMax > 1 ? nTicksMax : 2); + nTicksMax = (nTicksMax < 10 ? nTicksMax : 10); + + tickV = TickVDescriptor.bestTickVTime(getDataMinimum(), getDataMaximum(), 3, nTicksMax, true); + + } + + datumFormatter = resolveFormatter(tickV); + + if (drawTca && !dataset.equals("") && dsd != null) { + updateTCADataSet(); + } + } + + public synchronized void updateTickV() { + if ( !valueIsAdjusting() ) { + if (autoTickV) { + TickVDescriptor oldTicks = this.tickV; + if (getUnits() instanceof TimeLocationUnits) { + updateTickVTime(); + } else if (dataRange.isLog()) { + updateTickVLog(); + } else { + updateTickVLinear(); + } + firePropertyChange(PROPERTY_TICKS, oldTicks, this.tickV); + } + } + + } + private String errorMessage; + + /** + * checks the validity of the state, setting variable errorMessage to non-null if there is a problem. + */ + private void checkState() { + double dmin = getDataMinimum(dataRange.getUnits()); + double dmax = getDataMaximum(dataRange.getUnits()); + + String em = ""; + + if (Double.isNaN(dmin)) { + em += "dmin is NaN, "; + } + if (Double.isNaN(dmax)) { + em += "dmax is NaN, "; + } + if (Double.isInfinite(dmin)) { + em += "dmin is infinite, "; + } + if (Double.isInfinite(dmax)) { + em += "dmax is infinite, "; + } + if (dmin >= dmax) { + em += "min => max, "; + } + if (dataRange.isLog() && dmin <= 0) { + em += "min<= 0 and log, "; + } + + if (em.length() == 0) { + this.errorMessage = null; + } else { + this.errorMessage = em; + } + } + + /** paints the axis component. The tickV's and bounds should be calculated at this point */ + protected void paintComponent(Graphics graphics) { + logger.fine("enter DasAxis.paintComponent"); + /* This was code was keeping axes from being printed on PC's + Shape saveClip = null; + if (getCanvas().isPrintingThread()) { + saveClip = graphics.getClip(); + graphics.setClip(null); + } + */ + logger.fine("DasAxis clip=" + graphics.getClip() + " @ "+getX()+","+getY() ); + + Graphics2D g = (Graphics2D) graphics.create(); + //g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); + //g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + //g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + g.translate(-getX(), -getY()); + g.setColor(getForeground()); + + /* Debugging code */ + /* The compiler will optimize it out if DEBUG_GRAPHICS == false */ + if (DEBUG_GRAPHICS) { + g.setStroke(new BasicStroke(3f, BasicStroke.CAP_BUTT, BasicStroke.CAP_BUTT, 1f, new float[]{3f, 3f}, 0f)); + g.setColor(Color.BLUE); + if (blLabelRect != null) { + g.draw(blLabelRect); + } + g.setColor(Color.RED); + if (blLineRect != null) { + g.draw(blLineRect); + } + g.setColor(Color.GREEN); + if (blTickRect != null) { + g.draw(blTickRect); + } + g.setColor(Color.LIGHT_GRAY); + if (blTitleRect != null) { + g.draw(blTitleRect); + } + g.setColor(Color.BLUE); + if (trLabelRect != null) { + g.draw(trLabelRect); + } + g.setColor(Color.RED); + if (trLineRect != null) { + g.draw(trLineRect); + } + g.setColor(Color.GREEN); + if (trTickRect != null) { + g.draw(trTickRect); + } + g.setColor(Color.LIGHT_GRAY); + if (trTitleRect != null) { + g.draw(trTitleRect); + } + g.setStroke(new BasicStroke(1f)); + g.setColor(DEBUG_COLORS[debugColorIndex]); + debugColorIndex++; + if (debugColorIndex >= DEBUG_COLORS.length) { + debugColorIndex = 0; + } + } + /* End debugging code */ + + if (tickV == null || tickV.tickV.getUnits().isConvertableTo(getUnits())) { + if (isHorizontal()) { + paintHorizontalAxis(g); + } else { + paintVerticalAxis(g); + } + } else { + //System.err.println("inconvertable units"); + } + + Rectangle clip = g.getClipBounds(); + if (clip == null) { + clip = new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + if (drawTca && getOrientation() == BOTTOM && tcaData != null && blLabelRect != null && blLabelRect.intersects(clip)) { + + int position = getRow().getDMaximum(); + int DMin = getColumn().getDMinimum(); + Font tickLabelFont = getTickLabelFont(); + FontMetrics tickLabelFontMetrics = getFontMetrics(tickLabelFont); + int tickLength = tickLabelFont.getSize() * 2 / 3; + int tickLabelGap = tickLabelFontMetrics.stringWidth(" "); + int lineHeight = tickLabelFont.getSize() + getLineSpacing(); + + int baseLine = position + tickLength + tickLabelGap + tickLabelFont.getSize(); + int rightEdge = DMin - tickLabelFontMetrics.stringWidth("000") - tickLabelGap; // Note assumes top label is date but lower labels are shorter. + + GrannyTextRenderer idlt = new GrannyTextRenderer(); + /* + idlt.setString(this.getGraphics(), "SCET"); + int width = (int)Math.ceil(idlt.getWidth()); + int leftEdge = rightEdge - width; + idlt.draw(g, (float)leftEdge, (float)baseLine); + */ + + + int width, leftEdge; + + for (int i = 0; i < tcaData.length; i++) { + String label = (String) tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL); + if (label == null) { + label = (String) tcaData[i].getProperty("label"); + } + if (label == null) { + label = ""; + } + baseLine += lineHeight; + idlt.setString(g, label); + width = (int) Math.floor(idlt.getWidth() + 0.5); + leftEdge = rightEdge - width; + idlt.draw(g, (float) leftEdge, (float) baseLine); + } + } + + g.dispose(); + getDasMouseInputAdapter().paint(graphics); + + /* This was code was keeping axes from being printed on PC's + if (getCanvas().isPrintingThread()) { + g.setClip(saveClip); + } + */ + } + + /** Paint the axis if it is horizontal */ + protected void paintHorizontalAxis(Graphics2D g) { + try { + Rectangle clip = g.getClipBounds(); + if (clip == null) { + clip = new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + boolean bottomLine = ((orientation == BOTTOM || oppositeAxisVisible) && blLineRect != null && blLineRect.intersects(clip)); + boolean bottomTicks = ((orientation == BOTTOM || oppositeAxisVisible) && blTickRect != null && blTickRect.intersects(clip)); + boolean bottomTickLabels = ((orientation == BOTTOM && tickLabelsVisible) && blLabelRect != null && blLabelRect.intersects(clip)); + boolean bottomLabel = ((orientation == BOTTOM && !axisLabel.equals("")) && blTitleRect != null && blTitleRect.intersects(clip)); + boolean topLine = ((orientation == TOP || oppositeAxisVisible) && trLineRect != null && trLineRect.intersects(clip)); + boolean topTicks = ((orientation == TOP || oppositeAxisVisible) && trTickRect != null && trTickRect.intersects(clip)); + boolean topTickLabels = ((orientation == TOP && tickLabelsVisible) && trLabelRect != null && trLabelRect.intersects(clip)); + boolean topLabel = ((orientation == TOP && !axisLabel.equals("")) && trTitleRect != null && trTitleRect.intersects(clip)); + + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + int DMax = getColumn().getDMaximum(); + int DMin = getColumn().getDMinimum(); + + Font labelFont = getTickLabelFont(); + + TickVDescriptor ticks = getTickV(); + + if (bottomLine) { + g.drawLine(DMin, bottomPosition, DMax, bottomPosition); + } + if (topLine) { + g.drawLine(DMin, topPosition, DMax, topPosition); + } + + int tickLengthMajor = labelFont.getSize() * 2 / 3; + int tickLengthMinor = tickLengthMajor / 2; + int tickLength; + + String[] labels = tickFormatter(ticks.tickV, getDatumRange()); + + for (int i = 0; i < ticks.tickV.getLength(); i++) { + Datum tick1 = ticks.tickV.get(i); + int tickPosition = (int) Math.floor(transform(tick1)); + if (DMin <= tickPosition && tickPosition <= DMax) { + tickLength = tickLengthMajor; + if (bottomTicks) { + g.drawLine(tickPosition, bottomPosition, tickPosition, bottomPosition + tickLength); + } + if (bottomTickLabels) { + drawLabel(g, tick1, labels[i], i, tickPosition, bottomPosition + tickLength); + } + if (topTicks) { + g.drawLine(tickPosition, topPosition, tickPosition, topPosition - tickLength); + } + if (topTickLabels) { + drawLabel(g, tick1, labels[i], i, tickPosition, topPosition - tickLength + 1); + } + } + } + + for (int i = 0; i < ticks.minorTickV.getLength(); i++) { + Datum tick = ticks.minorTickV.get(i); + int tickPosition = (int) Math.floor(transform(tick)); + if (DMin <= tickPosition && tickPosition <= DMax) { + tickLength = tickLengthMinor; + if (bottomTicks) { + g.drawLine(tickPosition, bottomPosition, tickPosition, bottomPosition + tickLength); + } + if (topTicks) { + g.drawLine(tickPosition, topPosition, tickPosition, topPosition - tickLength); + } + } + } + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D) g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g2, axisLabel); + int titleWidth = (int) gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (bottomLabel) { + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = bottomPosition + titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + if (topLabel) { + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = topPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + g2.dispose(); + } + } catch (InconvertibleUnitsException ex) { + logger.log(Level.WARNING, ex.getMessage(), ex); + } + } + + /** Paint the axis if it is vertical */ + protected void paintVerticalAxis(Graphics2D g) { + try { + Rectangle clip = g.getClipBounds(); + if (clip == null) { + clip = new Rectangle(getX(), getY(), getWidth(), getHeight()); + } + + boolean leftLine = ((orientation == LEFT || oppositeAxisVisible) && blLineRect != null && blLineRect.intersects(clip)); + boolean leftTicks = ((orientation == LEFT || oppositeAxisVisible) && blTickRect != null && blTickRect.intersects(clip)); + boolean leftTickLabels = ((orientation == LEFT && tickLabelsVisible) && blLabelRect != null && blLabelRect.intersects(clip)); + boolean leftLabel = ((orientation == LEFT && !axisLabel.equals("")) && blTitleRect != null && blTitleRect.intersects(clip)); + boolean rightLine = ((orientation == RIGHT || oppositeAxisVisible) && trLineRect != null && trLineRect.intersects(clip)); + boolean rightTicks = ((orientation == RIGHT || oppositeAxisVisible) && trTickRect != null && trTickRect.intersects(clip)); + boolean rightTickLabels = ((orientation == RIGHT && tickLabelsVisible) && trLabelRect != null && trLabelRect.intersects(clip)); + boolean rightLabel = ((orientation == RIGHT && !axisLabel.equals("")) && trTitleRect != null && trTitleRect.intersects(clip)); + + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax = getRow().getDMaximum(); + int DMin = getRow().getDMinimum(); + + Font labelFont = getTickLabelFont(); + + + TickVDescriptor ticks = getTickV(); + + if (leftLine) { + g.drawLine(leftPosition, DMin, leftPosition, DMax); + } + if (rightLine) { + g.drawLine(rightPosition, DMin, rightPosition, DMax); + } + + int tickLengthMajor = labelFont.getSize() * 2 / 3; + int tickLengthMinor = tickLengthMajor / 2; + int tickLength; + + String[] labels = tickFormatter(ticks.tickV, getDatumRange()); + for (int i = 0; i < ticks.tickV.getLength(); i++) { + Datum tick1 = ticks.tickV.get(i); + int tickPosition = (int) Math.floor(transform(tick1)); + if (DMin <= tickPosition && tickPosition <= DMax) { + + tickLength = tickLengthMajor; + if (leftTicks) { + g.drawLine(leftPosition, tickPosition, leftPosition - tickLength, tickPosition); + } + if (leftTickLabels) { + drawLabel(g, tick1, labels[i], i, leftPosition - tickLength, tickPosition); + } + if (rightTicks) { + g.drawLine(rightPosition, tickPosition, rightPosition + tickLength, tickPosition); + } + if (rightTickLabels) { + drawLabel(g, tick1, labels[i], i, rightPosition + tickLength, tickPosition); + } + } + } + + for (int i = 0; i < ticks.minorTickV.getLength(); i++) { + tickLength = tickLengthMinor; + double tick1 = ticks.minorTickV.doubleValue(i, getUnits()); + int tickPosition = (int) Math.floor(transform(tick1, ticks.units)); + if (DMin <= tickPosition && tickPosition <= DMax) { + tickLength = tickLengthMinor; + if (leftTicks) { + g.drawLine(leftPosition, tickPosition, leftPosition - tickLength, tickPosition); + } + if (rightTicks) { + g.drawLine(rightPosition, tickPosition, rightPosition + tickLength, tickPosition); + } + } + } + + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D) g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g2, axisLabel); + int titleWidth = (int) gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (leftLabel) { + g2.rotate(-Math.PI / 2.0); + leftEdge = -DMax + (DMax - DMin - titleWidth) / 2; + baseline = leftPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + if (rightLabel) { + if ( flipLabel ) { + g2.rotate( -Math.PI / 2.0); + leftEdge = DMin + (DMax - DMin + titleWidth) / 2; + baseline = rightPosition + titlePositionOffset; + gtr.draw(g2, (float) -leftEdge, (float) baseline); + g2.getClipBounds(); + } else { + g2.rotate( Math.PI / 2.0 ); + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = -rightPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + } + g2.dispose(); + } + } catch (InconvertibleUnitsException e) { + // do nothing + } + } + + /** TODO + * @return + */ + protected int getTitlePositionOffset() { + Font tickLabelFont = getTickLabelFont(); + FontMetrics fm = getFontMetrics(tickLabelFont); + Font labelFont = getLabelFont(); + int tickLength = tickLabelFont.getSize() * 2 / 3; + + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, axisLabel); + + int offset; + + if (orientation == BOTTOM) { + offset = tickLabelFont.getSize() + tickLength + fm.stringWidth(" ") + labelFont.getSize() + labelFont.getSize() / 2; + } else if (orientation == TOP) { + offset = tickLength + fm.stringWidth(" ") + labelFont.getSize() + labelFont.getSize() / 2 + (int) gtr.getDescent(); + } else if (orientation == LEFT) { + //offset = tickLength + (int)this.blLabelRect.getWidth() + fm.stringWidth(" ") + labelFont.getSize() / 2 + (int) gtr.getDescent(); + offset= getColumn().getDMinimum() - blLabelRect.x + labelFont.getSize() / 2 + (int) gtr.getDescent(); + } else { + offset= this.trLabelRect.x + this.trLabelRect.width - getColumn().getDMaximum() + labelFont.getSize() / 2 + + (int) ( flipLabel ? gtr.getAscent() : gtr.getDescent() ); + /*if ( !flipLabel ) { + offset = tickLength + (int)this.trLabelRect.getWidth() + fm.stringWidth(" ") + labelFont.getSize() / 2 + (int) gtr.getDescent(); + } else { + offset = tickLength + (int)this.trLabelRect.getWidth() + fm.stringWidth(" ") + labelFont.getSize() / 2 + (int) gtr.getAscent(); + offset= this.trLabelRect.x + this.trLabelRect.width - getColumn().getDMaximum() + labelFont.getSize() / 2 + }*/ + } + if (getOrientation() == BOTTOM && drawTca && tcaData != null) { + offset += tcaData.length * (tickLabelFont.getSize() + getLineSpacing()); + } + return offset; + } + + public int getLineSpacing() { + return getTickLabelFont().getSize() / 4; + } + + /** TODO */ + protected void drawLabel(Graphics2D g, Datum value, String label, int index, int x, int y) { + + if (!tickLabelsVisible) { + return; + } + + g.setFont(getTickLabelFont()); + GrannyTextRenderer idlt = new GrannyTextRenderer(); + idlt.setString(g, label); + + int width; + width = (int) (isHorizontal() ? idlt.getLineOneWidth() : idlt.getWidth()); + int height = (int) idlt.getHeight(); + int ascent = (int) idlt.getAscent(); + + int tick_label_gap = getFontMetrics(getTickLabelFont()).stringWidth(" "); + + if (orientation == BOTTOM) { + x -= width / 2; + y += getTickLabelFont().getSize() + tick_label_gap; + } else if (orientation == TOP) { + x -= width / 2; + y -= tick_label_gap + idlt.getDescent(); + } else if (orientation == LEFT) { + x -= (width + tick_label_gap); + y += ascent - height / 2; + } else { + x += tick_label_gap; + y += ascent - height / 2; + } + Color c = g.getColor(); + idlt.draw(g, x, y); + if (orientation == BOTTOM && drawTca && tcaData != null) { + drawTCAItems(g, value, x, y, width); + } + } + + private void drawTCAItems(Graphics g, Datum value, int x, int y, int width) { + int index; + + int baseLine, leftEdge, rightEdge; + double pixelSize; + double tcaValue; + + if (tcaData == null || tcaData.length == 0) { + return; + } + + baseLine = y; + leftEdge = x; + rightEdge = leftEdge + width; + + index = DataSetUtil.closestColumn(tcaData[0], value); + if (index < 0 || index > tcaData[0].getXLength()) { + return; + } + pixelSize = getDatumRange().width().divide(getDLength()).doubleValue( + getUnits().getOffsetUnits()); + + if ( tcaData[0].getXLength()==0 ) { + g.drawString("tca data is empty", leftEdge, baseLine); + return; + } + + tcaValue = tcaData[0].getXTagDouble(index, getUnits()); + + //Added in to say take nearest nieghbor as long as the distance to the nieghbor is + //not more than the xtagwidth. + double xTagWidth = DataSetUtil.guessXTagWidth(tcaData[0]).doubleValue( + getUnits().getOffsetUnits()); + double limit = Math.max(xTagWidth, pixelSize); + + if (Math.abs(tcaValue - value.doubleValue(getUnits())) > limit) { + return; + } + + Font tickLabelFont = getTickLabelFont(); + FontMetrics fm = getFontMetrics(tickLabelFont); + int lineHeight = tickLabelFont.getSize() + getLineSpacing(); + for (int i = 0; i < tcaData.length; i++) { + baseLine += lineHeight; + String item; + if (tcaFormatters[i] == null) { + item = format(tcaData[i].getDouble(index, tcaData[i].getYUnits()), "(f8.2)"); + } + else { + item = tcaFormatters[i].format(tcaData[i].getDatum(index)); + } + width = fm.stringWidth(item); + leftEdge = rightEdge - width; + g.drawString(item, leftEdge, baseLine); + } + } + + /** TODO + * @return + */ + public Font getTickLabelFont() { + return this.getFont(); + } + + /** TODO + * @param tickLabelFont + */ + public void setTickLabelFont(Font tickLabelFont) { + } + + /** TODO + * @return + */ + public Font getLabelFont() { + return this.getFont(); + } + + /** TODO + * @param labelFont + */ + public void setLabelFont(Font labelFont) { + // TODO: whah?--jbf + } + + public static class Memento { + + private DatumRange range; + private int dmin, dmax; + private boolean log; + private boolean flipped; + private DasAxis axis; + + public boolean equals(Object o) { + Memento m = (Memento) o; + return this == m || (this.range.equals(m.range) && + this.dmin == m.dmin && + this.dmax == m.dmax && + this.log == m.log && + this.flipped == m.flipped && + this.axis == m.axis); + } + + public String toString() { + return (log ? "log " : "") + range.toString() + " " + (dmax - dmin) + " pixels @ " + dmin; + } + } + + public Memento getMemento() { + Memento result = new Memento(); + result.range = this.getDatumRange(); + if (isHorizontal()) { + if (getColumn() != DasColumn.NULL) { + result.dmin = getColumn().getDMinimum(); + result.dmax = getColumn().getDMaximum(); + } else { + result.dmin = 0; + result.dmax = 0; + } + } else { + if (getRow() != DasRow.NULL) { + result.dmin = getRow().getDMinimum(); + result.dmax = getRow().getDMaximum(); + } else { + result.dmin = 0; + result.dmax = 0; + } + } + result.log = this.isLog(); + result.flipped = flipped; + result.axis = this; + return result; + } + + /** + * return the AffineTransform, or null. The transform will be applied after the input + * transform is applied. So to just get the transform, pass in identity. + */ + public AffineTransform getAffineTransform(Memento memento, AffineTransform at) { + if (at == null) { + return null; + } + if (memento.log != isLog()) { + return null; + } + if (memento.flipped != flipped) { + return null; + } + if (!memento.range.getUnits().isConvertableTo(getUnits())) { + return null; + } + + //TODO: remove cut-n-paste code + //return getAffineTransform(memento.range, false, at); + + double dmin0, dmax0; + + dmin0 = transform(memento.range.min()); + dmax0 = transform(memento.range.max()); + + if (!(isHorizontal() ^ flipped)) { + double tmp = dmin0; + dmin0 = dmax0; + dmax0 = tmp; + } + + if (!isHorizontal()) { + double dmin1 = getRow().getDMinimum(); + double dmax1 = getRow().getDMaximum(); + double scaley = (dmin0 - dmax0) / (dmin1 - dmax1); + double transy = -1 * dmin1 * scaley + dmin0; + at.translate(0., transy); + at.scale(1., scaley); + + } else { + double dmin1 = getColumn().getDMinimum(); + double dmax1 = getColumn().getDMaximum(); + + double scalex = (dmin0 - dmax0) / (dmin1 - dmax1); + double transx = -1 * dmin1 * scalex + dmin0; + at.translate(transx, 0); + at.scale(scalex, 1.); + + } + + if (at.getDeterminant() == 0.000) { + return null; + } else { + return at; + } + } + + /** TODO + * @return + */ + public Object clone() { + try { + DasAxis result = (DasAxis) super.clone(); + result.dataRange = (DataRange) result.dataRange.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new Error("Assertion failure"); + } + } + + private void setTickDirection(int direction) { + if (direction == UP || direction == RIGHT) { + tickDirection = -1; + } else if (direction == DOWN || direction == LEFT) { + tickDirection = 1; + } else { + throw new IllegalArgumentException("Invalid tick direction"); + } + } + + /** + * calculate the biggest label width + * @return the width in pixels of the widest label. + */ + private int getMaxLabelWidth() { + try { + Font f = getTickLabelFont(); + TickVDescriptor ticks = getTickV(); + DatumVector tickv = ticks.tickV; + int size = Integer.MIN_VALUE; + Graphics g = this.getGraphics(); + for (int i = 0; i < tickv.getLength(); i++) { + String label = tickFormatter(tickv.get(i)); + GrannyTextRenderer idlt = new GrannyTextRenderer(); + idlt.setString(f, label); + int labelSize = (int) Math.round(idlt.getWidth()); + if (labelSize > size) { + size = labelSize; + } + } + return size; + } catch (InconvertibleUnitsException ex) { + return 10; + } + } + + /** TODO + * @param fm + * @deprecated use getMaxLabelWidth() + * @return the width in pixels of the widest label. + */ + protected int getMaxLabelWidth(FontMetrics fm) { + try { + TickVDescriptor ticks = getTickV(); + DatumVector tickv = ticks.tickV; + int size = Integer.MIN_VALUE; + Graphics g = this.getGraphics(); + for (int i = 0; i < tickv.getLength(); i++) { + String label = tickFormatter(tickv.get(i)); + GrannyTextRenderer idlt = new GrannyTextRenderer(); + idlt.setString(g, label); + int labelSize = (int) Math.round(idlt.getWidth()); + if (labelSize > size) { + size = labelSize; + } + } + return size; + } catch (InconvertibleUnitsException ex) { + return 10; + } + } + + /** TODO */ + public void resize() { + resetTransform(); + Rectangle oldBounds = this.getBounds(); + setBounds(getAxisBounds()); + //setBounds(getAxisBoundsNew()); + invalidate(); + if (tickV == null || tickV.tickV.getUnits().isConvertableTo(getUnits())) { + validate(); + } + firePropertyChange(PROP_BOUNDS, oldBounds, getBounds()); + } + + + /** + * calculate the bounds of the labels. This should including regions that + * the labels could occupy if the axis were panned, so that result doesn't + * change during panning. + * + * @return Rectangle in the canvas coordinate frame. + */ + protected synchronized Rectangle getLabelBounds( Rectangle bounds ) { + String[] labels = tickFormatter(this.getTickV().tickV, getDatumRange()); + + int majorTickLength = (int) getEmSize(); + + GrannyTextRenderer gtr = new GrannyTextRenderer(); + + Font labelFont = this.getLabelFont(); + + int tickLen = (int) getEmSize(); + + double dmin= transform(getDataMinimum()); + double dmax= transform(getDataMaximum()); + + DatumVector ticks= this.getTickV().tickV; + for (int i = 0; i < labels.length; i++) { + Datum d = ticks.get(i); + DatumRange dr= getDatumRange(); + if (DatumRangeUtil.sloppyContains(dr, d)) { + gtr.setString(labelFont, labels[i]); + Rectangle rmin = gtr.getBounds(); + Rectangle rmax= new Rectangle(rmin); // same bound, but positioned at the axis max. + double flw = gtr.getLineOneWidth(); + + if (isHorizontal()) { + if (getOrientation() == BOTTOM) { + rmin.translate((int) (dmin - flw / 2), getRow().bottom() + tickLen + labelFont.getSize() ); + rmax.translate((int) (dmax - flw / 2), getRow().bottom() + tickLen + labelFont.getSize() ); + } else { + rmin.translate((int) (dmin - flw / 2), getRow().top() - tickLen - (int) rmin.getHeight()); + rmin.translate((int) (dmax - flw / 2), getRow().top() - tickLen - (int) rmax.getHeight()); + } + bounds.add(rmin); + bounds.add(rmax); + } else { + if (getOrientation() == LEFT) { + rmin.translate(-(int) rmin.getWidth() - tickLen + getColumn().left(), + (int) (dmin + getEmSize() / 2)); + rmax.translate(-(int) rmax.getWidth() - tickLen + getColumn().left(), + (int) (dmax + getEmSize() / 2)); + } else { + rmin.translate(tickLen + getColumn().right(), (int) (dmin + getEmSize() / 2)); + rmax.translate(tickLen + getColumn().right(), (int) (dmax + getEmSize() / 2)); + } + bounds.add(rmin); + bounds.add(rmax); + } + } + } + return bounds; + } + + /** + * Calculate the rectangle that bounds the axis including its labels. + * When the axis is drawn on both sides of the plot, this rectangle will + * extend across the plot. + * @return Rectangle containing the axes and its labels. + */ + protected Rectangle getAxisBounds() { + Rectangle bounds; + if (isHorizontal()) { + bounds = getHorizontalAxisBounds(); + } else { + bounds = getVerticalAxisBounds(); + } + if (getOrientation() == BOTTOM && areTickLabelsVisible()) { + if (drawTca && tcaData != null && tcaData.length != 0) { + int DMin = getColumn().getDMinimum(); + int DMax = getColumn().getDMaximum(); + Font tickLabelFont = getTickLabelFont(); + int tick_label_gap = getFontMetrics(tickLabelFont).stringWidth(" "); + int tcaHeight = (tickLabelFont.getSize() + getLineSpacing()) * tcaData.length; + int maxLabelWidth = getMaxLabelWidth(); + bounds.height += tcaHeight; + blLabelRect.height += tcaHeight; + if (blTitleRect != null) { + blTitleRect.y += tcaHeight; + } + GrannyTextRenderer idlt = new GrannyTextRenderer(); + idlt.setString(tickLabelFont, "SCET"); + int tcaLabelWidth = (int) Math.floor(idlt.getWidth() + 0.5); + for (int i = 0; i < tcaData.length; i++) { + String label = (String)tcaData[i].getProperty(DataSet.PROPERTY_Y_LABEL); + if (label == null) { + label = (String)tcaData[i].getProperty("label"); + } + if (label == null) { + label = ""; + } + idlt.setString(tickLabelFont, label); + int width = (int) Math.floor(idlt.getWidth() + 0.5); + tcaLabelWidth = Math.max(tcaLabelWidth, width); + } + tcaLabelWidth += 50; + if (tcaLabelWidth > 0) { + int tcaLabelSpace = DMin - tcaLabelWidth - tick_label_gap; + int minX = Math.min(tcaLabelSpace - maxLabelWidth / 2, bounds.x); + int maxX = bounds.x + bounds.width; + bounds.x = minX; + bounds.width = maxX - minX; + blLabelRect.x = minX; + blLabelRect.width = maxX - minX; + } + } + + for (int i = 0; i < tickV.tickV.getLength(); i++) { + if (false) { // this is unnecessary? I think it's a kludge for multiline ticks... + bounds.height += getTickLabelFont().getSize() + getLineSpacing(); + if (getTickDirection() == -1) { + bounds.y -= getTickLabelFont().getSize() + getLineSpacing(); + } + } + } + } + return bounds; + } + + private Rectangle getHorizontalAxisBounds() { + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + DasDevicePosition range = getColumn(); + int DMax = range.getDMaximum(); + int DMin = range.getDMinimum(); + int DWidth= DMax- DMin; + + boolean bottomTicks = (orientation == BOTTOM || oppositeAxisVisible); + boolean bottomTickLabels = (orientation == BOTTOM && tickLabelsVisible); + boolean bottomLabel = (bottomTickLabels && !axisLabel.equals("")); + boolean topTicks = (orientation == TOP || oppositeAxisVisible); + boolean topTickLabels = (orientation == TOP && tickLabelsVisible); + boolean topLabel = (topTickLabels && !axisLabel.equals("")); + + Rectangle bounds; + + Font tickLabelFont = getTickLabelFont(); + + int tickSize = tickLabelFont.getSize() * 2 / 3; + + if (bottomTicks) { + if (blLineRect == null) { + blLineRect = new Rectangle(); + } + blLineRect.setBounds(DMin, bottomPosition, DWidth + 1, 1); + } + if (topTicks) { + if (trLineRect == null) { + trLineRect = new Rectangle(); + } + trLineRect.setBounds(DMin, topPosition, DWidth + 1, 1); + } + + //Add room for ticks + if (bottomTicks) { + int x = DMin; + int y = bottomPosition + 1; + int width = DWidth; + int height = tickSize; + //The last tick is at position (x + width), so add 1 to width + blTickRect = setRectangleBounds(blTickRect, x, y, width + 1, height); + } + if (topTicks) { + int x = DMin; + int y = topPosition - tickSize; + int width = DWidth; + int height = tickSize; + //The last tick is at position (x + width), so add 1 to width + trTickRect = setRectangleBounds(trTickRect, x, y, width + 1, height); + } + + //int maxLabelWidth = getMaxLabelWidth(); + //int tick_label_gap = getFontMetrics(tickLabelFont).stringWidth(" "); + + if (bottomTickLabels) { + blLabelRect = getLabelBounds( new Rectangle( DMin, blTickRect.y, DWidth, 10 ) ); + //int x = DMin - maxLabelWidth / 2; + //int y = blTickRect.y + blTickRect.height; + //int width = DMax - DMin + maxLabelWidth; + //int height = tickLabelFont.getSize() * 3 / 2 + tick_label_gap; + //blLabelRect = setRectangleBounds(blLabelRect, x, y, width, height); + } + if (topTickLabels) { + trLineRect = getLabelBounds( new Rectangle( DMin, topPosition-10, DWidth, 10 ) ); + //int x = DMin - maxLabelWidth / 2; + //int y = topPosition - (tickLabelFont.getSize() * 3 / 2 + tick_label_gap + 1); + //int width = DMax - DMin + maxLabelWidth; + //int height = tickLabelFont.getSize() * 3 / 2 + tick_label_gap; + //trLabelRect = setRectangleBounds(trLabelRect, x, y, width, height); + } + + //Add room for the axis label + Font labelFont = getLabelFont(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, getLabel()); + int labelSpacing = (int) gtr.getHeight() + labelFont.getSize() / 2; + if (bottomLabel) { + int x = DMin; + int y = blLabelRect.y + blLabelRect.height; + int width = DMax - DMin; + int height = labelSpacing; + blTitleRect = setRectangleBounds(blTitleRect, x, y, width, height); + } + if (topLabel) { + int x = DMin; + int y = trLabelRect.y - labelSpacing; + int width = DMax - DMin; + int height = labelSpacing; + trTitleRect = setRectangleBounds(trTitleRect, x, y, width, height); + } + + bounds = new Rectangle((orientation == BOTTOM) ? blLineRect : trLineRect); + if (bottomTicks) { + bounds.add(blLineRect); + bounds.add(blTickRect); + } + if (bottomTickLabels) { + bounds.add(blLabelRect); + } + if (bottomLabel) { + bounds.add(blTitleRect); + } + if (topTicks) { + bounds.add(trLineRect); + bounds.add(trTickRect); + } + if (topTickLabels) { + bounds.add(trLabelRect); + } + if (topLabel) { + bounds.add(trTitleRect); + } + + //Add room for the scan buttons (if present) + if (scanPrevious != null && scanNext != null) { + Dimension prevSize = scanPrevious.getPreferredSize(); + Dimension nextSize = scanPrevious.getPreferredSize(); + int minX = Math.min(DMin - prevSize.width, bounds.x); + int maxX = Math.max(DMax + nextSize.width, bounds.x + bounds.width); + bounds.x = minX; + bounds.width = maxX - minX; + } + + return bounds; + } + + private Rectangle getVerticalAxisBounds() { + boolean leftTicks = (orientation == LEFT || oppositeAxisVisible); + boolean leftTickLabels = (orientation == LEFT && tickLabelsVisible); + boolean leftLabel = (orientation == LEFT && !axisLabel.equals("")); + boolean rightTicks = (orientation == RIGHT || oppositeAxisVisible); + boolean rightTickLabels = (orientation == RIGHT && tickLabelsVisible); + boolean rightLabel = (orientation == RIGHT && !axisLabel.equals("")); + + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax = getRow().getDMaximum(); + int DMin = getRow().getDMinimum(); + int DWidth= DMax-DMin; + + Rectangle bounds; + + Font tickLabelFont = getTickLabelFont(); + + int tickSize = tickLabelFont.getSize() * 2 / 3; + + if (leftTicks) { + if (blLineRect == null) { + blLineRect = new Rectangle(); + } + blLineRect.setBounds(leftPosition, DMin, 1, DWidth + 1); + } + if (rightTicks) { + if (trLineRect == null) { + trLineRect = new Rectangle(); + } + trLineRect.setBounds(rightPosition, DMin, 1, DWidth + 1); + } + + //Add room for ticks + if (leftTicks) { + int x = leftPosition - tickSize; + int y = DMin; + int width = tickSize; + int height = DWidth; + //The last tick is at position (y + height), so add 1 to height + blTickRect = setRectangleBounds(blTickRect, x, y, width, height + 1); + } + if (rightTicks) { + int x = rightPosition + 1; + int y = DMin; + int width = tickSize; + int height = DWidth; + //The last tick is at position (y + height), so add 1 to height + trTickRect = setRectangleBounds(trTickRect, x, y, width, height + 1); + } + + //int maxLabelWidth = getMaxLabelWidth(); + //int tick_label_gap = getFontMetrics(tickLabelFont).stringWidth(" "); + + //Add room for tick labels + if (leftTickLabels) { + //int x = blTickRect.x - (maxLabelWidth + tick_label_gap); + //int y = DMin - tickLabelFont.getSize(); + //int width = maxLabelWidth + tick_label_gap; + //int height = DMax - DMin + tickLabelFont.getSize() * 2; + //blLabelRect = setRectangleBounds(blLabelRect, x, y, width, height); + blLabelRect= getLabelBounds( new Rectangle(blTickRect.x-10,DMin,10,DWidth) ); + } + if (rightTickLabels) { + trLabelRect= getLabelBounds( new Rectangle(trTickRect.x+trTickRect.width,DMin,10,DWidth) ); + //int x = trTickRect.x + trTickRect.width; + //int y = DMin - tickLabelFont.getSize(); + //int width = maxLabelWidth + tick_label_gap; + //int height = DMax - DMin + tickLabelFont.getSize() * 2; + //trLabelRect = setRectangleBounds(trLabelRect, x, y, width, height); + } + + //Add room for the axis label + Font labelFont = getLabelFont(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(labelFont, getLabel()); + int labelSpacing = (int) gtr.getHeight() + labelFont.getSize() / 2; + if (leftLabel) { + int x = blLabelRect.x - labelSpacing; + int y = DMin; + int width = labelSpacing; + int height = DWidth; + blTitleRect = setRectangleBounds(blTitleRect, x, y, width, height); + } + if (rightLabel) { + int x = trLabelRect.x + trLabelRect.width; + int y = DMin; + int width = labelSpacing; + int height = DWidth; + trTitleRect = setRectangleBounds(trTitleRect, x, y, width, height); + } + + bounds = new Rectangle((orientation == LEFT) ? blLineRect : trLineRect); + if (leftTicks) { + bounds.add(blLineRect); + bounds.add(blTickRect); + } + if (leftTickLabels) { + bounds.add(blLabelRect); + } + if (leftLabel) { + bounds.add(blTitleRect); + } + if (rightTicks) { + bounds.add(trLineRect); + bounds.add(trTickRect); + } + if (rightTickLabels) { + bounds.add(trLabelRect); + } + if (rightLabel) { + bounds.add(trTitleRect); + } + + return bounds; + } + + private static Rectangle setRectangleBounds(Rectangle rc, int x, int y, int width, int height) { + if (rc == null) { + return new Rectangle(x, y, width, height); + } else { + rc.setBounds(x, y, width, height); + return rc; + } + } + + /** + * returns the orientation of the axis, which is one of BOTTOM,TOP,LEFT or RIGHT. + * @return BOTTOM,TOP,LEFT or RIGHT. + */ + public int getOrientation() { + return orientation; + } + + /** + * test if the axis is horizontal. + * @return true if the orientation is BOTTOM or TOP. + */ + public boolean isHorizontal() { + return orientation == BOTTOM || orientation == TOP; + } + + /** TODO + * @return + */ + public int getTickDirection() { + return tickDirection; + } + + /** TODO + * @return + */ + public DatumFormatter getDatumFormatter() { + return datumFormatter; + } + + /** Transforms a Datum in data coordinates to a horizontal or vertical + * position on the parent canvas. + * @param datum a data value + * @return Horizontal or vertical position on the canvas. + */ + public double transform(Datum datum) { + return transform(datum.doubleValue(getUnits()), getUnits()); + } + + protected double transformFast(double data, Units units) { + if (dataRange.isLog()) { + if (data <= 0.) { + data = dataRange.getMinimum() - 3; // TODO verify that dataRange.getMinimum() is log. + } else { + data = DasMath.log10(data); + } + } + double result = at_m * data + at_b; + return result; + } + + /** Transforms a double in the given units in data coordinates to a horizontal or vertical + * position on the parent canvas. + * @param data a data value + * @param units the units of the given data value. + * @return Horizontal or vertical position on the canvas. + */ + protected double transform(double data, Units units) { + DasDevicePosition range; + // TODO: consider optimization here + if (isHorizontal()) { + range = getColumn(); + return transform(data, units, range.getDMinimum(), range.getDMaximum()); + } else { + range = getRow(); + return transform(data, units, range.getDMaximum(), range.getDMinimum()); + } + } + + protected double transform(double data, Units units, int dmin, int dmax) { + if (units != dataRange.getUnits()) { + data = units.convertDoubleTo(dataRange.getUnits(), data); + } + + double device_range = (dmax - dmin); + double result; + + if (dataRange.isLog()) { + if (data <= 0.) { + data = -1e308; + } else { + data = DasMath.log10(data); + } + } + + double minimum = dataRange.getMinimum(); + double maximum = dataRange.getMaximum(); + double data_range = maximum - minimum; + + if (flipped) { + result = dmax - (device_range * (data - minimum) / data_range); + } else { + result = (device_range * (data - minimum) / data_range) + dmin; + } + + if (result > 10000) { + result = 10000; + } + if (result < -10000) { + result = -10000; + } + return result; + } + + public Datum invTransform(double idata) { + double data; + DasDevicePosition range = (isHorizontal() + ? (DasDevicePosition) getColumn() + : (DasDevicePosition) getRow()); + + double alpha = (idata - range.getDMinimum()) / (double) getDLength(); + if (!isHorizontal()) { + alpha = 1.0 - alpha; + } + if (flipped) { + alpha = 1.0 - alpha; + } + + double minimum = dataRange.getMinimum(); + double maximum = dataRange.getMaximum(); + double data_range = maximum - minimum; + data = data_range * alpha + minimum; + + double resolution = data_range / getDLength(); + if (dataRange.isLog()) { + data = DasMath.exp10(data); + resolution = data * (DasMath.exp10(resolution) - 1); + } + + Datum result = Datum.create(data, dataRange.getUnits(), resolution); + + return result; + } + + /** + * return a label for this datum and visible range. This is intended + * to be overriden to change behavior. Note that both tickFormatter methods + * should be overridden. + * @param tickv + * @return string, possibly with Granny control characters. + */ + protected String tickFormatter(Datum d) { + // TODO: label the axis with the Unit! + return datumFormatter.grannyFormat(d, d.getUnits()); + + } + + /** + * return the tick labels for these datums and visible range. This is intended + * to be overriden to change behavior. Note that both tickFormatter methods + * should be overridden. + * @param tickV + * @param datumRange + * @return Strings, possibly with Granny control characters. + */ + protected String[] tickFormatter(DatumVector tickV, DatumRange datumRange) { + return datumFormatter.axisFormat(tickV, datumRange); + } + + /** TODO + * @param e + */ + public void dataRangeSelected(DataRangeSelectionEvent e) { + this.setDataRange(e.getMinimum(), e.getMaximum()); + } + + /** TODO + * @param xDatum + * @param direction + * @param minor + * @return + * + * @depricated. Use getTickVDescriptor.findTick + */ + public Datum findTick(Datum xDatum, double direction, boolean minor) { + return getTickV().findTick(xDatum, direction, minor); + } + + /** TODO + * @param min0 + * @param max0 + * @param min1 + * @param max1 + */ + private void animateChange(double min0, double max0, double min1, double max1) { + + if (animated && EventQueue.isDispatchThread()) { + + + + + logger.fine("animate axis"); + + boolean drawTca0 = getDrawTca(); + setDrawTca(false); + + long t0 = System.currentTimeMillis(); + long frames = 0; + + DataRange dataRange0 = dataRange; + DataRange tempRange = DataRange.getAnimationDataRange(dataRange.getDatumRange(), dataRange.isLog()); + + this.dataRange = tempRange; + + double transitionTime = 300; // millis + //double transitionTime= 1500; // millis + double alpha = (System.currentTimeMillis() - t0) / transitionTime; + + while (alpha < 1.0) { + alpha = (System.currentTimeMillis() - t0) / transitionTime; + + final double[] aa = new double[]{0.0, 0.3, 0.85, 1.0}; + final double[] aa1 = new double[]{0.0, 0.05, 0.90, 1.0}; + + double f1 = DasMath.findex(aa, alpha, 0); + double a1 = DasMath.interpolate(aa1, f1); + double a0 = 1 - a1; + + tempRange.setRange(min0 * a0 + min1 * a1, max0 * a0 + max1 * a1); + + //updateTickV(); + this.paintImmediately(0, 0, this.getWidth(), this.getHeight()); + + if (dasPlot != null) { + dasPlot.paintImmediately(0, 0, dasPlot.getWidth(), dasPlot.getHeight()); + } + frames++; + } + + logger.fine("animation frames/sec= " + (1000. * frames / transitionTime)); + setDrawTca(drawTca0); + + this.dataRange = dataRange0; + } + } + + /** TODO */ + protected void updateImmediately() { + super.updateImmediately(); + logger.finer("" + getDatumRange() + " " + isLog()); + resetTransform(); + updateTickV(); + } + + /** TODO + * @return + * @deprecated use isTickLabelsVisible + */ + public boolean areTickLabelsVisible() { + return tickLabelsVisible; + } + + /** + * true if the tick labels should be drawn. + * @return + */ + public boolean isTickLabelsVisible() { + return tickLabelsVisible; + } + + /** TODO + * @param b + */ + public void setTickLabelsVisible(boolean b) { + if (tickLabelsVisible == b) { + return; + } + boolean oldValue = ticksVisible; + tickLabelsVisible = b; + update(); + firePropertyChange("tickLabelsVisible", oldValue, b); + } + + /** TODO */ + protected void installComponent() { + super.installComponent(); + } + + /** TODO */ + protected void uninstallComponent() { + super.uninstallComponent(); + } + + /** Process an <axis> element. + * + * @param element The DOM tree node that represents the element + */ + static DasAxis processAxisElement(Element element, FormBase form) throws DasPropertyException, DasNameException, ParseException { + String name = element.getAttribute("name"); + boolean log = element.getAttribute(PROP_LOG).equals("true"); + Datum dataMinimum; + Datum dataMaximum; + if ("TIME".equals(element.getAttribute("units"))) { + String min = element.getAttribute("dataMinimum"); + String max = element.getAttribute("dataMaximum"); + dataMinimum = (min == null || min.equals("") ? TimeUtil.create("1979-02-26") : TimeUtil.create(min)); + dataMaximum = (max == null || max.equals("") ? TimeUtil.create("1979-02-27") : TimeUtil.create(max)); + } else { + String min = element.getAttribute("dataMinimum"); + String max = element.getAttribute("dataMaximum"); + dataMinimum = (min == null || min.equals("") ? Datum.create(1.0) : Datum.create(Double.parseDouble(min))); + dataMaximum = (max == null || max.equals("") ? Datum.create(10.0) : Datum.create(Double.parseDouble(max))); + } + int orientation = parseOrientationString(element.getAttribute("orientation")); + DasAxis axis = new DasAxis(dataMinimum, dataMaximum, orientation, log); + String rowString = element.getAttribute("row"); + if (!rowString.equals("")) { + DasRow row = (DasRow) form.checkValue(rowString, DasRow.class, ""); + axis.setRow(row); + } + String columnString = element.getAttribute("column"); + if (!columnString.equals("")) { + DasColumn column = (DasColumn) form.checkValue(columnString, DasColumn.class, ""); + axis.setColumn(column); + } + + axis.setLabel(element.getAttribute(PROP_LABEL)); + axis.setOppositeAxisVisible(!element.getAttribute(PROP_OPPOSITE_AXIS_VISIBLE).equals("false")); + axis.setTickLabelsVisible(!element.getAttribute("tickLabelsVisible").equals("false")); + + axis.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, axis); + + return axis; + } + + /** TODO + * @param i + * @return + */ + protected static String orientationToString(int i) { + switch (i) { + case TOP: + return "top"; + case BOTTOM: + return "bottom"; + case LEFT: + return "left"; + case RIGHT: + return "right"; + default: + throw new IllegalStateException("invalid orienation: " + i); + } + } + + /** TODO + * @param orientationString + * @return + */ + protected static int parseOrientationString(String orientationString) { + if (orientationString.equals("horizontal")) { + return HORIZONTAL; + } else if (orientationString.equals("vertical")) { + return VERTICAL; + } else if (orientationString.equals("left")) { + return LEFT; + } else if (orientationString.equals("right")) { + return RIGHT; + } else if (orientationString.equals("top")) { + return TOP; + } else if (orientationString.equals("bottom")) { + return BOTTOM; + } else { + throw new IllegalArgumentException("Invalid orientation: " + orientationString); + } + } + + /** TODO + * @param document + * @return + */ + public Element getDOMElement(Document document) { + Element element; + if (this.isAttached()) { + element = document.createElement("attachedaxis"); + } else { + element = document.createElement("axis"); + } + if (this.isAttached()) { + element.setAttribute("ref", this.getMasterAxis().getDasName()); + } else { + String minimumStr = getDataMinimum().toString(); + element.setAttribute("dataMinimum", minimumStr); + String maximumStr = getDataMaximum().toString(); + element.setAttribute("dataMaximum", maximumStr); + } + + element.setAttribute("name", getDasName()); + element.setAttribute("row", getRow().getDasName()); + element.setAttribute("column", getColumn().getDasName()); + + element.setAttribute(PROP_LABEL, getLabel()); + element.setAttribute(PROP_LOG, Boolean.toString(isLog())); + element.setAttribute("tickLabelsVisible", Boolean.toString(areTickLabelsVisible())); + element.setAttribute(PROP_OPPOSITE_AXIS_VISIBLE, Boolean.toString(isOppositeAxisVisible())); + element.setAttribute("animated", Boolean.toString(isAnimated())); + element.setAttribute("orientation", orientationToString(getOrientation())); + + return element; + } + + /** Create a new axis that uses the same DataRange object as this axis. + * + * @return A new DasAxis with the same "backing store" (i.e. the DataRange object) + * as this axis. + */ + public DasAxis createAttachedAxis() { + return new DasAxis(this.dataRange, this.getOrientation()); + } + + /** + * Note: This is commonly used for pop-up slicer windows. + * @return + */ + public DasAxis createAttachedAxis(int orientation) { + return new DasAxis(this.dataRange, orientation); + } + + /** Process a <attachedaxis> element. + * + * @param element The DOM tree node that represents the element + */ + static DasAxis processAttachedaxisElement(Element element, FormBase form) throws DasPropertyException, DasNameException { + String name = element.getAttribute("name"); + DasAxis ref = (DasAxis) form.checkValue(element.getAttribute("ref"), DasAxis.class, ""); + int orientation = (element.getAttribute("orientation").equals("horizontal") ? HORIZONTAL : DasAxis.VERTICAL); + + DasAxis axis = ref.createAttachedAxis(orientation); + + String rowString = element.getAttribute("row"); + if (!rowString.equals("")) { + DasRow row = (DasRow) form.checkValue(rowString, DasRow.class, ""); + axis.setRow(row); + } + String columnString = element.getAttribute("column"); + if (!columnString.equals("")) { + DasColumn column = (DasColumn) form.checkValue(columnString, DasColumn.class, ""); + axis.setColumn(column); + } + + axis.setDataPath(element.getAttribute("dataPath")); + axis.setDrawTca(element.getAttribute("showTca").equals("true")); + axis.setLabel(element.getAttribute(PROP_LABEL)); + axis.setOppositeAxisVisible(!element.getAttribute(PROP_OPPOSITE_AXIS_VISIBLE).equals("false")); + axis.setTickLabelsVisible(!element.getAttribute("tickLabelsVisible").equals("false")); + + axis.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, axis); + + return axis; + } + + public void setPlot(DasPlot p) { + dasPlot = p; + } + + /** TODO + * @param name + * @return + */ + public static DasAxis createNamedAxis(String name) { + DasAxis axis = new DasAxis(Datum.create(1.0, Units.dimensionless), Datum.create(10.0, Units.dimensionless), DasAxis.HORIZONTAL); + if (name == null) { + name = "axis_" + Integer.toHexString(System.identityHashCode(axis)); + } + try { + axis.setDasName(name); + } catch (DasNameException dne) { + DasExceptionHandler.handle(dne); + } + return axis; + } + + /** TODO */ + public void scanPrevious() { + Datum delta = (getDataMaximum().subtract(getDataMinimum())).multiply(1.0); + Datum tmin = getDataMinimum().subtract(delta); + Datum tmax = getDataMaximum().subtract(delta); + setDataRange(tmin, tmax); + } + + /** TODO */ + public void scanNext() { + Datum delta = (getDataMaximum().subtract(getDataMinimum())).multiply(1.0); + Datum tmin = getDataMinimum().add(delta); + Datum tmax = getDataMaximum().add(delta); + setDataRange(tmin, tmax); + } + + /** TODO + * @return + */ + public Shape getActiveRegion() { + Rectangle primaryBounds = primaryInputPanel.getBounds(); + primaryBounds.translate(getX(), getY()); + if (oppositeAxisVisible) { + Rectangle secondaryBounds = secondaryInputPanel.getBounds(); + secondaryBounds.translate(getX(), getY()); + GeneralPath path = new GeneralPath(primaryBounds); + path.setWindingRule(GeneralPath.WIND_EVEN_ODD); + path.append(secondaryBounds, false); + return path; + } else { + return primaryBounds; + } + } + + /** + * Adds a MouseWheelListener to the DasAxis. Special care must be taken + * with the DasAxis, because it is composed of two sub panels, and their + * parent panel (this), must not recieve the events. (This is because + * the DasPlot between them should get the events, and the DasPlot does + * not have a simple rectangular boundary. + */ + public void addMouseWheelListener(MouseWheelListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.addMouseWheelListener(l); + secondaryInputPanel.addMouseWheelListener(l); + } + + public void removeMouseWheelListener(MouseWheelListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.removeMouseWheelListener(l); + secondaryInputPanel.removeMouseWheelListener(l); + } + + /** TODO + * @param l + */ + public void addMouseListener(MouseListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.addMouseListener(l); + secondaryInputPanel.addMouseListener(l); + } + + /** TODO + * @param l + */ + public void removeMouseListener(MouseListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.removeMouseListener(l); + secondaryInputPanel.removeMouseListener(l); + } + + /** TODO + * @param l + */ + public void addMouseMotionListener(MouseMotionListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.addMouseMotionListener(l); + secondaryInputPanel.addMouseMotionListener(l); + } + + /** TODO + * @param l + */ + public void removeMouseMotionListener(MouseMotionListener l) { + maybeInitializeInputPanels(); + primaryInputPanel.removeMouseMotionListener(l); + secondaryInputPanel.removeMouseMotionListener(l); + } + + public void timeRangeSelected(TimeRangeSelectionEvent e) { + if (e.getSource() != this && !e.equals(lastProcessedEvent)) { + setDatumRange(e.getRange()); // setDatumRange fires the event + lastProcessedEvent = e; + } + } + + /** Registers TimeRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addTimeRangeSelectionListener(org.das2.event.TimeRangeSelectionListener listener) { + if (timeRangeListenerList == null) { + timeRangeListenerList = new javax.swing.event.EventListenerList(); + } + timeRangeListenerList.add(org.das2.event.TimeRangeSelectionListener.class, listener); + } + + /** Removes TimeRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeTimeRangeSelectionListener(org.das2.event.TimeRangeSelectionListener listener) { + timeRangeListenerList.remove(org.das2.event.TimeRangeSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireTimeRangeSelectionListenerTimeRangeSelected(TimeRangeSelectionEvent event) { + if (timeRangeListenerList == null) { + return; + } + Object[] listeners = timeRangeListenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == org.das2.event.TimeRangeSelectionListener.class) { + String logmsg = "fire event: " + this.getClass().getName() + "-->" + listeners[i + 1].getClass().getName() + " " + event; + DasLogger.getLogger(DasLogger.GUI_LOG).fine(logmsg); + ((org.das2.event.TimeRangeSelectionListener) listeners[i + 1]).timeRangeSelected(event); + } + } + } + + static DasAxis processTimeaxisElement(Element element, FormBase form) throws org.das2.DasPropertyException,org.das2.DasNameException, DasException, java.text.ParseException { + String name = element.getAttribute("name"); + Datum timeMinimum = TimeUtil.create(element.getAttribute("timeMinimum")); + Datum timeMaximum = TimeUtil.create(element.getAttribute("timeMaximum")); + int orientation = parseOrientationString(element.getAttribute("orientation")); + + DasAxis timeaxis = new DasAxis(timeMinimum, timeMaximum, orientation); + + String rowString = element.getAttribute("row"); + if (!rowString.equals("")) { + DasRow row = (DasRow) form.checkValue(rowString, DasRow.class, ""); + timeaxis.setRow(row); + } + String columnString = element.getAttribute("column"); + if (!columnString.equals("")) { + DasColumn column = (DasColumn) form.checkValue(columnString, DasColumn.class, ""); + timeaxis.setColumn(column); + } + + timeaxis.setDataPath(element.getAttribute("dataPath")); + timeaxis.setDrawTca(element.getAttribute("showTca").equals("true")); + timeaxis.setLabel(element.getAttribute(PROP_LABEL)); + timeaxis.setOppositeAxisVisible(!element.getAttribute(PROP_OPPOSITE_AXIS_VISIBLE).equals("false")); + timeaxis.setTickLabelsVisible(!element.getAttribute("tickLabelsVisible").equals("false")); + + timeaxis.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, timeaxis); + + return timeaxis; + } + private static final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\([eEfF]\\d+.\\d+\\)"); + + private static String format(double d, String f) { + Matcher m = pattern.matcher(f); + if (!m.matches()) { + throw new IllegalArgumentException("\"" + f + "\" is not a valid format specifier"); + } + int length = Integer.parseInt(f.substring(2, f.indexOf('.'))); + int fracLength = Integer.parseInt(f.substring(f.indexOf('.') + 1, f.indexOf(')'))); + char[] buf = new char[length]; + String result; + if (f.charAt(1) == 'f' || f.charAt(1) == 'F') { + int i = 0; + while (i < length - fracLength - 2) { + buf[i] = '#'; + i++; + } + buf[i] = '0'; + i++; + buf[i] = '.'; + i++; + while (i < length) { + buf[i] = '0'; + i++; + } + DecimalFormat form = new DecimalFormat(new String(buf)); + result = form.format(d); + } else { + int i = 0; + while (i < length - fracLength - 6) { + buf[i] = '#'; + i++; + } + buf[i] = '0'; + i++; + buf[i] = '.'; + i++; + while (i < length - 5) { + buf[i] = '0'; + i++; + } + buf[i] = 'E'; + buf[i + 1] = (d > -1.0 && d < 1.0 ? '-' : '+'); + buf[i + 2] = '0'; + buf[i + 3] = '0'; + java.text.DecimalFormat form = new java.text.DecimalFormat(new String(buf)); + result = form.format(d); + } + + if (result.length() > length) { + java.util.Arrays.fill(buf, '*'); + return new String(buf); + } + + while (result.length() < length) { + result = " " + result; + } + return result; + } + + public String toString() { + String retValue; + retValue = super.toString() + "(" + getUnits() + ")"; + return retValue; + } + + protected class AxisLayoutManager implements LayoutManager { + //NOOP + /** TODO + * @param name + * @param comp + */ + public void addLayoutComponent(String name, Component comp) { + } + + /** TODO + * @param parent + */ + public void layoutContainer(Container parent) { + if (DasAxis.this != parent) { + throw new IllegalArgumentException(); + } + if (DasAxis.this.isHorizontal()) { + horizontalLayout(); + } else { + verticalLayout(); + } + if (drawTca && getOrientation() == BOTTOM && tcaData != null) { + Rectangle bounds = primaryInputPanel.getBounds(); + int tcaHeight = (getTickLabelFont().getSize() + getLineSpacing()) * tcaData.length; + bounds.height += tcaHeight; + primaryInputPanel.setBounds(bounds); + } + } + + /** TODO */ + protected void horizontalLayout() { + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + int DMax = getColumn().getDMaximum(); + int DMin = getColumn().getDMinimum(); + + + boolean bottomTicks = (orientation == BOTTOM || oppositeAxisVisible); + boolean bottomTickLabels = (orientation == BOTTOM && tickLabelsVisible); + boolean topTicks = (orientation == TOP || oppositeAxisVisible); + boolean topTickLabels = (orientation == TOP && tickLabelsVisible); + Rectangle bottomBounds = null; + Rectangle topBounds = null; + Font tickLabelFont = getTickLabelFont(); + int tickSize = tickLabelFont.getSize() * 2 / 3; + //Initialize bounds rectangle + if (bottomTicks) { + bottomBounds = new Rectangle(DMin, bottomPosition, DMax - DMin + 1, 1); + } + if (topTicks) { + topBounds = new Rectangle(DMin, topPosition, DMax - DMin + 1, 1); + } + //Add room for ticks + if (bottomTicks) { + bottomBounds.height += tickSize; + } + if (topTicks) { + topBounds.height += tickSize; + topBounds.y -= tickSize; + } + int tick_label_gap = getFontMetrics(tickLabelFont).stringWidth(" "); + //Add room for tick labels + if (bottomTickLabels) { + bottomBounds.height += tickLabelFont.getSize() * 3 / 2 + tick_label_gap; + } + if (topTickLabels) { + topBounds.y -= (tickLabelFont.getSize() * 3 / 2 + tick_label_gap); + topBounds.height += tickLabelFont.getSize() * 3 / 2 + tick_label_gap; + } + + Rectangle primaryBounds = (orientation == BOTTOM ? bottomBounds : topBounds); + Rectangle secondaryBounds = (orientation == BOTTOM ? topBounds : bottomBounds); + + primaryBounds.translate(-DasAxis.this.getX(), -DasAxis.this.getY()); + if (oppositeAxisVisible) { + secondaryBounds.translate(-DasAxis.this.getX(), -DasAxis.this.getY()); + } + + primaryInputPanel.setBounds(primaryBounds); + if (oppositeAxisVisible) { + secondaryInputPanel.setBounds(secondaryBounds); + } else { + secondaryInputPanel.setBounds(-100, -100, 0, 0); + } + + if (scanPrevious != null && scanNext != null) { + Dimension preferred = scanPrevious.getPreferredSize(); + int x = DMin - preferred.width - DasAxis.this.getX(); + int y = (orientation == BOTTOM ? bottomPosition : topPosition - preferred.height) - DasAxis.this.getY(); + scanPrevious.setBounds(x, y, preferred.width, preferred.height); + preferred = scanNext.getPreferredSize(); + x = DMax - DasAxis.this.getX(); + scanNext.setBounds(x, y, preferred.width, preferred.height); + } + } + + /** TODO */ + protected void verticalLayout() { + boolean leftTicks = (orientation == LEFT || oppositeAxisVisible); + boolean leftTickLabels = (orientation == LEFT && tickLabelsVisible); + boolean rightTicks = (orientation == RIGHT || oppositeAxisVisible); + boolean rightTickLabels = (orientation == RIGHT && tickLabelsVisible); + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax = getRow().getDMaximum(); + int DMin = getRow().getDMinimum(); + Rectangle leftBounds = null; + Rectangle rightBounds = null; + Font tickLabelFont = getTickLabelFont(); + int tickSize = tickLabelFont.getSize() * 2 / 3; + //Initialize bounds rectangle(s) + if (leftTicks) { + leftBounds = new Rectangle(leftPosition, DMin, 1, DMax - DMin + 1); + } + if (rightTicks) { + rightBounds = new Rectangle(rightPosition, DMin, 1, DMax - DMin + 1); + } + //Add room for ticks + if (leftTicks) { + leftBounds.width += tickSize; + leftBounds.x -= tickSize; + } + if (rightTicks) { + rightBounds.width += tickSize; + } + int maxLabelWidth = getMaxLabelWidth(); + int tick_label_gap = getFontMetrics(tickLabelFont).stringWidth(" "); + //Add room for tick labels + if (leftTickLabels) { + leftBounds.x -= (maxLabelWidth + tick_label_gap); + leftBounds.width += maxLabelWidth + tick_label_gap; + //bounds.y -= tickLabelFont.getSize(); + //bounds.height += tickLabelFont.getSize()*2; + } + if (rightTickLabels) { + rightBounds.width += maxLabelWidth + tick_label_gap; + //bounds.y -= tickLabelFont.getSize(); + //bounds.height += tickLabelFont.getSize()*2; + } + + Rectangle primaryBounds = (orientation == LEFT ? leftBounds : rightBounds); + Rectangle secondaryBounds = (orientation == LEFT ? rightBounds : leftBounds); + + primaryBounds.translate(-DasAxis.this.getX(), -DasAxis.this.getY()); + if (oppositeAxisVisible) { + secondaryBounds.translate(-DasAxis.this.getX(), -DasAxis.this.getY()); + } + + primaryInputPanel.setBounds(primaryBounds); + if (oppositeAxisVisible) { + secondaryInputPanel.setBounds(secondaryBounds); + } else { + secondaryInputPanel.setBounds(-100, -100, 0, 0); + } + } + + /** TODO + * @param parent + * @return + */ + public Dimension minimumLayoutSize(Container parent) { + return new Dimension(); + } + + /** TODO + * @param parent + * @return + */ + public Dimension preferredLayoutSize(Container parent) { + return new Dimension(); + } + //NOOP + /** TODO + * @param comp + */ + public void removeLayoutComponent(Component comp) { + } + } + + private static class ScanButton extends JButton { + + private boolean hover; + private boolean pressed; + + /** TODO + * @param text + */ + public ScanButton(String text) { + setOpaque(true); + setContentAreaFilled(false); + setText(text); + setFocusable(false); + setBorder(new CompoundBorder( + new LineBorder(Color.BLACK), + new EmptyBorder(2, 2, 2, 2))); + this.addMouseListener(new MouseAdapter() { + + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + setForeground(Color.LIGHT_GRAY); + pressed = true; + repaint(); + } + } + + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + setForeground(Color.BLACK); + pressed = false; + repaint(); + } + } + + public void mouseEntered(MouseEvent e) { + hover = true; + repaint(); + } + + public void mouseExited(MouseEvent e) { + hover = false; + repaint(); + } + }); + } + + /** TODO + * @param g + */ + protected void paintComponent(Graphics g) { + if (hover || pressed) { + Graphics2D g2 = (Graphics2D) g; + g2.setColor(Color.white); + g2.fillRect(0, 0, getWidth(), getHeight()); + Object aaHint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING); + Object aaOn = RenderingHints.VALUE_ANTIALIAS_ON; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaOn); + super.paintComponent(g2); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint); + } + } + + /** TODO + * @param g + */ + protected void paintBorder(Graphics g) { + if (hover || pressed) { + super.paintBorder(g); + } + } + } + + public boolean isFlipped() { + return flipped; + } + + public void setFlipped(boolean b) { + update(); + this.flipped = b; + } + protected String formatString = ""; + public static final String PROP_FORMATSTRING = "formatString"; + + public String getFormat() { + return formatString; + } + + protected boolean flipLabel = false; + public static final String PROP_FLIPLABEL = "flipLabel"; + + public boolean isFlipLabel() { + return flipLabel; + } + + public void setFlipLabel(boolean flipLabel) { + boolean oldFlipLabel = this.flipLabel; + this.flipLabel = flipLabel; + repaint(); + firePropertyChange(PROP_FLIPLABEL, oldFlipLabel, flipLabel); + } + + + /** + * set a hint at the format string. Examples include: + * 0.000 + * %H:%M!c%Y-%j + * @param formatString + */ + public void setFormat(String formatString) { + try { + String oldFormatString = this.formatString; + this.formatString = formatString; + if (formatString.equals("")) { + setUserDatumFormatter(null); + } else { + setUserDatumFormatter(getUnits().getDatumFormatterFactory().newFormatter(formatString)); + } + updateTickV(); + repaint(); + firePropertyChange(PROP_FORMATSTRING, oldFormatString, formatString); + } catch (ParseException e) { + setUserDatumFormatter(null); + } + } + + private void resetTransform() { + DasDevicePosition pos; + if (isHorizontal()) { + pos = getColumn(); + } else { + pos = getRow(); + } + double dmin = pos.getDMinimum(); + double dmax = pos.getDMaximum(); + if (isFlipped()) { + double t = dmin; + dmin = dmax; + dmax = t; + } + double[] at = GraphUtil.getSlopeIntercept(dataRange.getMinimum(), dmin, dataRange.getMaximum(), dmax); + at_m = at[0]; + at_b = at[1]; + } + + public Lock mutatorLock() { + return dataRange.mutatorLock(); + } + + /** + * true if a lock is out and an object is rapidly mutating the object. + * clients listening for property changes can safely ignore property + * changes while valueIsAdjusting is true, as they should receive a + * final propertyChangeEvent after the lock is released. (note it's not + * clear who is responsible for this. + * See http://www.das2.org/wiki/index.php/Das2.valueIsAdjusting) + */ + public boolean valueIsAdjusting() { + return dataRange.valueIsAdjusting(); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvas.java b/dasCore/src/main/java/org/das2/graph/DasCanvas.java new file mode 100755 index 000000000..5a7d8f6ec --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasCanvas.java @@ -0,0 +1,2393 @@ +/* File: DasCanvas.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasNameException; +import org.das2.DasProperties; +import org.das2.DasPropertyException; +import org.das2.NameContext; +import org.das2.dasml.FormBase; +import org.das2.dasml.FormComponent; +import org.das2.dasml.ParsedExpressionException; +import org.das2.event.DragRenderer; +import org.das2.graph.dnd.TransferableCanvasComponent; +import org.das2.system.DasLogger; +import org.das2.system.RequestProcessor; +import org.das2.util.AboutUtil; +import org.das2.util.DasExceptionHandler; +import org.das2.util.DasPNGConstants; +import org.das2.util.DasPNGEncoder; +import org.das2.util.awt.EventQueueBlocker_1; +import org.das2.util.awt.GraphicsOutput; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.LayoutManager; +import java.awt.Paint; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLayeredPane; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.LookAndFeel; +import javax.swing.Scrollable; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.MouseInputAdapter; +import javax.swing.event.MouseInputListener; +import javax.swing.filechooser.FileFilter; +import org.das2.components.propertyeditor.Editable; +import org.das2.components.propertyeditor.PropertyEditor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** Canvas for das2 graphics. The DasCanvas contains any number of DasCanvasComponents + * such as axes, plots, colorbars, etc. + * + * A basic hierarchy of ownership is: + *
    + * DasCanvas
    + *  |
    + *  +- {@link DasDevicePosition} (2-N objects)
    + *  |   |
    + *  |   +- {@link DasDevicePosition} (sub positions, 0-N objects)
    + *  |
    + *  +- {@link DasCanvasComponent} (0-N objects)
    + *
    + * For example: For a 2-D line plot the basic components are: + *
    + * DasCavas
    + *  |
    + *  +- {@link DasRow} (a DasDevicePosition)
    + *  |
    + *  +- {@link DasColumn} (a DasDevicePosition)
    + *  |
    + *  +- {@link DasAxis} (a DasCanvasComponent)
    + *  |
    + *  +- {@link DasAxis} (a DasCanvasComponent)
    + *  |
    + *  +- {@link DasPlot} (a DasCanvasComponent)
    + *     |
    + *     +- {@link SymbolLineRenderer} (Helper for DasPlot, implements a data painting style.)
    + *
    + * Note that {@link DasPlot}s don't know how to paint data, that job is delegated to one or + * more {@link Renderer} objects. {@link SymbolLineRenderer}s know how to draw simple line + * plots. + * + * @see DasColumn + * @see DasRow + * @see DasAxis + * @see DasPlot + * @see Renderer + * @author eew + */ +public class DasCanvas extends JLayeredPane implements Printable, Editable, FormComponent, Scrollable { + + /** Default drawing layer of the JLayeredPane */ + public static final Integer DEFAULT_LAYER = JLayeredPane.DEFAULT_LAYER; + /** Z-Layer for drawing the plot. */ + public static final Integer PLOT_LAYER = new Integer(300); + /** Z-Layer for vertical axis. Presently lower than the horizontal axis, presumably to remove ambiguity */ + public static final Integer VERTICAL_AXIS_LAYER = new Integer(400); + /** Z-Layer */ + public static final Integer HORIZONTAL_AXIS_LAYER = new Integer(500); + /** Z-Layer */ + public static final Integer AXIS_LAYER = VERTICAL_AXIS_LAYER; + /** Z-Layer */ + public static final Integer ANNOTATION_LAYER = new Integer(1000); + /** Z-Layer */ + public static final Integer GLASS_PANE_LAYER = new Integer(30000); + private static final Paint PAINT_ROW = new Color(0xff, 0xb2, 0xb2, 0x92); + private static final Paint PAINT_COLUMN = new Color(0xb2, 0xb2, 0xff, 0x92); + private static final Paint PAINT_SELECTION = Color.GRAY; + private static final Stroke STROKE_DASHED; + + + static { + float thick = 3.0f; + int cap = BasicStroke.CAP_SQUARE; + int join = BasicStroke.JOIN_MITER; + float[] dash = new float[]{thick * 4.0f, thick * 4.0f}; + STROKE_DASHED = new BasicStroke(thick, cap, join, thick, dash, 0.0f); + } + + /* Canvas actions */ + protected static abstract class CanvasAction extends AbstractAction { + + protected static DasCanvas currentCanvas; + + CanvasAction(String label) { + super(label); + } + } + + private static FileFilter getFileNameExtensionFilter(final String description, final String ext) { + return new FileFilter() { + + @Override + public boolean accept(File f) { + return f.isDirectory() || f.toString().endsWith(ext); + } + + @Override + public String getDescription() { + return description; + } + }; + } + private static File currentFile; + public static final Action SAVE_AS_PNG_ACTION = new CanvasAction("Save as PNG") { + + @Override + public void actionPerformed(ActionEvent e) { + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Write to PNG"); + fileChooser.setFileFilter(getFileNameExtensionFilter("png files", "png")); + Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); + String savedir = prefs.get("savedir", null); + if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); + if (currentFile != null) fileChooser.setSelectedFile(currentFile); + int choice = fileChooser.showSaveDialog(currentCanvas); + if (choice == JFileChooser.APPROVE_OPTION) { + final DasCanvas canvas = currentCanvas; + String fname = fileChooser.getSelectedFile().toString(); + if (!fname.toLowerCase().endsWith(".png")) fname += ".png"; + final String ffname = fname; + prefs.put("savedir", new File(ffname).getParent()); + currentFile = new File(ffname.substring(0, ffname.length() - 4)); + Runnable run = new Runnable() { + + public void run() { + try { + canvas.writeToPng(ffname); + } catch (java.io.IOException ioe) { + org.das2.util.DasExceptionHandler.handle(ioe); + } + } + }; + new Thread(run, "writePng").start(); + } + } + }; + public static final Action SAVE_AS_SVG_ACTION = new CanvasAction("Save as SVG") { + + @Override + public void actionPerformed(ActionEvent e) { + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setApproveButtonText("Select File"); + fileChooser.setDialogTitle("Write to SVG"); + fileChooser.setFileFilter(getFileNameExtensionFilter("svg files", "svg")); + Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); + String savedir = prefs.get("savedir", null); + if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); + if (currentFile != null) fileChooser.setSelectedFile(currentFile); + int choice = fileChooser.showSaveDialog(currentCanvas); + if (choice == JFileChooser.APPROVE_OPTION) { + final DasCanvas canvas = currentCanvas; + String fname = fileChooser.getSelectedFile().toString(); + if (!fname.toLowerCase().endsWith(".svg")) fname += ".svg"; + final String ffname = fname; + prefs.put("savedir", new File(ffname).getParent()); + currentFile = new File(ffname.substring(0, ffname.length() - 4)); + Runnable run = new Runnable() { + + public void run() { + try { + canvas.writeToSVG(ffname); + } catch (java.io.IOException ioe) { + org.das2.util.DasExceptionHandler.handle(ioe); + } + } + }; + new Thread(run, "writeSvg").start(); + } + } + }; + public static final Action SAVE_AS_PDF_ACTION = new CanvasAction("Save as PDF") { + + @Override + public void actionPerformed(ActionEvent e) { + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setApproveButtonText("Select File"); + fileChooser.setDialogTitle("Write to PDF"); + fileChooser.setFileFilter(getFileNameExtensionFilter("pdf files", "pdf")); + Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); + String savedir = prefs.get("savedir", null); + if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); + if (currentFile != null) fileChooser.setSelectedFile(currentFile); + int choice = fileChooser.showDialog(currentCanvas, "Select File"); + if (choice == JFileChooser.APPROVE_OPTION) { + final DasCanvas canvas = currentCanvas; + String fname = fileChooser.getSelectedFile().toString(); + if (!fname.toLowerCase().endsWith(".pdf")) fname += ".pdf"; + final String ffname = fname; + prefs.put("savedir", new File(ffname).getParent()); + currentFile = new File(ffname.substring(0, ffname.length() - 4)); + Runnable run = new Runnable() { + + public void run() { + try { + canvas.writeToPDF(ffname); + } catch (java.io.IOException ioe) { + org.das2.util.DasExceptionHandler.handle(ioe); + } + } + }; + new Thread(run, "writePdf").start(); + } + } + }; + public static final Action EDIT_DAS_PROPERTIES_ACTION = new AbstractAction("DAS Properties") { + + @Override + public void actionPerformed(ActionEvent e) { + org.das2.DasProperties.showEditor(); + } + }; + public static final Action PRINT_ACTION = new CanvasAction("Print...") { + + @Override + public void actionPerformed(ActionEvent e) { + Printable p = currentCanvas.getPrintable(); + PrinterJob pj = PrinterJob.getPrinterJob(); + pj.setPrintable(p); + if (pj.printDialog()) { + try { + pj.print(); + } catch (PrinterException pe) { + Object[] message = {"Error printing", pe.getMessage()}; + JOptionPane.showMessageDialog(null, message, "ERROR", JOptionPane.ERROR_MESSAGE); + } + } + } + }; + public static final Action REFRESH_ACTION = new CanvasAction("Refresh") { + + public void actionPerformed(ActionEvent e) { + DasCanvasComponent[] comps = currentCanvas.getCanvasComponents(); + for (int i = 0; i < comps.length; i++) { + comps[i].update(); + } + } + }; + + public static final Action ABOUT_ACTION = new CanvasAction("About") { + + @Override + public void actionPerformed(ActionEvent e) { + String aboutContent = AboutUtil.getAboutHtml(); + + JOptionPane.showConfirmDialog(currentCanvas, aboutContent, "about das2", JOptionPane.PLAIN_MESSAGE); + } + }; + public final Action PROPERTIES_ACTION = new CanvasAction("properties") { + + public void actionPerformed(ActionEvent e) { + PropertyEditor editor = new PropertyEditor(DasCanvas.this); + editor.showDialog(DasCanvas.this); + } + }; + + public static Action[] getActions() { + return new Action[]{ + ABOUT_ACTION, + REFRESH_ACTION, + EDIT_DAS_PROPERTIES_ACTION, + PRINT_ACTION, + SAVE_AS_PNG_ACTION, + SAVE_AS_SVG_ACTION, + SAVE_AS_PDF_ACTION, + }; + } + private DasApplication application; + private static final Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + private final GlassPane glassPane; + private String dasName; + private JPopupMenu popup; + private boolean editable; + private int printing = 0; + List devicePositionList = new ArrayList(); + org.das2.util.DnDSupport dndSupport; + DasCanvasStateSupport stateSupport; + /** The set of Threads that are currently printing this canvas. + * This set is used to determine of certain operations that are only + * appropriate in printing situations should occur. + */ + private Set printingThreads; + + /** Creates a new instance of DasCanvas + * TODO + */ + public DasCanvas() { + LookAndFeel.installColorsAndFont(this, "Panel.background", "Panel.foreground", "Panel.font"); + application = DasApplication.getDefaultApplication(); + String name = application.suggestNameFor(this); + setName(name); + setOpaque(true); + setLayout(new RowColumnLayout()); + addComponentListener(createResizeListener()); + setBackground(Color.white); + setPreferredSize(new Dimension(400, 300)); + this.setDoubleBuffered(true); + glassPane = new GlassPane(); + add(glassPane, GLASS_PANE_LAYER); + if (!application.isHeadless()) { + popup = createPopupMenu(); + this.addMouseListener(createMouseInputAdapter()); + + try { + dndSupport = new CanvasDnDSupport(); + } catch (SecurityException ex) { + dndSupport = new CanvasDnDSupport(); + } + } + CanvasAction.currentCanvas = this; + stateSupport = new DasCanvasStateSupport(this); + } + + private MouseInputAdapter createMouseInputAdapter() { + return new MouseInputAdapter() { + + public void mousePressed(MouseEvent e) { + Point primaryPopupLocation = e.getPoint(); + CanvasAction.currentCanvas = DasCanvas.this; + if (SwingUtilities.isRightMouseButton(e)) + popup.show(DasCanvas.this, e.getX(), e.getY()); + } + }; + } + + private JPopupMenu createPopupMenu() { + JPopupMenu popup = new JPopupMenu(); + + JMenuItem props = new JMenuItem(PROPERTIES_ACTION); + popup.add(props); + + popup.addSeparator(); + + Action[] actions = getActions(); + for (int iaction = 0; iaction < actions.length; iaction++) { + JMenuItem item = new JMenuItem(); + item.setAction(actions[iaction]); + popup.add(item); + } + + popup.addSeparator(); + + JMenuItem close = new JMenuItem("close"); + close.setToolTipText("close this popup"); + popup.add(close); + + return popup; + } + + /** returns the GlassPane above all other components. This is used for drawing dragRenderers, etc. + * @return + */ + public Component getGlassPane() { + return glassPane; + } + + /** TODO + * @return + */ + public List getDevicePositionList() { + return Collections.unmodifiableList(devicePositionList); + } + private int displayLockCount = 0; + private Object displayLockObject = new String("DISPLAY_LOCK_OBJECT"); + + /** + * Lock the display for this canvas. All Mouse and Key events are captured + * and swallowed by the glass pane. + * + * @param o the Object requesting the lock. + * @see #freeDisplay(Object); + */ + synchronized void lockDisplay(Object o) { + synchronized (displayLockObject) { + displayLockCount++; + //if (displayLockCount == 1) { + // glassPane.setBlocking(true); + //} + } + } + + /** + * Frees the lock the specified object has requested on the display. + * The display will not be freed until all locks have been freed. + * + * @param o the object releasing its lock on the display + * @see #lockDisplay(Object) + */ + synchronized void freeDisplay(Object o) { + synchronized (displayLockObject) { + displayLockCount--; + if (displayLockCount == 0) { + // glassPane.setBlocking(false); + displayLockObject.notifyAll(); + } + } + } + + /** Creates a new instance of DasCanvas with the specified width and height + * TODO + * @param width The width of the DasCanvas + * @param height The height of the DasCanvas + */ + public DasCanvas(int width, int height) { + this(); + setPreferredSize(new Dimension(width, height)); + } + + public DasApplication getApplication() { + return application; + } + + public void setApplication(DasApplication application) { + this.application = application; + } + + /** simply returns getPreferredSize(). + * @return getPreferredSize() + */ + @Override + public Dimension getMaximumSize() { + return getPreferredSize(); + } + + /** paints the canvas itself. If printing, stamps the date on it as well. + * @param gl the Graphics object + */ + @Override + protected void paintComponent(Graphics g1) { + logger.fine("entering DasCanvas.paintComponent"); + + Graphics2D g = (Graphics2D) g1; + + g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + textAntiAlias ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); + + + if (!(isPrintingThread() && getBackground().equals(Color.WHITE))) { + g.setColor(getBackground()); + //g.fillRect(0, 0, getWidth(), getHeight()); + Graphics2D g2 = g; + g2.fill(g2.getClipBounds()); + } + g.setColor(getForeground()); + if (isPrintingThread()) { + int width, height; + Date now; + SimpleDateFormat dateFormat; + Font font, oldFont; + FontMetrics metrics; + String s; + + if (!printingTag.equals("")) { + now = new Date(); + dateFormat = new SimpleDateFormat(printingTag); + s = dateFormat.format(now); + + oldFont = g.getFont(); + font = oldFont.deriveFont((float) oldFont.getSize() / 2); + metrics = g.getFontMetrics(font); + width = metrics.stringWidth(s); + height = metrics.getHeight(); + + g.setFont(font); + g.drawString(s, getWidth() - width, getHeight() -height ); + g.setFont(oldFont); + } + } + } + + /** Prints the canvas, scaling and possibly rotating it to improve fit. + * @param printGraphics the Graphics object. + * @param format the PageFormat object. + * @param pageIndex should be 0, since the image will be on one page. + * @return Printable.PAGE_EXISTS or Printable.NO_SUCH_PAGE + */ + @Override + public int print(Graphics printGraphics, PageFormat format, int pageIndex) { + + if (pageIndex != 0) return NO_SUCH_PAGE; + + Graphics2D g2 = (Graphics2D) printGraphics; + double canvasWidth = (double) getWidth(); + double canvasHeight = (double) getHeight(); + double printableWidth = format.getImageableWidth(); + double printableHeight = format.getImageableHeight(); + + g2.translate(format.getImageableX(), format.getImageableY()); + + double canvasMax = Math.max(canvasWidth, canvasHeight); + double canvasMin = Math.min(canvasWidth, canvasHeight); + double printableMax = Math.max(printableWidth, printableHeight); + double printableMin = Math.min(printableWidth, printableHeight); + + double maxScaleFactor = printableMax / canvasMax; + double minScaleFactor = printableMin / canvasMin; + double scaleFactor = Math.min(maxScaleFactor, minScaleFactor); + g2.scale(scaleFactor, scaleFactor); + + if ((canvasWidth == canvasMax) ^ (printableWidth == printableMax)) { + g2.rotate(Math.PI / 2.0); + g2.translate(0.0, -canvasHeight); + } + + print(g2); + + return PAGE_EXISTS; + + } + + /** + * Layout manager for managing the Row, Column layout implemented by swing. + * This will probably change in the future when we move away from using + * swing to handle the DasCanvasComponents. + */ + protected static class RowColumnLayout implements LayoutManager { + + @Override + public void layoutContainer(Container target) { + synchronized (target.getTreeLock()) { + int count = target.getComponentCount(); + for (int i = 0; i < count; i++) { + Component c = target.getComponent(i); + if (c instanceof DasCanvasComponent) { + ((DasCanvasComponent) c).update(); + } else if (c == ((DasCanvas) target).glassPane) { + Dimension size = target.getSize(); + c.setBounds(0, 0, size.width, size.height); + } + } + } + } + + @Override + public Dimension minimumLayoutSize(Container target) { + return new Dimension(0, 0); + } + + @Override + public Dimension preferredLayoutSize(Container target) { + return new Dimension(400, 300); + } + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + } + } + + /** + * @return true if the current thread is registered as the one printing this component. + */ + protected final boolean isPrintingThread() { + synchronized (this) { + return printingThreads == null ? false : printingThreads.contains(Thread.currentThread()); + } + } + + @Override + public void print(Graphics g) { + synchronized (this) { + if (printingThreads == null) { + printingThreads = new HashSet(); + } + printingThreads.add(Thread.currentThread()); + } + try { + setOpaque(false); + logger.fine("*** print graphics: " + g); + logger.fine("*** print graphics clip: " + g.getClip()); + + for (int i = 0; i < getComponentCount(); i++) { + Component c = getComponent(i); + if (c instanceof DasPlot) { + DasPlot p = (DasPlot) c; + logger.fine(" DasPlot.isDirty()=" + p.isDirty()); + logger.fine(" DasPlot.getBounds()=" + p.getBounds()); + /* System.err.println(" DasPlot.isDirty()=" + p.isDirty()); + System.err.println(" DasPlot.getBounds()=" + p.getBounds()); */ + } + } + super.print(g); + } finally { + setOpaque(true); + synchronized (this) { + printingThreads.remove(Thread.currentThread()); + } + } + } + + /** Returns an instance of java.awt.print.Printable that can + * be used to render this canvas to a printer. The current implementation + * returns a reference to this canvas. This method is provided so that in + * the future, the canvas can delegate it's printing to another object. + * @return a Printable instance for rendering this component. + */ + public Printable getPrintable() { + return this; + } + + /** + * uses getImage to get an image of the canvas and encodes it + * as a png. + * + * TODO: this should take an output stream, and then a helper class + * manages the file. (e.g. web servers) + * + * @param filename the specified filename + * @throws IOException if there is an error opening the file for writing + */ + public void writeToPng(String filename) throws IOException { + writeToPng(filename, Collections.EMPTY_MAP); + } + + public void writeToPng(String filename, Map txt) throws IOException { + + final FileOutputStream out = new FileOutputStream(filename); + + logger.fine("Enter writeToPng"); + + Image image = getImage(getWidth(), getHeight()); + + DasPNGEncoder encoder = new DasPNGEncoder(); + encoder.addText(DasPNGConstants.KEYWORD_CREATION_TIME, new Date().toString()); + for (String key : txt.keySet()) { + encoder.addText(key, txt.get(key)); + } + try { + logger.fine("Encoding image into png"); + encoder.write((BufferedImage) image, out); + logger.fine("write png file " + filename); + } catch (IOException ioe) { + } finally { + try { + out.close(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + } + + public void writeToPDF(String filename) throws IOException { + try { + writeToGraphicsOutput(filename, "org.das2.util.awt.PdfGraphicsOutput"); + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("write pdf file " + filename); + } catch (NoClassDefFoundError cnfe) { + DasExceptionHandler.handle(new RuntimeException("PDF output is not available", cnfe)); + } catch (ClassNotFoundException cnfe) { + DasExceptionHandler.handle(new RuntimeException("PDF output is not available", cnfe)); + } catch (InstantiationException ie) { + DasExceptionHandler.handleUncaught(ie); + } catch (IllegalAccessException iae) { + DasExceptionHandler.handleUncaught(iae); + } + } + + /** + * write to various graphics devices such as png, pdf and svg. This handles the synchronization and + * parameter settings. + * @param out OutputStream to receive the data + * @param go GraphicsOutput object. + */ + public void writeToGraphicsOutput(OutputStream out, GraphicsOutput go) throws IOException, IllegalAccessException { + go.setOutputStream(out); + go.setSize(getWidth(), getHeight()); + go.start(); + print(go.getGraphics()); + go.finish(); + } + + public void writeToGraphicsOutput(String filename, String graphicsOutput) + throws IOException, ClassNotFoundException, + InstantiationException, IllegalAccessException { + FileOutputStream out = new FileOutputStream(filename); + Class goClass = Class.forName(graphicsOutput); + GraphicsOutput go = (GraphicsOutput) goClass.newInstance(); + writeToGraphicsOutput(out, go); + + } + + /** + * @param filename the specified filename + * @throws IOException if there is an error opening the file for writing + */ + public void writeToSVG(String filename) throws IOException { + try { + writeToGraphicsOutput(filename, "org.das2.util.awt.SvgGraphicsOutput"); + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("write svg file " + filename); + } catch (ClassNotFoundException cnfe) { + DasExceptionHandler.handle(new RuntimeException("SVG output is not available", cnfe)); + } catch (InstantiationException ie) { + DasExceptionHandler.handleUncaught(ie); + } catch (IllegalAccessException iae) { + DasExceptionHandler.handleUncaught(iae); + } + } + + /** + * returns true if work needs to be done to make the canvas clean. This checks each component's + * isDirty. + * + * @return + */ + public boolean isDirty() { + DasCanvasComponent[] cc= this.getCanvasComponents(); + boolean result= false; + for ( int i=0; iDasCanvasComponent to this canvas. + * @param c the component to be added to this canvas + * Note that the canvas will need to be revalidated after the component + * is added. + * @param row DasRow specifying the layout of the component. + * @param column DasColumn specifying the layout of the component. + */ + public void add(DasCanvasComponent c, DasRow row, DasColumn column) { + if (c.getRow() == DasRow.NULL || c.getRow().getParent() != this) { + c.setRow(row); + } + if (c.getColumn() == DasColumn.NULL || c.getColumn().getParent() != this) { + c.setColumn(column); + } + add(c); + PropertyChangeListener positionListener = new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + repaint(); + } + }; + + if(row != null) + row.addPropertyChangeListener(positionListener); + + if(column != null) + column.addPropertyChangeListener(positionListener); + } + + /** TODO + * @param comp + * @param constraints + * @param index + */ + @Override + protected void addImpl(Component comp, Object constraints, int index) { + if (comp == null) { + org.das2.util.DasDie.println("NULL COMPONENT"); + Thread.dumpStack(); + return; + } + if (index < 0) index = 0; + + Integer layer = (Integer) ((JComponent) comp).getClientProperty(LAYER_PROPERTY); + if (layer == null) { + if (comp instanceof DasPlot) { + ((DasPlot) comp).putClientProperty(LAYER_PROPERTY, PLOT_LAYER); + } else if (comp instanceof DasAxis) { + ((DasAxis) comp).putClientProperty(LAYER_PROPERTY, AXIS_LAYER); + } else if (comp instanceof Legend) { + ((Legend) comp).putClientProperty(LAYER_PROPERTY, AXIS_LAYER); + } else if (comp instanceof DasAnnotation) { + ((DasAnnotation) comp).putClientProperty(LAYER_PROPERTY, ANNOTATION_LAYER); + } else if (comp instanceof JComponent) { + ((JComponent) comp).putClientProperty(LAYER_PROPERTY, DEFAULT_LAYER); + } + } + super.addImpl(comp, constraints, index); + if (comp instanceof DasCanvasComponent) { + ((DasCanvasComponent) comp).installComponent(); + } + } + + /** + * Sets the preferred width of the canvas to the specified width. + * + * @param width the specified width. + */ + public void setPreferredWidth(int width) { + Dimension pref = getPreferredSize(); + pref.width = width; + setPreferredSize(pref); + if (getParent() != null) ((JComponent) getParent()).revalidate(); + } + + /** Sets the preferred height of the canvas to the specified height. + * + * @param height the specified height + */ + public void setPreferredHeight(int height) { + Dimension pref = getPreferredSize(); + pref.height = height; + setPreferredSize(pref); + if (getParent() != null) ((JComponent) getParent()).revalidate(); + } + + + protected boolean scaleFonts = true; + + /** + * The font used should be the base font scaled based on the canvas size. + * If this is false, then the canvas font is simply the base font. + */ + public static final String PROP_SCALEFONTS = "scaleFonts"; + + public boolean isScaleFonts() { + return scaleFonts; + } + + public void setScaleFonts(boolean scaleFonts) { + boolean oldScaleFonts = this.scaleFonts; + this.scaleFonts = scaleFonts; + setBaseFont(getBaseFont()); + firePropertyChange(PROP_SCALEFONTS, oldScaleFonts, scaleFonts); + } + + private Font baseFont = null; + + /** TODO + * @return + */ + public Font getBaseFont() { + if (baseFont == null) { + baseFont = getFont(); + } + return this.baseFont; + } + + /** + * The base font is the font from which all other fonts should be derived. When the + * canvas is resized, the base font size is scaled. + * @param font the font used to derive all other fonts. + */ + public void setBaseFont(Font font) { + Font oldFont = getFont(); + this.baseFont = font; + if ( scaleFonts ) { + setFont(getFontForSize(getWidth(), getHeight())); + } else { + setFont( font ); + } + firePropertyChange("font", oldFont, getFont()); //TODO: really? + repaint(); + } + private static final int R_1024_X_768 = 1024 * 768; + private static final int R_800_X_600 = 800 * 600; + private static final int R_640_X_480 = 640 * 480; + private static final int R_320_X_240 = 320 * 240; + + private Font getFontForSize(int width, int height) { + int area = width * height; + Font f; + + float baseFontSize = getBaseFont().getSize2D(); + + if (area >= (R_1024_X_768 - R_800_X_600) / 2 + R_800_X_600) { + f = getBaseFont().deriveFont(baseFontSize / 12f * 18f); //new Font("Serif", Font.PLAIN, 18); + } else if (area >= (R_800_X_600 - R_640_X_480) / 2 + R_640_X_480) { + f = getBaseFont().deriveFont(baseFontSize / 12f * 14f); //new Font("Serif", Font.PLAIN, 14); + } else if (area >= (R_640_X_480 - R_320_X_240) / 2 + R_320_X_240) { + f = getBaseFont().deriveFont(baseFontSize / 12f * 12f); //new Font("Serif", Font.PLAIN, 12); + } else if (area >= (R_320_X_240) / 2) { + f = getBaseFont().deriveFont(baseFontSize / 12f * 8f); //new Font("Serif", Font.PLAIN, 8); + } else { + f = getBaseFont().deriveFont(baseFontSize / 12f * 6f); //new Font("Serif", Font.PLAIN, 6); + } + return f; + } + + private ComponentListener createResizeListener() { + return new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + Font aFont; + if ( scaleFonts ) { + aFont= getFontForSize(getWidth(), getHeight()); + } else { + aFont= getBaseFont(); + } + if (!aFont.equals(getFont())) { + setFont(aFont); + } + } + }; + } + + /** TODO + * @return + * @param document + */ + @Override + public Element getDOMElement(Document document) { + Element element = document.createElement("canvas"); + Dimension size = getPreferredSize(); + element.setAttribute("name", getDasName()); + element.setAttribute("width", Integer.toString(size.width)); + element.setAttribute("height", Integer.toString(size.height)); + + for (int index = 0; index < devicePositionList.size(); index++) { + Object obj = devicePositionList.get(index); + if (obj instanceof DasRow) { + DasRow row = (DasRow) obj; + element.appendChild(row.getDOMElement(document)); + } else if (obj instanceof DasColumn) { + DasColumn column = (DasColumn) obj; + element.appendChild(column.getDOMElement(document)); + } + } + + Component[] components = getComponents(); + Map elementMap = new LinkedHashMap(); + + //THREE PASS ALGORITHM. + //1. Process all DasAxis components. + // Add all , , elements to elementList. + //2. Process all DasColorBar components. + // Remove all elements that correspond to axis property of colorbars. + // Add all elements to elementList. + //3. Process all DasSpectrogramPlot and DasPlot components. + // Remove all , , , and elements + // that correspond to xAxis, yAxis, and colorbar properties of + // plots spectrograms and spectrogram renderers. + // Add all elements to elementList. + + for (int index = 0; index < components.length; index++) { + if (components[index] instanceof DasAxis) { + DasAxis axis = (DasAxis) components[index]; + elementMap.put(axis.getDasName(), axis.getDOMElement(document)); + } + } + for (int index = 0; index < components.length; index++) { + if (components[index] instanceof DasColorBar) { + DasColorBar colorbar = (DasColorBar) components[index]; + elementMap.put(colorbar.getDasName(), colorbar.getDOMElement(document)); + } + } + for (int index = 0; index < components.length; index++) { + if (components[index] instanceof DasPlot) { + DasPlot plot = (DasPlot) components[index]; + elementMap.remove(plot.getXAxis().getDasName()); + elementMap.remove(plot.getYAxis().getDasName()); + Renderer[] renderers = plot.getRenderers(); + for (int i = 0; i < renderers.length; i++) { + if (renderers[i] instanceof SpectrogramRenderer) { + SpectrogramRenderer spectrogram = (SpectrogramRenderer) renderers[i]; + elementMap.remove(spectrogram.getColorBar().getDasName()); + } + } + elementMap.put(plot.getDasName(), plot.getDOMElement(document)); + } + } + + for (Iterator iterator = elementMap.values().iterator(); iterator.hasNext();) { + Element e = (Element) iterator.next(); + if (e != null) { + element.appendChild(e); + } + } + return element; + } + + /** Process a <canvas> element. + * + * @param form + * @param element The DOM tree node that represents the element + * @throws DasPropertyException + * @throws DasNameException + * @throws ParsedExpressionException + * @return + */ + public static DasCanvas processCanvasElement(Element element, FormBase form) + throws DasPropertyException, DasNameException, DasException, ParsedExpressionException, java.text.ParseException { + try { + Logger log = DasLogger.getLogger(DasLogger.DASML_LOG); + + String name = element.getAttribute("name"); + int width = Integer.parseInt(element.getAttribute("width")); + int height = Integer.parseInt(element.getAttribute("height")); + + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + + DasCanvas canvas = new DasCanvas(width, height); + + NodeList children = element.getChildNodes(); + int childCount = children.getLength(); + for (int index = 0; index < childCount; index++) { + Node node = children.item(index); + log.fine("node=" + node.getNodeName()); + if (node instanceof Element) { + String tagName = node.getNodeName(); + if (tagName.equals("row")) { + DasRow row = DasRow.processRowElement((Element) node, canvas, form); + } else if (tagName.equals("column")) { + DasColumn column = DasColumn.processColumnElement((Element) node, canvas, form); + } else if (tagName.equals("axis")) { + DasAxis axis = DasAxis.processAxisElement((Element) node, form); + canvas.add(axis); + } else if (tagName.equals("timeaxis")) { + DasAxis timeaxis = DasAxis.processTimeaxisElement((Element) node, form); + canvas.add(timeaxis); + } else if (tagName.equals("attachedaxis")) { + DasAxis attachedaxis = DasAxis.processAttachedaxisElement((Element) node, form); + canvas.add(attachedaxis); + } else if (tagName.equals("colorbar")) { + DasColorBar colorbar = DasColorBar.processColorbarElement((Element) node, form); + canvas.add(colorbar); + } else if (tagName.equals("plot")) { + DasPlot plot = DasPlot.processPlotElement((Element) node, form); + canvas.add(plot); + } else if (tagName.equals("spectrogram")) { + DasPlot plot = DasPlot.processPlotElement((Element) node, form); + canvas.add(plot); + } + + } + } + canvas.setDasName(name); + nc.put(name, canvas); + + return canvas; + } catch (org.das2.DasPropertyException dpe) { + if (!element.getAttribute("name").equals("")) { + dpe.setObjectName(element.getAttribute("name")); + } + throw dpe; + } + } + + /** + * @param name + * @param width + * @param height + * @return DasCanvas with a name. + */ + public static DasCanvas createFormCanvas(String name, int width, int height) { + DasCanvas canvas = new DasCanvas(width, height); + if (name == null) { + name = "canvas_" + Integer.toHexString(System.identityHashCode(canvas)); + } + try { + canvas.setDasName(name); + } catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + return canvas; + } + + /** Returns the DasCanvasComponent that contains the (x, y) location. + * If there is no component at that location, this method + * returns null + * @param x the x coordinate + * @param y the y coordinate + * @return the component at the specified point, or null + */ + public DasCanvasComponent getCanvasComponentAt(int x, int y) { + Component[] components = getComponents(); + for (int index = 1; index < components.length; index++) { + Component c = components[index]; + if (c instanceof DasCanvasComponent) { + DasCanvasComponent cc = (DasCanvasComponent) c; + if (cc.getActiveRegion().contains((double) x, (double) y)) { + return cc; + } + } + } + return null; + } + + /** + * Removes the component, specified by index, + * from this container. + * @param index the index of the component to be removed. + */ + @Override + public void remove(int index) { + Component comp = this.getComponent(index); + super.remove(index); + if (comp instanceof DasCanvasComponent) { + ((DasCanvasComponent) comp).uninstallComponent(); + } + } + + private class CanvasDnDSupport extends org.das2.util.DnDSupport { + + CanvasDnDSupport() { + super(DasCanvas.this, DnDConstants.ACTION_COPY_OR_MOVE, null); + } + private List acceptList = Arrays.asList(new DataFlavor[]{ + org.das2.graph.dnd.TransferableCanvasComponent.PLOT_FLAVOR, + org.das2.graph.dnd.TransferableCanvasComponent.AXIS_FLAVOR, + org.das2.graph.dnd.TransferableCanvasComponent.COLORBAR_FLAVOR + }); + + private Rectangle getAxisRectangle(Rectangle rc, Rectangle t, int x, int y) { + if (t == null) { + t = new Rectangle(); + } + int o = getAxisOrientation(rc, x, y); + switch (o) { + case DasAxis.TOP: + t.width = rc.width; + t.height = 3 * getFont().getSize(); + t.x = rc.x; + t.y = rc.y - t.height; + break; + case DasAxis.RIGHT: + t.width = 3 * getFont().getSize(); + t.height = rc.height; + t.x = rc.x + rc.width; + t.y = rc.y; + break; + case DasAxis.LEFT: + t.width = 3 * getFont().getSize(); + t.height = rc.height; + t.x = rc.x - t.width; + t.y = rc.y; + break; + case DasAxis.BOTTOM: + t.width = rc.width; + t.height = 3 * getFont().getSize(); + t.x = rc.x; + t.y = rc.y + rc.height; + break; + default: + throw new RuntimeException("invalid orientation: " + o); + } + return t; + } + + private int getAxisOrientation(Rectangle rc, int x, int y) { + int nx = (x - rc.x) * rc.height; + int ny = (y - rc.y) * rc.width; + int a = rc.width * rc.height; + boolean b = nx + ny < a; + return (nx > ny ? (b ? DasAxis.TOP : DasAxis.RIGHT) : (b ? DasAxis.LEFT : DasAxis.BOTTOM)); + } + + @Override + protected int canAccept(DataFlavor[] flavors, int x, int y, int action) { + glassPane.setAccepting(true); + List flavorList = java.util.Arrays.asList(flavors); + Cell cell = getCellAt(x, y); + Rectangle cellBounds = (cell == null ? null : cell.getCellBounds()); + Rectangle target = glassPane.target; + if (flavorList.contains(TransferableCanvasComponent.COLORBAR_FLAVOR)) { + return action; + } else if (flavorList.contains(TransferableCanvasComponent.AXIS_FLAVOR)) { + if (target != cellBounds && (target == null || !target.equals(cellBounds))) { + if (target != null) { + glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); + } + if (cellBounds != null) { + target = glassPane.target = getAxisRectangle(cellBounds, target, x, y); + glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); + } else { + glassPane.target = null; + } + } + return action; + } else if (flavorList.contains(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR)) { + if (target != cellBounds && (target == null || !target.equals(cellBounds))) { + if (target != null) { + glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); + } + target = glassPane.target = cellBounds; + if (cellBounds != null) { + glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); + } + } + return action; + } + return -1; + } + + @Override + protected void done() { + glassPane.setAccepting(false); + if (glassPane.target != null) { + Rectangle target = glassPane.target; + glassPane.target = null; + glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); + } + } + + @Override + protected boolean importData(Transferable t, int x, int y, int action) { + boolean success = false; + try { + if (t.isDataFlavorSupported(TransferableCanvasComponent.COLORBAR_FLAVOR)) { + Cell c = getCellAt(x, y); + if (c != null) { + DasCanvasComponent comp = (DasCanvasComponent) t.getTransferData(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR); + comp.setRow(c.getRow()); + comp.setColumn(c.getColumn()); + add(comp); + revalidate(); + success = true; + } + } else if (t.isDataFlavorSupported(TransferableCanvasComponent.AXIS_FLAVOR)) { + Cell c = getCellAt(x, y); + if (c != null) { + DasAxis axis = (DasAxis) t.getTransferData(TransferableCanvasComponent.AXIS_FLAVOR); + axis.setRow(c.getRow()); + axis.setColumn(c.getColumn()); + Rectangle cellBounds = c.getCellBounds(); + int orientation = getAxisOrientation(cellBounds, x, y); + axis.setOrientation(orientation); + add(axis); + revalidate(); + success = true; + } + } else if (t.isDataFlavorSupported(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR)) { + Cell c = getCellAt(x, y); + if (c != null) { + DasCanvasComponent comp = (DasCanvasComponent) t.getTransferData(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR); + comp.setRow(c.getRow()); + comp.setColumn(c.getColumn()); + add(comp); + revalidate(); + success = true; + } + } + } catch (UnsupportedFlavorException ufe) { + } catch (IOException ioe) { + } + return success; + } + + @Override + protected Transferable getTransferable(int x, int y, int action) { + DasCanvasComponent component = DasCanvas.this.getCanvasComponentAt(x, y); + if (component instanceof DasColorBar) { + return new TransferableCanvasComponent((DasColorBar) component); + } else if (component instanceof DasAxis) { + return new TransferableCanvasComponent((DasAxis) component); + } else if (component instanceof DasPlot) { + return new TransferableCanvasComponent((DasPlot) component); + } else { + return null; + } + } + + @Override + protected void exportDone(Transferable t, int action) { + } + } + + /** + * JPanel that lives above all other components, and is capable of blocking keyboard and mouse input from + * all components underneath. + */ + public static class GlassPane extends JPanel implements MouseInputListener, KeyListener { + + boolean blocking = false; + boolean accepting = false; + Rectangle target; + DragRenderer dragRenderer = null; + Point p1 = null, p2 = null; + + public GlassPane() { + setOpaque(false); + setLayout(null); + } + + DasCanvas getCanvas() { + return (DasCanvas) getParent(); + } + + void setBlocking(boolean b) { + if (b != blocking) { + blocking = b; + if (b) { + addMouseListener(this); + addMouseMotionListener(this); + } else { + removeMouseListener(this); + removeMouseMotionListener(this); + } + repaint(); + } + } + + void setAccepting(boolean b) { + if (b != accepting) { + accepting = b; + repaint(); + } + } + + public void setDragRenderer(DragRenderer r, Point p1, Point p2) { + this.dragRenderer = r; + this.p1 = p1; + this.p2 = p2; + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + if (blocking) { + paintLoading(g2); + } + if (((DasCanvas) getParent()).getEditingMode()) { + paintRowColumn(g2); + } + if (accepting && target != null) { + paintDnDTarget(g2); + } + if (dragRenderer != null) { + dragRenderer.renderDrag(g2, p1, p2); + } + } + + private void paintRowColumn(Graphics2D g2) { + g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); + DasCanvas canvas = getCanvas(); + for (Iterator i = canvas.devicePositionList.iterator(); i.hasNext();) { + DasDevicePosition d = (DasDevicePosition) i.next(); + double minimum = d.getMinimum(); + double maximum = d.getMaximum(); + int cWidth = canvas.getWidth(); + int cHeight = canvas.getHeight(); + int x, y, width, height; + Paint paint; + if (d instanceof DasRow) { + x = 0; + width = cWidth; + y = (int) Math.floor(minimum * cHeight + 0.5); + height = (int) Math.floor(maximum * cHeight + 0.5) - y; + paint = PAINT_ROW; + } else { + x = (int) Math.floor(minimum * cWidth + 0.5); + width = (int) Math.floor(maximum * cWidth + 0.5) - x; + y = 0; + height = cHeight; + paint = PAINT_COLUMN; + } + g2.setPaint(paint); + g2.fillRect(x, y, width, height); + } + } + + private void paintDnDTarget(Graphics2D g2) { + g2.setStroke(STROKE_DASHED); + g2.setPaint(PAINT_SELECTION); + g2.drawRect(target.x + 1, target.y + 1, target.width - 2, target.height - 2); + } + + private void paintLoading(Graphics2D g2) { + g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); + g2.setColor(new Color(0xdcFFFFFF, true)); + Rectangle rect = g2.getClipBounds(); + if (rect == null) { + g2.fillRect(0, 0, getWidth(), getHeight()); + } else { + g2.fillRect(rect.x, rect.y, rect.width, rect.height); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseDragged(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void mouseMoved(MouseEvent e) { + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + /** Invoked when a key has been pressed. + * See the class description for {@link KeyEvent} for a definition of + * a key pressed event. + */ + @Override + public void keyPressed(KeyEvent e) { + } + + /** Invoked when a key has been released. + * See the class description for {@link KeyEvent} for a definition of + * a key released event. + */ + @Override + public void keyReleased(KeyEvent e) { + } + + /** Invoked when a key has been typed. + * See the class description for {@link KeyEvent} for a definition of + * a key typed event. + */ + @Override + public void keyTyped(KeyEvent e) { + } + } + private HashSet horizontalLineSet = new HashSet(); + private HashSet verticalLineSet = new HashSet(); + private HashSet cellSet = new HashSet(); + + /** TODO + * @param x + * @param y + * @return + */ + public HotLine getLineAt(int x, int y) { + Iterator iterator = horizontalLineSet.iterator(); + while (iterator.hasNext()) { + HotLine line = (HotLine) iterator.next(); + if (y >= line.position - 1 && y <= line.position + 1) { + return line; + } + } + iterator = verticalLineSet.iterator(); + while (iterator.hasNext()) { + HotLine line = (HotLine) iterator.next(); + if (x >= line.position - 1 && x <= line.position + 1) { + return line; + } + } + return null; + } + + /** TODO + * @param x + * @param y + * @return + */ + public Cell getCellAt(int x, int y) { + Cell best = null; + Point bestCenter = null; + Point boxCenter = null; + Iterator iterator = cellSet.iterator(); + while (iterator.hasNext()) { + Cell box = (Cell) iterator.next(); + Rectangle rc = box.rc; + if (rc.contains(x, y)) { + if (best == null) { + best = box; + } else { + if (bestCenter == null) { + bestCenter = new Point(); + boxCenter = new Point(); + } + if (best.rc.contains(rc)) { + best = box; + } else { + bestCenter.setLocation(best.rc.x + best.rc.width / 2, best.rc.y + best.rc.height / 2); + boxCenter.setLocation(rc.x + rc.width / 2, rc.y + rc.height / 2); + int bestDistance = distanceSquared(x, y, bestCenter.x, bestCenter.y); + int boxDistance = distanceSquared(x, y, boxCenter.x, boxCenter.y); + if (boxDistance < bestDistance) { + best = box; + } else if (boxDistance == bestDistance) { + if (rc.width * rc.height < best.rc.width * best.rc.height) { + best = box; + } + } + } + } + } + } + return best; + } + + private static int distanceSquared(int x1, int y1, int x2, int y2) { + return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); + } + + /** + * This method should only be called in a constructor defined in the + * DasDevicePosition class. + * + * @param position the DasDevicePosition object calling this method. + */ + void addDevicePosition(DasDevicePosition position) { + devicePositionList.add(position); + if (position instanceof DasRow) { + addRow((DasRow) position); + } else if (position instanceof DasColumn) { + addColumn((DasColumn) position); + } + } + + private void addRow(DasRow row) { + HotLine min = new HotLine(row, HotLine.MIN); + HotLine max = new HotLine(row, HotLine.MAX); + horizontalLineSet.add(min); + horizontalLineSet.add(max); + Iterator iterator = devicePositionList.iterator(); + while (iterator.hasNext()) { + DasDevicePosition position = (DasDevicePosition) iterator.next(); + if (position instanceof DasColumn) { + DasColumn column = (DasColumn) position; + cellSet.add(new Cell(row, column)); + } + } + } + + private void addColumn(DasColumn column) { + HotLine min = new HotLine(column, HotLine.MIN); + HotLine max = new HotLine(column, HotLine.MAX); + verticalLineSet.add(min); + verticalLineSet.add(max); + Iterator iterator = devicePositionList.iterator(); + while (iterator.hasNext()) { + DasDevicePosition position = (DasDevicePosition) iterator.next(); + if (position instanceof DasRow) { + DasRow row = (DasRow) position; + cellSet.add(new Cell(row, column)); + } + } + } + + /** TODO + * @param position + */ + public void removepwDevicePosition(DasDevicePosition position) { + + devicePositionList.remove(position); + if (position instanceof DasRow) { + removeRow((DasRow) position); + } else if (position instanceof DasColumn) { + removeColumn((DasColumn) position); + } + } + + private void removeRow(DasRow row) { + for (Iterator i = horizontalLineSet.iterator(); i.hasNext();) { + HotLine line = (HotLine) i.next(); + if (line.devicePosition == row) { + i.remove(); + } + } + for (Iterator i = cellSet.iterator(); i.hasNext();) { + Cell cell = (Cell) i.next(); + if (cell.row == row) { + i.remove(); + } + } + } + + private void removeColumn(DasColumn column) { + for (Iterator i = verticalLineSet.iterator(); i.hasNext();) { + HotLine line = (HotLine) i.next(); + if (line.devicePosition == column) { + i.remove(); + } + } + for (Iterator i = cellSet.iterator(); i.hasNext();) { + Cell cell = (Cell) i.next(); + if (cell.column == column) { + i.remove(); + } + } + } + + /** TODO + * @return + */ + @Override + public FormBase getForm() { + Component parent = getParent(); + if (parent instanceof FormComponent) { + return ((FormComponent) parent).getForm(); + } + return null; + } + + /** TODO + * @return + */ + @Override + public boolean getEditingMode() { + return editable; + } + + /** TODO + * @param b + */ + @Override + public void setEditingMode(boolean b) { + if (editable == b) { + return; + } + editable = b; + revalidate(); + } + + /** TODO + * @return + */ + @Override + public org.das2.util.DnDSupport getDnDSupport() { + return dndSupport; + } + + /** TODO + * @param x + * @param y + * @param action + * @param evt + * @return + */ + @Override + public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + for (int i = 0; i < getComponentCount(); i++) { + if (getComponent(i).getBounds().contains(x, y)) { + dndSupport.startDrag(x, y, action, evt); + return true; + } + } + return false; + } + + /** TODO + * @return + */ + @Override + public String getDasName() { + return dasName; + } + + /** TODO + * @param name + * @throws DasNameException + */ + @Override + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + @Override + public void deregisterComponent() { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + for (Iterator i = devicePositionList.iterator(); i.hasNext();) { + DasDevicePosition dp = (DasDevicePosition) i.next(); + try { + if (nc.get(dp.getDasName()) == dp) { + nc.remove(dp.getDasName()); + } + } catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof DasCanvasComponent) { + DasCanvasComponent cc = (DasCanvasComponent) c; + try { + if (nc.get(cc.getDasName()) == cc) { + nc.remove(cc.getDasName()); + } + } catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + try { + if (nc.get(getDasName()) == this) { + nc.remove(getDasName()); + } + } catch (DasPropertyException dpe) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(dpe.toString()); + se.initCause(dpe); + throw se; + } catch (java.lang.reflect.InvocationTargetException ite) { + //This exception would only occur due to some invalid state. + //So, wrap it and toss it. + IllegalStateException se = new IllegalStateException(ite.toString()); + se.initCause(ite); + throw se; + } + } + } + + @Override + public DasApplication getDasApplication() { + Container p = getParent(); + if (p instanceof FormComponent) { + return ((FormComponent) p).getDasApplication(); + } else { + return null; + } + } + + @Override + public void registerComponent() throws org.das2.DasException { + try { + DasApplication app = getDasApplication(); + if (app != null) { + NameContext nc = app.getNameContext(); + for (Iterator i = devicePositionList.iterator(); i.hasNext();) { + DasDevicePosition dp = (DasDevicePosition) i.next(); + nc.put(dp.getDasName(), dp); + } + for (int index = 0; index < getComponentCount(); index++) { + Component c = getComponent(index); + if (c instanceof DasCanvasComponent) { + DasCanvasComponent cc = (DasCanvasComponent) c; + nc.put(cc.getDasName(), cc); + } + } + nc.put(getDasName(), this); + } + } catch (DasNameException dne) { + deregisterComponent(); + throw dne; + } + } + + /** Support reloading and refreshing all data on the canvas + */ + public void reload(){ + int nLen = getComponentCount(); + Component cmp; + for(int i = 0; i < nLen; i++){ + cmp = getComponent(i); + if( cmp instanceof DasCanvasComponent) + ((DasCanvasComponent)cmp).reload(); + } + } + + public DasCanvasComponent getCanvasComponents(int index) { + return (DasCanvasComponent) getComponent(index + 1); + } + + public DasCanvasComponent[] getCanvasComponents() { + int n = getComponentCount() - 1; + DasCanvasComponent[] result = new DasCanvasComponent[n]; + for (int i = 0; i < n; i++) { + result[i] = getCanvasComponents(i); + } + return result; + } + + @Override + public String toString() { + return "[DasCanvas " + this.getWidth() + "x" + this.getHeight() + " " + this.getDasName() + "]"; + } + + /** TODO */ + public static class HotLine implements PropertyChangeListener { + + /** TODO */ + public static final int MIN = -1; + /** TODO */ + public static final int NONE = 0; + /** TODO */ + public static final int MAX = 1; + int position; + DasDevicePosition devicePosition; + int minOrMax; + + HotLine(DasDevicePosition devicePosition, int minOrMax) { + this.devicePosition = devicePosition; + this.minOrMax = minOrMax; + refresh(); + devicePosition.addPropertyChangeListener((minOrMax == MIN ? "dMinimum" : "dMaximum"), this); + } + + void refresh() { + position = (minOrMax == MIN + ? (int) Math.floor(devicePosition.getDMinimum() + 0.5) + : (int) Math.floor(devicePosition.getDMaximum() + 0.5)); + } + + /** TODO + * @param e + */ + public void propertyChange(PropertyChangeEvent e) { + refresh(); + } + + /** TODO + * @param o + * @return + */ + public boolean equals(Object o) { + if (o instanceof HotLine) { + HotLine h = (HotLine) o; + return h.devicePosition == devicePosition && h.minOrMax == minOrMax; + } + return false; + } + + /** TODO + * @return + */ + public int hashCode() { + return minOrMax * devicePosition.hashCode(); + } + + /** TODO + * @return + */ + public String toString() { + return "{" + devicePosition.getDasName() + (minOrMax == MIN ? ", MIN, " : ", MAX, ") + position + "}"; + } + + /** TODO + * @return + */ + public DasDevicePosition getDevicePosition() { + return devicePosition; + } + + /** TODO + * @return + */ + public int getMinOrMax() { + return minOrMax; + } + } + + /** TODO */ + public static class Cell implements PropertyChangeListener { + + Rectangle rc; + DasRow row; + DasColumn column; + + Cell(DasRow row, DasColumn column) { + this.row = row; + this.column = column; + rc = new Rectangle(); + row.addPropertyChangeListener("dMinimum", this); + row.addPropertyChangeListener("dMaximum", this); + column.addPropertyChangeListener("dMinimum", this); + column.addPropertyChangeListener("dMaximum", this); + rc.x = (int) Math.floor(column.getDMinimum() + 0.5); + rc.y = (int) Math.floor(row.getDMinimum() + 0.5); + rc.width = (int) Math.floor(column.getDMaximum() + 0.5) - rc.x; + rc.height = (int) Math.floor(row.getDMaximum() + 0.5) - rc.y; + } + + /** TODO + * @param e + */ + @Override + public void propertyChange(PropertyChangeEvent e) { + if (e.getSource() == row) { + rc.y = (int) Math.floor(row.getDMinimum() + 0.5); + rc.height = (int) Math.floor(row.getDMaximum() + 0.5) - rc.y; + } else { + rc.x = (int) Math.floor(column.getDMinimum() + 0.5); + rc.width = (int) Math.floor(column.getDMaximum() + 0.5) - rc.x; + } + } + + /** TODO + * @param o + * @return + */ + @Override + public boolean equals(Object o) { + if (o instanceof Cell) { + Cell box = (Cell) o; + return box.row == row && box.column == column; + } + return false; + } + + /** TODO + * @return + */ + @Override + public String toString() { + return "{" + row.getDasName() + " x " + column.getDasName() + ": " + rc.toString() + "}"; + } + + /** TODO + * @return + */ + public Rectangle getCellBounds() { + return new Rectangle(rc); + } + + /** TODO + * @param r + * @return + */ + public Rectangle getCellBounds(Rectangle r) { + if (r == null) { + return getCellBounds(); + } + r.setBounds(rc); + return r; + } + + /** TODO + * @return + */ + public DasRow getRow() { + return row; + } + + /** TODO + * @return + */ + public DasColumn getColumn() { + return column; + } + } + /** + * printingTag is the DateFormat string to use to tag printed images. + */ + private String printingTag = "'UIOWA 'yyyyMMdd"; + + /** + * printingTag is the DateFormat string to use to tag printed images. + * @return Value of property printingTag. + */ + public String getPrintingTag() { + return this.printingTag; + } + + /** + * printingTag is the DateFormat string to use to tag printed images. + * @param printingTag New value of property printingTag. + */ + public void setPrintingTag(String printingTag) { + String old = this.printingTag; + this.printingTag = printingTag; + firePropertyChange("printingTag", old, printingTag); + } + /** + * Holds value of property textAntiAlias. + */ + private boolean textAntiAlias = true; + + /** + * Getter for property textAntiAlias. + * @return Value of property textAntiAlias. + */ + public boolean isTextAntiAlias() { + return this.textAntiAlias; + } + + /** + * Setter for property textAntiAlias. + * @param textAntiAlias New value of property textAntiAlias. + */ + public void setTextAntiAlias(boolean textAntiAlias) { + boolean old = this.textAntiAlias; + this.textAntiAlias = textAntiAlias; + firePropertyChange("textAntiAlias", old, textAntiAlias); + } + /** + * Holds value of property antiAlias. + */ + private boolean antiAlias = "on".equals(DasProperties.getInstance().get("antiAlias")); + + /** + * Getter for property antiAlias. + * @return Value of property antiAlias. + */ + public boolean isAntiAlias() { + return this.antiAlias; + } + + /** + * Setter for property antiAlias. + * @param antiAlias New value of property antiAlias. + */ + public void setAntiAlias(boolean antiAlias) { + boolean old = this.antiAlias; + this.antiAlias = antiAlias; + firePropertyChange("antiAlias", old, antiAlias); + } + private boolean fitted; + + /** + * If true, and the canvas was added to a scrollpane, the canvas + * will size itself to fit within the scrollpane. + * + * @return value of fitted property + */ + public boolean isFitted() { + return fitted; + } + + public void setFitted(boolean fitted) { + boolean oldValue = this.fitted; + this.fitted = fitted; + firePropertyChange("fitted", oldValue, fitted); + revalidate(); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + switch (orientation) { + case SwingConstants.HORIZONTAL: + return visibleRect.width / 10; + case SwingConstants.VERTICAL: + return visibleRect.height / 10; + default: + return 10; + } + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + switch (orientation) { + case SwingConstants.HORIZONTAL: + return visibleRect.width; + case SwingConstants.VERTICAL: + return visibleRect.height; + default: + return 10; + } + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return fitted; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return fitted; + } + + public void registerPendingChange(Object client, Object lockObject) { + stateSupport.registerPendingChange(client, lockObject); + } + + public void performingChange(Object client, Object lockObject) { + stateSupport.performingChange(client, lockObject); + } + + public void changePerformed(Object client, Object lockObject) { + stateSupport.changePerformed(client, lockObject); + } + + public boolean isPendingChanges() { + return stateSupport.isPendingChanges(); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java b/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java new file mode 100755 index 000000000..075f906f0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java @@ -0,0 +1,504 @@ +/* File: DasCanvasComponent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.DasApplication; +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.MouseModule; +import org.das2.graph.event.DasUpdateListener; +import org.das2.system.DasLogger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.logging.*; +import org.das2.components.propertyeditor.Editable; +import org.das2.components.propertyeditor.PropertyEditor; + +/** + * + * @author eew + */ +public abstract class DasCanvasComponent extends JComponent implements Editable { + + private Logger logger= DasLogger.getLogger(DasLogger.GUI_LOG); + + protected static abstract class CanvasComponentAction extends DasCanvas.CanvasAction { + private static DasCanvasComponent currentCanvasComponent; + public CanvasComponentAction(String label) { + super(label); + } + public static DasCanvasComponent getCurrentComponent() { + return currentCanvasComponent; + } + } + + private static final MouseListener currentComponentListener = new MouseAdapter() { + public void mousePressed(MouseEvent e) { + DasCanvasComponent dcc; + if (e.getSource() instanceof DasCanvasComponent) { + dcc = (DasCanvasComponent)e.getComponent(); + } else { + dcc = (DasCanvasComponent)SwingUtilities.getAncestorOfClass(DasCanvasComponent.class, e.getComponent()); + } + CanvasComponentAction.currentCanvasComponent = dcc; + DasCanvas canvas = dcc.getCanvas(); + DasCanvas.CanvasAction.currentCanvas = canvas; + } + }; + + public static final Action PROPERTIES_ACTION = new CanvasComponentAction("Properties") { + public void actionPerformed(ActionEvent e) { + if (getCurrentComponent() != null) { + getCurrentComponent().showProperties(); + } + } + }; + + private DasRow row; + private DasColumn column; + private ResizeListener rl; + protected DasMouseInputAdapter mouseAdapter; + private String dasName; + + /** + * constructs a DasCanvasComponent, creating the + * DasMouseInputAdapter for it and assigning a + * default name to it. + */ + public DasCanvasComponent() { + setOpaque(false); + rl = new ResizeListener(); + + row= DasRow.NULL; + column= DasColumn.NULL; + + setDasMouseInputAdapter( new DasMouseInputAdapter(this) ); + + try { + String name= DasApplication.getDefaultApplication().suggestNameFor(this); + setDasName(name); + } catch (org.das2.DasNameException dne) { + } + } + + /** + * Add the MouseModule to the list of MouseModules + * attached to the component via the DasMouseInputAdapter. + * MouseModules will appear the in the order that they + * are added. + */ + public void addMouseModule(MouseModule module) { + mouseAdapter.addMouseModule(module); + } + + /** + * Remove the MouseModule from the list of MouseModules + * attached to the component via the DasMouseInputAdapter. + */ + public void removeMouseModule(MouseModule module) { + mouseAdapter.removeMouseModule(module); + } + + + /** + * accessor for the DasRow used for positioning the component. + * @return DasRow used for positioning the component. + */ + public DasRow getRow() { + return row; + } + + /** + * accessor for the DasColumn used for positioning the component. + * @return DasColumn used for positioning the component. + */ + public DasColumn getColumn() { + return column; + } + + /** Called by the DasCanvas layout manager to request this component + * to set its bounds. + */ + public void resize() { + if (column == DasColumn.NULL || row == DasRow.NULL ) { + logger.warning("Null row and/or column in resize: row=" + row + + " column=" + column); + } else { + setBounds(column.getDMinimum(),row.getDMinimum(), + (column.getDMaximum()-column.getDMinimum()), + (row.getDMaximum()-row.getDMinimum())); + } + } + + @Override + public void setBounds(int x, int y, int width, int height) { + if ( getDasName().startsWith("plot_") ) { + //new Exception().printStackTrace(); + //System.err.println( getDasName() + " setBounds(" + new Rectangle(x, y, width, height) + ")" ); + } + super.setBounds(x, y, width, height); + } + + @Override + public void setBounds(Rectangle r) { + //if ( getDasName().startsWith("plot_") ) System.err.println( getDasName() + " setBounds(" + r ); + super.setBounds(r); + } + + + /** + * class for handling resize events. + */ + private class ResizeListener implements DasUpdateListener { + public void update(org.das2.graph.event.DasUpdateEvent e) { + logger.fine("component row or column moved: "+e.getSource()); + markDirty(); + DasCanvasComponent.this.update(); + } + + } + + /** + * set the DasRow for positioning the component vertically. + * The current row is disconnected, and a propertyChange is + * fired. + */ + public void setRow(DasRow r) { + if (row == r) { + return; + } + Object oldValue = row; + if (row != DasRow.NULL ) { + row.removepwUpdateListener(rl); + } + row = r; + if (row != DasRow.NULL ) { + row.addpwUpdateListener(rl); + } /*else { + throw new IllegalArgumentException("null row is not allowed for the meantime"); + }*/ + firePropertyChange("row", oldValue, r); + } + + /** + * set the DasColumn for positioning the component horizontally. + * The current column is disconnected, and a propertyChange is + * fired. + */ + public void setColumn(DasColumn c) { + if (column == c) { + return; + } + Object oldValue = column; + if (column != DasColumn.NULL ) { + column.removepwUpdateListener(rl); + } + column = c; + if (column != DasColumn.NULL ) { + column.addpwUpdateListener(rl); + } /*else { + throw new IllegalArgumentException("null column is not allowed for the meantime"); + }*/ + firePropertyChange("column", oldValue, c); + } + + /** + * popup the PropertyEditor for editing the state + * of this component. + */ + public void showProperties() { + PropertyEditor editor = new PropertyEditor(this); + editor.showDialog(this); + } + + /** + * @return a concise String representation of the object. + */ + public String toString() { + return getClass().getName()+"'"+getDasName()+"'"; + } + + /** + * This method is called when a DasUpdateEvent is processed. + * The default implementation does nothing. If a subclass + * needs to do any expensive operations involved in updating, + * they should be done by overriding this method so that + * the AWT Event Queue can coalesce update events. + */ + protected void updateImmediately() { + logger.finer("updateImmediately for "+this.getClass().getName() ); + } + + private org.das2.event.DasUpdateEvent devt; + + /** + * posts an update event on the SystemEventQueue, indicating that work needs to be + * done to get the get the component back into a valid state. + */ + public void update() { + logger.finer("update for "+this.getClass().getName() ); + java.awt.EventQueue eventQueue = + Toolkit.getDefaultToolkit().getSystemEventQueue(); + if (devt == null) devt = new org.das2.event.DasUpdateEvent(this); + eventQueue.postEvent(devt); + } + + /** Cause the component to reload, recalculate and repaint. + * This method is meant to support user-initiated data reloading, tick recalculation, + * etc. Override this to provide a refresh support. + */ + public void reload(){ + repaint(); + } + + /** Processes events occurring on this component. By default this + * method calls the appropriate + * process<event type>Event + * method for the given class of event. + *

    Note that if the event parameter is null + * the behavior is unspecified and may result in an + * exception. + * + * @param e the event + * @see java.awt.Component#processComponentEvent + * @see java.awt.Component#processFocusEvent + * @see java.awt.Component#processKeyEvent + * @see java.awt.Component#processMouseEvent + * @see java.awt.Component#processMouseMotionEvent + * @see java.awt.Component#processInputMethodEvent + * @see java.awt.Component#processHierarchyEvent + * @see java.awt.Component#processMouseWheelEvent + * @see #processDasUpdateEvent + */ + protected void processEvent(AWTEvent e) { + super.processEvent(e); + if (e instanceof org.das2.event.DasUpdateEvent) { + processDasUpdateEvent((org.das2.event.DasUpdateEvent)e); + } + } + + protected void processDasUpdateEvent(org.das2.event.DasUpdateEvent e) { + if (isDisplayable()) { + if (isDirty()) { + markClean(); + updateImmediately(); + } + resize(); + repaint(); + } + } + + /** Potentially coalesce an event being posted with an existing + * event. This method is called by EventQueue.postEvent + * if an event with the same ID as the event to be posted is found in + * the queue (both events must have this component as their source). + * This method either returns a coalesced event which replaces + * the existing event (and the new event is then discarded), or + * null to indicate that no combining should be done + * (add the second event to the end of the queue). Either event + * parameter may be modified and returned, as the other one is discarded + * unless null is returned. + *

    + * This implementation of coalesceEvents coalesces + * DasUpdateEvents, returning the existingEvent parameter + * + * @param existingEvent the event already on the EventQueue + * @param newEvent the event being posted to the + * EventQueue + * @return a coalesced event, or null indicating that no + * coalescing was done + */ + protected AWTEvent coalesceEvents(AWTEvent existingEvent, AWTEvent newEvent) { + if (existingEvent instanceof org.das2.event.DasUpdateEvent && newEvent instanceof org.das2.event.DasUpdateEvent) { + return existingEvent; + } + return super.coalesceEvents(existingEvent, newEvent); + } + + protected void installComponent() {} + + protected void uninstallComponent() {} + + public Font getFont() { + return (getParent() == null ? super.getFont() : getParent().getFont()); + } + + /** + * convenient method intended to encourage use of em's. returns the em size for the canvas. + * We define the em size as the height of the component's font. + * @return the height of the component's font. + */ + public double getEmSize() { + return getFont().getSize2D(); + } + + boolean dirty = true; + + /** + * set the dirty flag indicating the state has changed and work is to be + * done to restore a valid state. For example, a DasAxis' minimum is + * changed, so we will need to recalculate the ticks. (But we don't want + * to recalculate the ticks immediately, since the maximum may change + * as well. + */ + void markDirty() { + dirty = true; + } + /** + * @return true if the component has been marked as dirty, meaning + * work needs to be done to restore it to a valid state. + */ + boolean isDirty() { + return dirty; + } + + /** + * clear the dirty flag, indicating the component is in a self-consistent + * state. + */ + void markClean() { + dirty = false; + } + + /** + * get the DasCanvas which contains this DasCanvasComponent. + * @return the DasCanvas which contains this DasCanvasComponent. + */ + public DasCanvas getCanvas() { + return (DasCanvas)getParent(); + } + + /** + * Get the String identifier for the component which identifies + * the component within the application. This name should be + * consistent between sessions of an application, where + * applicable, for persistent state support. + * + * @return the name of the component. + */ + public String getDasName() { + return dasName; + } + + /** + * Set the String identifier for the component which identifies + * the component within the application. This name should be + * consistent between sessions of an application, where + * applicable, for persistent state support. For example, + * "timeAxis1" or "theTimeAxis" + * @param name unique String identifying the component within + * the application. + * @throws org.das2.DasNameException + */ + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = DasApplication.getDefaultApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + /** + * returns the active region of the canvas component, which is not necessarily the bounds. + */ + public Shape getActiveRegion() { + int x = getColumn().getDMinimum(); + int y = getRow().getDMinimum(); + int width = getColumn().getDMaximum() - x; + int height = getRow().getDMaximum() - y; + return new Rectangle(x, y, width, height); + } + + /** + * returns true if the component is suitable context for the point. For example, + * the operator right-clicks at the point, is this point a transparent region of + * the component, and accepting context would be confusing to the operator? This + * was first introduced to support the annotation component, which draws a compact + * background bubble around a message, which is typically smaller than its bounds, + * plus an arrow. + * @param x + * @param y + * @return true if the component accepts the context at this point. + */ + public boolean acceptContext( int x, int y ) { + return true; + } + + /** + * accessor to the DasMouseInputAdapter handling mouse input for the component. + * Note there is also getDasMouseInputAdapter. + * @return DasMouseInputAdaptor handling mouse input for the component. + * @deprecated use getDasMouseInputAdapter instead + */ + public DasMouseInputAdapter getMouseAdapter() { + return mouseAdapter; + } + + public Action[] getActions() { + return new Action[] { + PROPERTIES_ACTION, + }; + } + + /** + * Getter for property dasMouseInputAdapter, the DasMouseInputAdapter handling mouse input for the component. + * @return Value of property dasMouseInputAdapter. + */ + public DasMouseInputAdapter getDasMouseInputAdapter() { + return this.mouseAdapter; + } + + /** + * Setter for property dasMouseInputAdapter. + * @param dasMouseInputAdapter New value of property dasMouseInputAdapter. + */ + public synchronized void setDasMouseInputAdapter(DasMouseInputAdapter dasMouseInputAdapter) { + if ( mouseAdapter!=null ) { + removeMouseListener(mouseAdapter); + removeMouseMotionListener(mouseAdapter); + removeMouseListener(currentComponentListener); + removeKeyListener(mouseAdapter.getKeyAdapter()); + removeMouseWheelListener(mouseAdapter); + } + this.mouseAdapter = dasMouseInputAdapter; + if ( ! DasApplication.getDefaultApplication().isHeadless() ) { + addMouseListener(mouseAdapter); + addMouseMotionListener(mouseAdapter); + addMouseListener(currentComponentListener); + addKeyListener(mouseAdapter.getKeyAdapter()); + addMouseWheelListener(mouseAdapter); + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvasComponentInterface.java b/dasCore/src/main/java/org/das2/graph/DasCanvasComponentInterface.java new file mode 100644 index 000000000..cd27c103e --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasCanvasComponentInterface.java @@ -0,0 +1,32 @@ +/* + * DasCanvasComponentInterface.java + * + * Created on November 4, 2004, 5:26 PM + */ + +package org.das2.graph; + +import java.awt.*; + +/** + * All entities on the DasCanvas are DasCanvasComponents. + * @author Jeremy + */ +public interface DasCanvasComponentInterface { + + /** + * this paints the component, the point 0,0 always refers to the upper-left corner + * of the canvas. + * @param g + */ + void paintComponent( Graphics g ); + + /** + * This is called when the canvas is resized or something has happened to make the + * boundries change. This code should call the setBounds( Rectangle ) + */ + void resize(); + + + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvasStateSupport.java b/dasCore/src/main/java/org/das2/graph/DasCanvasStateSupport.java new file mode 100755 index 000000000..cee0ff352 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasCanvasStateSupport.java @@ -0,0 +1,100 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.graph; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * DasCanvasStateSupport is a registery where objects can tell the canvas they + * intend to mutate the canvas, so canvas clients should know that its result + * may be incomplete. This is first introduced to support server-side + * processing, where in Autoplot the Autolayout feature knows then layout is + * going to be adjusted, but doesn't have enough information to perform the + * function. + * + * Also, this may be used to address bugs like + * https://bugs-pw.physics.uiowa.edu/mantis/view.php?id=303, + * "strange intermediate states in transitions," since canvas painting can + * be halted while the change is being performed. + * + * @author jbf + */ +public class DasCanvasStateSupport { + DasCanvas canvas; + Map changesPending; + private static final Logger logger= Logger.getLogger( "das2.graphics" ); + + DasCanvasStateSupport( DasCanvas canvas ) { + this.canvas= canvas; + this.changesPending= new HashMap(); // client->lock + } + + /** + * the client knows a change will be coming, and the canvas' clients should + * know that its current state will change soon. Example pending changes + * would be: + * layout because tick labels are changing + * data is loading + * + * @param client the object that will perform the change. This allows the + * canvas (and developers) identify who has registered the change. + * @param lockObject object identifying the change. + */ + synchronized void registerPendingChange( Object client, Object lockObject ) { + logger.fine( "registerPendingChange "+lockObject+" by "+client); + Object existingClient= changesPending.get(lockObject); + if ( existingClient!=null ) { + if ( existingClient!=client ) { + throw new IllegalStateException( "lock object in use: "+lockObject + ", by "+changesPending.get(lockObject) ); + } else { + return; + } + } + boolean oldVal= this.isPendingChanges(); + changesPending.put( lockObject, client ); + canvas.firePropertyChange( PROP_PENDINGCHANGES, oldVal, true ); + } + + /** + * performingChange tells that the change is about to be performed. This + * is a place holder in case we use a mutator lock, but currently does + * nothing. + * @param lockObject + */ + synchronized void performingChange( Object client, Object lockObject ) { + + } + + /** + * the change is complete, and as far as the client is concerned, the canvas + * is valid. + * @param lockObject + */ + synchronized void changePerformed( Object client, Object lockObject ) { + logger.fine( "clearPendingChange "+lockObject+" by "+client); + if ( changesPending.get(lockObject)==null ) { + // throw new IllegalStateException( "no such lock object: "+lockObject ); //TODO: handle multiple registrations by the same client + } + boolean oldVal= this.isPendingChanges(); + changesPending.remove(lockObject); + canvas.firePropertyChange( PROP_PENDINGCHANGES, oldVal, true ); + } + + // --- properties + + /** + * someone has registered a pending change. + */ + public static final String PROP_PENDINGCHANGES = "pendingChanges"; + + public boolean isPendingChanges() { + return changesPending.size() > 0; + } + +} + diff --git a/dasCore/src/main/java/org/das2/graph/DasColorBar.java b/dasCore/src/main/java/org/das2/graph/DasColorBar.java new file mode 100644 index 000000000..6257cd705 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasColorBar.java @@ -0,0 +1,657 @@ +/* File: DasColorBar.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.TimeUtil; +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.components.propertyeditor.Enumeration; +import org.das2.dasml.FormBase; +import org.das2.event.DataRangeSelectionEvent; +import org.das2.event.HorizontalSliceSelectionRenderer; +import org.das2.event.MouseModule; +import org.das2.event.MousePointSelectionEvent; +import java.awt.image.IndexColorModel; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import javax.swing.event.EventListenerList; + +/** + * + * @author jbf + */ +public class DasColorBar extends DasAxis { + + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_FILL_COLOR= "fillColor"; + + private BufferedImage image; + private DasColorBar.Type type; + private static int fillColor= Color.LIGHT_GRAY.getRGB(); + private int fillColorIndex; + private int ncolor; + + private static final int COLORTABLE_SIZE=240; + + public DasColorBar( Datum min, Datum max, boolean isLog) { + this(min, max, RIGHT, isLog); + } + + public DasColorBar( Datum min, Datum max, int orientation, boolean isLog) { + super(min, max, orientation, isLog); + setLayout(new ColorBarLayoutManager()); + setType(DasColorBar.Type.COLOR_WEDGE); + } + + public DasColorBar( DatumRange range, int orientation, boolean isLog) { + super(range, orientation); + setLog(isLog); + setLayout(new ColorBarLayoutManager()); + setType(DasColorBar.Type.COLOR_WEDGE); + } + + public DasColorBar( DatumRange range, boolean isLog) { + this(range, RIGHT, isLog); + } + + public int rgbTransform(double x, Units units) { + int icolor= (int)transform(x,units,0, ncolor); + + if ( units.isFill(x) ) { + return fillColor; + } else { + icolor= (icolor<0)?0:icolor; + icolor= (icolor>=ncolor)?(ncolor-1):icolor; + return type.getRGB(icolor); + } + } + + public int indexColorTransform( double x, Units units ) { + if ( units.isFill(x) ) { + return fillColorIndex; + } else { + int icolor= (int)transform(x,units,0,ncolor); + icolor= (icolor<0)?0:icolor; + icolor= (icolor>=ncolor)?(ncolor-1):icolor; + return icolor; + } + } + + public IndexColorModel getIndexColorModel() { + return new IndexColorModel( 8, type.getColorCount()+1, type.colorTable, 0, true, -1, DataBuffer.TYPE_BYTE ); + } + + public int getFillColorIndex() { + return fillColorIndex; + } + + public DasColorBar.Type getType() { + return type; + } + + + public void setType(DasColorBar.Type type) { + if (this.type == type) { + return; + } + DasColorBar.Type oldValue = this.type; + this.type = type; + this.ncolor= type.getColorCount(); + image = null; + fillColorIndex= getType().getColorCount(); + fillColor= getType().getRGB(fillColorIndex); + markDirty(); + update(); + firePropertyChange( PROPERTY_TYPE, oldValue,type); + } + + protected void paintComponent(Graphics g) { + int x = (int)Math.round(getColumn().getDMinimum()); + int y = (int)Math.round(getRow().getDMinimum()); + int width = (int)Math.round(getColumn().getDMaximum()) - x; + int height = (int)Math.round(getRow().getDMaximum()) - y; + //if (image == null || image.getWidth() != width || image.getHeight() != height) { + if (isHorizontal()) { + image = type.getHorizontalScaledImage(width, height); + } else { + image = type.getVerticalScaledImage(width, height); + } + //} + g.translate(-getX(), -getY()); + if (!isHorizontal()) { + y++; + } + g.drawImage(image, x, y, this); + g.translate(getX(), getY()); + super.paintComponent(g); + } + + protected Rectangle getAxisBounds() { + int x = (int)Math.round(getColumn().getDMinimum()); + int y = (int)Math.round(getRow().getDMinimum()); + int width = (int)Math.round(getColumn().getDMaximum()) - x; + int height = (int)Math.round(getRow().getDMaximum()) - y; + Rectangle rc = new Rectangle(x, y, width, height); + Rectangle bounds = super.getAxisBounds(); + bounds.add(rc); + return bounds; + } + + public static DasColumn getColorBarColumn(DasColumn column) { + return new DasColumn( null, column, 1.0, 1.0, 1, 2, 0, 0 ); + } + + /** Process a <colorbar> element. + * + * @param element The DOM tree node that represents the element + */ + static DasColorBar processColorbarElement(Element element, FormBase form) throws org.das2.DasPropertyException,org.das2.DasNameException, java.text.ParseException { + String name = element.getAttribute("name"); + boolean log = element.getAttribute("log").equals("true"); + String unitStr = element.getAttribute("units"); + if (unitStr == null) { + unitStr = ""; + } + Datum dataMinimum; + Datum dataMaximum; + if (unitStr.equals("TIME")) { + String min = element.getAttribute("dataMinimum"); + String max = element.getAttribute("dataMaximum"); + dataMinimum = (min == null || min.equals("") ? TimeUtil.create("1979-02-26") : TimeUtil.create(min)); + dataMaximum = (max == null || max.equals("") ? TimeUtil.create("1979-02-27") : TimeUtil.create(max)); + } else { + Units units = Units.lookupUnits(unitStr); + String min = element.getAttribute("dataMinimum"); + String max = element.getAttribute("dataMaximum"); + dataMinimum = (min == null || min.equals("") ? Datum.create(1.0, units) : Datum.create(Double.parseDouble(min), units)); + dataMaximum = (max == null || max.equals("") ? Datum.create(10.0, units) : Datum.create(Double.parseDouble(max), units)); + } + int orientation = parseOrientationString(element.getAttribute("orientation")); + + DasColorBar cb = new DasColorBar(dataMinimum, dataMaximum, orientation, log); + + String rowString = element.getAttribute("row"); + if (!rowString.equals("")) { + DasRow row = (DasRow)form.checkValue(rowString, DasRow.class, ""); + cb.setRow(row); + } + String columnString = element.getAttribute("column"); + if (!columnString.equals("")) { + DasColumn column = (DasColumn)form.checkValue(columnString, DasColumn.class, ""); + cb.setColumn(column); + } + + cb.setLabel(element.getAttribute("label")); + cb.setOppositeAxisVisible(!element.getAttribute("oppositeAxisVisible").equals("false")); + cb.setTickLabelsVisible(!element.getAttribute("tickLabelsVisible").equals("false")); + cb.setType(DasColorBar.Type.parse(element.getAttribute(PROPERTY_TYPE))); + + cb.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, cb); + + return cb; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("colorbar"); + String minimumStr = getDataMinimum().toString(); + element.setAttribute("dataMinimum", minimumStr); + String maximumStr = getDataMaximum().toString(); + element.setAttribute("dataMaximum", maximumStr); + + element.setAttribute("name", getDasName()); + element.setAttribute("row", getRow().getDasName()); + element.setAttribute("column", getColumn().getDasName()); + + element.setAttribute("label", getLabel()); + element.setAttribute("log", Boolean.toString(isLog())); + element.setAttribute("tickLabelsVisible", Boolean.toString(isTickLabelsVisible())); + element.setAttribute("oppositeAxisVisible", Boolean.toString(isOppositeAxisVisible())); + element.setAttribute("animated", Boolean.toString(isAnimated())); + element.setAttribute("orientation", orientationToString(getOrientation())); + element.setAttribute(PROPERTY_TYPE, getType().toString()); + + return element; + } + + public static DasColorBar createNamedColorBar(String name) { + DasColorBar cb = new DasColorBar(Datum.create(1.0, Units.dimensionless), Datum.create(10.0, Units.dimensionless), false); + if (name == null) { + name = "colorbar_" + Integer.toHexString(System.identityHashCode(cb)); + } + try { + cb.setDasName(name); + } catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + return cb; + } + + public Shape getActiveRegion() { + int x = (int)Math.round(getColumn().getDMinimum()); + int y = (int)Math.round(getRow().getDMinimum()); + int width = (int)Math.round(getColumn().getDMaximum()) - x; + int height = (int)Math.round(getRow().getDMaximum()) - y; + Rectangle bounds = primaryInputPanel.getBounds(); + bounds.translate(getX(), getY()); + Rectangle middleBounds = new Rectangle(x, y, width, height); + bounds.add(middleBounds); + if (isOppositeAxisVisible()) { + Rectangle secondaryBounds = secondaryInputPanel.getBounds(); + secondaryBounds.translate(getX(), getY()); + bounds.add(secondaryBounds); + } + return bounds; + } + + protected class ColorBarLayoutManager extends AxisLayoutManager { + + public void layoutContainer(Container parent) { + super.layoutContainer(parent); + int x = (int)Math.round(getColumn().getDMinimum()); + int y = (int)Math.round(getRow().getDMinimum()); + int width = (int)Math.round(getColumn().getDMaximum()) - x; + int height = (int)Math.round(getRow().getDMaximum()) - y; + Rectangle rc = new Rectangle(x - getX(), y - getY(), width, height); + Rectangle bounds = primaryInputPanel.getBounds(); + bounds.add(rc); + primaryInputPanel.setBounds(bounds); + } + + } + + public static final class Type implements Enumeration, Displayable { + + public static final Type COLOR_WEDGE = new Type("color_wedge"); + //public static final Type BLUE_TO_ORANGE = new Type("blue_to_orange"); + public static final Type GRAYSCALE = new Type("grayscale"); + public static final Type INVERSE_GRAYSCALE = new Type("inverse_grayscale"); + public static final Type WRAPPED_COLOR_WEDGE = new Type("wrapped_color_wedge"); + + private BufferedImage image; + private int[] colorTable; + private final String desc; + private javax.swing.Icon icon; + + private Type(String desc) { + this.desc = desc; + } + + public javax.swing.Icon getListIcon() { + maybeInitializeIcon(); + return icon; + } + + public void maybeInitializeIcon() { + if (icon == null) { + icon = new javax.swing.ImageIcon(getVerticalScaledImage(16, 16)); + } + } + + public String toString() { + return desc; + } + + public String getListLabel() { + return desc; + } + + /* It's understood that the colors indeces from 0 to getColorCount()-1 are the color wedge, and getColorCount() is the fill color. */ + public int getColorCount() { + maybeInitializeColorTable(); + return colorTable.length-1; + } + + public int getRGB(int index) { + maybeInitializeColorTable(); + return colorTable[index]; + } + + public BufferedImage getHorizontalScaledImage(int width, int height) { + maybeInitializeImage(); + BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + AffineTransform at = new AffineTransform(); + at.scale((double)width / (double)getColorCount(), (double)height); + at.rotate(-Math.PI/2.0); + at.translate(-1.0, 0.0); + AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + op.filter(image, scaled); + return scaled; + } + + public BufferedImage getVerticalScaledImage(int width, int height) { + maybeInitializeImage(); + BufferedImage scaled = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + AffineTransform at = new AffineTransform(); + at.scale((double)width, -(double)height / (double)getColorCount()); + at.translate(0.0, -(double)getColorCount()); + AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + op.filter(image, scaled); + return scaled; + } + + private void maybeInitializeImage() { + if (image == null) { + maybeInitializeColorTable(); + image = new BufferedImage(1, getColorCount(), BufferedImage.TYPE_INT_RGB); + image.setRGB(0, 0, 1, getColorCount(), colorTable, 0, 1); + } + } + + // returns a color table with interpolated colors for the wedge from 0 to ncolor-1, and at ncolor, the fill color. + private static int[] makeColorTable( int [] index, int[] red, int[] green, int[] blue, int ncolor, int bottom, int top ) { + // index should go from 0-255. + // truncate when ncolor>COLORTABLE_SIZE + int[] colorTable = new int[ncolor]; + + int ii= 0; + for (int i = 0; i < ncolor-1; i++) { + float comp= ( i - bottom ) * 255 / ( top - bottom ); + if ( comp > index[ii + 1]) { + ii++; + } + + double a; + if ( ii>=(index.length-1) ) { + a= 1; + ii= index.length-2; + } else { + a= (comp-index[ii]) / (double)(index[ii+1]-index[ii]); + } + if ( a>1. ) a=1.; + if ( a<0. ) a=0.; + double rr= (red[ii]*(1-a) + red[ii+1]*a)/(double)255.; + double gg= (green[ii]*(1-a) + green[ii+1]*a)/(double)255.; + double bb= (blue[ii]*(1-a) + blue[ii+1]*a)/(double)255.; + colorTable[i]= new Color((float)rr,(float)gg,(float)bb).getRGB(); + } + + colorTable[ncolor-1]= fillColor; + return colorTable; + } + + private void maybeInitializeColorTable() { + if (colorTable == null) { + initializeColorTable(COLORTABLE_SIZE,0,COLORTABLE_SIZE); + } + } + + private void initializeColorTable( int size, int bottom, int top ) { + if (this == COLOR_WEDGE) { + initializeColorWedge(size, bottom, top); + } else if (this == GRAYSCALE) { + initializeGrayScale(size, bottom, top); + } else if (this == INVERSE_GRAYSCALE) { + initializeInverseGrayScale(size, bottom, top); + } else if (this == WRAPPED_COLOR_WEDGE) { + initializeWrappedColorWedge(size, bottom, top); + //} else if (this == BLUE_TO_ORANGE ) { + // initializeBlueToOrange(size, bottom, top); + } + } + + private void initializeColorWedge( int size, int bottom, int top ) { + int[] index = { 0, 30, 63, 126, 162, 192, 221, 255 }; + int[] red = { 0, 0, 0, 0, 255, 255, 255, 255 }; + int[] green = { 0, 0, 255, 255, 255, 185, 84, 0 }; + int[] blue = { 137, 255, 255, 0, 0, 0, 0, 0 }; + colorTable = makeColorTable( index, red, green, blue, size, bottom, top ); + colorTable[0] = ( colorTable[0] & 0xFFFFFF00 ) | 1; + } + + private void initializeBlueToOrange( int size, int bottom, int top ) { + // cat | awk '{ print $3 "," }' | xargs + int[] index = { 0, 23, 46, 69, 92, 115, 139, 162, 185, 208, 231, 255 }; + int[] red = { 0, 25, 50, 101, 153, 204, 255, 255, 255, 255, 255, 255 }; + int[] green = { 42, 101, 153, 204, 237, 255, 255, 238, 204, 153, 102, 42 }; + int[] blue = { 255, 255, 255, 255, 255, 255, 204, 153, 101, 50, 25, 0 }; + colorTable = makeColorTable( index, red, green, blue, size, bottom, top ); + colorTable[0] = ( colorTable[0] & 0xFFFFFF00 ) | 1; + } + + private void initializeWrappedColorWedge( int size, int bottom, int top ) { + int[] index = { 0, 32, 64, 96, 128, 160, 192, 224, 255, }; + int[] red = { 225, 0, 0, 0, 255, 255, 255, 255, 255, }; + int[] green = { 0, 0, 255, 255, 255, 185, 84, 0, 0, }; + int[] blue = { 225, 255, 255, 0, 0, 0, 0, 0, 255, }; + colorTable = makeColorTable( index, red, green, blue, size, bottom, top ); + } + + private void initializeInverseGrayScale( int size, int bottom, int top ) { + int [] index= { 0, 255 }; + int [] red= { 0, 255 }; + int [] green= { 0, 255 }; + int [] blue= { 0, 255 }; + colorTable = makeColorTable( index, red, green, blue, size, bottom, top ); + } + + private void initializeGrayScale( int size, int bottom, int top ) { + int [] index= { 0, 255 }; + int [] red= { 255, 0 }; + int [] green= { 255, 0 }; + int [] blue= { 255, 0 }; + colorTable = makeColorTable( index, red, green, blue, size, bottom, top ); + } + + public static Type parse(String s) { + if (s.equals("color_wedge")) { + return COLOR_WEDGE; + } else if (s.equals("grayscale")) { + return GRAYSCALE; + } else if (s.equals("inverse_grayscale")) { + return INVERSE_GRAYSCALE; + //} else if (s.equals("blue_to_orange")) { + // return BLUE_TO_ORANGE; + } else { + throw new IllegalArgumentException("invalid DasColorBar.Type string: " + s); + } + } + + } + + public MouseModule getRepaletteMouseModule( Renderer r ) { + return new ColorBarRepaletteMouseModule( r, this ); + } + + public class ColorBarRepaletteMouseModule extends MouseModule { + + DasColorBar colorBar; + Renderer parent; + DatumRange range0; + int lastTopColor; + int lastBottomColor; + + boolean animated0; + int state; + int STATE_IGNORE=300; + int STATE_TOP=200; + int STATE_BOTTOM=100; + + /** Utility field used by event firing mechanism. */ + private EventListenerList listenerList = null; + + public String getLabel() { return "Repalette"; }; + + public ColorBarRepaletteMouseModule( Renderer parent, DasColorBar colorBar ) { + if (colorBar.isHorizontal()) { + throw new IllegalArgumentException("Axis orientation is not vertical"); + } + if ( parent==null ) { + throw new IllegalArgumentException("Parent is null"); + } + this.parent= parent; + // this.dragRenderer= (DragRenderer)HorizontalRangeRenderer.renderer; + this.dragRenderer= new HorizontalSliceSelectionRenderer(parent.getParent()); + this.colorBar= colorBar; + } + + private void setColorBar( int y ) { + + int bottomColor, topColor; + + DatumRange dr; + DasRow row= colorBar.getRow(); + + double alpha= ( row.getDMaximum() - y ) / (1.*row.getHeight()); + + if ( state==STATE_TOP ) { + topColor= (int)( COLORTABLE_SIZE * alpha ); + topColor= Math.max( COLORTABLE_SIZE / 20 + 1, topColor ); + bottomColor= 0; + } else if ( state==STATE_BOTTOM ) { + topColor= COLORTABLE_SIZE; + bottomColor= (int)( COLORTABLE_SIZE * alpha ); + bottomColor= Math.min( COLORTABLE_SIZE * 19 / 20, bottomColor ); + } else { + return; + } + + System.err.println( ""+bottomColor + " "+topColor ); + lastTopColor= topColor; + lastBottomColor= bottomColor; + + colorBar.type.initializeColorTable( COLORTABLE_SIZE, bottomColor, lastTopColor ); + + colorBar.image= null; + colorBar.type.image= null; + colorBar.repaint(); + parent.refreshImage(); + } + + public void mouseReleased( MouseEvent e ) { + if ( state!=STATE_IGNORE ) { + colorBar.setAnimated(animated0); + int lastTopColor= this.lastTopColor; + + DatumRange dr; + double a0= lastBottomColor / ( 1.*COLORTABLE_SIZE ); + double a1= lastTopColor / ( 1.*COLORTABLE_SIZE); + + if ( isLog() ) { + dr= DatumRangeUtil.rescaleLog( range0, a0, a1 ); + } else { + dr= DatumRangeUtil.rescale( range0, a0, a1); + } + + colorBar.setDatumRange( dr ); + + colorBar.type.initializeColorTable( COLORTABLE_SIZE, 0, COLORTABLE_SIZE ); + + colorBar.image= null; + colorBar.type.image= null; + colorBar.repaint(); + parent.refreshImage(); + + } + } + + public void mousePointSelected(MousePointSelectionEvent e) { + setColorBar( e.getY() ); + } + + /** Registers DataRangeSelectionListener to receive events. + * @param listener The listener to register. + */ + public synchronized void addDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + if (listenerList == null ) { + listenerList = new EventListenerList(); + } + listenerList.add(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Removes DataRangeSelectionListener from the list of listeners. + * @param listener The listener to remove. + */ + public synchronized void removeDataRangeSelectionListener(org.das2.event.DataRangeSelectionListener listener) { + listenerList.remove(org.das2.event.DataRangeSelectionListener.class, listener); + } + + /** Notifies all registered listeners about the event. + * + * @param event The event to be fired + */ + private void fireDataRangeSelectionListenerDataRangeSelected(DataRangeSelectionEvent event) { + if (listenerList == null) return; + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==org.das2.event.DataRangeSelectionListener.class) { + ((org.das2.event.DataRangeSelectionListener)listeners[i+1]).dataRangeSelected(event); + } + } + } + + public void mousePressed(java.awt.event.MouseEvent e) { + super.mousePressed(e); + if ( DasColorBar.this.getColumn().contains(e.getX()+DasColorBar.this.getX()) ) { + if ( e.getY() + DasColorBar.this.getY() > DasColorBar.this.getRow().getDMiddle() ) { + state= STATE_BOTTOM; + } else { + state= STATE_TOP; + } + animated0= colorBar.isAnimated(); + colorBar.setAnimated(false); + range0= colorBar.getDatumRange(); + } else { + state= STATE_IGNORE; + } + } + + } + + /** + * Getter for property fillColor. + * @return Value of property fillColor. + */ + public Color getFillColor() { + return new Color( this.fillColor, true ); + } + + /** + * Setter for property fillColor. + * @param fillColor New value of property fillColor. + */ + public void setFillColor(Color fillColor) { + Color oldColor= new Color( this.fillColor ); + this.fillColor = fillColor.getRGB(); + this.type.initializeColorTable( COLORTABLE_SIZE, 0, this.type.getColorCount() ); + markDirty(); + update(); + firePropertyChange( PROPERTY_FILL_COLOR, oldColor,fillColor ); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasColumn.java b/dasCore/src/main/java/org/das2/graph/DasColumn.java new file mode 100755 index 000000000..5722c2bdb --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasColumn.java @@ -0,0 +1,193 @@ +/* File: DasColumn.java + * Copyright (C) 2002-2014 The University of Iowa + * Created by: Jeremy Faden + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.dasml.FormBase; +import java.text.ParseException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** Define a vertical region on a DasCanvas. + * Extends from the top to the bottom of the canvas. Canvas components such as + * plots, axes, colorbars and labels use DasColumns and DasRows for positioning. + * + * @author jbf + * @author eew + * @since 2.0 + */ +public class DasColumn extends DasDevicePosition { + + /** Create a new vertical region on a DasCanvas. + * + * @param parent The canvas on which the region will be defined + * @param rLeft a value between 0.0 and 1.0 defining the left boundary of the column + * @param rRight a value between 0.0 and 1.0 defining the right boundary of the column + */ + public DasColumn(DasCanvas parent, double rLeft, double rRight) + { + super(parent,rLeft,rRight,true); + } + + /** Create a new vertical region on a DasCanvas + * + * @param canvas The canvas on which the region will be defined + * @param parent If not null, the column is defined relative to another column. So + * rMin = 0.0 is the left position of the parent column and rMax = 1.0 is the + * right position of the parent column. + * @param rLeft a value between 0.0 and 1.0 defining the left boundary of the column + * @param rRight a value between 0.0 and 1.0 defining the right boundary of the column + * @param emMin Offset from rLeft in "M's", adds with ptMin + * @param emMax Offset from rRight in "M's", adds with ptMax + * @param ptMin Offset from rLeft in pixels, adds with emMin + * @param ptMax Offset from rRight in pixels, adds with emMax + */ + public DasColumn(DasCanvas canvas, DasColumn parent, double rLeft, double rRight, + double emMin, double emMax, int ptMin, int ptMax ) + { + super( canvas, true, parent, rLeft, rRight, emMin, emMax, ptMin, ptMax ); + } + + + /** Makes a new DasColumn by parsing a formatted string + * makes a new DasColumn by parsing a string like "100%-5em+3pt" to get the offsets. + * The three qualifiers are "%", "em", and "pt", but "px" is allowed as well + * as surely people will use that by mistake. If an offset or the normal position + * is not specified, then 0 is used. + * + * @param canvas the canvas for the layout, ignored when a parent DasColumn is used. + * @param parent if non-null, this DasColumn is specified with respect to parent. + * @param minStr a string like "0%+5em" + * @param maxStr a string like "100%-7em" + * @throws IllegalArgumentException if the strings cannot be parsed + */ + public static DasColumn create(DasCanvas canvas, DasColumn parent, String minStr, + String maxStr ) + { + double[] min, max; + try { + min= parseFormatStr( minStr ); + } catch ( ParseException e ) { + throw new IllegalArgumentException("unable to parse min: \""+minStr+"\""); + } + try { + max= parseFormatStr( maxStr ); + } catch ( ParseException e ) { + throw new IllegalArgumentException("unable to parse max: \""+maxStr+"\""); + } + return new DasColumn( canvas, parent, min[0], max[0], min[1], max[1], (int)min[2], (int)max[2] ); + } + + public static final DasColumn NULL= new DasColumn(null,null,0,0,0,0,0,0); + + /** + * @deprecated This created a column that was not attached to anything, so + * it was simply a convenience method that didn't save much effort. + */ + public DasColumn createSubColumn( double pleft, double pright ) { + double left= getMinimum(); + double right= getMaximum(); + double delta= right-left; + return new DasColumn(getCanvas(),left+pleft*delta,left+pright*delta); + } + + public int getWidth() { + return getDMaximum()-getDMinimum(); + } + + public static DasColumn create(DasCanvas parent) { + return new DasColumn(parent,null,0.0,1.0,5,-5,0,0); + } + + public static DasColumn create( DasCanvas parent, int iplot, int nplot ) { + double min= 0.1 + iplot * ( 0.7 ) / nplot; + double max= 0.099 + ( iplot + 1 ) * ( 0.7 ) / nplot; + return new DasColumn( parent, min, max ); + } + + public DasColumn createAttachedColumn(double pleft, double pright) { + return new DasColumn(null,this,pleft,pright,0,0,0,0); + } + + /** + * create a child by parsing spec strings like "50%+3em" + * @throws IllegalArgumentException when the string is malformed. + * @param smin + * @param smax + * @return + */ + public DasColumn createChildColumn( String smin, String smax ) { + try { + double[] min= DasDevicePosition.parseFormatStr(smin); + double[] max= DasDevicePosition.parseFormatStr(smax); + return new DasColumn( null, this, min[0], max[0], min[1], max[1], (int)min[2], (int)max[2] ); + } catch ( ParseException ex ) { + throw new IllegalArgumentException(ex); + } + } + + /** Process a <column7gt; element. + * + * @param element The DOM tree node that represents the element + */ + static DasColumn processColumnElement(Element element, DasCanvas canvas, FormBase form) throws DasException { + String name = element.getAttribute("name"); + double minimum + = Double.parseDouble(element.getAttribute("minimum")); + double maximum + = Double.parseDouble(element.getAttribute("maximum")); + DasColumn column = new DasColumn(canvas, minimum, maximum); + column.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, column); + return column; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("column"); + element.setAttribute("name", getDasName()); + element.setAttribute("minimum", Double.toString(getMinimum())); + element.setAttribute("maximum", Double.toString(getMaximum())); + return element; + } + + /** + * return the left of the column. + * @return + */ + public int left() { + return getDMinimum(); + } + + /** + * return the right (non-inclusive) of the column. + * @return + */ + public int right() { + return getDMaximum(); + } +} + diff --git a/dasCore/src/main/java/org/das2/graph/DasDevicePosition.java b/dasCore/src/main/java/org/das2/graph/DasDevicePosition.java new file mode 100755 index 000000000..29877d791 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasDevicePosition.java @@ -0,0 +1,583 @@ +/* File: DasDevicePosition.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.DasApplication; +import org.das2.graph.event.DasUpdateEvent; +import org.das2.graph.event.DasUpdateListener; +import java.beans.PropertyChangeEvent; + +import javax.swing.event.EventListenerList; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.StringTokenizer; +import org.das2.components.propertyeditor.Editable; +import org.das2.system.MutatorLock; + +/** + * + * @author jbf + */ +public abstract class DasDevicePosition implements Editable, java.io.Serializable { + + protected transient DasCanvas canvas; + protected transient DasDevicePosition parent; + + private double minimum; + private double maximum; + + private boolean isWidth; + + private String dasName; + private transient PropertyChangeSupport propertyChangeDelegate; + + protected EventListenerList listenerList = new EventListenerList(); + + private PropertyChangeListener canvasListener= new PropertyChangeListener() { + public void propertyChange( PropertyChangeEvent ev ) { + if ( DasDevicePosition.this.emMinimum!=0 || DasDevicePosition.this.emMaximum!=0 ) { + revalidate(); + } + } + }; + + protected DasDevicePosition( DasCanvas canvas, boolean isWidth, DasDevicePosition parent, + double minimum, double maximum, + double emMinimum, double emMaximum, + int ptMinimum, int ptMaximum ) { + if ( minimum > maximum ) { + throw new IllegalArgumentException( "minimum>maximum" ); + } + + // isNull indicates this is the NULL row or column. + boolean isNull= ( canvas==null ) && ( parent==null ); + + if ( parent!=null ) { + canvas= parent.getCanvas(); + isWidth= parent.isWidth; + } + + if ( canvas==null & ( ! isNull ) ) { + throw new IllegalArgumentException("parent cannot be null"); + } + + this.canvas = canvas; + this.parent= parent; + this.minimum = minimum; + this.maximum = maximum; + this.emMinimum= emMinimum; + this.emMaximum= emMaximum; + this.ptMinimum= ptMinimum; + this.ptMaximum= ptMaximum; + this.isWidth = isWidth; + + this.dasName = DasApplication.getDefaultApplication().suggestNameFor(this); + this.propertyChangeDelegate = new PropertyChangeSupport(this); + if ( parent!=null ) { + parent.addPropertyChangeListener( new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + revalidate(); + } + } ); + } else { + if (canvas != null) { + canvas.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + revalidate(); + } + }); + canvas.addPropertyChangeListener( "font", canvasListener ); + canvas.addDevicePosition(this); + } + } + if ( !isNull ) { + revalidate(); + } + } + + /** + * parse position strings like "100%-5em+4pt" into npos, emoffset, pt_offset. + * Note px is acceptable, but pt is proper. + * Ems are rounded to the nearest hundredth. + * Percents are returned as normal (0-1) and rounded to the nearest thousandth. + */ + public static double[] parseFormatStr( String s ) throws ParseException { + double[] result= new double[] { 0, 0, 0 }; + StringTokenizer tok= new StringTokenizer( s, "%emptx", true ); + int pos=0; + while ( tok.hasMoreTokens() ) { + String ds= tok.nextToken(); + pos+=ds.length(); + double d= Double.parseDouble(ds); + String u= tok.nextToken(); + pos+=u.length(); + u.trim(); + if ( u.charAt(0)=='%' ) { + result[0]= d/100.; + } else if ( u.equals("e") ) { + String s2= tok.nextToken(); + if ( !s2.equals("m") ) throw new ParseException( "expected m following e",pos); + pos+= s2.length(); + result[1]= d; + } else if ( u.equals("p") ) { + String s2= tok.nextToken(); + if ( !( s2.equals("t") || s2.equals("x") ) ) throw new ParseException( "expected t following p",pos); + pos+= s2.length(); + result[2]= d; + } + } + result[0]= Math.round(result[0]*1000)/1000.; + result[1]= Math.round(result[1]*10)/10.; + return result; + } + + public static void parseLayoutStr( DasDevicePosition pos, String spec ) throws ParseException { + String[] ss= spec.split(","); + double[] pmin= parseFormatStr( ss[0] ); + double[] pmax= parseFormatStr( ss[1] ); + + MutatorLock lock= pos.mutatorLock(); + lock.lock(); + pos.setMinimum(pmin[0]); + pos.setEmMinimum(pmin[1]); + pos.setPtMinimum((int)pmin[2]); + pos.setMaximum(pmax[0]); + pos.setEmMaximum(pmax[1]); + pos.setPtMaximum((int)pmax[2]); + lock.unlock(); + } + + public static String formatLayoutStr( DasDevicePosition pos, boolean min ) { + StringBuffer buf= new StringBuffer(); + DecimalFormat nf2= new DecimalFormat("0.00"); + DecimalFormat nf1= new DecimalFormat("0.0"); + DecimalFormat nf0= new DecimalFormat("0"); + if ( min ) { + if ( pos.getMinimum()!=0 ) buf.append( nf2.format(pos.getMinimum()*100 )+"%" ); + if ( pos.getEmMinimum()!=0 ) buf.append( nf1.format(pos.getEmMinimum() )+"em" ); + if ( pos.getPtMinimum()!=0 ) buf.append( nf0.format(pos.getPtMinimum()) + "pt" ); + } else { + if ( pos.getMaximum()!=0 ) buf.append( nf2.format(pos.getMaximum()*100 )+"%" ); + if ( pos.getEmMaximum()!=0 ) buf.append( nf1.format(pos.getEmMaximum() )+"em" ); + if ( pos.getPtMaximum()!=0 ) buf.append( nf0.format(pos.getPtMaximum()) + "pt" ); + } + if ( buf.length()==0 ) return "0%"; + return buf.toString(); + } + + public DasDevicePosition(DasCanvas parent, double minimum, double maximum, boolean width) { + this( parent, width, null, minimum, maximum, 0., 0., 0, 0 ); + } + + protected DasCanvas getCanvas() { + return this.canvas; + } + + public void setDasName(String name) throws org.das2.DasNameException { + if (name.equals(dasName)) { + return; + } + String oldName = dasName; + dasName = name; + DasApplication app = canvas.getDasApplication(); + if (app != null) { + app.getNameContext().put(name, this); + if (oldName != null) { + app.getNameContext().remove(oldName); + } + } + this.firePropertyChange("name", oldName, name); + } + + public String getDasName() { + return dasName; + } + + /** + * returns the em size for the canvas. We define the em size as the height of the + * font. + * @return the em height in points. + */ + public int getEmSize() { + return canvas.getFont().getSize(); + } + + private int getParentMin() { + if ( parent==null ) { + return 0; + } else { + return parent.getDMinimum(); + } + } + + private int getParentMax() { + if ( parent==null ) { + return isWidth ? canvas.getWidth() : canvas.getHeight(); + } else { + return parent.getDMaximum(); + } + } + + private int dMinimum, dMaximum; + + /** + * recalculates dMinimum and dMaximum becased on the new values, and checks for + * correctness. Note if dMaximum<=dMinimum, we define dMaximum= dMinimum+1. + */ + private void revalidate() { + int oldmin= dMinimum; + int oldmax= dMaximum; + dMinimum= (int)( getParentMin() + minimum*getDeviceSize() + getEmSize() * emMinimum + ptMinimum ); + dMaximum= (int)( getParentMin() + maximum*getDeviceSize() + getEmSize() * emMaximum + ptMaximum ); + if ( dMaximum<=dMinimum ) dMaximum= dMinimum+1; + if ( dMinimum!=oldmin ) firePropertyChange( "dMinimum", oldmin, dMinimum ); + if ( dMaximum!=oldmax ) firePropertyChange( "dMaximum", oldmax, dMaximum ); + if ( dMinimum!=oldmin || dMaximum!=oldmax ) fireUpdate(); + canvas.repaint(); + } + + /** + * returns the pixel position of the minimum of the Row/Column. This is + * the left side of a column and the top of a row. + * @return the pixel position (pixel=point for now) + */ + public int getDMinimum() { + if ( canvas==null && parent==null ) { + String type= isWidth ? "column" : "row"; + throw new RuntimeException("null "+type+", "+type+" was not set before layout"); + } + return dMinimum; + } + + /** + * returns the pixel position of the maximum of the Row/Column. This is + * the right side of a column and the bottom of a row. + * @return the pixel position (pixel=point for now) + */ + public int getDMaximum() { + if ( canvas==null && parent==null ) { + String type= isWidth ? "column" : "row"; + throw new RuntimeException("null "+type+", "+type+" was not set before layout"); + } + return dMaximum; + } + + /** + * return the normal position control of the top/left. + * @return + */ + public double getMinimum() { + return minimum; + } + + public double getMaximum() { + return maximum; + } + + private void setPosition(double minimum, double maximum) { + double oldMin = this.minimum; + double oldMax = this.maximum; + double doldMin = this.minimum; + double doldMax = this.maximum; + this.minimum = Math.min(minimum, maximum); + this.maximum = Math.max(maximum, maximum); + revalidate(); + if (oldMin != this.minimum) { + firePropertyChange("minimum", oldMin, this.minimum); + } + if (oldMax != this.maximum) { + firePropertyChange("maximum", oldMax, this.maximum); + } + } + + public void setDPosition( int minimum, int maximum) { + int pmin= getParentMin(); + int pmax= getParentMax(); + int em= getEmSize(); + int length= pmax - pmin; + double nmin= ( minimum - emMinimum * em - ptMinimum - pmin ) / length; + double nmax= ( maximum - emMaximum * em - ptMaximum - pmin ) / length; + setPosition( nmin, nmax ); + } + + public void setMaximum(double maximum) { + if (maximum == this.maximum) { + return; + } + if (maximum < this.minimum) { + setPosition(maximum, this.minimum); + } else { + double oldValue = this.maximum; + this.maximum = maximum; + firePropertyChange("maximum", oldValue, maximum); + revalidate(); + } + } + + /** + * set the new pixel position of the bottom/right boundry. em and pt offsets + * are not modified, and the normal position is recalculated. + * @param maximum + */ + public void setDMaximum( int maximum) { + int pmin= getParentMin(); + int pmax= getParentMax(); + int em= getEmSize(); + int length= pmax - pmin; + double n= ( maximum - emMaximum * em - ptMaximum ) / length; + setMaximum( n ); + } + + public void setMinimum( double minimum) { + if (minimum == this.minimum) { + return; + } + if (minimum > this.maximum) { + setPosition(this.maximum, minimum); + } else { + double oldValue = this.minimum; + this.minimum = minimum; + firePropertyChange("minimum", oldValue, minimum); + revalidate(); + } + } + + /** + * set the new pixel position of the top/left boundry. em and pt offsets + * are not modified, and the normal position is recalculated. + * @param minimum + */ + public void setDMinimum( int minimum) { + int pmin= getParentMin(); + int pmax= getParentMax(); + int em= getEmSize(); + int length= pmax - pmin; + double n= ( minimum - emMinimum * em - ptMinimum ) / length; + setMinimum( n ); + } + + public DasCanvas getParent() { + return this.canvas; + } + + public void setParent(DasCanvas parent) { + this.canvas= parent; + fireUpdate(); + } + + private boolean valueIsAdjusting= false; + + protected synchronized MutatorLock mutatorLock() { + return new MutatorLock() { + public void lock() { + if ( isValueIsAdjusting() ) { + System.err.println("lock is already set!"); + } + valueIsAdjusting= true; + } + public void unlock() { + valueIsAdjusting= false; + propertyChangeDelegate.firePropertyChange( "mutatorLock", "locked", "unlocked"); + } + }; + } + + + public void addpwUpdateListener(DasUpdateListener l) { + listenerList.add(DasUpdateListener.class, l); + } + + public void removepwUpdateListener(DasUpdateListener l) { + listenerList.remove(DasUpdateListener.class, l); + } + + protected void fireUpdate() { + DasUpdateEvent e = new DasUpdateEvent(this); + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length-2; i>=0; i-=2) { + if (listeners[i]==DasUpdateListener.class) { + ((DasUpdateListener)listeners[i+1]).update(e); + } + } + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeDelegate.addPropertyChangeListener(listener); + } + + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeDelegate.addPropertyChangeListener(propertyName, listener); + } + + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeDelegate.addPropertyChangeListener(propertyName, listener); + } + + protected void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { + firePropertyChange(propertyName, + (oldValue ? Boolean.TRUE : Boolean.FALSE), + (newValue ? Boolean.TRUE : Boolean.FALSE)); + } + + protected void firePropertyChange(String propertyName, int oldValue, int newValue) { + firePropertyChange(propertyName, new Integer(oldValue), new Integer(newValue)); + } + + protected void firePropertyChange(String propertyName, long oldValue, long newValue) { + firePropertyChange(propertyName, new Long(oldValue), new Long(newValue)); + } + + protected void firePropertyChange(String propertyName, float oldValue, float newValue) { + firePropertyChange(propertyName, new Float(oldValue), new Float(newValue)); + } + + protected void firePropertyChange(String propertyName, double oldValue, double newValue) { + firePropertyChange(propertyName, new Double(oldValue), new Double(newValue)); + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + propertyChangeDelegate.firePropertyChange(propertyName, oldValue, newValue); + } + + protected int getDeviceSize() { + return getParentMax() - getParentMin(); + } + + public static java.awt.Rectangle toRectangle(DasRow row, DasColumn column) { + int xmin=column.getDMinimum(); + int ymin=row.getDMinimum(); + return new java.awt.Rectangle(xmin,ymin, + column.getDMaximum()-xmin, + row.getDMaximum()-ymin); + } + + public String toString() { + //String format="%.1f%%%+.1fem%+dpt"; + //String smin= String.format(format, minimum*100, emMinimum, ptMinimum ); + //String smax= String.format(format, maximum*100, emMaximum, ptMaximum ); + return getClass().getName() + " " + formatLayoutStr(this, true) + "," +formatLayoutStr(this, false) + " [dpos=" + getDMinimum() + "," + getDMaximum() + "]"; + } + + /** + * returns true if ( getDMinimum() <= x ) && ( x <= getDMaximum() ); + * @param x the pixel position + * @return + */ + public boolean contains( int x ) { + return ( getDMinimum() <= x ) && ( x <= getDMaximum() ); + } + + /** + * returns pixel position (device position) of the the middle of the row or column + * @return + */ + public int getDMiddle() { + return (getDMinimum()+getDMaximum())/2; + } + + /** + * property emMinimum, the em (font height * 2/3) offset from the minimum + */ + private double emMinimum; + + /** + * return the em offset that controls the position of the top/left boundry. + * @return + */ + public double getEmMinimum() { + return this.emMinimum; + } + + public void setEmMinimum(double emMinimum) { + double oldValue= this.emMinimum; + this.emMinimum = emMinimum; + firePropertyChange("emMinimum", oldValue, emMinimum); + revalidate(); + } + + /** + * property emMaximum, the em (font height * 2/3) offset from the maximum + */ + private double emMaximum; + + public double getEmMaximum() { + return this.emMaximum; + } + + public void setEmMaximum(double emMaximum) { + double oldValue= this.emMaximum; + this.emMaximum = emMaximum; + firePropertyChange("emMaximum", oldValue, emMaximum); + revalidate(); + } + + private int ptMinimum; + + /** + * return the points offset that controls the position of the top/left boundry. + * @return + */ + public int getPtMinimum() { + return this.ptMinimum; + } + + public void setPtMinimum(int ptMinimum) { + int oldValue= this.ptMinimum; + this.ptMinimum = ptMinimum; + firePropertyChange("ptMinimum", oldValue, ptMinimum); + revalidate(); + } + + /** + * property ptMaximum, the pixel offset from the maximum + */ + private int ptMaximum=0; + + /** + * return the points offset that controls the position of the bottom/right boundry. + * @return + */ + public int getPtMaximum() { + return this.ptMaximum; + } + public void setPtMaximum(int ptMaximum) { + int oldValue= this.ptMaximum; + this.ptMaximum = ptMaximum; + firePropertyChange("ptMaximum", oldValue, ptMaximum); + revalidate(); + } + + public DasDevicePosition getParentDevicePosition() { + return this.parent; + } + + public boolean isValueIsAdjusting() { + return valueIsAdjusting; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasEventsIndicator.java b/dasCore/src/main/java/org/das2/graph/DasEventsIndicator.java new file mode 100755 index 000000000..2bfcddd4d --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasEventsIndicator.java @@ -0,0 +1,71 @@ +/* + * DasEventsIndicator.java + * + * Created on April 6, 2004, 10:39 AM + */ + +package org.das2.graph; + +import org.das2.dataset.DataSetUpdateEvent; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.DataSetUpdateListener; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; + +/** + * + * @author Jeremy + * + * The DasEventsIndicator takes a DataSetDescriptor that produces VectorDataSets with a plane + * "xTagWidth." This plane should consist of Datums with the same Units as the xAxis offset Units. + * The y values of the DataSet will be toString'ed and this value will be the tooltip of the bar. + */ +public class DasEventsIndicator extends DasPlot implements DataSetUpdateListener { + EventsRenderer renderer; + + /** Creates a new instance of DasEventsIndicator */ + public DasEventsIndicator( DataSetDescriptor dsd, DasAxis axis, DasAxis yAxis, String planeId ) { + super( axis, yAxis ); + renderer= new EventsRenderer( dsd ) ; + addRenderer( renderer ); + } + + public DasEventsIndicator(DasAxis axis, DasAxis yAxis, String planeId ) { + super( axis, yAxis ); + renderer= new EventsRenderer( ) ; + addRenderer( renderer ); + } + + + /** Creates a new instance of a DasEventsIndicator that doesn't auto-request data + * + */ + public static DasEventsIndicator create(DasAxis xAxis, String planeId){ + DasAxis yAxis= new DasAxis( new DatumRange( 0,1, Units.dimensionless ), DasAxis.VERTICAL ); + yAxis.setVisible(false); + return new DasEventsIndicator(xAxis, yAxis, planeId ); + } + + /** + * This method replaces the old constructor. This is unavoidable + */ + public static DasEventsIndicator create( DataSetDescriptor dsd, DasAxis axis, String planeId ) { + DasAxis yAxis= new DasAxis( new DatumRange( 0,1, Units.dimensionless ), DasAxis.VERTICAL ); + yAxis.setVisible(false); + return new DasEventsIndicator( dsd, axis, yAxis, planeId ); + } + + + public void setDataSetDescriptor( DataSetDescriptor dsd ) { + renderer.setDataSetDescriptor(dsd); + } + + public DataSetDescriptor getDataSetDescriptor( ) { + return renderer.getDataSetDescriptor(); + } + + public void dataSetUpdated(DataSetUpdateEvent e) { + ((XAxisDataLoader)renderer.getDataLoader()).dataSetUpdated(e); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasLabelAxis.java b/dasCore/src/main/java/org/das2/graph/DasLabelAxis.java new file mode 100755 index 000000000..78c7105ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasLabelAxis.java @@ -0,0 +1,470 @@ +/* File: DasLabelAxis.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import org.das2.datum.DatumUtil; +import org.das2.util.GrannyTextRenderer; +import org.das2.datum.format.DatumFormatter; +import org.das2.graph.event.DasUpdateEvent; +import org.das2.graph.event.DasUpdateListener; +import java.awt.*; +import java.text.DecimalFormat; + + +public class DasLabelAxis extends DasAxis implements DasUpdateListener { + + DecimalFormat nfy = null; + DatumVector labels = null; + double[] labelValues = null; + Units labelUnits = null; + int[] labelPositions = null; + DatumFormatter df = null; + int indexMinimum; // first label to be displayed + int indexMaximum; + /** Holds value of property outsidePadding. */ + private int outsidePadding = 5; + /** Holds value of property floppyltemSpacing. */ + private boolean floppyItemSpacing = false; + // last label to be displayed + private void setLabels(DatumVector labels) { + if (labels.getLength() == 0) { + throw new IllegalArgumentException("labels can not be a zero-length array!"); + } + this.labels = labels; + this.labelPositions = new int[labels.getLength()]; + indexMinimum = 0; + indexMaximum = labels.getLength() - 1; + labelUnits = labels.getUnits(); + labelValues = labels.toDoubleArray(labelUnits); + this.df = DatumUtil.bestFormatter(labels); + } + + /** + * vg1pws needed a way to explicitly set this. + */ + public void setLabelFormatter(DatumFormatter df) { + this.df = df; + } + + protected DasLabelAxis(DatumVector labels, DataRange dataRange, int orientation) { + super(dataRange, orientation); + setLabels(labels); + getDataRange().addUpdateListener(this); + } + + public DasLabelAxis(DatumVector labels, int orientation) { + super(labels.get(0), labels.get(labels.getLength() - 1), orientation, false); + setLabels(labels); + getDataRange().addUpdateListener(this); + } + + public int[] getLabelPositions() { + return this.labelPositions; + } + + private void updateTickPositions() { + if (isDisplayable()) { + int nlabel = indexMaximum - indexMinimum + 1; + + int size; + int min; + + double interItemSpacing; + + if (this.getOrientation() == DasAxis.HORIZONTAL) { + size = getColumn().getWidth() - outsidePadding * 2; + interItemSpacing = ((float) size) / nlabel; + if (!floppyItemSpacing) { + interItemSpacing = (int) interItemSpacing; + } + min = (getColumn().getDMinimum() + outsidePadding + (int) (interItemSpacing / 2)); + } else { + size = getRow().getHeight() - outsidePadding * 2; + interItemSpacing = -1 * ((float) size) / nlabel; + if (!floppyItemSpacing) { + interItemSpacing = (int) interItemSpacing; + } + min = getRow().getDMaximum() - outsidePadding + (int) (interItemSpacing / 2); + } + + for (int i = 0; i < labelPositions.length; i++) { + labelPositions[i] = min + (int) (interItemSpacing * ((i - indexMinimum) + 0)); + } + + firePropertyChange("labelPositions", null, labelPositions); + } + } + + @Override + public Datum findTick(Datum xDatum, double direction, boolean minor) { + // somehow tickv.minor is set to non-zero, and Axis.findTick gets messed up. + // This is a work-around... + return xDatum; + } + + @Override + public void updateTickV() { + //super.updateTickV(); + updateTickPositions(); + } + + @Override + public TickVDescriptor getTickV() { + TickVDescriptor result = new TickVDescriptor(); + result.units = getUnits(); + result.tickV = labels.getSubVector(indexMinimum, indexMaximum + 1); + result.minorTickV = DatumVector.newDatumVector(new double[0], result.units); + return result; + } + + @Override + public double transform(double value, Units units) { + if (units != this.labelUnits) { + throw new IllegalArgumentException("units don't match"); + } + int iclose = findClosestIndex(labelValues, value); + return labelPositions[iclose]; + } + + private int findClosestIndex(int[] data, int searchFor) { + int iclose = 0; + double closest = Math.abs(data[iclose] - searchFor); + for (int i = 0; i < labelPositions.length; i++) { + double c1 = Math.abs(data[i] - searchFor); + if (c1 < closest) { + iclose = i; + closest = c1; + } + } + return iclose; + } + + private int findClosestIndex(double[] data, double searchFor) { + int iclose = 0; + double closest = Math.abs(data[iclose] - searchFor); + for (int i = 0; i < labelPositions.length; i++) { + double c1 = Math.abs(data[i] - searchFor); + if (c1 < closest) { + iclose = i; + closest = c1; + } + } + return iclose; + } + + @Override + public Datum invTransform(double d) { + int iclose = findClosestIndex(labelPositions, (int) d); + return labels.get(iclose); + } + + @Override + protected boolean rangeIsAcceptable(DatumRange dr) { + return true; + } + + @Override + protected String tickFormatter( Datum t ) { + return df.format( t ); + } + + @Override + protected String[] tickFormatter(DatumVector tickV, DatumRange datumRange) { + return df.axisFormat( tickV, datumRange ); + } + + public int getInterItemSpace() { + return (int) Math.abs(transform(labels.get(1)) - transform(labels.get(0))); + } + + public int getItemMin(Datum d) { + Units units = d.getUnits(); + double value = d.doubleValue(units); + + int iclose = findClosestIndex(labelValues, units.convertDoubleTo(this.getUnits(), value)); + int tickPosition = labelPositions[iclose]; + int w = getInterItemSpace(); + return tickPosition - w / 2; + } + + public int getItemMax(Datum d) { + int w = getInterItemSpace(); + return getItemMin(d) + w; + } + + public DasAxis createAttachedAxis(DasRow row, DasColumn column) { + DasLabelAxis result = new DasLabelAxis(labels, getDataRange(), this.getOrientation()); + return result; + } + + @Override + public DasAxis createAttachedAxis(int orientation) { + return new DasLabelAxis(labels, getDataRange(), orientation); + } + + @Override + public void update(DasUpdateEvent e) { + double minimum = getDataRange().getMinimum(); + double maximum = getDataRange().getMaximum(); + if (getDataRange().getUnits() != this.labelUnits) { + throw new IllegalArgumentException("units don't match"); + } + + this.indexMinimum = findClosestIndex(labelValues, minimum); + this.indexMaximum = findClosestIndex(labelValues, maximum); + + if (this.indexMinimum > this.indexMaximum) { + int t = this.indexMinimum; + this.indexMaximum = this.indexMinimum; + this.indexMinimum = t; + } + + + } + + @Override + protected void paintHorizontalAxis(java.awt.Graphics2D g) { + boolean bottomTicks = (getOrientation() == BOTTOM || isOppositeAxisVisible()); + boolean bottomTickLabels = (getOrientation() == BOTTOM && isTickLabelsVisible()); + boolean bottomLabel = (getOrientation() == BOTTOM && !axisLabel.equals("")); + boolean topTicks = (getOrientation() == TOP || isOppositeAxisVisible()); + boolean topTickLabels = (getOrientation() == TOP && isTickLabelsVisible()); + boolean topLabel = (getOrientation() == TOP && !axisLabel.equals("")); + + int topPosition = getRow().getDMinimum() - 1; + int bottomPosition = getRow().getDMaximum(); + int DMax = getColumn().getDMaximum(); + int DMin = getColumn().getDMinimum(); + + Font labelFont = getTickLabelFont(); + + double dataMax = dataRange.getMaximum(); + double dataMin = dataRange.getMinimum(); + + TickVDescriptor ticks = getTickV(); + + if (bottomTicks) { + g.drawLine(DMin, bottomPosition, DMax, bottomPosition); + } + if (topTicks) { + g.drawLine(DMin, topPosition, DMax, topPosition); + } + + int tickLengthMajor = labelFont.getSize() * 2 / 3; + int tickLengthMinor = tickLengthMajor / 2; + int tickLength; + + String[] llabels= tickFormatter( ticks.tickV, getDatumRange() ); + + for (int i = 0; i < ticks.tickV.getLength(); i++) { + Datum d = ticks.tickV.get(i); + int w = getInterItemSpace(); + int tickPosition = (int) Math.floor(transform(d) + 0.5) - w / 2; + tickLength = tickLengthMajor; + if (bottomTicks) { + g.drawLine(getItemMin(d), bottomPosition, getItemMin(d), bottomPosition + tickLength); + if (i == ticks.tickV.getLength() - 1) { + g.drawLine(getItemMax(d), bottomPosition, getItemMax(d), bottomPosition + tickLength); + } + if (bottomTickLabels) { + drawLabel(g, d, llabels[i], i, tickPosition + w / 2, bottomPosition + tickLength); + } + } + if (topTicks) { + g.drawLine(getItemMin(d), topPosition, getItemMin(d), topPosition - tickLength); + if (i == ticks.tickV.getLength() - 1) { + g.drawLine(getItemMax(d), topPosition, getItemMax(d), topPosition - tickLength); + } + if (topTickLabels) { + drawLabel(g, d, llabels[i], i, tickPosition + w / 2, topPosition - tickLength); + } + } + } + + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D) g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g2, axisLabel); + int titleWidth = (int) gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (bottomLabel) { + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = bottomPosition + titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + if (topLabel) { + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = topPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + g2.dispose(); + } + } + + @Override + protected void paintVerticalAxis(java.awt.Graphics2D g) { + boolean leftTicks = (getOrientation() == LEFT || isOppositeAxisVisible()); + boolean leftTickLabels = (getOrientation() == LEFT && isTickLabelsVisible()); + boolean leftLabel = (getOrientation() == LEFT && !axisLabel.equals("")); + boolean rightTicks = (getOrientation() == RIGHT || isOppositeAxisVisible()); + boolean rightTickLabels = (getOrientation() == RIGHT && isTickLabelsVisible()); + boolean rightLabel = (getOrientation() == RIGHT && !axisLabel.equals("")); + + int leftPosition = getColumn().getDMinimum() - 1; + int rightPosition = getColumn().getDMaximum(); + int DMax = getRow().getDMaximum(); + int DMin = getRow().getDMinimum(); + + Font labelFont = getTickLabelFont(); + + double dataMax = dataRange.getMaximum(); + double dataMin = dataRange.getMinimum(); + + TickVDescriptor ticks = getTickV(); + + if (leftTicks) { + g.drawLine(leftPosition, DMin, leftPosition, DMax); + } + if (rightTicks) { + g.drawLine(rightPosition, DMin, rightPosition, DMax); + } + + int tickLengthMajor = labelFont.getSize() * 2 / 3; + int tickLengthMinor = tickLengthMajor / 2; + int tickLength; + + String[] llabels= tickFormatter( ticks.tickV, getDatumRange() ); + for (int i = 0; i < ticks.tickV.getLength(); i++) { + Datum datum = ticks.tickV.get(i); + + int w = getInterItemSpace(); + int tickPosition = (getItemMax(datum) + getItemMin(datum)) / 2 - g.getFontMetrics().getAscent() / 5; + if (getRow().contains(tickPosition)) { + tickLength = tickLengthMajor; + if (leftTicks) { + if (i == ticks.tickV.getLength() - 1) { + g.drawLine(leftPosition, getItemMin(datum), leftPosition - tickLength, getItemMin(datum)); + } + g.drawLine(leftPosition, getItemMax(datum), leftPosition - tickLength, getItemMax(datum)); + if (leftTickLabels) { + drawLabel(g, datum, llabels[i], i, leftPosition - tickLength, tickPosition); + } + } + if (rightTicks) { + if (i == ticks.tickV.getLength() - 1) { + g.drawLine(rightPosition, getItemMin(datum), rightPosition + tickLength, getItemMin(datum)); + } + g.drawLine(rightPosition, getItemMax(datum), rightPosition + tickLength, getItemMax(datum)); + if (rightTickLabels) { + drawLabel(g, datum, llabels[i], i, rightPosition + tickLength, tickPosition); + } + } + } + + } + + if (!axisLabel.equals("")) { + Graphics2D g2 = (Graphics2D) g.create(); + int titlePositionOffset = getTitlePositionOffset(); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(g2, axisLabel); + int titleWidth = (int) gtr.getWidth(); + int baseline; + int leftEdge; + g2.setFont(getLabelFont()); + if (leftLabel) { + g2.rotate(-Math.PI / 2.0); + leftEdge = -DMax + (DMax - DMin - titleWidth) / 2; + baseline = leftPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + if (rightLabel) { + g2.rotate(Math.PI / 2.0); + leftEdge = DMin + (DMax - DMin - titleWidth) / 2; + baseline = -rightPosition - titlePositionOffset; + gtr.draw(g2, (float) leftEdge, (float) baseline); + } + g2.dispose(); + } + + } + + /** Getter for property outsidePadding. + * @return Value of property outsidePadding. + * + */ + public int getOutsidePadding() { + return this.outsidePadding; + } + + /** Setter for property outsidePadding. + * @param outsidePadding New value of property outsidePadding. + * + */ + public void setOutsidePadding(int outsidePadding) { + int oldValue = outsidePadding; + this.outsidePadding = outsidePadding; + firePropertyChange("setOutsidePadding", oldValue, outsidePadding); + updateTickPositions(); + update(); + } + + /** Getter for property floppyltemSpacing. + * @return Value of property floppyltemSpacing. + * + */ + public boolean isFloppyItemSpacing() { + return this.floppyItemSpacing; + } + + /** Setter for property floppyltemSpacing. + * @param floppyItemSpacing New value of property floppyltemSpacing. + * + */ + public void setFloppyItemSpacing(boolean floppyItemSpacing) { + boolean oldValue = this.floppyItemSpacing; + this.floppyItemSpacing = floppyItemSpacing; + firePropertyChange("floppyItemSpacing", oldValue, floppyItemSpacing); + updateTickPositions(); + update(); + } + + @Override + public java.awt.geom.AffineTransform getAffineTransform(Memento memento, java.awt.geom.AffineTransform at) { + return at; + //equals doesn't seem to work + /*if ( this.getMemento().equals( memento ) ) { + return at; + } else { + return null; + }*/ + } +} diff --git a/dasCore/src/main/java/org/das2/graph/DasNumberFormatter.java b/dasCore/src/main/java/org/das2/graph/DasNumberFormatter.java new file mode 100644 index 000000000..ecdebe9da --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasNumberFormatter.java @@ -0,0 +1,192 @@ +/* File: DasNumberFormatter.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * + * @author jessica + */ +package org.das2.graph; + +import java.text.DecimalFormat; + +public class DasNumberFormatter { + + /** Creates a new instance of DecFormat */ + public DasNumberFormatter() { + } + + /** + * @param args the command line arguments + */ + + + public static void main(String[] args) { + DecimalFormat dFormatter = new DecimalFormat(); + DecimalFormat dF = new DecimalFormat(); + String result; + double [] b = {123.123, 12.1234, 1.2345, 1234.12, 123.1}; + + dFormatter = createDF(b); + for(int i=0; i max_Num) + max_Num = testNum; + + + //if length of current fracDigit is larger than max, set max to current length + if(len_fracDigits > max_fracVal) + max_fracVal=len_fracDigits; + + intDigits = value[i].substring(0, index); //String of beginning integer digits of # + len_intDigits = intDigits.length(); //get the length of String of integer digits + + //if length of current intDigit is larger than max, set max to current length + if(len_intDigits > max_intVal) + max_intVal=len_intDigits; + }//for (int i=0; i last.length()){ + last = last + 0; + }//while + myPattern = "###." + last; + decFormat = new DecimalFormat(myPattern); + }//if (max_fracVal < 5) + + //if the largest fractional digit in series is 0, then return pattern ###, + //ex. 1.0 will be 1, 10.0 will be 10, it will drop unneeded decimal point and trailing 0 + if(max_Num == 0){ + myPattern = "###"; + decFormat = new DecimalFormat(myPattern); + }//(max_Num == 0) + + //scientific notation + int newMax = len_intDigits + len_fracDigits; + + if(max_intVal > 5){ + String subPattern = ""; + + for(int k=0; k<=newMax; k++){ + subPattern = subPattern + '#'; + if(newMax==3) + subPattern = subPattern + '.'; + }//for(int k=0; k<==nexMax; k++) + + //myPattern = subPattern; + myPattern = "###.####E0"; + decFormat = new DecimalFormat(myPattern); + decFormat.setMaximumIntegerDigits(2); + decFormat.setMinimumIntegerDigits(2); + }//if(max_intVal > 5) + + return decFormat; + }//static DecimalFormat createDF (double [] a) + + + + static final double LOG_10 = Math.log(10); + + static DecimalFormat makeNewDF(double [] a){ + DecimalFormat df = new DecimalFormat(); + String pattern; + int len_a = a.length; + int intDigit, fracDigit, first; + double intBase, fracBase, digit, second; + + for(int k=0; k + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.event.MouseModule; +import org.das2.event.HorizontalRangeSelectorMouseModule; +import org.das2.event.LengthDragRenderer; +import org.das2.event.DisplayDataMouseModule; +import org.das2.event.CrossHairMouseModule; +import org.das2.event.BoxZoomMouseModule; +import org.das2.event.VerticalRangeSelectorMouseModule; +import org.das2.event.ZoomPanMouseModule; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.VectorUtil; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.CancelledOperationException; +import org.das2.DasProperties; +import org.das2.util.GrannyTextRenderer; +import org.das2.util.DasExceptionHandler; +import org.das2.util.DnDSupport; +import java.beans.PropertyChangeEvent; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.PropertyEditor; +import org.das2.datum.Datum; +import org.das2.dasml.FormBase; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumVector; +import org.das2.graph.dnd.TransferableRenderer; +import org.das2.system.DasLogger; +import java.awt.image.BufferedImage; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import javax.swing.event.MouseInputAdapter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.event.*; +import java.awt.geom.AffineTransform; +import java.beans.PropertyChangeListener; +import java.io.*; +import java.io.IOException; +import java.nio.channels.*; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import org.das2.DasException; +import org.das2.DasNameException; +import org.das2.DasPropertyException; + +public class DasPlot extends DasCanvasComponent implements DataSetConsumer { + + public static String PROP_TITLE = "title"; + protected DataSetDescriptor dataSetDescriptor; + protected DataSet Data; + private DasAxis xAxis; + private DasAxis yAxis; + DasAxis.Memento xmemento; + DasAxis.Memento ymemento; + protected String offsetTime = ""; + protected String plotTitle = ""; + protected double[] psym_x; + protected double[] psym_y; + protected RebinListener rebinListener = new RebinListener(); + protected PropertyChangeListener ticksListener = new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + if (drawGrid || drawMinorGrid) { + //invalidateCacheImage(); + } + } + }; + DnDSupport dndSupport; + final static Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + private JMenuItem editRendererMenuItem; + /** + * cacheImage is a cached image that all the renderers have drawn on. This + * relaxes the need for renderers' render method to execute in + * animation-interactive time. + */ + boolean cacheImageValid = false; + BufferedImage cacheImage; + Rectangle cacheImageBounds; + /** + * property preview. If set, the cache image may be scaled to reflect + * the new axis position in animation-interactive time. + */ + boolean preview = false; + private int repaintCount = 0; + private int paintComponentCount = 0; + + public DasPlot(DasAxis xAxis, DasAxis yAxis) { + super(); + + addMouseListener(new MouseInputAdapter() { + + public void mousePressed(MouseEvent e) { + //if (e.getButton() == MouseEvent.BUTTON3) { + if (editRendererMenuItem != null) { + //TODO: check out SwingUtilities, I think this is wrong: + int ir = findRendererAt(getX() + e.getX(), getY() + e.getY()); + editRendererMenuItem.setText("Renderer Properties"); + if (ir > -1) { + editRendererMenuItem.setEnabled(true); + Renderer r = renderers.get(ir); + if (r instanceof Displayable) { + Displayable d = (Displayable) r; + editRendererMenuItem.setIcon(d.getListIcon()); + } else { + editRendererMenuItem.setIcon(null); + } + setFocusRenderer(r); + } else { + editRendererMenuItem.setEnabled(false); + editRendererMenuItem.setIcon(null); + setFocusRenderer(null); + } + } + //} + } + }); + + setOpaque(false); + + this.renderers = new ArrayList(); + this.xAxis = xAxis; + if (xAxis != null) { + if (!xAxis.isHorizontal()) { + throw new IllegalArgumentException("xAxis is not horizontal"); + } + xAxis.addPropertyChangeListener("dataMinimum", rebinListener); + xAxis.addPropertyChangeListener("dataMaximum", rebinListener); + xAxis.addPropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + xAxis.addPropertyChangeListener("log", rebinListener); + xAxis.addPropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + this.yAxis = yAxis; + if (yAxis != null) { + if (yAxis.isHorizontal()) { + throw new IllegalArgumentException("yAxis is not vertical"); + } + yAxis.addPropertyChangeListener("dataMinimum", rebinListener); + yAxis.addPropertyChangeListener("dataMaximum", rebinListener); + yAxis.addPropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + yAxis.addPropertyChangeListener("log", rebinListener); + xAxis.addPropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + + if (!"true".equals(DasApplication.getProperty("java.awt.headless", "false"))) { + addDefaultMouseModules(); + } + } + + /** + * returns the Renderer with the current focus. Clicking on a trace sets the focus. + */ + protected Renderer focusRenderer = null; + public static final String PROP_FOCUSRENDERER = "focusRenderer"; + + public Renderer getFocusRenderer() { + return focusRenderer; + } + + public void setFocusRenderer(Renderer focusRenderer) { + Renderer oldFocusRenderer = this.focusRenderer; + this.focusRenderer = focusRenderer; + firePropertyChange(PROP_FOCUSRENDERER, oldFocusRenderer, focusRenderer); + } + + private void drawLegend(Graphics2D graphics) { + + int em = (int) getEmSize(); + int msgx, msgy; + + Color backColor = GraphUtil.getRicePaperColor(); + Rectangle boundRect = null; + Rectangle mrect; + + em = (int) getEmSize(); + + msgx = xAxis.getColumn().getDMiddle() + em; + msgy = yAxis.getRow().getDMinimum() + em; + + int maxIconWidth = 0; + for (int i = 0; i < legendElements.size(); i++) { + LegendElement le = legendElements.get(i); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(graphics, String.valueOf(le.label)); // protect from nulls, which seems to happen + mrect = gtr.getBounds(); + mrect.translate(msgx, msgy); + maxIconWidth = Math.max(maxIconWidth, le.icon.getIconWidth()); + mrect.height = Math.max(mrect.height, le.icon.getIconHeight()); + if (boundRect == null) { + boundRect = mrect; + } else { + boundRect.add(mrect); + } + msgy += mrect.getHeight(); + } + + mrect = new Rectangle(boundRect); + int iconColumnWidth = maxIconWidth + em / 4; + mrect.width += iconColumnWidth; + mrect.x = xAxis.getColumn().getDMaximum() - em - mrect.width; + boundRect.x= mrect.x; + + graphics.setColor(backColor); + graphics.fillRoundRect( mrect.x - em / 4 , mrect.y, mrect.width + em / 2, mrect.height, 5, 5); + graphics.setColor(getForeground()); + graphics.drawRoundRect( mrect.x - em / 4 , mrect.y, mrect.width + em / 2, mrect.height, 5, 5); + + msgx = xAxis.getColumn().getDMaximum() - boundRect.width - em ; + msgy = yAxis.getRow().getDMinimum() + em; + + for (int i = 0; i < legendElements.size(); i++) { + LegendElement le = legendElements.get(i); + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(graphics, String.valueOf(le.label)); // protect from nulls, which seems to happen + mrect = gtr.getBounds(); + mrect.translate(msgx, msgy); + gtr.draw(graphics, msgx, msgy); + Rectangle imgBounds= new Rectangle( msgx - (le.icon.getIconWidth() + em / 4), + msgy - (int) (mrect.getHeight() / 2 + le.icon.getIconHeight() / 2), + le.icon.getIconWidth(), le.icon.getIconHeight() ); + graphics.drawImage( le.icon.getImage(), imgBounds.x, imgBounds.y, null ); + msgy += mrect.getHeight(); + mrect.add( imgBounds ); + le.bounds= mrect; + } + + } + + private void drawMessages(Graphics2D graphics) { + Font font0 = graphics.getFont(); + int msgem = (int) Math.max(8, font0.getSize2D() / 2); + graphics.setFont(font0.deriveFont((float) msgem)); + int em = (int) getEmSize(); + + int msgx = xAxis.getColumn().getDMinimum() + em; + int msgy = yAxis.getRow().getDMinimum() + em; + + Color warnColor = new Color(255, 255, 100, 200); + Color errorColor = new Color(255, 140, 140, 200); + for (int i = 0; i < messages.size(); i++) { + MessageDescriptor message = (MessageDescriptor) messages.get(i); + + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(graphics, String.valueOf(message.text)); // protect from nulls, which seems to happen + Rectangle mrect = gtr.getBounds(); + mrect.translate(msgx, msgy); + Color backColor = GraphUtil.getRicePaperColor(); + if (message.messageType == DasPlot.WARNING) { + backColor = warnColor; + } else if (message.messageType == DasPlot.ERROR) { + backColor = errorColor; + } + graphics.setColor(backColor); + graphics.fillRoundRect(mrect.x - em / 4, mrect.y, mrect.width + em / 2, mrect.height, 5, 5); + graphics.setColor(getForeground()); + graphics.drawRoundRect(mrect.x - em / 4, mrect.y, mrect.width + em / 2, mrect.height, 5, 5); + gtr.draw(graphics, msgx, msgy); + message.bounds = mrect; + + msgy += gtr.getHeight() + msgem / 2; + } + } + + private void maybeDrawGrid(Graphics2D plotGraphics) { + Color gridColor = new Color(128, 128, 128, 70); + Color minorGridColor = new Color(128, 128, 128, 40); + + if (drawMinorGrid) { + DatumVector xticks = null; + DatumVector yticks = null; + if (getXAxis().isVisible()) { + xticks = getXAxis().getTickV().getMinorTicks(); + } + if (getYAxis().isVisible()) { + yticks = getYAxis().getTickV().getMinorTicks(); + } + plotGraphics.setColor(minorGridColor); + drawGrid(plotGraphics, xticks, yticks); + } + + if (drawGrid) { + DatumVector xticks = null; + DatumVector yticks = null; + if (getXAxis().isVisible()) { + xticks = getXAxis().getTickV().getMajorTicks(); + } + if (getYAxis().isVisible()) { + yticks = getYAxis().getTickV().getMajorTicks(); + } + plotGraphics.setColor(gridColor); + drawGrid(plotGraphics, xticks, yticks); + } + + } + + private void drawCacheImage(Graphics2D plotGraphics) { + + /* clear all the messages */ + messages = new ArrayList(); + legendElements = new ArrayList(); + + if (!gridOver) { + maybeDrawGrid(plotGraphics); + } + drawContent(plotGraphics); + + boolean noneActive = true; + for (int i = 0; i < renderers.size(); i++) { + Renderer rend = renderers.get(i); + if (rend.isActive()) { + logger.finest("rendering #" + i + ": " + rend); + rend.render(plotGraphics, xAxis, yAxis, new NullProgressMonitor()); + noneActive = false; + } + } + + if (gridOver) { + maybeDrawGrid(plotGraphics); + } + if (renderers.size() == 0) { + postMessage(null, "(no renderers)", DasPlot.INFO, null, null); + logger.fine("dasPlot has no renderers"); + } else if (noneActive) { + postMessage(null, "(no active renderers)", DasPlot.INFO, null, null); + } + } + + /** + * return the index of the renderer at canvas location (x,y), or -1 if + * no renderer is found at the position. + */ + public int findRendererAt(int x, int y) { + for (int i = 0; messages != null && i < messages.size(); i++) { + MessageDescriptor message = (MessageDescriptor) messages.get(i); + if (message.bounds.contains(x, y) && message.renderer != null) { + int result = this.renderers.indexOf(message.renderer); + if (result != -1) { + return result; + } + } + } + + for (int i = 0; legendElements != null && i < legendElements.size(); i++) { + LegendElement legendElement = legendElements.get(i); + if (legendElement.bounds.contains(x, y) && legendElement.renderer != null) { + int result = this.renderers.indexOf( legendElement.renderer ); + if (result != -1) { + return result; + } + } + } + + for (int i = renderers.size() - 1; i >= 0; i--) { + Renderer rend = renderers.get(i); + if (rend.isActive() && rend.acceptContext(x, y)) { + return i; + } + } + return -1; + } + + private Action getEditAction() { + return new AbstractAction("Renderer Properties") { + + public void actionPerformed(ActionEvent e) { + Point p = getDasMouseInputAdapter().getMousePressPosition(); + int i = findRendererAt(p.x + getX(), p.y + getY()); + if (i > -1) { + Renderer rend = getRenderer(i); + PropertyEditor editor = new PropertyEditor(rend); + editor.showDialog(DasPlot.this); + } + } + }; + } + + private void addDefaultMouseModules() { + + HorizontalRangeSelectorMouseModule hrs = + new HorizontalRangeSelectorMouseModule(this, xAxis); + mouseAdapter.addMouseModule(hrs); + hrs.addDataRangeSelectionListener(xAxis); + // TODO: support setYAxis, setXAxis + + VerticalRangeSelectorMouseModule vrs = + new VerticalRangeSelectorMouseModule(this, yAxis); + mouseAdapter.addMouseModule(vrs); + vrs.addDataRangeSelectionListener(yAxis); + // TODO: support setYAxis, setXAxis + + MouseModule x = CrossHairMouseModule.create(this); + mouseAdapter.addMouseModule(x); + + mouseAdapter.setSecondaryModule(new ZoomPanMouseModule(this, getXAxis(), getYAxis())); + + mouseAdapter.setPrimaryModule(x); + + mouseAdapter.addMouseModule(new BoxZoomMouseModule(this, null, getXAxis(), getYAxis())); + // TODO: support setYAxis, setXAxis. + + x = new MouseModule(this, new LengthDragRenderer(this, null, null), "Length"); + mouseAdapter.addMouseModule(x); + + x = new DisplayDataMouseModule(this); + mouseAdapter.addMouseModule(x); + + editRendererMenuItem = new JMenuItem(getEditAction()); + getDasMouseInputAdapter().addMenuItem(editRendererMenuItem); + + if (DasApplication.hasAllPermission()) { + JMenuItem dumpMenuItem = new JMenuItem(DUMP_TO_FILE_ACTION); + mouseAdapter.addMenuItem(dumpMenuItem); + } + + + } + public Action DUMP_TO_FILE_ACTION = new AbstractAction("Dump Data Set to File") { + + public void actionPerformed(ActionEvent e) { + if (renderers.isEmpty()) { + return; + } + Renderer renderer = renderers.get(0); + JFileChooser chooser = new JFileChooser(); + int result = chooser.showSaveDialog(DasPlot.this); + if (result == JFileChooser.APPROVE_OPTION) { + File selected = chooser.getSelectedFile(); + try { + FileChannel out = new FileOutputStream(selected).getChannel(); + DataSet ds = renderer.getDataSet(); + if (ds instanceof TableDataSet) { + TableUtil.dumpToAsciiStream((TableDataSet) ds, out); + } else if (ds instanceof VectorDataSet) { + VectorUtil.dumpToAsciiStream((VectorDataSet) ds, out); + } + } catch (IOException ioe) { + DasExceptionHandler.handle(ioe); + } + } + } + }; + + public DataSet getDataSet() { + // TODO: get rid of this!!! + return Data; + } + + public DataSet getConsumedDataSet() { + // TODO: get rid of this!!! + return Data; + } + + public DataSet getData() { + // TODO: get rid of this!!! + return Data; + } + + public void setXAxis(DasAxis xAxis) { + Object oldValue = this.xAxis; + Container parent = getParent(); + if (this.xAxis != null) { + DasProperties.getLogger().fine("setXAxis upsets the dmia"); + if (parent != null) { + parent.remove(this.xAxis); + } + xAxis.removePropertyChangeListener("dataMinimum", rebinListener); + xAxis.removePropertyChangeListener("dataMaximum", rebinListener); + xAxis.removePropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + xAxis.removePropertyChangeListener("log", rebinListener); + xAxis.removePropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + this.xAxis = xAxis; + if (xAxis != null) { + if (!xAxis.isHorizontal()) { + throw new IllegalArgumentException("xAxis is not horizontal"); + } + if (parent != null) { + parent.add(this.xAxis); + parent.validate(); + } + xAxis.addPropertyChangeListener("dataMinimum", rebinListener); + xAxis.addPropertyChangeListener("dataMaximum", rebinListener); + xAxis.addPropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + xAxis.addPropertyChangeListener("log", rebinListener); + xAxis.addPropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + if (xAxis != oldValue) { + firePropertyChange("xAxis", oldValue, xAxis); + } + } + + /** + * Attaches a new axis to the plot. The old axis is disconnected, and + * the plot will listen to the new axis. Also, the row and column for the + * axis is set, but this might change in the future. null appears to be + * a valid input as well. + * + * TODO: plot does not seem to be responding to changes in the axis. + * (goes grey because updatePlotImage is never done. + */ + public void setYAxis(DasAxis yAxis) { + Object oldValue = this.yAxis; + logger.fine("setYAxis(" + yAxis.getName() + "), removes " + this.yAxis); + Container parent = getParent(); + if (this.yAxis != null) { + DasProperties.getLogger().fine("setYAxis upsets the dmia"); + if (parent != null) { + parent.remove(this.yAxis); + } + this.yAxis.removePropertyChangeListener("dataMinimum", rebinListener); + this.yAxis.removePropertyChangeListener("dataMaximum", rebinListener); + this.yAxis.removePropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + this.yAxis.removePropertyChangeListener("log", rebinListener); + this.yAxis.removePropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + this.yAxis = yAxis; + if (yAxis != null) { + if (yAxis.isHorizontal()) { + throw new IllegalArgumentException("yAxis is not vertical"); + } + yAxis.setRow(getRow()); + yAxis.setColumn(getColumn()); + if (parent != null) { + parent.add(this.yAxis); + parent.validate(); + } + yAxis.addPropertyChangeListener("dataMinimum", rebinListener); + yAxis.addPropertyChangeListener("dataMaximum", rebinListener); + yAxis.addPropertyChangeListener(DasAxis.PROPERTY_DATUMRANGE, rebinListener); + yAxis.addPropertyChangeListener("log", rebinListener); + yAxis.addPropertyChangeListener(DasAxis.PROPERTY_TICKS, ticksListener); + } + if (yAxis != oldValue) { + firePropertyChange("yAxis", oldValue, yAxis); + } + } + + @Override + protected void updateImmediately() { + paintImmediately(0, 0, getWidth(), getHeight()); + logger.finer("DasPlot.updateImmediately"); + for (int i = 0; i < renderers.size(); i++) { + Renderer rend = renderers.get(i); + rend.update(); + } + } + + /* + * returns the AffineTransform to transform data from the last updatePlotImage call + * axes (if super.updatePlotImage was called), or null if the transform is not possible. + */ + protected AffineTransform getAffineTransform(DasAxis xAxis, DasAxis yAxis) { + if (xmemento == null) { + logger.fine("unable to calculate AT, because old transform is not defined."); + return null; + } else { + AffineTransform at = new AffineTransform(); + at = xAxis.getAffineTransform(xmemento, at); + at = yAxis.getAffineTransform(ymemento, at); + return at; + } + } + + /** + * at.isIdentity returns false if the at is not precisely identity, + * so this allows for some fuzz. + */ + private boolean isIdentity(AffineTransform at) { + return at.isIdentity() || + (Math.abs(at.getScaleX() - 1.00) < 0.001 && Math.abs(at.getScaleY() - 1.00) < 0.001 && Math.abs(at.getTranslateX()) < 0.001 && Math.abs(at.getTranslateY()) < 0.001); + } + + private void paintInvalidScreen(Graphics atGraphics, AffineTransform at) { + Color c = GraphUtil.getRicePaperColor(); + atGraphics.setColor(c); + int x = getColumn().getDMinimum(); + int y = getRow().getDMinimum(); + + atGraphics.fillRect(x - 1, y - 1, getWidth(), getHeight()); + final boolean debug = false; + if (debug) { + atGraphics.setColor(Color.DARK_GRAY); + + atGraphics.drawString("moment...", x + 10, y + 10); + String atstr = GraphUtil.getATScaleTranslateString(at); + + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(atGraphics, atstr); + gtr.draw(atGraphics, x + 10, y + 10 + atGraphics.getFontMetrics().getHeight()); + } + + logger.finest(" using cacheImage with ricepaper to invalidate"); + } + + protected void paintComponent(Graphics graphics1) { + + if (!getCanvas().isPrintingThread() && !EventQueue.isDispatchThread()) { + throw new RuntimeException("not event thread: " + Thread.currentThread().getName()); + } + //paintComponentCount++; + logger.finer("entering DasPlot.paintComponent"); + + if ( getCanvas().isPrintingThread() ) logger.fine("* printing thread *"); + + int x = getColumn().getDMinimum(); + int y = getRow().getDMinimum(); + + int xSize = getColumn().getDMaximum() - x; + int ySize = getRow().getDMaximum() - y; + + logger.fine("DasPlot clip=" + graphics1.getClip() + " @ "+getX()+","+getY() ); + + Rectangle clip = graphics1.getClipBounds(); + if (clip != null && (clip.y + getY()) >= (y + ySize)) { + logger.finer("returning because clip indicates nothing to be done."); + return; + } + + boolean disableImageCache= false; + + Graphics2D graphics = (Graphics2D) graphics1; + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + graphics.translate(-getX(), -getY()); + if (cacheImageValid && !getCanvas().isPrintingThread() && !disableImageCache ) { + + Graphics2D atGraphics = (Graphics2D) graphics.create(); + + AffineTransform at = getAffineTransform(xAxis, yAxis); + if (at == null || (preview == false && !isIdentity(at))) { + atGraphics.drawImage(cacheImage, cacheImageBounds.x, cacheImageBounds.y, cacheImageBounds.width, cacheImageBounds.height, this); + paintInvalidScreen(atGraphics, at); + + } else { + String atDesc; + NumberFormat nf = new DecimalFormat("0.00"); + atDesc = GraphUtil.getATScaleTranslateString(at); + + if (!at.isIdentity()) { + logger.finest(" using cacheImage w/AT " + atDesc); + atGraphics.transform(at); + } else { + logger.finest(" using cacheImage " + cacheImageBounds + " " + xmemento + " " + ymemento ); + } + + //int reduceHeight= getRow().getDMinimum() - clip.y; + //if ( reduceHeight>0 ) { + // clip.y+= reduceHeight; + // clip.height-= reduceHeight; + //} + //clip.translate( getX(), getY() ); + //atGraphics.setClip(clip); + atGraphics.drawImage(cacheImage, cacheImageBounds.x, cacheImageBounds.y, cacheImageBounds.width, cacheImageBounds.height, this); + //atGraphics.setClip(null); + //return; + //graphics.drawString( "cacheImage "+atDesc, getWidth()/2, getHeight()/2 ); + + } + + atGraphics.dispose(); + + } else { + + synchronized (this) { + Graphics2D plotGraphics; + if ( getCanvas().isPrintingThread() || disableImageCache ) { + plotGraphics = (Graphics2D) graphics.create(x - 1, y - 1, xSize + 2, ySize + 2); + cacheImageBounds = new Rectangle(); + cacheImageBounds.width = getWidth(); + cacheImageBounds.height = getHeight(); + cacheImageBounds.x = x - 1; + cacheImageBounds.y = y - 1; + logger.finest(" printing thread, drawing"); + } else { + if (overSize) { + cacheImageBounds = new Rectangle(); + cacheImageBounds.width = 16 * getWidth() / 10; + cacheImageBounds.height = getHeight(); + cacheImage = new BufferedImage(cacheImageBounds.width, cacheImageBounds.height, BufferedImage.TYPE_4BYTE_ABGR); + cacheImageBounds.x = x - 3 * getWidth() / 10; + cacheImageBounds.y = y - 1; + + } else { + cacheImageBounds = new Rectangle(); + cacheImageBounds.width = getWidth(); + cacheImageBounds.height = getHeight(); + cacheImage = new BufferedImage(cacheImageBounds.width, cacheImageBounds.height, BufferedImage.TYPE_4BYTE_ABGR); + cacheImageBounds.x = x - 1; + cacheImageBounds.y = y - 1; + } + plotGraphics = (Graphics2D) cacheImage.getGraphics(); + plotGraphics.setBackground(getBackground()); + plotGraphics.setColor(getForeground()); + plotGraphics.setRenderingHints(org.das2.DasProperties.getRenderingHints()); + if (overSize) { + plotGraphics.translate(x - cacheImageBounds.x - 1, y - cacheImageBounds.y - 1); + } + + logger.finest(" rebuilding cacheImage"); + + } + + plotGraphics.translate(-x + 1, -y + 1); + + drawCacheImage(plotGraphics); + + //if (overSize) { + // postMessage(null, "Over size on", DasPlot.INFO, null, null); + //} + } + + + if ( !disableImageCache && !getCanvas().isPrintingThread() ) { + cacheImageValid = true; + //clip.y= Math.max( clip.y, getRow().getDMinimum() ); + //clip.translate( getX(), getY() ); + //graphics.setClip(clip); + graphics.drawImage(cacheImage, cacheImageBounds.x, cacheImageBounds.y, cacheImageBounds.width, cacheImageBounds.height, this); + //graphics.drawString( "new image", getWidth()/2, getHeight()/2 ); + //graphics.setClip(null); + + xmemento = xAxis.getMemento(); + ymemento = yAxis.getMemento(); + + logger.finest("recalc cacheImage, xmemento=" + xmemento + " ymemento=" + ymemento ); + } + } + + graphics.setColor(getForeground()); + graphics.drawRect(x - 1, y - 1, xSize + 1, ySize + 1); + + if (plotTitle != null && plotTitle.length() != 0) { + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setAlignment(GrannyTextRenderer.CENTER_ALIGNMENT); + gtr.setString(graphics, plotTitle); + int titleWidth = (int) gtr.getWidth(); + int titleX = x + (xSize - titleWidth) / 2; + int titleY = y - (int) gtr.getDescent() - (int) gtr.getAscent() / 2; + gtr.draw(graphics, (float) titleX, (float) titleY); + } + + Font font0= graphics.getFont(); + // --- draw messages --- + if (messages.size() > 0) { + drawMessages(graphics); + } + + if (legendElements.size() > 0) { + drawLegend(graphics); + } + + graphics.setFont(font0); + + graphics.translate(getX(), getY()); + + getDasMouseInputAdapter().paint(graphics); + + } + + private class MessageDescriptor { + + /** + * the renderer posting the text, or null if the plot owns the text + */ + Renderer renderer; + String text; + int messageType; + Datum x; + Datum y; + Rectangle bounds; // stores the drawn boundaries of the message for context menu. + + MessageDescriptor(Renderer renderer, String text, int messageType, Datum x, Datum y) { + this.renderer = renderer; + this.text = text; + this.messageType = messageType; + this.x = x; + this.y = y; + } + } + + private class LegendElement { + + ImageIcon icon; + Renderer renderer; + String label; + Rectangle bounds; + + LegendElement(ImageIcon icon, Renderer rend, String label) { + this.icon = icon; + this.renderer = rend; + this.label = label; + } + } + public static final int INFO = 0; + public static final int WARNING = 1; + public static final int ERROR = 2; + List messages; + List legendElements; + + /** + * Notify user of an exception, in the context of the plot. A position in + * the data space may be specified to locate the text within the data context. + * Note either or both x or y may be null. Messages must only be posted while the + * Renderer's render method is called. All messages are cleared before the render + * step. + * + * + * @param renderer identifies the renderer posting the exception + * @param text the text to be displayed, may contain granny text. + * @param messageType DasPlot.INFORMATION_MESSAGE, DasPlot.WARNING_MESSAGE, or DasPlot.ERROR_MESSAGE. + * @param x if non-null, the location on the x axis giving context for the text. + * @param y if non-null, the location on the y axis giving context for the text. + */ + public void postMessage(Renderer renderer, String message, int messageType, Datum x, Datum y) { + messages.add(new MessageDescriptor(renderer, message, messageType, x, y)); + } + + /** + * notify user of an exception, in the context of the plot. + */ + public void postException(Renderer renderer, Exception exception) { + String message = exception.getMessage(); + if (message == null) { + message = String.valueOf(exception); + } + int errorLevel = ERROR; + if (exception instanceof CancelledOperationException) { + errorLevel = INFO; + if (exception.getMessage() == null) { + message = "Operation Cancelled"; + } + } + postMessage(renderer, message, errorLevel, null, null); + } + + /** + * + * @param renderer identifies the renderer adding to the legend + * @param icon if non-null, an icon to use. If null, the renderer's icon is used. + * @param pos integer order parameter, and also identifies item. + * @param message String message to display. + */ + public void addToLegend(Renderer renderer, ImageIcon icon, int pos, String message) { + legendElements.add(new LegendElement( icon, renderer, message )); + } + + private void drawGrid(Graphics2D g, DatumVector xticks, DatumVector yticks) { + int xmin = this.cacheImageBounds.x; + int xmax = this.cacheImageBounds.x + this.cacheImageBounds.width; + int ymin = this.cacheImageBounds.y; + int ymax = this.cacheImageBounds.y + this.cacheImageBounds.height; + + if (yticks != null && yticks.getUnits().isConvertableTo(yAxis.getUnits())) { + for (int i = 0; i < yticks.getLength(); i++) { + int y = (int) yAxis.transform(yticks.get(i)); + g.drawLine(xmin, y, xmax, y); + } + } + if (xticks != null && xticks.getUnits().isConvertableTo(xAxis.getUnits())) { + for (int i = 0; i < xticks.getLength(); i++) { + int x = (int) xAxis.transform(xticks.get(i)); + g.drawLine(x, ymin, x, ymax); + } + } + } + + protected void drawContent(Graphics2D g) { + // override me to add to the axes. + } + + public void resize() { + logger.fine("resize DasPlot"); + if (isDisplayable()) { + Rectangle oldBounds= getBounds(); + Rectangle bounds = new Rectangle(); + bounds.x = getColumn().getDMinimum() - 1; + bounds.y = getRow().getDMinimum() - 1; + bounds.width = getColumn().getDMaximum() - bounds.x + 1; + bounds.height = getRow().getDMaximum() - bounds.y + 1; + if (!getTitle().equals("")) { + GrannyTextRenderer gtr = new GrannyTextRenderer(); + gtr.setString(getFont(), getTitle()); + + int titleHeight = (int) gtr.getHeight() + (int) gtr.getAscent() / 2; + + bounds.y -= titleHeight; + bounds.height += titleHeight; + } + // TODO check bounds.height<10 + logger.fine("DasPlot setBounds "+bounds); + if ( !oldBounds.equals(bounds) ) { + invalidateCacheImage(); + } + setBounds(bounds); + } + } + + /** Sets the title which will be displayed above this plot. + * + * @param t The new title for this plot. + */ + public void setTitle(String t) { + Object oldValue = plotTitle; + plotTitle = t; + if (getCanvas() != null) { + FontMetrics fm = getFontMetrics(getCanvas().getFont()); + int titleHeight = fm.getHeight() + fm.getHeight() / 2; + resize(); + invalidateCacheImage(); + } + if (t != oldValue) { + firePropertyChange(PROP_TITLE, oldValue, t); + } + } + + /** Returns the title of this plot. + * + * @see #setTitle(String) + * + * @return The plot title + */ + public String getTitle() { + return plotTitle; + } + private List renderers = null; + + public DasAxis getXAxis() { + return this.xAxis; + } + + public DasAxis getYAxis() { + return this.yAxis; + } + + /** Getter for property dataSetDescriptor. + * @return Value of property dataSetDescriptor. + */ + public DataSetDescriptor getDataSetDescriptor() { + return dataSetDescriptor; + } + + /** Setter for property dataSetDescriptor. + * @param dataSetDescriptor New value of property dataSetDescriptor. + */ + public void setDataSetDescriptor(DataSetDescriptor dataSetDescriptor) { + this.dataSetDescriptor = dataSetDescriptor; + markDirty(); + } + + public void setData(DataSet ds) { + // TODO: get rid of this!!! + this.Data = ds; + markDirty(); + } + + protected class RebinListener implements java.beans.PropertyChangeListener { + + public void propertyChange(java.beans.PropertyChangeEvent e) { + // logger.fine("rebin listener got property change: "+e.getNewValue()); + markDirty(); + DasPlot.this.update(); + } + } + + protected void installComponent() { + super.installComponent(); + if (xAxis != null) { + getCanvas().add(xAxis, getRow(), getColumn()); + } + if (yAxis != null) { + getCanvas().add(yAxis, getRow(), getColumn()); + } + Renderer[] r = getRenderers(); + for (int index = 0; index < r.length; index++) { + r[index].installRenderer(); + } + if (!"true".equals(DasApplication.getProperty("java.awt.headless", "false"))) { + dndSupport = new PlotDnDSupport(getCanvas().dndSupport); + } + } + + protected void uninstallComponent() { + super.uninstallComponent(); + if (xAxis != null && xAxis.getCanvas() != null) { + xAxis.getCanvas().remove(xAxis); + } + if (yAxis != null && yAxis.getCanvas() != null) { + yAxis.getCanvas().remove(yAxis); + } + Renderer[] r = getRenderers(); + for (int index = 0; index < r.length; index++) { + r[index].uninstallRenderer(); + } + } + + public void addRenderer(Renderer rend) { + logger.fine("addRenderer(" + rend + ")"); + if (rend.parent != null) { + rend.parent.removeRenderer(rend); + } + renderers.add(rend); + rend.parent = this; + if (getCanvas() != null) { + rend.installRenderer(); + } + rend.update(); + invalidateCacheImage(); + } + + public void removeRenderer(Renderer rend) { + if (getCanvas() != null) { + rend.uninstallRenderer(); + } + renderers.remove(rend); + rend.parent = null; + invalidateCacheImage(); + + } + + public static DasPlot createDummyPlot() { + DasAxis xAxis = new DasAxis(Datum.create(-10), Datum.create(10), DasAxis.HORIZONTAL); + DasAxis yAxis = new DasAxis(Datum.create(-10), Datum.create(10), DasAxis.VERTICAL); + DasPlot result = new DasPlot(xAxis, yAxis); + return result; + } + + public static DasPlot createPlot(DatumRange xrange, DatumRange yrange) { + DasAxis xAxis = new DasAxis(xrange, DasAxis.HORIZONTAL); + DasAxis yAxis = new DasAxis(yrange, DasAxis.VERTICAL); + DasPlot result = new DasPlot(xAxis, yAxis); + return result; + } + + public Renderer getRenderer(int index) { + return renderers.get(index); + } + + public Renderer[] getRenderers() { + return (Renderer[]) renderers.toArray(new Renderer[0]); + } + + public static DasPlot processPlotElement(Element element, FormBase form) throws org.das2.DasPropertyException, org.das2.DasNameException, DasException, java.text.ParseException { + String name = element.getAttribute("name"); + + DasRow row = (DasRow) form.checkValue(element.getAttribute("row"), DasRow.class, ""); + DasColumn column = (DasColumn) form.checkValue(element.getAttribute("column"), DasColumn.class, ""); + + DasAxis xAxis = null; + DasAxis yAxis = null; + DasColorBar colorbar = null; + + //Get the axes + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + if (node.getNodeName().equals("xAxis")) { + xAxis = processXAxisElement((Element) node, row, column, form); + } else if (node.getNodeName().equals("yAxis")) { + yAxis = processYAxisElement((Element) node, row, column, form); + } else if (node.getNodeName().equals("zAxis")) { + colorbar = processZAxisElement((Element) node, row, column, form); + } + + } + } + + if (xAxis == null) { + xAxis = (DasAxis) form.checkValue(element.getAttribute("xAxis"), DasAxis.class, " or "); + } + if (yAxis == null) { + yAxis = (DasAxis) form.checkValue(element.getAttribute("yAxis"), DasAxis.class, " or "); + } + + DasPlot plot = new DasPlot(xAxis, yAxis); + + if (element.getNodeName().equals("spectrogram")) { + SpectrogramRenderer rend = new SpectrogramRenderer(null, colorbar); + plot.addRenderer(rend); + } + + plot.setTitle(element.getAttribute("title")); + plot.setDasName(name); + plot.setRow(row); + plot.setColumn(column); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, plot); + + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + if (node.getNodeName().equals("renderers")) { + processRenderersElement((Element) node, plot, form); + } + } + } + + return plot; + } + + private static DasAxis processXAxisElement(Element element, DasRow row, DasColumn column, FormBase form) throws org.das2.DasPropertyException, org.das2.DasNameException, DasException, java.text.ParseException { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + Element e = (Element) node; + if (node.getNodeName().equals("axis")) { + DasAxis axis = DasAxis.processAxisElement(e, form); + if (!axis.isHorizontal()) { + axis.setOrientation(DasAxis.HORIZONTAL); + } + return axis; + } else if (node.getNodeName().equals("timeaxis")) { + DasAxis axis = DasAxis.processTimeaxisElement(e, form); + if (!axis.isHorizontal()) { + axis.setOrientation(DasAxis.HORIZONTAL); + } + return axis; + } else if (node.getNodeName().equals("attachedaxis")) { + DasAxis axis = DasAxis.processAttachedaxisElement(e, form); + if (!axis.isHorizontal()) { + axis.setOrientation(DasAxis.HORIZONTAL); + } + return axis; + } + } + } + return null; + } + + private static DasAxis processYAxisElement(Element element, DasRow row, DasColumn column, FormBase form) throws org.das2.DasPropertyException, org.das2.DasNameException, org.das2.DasException, java.text.ParseException { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + Element e = (Element) node; + if (node.getNodeName().equals("axis")) { + DasAxis axis = DasAxis.processAxisElement(e, form); + if (axis.isHorizontal()) { + axis.setOrientation(DasAxis.VERTICAL); + } + return axis; + } else if (node.getNodeName().equals("timeaxis")) { + DasAxis axis = DasAxis.processTimeaxisElement(e, form); + if (axis.isHorizontal()) { + axis.setOrientation(DasAxis.VERTICAL); + } + return axis; + } else if (node.getNodeName().equals("attachedaxis")) { + DasAxis axis = DasAxis.processAttachedaxisElement(e, form); + if (axis.isHorizontal()) { + axis.setOrientation(DasAxis.VERTICAL); + } + return axis; + } + } + } + return null; + } + + private static DasColorBar processZAxisElement(Element element, DasRow row, DasColumn column, FormBase form) throws DasPropertyException, DasNameException, DasException, java.text.ParseException { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + if (node.getNodeName().equals("colorbar")) { + return DasColorBar.processColorbarElement((Element) node, form); + } + } + } + return null; + } + + private static void processRenderersElement(Element element, DasPlot parent, FormBase form) throws org.das2.DasPropertyException, org.das2.DasNameException, org.das2.DasException, java.text.ParseException { + NodeList children = element.getChildNodes(); + for (int index = 0; index < children.getLength(); index++) { + Node node = children.item(index); + if (node instanceof Element) { + if (node.getNodeName().equals("spectrogram")) { + parent.addRenderer(SpectrogramRenderer.processSpectrogramElement((Element) node, parent, form)); + } else if (node.getNodeName().equals("lineplot")) { + parent.addRenderer(SymbolLineRenderer.processLinePlotElement((Element) node, parent, form)); + } + } + } + } + + public Element getDOMElement(Document document) { + + Element element = document.createElement("plot"); + element.setAttribute("name", getDasName()); + element.setAttribute("row", getRow().getDasName()); + element.setAttribute("column", getColumn().getDasName()); + element.setAttribute("title", getTitle()); + + Element xAxisChild = document.createElement("xAxis"); + Element xAxisElement = getXAxis().getDOMElement(document); + xAxisElement.removeAttribute("orientation"); + if (xAxisElement.getAttribute("row").equals(getRow().getDasName())) { + xAxisElement.removeAttribute("row"); + } + if (xAxisElement.getAttribute("column").equals(getColumn().getDasName())) { + xAxisElement.removeAttribute("column"); + } + xAxisChild.appendChild(xAxisElement); + element.appendChild(xAxisChild); + + Element yAxisChild = document.createElement("yAxis"); + Element yAxisElement = getYAxis().getDOMElement(document); + yAxisElement.removeAttribute("orientation"); + if (yAxisElement.getAttribute("row").equals(getRow().getDasName())) { + yAxisElement.removeAttribute("row"); + } + if (yAxisElement.getAttribute("column").equals(getColumn().getDasName())) { + yAxisElement.removeAttribute("column"); + } + yAxisChild.appendChild(yAxisElement); + element.appendChild(yAxisChild); + + Renderer[] renderers = getRenderers(); + if (renderers.length > 0) { + Element renderersChild = document.createElement("renderers"); + for (int index = 0; index < renderers.length; index++) { + renderersChild.appendChild(renderers[index].getDOMElement(document)); + } + element.appendChild(renderersChild); + } + return element; + } + + public static DasPlot createNamedPlot(String name) { + DasAxis xAxis = DasAxis.createNamedAxis(null); + xAxis.setOrientation(DasAxis.BOTTOM); + DasAxis yAxis = DasAxis.createNamedAxis(null); + yAxis.setOrientation(DasAxis.LEFT); + DasPlot plot = new DasPlot(xAxis, yAxis); + if (name == null) { + name = "plot_" + Integer.toHexString(System.identityHashCode(plot)); + } + try { + plot.setDasName(name); + } catch (org.das2.DasNameException dne) { + org.das2.util.DasExceptionHandler.handle(dne); + } + return plot; + } + + private class PlotDnDSupport extends org.das2.util.DnDSupport { + + PlotDnDSupport(org.das2.util.DnDSupport parent) { + super(DasPlot.this, DnDConstants.ACTION_COPY_OR_MOVE, parent); + } + + public void drop(DropTargetDropEvent dtde) { + } + + protected int canAccept(DataFlavor[] flavors, int x, int y, int action) { + for (int i = 0; i < flavors.length; i++) { + if (flavors[i].equals(TransferableRenderer.RENDERER_FLAVOR)) { + return action; + } + } + return -1; + } + + protected void done() { + } + + protected boolean importData(Transferable t, int x, int y, int action) { + boolean success = false; + try { + Renderer r = (Renderer) t.getTransferData(TransferableRenderer.RENDERER_FLAVOR); + addRenderer(r); + revalidate(); + success = true; + } catch (UnsupportedFlavorException ufe) { + } catch (IOException ioe) { + } + return success; + } + + protected Transferable getTransferable(int x, int y, int action) { + return null; + } + + protected void exportDone(Transferable t, int action) { + } + } + + public Shape getActiveRegion() { + return getBounds(); + } + + /** Potentially coalesce an event being posted with an existing + * event. This method is called by EventQueue.postEvent + * if an event with the same ID as the event to be posted is found in + * the queue (both events must have this component as their source). + * This method either returns a coalesced event which replaces + * the existing event (and the new event is then discarded), or + * null to indicate that no combining should be done + * (add the second event to the end of the queue). Either event + * parameter may be modified and returned, as the other one is discarded + * unless null is returned. + *

    + * This implementation of coalesceEvents coalesces + * DasUpdateEvents, returning the existingEvent parameter + * + * @param existingEvent the event already on the EventQueue + * @param newEvent the event being posted to the + * EventQueue + * @return a coalesced event, or null indicating that no + * coalescing was done + */ + protected AWTEvent coalesceEvents(AWTEvent existingEvent, AWTEvent newEvent) { + if (existingEvent instanceof DasRendererUpdateEvent && newEvent instanceof DasRendererUpdateEvent) { + DasRendererUpdateEvent e1 = (DasRendererUpdateEvent) existingEvent; + DasRendererUpdateEvent e2 = (DasRendererUpdateEvent) newEvent; + if (e1.getRenderer() == e2.getRenderer()) { + return existingEvent; + } else { + return null; + } + } + return super.coalesceEvents(existingEvent, newEvent); + } + + /** Processes events occurring on this component. By default this + * method calls the appropriate + * process<event type>Event + * method for the given class of event. + *

    Note that if the event parameter is null + * the behavior is unspecified and may result in an + * exception. + * + * @param e the event + * @see java.awt.Component#processComponentEvent + * @see java.awt.Component#processFocusEvent + * @see java.awt.Component#processKeyEvent + * @see java.awt.Component#processMouseEvent + * @see java.awt.Component#processMouseMotionEvent + * @see java.awt.Component#processInputMethodEvent + * @see java.awt.Component#processHierarchyEvent + * @see java.awt.Component#processMouseWheelEvent + * @see #processDasUpdateEvent + */ + protected void processEvent(AWTEvent e) { + if (e instanceof DasRendererUpdateEvent) { + DasRendererUpdateEvent drue = (DasRendererUpdateEvent) e; + drue.getRenderer().updateImmediately(); + cacheImageValid = false; + repaint(); + } else { + super.processEvent(e); + } + } + + public void repaint() { + super.repaint(); + repaintCount++; + } + + public synchronized void invalidateCacheImage() { + cacheImageValid = false; + repaint(); + } + + void markDirty() { + logger.finer("DasPlot.markDirty"); + super.markDirty(); + repaint(); + } + /** + * property drawGrid. If true, faint grey lines continue the axis major + * ticks across the plot. + */ + private boolean drawGrid = false; + + /** + * Getter for property drawGrid. If true, faint grey lines continue the axis major + * ticks across the plot. + * @return Value of property drawGrid. + */ + public boolean isDrawGrid() { + return this.drawGrid; + } + + /** + * Setter for property drawGrid. If true, faint grey lines continue the axis major + * ticks across the plot. + * @param drawGrid New value of property drawGrid. + */ + public void setDrawGrid(boolean drawGrid) { + boolean bOld = this.drawGrid; + this.drawGrid = drawGrid; + this.invalidateCacheImage(); + this.repaint(); + + if (bOld != drawGrid) { + firePropertyChange(PROP_DRAWGRID, bOld, drawGrid); + } + } + public static final String PROP_DRAWGRID = "drawGrid"; + private boolean drawMinorGrid; + public static final String PROP_DRAWMINORGRID = "drawMinorGrid"; + + /** + * Get the value of drawMinorGrid + * + * @return the value of drawMinorGrid + */ + public boolean isDrawMinorGrid() { + return this.drawMinorGrid; + } + + /** + * Set the value of drawMinorGrid + * + * @param newdrawMinorGrid new value of drawMinorGrid + */ + public void setDrawMinorGrid(boolean newdrawMinorGrid) { + boolean olddrawMinorGrid = drawMinorGrid; + this.drawMinorGrid = newdrawMinorGrid; + this.invalidateCacheImage(); + this.repaint(); + firePropertyChange(PROP_DRAWMINORGRID, olddrawMinorGrid, newdrawMinorGrid); + } + protected boolean gridOver = true; + public static final String PROP_GRIDOVER = "gridOver"; + + public boolean isGridOver() { + return gridOver; + } + + public void setGridOver(boolean gridOver) { + boolean oldGridOver = this.gridOver; + this.gridOver = gridOver; + this.invalidateCacheImage(); + this.repaint(); + firePropertyChange(PROP_GRIDOVER, oldGridOver, gridOver); + } + + public void setPreviewEnabled(boolean preview) { + this.preview = preview; + } + + public boolean isPreviewEnabled() { + return this.preview; + } + + public void setVisible(boolean visible) { + super.setVisible(visible); + xAxis.setVisible(visible); + yAxis.setVisible(visible); + } + protected boolean overSize = false; + public static final String PROP_OVERSIZE = "overSize"; + + public boolean isOverSize() { + return overSize; + } + + public void setOverSize(boolean overSize) { + boolean oldOverSize = this.overSize; + this.overSize = overSize; + invalidateCacheImage(); + firePropertyChange(PROP_OVERSIZE, oldOverSize, overSize); + } + + /** + * returns the rectange that renderers should paint so that when they + * are asked to render, they have everything pre-rendered. This is + * the same as the axis bounds them oversize is turned off. + * + * @return + */ + protected Rectangle getUpdateImageBounds() { + int x = getColumn().getDMinimum(); + int y = getRow().getDMinimum(); + cacheImageBounds = new Rectangle(); + cacheImageBounds.width = 16 * getWidth() / 10; + cacheImageBounds.height = getHeight(); + cacheImageBounds.x = x - 3 * getWidth() / 10; + cacheImageBounds.y = y - 1; + return cacheImageBounds; + } + + /** + * returns the position of the cacheImage in the canvas frame of reference. + * @return Rectangle + */ + protected Rectangle getCacheImageBounds() { + return cacheImageBounds; + } + + /** Reload and repaint the current data. + * Internally this tells the renderers to reload and repaint. + */ + @Override + public void reload(){ + for(Renderer rndr: renderers){ + rndr.reload(); + } + } +} diff --git a/dasCore/src/main/java/org/das2/graph/DasRendererUpdateEvent.java b/dasCore/src/main/java/org/das2/graph/DasRendererUpdateEvent.java new file mode 100644 index 000000000..3ee5150ab --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasRendererUpdateEvent.java @@ -0,0 +1,47 @@ +/* File: DasRendererUpdateEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on June 24, 2004, 4:48 PM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.event.DasUpdateEvent; + + +/** + * + * @author eew + */ +public class DasRendererUpdateEvent extends DasUpdateEvent { + + private Renderer renderer; + + /** Creates a new instance of DasRendererUpdateEvent */ + public DasRendererUpdateEvent(DasPlot parent, Renderer r) { + super(parent); + renderer = r; + } + + public Renderer getRenderer() { + return renderer; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DasRow.java b/dasCore/src/main/java/org/das2/graph/DasRow.java new file mode 100755 index 000000000..453454636 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasRow.java @@ -0,0 +1,222 @@ +/* File: DasRow.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.NameContext; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.dasml.FormBase; +import java.text.ParseException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** A DasRow defines a horizontal region of a DasCanvas. + * + * When placing {@link DasCanvasComponent}s, a DasRow and DasColumn are typically supplied + * to define the position of the component on the canvas. + * + * @see DasCanvas + * @see DasColumn + * @author jbf + */ +public class DasRow extends DasDevicePosition { + + /** Create a new row region on a {@link DasCanvas}, simplified constructor. + * + * @param parent The {@link DasCanvas} on which to define a horizontal region. + *

    + * @param top Sets the position the top of the Row, 0.0 = Top of the canvas, + * 1.0 = Bottom of the canvas. + *

    + * @param bottom Sets the position the bottom of the Row, 0.0 = Top of Parent object, + * 1.0 = Bottom of Parent object. + *

    + */ + public DasRow( DasCanvas parent, double top, double bottom) { + super(parent,top,bottom,false ); + } + + /** Create a new row region on a {@link DasCanvas}. + * + * @param canvas Set positions relative to a DasCanvas parent. Should be NULL + * if parent is non-null. + *

    + * @param parent Set positions relative to DasRow parent. Should be null if + * canvas is non-null. + *

    + * @param nMin Sets the position the top of the Row, 0.0 = Top of Parent object, + * 1.0 = Bottom of Parent object. + *

    + * @param nMax Sets the position the bottom of the Row, 0.0 = Top of Parent object, + * 1.0 = Bottom of Parent object. + *

    + * @param emMin Adjust the top boarder position in EM's (An EM is the size of a + * capital M in the current Canvas font.) Positive numbers move the + * top border down, Negative numbers move it up. + *

    + * @param emMax Adjust the bottom boarder position in EM's (An EM is the size of a + * capital M in the current Canvas font.) Positive numbers move the + * bottom border down, Negative numbers move it up. + *

    + * @param ptMin Similar to emMin, but move the top border in pixels. + * + *

    + * @param ptMax Similar to emMax, but move the bottom border in pixels. + */ + public DasRow( DasCanvas canvas, DasRow parent, double nMin, double nMax, + double emMin, double emMax, int ptMin, int ptMax ) { + super( canvas, false, parent, nMin, nMax, emMin, emMax, ptMin, ptMax ); + } + + /** + * makes a new DasRow by parsing a string like "100%-5em+3pt" to get the offsets. + * The three qualifiers are "%", "em", and "pt", but "px" is allowed as well + * as surely people will use that by mistake. If an offset or the normal position + * is not specified, then 0 is used. + * + * @param canvas the canvas for the layout, ignored when a parent DasRow is used. + * @param parent if non-null, this DasRow is specified with respect to parent. + * @param minStr a string like "0%+5em" + * @param maxStr a string like "100%-7em" + * @throws IllegalArgumentException if the strings cannot be parsed + */ + public static DasRow create( DasCanvas canvas, DasRow parent, String minStr, String maxStr ) { + double[] min, max; + try { + min= parseFormatStr( minStr ); + } catch ( ParseException e ) { + throw new IllegalArgumentException("unable to parse min: \""+minStr+"\""); + } + try { + max= parseFormatStr( maxStr ); + } catch ( ParseException e ) { + throw new IllegalArgumentException("unable to parse max: \""+maxStr+"\""); + } + return new DasRow( canvas, parent, min[0], max[0], min[1], max[1], (int)min[2], (int)max[2] ); + } + + public static final DasRow NULL= new DasRow(null,null,0,0,0,0,0,0); + + /** + * @deprecated This created a row that was not attached to anything, so + * it was simply a convenience method that didn't save much effort. + */ + public DasRow createSubRow(double ptop, double pbottom) { + double top= getMinimum(); + double bottom= getMaximum(); + double delta= top-bottom; + return new DasRow(getCanvas(),bottom+ptop*delta,bottom+pbottom*delta); + } + + public int getHeight() { + return getDMaximum()-getDMinimum(); + } + + public static DasRow create(DasCanvas parent) { + return new DasRow(parent,null,0.,1.0, 5, -5, 0,0 ); + } + + public static DasRow create( DasCanvas parent, int iplot, int nplot ) { + double min= 0.1 + iplot * ( 0.8 ) / nplot; + double max= 0.099 + ( iplot + 1 ) * ( 0.8 ) / nplot; + return new DasRow( parent, min, max ); + } + + /** Create a sub-row of a parent row. + * + * The canvas coordinates for objects in this row will be calculated as fractional + * offsets from the top and bottom of the parent row. + * + * @param ptop Fractional distance from the top of the parent row, to the top + * of the new sub-row. Here 0.0 would mean make the top of the sub-row + * coterminus with the top of the parent. + * + * @param pbottom Fractional distance from the top of the parent row, to the bottom + * of the new sub-row. Here 1.0 would mean make the bottom of the sub-row + * coterminus with bottom of the parent. + * + * @return a new DasRow object with top and bottom positions defined in relation to + * this row, instead of the Canvas. + */ + public DasRow createAttachedRow(double ptop, double pbottom) { + return new DasRow(null,this,ptop,pbottom,0,0,0,0); + } + + /** + * create a child by parsing spec strings like "50%+3em" + * @throws IllegalArgumentException when the string is malformed. + * @param smin + * @param smax + * @return + */ + public DasRow createChildRow( String smin, String smax ) { + try { + double[] min= DasDevicePosition.parseFormatStr(smin); + double[] max= DasDevicePosition.parseFormatStr(smax); + return new DasRow( null, this, min[0], max[0], min[1], max[1], (int)min[2], (int)max[2] ); + } catch (ParseException ex ) { + throw new IllegalArgumentException(ex); + } + } + + /** Process a <row> element. + * + * @param element The DOM tree node that represents the element + */ + static DasRow processRowElement(Element element, DasCanvas canvas, FormBase form) throws DasException { + String name = element.getAttribute("name"); + double minimum = Double.parseDouble(element.getAttribute("minimum")); + double maximum = Double.parseDouble(element.getAttribute("maximum")); + DasRow row = new DasRow(canvas, minimum, maximum); + row.setDasName(name); + DasApplication app = form.getDasApplication(); + NameContext nc = app.getNameContext(); + nc.put(name, row); + return row; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("row"); + element.setAttribute("name", getDasName()); + element.setAttribute("minimum", Double.toString(getMinimum())); + element.setAttribute("maximum", Double.toString(getMaximum())); + return element; + } + + /** + * return the device location of the top of the row. + * @return + */ + public int top() { + return getDMinimum(); + } + + /** + * return the device location of the bottom (non-inclusive) of the row. + * @return + */ + public int bottom() { + return getDMaximum(); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/DasSerializeable.java b/dasCore/src/main/java/org/das2/graph/DasSerializeable.java new file mode 100644 index 000000000..c2fc87995 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasSerializeable.java @@ -0,0 +1,23 @@ +/* + * DasSerializeable.java + * + * Created on November 4, 2004, 5:35 PM + */ + +package org.das2.graph; + +import org.das2.dasml.FormBase; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import java.text.*; +import org.w3c.dom.*; + +/** + * + * @author Jeremy + */ +public interface DasSerializeable { + + Element getDOMElement( Document document ); + Object processElement( Element element, DasPlot parent, FormBase form) throws DasPropertyException, DasNameException, ParseException; +} diff --git a/dasCore/src/main/java/org/das2/graph/DasZAxisPlot.java b/dasCore/src/main/java/org/das2/graph/DasZAxisPlot.java new file mode 100644 index 000000000..96dffb2a8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DasZAxisPlot.java @@ -0,0 +1,44 @@ +/* File: DasZAxisPlot.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +/** + * + * @author jbf + */ +import org.das2.dataset.DataSet; +import org.das2.dataset.TableDataSetConsumer; + +public interface DasZAxisPlot extends org.das2.dataset.TableDataSetConsumer { + + /** Creates a new instance of DasZAxisPlot */ + public DasAxis getZAxis(); + public DasAxis getYAxis(); + public DasAxis getXAxis(); + + public org.das2.dataset.DataSet getDataSet(); + + public void setZAxis(DasAxis zAxis); + +} diff --git a/dasCore/src/main/java/org/das2/graph/DataLoader.java b/dasCore/src/main/java/org/das2/graph/DataLoader.java new file mode 100644 index 000000000..d346a1753 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DataLoader.java @@ -0,0 +1,124 @@ +/* + * DataLoader.java + * + * Created on September 13, 2005, 6:41 AM + * + * To change this template, choose Tools | Options and locate the template under + * the Source Creation and Management node. Right-click the template and choose + * Open. You can then make changes to the template in the Source Editor. + */ + +package org.das2.graph; +import org.das2.DasApplication; +import org.das2.dataset.RebinDescriptor; +import org.das2.util.monitor.ProgressMonitor; + +/** + * + * @author Jeremy + */ +public abstract class DataLoader { + + Renderer renderer; + + protected ProgressMonitor getMonitor( String description ) { + return DasApplication.getDefaultApplication().getMonitorFactory() + .getMonitor( renderer.getParent(), "Loading data set", description ); + } + + protected DataLoader( Renderer renderer ) { + this.renderer= renderer; + } + + /** + * an update message sent by the Renderer to indicate that something might have changed. + * Presently, the axis will send an update message to the plot, the plot will send an + * update message to the renderer, who will send an update message to the loader. + * THIS WILL PROBABLY CHANGE. + */ + abstract public void update(); + + private boolean fullResolution = false; + public boolean isFullResolution() { + return fullResolution; + } + + public void setFullResolution(boolean b) { + if (fullResolution == b) return; + fullResolution = b; + update(); + } + + // reloadDataSet is a dummy property that is Jeremy's way of telling the thing to + // reload through the property editor. calling setReloadDataSet(true) causes the + // dataset to reload and the image to be redrawn. + private boolean reloadDataSet; + public void setReloadDataSet( boolean reloadDataSet ) { + if ( reloadDataSet ) { + renderer.setDataSet( null ); + renderer.getParent().markDirty(); + renderer.getParent().update(); + + } + reloadDataSet= false; + } + + public boolean isReloadDataSet() { + return this.reloadDataSet; + } + + protected Renderer getRenderer() { + return this.renderer; + } + + public class Request { + public ProgressMonitor monitor; + public DasAxis.Memento xmem; + public DasAxis.Memento ymem; + public Request( ProgressMonitor mon, DasAxis.Memento xmem, DasAxis.Memento ymem ) { + this.monitor= mon; + this.xmem= xmem; + this.ymem= ymem; + } + public String toString() { + return xmem.toString(); + } + } + + /** + * convenient method for getting a rebindescriptor with one bin per pixel. -1 is + * returned by the rebinDescriptor when no bin holds the point. + */ + protected RebinDescriptor getRebinDescriptor( DasAxis axis ) { + int npix; + if ( axis.getOrientation()==DasAxis.VERTICAL ) { + npix= axis.getRow().getHeight(); + } else { + npix= axis.getColumn().getWidth(); + } + + RebinDescriptor rebinDescriptor; + rebinDescriptor = new RebinDescriptor( + axis.getDataMinimum(), axis.getDataMaximum(), + npix, + axis.isLog()); + rebinDescriptor.setOutOfBoundsAction( RebinDescriptor.MINUSONE ); + return rebinDescriptor; + } + + /** + * If active is false, then no data loading should occur. + */ + private boolean active= true; + + + public boolean isActive() { + return this.active && renderer.isActive(); + } + + public void setActive(boolean active) { + this.active = active; + update(); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/DataRange.java b/dasCore/src/main/java/org/das2/graph/DataRange.java new file mode 100755 index 000000000..0d45c19a9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/DataRange.java @@ -0,0 +1,451 @@ + +/* File: DataRange.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.util.DasMath; +import org.das2.graph.event.DasUpdateListener; +import org.das2.graph.event.DasUpdateEvent; +import org.das2.system.DasLogger; + +import javax.swing.event.EventListenerList; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.*; +import java.util.Stack; +import java.util.logging.Logger; + +public class DataRange implements Cloneable { + + /** + * danger! axes share this object + */ + private DasAxis parent; + + private Units units; + + /** + * minimum, possibly with log applied + */ + private double minimum; + + /** + * maximum, possibly with log applied + */ + private double maximum; + + /** + * storage for values of temporary invalid states during state transition. + */ + private Datum pendingMin=null, pendingMax=null; + + /** range is the min and max, not in the log space. This is the + * range that controls the DataRange, as opposed to minimum and + * maximum, which are simply to implement it. + */ + private DatumRange range; + public static String PROPERTY_DATUMRANGE="datumRange"; + + private boolean log; + + private EventListenerList listenerList = new EventListenerList(); + + private Stack history; + + private Stack forwardHistory; + + private List favorites; + + private PropertyChangeSupport propertyChangeDelegate; + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error("Assertion failure"); + } + } + + public DataRange( DasAxis parent, Datum min, Datum max, boolean log ) { + if (min.gt(max)) throw new IllegalArgumentException("data min on axis is greater than data max ("+min+">"+max+")"); + if (!min.isFinite()) throw new IllegalArgumentException("data_minimum on axis is not finite"); + if (!max.isFinite()) throw new IllegalArgumentException("data_maximum on axis is not finite"); + //if (min.getUnits()!=max.getUnits()) throw new IllegalArgumentException("units don't match on range"); + if (!min.getUnits().isConvertableTo(max.getUnits())) throw new IllegalArgumentException("units are not conversion compatible"); + this.parent= parent; + units= min.getUnits(); + if ( log ) { + minimum = DasMath.log10(min.doubleValue(units)); + maximum = DasMath.log10(max.doubleValue(units)); + } else { + minimum = min.doubleValue(units); + maximum = max.doubleValue(units); + } + this.range= new DatumRange( min, max ); + this.log = log; + history = new Stack(); + forwardHistory = new Stack(); + propertyChangeDelegate = new PropertyChangeSupport(this); + } + + public boolean isLog() { + return log; + } + + /* + * need some method for changing the axis units... + */ + public void resetRange( DatumRange range ) { + this.units= range.getUnits(); + this.range= range; + this.minimum= range.min().doubleValue(this.units); + this.maximum= range.max().doubleValue(this.units); + if ( isLog() ) { + this.minimum= DasMath.log10( this.minimum ); + this.maximum= DasMath.log10( this.maximum ); + } + fireUpdate(); + } + + /** + * set the log property. If log is true and max or min is zero or negative, + * then the values are reset to make them valid. + * @param log + */ + public void setLog(boolean log) { + /* + * propose new logic for going between lin/log axes: + * to log: pick first minor tick greater than zero. + * to lin: pick first minor tick in linear space less than min. + */ + + if (this.log==log) return; + boolean oldLog = this.log; + if (log) { + if (minimum<=0. || maximum <=0.) { + if ( maximum<=0 ) { + double oldMax= maximum; + maximum=100; + firePropertyChange("maximum", oldMax, maximum); + } + if ( minimum<=0 ) { + double oldMin= minimum; + minimum= maximum/1000; // three cycles, and typical the number of pixels. + firePropertyChange("minimum", oldMin, minimum); + } + this.range= new DatumRange( minimum, maximum, range.getUnits() ); + firePropertyChange("log", oldLog, log); + } + this.minimum= DasMath.log10(minimum); + this.maximum= DasMath.log10(maximum); + } else { + this.minimum= DasMath.exp10(minimum); + this.maximum= DasMath.exp10(maximum); + /* don't do this for now, we need to check that this does not mess things up when changing a bunch of properties at the same time. + if ( maximum / minimum > 999. ) { + minimum= 0; // minimum is close enough to zero. + this.range= new DatumRange( minimum, maximum, range.getUnits() ); + }*/ + } + clearHistory(); + this.log=log; + firePropertyChange("log", oldLog, log); + fireUpdate(); + } + + public DasAxis getCreator() { + return parent; + } + + public double getMinimum() { return minimum; } + + public double getMaximum() { return maximum; } + + /* @returns the floating point index within the range, where 0.0 indicates + * @param value is equal to minimum and 1.0 means it is equal to maximum, + * with log/lin/??? curvature considered. + */ + public final double findex( double value ) { + if ( log ) { + value= DasMath.log10(value); + } + return ( value-minimum ) / ( maximum - minimum ); + } + + public Units getUnits() { return units; } + + public DatumRange getDatumRange() { return range; } + + public void setUnits(Units newUnits) { + if (units.equals(newUnits)) { + return; + } + + minimum = units.convertDoubleTo(newUnits, minimum); + maximum = units.convertDoubleTo(newUnits, maximum); + units = newUnits; + + clearHistory(); + + } + + public void setMinimum( Datum min ) { + Datum max= pendingMax!=null ? pendingMax : this.range.max(); + if ( min.le( max ) ) { + setRange( new DatumRange( min, max ) ); + } else { + this.pendingMin= min; + } + } + + public void setMaximum( Datum max ) { + Datum min= pendingMin!=null ? pendingMin : this.range.min(); + if ( min.le( max ) ) { + setRange( new DatumRange( min, max ) ); + } else { + this.pendingMax= max; + } + } + + private void reportHistory() { + Logger log= DasLogger.getLogger( DasLogger.GUI_LOG ); + log.finest("history: "+history.size()); + for ( int i=0; i=0; i-=2) { + if (listeners[i]==DasUpdateListener.class) { + ((DasUpdateListener)listeners[i+1]).update(e); + } + } + } + + /** + * pop ipop items off the history list. This is used by the history menu + */ + protected void popHistory( int ipop ) { + for ( int i=0; i= eventMap.length ) { + setLabel(null); + } else { + int i= eventMap[ix]; + if ( i>=0 ) { + Datum sx= vds.getXTagDatum(i); + Datum sz= vds.getDatum(i); + VectorDataSet widthsDs= (VectorDataSet)vds.getPlanarView(widthPlaneId); + DatumRange dr= new DatumRange( sx, sx.add(widthsDs.getDatum(i)) ); + setLabel( textSpecifier.getText( dr, sz ) ); + } else { + setLabel(null); + } + } + return super.renderDrag( g, p1, p2 ); + } + + } + + private MouseModule getMouseModule() { + return new MouseModule( parent, new DragRenderer(parent), "event lookup" ); + } + + public void render(java.awt.Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + VectorDataSet vds= (VectorDataSet)getDataSet(); + if (vds == null || vds.getXLength() == 0) { + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("null data set"); + return; + } + + Graphics2D g= ( Graphics2D ) g1.create(); + + g.setColor(color); + + if ( vds==null && getLastException()!=null ) { + renderException( g, xAxis, yAxis, getLastException() ); + + } else { + VectorDataSet widthsDs= (VectorDataSet)vds.getPlanarView(widthPlaneId); + if ( widthsDs==null ) { + throw new IllegalArgumentException("no width plane named \""+widthPlaneId+"\" found"); + } + + DasColumn column= xAxis.getColumn(); + DasRow row= parent.getRow(); + + eventMap= new int[column.getWidth()]; + for ( int k=0; k0 ) { + UnitsConverter uc= UnitsConverter.getConverter( widthsDs.getYUnits(), xAxis.getUnits().getOffsetUnits() ); + + int ivds0= 0; + int ivds1= vds.getXLength(); + for ( int i=ivds0; i (ix) ) { + if ( iwidth==0 ) iwidth=1; + g.fill( new Rectangle( ix, row.getDMinimum(), iwidth, row.getHeight() ) ); + int im= ix-column.getDMinimum(); + int em0= im-1; + int em1= im+iwidth+1; + for ( int k=em0; k=0 && k Math.abs( tickSlope ) ) { // tick intersects the height of the label bounds. + if ( tickLine.getX2()>tickLine.getX1() ) { // e.g. 3 O'Clock + double rise= tickSlope * labelWidth / 2; + labelX= tickLine.getX2(); + labelY= tickLine.getY2() - (labelHeight)/2 + gtr.getAscent() + rise; + //g.setColor( Color.green ); + //((Graphics2D)g).draw( tickLine ); + } else { // e.g. 9 O'Clock + double rise= - tickSlope * labelWidth / 2; + labelX= tickLine.getX2() - labelWidth; + labelY= tickLine.getY2() - labelHeight/2 + gtr.getAscent() + rise; + //g.setColor( Color.red ); + //((Graphics2D)g).draw( tickLine ); + } + } else { // tick intersects the width of the label bounds + if ( tickLine.getY2() xSampleWidth) { + newPath.moveTo((float) i, (float) j); + skippedLast = false; + } else { + if (histogram) { + double i1 = (i0 + i) / 2; + newPath.lineTo((float) i1, (float) j0); + newPath.lineTo((float) i1, (float) j); + newPath.lineTo((float) i, (float) j); + } else { + newPath.lineTo((float) i, (float) j); + } + skippedLast = false; + } + t0 = t; + x0 = x; + y0 = y; + i0 = i; + j0 = j; + } + return newPath; + + } + + /** + * calculates the AffineTransform between two sets of x and y axes, if possible. + * @param xaxis0 the original reference frame x axis + * @param yaxis0 the original reference frame y axis + * @param xaxis1 the new reference frame x axis + * @param yaxis1 the new reference frame y axis + * @return an AffineTransform that transforms data positioned with xaxis0 and yaxis0 on xaxis1 and yaxis1, or null if no such transform exists. + */ + public static AffineTransform calculateAT(DasAxis xaxis0, DasAxis yaxis0, DasAxis xaxis1, DasAxis yaxis1) { + return calculateAT( xaxis0.getDatumRange(), yaxis0.getDatumRange(), xaxis1, yaxis1 ); + } + + public static AffineTransform calculateAT( + DatumRange xaxis0, DatumRange yaxis0, + DasAxis xaxis1, DasAxis yaxis1 ) { + + AffineTransform at = new AffineTransform(); + + double dmin0 = xaxis1.transform( xaxis0.min() ); // old axis in new axis space + double dmax0 = xaxis1.transform( xaxis0.max() ); + double dmin1 = xaxis1.transform( xaxis1.getDataMinimum() ); + double dmax1 = xaxis1.transform( xaxis1.getDataMaximum() ); + + double scalex = (dmin0 - dmax0) / (dmin1 - dmax1); + double transx = -1 * dmin1 * scalex + dmin0; + + at.translate(transx, 0); + at.scale(scalex, 1.); + + if (at.getDeterminant() == 0.000) { + return null; + } + + dmin0 = yaxis1.transform( yaxis0.min() ); // old axis in new axis space + dmax0 = yaxis1.transform( yaxis0.max() ); + dmin1 = yaxis1.transform(yaxis1.getDataMinimum()); + dmax1 = yaxis1.transform(yaxis1.getDataMaximum()); + + double scaley = (dmin0 - dmax0) / (dmin1 - dmax1); + double transy = -1 * dmin1 * scaley + dmin0; + + at.translate(0, transy); + at.scale(1., scaley); + + return at; + } + + public static DasAxis guessYAxis(DataSet dsz) { + boolean log = false; + + if (dsz.getProperty(DataSet.PROPERTY_Y_SCALETYPE) != null) { + if (dsz.getProperty(DataSet.PROPERTY_Y_SCALETYPE).equals("log")) { + log = true; + } + } + + DasAxis result; + + if (dsz instanceof TableDataSet) { + TableDataSet ds = (TableDataSet) dsz; + Units yunits = ds.getYUnits(); + Datum min, max; + + DatumRange yrange = DataSetUtil.yRange(dsz); + Datum dy = TableUtil.guessYTagWidth(ds); + if (UnitsUtil.isRatiometric(dy.getUnits())) { + log = true; + } + result = new DasAxis(yrange.min(), yrange.max(), DasAxis.LEFT, log); + + } else if (dsz instanceof VectorDataSet) { + VectorDataSet ds = (VectorDataSet) dsz; + Units yunits = ds.getYUnits(); + + DatumRange range = DataSetUtil.yRange(dsz); + if (range.width().doubleValue(yunits.getOffsetUnits()) == 0.) { + range = range.include(yunits.createDatum(0)); + if (range.width().doubleValue(yunits.getOffsetUnits()) == 0.) { + range = new DatumRange(0, 10, yunits); + } + } + result = new DasAxis(range.min(), range.max(), DasAxis.LEFT, log); + + } else { + throw new IllegalArgumentException("not supported: " + dsz); + } + + if (dsz.getProperty(DataSet.PROPERTY_Y_LABEL) != null) { + result.setLabel((String) dsz.getProperty(DataSet.PROPERTY_Y_LABEL)); + } + return result; + } + + public static DasAxis guessXAxis(DataSet ds) { + Datum min = ds.getXTagDatum(0); + Datum max = ds.getXTagDatum(ds.getXLength() - 1); + return new DasAxis(min, max, DasAxis.BOTTOM); + } + + public static DasAxis guessZAxis(DataSet dsz) { + if (!(dsz instanceof TableDataSet)) { + throw new IllegalArgumentException("only TableDataSet supported"); + } + TableDataSet ds = (TableDataSet) dsz; + Units zunits = ds.getZUnits(); + + DatumRange range = DataSetUtil.zRange(ds); + + boolean log = false; + if (dsz.getProperty(DataSet.PROPERTY_Z_SCALETYPE) != null) { + if (dsz.getProperty(DataSet.PROPERTY_Z_SCALETYPE).equals("log")) { + log = true; + if (range.min().doubleValue(range.getUnits()) <= 0) { // kludge for VALIDMIN + + double max = range.max().doubleValue(range.getUnits()); + range = new DatumRange(max / 1000, max, range.getUnits()); + } + } + } + + DasAxis result = new DasAxis(range.min(), range.max(), DasAxis.LEFT, log); + if (dsz.getProperty(DataSet.PROPERTY_Z_LABEL) != null) { + result.setLabel((String) dsz.getProperty(DataSet.PROPERTY_Z_LABEL)); + } + return result; + } + + public static Renderer guessRenderer(DataSet ds) { + Renderer rend = null; + if (ds instanceof VectorDataSet) {// TODO use SeriesRenderer + + if (ds.getXLength() > 10000) { + rend = new ImageVectorDataSetRenderer(new ConstantDataSetDescriptor(ds)); + } else { + rend = new SymbolLineRenderer(); + rend.setDataSet(ds); + ((SymbolLineRenderer) rend).setPsym(Psym.DOTS); + ((SymbolLineRenderer) rend).setSymSize(2.0); + } + + } else if (ds instanceof TableDataSet) { + Units zunits = ((TableDataSet) ds).getZUnits(); + DasAxis zaxis = guessZAxis(ds); + DasColorBar colorbar = new DasColorBar(zaxis.getDataMinimum(), zaxis.getDataMaximum(), zaxis.isLog()); + colorbar.setLabel(zaxis.getLabel()); + rend = new SpectrogramRenderer(new ConstantDataSetDescriptor(ds), colorbar); + } + return rend; + } + + public static DasPlot guessPlot(DataSet ds) { + DasAxis xaxis = guessXAxis(ds); + DasAxis yaxis = guessYAxis(ds); + DasPlot plot = new DasPlot(xaxis, yaxis); + plot.addRenderer(guessRenderer(ds)); + return plot; + } + + + public static DasPlot visualize( DataSet ds, String title, String xlabel, String ylabel ) { + DasPlot p= visualize(ds); + p.setTitle( title ); + p.getXAxis().setLabel(xlabel); + p.getYAxis().setLabel(ylabel); + return p; + } + + public static DasPlot visualize(DataSet ds) { + + JFrame jframe = new JFrame("DataSetUtil.visualize"); + DasCanvas canvas = new DasCanvas(400, 400); + jframe.getContentPane().add(canvas); + DasPlot result = guessPlot(ds); + canvas.add(result, DasRow.create(canvas), DasColumn.create(canvas,null,"5em","100%-10em") ); + + jframe.pack(); + jframe.setVisible(true); + jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + return result; + } + + public static DasPlot visualize(DataSet ds, boolean ylog) { + DatumRange xRange = DataSetUtil.xRange(ds); + DatumRange yRange = DataSetUtil.yRange(ds); + JFrame jframe = new JFrame("DataSetUtil.visualize"); + DasCanvas canvas = new DasCanvas(400, 400); + jframe.getContentPane().add(canvas); + DasPlot result = guessPlot(ds); + canvas.add(result, DasRow.create(canvas), DasColumn.create(canvas,null,"5em","100%-10em")); + Units xunits = result.getXAxis().getUnits(); + result.getXAxis().setDatumRange(xRange.zoomOut(1.1)); + Units yunits = result.getYAxis().getUnits(); + if (ylog) { + result.getYAxis().setDatumRange(yRange); + result.getYAxis().setLog(true); + } else { + result.getYAxis().setDatumRange(yRange.zoomOut(1.1)); + } + jframe.pack(); + jframe.setVisible(true); + jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + return result; + } + + /** + * Returns the input GeneralPath filled with new points which will be rendered identically to the input path, + * but contains a minimal number of points. Successive points occupying the same pixel are + * culled. + * TODO: bin average the points within a cell. The descretization messes up the label orientation in contour plotting. + * @return a new GeneralPath which will be rendered identically to the input path, + * but contains a minimal number of points. + * @param it A path iterator with minute details that will be lost when rendering. + * @param result A GeneralPath to put the result into. + */ + public static GeneralPath reducePath(PathIterator it, GeneralPath result) { + + float[] p = new float[6]; + + float x0 = Float.MAX_VALUE; + float y0 = Float.MAX_VALUE; + float sx0 = 0; + float sy0 = 0; + int nx0 = 0; + int ny0 = 0; + float ax0 = Float.NaN; + float ay0 = Float.NaN; // last averaged location + + int type0 = -999; + + float xres = 1; + float yres = 1; + + String[] types = new String[]{"M", "L", "QUAD", "CUBIC", "CLOSE"}; + + int points = 0; + int inCount = 0; + + while (!it.isDone()) { + inCount++; + + int type = it.currentSegment(p); + it.next(); + float dx = p[0] - x0; + float dy = p[1] - y0; + + //System.err.print("" + inCount + ": type: " + types[type] + " " + String.format("[ %f %f ] ", p[0], p[1])); + if ((type == PathIterator.SEG_MOVETO || type == type0) && Math.abs(dx) < xres && Math.abs(dy) < yres) { + sx0 += p[0]; + sy0 += p[1]; + nx0 += 1; + ny0 += 1; + //System.err.println(" accum"); + continue; + } else { + x0 = 0.5f + (int) Math.floor(p[0]); + y0 = 0.5f + (int) Math.floor(p[1]); + ax0 = nx0 > 0 ? sx0 / nx0 : p[0]; + ay0 = ny0 > 0 ? sy0 / ny0 : p[1]; + sx0 = p[0]; + sy0 = p[1]; + nx0 = 1; + ny0 = 1; + //System.err.print(" avg " + nx0 + " points (" + String.format("[ %f %f ]", ax0, ay0)); + } + + switch (type0) { + case PathIterator.SEG_LINETO: + result.lineTo(ax0, ay0); + points++; + //j System.err.println( ""+points+": " + GraphUtil.describe(result, false) ); + //System.err.println(" lineTo"+ String.format("[ %f %f ]", ax0, ay0)); + break; + case PathIterator.SEG_MOVETO: + result.moveTo(ax0, ay0); + //System.err.println(" moveTo"+ String.format("[ %f %f ]", ax0, ay0)); + break; + case -999: + //System.err.println(" ignore"+ String.format("[ %f %f ]", ax0, ay0)); + break; + default: + throw new IllegalArgumentException("not supported"); + } + + type0 = type; + } + + ax0 = nx0 > 0 ? sx0 / nx0 : p[0]; + ay0 = ny0 > 0 ? sy0 / ny0 : p[1]; + //System.err.print(" avg " + nx0 + " points " ); + + switch (type0) { + case PathIterator.SEG_LINETO: + result.lineTo(ax0, ay0); + points++; + //j System.err.println( ""+points+": " + GraphUtil.describe(result, false) ); + //System.err.println(" lineTo"+ String.format("[ %f %f ]", ax0, ay0) ); + break; + case PathIterator.SEG_MOVETO: + result.moveTo(ax0, ay0); + //System.err.println(" moveTo "+ String.format("[ %f %f ]", ax0, ay0) ); + break; + case -999: + //System.err.println(" ignore"); + break; + default: + throw new IllegalArgumentException("not supported"); + } + + return result; + } + + /** + * return the points along a curve. Used by ContourRenderer. The returned + * result is the remaining path length. Elements of pathlen that are beyond + * the total path length are not computed, and the result points will be null. + * @param pathlen monotonically increasing path lengths at which the position is to be located. May be null if only the total path length is desired. + * @param result the resultant points will be put into this array. This array should have the same number of elements as pathlen + * @param orientation the local orientation, in radians, of the point at will be put into this array. This array should have the same number of elements as pathlen + * @param it PathIterator first point is used to start the length. + * @param stopAtMoveTo treat SEG_MOVETO as the end of the path. The pathIterator will be left at this point. + * @return the remaining length. Note null may be used for pathlen, result, and orientation and this will simply return the total path length. + */ + public static double pointsAlongCurve(PathIterator it, double[] pathlen, Point2D.Double[] result, double[] orientation, boolean stopAtMoveTo) { + + float[] point = new float[6]; + float fx0 = Float.NaN, fy0 = Float.NaN; + + double slen = 0; + int pathlenIndex = 0; + int type; + + if (pathlen == null) { + pathlen = new double[0]; + } + while (!it.isDone()) { + type = it.currentSegment(point); + it.next(); + + if (!Float.isNaN(fx0) && type == PathIterator.SEG_MOVETO && stopAtMoveTo) { + break; + } + if (PathIterator.SEG_CUBICTO == type) { + throw new IllegalArgumentException("cubicto not supported"); + } else if (PathIterator.SEG_QUADTO == type) { + throw new IllegalArgumentException("quadto not supported"); + } else if (PathIterator.SEG_LINETO == type) { + } + + if (Float.isNaN(fx0)) { + fx0 = point[0]; + fy0 = point[1]; + continue; + } + + double thislen = (float) Point.distance(fx0, fy0, point[0], point[1]); + + if (thislen == 0) { + continue; + } else { + slen += thislen; + } + + while (pathlenIndex < pathlen.length && slen >= pathlen[pathlenIndex]) { + double alpha = 1 - (slen - pathlen[pathlenIndex]) / thislen; + double dx = point[0] - fx0; + double dy = point[1] - fy0; + if (result != null) { + result[pathlenIndex] = new Point2D.Double(fx0 + dx * alpha, fy0 + dy * alpha); + } + if (orientation != null) { + orientation[pathlenIndex] = Math.atan2(dy, dx); + } + pathlenIndex++; + } + + fx0 = point[0]; + fy0 = point[1]; + + } + + double remaining; + if (pathlenIndex > 0) { + remaining = slen - pathlen[pathlenIndex - 1]; + } else { + remaining = slen; + } + + if (result != null) { + for (; pathlenIndex < result.length; pathlenIndex++) { + result[pathlenIndex] = null; + } + } + + return remaining; + } + + /** + * @return a string representation of the affine transforms used in DasPlot for + * debugging. + */ + public static String getATScaleTranslateString(AffineTransform at) { + String atDesc; + NumberFormat nf = new DecimalFormat("0.00"); + + if (at == null) { + return "null"; + } else if (!at.isIdentity()) { + atDesc = "scaleX:" + nf.format(at.getScaleX()) + " translateX:" + nf.format(at.getTranslateX()); + atDesc += "!c" + "scaleY:" + nf.format(at.getScaleY()) + " translateY:" + nf.format(at.getTranslateY()); + return atDesc; + } else { + return "identity"; + } + } + + /** + * calculates the slope and intercept of a line going through two points. + * @return a double array with two elements [ slope, intercept ]. + */ + public static double[] getSlopeIntercept(double x0, double y0, double x1, double y1) { + double slope = (y1 - y0) / (x1 - x0); + double intercept = y0 - slope * x0; + return new double[]{slope, intercept}; + } + + public static Color getRicePaperColor() { + return new Color(255, 255, 255, 128); + } + + public static String describe(GeneralPath path, boolean enumeratePoints) { + PathIterator it = path.getPathIterator(null); + int count = 0; + int lineToCount = 0; + double[] coords = new double[6]; + while (!it.isDone()) { + int type = it.currentSegment(coords); + if (type == PathIterator.SEG_LINETO) { + lineToCount++; + } + if (enumeratePoints) { + //j System.err.println(" " + coords[0] + " " + coords[1]); + } + count++; + it.next(); + } + return "count: " + count + " lineToCount: " + lineToCount; + } + + static String toString(Line2D line) { + return ""+line.getX1()+","+line.getY1()+" "+line.getX2()+","+line.getY2(); + } + + //TODO: sun.awt.geom.Curve and sun.awt.geom.Crossings are GPL open-source, so + // these methods will provide reliable methods for getting rectangle, line + // intersections. + + /** + * returns the point where the two line segments intersect, or null. + * @param line1 + * @param line2 + * @param noBoundsCheck if true, then do not check the segment bounds. + * @return + */ + public static Point2D lineIntersection(Line2D line1, Line2D line2, boolean noBoundsCheck) { + Point2D result = null; + double a1, b1, c1, a2, b2, c2, denom; + a1 = line1.getY2() - line1.getY1(); + b1 = line1.getX1() - line1.getX2(); + c1 = line1.getX2() * line1.getY1() - line1.getX1() * line1.getY2(); + + a2 = line2.getY2() - line2.getY1(); + b2 = line2.getX1() - line2.getX2(); + c2 = line2.getX2() * line2.getY1() - line2.getX1() * line2.getY2(); + + denom = a1 * b2 - a2 * b1; + if (denom != 0) { + result = new Point2D.Double((b1 * c2 - b2 * c1) / denom, (a2 * c1 - + a1 * c2) / denom); + if (noBoundsCheck + ||(((result.getX() - line1.getX1()) * (line1.getX2() - result.getX()) >= 0) + && ((result.getY() - line1.getY1()) * (line1.getY2() - result.getY()) >= 0) + && ((result.getX() - line2.getX1()) * (line2.getX2() - result.getX()) >= 0) + && ((result.getY() - line2.getY1()) * (line2.getY2() - result.getY()) >= 0) ) ) { + return result; + } else { + return null; + } + } else { + return null; + } + } + + public static Point2D lineRectangleIntersection( Point2D p0, Point2D p1, Rectangle2D r0) { + + PathIterator it = r0.getPathIterator(null); + + Line2D line = new Line2D.Double( p0, p1 ); + + float[] c0 = new float[6]; + float[] c1 = new float[6]; + it.currentSegment(c0); + it.next(); + while ( !it.isDone() ) { + int type= it.currentSegment(c1); + if ( type==PathIterator.SEG_LINETO ) { + Line2D seg = new Line2D.Double(c0[0], c0[1], c1[0], c1[1]); + Point2D result = lineIntersection(line, seg, false); + if (result != null) { + return result; + } + } + it.next(); + c0[0]= c1[0]; + c0[1]= c1[1]; + } + return null; + } + + /** + * returns pixel range of the datum range, guarenteeing that the first + * element will be less than or equal to the second. + * @param axis + * @param range + * @return + */ + public static double[] transformRange( DasAxis axis, DatumRange range ) { + double x1= axis.transform(range.min()); + double x2= axis.transform(range.max()); + if ( x1>x2 ) { + double t= x2; + x2= x1; + x1= t; + } + return new double[] { x1, x2 }; + } + + public static DatumRange invTransformRange( DasAxis axis, double x1, double x2 ) { + Datum d1= axis.invTransform(x1); + Datum d2= axis.invTransform(x2); + if ( d1.gt(d2) ) { + Datum t= d2; + d2= d1; + d1= t; + } + return new DatumRange( d1, d2 ); + } + + /** + * return a block with the color and size. + * @param w + * @param h + * @return + */ + public static Icon colorIcon( Color iconColor, int w, int h ) { + BufferedImage image= new BufferedImage( w, h, BufferedImage.TYPE_INT_ARGB ); + Graphics g= image.getGraphics(); + Color save = g.getColor(); + if ( iconColor.getAlpha()!=255 ) { // draw checkerboard to indicate transparency + for ( int j=0; j<16/4; j++ ) { + for ( int i=0; i<16/4; i++ ) { + g.setColor( (i-j)%2 ==0 ? Color.GRAY : Color.WHITE ); + g.fillRect( 0+i*4,0+j*4,4,4); + } + } + } + g.setColor(iconColor); + g.fillRect( 0, 0, w, h ); + return new ImageIcon(image); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/ImageVectorDataSetRenderer.java b/dasCore/src/main/java/org/das2/graph/ImageVectorDataSetRenderer.java new file mode 100644 index 000000000..0083a1953 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/ImageVectorDataSetRenderer.java @@ -0,0 +1,358 @@ +/* + * ImageVectorDataSetRenderer.java + * + * This renderer can handle vector data sets with tens of thousands of points + * by histogramming the points and then creating a greyscale spectrogram of + * the histogram. The property "saturationHitCount" defines the number of pixel + * hits that will make the pixel black. In the future, this may be modified to + * support color, alpha channel, and connected psyms. + * + * Created on April 14, 2005, 8:45 PM + */ +package org.das2.graph; + +import org.das2.dataset.WritableTableDataSet; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.DasException; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import org.das2.dataset.DataSet; +import org.das2.dataset.NoDataInIntervalException; + +/** + * + * @author Jeremy + */ +public class ImageVectorDataSetRenderer extends Renderer { + + GeneralPath path; + //SymbolLineRenderer highResRenderer; + Datum xTagWidth; + BufferedImage plotImage; + Rectangle plotImageBounds; + DatumRange imageXRange; + DatumRange imageYRange; + TableDataSet hist; + private Color color = Color.BLACK; + + /** Creates a new instance of LotsaPointsRenderer */ + public ImageVectorDataSetRenderer(DataSetDescriptor dsd) { + super(dsd); + } + + public synchronized void render(java.awt.Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + if ( ds==null ) { + parent.postMessage(this, "no data set", DasPlot.INFO, null, null); + return; + } + if (!xAxis.getUnits().isConvertableTo(ds.getXUnits())) { + parent.postMessage(this, "inconvertable xaxis units", DasPlot.INFO, null, null); + return; + } + + if (!yAxis.getUnits().isConvertableTo(ds.getYUnits())) { + parent.postMessage(this, "inconvertable yaxis units", DasPlot.INFO, null, null); + return; + } + Graphics2D g2 = (Graphics2D) g1; + if (plotImage == null) { + if (getLastException() != null) { + if (getLastException() instanceof NoDataInIntervalException) { + parent.postMessage(this, "no data in interval:!c" + getLastException().getMessage(), DasPlot.WARNING, null, null); + } else { + parent.postException(this, getLastException()); + } + } else { + if (getDataSet() == null) { + parent.postMessage(this, "no data set", DasPlot.INFO, null, null); + } else if (getDataSet().getXLength() == 0) { + parent.postMessage(this, "empty data set", DasPlot.INFO, null, null); + } + } + } else if (plotImage != null) { + Point2D p; + p = new Point2D.Float(plotImageBounds.x, plotImageBounds.y); + int x = (int) (p.getX() + 0.5); + int y = (int) (p.getY() + 0.5); + if (parent.getCanvas().isPrintingThread() && print300dpi) { + AffineTransformOp atop = new AffineTransformOp(AffineTransform.getScaleInstance(4, 4), AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + BufferedImage image300 = atop.filter((BufferedImage) plotImage, null); + AffineTransform atinv; + try { + atinv = atop.getTransform().createInverse(); + } catch (NoninvertibleTransformException ex) { + throw new RuntimeException(ex); + } + atinv.translate(x * 4, y * 4); + g2.drawImage(image300, atinv, getParent()); + } else { + g2.drawImage(plotImage, x, y, getParent()); + } + } + + //renderGhostly(g1, xAxis, yAxis); + } + + private void ghostlyImage2(DasAxis xAxis, DasAxis yAxis, VectorDataSet ds, Rectangle plotImageBounds2) { + int ny = plotImageBounds2.height; + int nx = plotImageBounds2.width; + + logger.fine("create Image"); + BufferedImage image = new BufferedImage(nx, ny, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) image.getGraphics(); + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g.setColor(color); + g.setStroke(new BasicStroke(1.f / saturationHitCount)); + + g.translate( -plotImageBounds2.x, -plotImageBounds2.y); + + imageXRange= GraphUtil.invTransformRange( xAxis, plotImageBounds2.x, plotImageBounds2.x+plotImageBounds2.width ); + imageYRange= GraphUtil.invTransformRange( yAxis, plotImageBounds2.y, plotImageBounds2.y+plotImageBounds2.height ); + + DatumRange visibleRange = imageXRange; + + //if ( isOverloading() ) visibleRange= visibleRange.rescale(-1,2); + + boolean xmono = Boolean.TRUE == ds.getProperty(DataSet.PROPERTY_X_MONOTONIC); + + int firstIndex = xmono ? DataSetUtil.getPreviousColumn(ds, visibleRange.min()) : 0; + int lastIndex = xmono ? DataSetUtil.getNextColumn(ds, visibleRange.max()) : ds.getXLength(); + + final int STATE_LINETO = -991; + final int STATE_MOVETO = -992; + + int state = STATE_MOVETO; + + // TODO: data breaks + int ix0 = 0, iy0 = 0; + if (ds.getXLength() > 0) { + for (int i = firstIndex; i <= lastIndex; i++) { + if (ds.getDatum(i).isFill()) { + state = STATE_MOVETO; + } else { + int iy = (int) yAxis.transform(ds.getDatum(i)); + int ix = (int) xAxis.transform(ds.getXTagDatum(i)); + switch (state) { + case STATE_MOVETO: + g.fillRect(ix, iy, 1, 1); + ix0 = ix; + iy0 = iy; + break; + case STATE_LINETO: + g.draw(new Line2D.Float(ix0, iy0, ix, iy)); + g.fillRect(ix, iy, 1, 1); + ix0 = ix; + iy0 = iy; + break; + } + state = STATE_LINETO; + } + } + } + + logger.fine("done"); + plotImage = image; + + } + + private TableDataSet histogram(RebinDescriptor ddx, RebinDescriptor ddy, VectorDataSet ds) { + ddx.setOutOfBoundsAction(RebinDescriptor.MINUSONE); + ddy.setOutOfBoundsAction(RebinDescriptor.MINUSONE); + WritableTableDataSet tds = WritableTableDataSet.newSimple(ddx.numberOfBins(), ddx.getUnits(), + ddy.numberOfBins(), ddy.getUnits(), Units.dimensionless); + + if (ds.getXLength() > 0) { + Units xunits = ddx.getUnits(); + Units yunits = ddy.getUnits(); + Units zunits = Units.dimensionless; + + int i = DataSetUtil.getPreviousColumn(ds, ddx.binStart(0)); + int n = DataSetUtil.getNextColumn(ds, ddx.binStop(ddx.numberOfBins() - 1)); + for (; i <= n; i++) { + int ix = ddx.whichBin(ds.getXTagDouble(i, xunits), xunits); + int iy = ddy.whichBin(ds.getDouble(i, yunits), yunits); + if (ix != -1 && iy != -1) { + double d = tds.getDouble(ix, iy, zunits); + tds.setDouble(ix, iy, d + 1, zunits); + } + } + } + return tds; + } + + private void ghostlyImage(DasAxis xAxis, DasAxis yAxis, VectorDataSet ds, Rectangle plotImageBounds2) { + RebinDescriptor ddx; + + DatumRange xrange = new DatumRange(xAxis.invTransform(plotImageBounds2.x), + xAxis.invTransform(plotImageBounds2.x + plotImageBounds2.width)); + DatumRange yrange = new DatumRange(yAxis.invTransform(plotImageBounds2.y + plotImageBounds2.height), + yAxis.invTransform(plotImageBounds2.y)); + + ddx = new RebinDescriptor( + xrange.min(), + xrange.max(), + plotImageBounds2.width, + xAxis.isLog()); + + RebinDescriptor ddy = new RebinDescriptor( + yrange.min(), + yrange.max(), + plotImageBounds2.height, + yAxis.isLog()); + + + TableDataSet newHist = histogram(ddx, ddy, ds); + //WritableTableDataSet whist= (WritableTableDataSet)hist; + + /* double histMax= TableUtil.tableMax(hist, Units.dimensionless); + for ( int i=0; i 0 && d < histMax*floorFactor ) + whist.setDouble( i,j, histMax*floorFactor, Units.dimensionless ); + } + } */ + + + int h = ddy.numberOfBins(); + int w = ddx.numberOfBins(); + + int[] raster = new int[h * w]; + int colorInt = color.getRGB() & 0x00ffffff; + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { + int index = (i - 0) + (h - j - 1) * w; + // alpha=0 for transparent, alpha=255 for opaque + int alpha = 255 * (int) newHist.getDouble(i, j, Units.dimensionless) / saturationHitCount; + + int icolor = (alpha << 24) | colorInt; + raster[index] = icolor; + } + } + + plotImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + WritableRaster r = plotImage.getRaster(); + r.setDataElements(0, 0, w, h, raster); + + imageXRange = xrange; + imageYRange = yrange; + } + + @Override + public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, org.das2.util.monitor.ProgressMonitor monitor) throws DasException { + super.updatePlotImage(xAxis, yAxis, monitor); + + long t0 = System.currentTimeMillis(); + + VectorDataSet ds1 = (VectorDataSet) getDataSet(); + if (ds1 == null) { + return; + } + if (!xAxis.getUnits().isConvertableTo(ds1.getXUnits())) { + parent.postMessage(this, "inconvertable xaxis units", DasPlot.INFO, null, null); + return; + } + + if (!yAxis.getUnits().isConvertableTo(ds1.getYUnits())) { + parent.postMessage(this, "inconvertable yaxis units", DasPlot.INFO, null, null); + return; + } + + plotImageBounds = parent.getCacheImageBounds(); + if ( plotImageBounds==null ) { + //transient state in parent component. TODO: fix these + return; + } + + DatumRange visibleRange = xAxis.getDatumRange(); + + boolean xmono = Boolean.TRUE == ds1.getProperty(DataSet.PROPERTY_X_MONOTONIC); + + int firstIndex = xmono ? DataSetUtil.getPreviousColumn(ds1, visibleRange.min()) : 0; + int lastIndex = xmono ? DataSetUtil.getNextColumn(ds1, visibleRange.max()) : ds1.getXLength(); + + if ((lastIndex - firstIndex) > 20 * xAxis.getColumn().getWidth()) { + logger.fine("rendering with histogram"); + ghostlyImage(xAxis, yAxis, ds1, plotImageBounds); + } else { + logger.fine("rendinging with lines"); + ghostlyImage2(xAxis, yAxis, ds1, plotImageBounds); + } + logger.fine("done updatePlotImage"); + + } + int saturationHitCount = 5; + + public void setSaturationHitCount(int d) { + if (d > 10) { + d = 10; + } + this.saturationHitCount = d; + this.update(); + } + + public int getSaturationHitCount() { + return this.saturationHitCount; + } + + public void setColor(Color color) { + this.color = color; + refreshImage(); + } + + public Color getColor() { + return color; + } + + @Override + public boolean acceptContext(int x, int y) { + x -= parent.getCacheImageBounds().getX(); + y -= parent.getCacheImageBounds().getY(); + int i0 = Math.max(x - 2, 0); + int j0 = Math.max(y - 2, 0); + if (plotImage==null) return false; + int i1 = Math.min(x + 3, plotImage.getWidth()); + int j1 = Math.min(y + 3, plotImage.getHeight()); + for (int i = i0; i < i1; i++) { + for (int j = j0; j < j1; j++) { + if ( (this.plotImage.getRGB(i, j) & 0xff000000 ) > 0) { + return true; + } + } + } + return false; + } + /** + * Holds value of property print300dpi. + */ + private boolean print300dpi; + + /** + * Getter for property draw300dpi. + * @return Value of property draw300dpi. + */ + public boolean isPrint300dpi() { + return this.print300dpi; + } + + /** + * Setter for property draw300dpi. + * @param print300dpi New value of property draw300dpi. + */ + public void setPrint300dpi(boolean print300dpi) { + this.print300dpi = print300dpi; + } +} diff --git a/dasCore/src/main/java/org/das2/graph/Legend.java b/dasCore/src/main/java/org/das2/graph/Legend.java new file mode 100644 index 000000000..4b65743b4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/Legend.java @@ -0,0 +1,231 @@ +/* + * Legend.java + * + * Created on September 30, 2004, 5:01 PM + */ + +package org.das2.graph; + +import org.das2.system.DasLogger; +import org.das2.util.ObjectLocator; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.image.*; +import java.util.*; +import java.util.logging.Logger; +import javax.swing.*; +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.PropertyEditor; + +/** + * + * @author Jeremy + */ +public class Legend extends DasCanvasComponent { + + final static Logger logger= DasLogger.getLogger( DasLogger.GRAPHICS_LOG ); + + class LegendElement { + Icon icon; + Psym psym; + PsymConnector psymConnector; + Displayable rend; + Color color; + String label; + Legend parent; + + Icon getIcon() { + if ( rend!=null ) { + if ( rend instanceof Displayable ) { + return ((Displayable)rend).getListIcon(); + } else { + return null; + } + } else { + return icon; + } + } + + void update() { + if ( rend!=null ) { + if ( rend instanceof Displayable ) { + this.icon= ((Displayable)rend).getListIcon(); + } + } + } + + /** + * return the Displayable or null if no Displayable is associated with the + * legendElement. + */ + Displayable getDisplayable() { + return rend; + } + + private String getLabel() { + return label; + } + + private boolean isVisible() { + return ( rend==null || ! ( rend instanceof Renderer ) || ((Renderer)rend).isActive() ); + } + + LegendElement( Displayable rend, String label ) { + + this.icon= rend.getListIcon(); + this.label= label; + this.rend= rend; + } + + LegendElement( Icon icon, String label ) { + this.icon= icon; + this.label= label; + } + + + } + + ArrayList elements; // LegendElement + ObjectLocator locator; + + public Legend( ) { + elements= new ArrayList(); + getDasMouseInputAdapter().addMenuItem( new JMenuItem( getEditAction() ) ); + } + + private Action getEditAction() { + return new AbstractAction("Renderer Properties") { + public void actionPerformed(ActionEvent e) { + Point p= getDasMouseInputAdapter().getMousePressPosition(); + LegendElement item= (LegendElement)locator.closestObject(p); + if ( item==null ) return; + Displayable rend= item.getDisplayable(); + PropertyEditor editor= new PropertyEditor( rend ); + editor.showDialog(Legend.this); + } + }; + } + + public static Icon getIcon( Color color ) { + Image image= new BufferedImage(6,10,BufferedImage.TYPE_INT_RGB); + Graphics g2= image.getGraphics(); + g2.setColor(color); + g2.fillRect(0,0,6,10); + return new ImageIcon( image ); + } + + public void add( Displayable rend, String label ) { + LegendElement e= new LegendElement( rend, label ); + elements.add(e); + } + + public void remove( Displayable rend ) { + for ( int i=0; i icon.getIconHeight() ? fm.getHeight() : icon.getIconHeight() + border ); + int w1= itemWidth + x + 20; + if ( w1 > maxWidth ) maxWidth= w1; + y+= itemHeight; + } + + if ( !allVisible ) { + Font font0= g.getFont(); + g.setFont( font0.deriveFont(font0.getSize()*0.66f) ); + y+= g.getFontMetrics().getHeight()/2; + g.setFont(font0); + y+= g.getFontMetrics().getHeight()/2; + } + + g.setColor( new Color( 255, 255, 255, 240 ) ); + + g.fill( new Rectangle( 0, 0, maxWidth+10, y-1 ) ); + g.setColor( Color.DARK_GRAY ); + + g.draw( new Rectangle( 0,0, maxWidth+10, y-1 ) ); + + x= 5; + y= 5; + + for ( int i=0; i icon.getIconHeight() ? fm.getHeight() : icon.getIconHeight() + border ); + locator.addObject( new Rectangle( x, y-border/2, maxWidth, itemHeight ), e ); + y+= ( fm.getHeight() > icon.getIconHeight() ? fm.getHeight() : icon.getIconHeight() + border ); + } + + if ( !allVisible ) { + Font font0= g.getFont(); + g.setFont( font0.deriveFont(font0.getSize()*0.66f) ); + y+= g.getFontMetrics().getHeight()/2; + g.setColor( Color.DARK_GRAY ); + g.drawString( "\u00B9 not drawn", x+10, y ); + g.setFont(font0); + y+= g.getFontMetrics().getHeight()/2; + } + + g.setColor( color0 ); + int width= maxWidth+10+1; + int height= y; + + getDasMouseInputAdapter().paint(g1); + + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/Leveler.java b/dasCore/src/main/java/org/das2/graph/Leveler.java new file mode 100644 index 000000000..d2d832e15 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/Leveler.java @@ -0,0 +1,352 @@ +/* + * Leveler.java + * + * Created on April 22, 2003, 4:58 PM + */ + + +/** + * + * @author jbf + */ +// Manages a set of rows or columns, making sure they fill a space without +// overlapping. +package org.das2.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Leveler { + + ArrayList rows; // DasDevicePositions + ArrayList weights; // doubles + double interMargin; + + DasCanvas parent; + + private static class LevelRow extends DasRow { + + Leveler lev; + + /** Creates a new instance of DasLevelRow */ + public LevelRow( DasCanvas parent, Leveler lev, double nposition, double weight ) { + super(parent,0,0); + this.lev= lev; + lev.insertAt(nposition,this,weight); + } + + public LevelRow(DasCanvas parent, Leveler lev, double nposition) { + this( parent, lev, nposition, 1.0 ); + } + + public double getMaximum() { + if (lev==null) { + return 0.; + } else { + return lev.getMaximum(this); + } + } + + public double getMinimum() { + if (lev==null) { + return 0.; + } else { + return lev.getMinimum(this); + } + } + + public int getDMinimum() { + return (int)( getMinimum()*getDeviceSize() ); + } + + public int getDMaximum() { + return (int)( getMaximum()*getDeviceSize() ); + } + + public void setDPosition( int minimum, int maximum) { + lev.setMaximum(this,maximum/(float)getDeviceSize()); + lev.setMinimum(this,minimum/(float)getDeviceSize()); + } + + public void setDMinimum(int minimum) { + super.setDMinimum(minimum); + } + + public void setDMaximum(int maximum) { + super.setDMaximum(maximum); + } + } + + DasRow row; // this row contains the Leveler + + public Leveler( DasCanvas parent ) { + this( parent, new DasRow( parent, 0.05, 0.9 ) ); + } + + public Leveler( DasCanvas parent, DasRow row ) { + this.parent= parent; + this.row= row; + rows= new ArrayList(); + weights= new ArrayList(); + + interMargin= 0.03; + } + + public double getWeight( DasRow row ) { + int index= rows.indexOf(row); + return ((Double)this.weights.get(index)).doubleValue(); + } + + public double getPosition( DasRow row ) { + return row.getMaximum(); + } + + public DasRow addRow( double nposition, double weight ) { + LevelRow r= new LevelRow( parent, this, nposition, weight ); + return r; + } + + public DasRow addRow( double nposition ) { + LevelRow r= new LevelRow( parent, this, nposition ); + return r; + } + + public DasRow addRow( ) { + LevelRow r= new LevelRow( parent, this, 1.0 ); + return r; + } + + public DasRow whichRow( int y ) { + int i= objectIndexAt( y / parent.getHeight() ); + if ( i>=0 && i= nposition ) { + break; + } + } + rows.add(i,row); + weights.add(i,new Double(weight)); + for ( int ii=0; ii + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.Enumeration; +import org.das2.DasProperties; + +/** Type-safe enumeration class for the psym property of + * a DasSymbolPlot. + */ + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.*; +import java.awt.geom.Line2D; +import java.awt.image.*; + +public class Psym implements Enumeration, Displayable { + + private static final String[] NAMES = { + "none", //0 + "dots", //1 + "circles", //2 + "triangles", //3 + "cross", //4 + + }; + + public static final Psym NONE = new Psym(0); + + public static final Psym DOTS = new Psym(1); + + public static final Psym CIRCLES = new Psym(2); + + public static final Psym TRIANGLES = new Psym(3); + + public static final Psym CROSS = new Psym(4); + + private int nameIndex; + + private Line2D line = new Line2D.Double(); + private Ellipse2D ellipse = new Ellipse2D.Double(); + + ImageIcon imageIcon; + + private Psym(int nameIndex) { + this.nameIndex = nameIndex; + this.line= new Line2D.Double(); + Image i= new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB); + Graphics2D g= ( Graphics2D) i.getGraphics(); + g.setBackground( Color.white ); + g.setRenderingHints(DasProperties.getRenderingHints()); + g.setColor( Color.white ); + g.fillRect(0,0, 10,10); + g.setColor( Color.black ); + draw(g,5,5,2.f); + + this.imageIcon= new ImageIcon(i); + } + + public String toString() { + return NAMES[nameIndex]; + } + + public String getListLabel() { + return NAMES[nameIndex]; + } + + public Icon getListIcon() { + return imageIcon; + } + + /** Draw the psym at the given coordinates. + * if drawsLines() returns false, then the + * ix and iy parameters are ignored. + */ + public void draw(Graphics g, double x, double y, float size) { + //We are not guaranteed to get a Graphics2D. + Graphics2D g2 = (Graphics2D)(g instanceof Graphics2D ? g : null); + + switch (nameIndex) { + case 0: //LINES + break; + case 1: //DOTS + if ( size < 1f ) { + if (g instanceof Graphics2D) { + ellipse.setFrame(x, y, 1, 1); + g2.fill(ellipse); + } + else { + g.fillOval((int)x, (int)y, 1, 1); + } + } else { + if (g instanceof Graphics2D) { + ellipse.setFrame(x-size, y-size, size*2, size*2); + g2.fill(ellipse); + } + else { + g.fillOval((int)(x - size), (int)(x - size), (int)(size*2), (int)(size*2)); + } + } + break; + case 2: //CIRCLES + Color color0= g.getColor(); + ellipse.setFrame(x - size, y - size, size * 2, size * 2); + + Color backgroundColor= Color.white; + g.setColor(backgroundColor); + if (g instanceof Graphics2D) { + g2.fill(ellipse); + } + else { + g.fillOval((int)(x-size), (int)(y-size), (int)(size*2), (int)(size*2)); + } + g.setColor(color0); + if (g instanceof Graphics2D) { + g2.draw(ellipse); + } + else { + g.drawOval((int)(x-size), (int)(y-size), (int)(size*2), (int)(size*2)); + } + break; + case 3: //TRIANGLES + drawTriangle(g, x, y, size); + break; + case 4: //CROSS + if (g instanceof Graphics2D) { + line.setLine(x-size, y, x+size, y); + g2.draw(line); + line.setLine(x, y-size, x, y+size); + g2.draw(line); + } + else { + g.drawLine((int)(x-size), (int)y, (int)(x+size), (int)y); + g.drawLine((int)x, (int)(y-size), (int)x, (int)(y+size)); + } + break; + default: throw new IllegalArgumentException("Invalid nameIndex for psym"); + } + } + + public void drawTriangle(Graphics g, double x, double y, float size ) { + if (g instanceof Graphics2D) { + Graphics2D g2 = (Graphics2D)g; + line.setLine(x, y-size, x+size, y+size); + g2.draw(line); + line.setLine(x+size, y+size, x-size, y+size); + g2.draw(line); + line.setLine(x-size, y+size, x, y-size); + g2.draw(line); + } + else { + g.drawLine((int)x, (int)(y-size), (int)(x+size), (int)(y+size)); + g.drawLine((int)(x+size), (int)(y+size), (int)(x-size), (int)(y+size)); + g.drawLine((int)(x-size), (int)(y+size), (int)x, (int)(y-size)); + } + } + + public static Psym parsePsym(String str) { + if (str.equals("none")) { + return NONE; + } + else if (str.equals("dots")) { + return DOTS; + } + else if (str.equals("circles")) { + return CIRCLES; + } + else if (str.equals("triangles")) { + return TRIANGLES; + } + else if (str.equals("cross")) { + return CROSS; + } + else { + throw new IllegalArgumentException(str); + } + } + +} + diff --git a/dasCore/src/main/java/org/das2/graph/PsymConnector.java b/dasCore/src/main/java/org/das2/graph/PsymConnector.java new file mode 100644 index 000000000..869d9bc34 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/PsymConnector.java @@ -0,0 +1,141 @@ +/* File: PsymConnector.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on October 29, 2003, 10:42 AM by __FULLNAME__ <__EMAIL__> + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package org.das2.graph; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.Enumeration; +import org.das2.DasProperties; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; +import javax.swing.*; + +/** + * + * @author jbf + */ +public class PsymConnector implements Enumeration, Displayable { + + String name; + Icon imageIcon; + BasicStroke stroke; + BasicStroke cacheStroke; + float cacheWidth; + Line2D line; + + public static final PsymConnector NONE= new PsymConnector( "None", null ); + public static final PsymConnector SOLID= new PsymConnector( "Solid", new BasicStroke( 1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) ); + public static final PsymConnector DOTFINE= new PsymConnector( "DotFine", + new BasicStroke( 1.0f, BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, 1.0f, new float[] {1.5f,2.0f}, 0.f ) ); + public static final PsymConnector DASHFINE= new PsymConnector( "DashFine", + new BasicStroke( 1.0f, BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, 1.0f, new float[] {3.0f,2.0f}, 0.f ) ); + + private PsymConnector( String name, BasicStroke stroke ) { + line= new Line2D.Double(); + + this.name= name; + this.stroke= stroke; + this.cacheStroke= stroke; + if ( stroke!=null ) this.cacheWidth= cacheStroke.getLineWidth(); + + Image i= new BufferedImage(15,10,BufferedImage.TYPE_INT_RGB); + Graphics2D g= (Graphics2D)i.getGraphics(); + g.setRenderingHints(DasProperties.getRenderingHints()); + g.setColor(Color.LIGHT_GRAY); + g.fillRect(0,0,15,10); + g.setColor(Color.black); + drawLine(g,2,3,13,7,2.f); + this.imageIcon= new ImageIcon(i); + } + + protected Stroke getStroke( float width ) { + if ( width!=cacheWidth ) { + float[] dashArray= stroke.getDashArray(); + float[] dashArrayWidth=null; + if ( dashArray!=null ) { + dashArrayWidth= new float[dashArray.length]; + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.dataset.NoDataInIntervalException; +import org.das2.dataset.DataSetConsumer; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.VectorUtil; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableUtil; +import org.das2.dataset.VectorDataSet; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.util.DasExceptionHandler; +import java.beans.PropertyChangeListener; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.components.propertyeditor.Editable; +import org.das2.system.DasLogger; +import java.awt.geom.*; + +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import java.util.logging.Logger; +import org.w3c.dom.*; + +public abstract class Renderer implements DataSetConsumer, Editable { + + /** + * identifies the dataset (in the DataSetDescriptor sense) being plotted + * by the Renderer. May be null if no such identifier exists. See + * DataSetDescriptor.create( String id ). + */ + String dataSetId; + /** + * The dataset that is being plotted by the Renderer. + */ + protected DataSet ds; + /** + * Memento for x axis state last time updatePlotImage was called. + */ + private DasAxis.Memento xmemento; + /** + * Memento for y axis state last time updatePlotImage was called. + */ + private DasAxis.Memento ymemento; + /** + * plot containing this renderer + */ + DasPlot parent; + //DasPlot2 parent2; + /** + * the responsibility of keeping a relevant dataset loaded. Can be null + * if a loading mechanism is not used. The DataLoader will be calling + * setDataSet and setException. + */ + DataLoader loader; + + /** DataSet properties to override during the rendering process */ + HashMap override; + + /** + * When a dataset cannot be loaded, the exception causing the failure + * will be rendered instead. + */ + private Exception lastException; + + protected static Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + private String PROPERTY_ACTIVE = "active"; + private String PROPERTY_DATASET = "dataSet"; + + protected Renderer(DataSetDescriptor dsd) { + override = new HashMap<>(); + this.loader = new XAxisDataLoader(this, dsd); + } + + protected Renderer(DataSet ds) { + override = new HashMap<>(); + this.ds = ds; + this.loader = null; + } + + protected Renderer() { + this((DataSetDescriptor) null); + } + + public DasPlot getParent() { + return this.parent; + } + + protected void invalidateParentCacheImage() { + if (parent != null) parent.invalidateCacheImage(); + } + + /** + * returns the current dataset being displayed. + */ + public DataSet getDataSet() { + return this.ds; + } + + /** + * return the data for DataSetConsumer, which might be rebinned. + */ + public DataSet getConsumedDataSet() { + return this.ds; + } + private boolean dumpDataSet; + + /** Getter for property dumpDataSet. + * @return Value of property dumpDataSet. + * + */ + public boolean isDumpDataSet() { + return this.dumpDataSet; + } + + /** Setter for property dumpDataSet setting this to + * true causes the dataSet to be dumped. + * @param dumpDataSet New value of property dumpDataSet. + * + */ + public void setDumpDataSet(boolean dumpDataSet) { + this.dumpDataSet = dumpDataSet; + if (dumpDataSet == true) { + try { + if (ds == null) { + setDumpDataSet(false); + throw new DasException("data set is null"); + } else { + JFileChooser chooser = new JFileChooser(); + int xx = chooser.showSaveDialog(this.getParent()); + if (xx == JFileChooser.APPROVE_OPTION) { + File file = chooser.getSelectedFile(); + if (ds instanceof TableDataSet) { + TableUtil.dumpToAsciiStream((TableDataSet) ds, new FileOutputStream(file)); + } else if (ds instanceof VectorDataSet) { + VectorUtil.dumpToAsciiStream((VectorDataSet) ds, new FileOutputStream(file)); + } else { + throw new DasException("don't know how to serialize data set: " + ds); + } + } + setDumpDataSet(false); + } + } catch (Exception e) { + DasExceptionHandler.handle(e); + } + this.dumpDataSet = dumpDataSet; + } + } + + /** Get a copy of this renderer's display override's. + * + * @return The internal property override map wrapped as an unmodifiable map. + */ + public Map getOverrideMap(){ + return Collections.unmodifiableMap(override); + } + + /** Copy in a set of display overrides for this renderer + * + * @param m An object,object map. The keys should be property strings from + * DataSet + */ + public void setOverrideMap(Map m){ + override = new HashMap<>(m); + } + + /** Get and override property value without removing it + * + * @param name a well know dataset property name. These are provided + * in the DataSet class. + * @return The value for the override, see the DataSet class to cast the returned + * value to the appropriate type. + */ + public Object getOverride(String name){ + return override.get(name); + } + + /** Set a dataset property to override during the rendering process + * + * @param name a well know dataset property name. These are provided + * in the DataSet class. + * + * @return The value object for the property, or null if that property has not + * been overridden. See docs for the DataSet class to cast the return type + * appropriately. + */ + public void setOverride(String name, Object o){ + override.put(name, o); + } + + /** Determine if a particular DataSet property will be overridden during display + * processing + * @param name A DataSet property string + * @return true if overridden, false if the key name is not in the renderers internal + * override map + */ + public boolean hasOverride(String name){ + return override.get(name) == null; + } + + /** Remove an override + * + * @param name A DataSet property string + * @return The previous value held for the property override, or null the property in + * question did not have an override value + */ + public Object removeOverride(String name){ + return override.remove(name); + } + + public void setLastException(Exception e) { + this.lastException = e; + } + + public Exception getLastException() { + return this.lastException; + } + + public void setDataSet(DataSet ds) { + logger.finer("Renderer.setDataSet: " + ds); + DataSet oldDs = this.ds; + + if (oldDs != ds) { + this.ds = ds; + refresh(); + invalidateParentCacheImage(); + propertyChangeSupport.firePropertyChange(PROPERTY_DATASET, oldDs, ds); + } + } + + public void setException(Exception e) { + logger.finer("Renderer.setException: " + e); + Exception oldException = this.lastException; + this.lastException = e; + if (parent != null && oldException != e) { + //parent.markDirty(); + //parent.update(); + refresh(); + invalidateParentCacheImage(); + } + //refresh(); + } + + public void setDataSetID(String id) throws org.das2.DasException { + if (id == null) throw new NullPointerException("Null dataPath not allowed"); + if (id.equals("")) { + setDataSetDescriptor(null); + return; + } + try { + DataSetDescriptor dsd = DataSetDescriptor.create(id); + setDataSetDescriptor(dsd); + } catch (DasException ex) { + ex.printStackTrace(); + throw ex; + } + } + + public String getDataSetID() { + if (getDataSetDescriptor() == null) { + return ""; + } else { + return getDataSetDescriptor().getDataSetID(); + } + } + + /* + * returns the AffineTransform to transform data from the last updatePlotImage call + * axes (if super.updatePlotImage was called), or null if the transform is not possible. + * + * @depricated DasPlot handles the affine transform and previews now. + */ + protected AffineTransform getAffineTransform(DasAxis xAxis, DasAxis yAxis) { + if (xmemento == null) { + logger.fine("unable to calculate AT, because old transform is not defined."); + return null; + } else { + AffineTransform at = new AffineTransform(); + at = xAxis.getAffineTransform(xmemento, at); + at = yAxis.getAffineTransform(ymemento, at); + return at; + } + } + + /** Render is called whenever the image needs to be refreshed or the content + * has changed. This operation should occur with an animation-interactive + * time scale, and an image should be cached when this is not possible. The graphics + * object will have its origin at the upper-left corner of the screen. + */ + public abstract void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon); + + /** + * Returns true if the render thinks it can provide the context for a point. That is, + * the renderer affected that point, or nearby points. For example, this is used currently to provide + * a way for the operator to click on a plot and directly edit the renderer who drew the pixel. + * + * @param x the x coordinate in the canvas coordinate system. + * @param y the y coordinate in the canvas coordinate system. + */ + public boolean acceptContext(int x, int y) { + return false; + } + + protected void renderException(Graphics g, DasAxis xAxis, DasAxis yAxis, Exception e) { + + String s; + String message; + FontMetrics fm = g.getFontMetrics(); + + if (e instanceof NoDataInIntervalException) { + s = "no data in interval"; + message = e.getMessage(); + } else { + s = e.getMessage(); + message = ""; + if (s == null || s.length() < 10) { + s = e.toString(); + } + } + + if (!message.equals("")) { + s += ":!c" + message; + } + + parent.postMessage(this, s, DasPlot.ERROR, null, null); + + } + + /** updatePlotImage is called once the expensive operation of loading + * the data is completed. This operation should occur on an interactive + * time scale. This is an opportunity to create a cache + * image of the data with the current axis state, when the render + * operation cannot operate on an animation interactive time scale. + * Codes can no longer assume that the xAxis sent to render will be in + * the same state as it was when updatePlotImage was called, so use + * the getAffineTransform method. Only Renderer should call this method! + */ + public void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor) throws DasException { + } + + protected void refreshImage() { + if (getParent() != null) { + refresh(); + } + } + + /** + * Something has changed with the Render, and the plot should come back + * to allow this render to repaint. Its cacheImage is invalidated and a + * repaint is posted on the event thread. + */ + public void update() { + if (getParent() != null) getParent().repaint(); + logger.fine("Renderer.update"); + if (parent != null) { + java.awt.EventQueue eventQueue = + Toolkit.getDefaultToolkit().getSystemEventQueue(); + DasRendererUpdateEvent drue = new DasRendererUpdateEvent(parent, this); + eventQueue.postEvent(drue); + } else { + logger.fine("update but parent was null"); + } + } + + /** + * updateImmediately is called from DasPlot when it gets an update event from the + * AWT Event thread. This should trigger a data load and eventually a refresh to + * render the dataset. + */ + protected void updateImmediately() { + logger.finer("entering Renderer.updateImmediately"); + if (parent == null || !parent.isDisplayable()) { + return; + } + + // If there's a loader, then tell him he might want to load new data. + if (loader != null) { + loader.update(); + } + + // The parent has already used an AffineTransform to preview the image, but + // we might as well re-render using the dataset we have. + refresh(); + } + + /** + * recalculate the plot image and repaint. The dataset or exception have + * been updated, or the axes have changed, so we need to perform updatePlotImage + * to do the expensive parts of rendering. + */ + protected void refresh() { + if (!isActive()) return; + + logger.fine("entering Renderer.refresh"); + if (parent == null) { + logger.fine("null parent in refresh"); + return; + } + if (!parent.isDisplayable()) { + logger.fine("parent not displayable"); + return; + } + + Runnable run = new Runnable() { + + public void run() { + logger.fine("update plot image"); + try { + if (parent != null) { // TODO: make synchronized, but this is non-trivial since deadlock. + final ProgressMonitor progressPanel = DasApplication.getDefaultApplication().getMonitorFactory().getMonitor(parent, "Rebinning data set", "updatePlotImage"); + updatePlotImage(parent.getXAxis(), parent.getYAxis(), progressPanel); + xmemento = parent.getXAxis().getMemento(); + ymemento = parent.getYAxis().getMemento(); + lastException = null; + } else { + return; + } + } catch (DasException de) { + // TODO: there's a problem here, that the Renderer can set its own exception and dataset. This needs to be addressed, or handled as an invalid state. + logger.warning("exception: " + de); + ds = null; + } catch (RuntimeException re) { + logger.warning("exception: " + re); + re.printStackTrace(); + parent.invalidateCacheImage(); + parent.repaint(); + throw re; + } finally { + // this code used to call finished() on the progressPanel + } + + logger.fine("invalidate parent cacheImage and repaint"); + + parent.invalidateCacheImage(); + parent.repaint(); + } + }; + + boolean async = false; // updating was done on the event thread... + if (EventQueue.isDispatchThread()) { + if (async) { + new Thread(run, "updatePlotImage").start(); + } else { + run.run(); + } + } else { + run.run(); + } + } + + public void setDataSetDescriptor(DataSetDescriptor dsd) { + if (loader == null) { + logger.warning("installing loader--danger!"); + loader = new XAxisDataLoader(this, dsd); + } + if (loader instanceof XAxisDataLoader) { + ((XAxisDataLoader) loader).setDataSetDescriptor(dsd); + if (parent != null) { + parent.markDirty(); + parent.update(); + } + this.ds = null; + } else { + throw new RuntimeException("loader is not based on DataSetDescriptor"); + } + + } + + public DataLoader getDataLoader() { + return this.loader; + } + + public void setDataSetLoader(DataLoader loader) { + this.loader = loader; + if (loader != null) loader.update(); + } + + public DataSetDescriptor getDataSetDescriptor() { + if (loader == null) { + return null; + } else { + if (this.loader instanceof XAxisDataLoader) { + return ((XAxisDataLoader) loader).getDataSetDescriptor(); + } else { + return null; + } + } + } + + protected void installRenderer() { + // override me + } + + protected void uninstallRenderer() { + // override me + } + + protected Element getDOMElement(Document document) { + // override me + return null; + } + private boolean overloading = false; + + public boolean isOverloading() { + return this.overloading; + } + + public void setOverloading(boolean overloading) { + this.overloading = overloading; + update(); + } + /** + * Holds value of property active. + */ + private boolean active = true; + + /** + * Getter for property active. + * @return Value of property active. + */ + public boolean isActive() { + return this.active; + } + + /** + * Setter for property active. + * @param active New value of property active. + */ + public void setActive(boolean active) { + boolean oldValue = this.active; + this.active = active; + propertyChangeSupport.firePropertyChange(PROPERTY_ACTIVE, oldValue, active); + update(); + } + /** + * Utility field used by bound properties. + */ + protected java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(propertyName, listener); + } + + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(propertyName, listener); + } + + /** Reload the current dataset */ + void reload(){ + + // If we have a data set factory, have it remake the data set, otherwise, just + // repaint. + if( getDataSetDescriptor() != null){ + loader.setReloadDataSet(true); + loader.update(); + } + + refresh(); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/RowRowConnector.java b/dasCore/src/main/java/org/das2/graph/RowRowConnector.java new file mode 100644 index 000000000..9b47f8610 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/RowRowConnector.java @@ -0,0 +1,89 @@ +package org.das2.graph; + +import java.awt.*; + +public class RowRowConnector extends DasCanvasComponent implements java.beans.PropertyChangeListener { + + private DasCanvas parent; + + private DasRow leftRow; + private DasRow rightRow; + + private DasColumn leftColumn; + private DasColumn rightColumn; + + private boolean centerRightRow= false; // this causes a funny bug + + public RowRowConnector( DasCanvas parent, DasRow leftRow, DasRow rightRow, DasColumn leftColumn, DasColumn rightColumn ) { + this.leftRow= leftRow; + this.rightRow= rightRow; + this.leftColumn= leftColumn; + this.rightColumn= rightColumn; + this.parent= parent; + leftRow.addPropertyChangeListener(this); + rightRow.addPropertyChangeListener(this); + rightColumn.addPropertyChangeListener(this); + leftColumn.addPropertyChangeListener(this); + } + + private Rectangle getMyBounds() { + if ( centerRightRow ) { + int rightHeight= rightRow.getHeight(); + int leftCenter= leftRow.getDMiddle(); + if ( leftCenter - rightHeight/2 < 0 ) leftCenter= rightHeight / 2; + if ( leftCenter + rightHeight/2 > parent.getHeight() ) leftCenter= parent.getHeight() - rightHeight / 2; + rightRow.setDPosition( leftCenter-rightHeight/2, leftCenter+rightHeight/2 ); + } + + int xleft= leftColumn.getDMaximum(); + int xright= rightColumn.getDMaximum(); + int ylow= Math.max( leftRow.getDMaximum(), rightRow.getDMaximum() ); + int yhigh= Math.min( leftRow.getDMinimum(), rightRow.getDMinimum() ); + + Rectangle result= new Rectangle( xleft, yhigh, (xright-xleft), (ylow-yhigh+2) ); + return result; + } + + public void setLeftRow( DasRow row ) { + this.leftRow= row; + update(); + } + + public void resize() { + setBounds(getMyBounds()); + } + + protected void paintComponent(Graphics g1) { + Graphics2D g= (Graphics2D)g1.create(); + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + g.translate(-getX(), -getY()); + + int hlen=3; + + int x1= leftColumn.getDMaximum()+hlen; + int x2= rightColumn.getDMaximum()-hlen; + int ylow1= leftRow.getDMaximum(); + int ylow2= rightRow.getDMaximum(); + int yhigh1= leftRow.getDMinimum(); + int yhigh2= rightRow.getDMinimum(); + + g.setColor(Color.lightGray); + g.draw(new java.awt.geom.Line2D.Double(x1-hlen,ylow1,x1,ylow1)); + g.draw(new java.awt.geom.Line2D.Double(x2,ylow2,x2+hlen,ylow2)); + g.draw(new java.awt.geom.Line2D.Double(x1,ylow1,x2,ylow2)); + g.draw(new java.awt.geom.Line2D.Double(x1-hlen,yhigh1,x1,yhigh1)); + g.draw(new java.awt.geom.Line2D.Double(x2,yhigh2,x2+hlen,yhigh2)); + g.draw(new java.awt.geom.Line2D.Double(x1,yhigh1,x2,yhigh2)); + + g.dispose(); + + getDasMouseInputAdapter().paint(g1); + } + + public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent) { + markDirty(); + update(); + } + +} + diff --git a/dasCore/src/main/java/org/das2/graph/SeriesRenderer.java b/dasCore/src/main/java/org/das2/graph/SeriesRenderer.java new file mode 100644 index 000000000..4c6464678 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/SeriesRenderer.java @@ -0,0 +1,1586 @@ +/* File: SeriesRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasProperties; +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.LengthDragRenderer; +import org.das2.event.MouseModule; +import org.das2.system.DasLogger; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; + +import java.awt.image.BufferedImage; +import java.awt.image.IndexColorModel; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.logging.Logger; +import javax.swing.ImageIcon; +import org.das2.datum.UnitsUtil; +import org.das2.util.monitor.ProgressMonitor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * SeriesRender is a high-performance replacement for the SymbolLineRenderer. + * The SymbolLineRenderer is limited to about 30,000 points, beyond which + * contracts for speed start breaking degrading usability. The goal of the + * SeriesRenderer is to plot 1,000,000 points without breaking the contracts. + * + * @author jbf + */ +public class SeriesRenderer extends Renderer implements Displayable { + + private DefaultPlotSymbol psym = DefaultPlotSymbol.CIRCLES; + private double symSize = 3.0; // radius in pixels + + private double lineWidth = 1.0; // width in pixels + + private boolean histogram = false; + private PsymConnector psymConnector = PsymConnector.SOLID; + private FillStyle fillStyle = FillStyle.STYLE_FILL; + private int renderCount = 0; + private int updateImageCount = 0; + private Color color = Color.BLACK; + private long lastUpdateMillis; + private boolean antiAliased = "on".equals(DasProperties.getInstance().get("antiAlias")); + private int firstIndex;/* the index of the first point drawn, nonzero when X is monotonic and we can clip. */ + + private int lastIndex;/* the non-inclusive index of the last point drawn. */ + + boolean updating = false; + private Logger log = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + /** + * indicates the dataset was clipped by dataSetSizeLimit + */ + private boolean dataSetClipped; + + public SeriesRenderer() { + super(); + updatePsym(); + } + Image psymImage; + Image[] coloredPsyms; + int cmx, cmy; + FillRenderElement fillElement = new FillRenderElement(); + ErrorBarRenderElement errorElement = new ErrorBarRenderElement(); + PsymConnectorRenderElement psymConnectorElement = new PsymConnectorRenderElement(); + PsymConnectorRenderElement[] extraConnectorElements; + PsymRenderElement psymsElement = new PsymRenderElement(); + public static final String PROPERTY_X_DELTA_PLUS = "X_DELTA_PLUS"; + public static final String PROPERTY_X_DELTA_MINUS = "X_DELTA_MINUS"; + public static final String PROPERTY_Y_DELTA_PLUS = "Y_DELTA_PLUS"; + public static final String PROPERTY_Y_DELTA_MINUS = "Y_DELTA_MINUS"; + + interface RenderElement { + + int render(Graphics2D g, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon); + + void update(DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon); + + boolean acceptContext(Point2D.Double dp); + } + + class PsymRenderElement implements RenderElement { + + protected GeneralPath psymsPath; // store the location of the psyms here. + + int[] colors; // store the color index of each psym + + int[] ipsymsPath; // store the location of the psyms here, evens=x, odds=y + + int count; // the number of points to plot + + + /** + * render the psyms by stamping an image at the psym location. The intent is to + * provide fast rendering by reducing fidelity. + * On 20080206, this was measured to run at 320pts/millisecond for FillStyle.FILL + * On 20080206, this was measured to run at 300pts/millisecond in FillStyle.OUTLINE + */ + private int renderStamp(Graphics2D g, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + + VectorDataSet colorByDataSet = null; + if (colorByDataSetId != null && !colorByDataSetId.equals("")) { + colorByDataSet = (VectorDataSet) vds.getPlanarView(colorByDataSetId); + } + + if (colorByDataSet != null) { + for (int i = 0; i < count; i++) { + int icolor = colors[i]; + g.drawImage(coloredPsyms[icolor], ipsymsPath[i * 2] - cmx, ipsymsPath[i * 2 + 1] - cmy, parent); + } + } else { + for (int i = 0; i < count; i++) { + g.drawImage(psymImage, ipsymsPath[i * 2] - cmx, ipsymsPath[i * 2 + 1] - cmy, parent); + } + } + + return count; + + } + + /** + * Render the psyms individually. This is the highest fidelity rendering, and + * should be used in printing. + * On 20080206, this was measured to run at 45pts/millisecond in FillStyle.FILL + * On 20080206, this was measured to run at 9pts/millisecond in FillStyle.OUTLINE + */ + private int renderDraw(Graphics2D graphics, DasAxis xAxis, DasAxis yAxis, VectorDataSet dataSet, ProgressMonitor mon) { + + float fsymSize = (float) symSize; + + if (colorByDataSetId != null && !colorByDataSetId.equals("")) { + colorByDataSet = (VectorDataSet) dataSet.getPlanarView(colorByDataSetId); + } + + graphics.setStroke(new BasicStroke((float) lineWidth)); + + Color[] ccolors = null; + if (colorByDataSet != null) { + IndexColorModel icm = colorBar.getIndexColorModel(); + ccolors = new Color[icm.getMapSize()]; + for (int j = 0; j < icm.getMapSize(); j++) { + ccolors[j] = new Color(icm.getRGB(j)); + } + } + + if (colorByDataSet != null) { + for (int i = 0; i < count; i++) { + graphics.setColor(ccolors[colors[i]]); + psym.draw(graphics, ipsymsPath[i * 2], ipsymsPath[i * 2 + 1], fsymSize, fillStyle); + } + + } else { + for (int i = 0; i < count; i++) { + psym.draw(graphics, ipsymsPath[i * 2], ipsymsPath[i * 2 + 1], fsymSize, fillStyle); + } + } + + return count; + + } + + public synchronized int render(Graphics2D graphics, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + int i; + if ( parent==null ) return 0; + if (stampPsyms && !parent.getCanvas().isPrintingThread()) { + i = renderStamp(graphics, xAxis, yAxis, vds, mon); + } else { + i = renderDraw(graphics, xAxis, yAxis, vds, mon); + } + return i; + } + + public synchronized void update(DasAxis xAxis, DasAxis yAxis, VectorDataSet dataSet, ProgressMonitor mon) { + + VectorDataSet colorByDataSet = null; + Units cunits = null; + if (colorByDataSetId != null && !colorByDataSetId.equals("")) { + colorByDataSet = (VectorDataSet) dataSet.getPlanarView(colorByDataSetId); + if (colorByDataSet != null) { + cunits = colorByDataSet.getYUnits(); + } + } + + + Units xUnits = xAxis.getUnits(); + Units yUnits = yAxis.getUnits(); + + double x, y; + int fx, fy; + + psymsPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 110 * (lastIndex - firstIndex) / 100); + ipsymsPath = new int[(lastIndex - firstIndex) * 2]; + colors = new int[lastIndex - firstIndex + 2]; + + int index = firstIndex; + + x = dataSet.getXTagDouble(index, xUnits); + y = dataSet.getDouble(index, yUnits); + fx = (int) xAxis.transform(x, xUnits); + fy = (int) yAxis.transform(y, yUnits); + + int i = 0; + for (; index < lastIndex; index++) { + x = dataSet.getXTagDouble(index, xUnits); + y = dataSet.getDouble(index, yUnits); + + final boolean isValid = yUnits.isValid(y) && xUnits.isValid(x); + + fx = (int) xAxis.transform(x, xUnits); + fy = (int) yAxis.transform(y, yUnits); + + if (isValid) { + ipsymsPath[i * 2] = fx; + ipsymsPath[i * 2 + 1] = fy; + if (colorByDataSet != null) { + colors[i] = colorBar.indexColorTransform(colorByDataSet.getDouble(index, cunits), cunits); + } + i++; + } + } + + count = i; + + } + + public boolean acceptContext(Point2D.Double dp) { + if (ipsymsPath == null) { + return false; + } + double rad = Math.max(symSize, 5); + + for (int index = firstIndex; index < lastIndex; index++) { + int i = index - firstIndex; + if (dp.distance(ipsymsPath[i * 2], ipsymsPath[i * 2 + 1]) < rad) { + return true; + } + } + return false; + } + } + + class ErrorBarRenderElement implements RenderElement { + + GeneralPath p; + + public int render(Graphics2D g, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + if (p == null) { + return 0; + } + g.draw(p); + return lastIndex - firstIndex; + } + + public synchronized void update(DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + VectorDataSet deltaPlusY = (VectorDataSet) vds.getPlanarView(PROPERTY_Y_DELTA_PLUS); + VectorDataSet deltaMinusY = (VectorDataSet) vds.getPlanarView(PROPERTY_Y_DELTA_MINUS); + + p = null; + + if (deltaMinusY == null) { + return; + } + if (deltaMinusY == null) { + return; + } + Units xunits = vds.getXUnits(); + Units yunits = vds.getYUnits(); + Units yoffsetUnits = yunits.getOffsetUnits(); + + p = new GeneralPath(); + for (int i = firstIndex; i < lastIndex; i++) { + float ix = (float) xAxis.transform(vds.getXTagDouble(i, xunits), xunits); + double dp= deltaPlusY.getDouble(i, yoffsetUnits); + double dm= deltaMinusY.getDouble(i, yoffsetUnits); + if ( yoffsetUnits.isValid(dp) && yoffsetUnits.isValid(dm) ) { + float iym = (float) yAxis.transform(vds.getDouble(i, yunits) - dm, yunits); + float iyp = (float) yAxis.transform(vds.getDouble(i, yunits) + dp, yunits); + p.moveTo(ix, iym); + p.lineTo(ix, iyp); + } + } + + } + + public boolean acceptContext(Point2D.Double dp) { + return p != null && p.contains(dp.x - 2, dp.y - 2, 5, 5); + + } + } + + class PsymConnectorRenderElement implements RenderElement { + + private GeneralPath path1; + private Color color; // override default color + + + public int render(Graphics2D g, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + if (path1 == null) { + return 0; + } + if (color != null) { + g.setColor(color); + } + psymConnector.draw(g, path1, (float) lineWidth); + return 0; + } + + public synchronized void update(DasAxis xAxis, DasAxis yAxis, VectorDataSet dataSet, ProgressMonitor mon) { + Units xUnits = xAxis.getUnits(); + Units yUnits = yAxis.getUnits(); + + if ( lastIndex-firstIndex==0 ) { + this.path1= null; + return; + } + + GeneralPath newPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 110 * (lastIndex - firstIndex) / 100); + + Datum sw = DataSetUtil.guessXTagWidth(dataSet); + double xSampleWidth; + boolean logStep; + if ( UnitsUtil.isRatiometric(sw.getUnits())) { + xSampleWidth = sw.doubleValue(Units.logERatio); + logStep= true; + } else { + xSampleWidth = sw.doubleValue(xUnits.getOffsetUnits()); + logStep= false; + } + + + /* fuzz the xSampleWidth */ + xSampleWidth = xSampleWidth * 1.10; + + double x = Double.NaN; + double y = Double.NaN; + + double x0 = Double.NaN; /* the last plottable point */ + double y0 = Double.NaN; /* the last plottable point */ + + float fx = Float.NaN; + float fy = Float.NaN; + float fx0 = Float.NaN; + float fy0 = Float.NaN; + + int index; + + index = firstIndex; + x = (double) dataSet.getXTagDouble(index, xUnits); + y = (double) dataSet.getDouble(index, yUnits); + + // first point // + logger.fine("firstPoint moveTo,LineTo= " + x + "," + y); + fx = (float) xAxis.transform(x, xUnits); + fy = (float) yAxis.transform(y, yUnits); + if (histogram) { + float fx1 = midPoint( xAxis, x, xUnits, xSampleWidth, sw.getUnits(), -0.5 ); + newPath.moveTo(fx1, fy); + newPath.lineTo(fx, fy); + } else { + newPath.moveTo(fx, fy); + newPath.lineTo(fx, fy); + } + + x0 = x; + y0 = y; + fx0 = fx; + fy0 = fy; + + index++; + + // now loop through all of them. // + boolean ignoreCadence= ! cadenceCheck; + for (; index < lastIndex; index++) { + + x = dataSet.getXTagDouble(index, xUnits); + y = dataSet.getDouble(index, yUnits); + + final boolean isValid = yUnits.isValid(y) && xUnits.isValid(x); + + fx = (float) xAxis.transform(x, xUnits); + fy = (float) yAxis.transform(y, yUnits); + + //double tx= xAxis.transformFast( x, xUnits ); + + //System.err.println( ""+(float)tx+ " " + fx ); + + if (isValid) { + double step= logStep ? Math.log(x/x0) : x-x0; + if ( ( ignoreCadence && step < xSampleWidth*20 ) || step < xSampleWidth) { + // draw connect-a-dot between last valid and here + if (histogram) { + float fx1 = (fx0 + fx) / 2; + newPath.lineTo(fx1, fy0); + newPath.lineTo(fx1, fy); + newPath.lineTo(fx, fy); + } else { + newPath.lineTo(fx, fy); // this is the typical path + + } + + } else { + // introduce break in line + if (histogram) { + float fx1 = (float) xAxis.transform(x0 + xSampleWidth / 2, xUnits); + newPath.lineTo(fx1, fy0); + + fx1 = (float) xAxis.transform(x - xSampleWidth / 2, xUnits); + newPath.moveTo(fx1, fy); + newPath.lineTo(fx, fy); + + } else { + newPath.moveTo(fx, fy); + newPath.lineTo(fx, fy); + } + + } // else introduce break in line + + x0 = x; + y0 = y; + fx0 = fx; + fy0 = fy; + + } else { + newPath.moveTo(fx0, fy0); // place holder + + } + + } // for ( ; index < ixmax && lastIndex; index++ ) + + + if (!histogram && simplifyPaths && colorByDataSet == null) { + //j System.err.println( "input: " ); + //j System.err.println( GraphUtil.describe( newPath, true) ); + this.path1 = GraphUtil.reducePath(newPath.getPathIterator(null), new GeneralPath(GeneralPath.WIND_NON_ZERO, lastIndex - firstIndex)); + } else { + this.path1 = newPath; + } + + } + + public boolean acceptContext(Point2D.Double dp) { + return this.path1 != null && path1.intersects(dp.x - 5, dp.y - 5, 10, 10); + } + } + + private float midPoint(DasAxis axis, double d1, Units units, double delta, Units offsetUnits, double alpha ) { + float fx1; + if (axis.isLog() && offsetUnits==Units.logERatio ) { + fx1 = (float) axis.transform( Math.exp( Math.log(d1) + delta * alpha ), units); + } else { + fx1 = (float) axis.transform( d1 + delta * alpha, units); + } + return fx1; + } + + class FillRenderElement implements RenderElement { + + private GeneralPath fillToRefPath1; + + public int render(Graphics2D g, DasAxis xAxis, DasAxis yAxis, VectorDataSet vds, ProgressMonitor mon) { + if ( fillToRefPath1==null ) { + return 0; + } + g.setColor(fillColor); + g.fill(fillToRefPath1); + return 0; + } + + public void update(DasAxis xAxis, DasAxis yAxis, VectorDataSet dataSet, ProgressMonitor mon) { + Units xUnits = xAxis.getUnits(); + Units yUnits = yAxis.getUnits(); + + GeneralPath fillPath = new GeneralPath(GeneralPath.WIND_NON_ZERO, 110 * (lastIndex - firstIndex) / 100); + + Datum sw = DataSetUtil.guessXTagWidth(dataSet); + double xSampleWidth; + if ( UnitsUtil.isRatiometric(sw.getUnits())) { + xSampleWidth = sw.doubleValue(Units.logERatio); + } else { + xSampleWidth = sw.doubleValue(xUnits.getOffsetUnits()); + } + + /* fuzz the xSampleWidth */ + xSampleWidth = xSampleWidth * 1.10; + + if (reference != null && reference.getUnits() != yAxis.getUnits()) { + // switch the units to the axis units. + reference = yAxis.getUnits().createDatum(reference.doubleValue(reference.getUnits())); + } + + if (reference == null) { + reference = yUnits.createDatum(yAxis.isLog() ? 1.0 : 0.0); + } + + double yref = (double) reference.doubleValue(yUnits); + + double x = Double.NaN; + double y = Double.NaN; + + double x0 = Double.NaN; /* the last plottable point */ + double y0 = Double.NaN; /* the last plottable point */ + + float fyref = (float) yAxis.transform(yref, yUnits); + float fx = Float.NaN; + float fy = Float.NaN; + float fx0 = Float.NaN; + float fy0 = Float.NaN; + + int index; + + index = firstIndex; + x = (double) dataSet.getXTagDouble(index, xUnits); + y = (double) dataSet.getDouble(index, yUnits); + + // first point // + fx = (float) xAxis.transform(x, xUnits); + fy = (float) yAxis.transform(y, yUnits); + if (histogram) { + float fx1; + fx1= midPoint( xAxis, x, xUnits, xSampleWidth, sw.getUnits(), -0.5 ); + fillPath.moveTo(fx1, fyref); + fillPath.lineTo(fx1, fy); + fillPath.lineTo(fx, fy); + + } else { + fillPath.moveTo(fx, fyref); + fillPath.lineTo(fx, fy); + + } + + x0 = x; + y0 = y; + fx0 = fx; + fy0 = fy; + + if (psymConnector != PsymConnector.NONE || fillToReference) { + // now loop through all of them. // + + for (; index < lastIndex; index++) { + + x = dataSet.getXTagDouble(index, xUnits); + y = dataSet.getDouble(index, yUnits); + + final boolean isValid = yUnits.isValid(y) && xUnits.isValid(x); + + fx = (float) xAxis.transform(x, xUnits); + fy = (float) yAxis.transform(y, yUnits); + + if (isValid) { + if ((x - x0) < xSampleWidth) { + // draw connect-a-dot between last valid and here + if (histogram) { + float fx1 = (fx0 + fx) / 2; //sloppy with ratiometric spacing + fillPath.lineTo(fx1, fy0); + fillPath.lineTo(fx1, fy); + fillPath.lineTo(fx, fy); + } else { + fillPath.lineTo(fx, fy); + } + + } else { + // introduce break in line + if (histogram) { + float fx1 = midPoint( xAxis, x0, xUnits, xSampleWidth, sw.getUnits(), 0.5 ); + fillPath.lineTo(fx1, fy0); + fillPath.lineTo(fx1, fyref); + fx1 = midPoint( xAxis, x, xUnits, xSampleWidth, sw.getUnits(), -0.5 ); + fillPath.moveTo(fx1, fyref); + fillPath.lineTo(fx1, fy); + fillPath.lineTo(fx, fy); + + } else { + fillPath.lineTo(fx0, fyref); + fillPath.moveTo(fx, fyref); + fillPath.lineTo(fx, fy); + } + + } // else introduce break in line + + x0 = x; + y0 = y; + fx0 = fx; + fy0 = fy; + + } + + } // for ( ; index < ixmax && lastIndex; index++ ) + + } + + fillPath.lineTo(fx0, fyref); + this.fillToRefPath1 = fillPath; + + if (simplifyPaths) { + fillToRefPath1 = GraphUtil.reducePath(fillToRefPath1.getPathIterator(null), new GeneralPath(GeneralPath.WIND_NON_ZERO, lastIndex - firstIndex)); + } + + } + + public boolean acceptContext(Point2D.Double dp) { + return fillToRefPath1 != null && fillToRefPath1.contains(dp); + } + } + + /** + * updates the image of a psym that is stamped + */ + private void updatePsym() { + int sx = 6+(int) Math.ceil(symSize + 2 * lineWidth); + int sy = 6+(int) Math.ceil(symSize + 2 * lineWidth); + double dcmx, dcmy; + dcmx = (lineWidth + (int) Math.ceil( ( symSize ) / 2)) +2 ; + dcmy = (lineWidth + (int) Math.ceil( ( symSize ) / 2)) +2 ; + BufferedImage image = new BufferedImage(sx, sy, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) image.getGraphics(); + + Object rendering = antiAliased ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, rendering); + g.setColor(color); + if (parent != null) { + g.setBackground(parent.getBackground()); + } + + g.setStroke(new BasicStroke((float) lineWidth)); + + psym.draw(g, dcmx, dcmy, (float) symSize, fillStyle); + psymImage = image; + + if (colorBar != null) { + IndexColorModel model = colorBar.getIndexColorModel(); + coloredPsyms = new Image[model.getMapSize()]; + for (int i = 0; i < model.getMapSize(); i++) { + Color c = new Color(model.getRGB(i)); + image = new BufferedImage(sx, sy, BufferedImage.TYPE_INT_ARGB); + g = (Graphics2D) image.getGraphics(); + if (parent != null) { + g.setBackground(parent.getBackground()); + } + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, rendering); + g.setColor(c); + g.setStroke(new BasicStroke((float) lineWidth)); + + psym.draw(g, dcmx, dcmy, (float) symSize, this.fillStyle); + coloredPsyms[i] = image; + } + + } + + cmx = (int) dcmx; + cmy = (int) dcmy; + + update(); + } + + private void reportCount() { + //if ( renderCount % 100 ==0 ) { + //System.err.println(" updates: "+updateImageCount+" renders: "+renderCount ); + //new Throwable("").printStackTrace(); + //} + } + + public synchronized void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + logger.fine( "ds: "+this.ds+", drawing indeces "+this.firstIndex+" to "+this.lastIndex ); + if ( parent==null ) return; + if ( this.ds == null && getLastException() != null) { + parent.postException(this, getLastException()); + return; + } + + if ( legendLabel!=null && legendLabel.length()>0 ) { + parent.addToLegend( this, (ImageIcon)this.getListIcon(), 0, legendLabel ); + } + + renderCount++; + reportCount(); + + long timer0 = System.currentTimeMillis(); + + DataSet dataSet = getDataSet(); + + if (dataSet == null) { + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("null data set"); + parent.postMessage(this, "no data set", DasPlot.INFO, null, null); + return; + } + + if (dataSet.getXLength() == 0) { + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("empty data set"); + parent.postMessage(this, "empty data set", DasPlot.INFO, null, null); + return; + } + + TableDataSet tds = null; + VectorDataSet vds = null; + boolean plottable = false; + + if (dataSet instanceof VectorDataSet) { + vds = (VectorDataSet) dataSet; + plottable = dataSet.getYUnits().isConvertableTo(yAxis.getUnits()); + } else if (dataSet instanceof TableDataSet) { + tds = (TableDataSet) dataSet; + plottable = tds.getZUnits().isConvertableTo(yAxis.getUnits()); + } + plottable = plottable && dataSet.getXUnits().isConvertableTo(xAxis.getUnits()); + + if (!plottable) { + parent.postMessage( this, "data set units cannot convert to axis units", DasPlot.WARNING, null, null ); + return; + } + + logger.fine("rendering points: " + lastIndex + " " + firstIndex); + if (lastIndex == firstIndex) { + parent.postMessage(SeriesRenderer.this, "dataset contains no valid data", DasPlot.INFO, null, null); + return; + } + + logger.fine("render data set " + dataSet); + + Graphics2D graphics = (Graphics2D) g.create(); + + if (antiAliased) { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } else { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + + if (tds != null) { + if (extraConnectorElements == null) { + return; + } + if (tds.getYLength(0) != extraConnectorElements.length) { + return; + } else { + int maxWidth = 0; + for (int j = 0; j < tds.getYLength(0); j++) { + String label = String.valueOf(tds.getYTagDatum(0, j)); + maxWidth = Math.max(maxWidth, g.getFontMetrics().stringWidth(label)); + } + for (int j = 0; j < tds.getYLength(0); j++) { + vds = tds.getYSlice(j, 0); + + graphics.setColor(color); + if (extraConnectorElements[j] != null) { // thread race + + extraConnectorElements[j].render(graphics, xAxis, yAxis, vds, mon); + + String label = String.valueOf(tds.getYTagDatum(0, j)).trim(); + + parent.addToLegend( this, (ImageIcon)GraphUtil.colorIcon( extraConnectorElements[j].color, 5, 5 ), j, label ); + } + } + } + return; + } + + if (this.fillToReference) { + fillElement.render(graphics, xAxis, yAxis, vds, mon); + } + + + graphics.setColor(color); + log.finest("drawing psymConnector in " + color); + + int count=0; + + count= Math.max( count, psymConnectorElement.render(graphics, xAxis, yAxis, vds, mon) ); + + count= Math.max( count, errorElement.render(graphics, xAxis, yAxis, vds, mon) ); + + if (psym != DefaultPlotSymbol.NONE) { + + count= Math.max( count, psymsElement.render(graphics, xAxis, yAxis, vds, mon) ); + +//double simplifyFactor = (double) ( i - firstIndex ) / (lastIndex - firstIndex); + if ( count==0 ) { + parent.postMessage( this, "no valid points", DasPlot.INFO, null, null ); + } + mon.finished(); + } + +//g.drawString( "renderCount="+renderCount+" updateCount="+updateImageCount,xAxis.getColumn().getDMinimum()+5, yAxis.getRow().getDMinimum()+20 ); + long milli = System.currentTimeMillis(); + long renderTime = (milli - timer0); + double dppms = (lastIndex - firstIndex) / (double) renderTime; + + setRenderPointsPerMillisecond(dppms); + + logger.finer("render: " + renderTime + " total:" + (milli - lastUpdateMillis) + " fps:" + (1000. / (milli - lastUpdateMillis)) + " pts/ms:" + dppms); + lastUpdateMillis = milli; + + if (dataSetClipped) { + parent.postMessage(this, "dataset clipped at " + dataSetSizeLimit + " points", DasPlot.WARNING, null, null); + } + + if ( count>0 && lastIndex - firstIndex < 2 ) { + parent.postMessage(this, "less than two points visible", DasPlot.INFO, null, null); + } + + } + + /** + * updates firstIndex and lastIndex that point to the part of + * the data that is plottable. The plottable part is the part that + * might be visible while limiting the number of plotted points. + */ + private synchronized void updateFirstLast(DasAxis xAxis, DasAxis yAxis, VectorDataSet dataSet) { + + Units xUnits = xAxis.getUnits(); + Units yUnits = yAxis.getUnits(); + + int ixmax; + int ixmin; + + Boolean xMono = (Boolean) dataSet.getProperty(DataSet.PROPERTY_X_MONOTONIC); + if (xMono != null && xMono.booleanValue()) { + DatumRange visibleRange = xAxis.getDatumRange(); + if (parent.isOverSize()) { + Rectangle plotBounds = parent.getCacheImageBounds(); + if ( plotBounds!=null ) { + visibleRange = new DatumRange(xAxis.invTransform(plotBounds.x), xAxis.invTransform(plotBounds.x + plotBounds.width)); + } + + } + ixmin = DataSetUtil.getPreviousColumn(dataSet, visibleRange.min()); + ixmax = DataSetUtil.getNextColumn(dataSet, visibleRange.max()) + 1; // +1 is for exclusive. + + } else { + ixmin = 0; + ixmax = dataSet.getXLength(); + } + + Datum sw = DataSetUtil.guessXTagWidth(dataSet); + double xSampleWidth = sw.doubleValue(xUnits.getOffsetUnits()); + + /* fuzz the xSampleWidth */ + xSampleWidth = xSampleWidth * 1.10; + + double x = Double.NaN; + double y = Double.NaN; + + int index; + + // find the first valid point, set x0, y0 // + for (index = ixmin; index < ixmax; index++) { + x = (double) dataSet.getXTagDouble(index, xUnits); + y = (double) dataSet.getDouble(index, yUnits); + + final boolean isValid = yUnits.isValid(y) && xUnits.isValid(x); + if (isValid) { + firstIndex = index; // TODO: what if no valid points? + + index++; + break; + } + } + + // find the last valid point, minding the dataSetSizeLimit + int pointsPlotted = 0; + for (index = firstIndex; index < ixmax && pointsPlotted < dataSetSizeLimit; index++) { + y = dataSet.getDouble(index, yUnits); + + final boolean isValid = yUnits.isValid(y) && xUnits.isValid(x); + + if (isValid) { + pointsPlotted++; + } + } + + if (index < ixmax && pointsPlotted == dataSetSizeLimit) { + dataSetClipped = true; + } + + lastIndex = index; + + } + + /** + * do the same as updatePlotImage, but use AffineTransform to implement axis transform. + */ + @Override + public synchronized void updatePlotImage(DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor) { + logger.fine("enter updatePlotImage"); + + updating = true; + + updateImageCount++; + + reportCount(); + + try { + super.updatePlotImage(xAxis, yAxis, monitor); + } catch (DasException e) { + // it doesn't throw DasException, but interface requires exception, jbf 5/26/2005 + throw new RuntimeException(e); + } + + + DataSet dataSet = getDataSet(); + + if (dataSet == null ) { + logger.fine("dataset was null"); + return; + } + + if ( dataSet.getXLength() == 0) { + logger.fine("dataset was empty"); + return; + } + + TableDataSet tds = null; + VectorDataSet vds = null; + boolean plottable = false; + + if (dataSet instanceof VectorDataSet) { + vds = (VectorDataSet) dataSet; + plottable = dataSet.getYUnits().isConvertableTo(yAxis.getUnits()); + } else if (dataSet instanceof TableDataSet) { + tds = (TableDataSet) dataSet; + plottable = tds.getZUnits().isConvertableTo(yAxis.getUnits()); + } + plottable = plottable && dataSet.getXUnits().isConvertableTo(xAxis.getUnits()); + + if (!plottable) { + return; + } + + + logger.fine("entering updatePlotImage"); + long t0 = System.currentTimeMillis(); + + dataSetClipped = false; + + + if (vds != null) { + updateFirstLast(xAxis, yAxis, vds); + + if (fillToReference) { + fillElement.update(xAxis, yAxis, vds, monitor); + } + if (psymConnector != PsymConnector.NONE) { + psymConnectorElement.update(xAxis, yAxis, vds, monitor); + } + + errorElement.update(xAxis, yAxis, vds, monitor); + psymsElement.update(xAxis, yAxis, vds, monitor); + + } else if (tds != null) { + extraConnectorElements = new PsymConnectorRenderElement[tds.getYLength(0)]; + for (int i = 0; i < tds.getYLength(0); i++) { + extraConnectorElements[i] = new PsymConnectorRenderElement(); + + float[] colorHSV = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); + if (colorHSV[2] < 0.7f) { + colorHSV[2] = 0.7f; + } + if (colorHSV[1] < 0.7f) { + colorHSV[1] = 0.7f; + } + extraConnectorElements[i].color = Color.getHSBColor(i / 6.f, colorHSV[1], colorHSV[2]); + vds = tds.getYSlice(i, 0); + + if (i == 0) { + updateFirstLast(xAxis, yAxis, vds); // minimal support assumes vert slice data is all valid or all invalid. + + } + extraConnectorElements[i].update(xAxis, yAxis, vds, monitor); + } + } + + if (getParent() != null) { + getParent().repaint(); + } + + logger.fine("done updatePlotImage in " + (System.currentTimeMillis() - t0) + " ms"); + updating = false; + long milli = System.currentTimeMillis(); + long renderTime = (milli - t0); + double dppms = (lastIndex - firstIndex) / (double) renderTime; + + setUpdatesPointsPerMillisecond(dppms); + } + + protected void installRenderer() { + if (!DasApplication.getDefaultApplication().isHeadless()) { + DasMouseInputAdapter mouseAdapter = parent.mouseAdapter; + DasPlot p = parent; + mouseAdapter.addMouseModule(new MouseModule(p, new LengthDragRenderer(p, p.getXAxis(), p.getYAxis()), "Length")); + } + + updatePsym(); + } + + protected void uninstallRenderer() { + } + + public Element getDOMElement(Document document) { + return null; + } + + /** + * get an Icon representing the trace. This will be an ImageIcon. + * TODO: cache the result to support use in legend. + * @return + */ + public javax.swing.Icon getListIcon() { + Image i = new BufferedImage(15, 10, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) i.getGraphics(); + g.setRenderingHints(DasProperties.getRenderingHints()); + if ( parent!=null ) g.setBackground(parent.getBackground()); + + // leave transparent if not white + if (color.equals(Color.white)) { + g.setColor(Color.GRAY); + } else { + g.setColor(new Color(0, 0, 0, 0)); + } + + g.fillRect(0, 0, 15, 10); + + if (fillToReference) { + g.setColor(fillColor); + Polygon p = new Polygon(new int[]{2, 13, 13, 2}, new int[]{3, 7, 10, 10}, 4); + g.fillPolygon(p); + } + + g.setColor(color); + Stroke stroke0 = g.getStroke(); + getPsymConnector().drawLine(g, 2, 3, 13, 7, 1.5f); + g.setStroke(stroke0); + psym.draw(g, 7, 5, 3.f, fillStyle); + return new ImageIcon(i); + } + + public String getListLabel() { + return String.valueOf(this.getDataSetDescriptor()); + } + +// ------- Begin Properties --------------------------------------------- // + public PsymConnector getPsymConnector() { + return psymConnector; + } + + public void setPsymConnector(PsymConnector p) { + PsymConnector old = this.psymConnector; + if (!p.equals(psymConnector)) { + psymConnector = p; + refreshImage(); + propertyChangeSupport.firePropertyChange("psymConnector", old, p); + } + + } + + /** Getter for property psym. + * @return Value of property psym. + */ + public PlotSymbol getPsym() { + return this.psym; + } + + /** Setter for property psym. + * @param psym New value of property psym. + */ + public void setPsym(PlotSymbol psym) { + if (psym == null) { + throw new NullPointerException("psym cannot be null"); + } + + if (psym != this.psym) { + Object oldValue = this.psym; + this.psym = (DefaultPlotSymbol) psym; + updatePsym(); + update(); + propertyChangeSupport.firePropertyChange("psym", oldValue, psym); + } + + } + + /** Getter for property symsize. + * @return Value of property symsize. + */ + public double getSymSize() { + return this.symSize; + } + + /** Setter for property symsize. + * @param symSize New value of property symsize. + */ + public void setSymSize(double symSize) { + double old = this.symSize; + if (this.symSize != symSize) { + this.symSize = symSize; + setPsym(this.psym); + updatePsym(); + update(); + propertyChangeSupport.firePropertyChange("symSize", new Double(old), new Double(symSize)); + } + + } + + /** Getter for property color. + * @return Value of property color. + */ + public Color getColor() { + return color; + } + + /** Setter for property color. + * @param color New value of property color. + */ + public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("null color"); + } + Color old = this.color; + if (!this.color.equals(color)) { + this.color = color; + updatePsym(); + refreshImage(); + propertyChangeSupport.firePropertyChange("color", old, color); + updatePsym(); + } + + } + + public double getLineWidth() { + return lineWidth; + } + + public void setLineWidth(double f) { + double old = this.lineWidth; + if (this.lineWidth != f) { + lineWidth = f; + updatePsym(); + refreshImage(); + propertyChangeSupport.firePropertyChange("lineWidth", new Double(old), new Double(f)); + } + + } + + /** Getter for property antiAliased. + * @return Value of property antiAliased. + * + */ + public boolean isAntiAliased() { + return this.antiAliased; + } + + /** Setter for property antiAliased. + * @param antiAliased New value of property antiAliased. + * + */ + public void setAntiAliased(boolean antiAliased) { + boolean old = this.antiAliased; + this.antiAliased = antiAliased; + updatePsym(); + refreshImage(); + propertyChangeSupport.firePropertyChange("antiAliased", old, antiAliased); + } + + public boolean isHistogram() { + return histogram; + } + + public void setHistogram(final boolean b) { + boolean old = b; + if (b != histogram) { + histogram = b; + refreshImage(); + propertyChangeSupport.firePropertyChange("histogram", old, antiAliased); + } + + } + /** + * Holds value of property selected. + */ + private boolean selected; + + /** + * Getter for property selected. + * @return Value of property selected. + */ + public boolean isSelected() { + return this.selected; + } + + /** + * Setter for property selected. + * @param selected New value of property selected. + */ + public void setSelected(boolean selected) { + this.selected = selected; + } + /** + * Holds value of property fillColor. + */ + private Color fillColor = Color.lightGray; + + /** + * Getter for property fillReference. + * @return Value of property fillReference. + */ + public Color getFillColor() { + return this.fillColor; + } + + /** + * Setter for property fillReference. + * @param fillReference New value of property fillReference. + */ + public void setFillColor(Color color) { + Color old = this.fillColor; + if (!this.fillColor.equals(color)) { + this.fillColor = color; + update(); + propertyChangeSupport.firePropertyChange("fillColor", old, color); + } + + } + /** + * Holds value of property colorByDataSetId. + */ + private String colorByDataSetId = null; + + /** + * Getter for property colorByDataSetId. + * @return Value of property colorByDataSetId. + */ + public String getColorByDataSetId() { + return this.colorByDataSetId; + } + + /** + * The dataset plane to use to get colors. If this is null or "", then + * no coloring is done. (Note the default plane cannot be used to color.) + * @param colorByDataSetId New value of property colorByDataSetId. + */ + public void setColorByDataSetId(String colorByDataSetId) { + String oldVal = this.colorByDataSetId; + this.colorByDataSetId = colorByDataSetId; + update(); + propertyChangeSupport.firePropertyChange("colorByDataSetId", oldVal, colorByDataSetId); + } + /** + * Holds value of property colorBar. + */ + private DasColorBar colorBar; + + /** + * Getter for property colorBar. + * @return Value of property colorBar. + */ + public DasColorBar getColorBar() { + return this.colorBar; + } + + /** + * Setter for property colorBar. + * @param colorBar New value of property colorBar. + */ + public void setColorBar(DasColorBar colorBar) { + this.colorBar = colorBar; + colorBar.addPropertyChangeListener(new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + if (colorByDataSetId != null && !colorByDataSetId.equals("")) { + update(); + } + } + }); + refreshImage(); + updatePsym(); + } + /** + * Holds value of property fillToReference. + */ + private boolean fillToReference; + + /** + * Getter for property fillToReference. + * @return Value of property fillToReference. + */ + public boolean isFillToReference() { + return this.fillToReference; + } + + /** + * Setter for property fillToReference. + * @param fillToReference New value of property fillToReference. + */ + public void setFillToReference(boolean fillToReference) { + boolean old = this.fillToReference; + if (this.fillToReference != fillToReference) { + this.fillToReference = fillToReference; + update(); + propertyChangeSupport.firePropertyChange("fillToReference", old, fillToReference); + } + + } + /** + * Holds value of property reference. + */ + private Datum reference = Units.dimensionless.createDatum(0); + + /** + * Getter for property reference. + * @return Value of property reference. + */ + public Datum getReference() { + return this.reference; + } + + /** + * Setter for property reference. + * @param reference New value of property reference. + */ + public void setReference(Datum reference) { + Datum old = this.reference; + if (!this.reference.equals(reference)) { + this.reference = reference; + refreshImage(); + propertyChangeSupport.firePropertyChange("reference", old, reference); + } + + } + /** + * Holds value of property colorByDataSet. + */ + private org.das2.dataset.VectorDataSet colorByDataSet; + + /** + * Getter for property colorByDataSet. + * @return Value of property colorByDataSet. + */ + public org.das2.dataset.VectorDataSet getColorByDataSet() { + return this.colorByDataSet; + } + + /** + * Setter for property colorByDataSet. + * @param colorByDataSet New value of property colorByDataSet. + */ + public void setColorByDataSet(org.das2.dataset.VectorDataSet colorByDataSet) { + this.colorByDataSet = colorByDataSet; + refreshImage(); + } + /** + * Holds value of property resetDebugCounters. + */ + private boolean resetDebugCounters; + + /** + * Getter for property resetDebugCounters. + * @return Value of property resetDebugCounters. + */ + public boolean isResetDebugCounters() { + return this.resetDebugCounters; + } + + /** + * Setter for property resetDebugCounters. + * @param resetDebugCounters New value of property resetDebugCounters. + */ + public void setResetDebugCounters(boolean resetDebugCounters) { + if (resetDebugCounters) { + renderCount = 0; + updateImageCount = 0; + update(); + } + + } + /** + * Holds value of property simplifyPaths. + */ + private boolean simplifyPaths = true; + + /** + * Getter for property simplifyPaths. + * @return Value of property simplifyPaths. + */ + public boolean isSimplifyPaths() { + return this.simplifyPaths; + } + + /** + * Setter for property simplifyPaths. + * @param simplifyPaths New value of property simplifyPaths. + */ + public void setSimplifyPaths(boolean simplifyPaths) { + this.simplifyPaths = simplifyPaths; + refreshImage(); + } + private boolean stampPsyms = true; + public static final String PROP_STAMPPSYMS = "stampPsyms"; + + public boolean isStampPsyms() { + return this.stampPsyms; + } + + public void setStampPsyms(boolean newstampPsyms) { + boolean oldstampPsyms = stampPsyms; + this.stampPsyms = newstampPsyms; + propertyChangeSupport.firePropertyChange(PROP_STAMPPSYMS, oldstampPsyms, newstampPsyms); + refreshImage(); + } + + public FillStyle getFillStyle() { + return fillStyle; + } + + public void setFillStyle(FillStyle fillStyle) { + this.fillStyle = fillStyle; + updatePsym(); + refreshImage(); + } + + /** + * If non-null and non-zero-length, use this label to describe the renderer + * in the plot's legend. + */ + public static final String PROP_LEGENDLABEL = "legendLabel"; + + protected String legendLabel = ""; + + public String getLegendLabel() { + return legendLabel; + } + + public void setLegendLabel(String legendLabel) { + String oldLegendLabel = this.legendLabel; + this.legendLabel = legendLabel; + propertyChangeSupport.firePropertyChange(PROP_LEGENDLABEL, oldLegendLabel, legendLabel); + } + + + @Override + public boolean acceptContext(int x, int y) { + boolean accept = false; + + Point2D.Double dp = new Point2D.Double(x, y); + + if (this.fillToReference && fillElement.acceptContext(dp)) { + accept = true; + } + + if ((!accept) && psymConnectorElement.acceptContext(dp)) { + accept = true; + } + + if ((!accept) && extraConnectorElements != null) { + for (int j = 0; j < extraConnectorElements.length; j++) { + if (!accept && extraConnectorElements[j] != null && extraConnectorElements[j].acceptContext(dp)) { + accept = true; + } + } + } + + if ((!accept) && psymsElement.acceptContext(dp)) { + accept = true; + } + + if ((!accept) && errorElement.acceptContext(dp)) { + accept = true; + } + + return accept; + } + /** + * property dataSetSizeLimit is the maximum number of points that will be rendered. + * This is introduced because large datasets cause java2D plotting to fail. + * When the size limit is reached, the data is clipped and a message is displayed. + */ + private int dataSetSizeLimit = 200000; + + /** + * Getter for property dataSetSizeLimit. + * @return Value of property dataSetSizeLimit. + */ + public int getDataSetSizeLimit() { + return this.dataSetSizeLimit; + } + + /** + * Setter for property dataSetSizeLimit. + * @param dataSetSizeLimit New value of property dataSetSizeLimit. + */ + public void setDataSetSizeLimit(int dataSetSizeLimit) { + int oldDataSetSizeLimit = this.dataSetSizeLimit; + this.dataSetSizeLimit = dataSetSizeLimit; + refreshImage(); + propertyChangeSupport.firePropertyChange("dataSetSizeLimit", new Integer(oldDataSetSizeLimit), new Integer(dataSetSizeLimit)); + } + private double updatesPointsPerMillisecond; + public static final String PROP_UPDATESPOINTSPERMILLISECOND = "updatesPointsPerMillisecond"; + + public double getUpdatesPointsPerMillisecond() { + return this.updatesPointsPerMillisecond; + } + + public void setUpdatesPointsPerMillisecond(double newupdatesPointsPerMillisecond) { + //double oldupdatesPointsPerMillisecond = updatesPointsPerMillisecond; + this.updatesPointsPerMillisecond = newupdatesPointsPerMillisecond; + //propertyChangeSupport.firePropertyChange(PROP_UPDATESPOINTSPERMILLISECOND, oldupdatesPointsPerMillisecond, newupdatesPointsPerMillisecond); + } + private double renderPointsPerMillisecond; + public static final String PROP_RENDERPOINTSPERMILLISECOND = "renderPointsPerMillisecond"; + + public double getRenderPointsPerMillisecond() { + return this.renderPointsPerMillisecond; + } + + public void setRenderPointsPerMillisecond(double newrenderPointsPerMillisecond) { + //double oldrenderPointsPerMillisecond = renderPointsPerMillisecond; + this.renderPointsPerMillisecond = newrenderPointsPerMillisecond; + //propertyChangeSupport.firePropertyChange(PROP_RENDERPOINTSPERMILLISECOND, oldrenderPointsPerMillisecond, newrenderPointsPerMillisecond); + } + + public int getFirstIndex() { + return this.firstIndex; + } + + public int getLastIndex() { + return this.lastIndex; + } + + protected boolean cadenceCheck = true; + public static final String PROP_CADENCECHECK = "cadenceCheck"; + + public boolean isCadenceCheck() { + return cadenceCheck; + } + + /** + * If true, then use a cadence estimate to determine and indicate data gaps. + * @param cadenceCheck + */ + public void setCadenceCheck(boolean cadenceCheck) { + boolean oldCadenceCheck = this.cadenceCheck; + this.cadenceCheck = cadenceCheck; + refreshImage(); + propertyChangeSupport.firePropertyChange(PROP_CADENCECHECK, oldCadenceCheck, cadenceCheck); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/SpectrogramRenderer.java b/dasCore/src/main/java/org/das2/graph/SpectrogramRenderer.java new file mode 100755 index 000000000..0584b8457 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/SpectrogramRenderer.java @@ -0,0 +1,680 @@ +/* File: SpectrogramRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.graph; + +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.MouseModule; +import org.das2.event.HorizontalSlicerMouseModule; +import org.das2.event.HorizontalDragRangeSelectorMouseModule; +import org.das2.event.VerticalSlicerMouseModule; +import org.das2.event.CrossHairMouseModule; +import org.das2.components.propertyeditor.Enumeration; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.NoDataInIntervalException; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.DataSet; +import org.das2.DasApplication; +import org.das2.DasException; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import org.das2.components.HorizontalSpectrogramSlicer; +import org.das2.components.VerticalSpectrogramAverager; +import org.das2.components.VerticalSpectrogramSlicer; +import org.das2.dasml.FormBase; +import org.das2.datum.DatumRange; +import org.das2.datum.InconvertibleUnitsException; +import org.das2.datum.Units; +import org.das2.system.DasLogger; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.*; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.geom.*; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.awt.image.IndexColorModel; +import java.awt.image.WritableRaster; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.net.URL; +import java.text.ParseException; +import java.util.Arrays; +import java.util.logging.Logger; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class SpectrogramRenderer extends Renderer implements TableDataSetConsumer, org.das2.components.propertyeditor.Displayable { + + private Object lockObject = new Object(); + private DasColorBar colorBar; + private Image plotImage; + private Rectangle plotImageBounds; + + private byte[] raster; + private int rasterWidth, rasterHeight; + DatumRange imageXRange; + DatumRange imageYRange; + DasAxis.Memento xmemento, ymemento, cmemento; + int updateImageCount = 0, renderCount = 0; + private TableDataSet rebinDataSet; // simpleTableDataSet at pixel resolution + + protected class RebinListener implements PropertyChangeListener { + + public void propertyChange(PropertyChangeEvent e) { + update(); + refreshImage(); + } + } + RebinListener rebinListener = new RebinListener(); + private static Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + /** Holds value of property rebinner. */ + private RebinnerEnum rebinnerEnum; + + public static class RebinnerEnum implements Enumeration { + + DataSetRebinner rebinner; + String label; + + public RebinnerEnum(DataSetRebinner rebinner, String label) { + this.rebinner = rebinner; + this.label = label; + } + public static final RebinnerEnum binAverage = new RebinnerEnum(new AverageTableRebinner(), "binAverage"); + //public static final RebinnerEnum nearestNeighbor = new RebinnerEnum(new NearestNeighborTableRebinner(), "nearestNeighbor"); + public static final RebinnerEnum nearestNeighbor; + public static final RebinnerEnum binAverageNoInterpolate; + public static final RebinnerEnum binAverageNoInterpolateNoEnlarge; + + + static { + AverageTableRebinner rebinner = new AverageTableRebinner(); + rebinner.setInterpolate(false); + binAverageNoInterpolate = new RebinnerEnum(rebinner, "noInterpolate"); + + rebinner = new AverageTableRebinner(); + rebinner.setInterpolate(false); + rebinner.setEnlargePixels(false); + binAverageNoInterpolateNoEnlarge = new RebinnerEnum(rebinner, "noInterpolateNoEnlarge"); + + rebinner = new AverageTableRebinner(); + rebinner.setInterpolateType( AverageTableRebinner.Interpolate.NearestNeighbor ); + nearestNeighbor = new RebinnerEnum(rebinner, "nearestNeighbor"); + } + + /*public static final RebinnerEnum binAverage= new RebinnerEnum(new AverageTableRebinner(),"binAverage"); + public static final RebinnerEnum nearestNeighbor; + public static final RebinnerEnum binAverageNoInterpolate= new RebinnerEnum(new AverageNoInterpolateTableRebinner(),"noInterpolate"); + public static final RebinnerEnum binAverageNoInterpolateNoEnlarge; + static { + AverageNoInterpolateTableRebinner rebin= new AverageNoInterpolateTableRebinner(); + rebin.setNearestNeighbor(true); + nearestNeighbor= new RebinnerEnum(rebin, "nearestNeighbor"); + AverageTableRebinner rebinner; + rebinner = new AverageTableRebinner(); + rebinner.setInterpolate(false); + rebinner.setEnlargePixels(false); + binAverageNoInterpolateNoEnlarge = new RebinnerEnum(rebinner, "noInterpolateNoEnlarge"); + }*/ + public Icon getListIcon() { + URL resource = SpectrogramRenderer.class.getResource("/images/icons/rebin." + label + ".png"); + if (resource == null) { + return null; + } + return new ImageIcon(resource); + } + + public String toString() { + return this.label; + } + + DataSetRebinner getRebinner() { + return this.rebinner; + } + } + + /** Creates a new instance of SpectrogramRenderer. + * @param colorBar A colorBar object I presume? + */ + public SpectrogramRenderer(DasColorBar colorBar) { + this(null, colorBar); + } + + /** Creates a new instance of SpectrogramRenderer. + * + * @param dsd And object used to provide data to the renderer based on an + * index range. Note: This may be null. + * @param colorBar A colorBar object I presume? + */ + public SpectrogramRenderer(DataSetDescriptor dsd, DasColorBar colorBar) { + super(dsd); + this.colorBar = colorBar; + if (this.colorBar != null) { + colorBar.addPropertyChangeListener("dataMinimum", rebinListener); + colorBar.addPropertyChangeListener("dataMaximum", rebinListener); + colorBar.addPropertyChangeListener("log", rebinListener); + colorBar.addPropertyChangeListener(DasColorBar.PROPERTY_TYPE, rebinListener); + colorBar.addPropertyChangeListener(DasColorBar.PROPERTY_FILL_COLOR, rebinListener); + } + setRebinner(SpectrogramRenderer.RebinnerEnum.binAverage); + } + + /** Creates a new instance of SpectrogramRenderer + * @deprecated use {link + * #SpectrogramRenderer(org.das2.dataset.DataSetDescriptor, + * org.das2.graph.DasColorBar)} + */ + public SpectrogramRenderer(DasPlot parent, DataSetDescriptor dsd, DasColorBar colorBar) { + this(dsd, colorBar); + this.parent = parent; + } + + public DasAxis getZAxis() { + return colorBar; //.getAxis(); + } + + public DasColorBar getColorBar() { + return colorBar; + } + + public void setColorBar(DasColorBar cb) { + if (colorBar == cb) { + return; + } + if (colorBar != null) { + colorBar.removePropertyChangeListener("dataMinimum", rebinListener); + colorBar.removePropertyChangeListener("dataMaximum", rebinListener); + colorBar.removePropertyChangeListener("log", rebinListener); + colorBar.removePropertyChangeListener(DasColorBar.PROPERTY_TYPE, rebinListener); + colorBar.removePropertyChangeListener(DasColorBar.PROPERTY_FILL_COLOR, rebinListener); + if (parent != null && parent.getCanvas() != null) { + parent.getCanvas().remove(colorBar); + } + } + colorBar = cb; + if (colorBar != null) { + colorBar.addPropertyChangeListener("dataMinimum", rebinListener); + colorBar.addPropertyChangeListener("dataMaximum", rebinListener); + colorBar.addPropertyChangeListener("log", rebinListener); + colorBar.addPropertyChangeListener(DasColorBar.PROPERTY_TYPE, rebinListener); + colorBar.addPropertyChangeListener(DasColorBar.PROPERTY_FILL_COLOR, rebinListener); + if (parent != null && parent.getCanvas() != null) { + parent.getCanvas().add(colorBar); + } + } + } + + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + logger.finer("entering SpectrogramRenderer.render"); + Graphics2D g2 = (Graphics2D) g; + + renderCount++; + reportCount(); + synchronized (lockObject) { + if (plotImage == null) { + if (getLastException() != null) { + if (getLastException() instanceof NoDataInIntervalException) { + parent.postMessage(this, "no data in interval:!c" + getLastException().getMessage(), DasPlot.WARNING, null, null); + } else { + parent.postException(this, getLastException()); + } + } else { + if (getDataSet() == null) { + parent.postMessage(this, "no data set", DasPlot.INFO, null, null); + } else if (getDataSet().getXLength() == 0) { + parent.postMessage(this, "empty data set", DasPlot.INFO, null, null); + } + } + } else if (plotImage != null) { + Point2D p; + p = new Point2D.Float( plotImageBounds.x, plotImageBounds.y ); + int x = (int) (p.getX() + 0.5); + int y = (int) (p.getY() + 0.5); + if (parent.getCanvas().isPrintingThread() && print300dpi) { + AffineTransformOp atop = new AffineTransformOp(AffineTransform.getScaleInstance(4, 4), AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + BufferedImage image300 = atop.filter((BufferedImage) plotImage, null); + AffineTransform atinv; + try { + atinv = atop.getTransform().createInverse(); + } catch (NoninvertibleTransformException ex) { + throw new RuntimeException(ex); + } + atinv.translate(x * 4, y * 4); + g2.drawImage(image300, atinv, getParent()); + } else { + g2.drawImage(plotImage, x, y, getParent()); + } + } + } + } + int count = 0; + private boolean sliceRebinnedData = true; + + /** + * transforms the simpleTableDataSet into a Raster byte array. The rows of + * the table are adjacent in the output byte array. + */ + private static byte[] transformSimpleTableDataSet(TableDataSet rebinData, DasColorBar cb, boolean flipY) { + + if (rebinData.tableCount() > 1) { + throw new IllegalArgumentException("TableDataSet contains more than one table"); + } + logger.fine("converting to pixel map"); + //TableDataSet weights= (TableDataSet)rebinData.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + int itable = 0; + int ny = rebinData.getYLength(itable); + int h = ny; + int nx = rebinData.tableEnd(itable) - rebinData.tableStart(itable); + int w = nx; + int icolor; + + Units units = cb.getUnits(); + int ncolor = cb.getType().getColorCount(); + + TableDataSet weights = (TableDataSet) rebinData.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + + byte[] pix = new byte[nx * ny]; + Arrays.fill(pix, (byte) cb.getFillColorIndex()); + + + for (int i = rebinData.tableStart(itable); i < rebinData.tableEnd(itable); i++) { + for (int j = 0; j < ny; j++) { + if (weights == null || weights.getDouble(i, j, Units.dimensionless) > 0.) { + int index; + index = (i - 0) + (ny - j - 1) * nx; + icolor = cb.indexColorTransform(rebinData.getDouble(i, j, units), units); + pix[index] = (byte) icolor; + } + } + } + + return pix; + } + + private void reportCount() { + if (updateImageCount % 10 == 0) { + //System.err.println(" updates: "+updateImageCount+" renders: "+renderCount ); + } + + } + +// private String getDsId() { +// if ( this.ds==null ) { +// return "null"; +// } else if ( this.ds.getXLength()==0 ) { +// return "empty"+this.ds.hashCode(); +// } else { +// return this.ds.getXTagDatum(0).toString(); +// } +// } + public void updatePlotImage( DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor ) throws DasException { + logger.finer("entering SpectrogramRenderer.updatePlotImage"); + updateImageCount++; + reportCount(); + try { + try { + + BufferedImage plotImage2; // index color model + + synchronized (lockObject) { + + Rectangle plotImageBounds2= parent.getUpdateImageBounds(); + + if ( raster != null + && xmemento != null && ymemento != null + && xAxis.getMemento().equals(xmemento) + && yAxis.getMemento().equals(ymemento) + && colorBar.getMemento().equals(cmemento) + && plotImageBounds2.width==rasterWidth // TODO: figure out how plotImageBounds2 and xmemento get out of sync. + && plotImageBounds2.height==rasterHeight ) { + logger.fine("same xaxis, yaxis, reusing raster"); + + } else { + + if (getParent() == null || plotImageBounds2==null || plotImageBounds2.width <= 1 || plotImageBounds2.height <= 1) { + logger.finest("canvas not useable!!!"); + return; + } + + if (this.ds == null) { + logger.fine("got null dataset, setting image to null"); + plotImage = null; + plotImageBounds= null; + rebinDataSet = null; + imageXRange = null; + imageYRange = null; + getParent().repaint(); + return; + + } + + if (this.ds.getXLength() == 0) { + logger.fine("got empty dataset, setting image to null"); + plotImage = null; + plotImageBounds= null; + rebinDataSet = null; + imageXRange = null; + imageYRange = null; + getParent().repaint(); + return; + } + + if (!this.ds.getXUnits().isConvertableTo(xAxis.getUnits())) { + logger.fine("dataset units are incompatable with x axis."); + return; + } + + if (!this.ds.getYUnits().isConvertableTo(yAxis.getUnits())) { + logger.fine("dataset units are incompatable with y axis."); + return; + } + + if (!((TableDataSet)this.ds).getZUnits().isConvertableTo(colorBar.getUnits()) ) { + logger.fine("dataset units are incompatable with colorbar."); + return; + } + + RebinDescriptor xRebinDescriptor; + xRebinDescriptor = new RebinDescriptor( + xAxis.invTransform(plotImageBounds2.x), + xAxis.invTransform(plotImageBounds2.x+plotImageBounds2.width), + plotImageBounds2.width, + xAxis.isLog()); + + RebinDescriptor yRebinDescriptor = new RebinDescriptor( + yAxis.invTransform(plotImageBounds2.y+plotImageBounds2.height), + yAxis.invTransform(plotImageBounds2.y), + plotImageBounds2.height, + yAxis.isLog()); + + imageXRange = xAxis.getDatumRange(); + imageYRange = yAxis.getDatumRange(); + + logger.fine("rebinning to pixel resolution: "+ xRebinDescriptor + " " + yRebinDescriptor ); + logger.fine("rebinning to pixel resolution: "+ plotImageBounds2 ); + + DataSetRebinner rebinner = this.rebinnerEnum.getRebinner(); + + long t0; + + t0 = System.currentTimeMillis(); + + rebinDataSet = (TableDataSet) + rebinner.rebin(this.ds, xRebinDescriptor, yRebinDescriptor, null); + + xmemento = xAxis.getMemento(); + ymemento = yAxis.getMemento(); + cmemento = colorBar.getMemento(); + + logger.fine("rebinning to pixel resolution: "+ xmemento + " " + ymemento ); + + raster = transformSimpleTableDataSet(rebinDataSet, colorBar, yAxis.isFlipped()); + rasterWidth = plotImageBounds2.width; + rasterHeight = plotImageBounds2.height; + + } + + IndexColorModel model = colorBar.getIndexColorModel(); + plotImage2 = new BufferedImage(plotImageBounds2.width, plotImageBounds2.height, BufferedImage.TYPE_BYTE_INDEXED, model); + + WritableRaster r = plotImage2.getRaster(); + + try { + if ( plotImageBounds2.width==rasterWidth && plotImageBounds2.height==rasterHeight ) { + r.setDataElements(0, 0, rasterWidth, rasterHeight, raster); + } else { + System.err.println("avoided raster ArrayIndex... track this down sometime..."); + } + } catch (ArrayIndexOutOfBoundsException ex) { + throw ex; + } + plotImage = plotImage2; + plotImageBounds= plotImageBounds2; + + } + } catch (InconvertibleUnitsException ex) { + logger.fine("inconvertable units, setting image to null"); + ex.printStackTrace(); + plotImage = null; + plotImageBounds= null; + rebinDataSet = null; + imageXRange = null; + imageYRange = null; + if (this.getLastException() == null) setException(ex); + getParent().repaint(); + return; + } + + } catch (NullPointerException ex) { + ex.printStackTrace(); + } catch (NoDataInIntervalException e) { + setLastException(e); + plotImage = null; + } finally { + getParent().repaint(); + } + } + + protected void installRenderer() { + if (parent != null && parent.getCanvas() != null) { + if (colorBar != null) { + if (colorBar.getColumn() == DasColumn.NULL) { + DasColumn column = parent.getColumn(); + colorBar.setColumn(new DasColumn(null, column, 1.0, 1.0, 1, 2, 0, 0)); + } + parent.getCanvas().add(colorBar, parent.getRow(), colorBar.getColumn()); + if (!"true".equals(DasApplication.getProperty("java.awt.headless", "false"))) { + DasMouseInputAdapter mouseAdapter = parent.mouseAdapter; + VerticalSpectrogramSlicer vSlicer = + VerticalSpectrogramSlicer.createSlicer(parent, this); + VerticalSlicerMouseModule vsl = VerticalSlicerMouseModule.create(this); + vsl.addDataPointSelectionListener(vSlicer); + mouseAdapter.addMouseModule(vsl); + + HorizontalSpectrogramSlicer hSlicer = HorizontalSpectrogramSlicer.createSlicer(parent, this); + HorizontalSlicerMouseModule hsl = HorizontalSlicerMouseModule.create(this); + hsl.addDataPointSelectionListener(hSlicer); + mouseAdapter.addMouseModule(hsl); + + VerticalSpectrogramAverager vAverager = VerticalSpectrogramAverager.createAverager(parent, this); + HorizontalDragRangeSelectorMouseModule vrl = new HorizontalDragRangeSelectorMouseModule(parent, this, parent.getXAxis()); + //vrl.setLabel("Vertical Averager"); + vrl.addDataRangeSelectionListener(vAverager); + mouseAdapter.addMouseModule(vrl); + + MouseModule ch = new CrossHairMouseModule(parent, this, parent.getXAxis(), parent.getYAxis()); + mouseAdapter.addMouseModule(ch); + + } + } + } + } + + protected void uninstallRenderer() { + if (colorBar != null && colorBar.getCanvas() != null) { + colorBar.getCanvas().remove(colorBar); + } + } + + public static SpectrogramRenderer processSpectrogramElement( + Element element, DasPlot parent, FormBase form) throws DasPropertyException, DasNameException, ParseException { + String dataSetID = element.getAttribute("dataSetID"); + DasColorBar colorbar = null; + + NodeList children = element.getChildNodes(); + for (int index = 0; index < children.getLength(); index++) { + Node node = children.item(index); + if (node instanceof Element && node.getNodeName().equals("zAxis")) { + colorbar = processZAxisElement((Element) node, form); + } + } + + if (colorbar == null) { + try { + colorbar = (DasColorBar) form.checkValue(element.getAttribute("colorbar"), DasColorBar.class, ""); + } catch (DasPropertyException dpe) { + dpe.setPropertyName("colorbar"); + throw dpe; + } + } + + SpectrogramRenderer renderer = new SpectrogramRenderer(parent, null, colorbar); + try { + renderer.setDataSetID(dataSetID); + } catch (DasException de) { + DasExceptionHandler.handle(de); + } + return renderer; + } + + private static DasColorBar processZAxisElement(Element element, FormBase form) throws DasPropertyException, DasNameException, ParseException { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + if (node.getNodeName().equals("colorbar")) { + return DasColorBar.processColorbarElement((Element) node, form); + } + } + } + return null; + } + + public Element getDOMElement(Document document) { + + Element element = document.createElement("spectrogram"); + element.setAttribute("dataSetID", getDataSetID()); + + Element zAxisChild = document.createElement("zAxis"); + Element zAxisElement = getColorBar().getDOMElement(document); + if (zAxisElement.getAttribute("row").equals(getParent().getRow().getDasName())) { + zAxisElement.removeAttribute("row"); + } + if (zAxisElement.getAttribute("column").equals(getParent().getColumn().getDasName())) { + zAxisElement.removeAttribute("column"); + } + zAxisChild.appendChild(zAxisElement); + element.appendChild(zAxisChild); + + return element; + } + + /** Getter for property rebinner. + * @return Value of property rebinner. + * + */ + public RebinnerEnum getRebinner() { + return this.rebinnerEnum; + } + + /** Setter for property rebinner. + * @param rebinnerEnum New value of property rebinner. + * + */ + public void setRebinner(RebinnerEnum rebinnerEnum) { + RebinnerEnum old = this.rebinnerEnum; + if (old != rebinnerEnum) { + this.rebinnerEnum = rebinnerEnum; + this.raster = null; + this.plotImage = null; + refreshImage(); + propertyChangeSupport.firePropertyChange("rebinner", old, rebinnerEnum); + } + } + + /** Getter for property sliceRebinnedData. + * @return Value of property sliceRebinnedData. + * + */ + public boolean isSliceRebinnedData() { + return this.sliceRebinnedData; + } + + /** Setter for property sliceRebinnedData. + * @param sliceRebinnedData New value of property sliceRebinnedData. + * + */ + public void setSliceRebinnedData(boolean sliceRebinnedData) { + this.sliceRebinnedData = sliceRebinnedData; + } + + public String getListLabel() { + return "spectrogram"; + } + + public Icon getListIcon() { + return rebinnerEnum.getListIcon(); + } + + public DataSet getConsumedDataSet() { + if (sliceRebinnedData) { + return rebinDataSet; + } else { + return this.ds; + } + } + + public void setDataSet(DataSet ds) { + DataSet oldDs = this.ds; + if (parent != null && oldDs != ds) { + this.raster = null; + // TODO: preserve plotImage until updatePlotImage is done + this.plotImage = null; + } + super.setDataSet(ds); + } + /** + * Holds value of property print300dpi. + */ + private boolean print300dpi; + + /** + * Getter for property draw300dpi. + * @return Value of property draw300dpi. + */ + public boolean isPrint300dpi() { + return this.print300dpi; + } + + /** + * Setter for property draw300dpi. + * @param print300dpi New value of property draw300dpi. + */ + public void setPrint300dpi(boolean print300dpi) { + this.print300dpi = print300dpi; + } + + public boolean acceptContext(int x, int y) { + return true; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer.java b/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer.java new file mode 100644 index 000000000..971035cd7 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer.java @@ -0,0 +1,630 @@ +/* File: DasStackedHistogramPlot.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.MouseModule; +import org.das2.event.LengthDragRenderer; +import org.das2.event.CrossHairMouseModule; +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.NearestNeighborTableRebinner; +import org.das2.dataset.AveragePeakTableRebinner; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.LocationUnits; +import org.das2.datum.DatumRange; +import org.das2.dasml.FormBase; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.DasException; +import org.das2.components.HorizontalSpectrogramSlicer; +import org.das2.components.VerticalSpectrogramSlicer; +import org.das2.event.HorizontalSlicerMouseModule; +import org.das2.event.VerticalSlicerMouseModule; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.components.propertyeditor.Enumeration; + +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.beans.*; +import java.text.*; +import java.util.Map; +import javax.swing.*; +import static org.das2.graph.Renderer.logger; +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class StackedHistogramRenderer extends org.das2.graph.Renderer + implements TableDataSetConsumer, PropertyChangeListener, Displayable +{ + + private DasLabelAxis yAxis= null; + private DasAxis zAxis= null; + private RowRowConnector zAxisConnector= null; + private DasRow littleRow=null; + + private RebinDescriptor xBins= null; + + private PeaksIndicator peaksIndicator; + + /** Holds value of property sliceRebinnedData. */ + private boolean sliceRebinnedData; + + Image plotImage; + DatumRange imageXRange, imageYRange; + + public static class PeaksIndicator implements Enumeration, Displayable { + + String id; + + PeaksIndicator(String id) { + this.id= id; + } + + @Override + public String toString() { + return this.id; + } + + public static final PeaksIndicator NoPeaks= new PeaksIndicator("None"); + /** + * draw grey bar up to max observed. + */ + public static final PeaksIndicator GrayPeaks= new PeaksIndicator("Gray Peaks"); + /** + * draw red bar up to max observed. + */ + public static final PeaksIndicator RedPeaks= new PeaksIndicator("Red Peaks"); + /** + * draw blue bar up to max observed. + */ + public static final PeaksIndicator BluePeaks= new PeaksIndicator("Blue Peaks"); + /** + * draw green bar up to max observed. + */ + public static final PeaksIndicator GreenPeaks= new PeaksIndicator("Green Peaks"); + /** + * draw a connect-a-dot line from peak to peak. + */ + public static final PeaksIndicator LinePeaks= new PeaksIndicator("Line Peaks"); + /** + * draw black bar up to max observed (only peaks are visible then). + */ + public static final PeaksIndicator BlackPeaks= new PeaksIndicator("Black Peaks"); + /** + * draw a point at the maximum observed. + */ + public static final PeaksIndicator MaxLines= new PeaksIndicator("Lines"); + + /** Get a peaks indicator object given a string. + * + * @param s + * @return null if the string doesn't match one of the pre-defined items + */ + public static PeaksIndicator fromString(String s){ + if(NoPeaks.toString().equals(s)) return NoPeaks; + if(GrayPeaks.toString().equals(s)) return GrayPeaks; + if(RedPeaks.toString().equals(s)) return RedPeaks; + if(BluePeaks.toString().equals(s)) return BluePeaks; + if(GreenPeaks.toString().equals(s)) return GreenPeaks; + if(LinePeaks.toString().equals(s)) return LinePeaks; + if(BlackPeaks.toString().equals(s)) return BlackPeaks; + if(MaxLines.toString().equals(s)) return MaxLines; + return null; + } + + @Override + public String getListLabel() { + return this.id; + } + + @Override + public javax.swing.Icon getListIcon() { + return null; + } + + } + + protected class RebinListener implements PropertyChangeListener { + @Override + public void propertyChange(PropertyChangeEvent e) { + update(); + } + } + + RebinListener rebinListener= new RebinListener(); + + public StackedHistogramRenderer( DasPlot parent, DataSetDescriptor dsd, DasAxis zAxis, DasLabelAxis yAxis ) { + super(); + + this.yAxis= yAxis; + this.zAxis= zAxis; + + zAxis.addPropertyChangeListener(rebinListener); + + setDataSetDescriptor( dsd ); + } + + + @Override + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + Graphics2D g2= (Graphics2D)g.create(); + + Point2D p; + if (getDataSet()==null && getLastException()!=null ) { + renderException(g2,xAxis,yAxis,getLastException()); + } else if (plotImage!=null) { + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ); + p= new Point2D.Float( xAxis.getColumn().getDMinimum(), yAxis.getRow().getDMinimum() ); + + g2.drawImage( plotImage,(int)(p.getX()+0.5),(int)(p.getY()+0.5), getParent() ); + + } + g2.dispose(); + } + + @Override + protected void installRenderer() { + DasCanvas canvas= parent.getCanvas(); + littleRow= new DasRow( canvas, 0.5,0.6 ); + zAxisConnector= new RowRowConnector( canvas, littleRow, zAxis.getRow(), parent.getColumn(), zAxis.getColumn() ); + zAxisConnector.setVisible(false); + canvas.add(zAxisConnector); + + yAxis.setFloppyItemSpacing(true); + yAxis.setOutsidePadding(1); + + this.peaksIndicator= PeaksIndicator.MaxLines; + + DasMouseInputAdapter mouseAdapter = parent.getDasMouseInputAdapter(); + + //TODO: consider delaying construction of slicers until first event + VerticalSpectrogramSlicer vSlicer = VerticalSpectrogramSlicer.createSlicer( parent, this ); + VerticalSlicerMouseModule vsl = VerticalSlicerMouseModule.create(this); + vsl.addDataPointSelectionListener(vSlicer); + mouseAdapter.addMouseModule(vsl); + + HorizontalSpectrogramSlicer hSlicer = HorizontalSpectrogramSlicer.createSlicer( parent, this); + HorizontalSlicerMouseModule hsl = HorizontalSlicerMouseModule.create(this); + hsl.addDataPointSelectionListener(hSlicer); + mouseAdapter.addMouseModule(hsl); + + MouseModule ch= new CrossHairMouseModule(parent,this,parent.getXAxis(), parent.getYAxis()); + mouseAdapter.addMouseModule(ch); + + DasPlot p= parent; + mouseAdapter.addMouseModule( new MouseModule( p, new LengthDragRenderer(p,p.getXAxis(),p.getYAxis()), "Length" ) ); + } + + @Override + protected void uninstallRenderer() { + } + + + public void setZAxis(DasAxis zAxis) { + this.zAxis= zAxis; + throw new IllegalStateException("not supported"); + } + + @Override + public void propertyChange(java.beans.PropertyChangeEvent e) { + // this code was intended to make it so the zaxis component would move up and down + // with the labelAxis. + /* DasLabelAxis axis= (DasLabelAxis)getYAxis(); + + if ( axis!=null ) { + if ( getRow()!=DasRow.NULL ) { + if ( axis.getInterItemSpace() > getRow().getHeight()/3.5 ) { + System.out.println("axis spacing exceeds zAxis spacing"); + int[] labelPositions= axis.getLabelPositions(); + zAxisComponent.getAxis().getRow().setDPosition( labelPositions[0], labelPositions[1] ); + } else { + int xx2= getRow().getDMaximum(); + int xx1= getRow().getDMinimum(); + zAxisComponent.getAxis().getRow().setDPosition( xx1, xx2 ); + } + } + } */ + } + + /** + * @throws IllegalArgumentException if the yAxis is not an instanceof DasLabelAxis + */ + public void setYAxis(DasAxis yAxis) { + if (yAxis instanceof DasLabelAxis) { + this.yAxis= (DasLabelAxis)yAxis; + yAxis.addPropertyChangeListener(this); + } else { + throw new IllegalArgumentException("You can't call setYAxis for stackedHistogramPlot"); + } + } + + protected String getPrimaryPeaksId(){ + String sPrimary = getDataSet().getPlaneIds()[0]; + + if(sPrimary.equals("")) // Handle un-named primary planes + return "peaks"; + else + return sPrimary + ".peaks"; // Handle named primary planes + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private int getInterpDistance(DasAxis axis){ + // Get peaks dataset in data coordinates, not screen coordinates + TableDataSet ds = (TableDataSet) getDataSet().getPlanarView(getPrimaryPeaksId()); + + Datum dInterp; + if(override.containsKey(DataSet.PROPERTY_X_TAG_WIDTH)) + dInterp = (Datum)override.get(DataSet.PROPERTY_X_TAG_WIDTH); + else + dInterp = (Datum)ds.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + + // Use the start of the axis a the basis point for finding the interpolation + // distance in screen coordinates + Datum dBeg = axis.getDataMaximum(); + + // Too bad can't do this in java: Datum dEnd = dBeg + dInterp; + Datum dEnd = dBeg.add(dInterp); + // The factor of 1.5 is to match the check used in + // AverageTableRebinner.fillInterpolateX -> line 508. + int iMaxInterp = (int) ((axis.transform(dEnd) - axis.transform(dBeg))*1.5); + return iMaxInterp; + } + + @Override + synchronized public void updatePlotImage( DasAxis xAxis, DasAxis yAxis_1, ProgressMonitor monitor ) throws DasException { + super.updatePlotImage( xAxis, yAxis_1, monitor ); + final Color BAR_COLOR= getParent().getForeground(); + final Color GREY_PEAKS_COLOR= Color.GRAY; + + Component parent= getParent(); + Cursor cursor0= parent.getCursor(); + parent.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + + DasColumn column= xAxis.getColumn(); + DasRow row= yAxis.getRow(); + + int w= column.getWidth(); + int h= row.getHeight(); + + if ( w==0 ) return; + + //plotImage = new java.awt.image.BufferedImage(w, h, java.awt.image.BufferedImage.TYPE_INT_ARGB); + BufferedImage plotImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g = plotImage.createGraphics(); + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + if ( ! isTransparentBackground() ) { + g.setColor( getParent().getBackground() ); + g.fillRect(0, 0, plotImage.getWidth(), plotImage.getHeight()); + } + g.translate(-column.getDMinimum(),-row.getDMinimum()); + + Dimension d; + + double iMin= column.getDMinimum(); + double jMin= row.getDMinimum(); + + RebinDescriptor xbins= new RebinDescriptor(xAxis.getDataMinimum(), xAxis.getDataMaximum(), (int)(Math.abs(column.getWidth())/1)+1, (xAxis.isLog())); + + imageXRange= xAxis.getDatumRange(); + imageYRange= yAxis.getDatumRange(); + + int xDMax= column.getDMaximum(); + int xDMin= column.getDMinimum(); + + TableDataSet xtysData= (TableDataSet)getDataSet(); + + if ( xtysData==null ) { + this.plotImage= null; + if ( getLastException()==null ) this.setLastException( new DasException("null data set" ) ); + return; + } + + if ( xtysData.tableCount()==0 ) { + this.setLastException( new DasException("empty data set") ); + this.ds= null; + return; + } + + DataSetRebinner rebinner = new Rebinner(); + + TableDataSet data= (TableDataSet)rebinner.rebin(xtysData, xbins, null, override); + TableDataSet peaks= (TableDataSet)data.getPlanarView(getPrimaryPeaksId()); + + DasLabelAxis yAxis= (DasLabelAxis)yAxis_1; + + int zmid= zAxis.getRow().getDMiddle(); + boolean haveLittleRow= false; + + for (int j = 0; j < data.getYLength(0); j++) { + + int yBase; + + if ( j==(data.getYLength(0)-1) ) { /* Draw top grey line */ + yBase= yAxis.getItemMin(data.getYTagDatum(0, j)); + g.setColor(GREY_PEAKS_COLOR); + g.drawLine(xDMin, yBase, xDMax, yBase ); + g.setColor(BAR_COLOR); + } + + yBase= yAxis.getItemMax(data.getYTagDatum(0, j)); + g.setColor(Color.lightGray); + g.drawLine(xDMin, yBase, xDMax, yBase ); + g.setColor(BAR_COLOR); + + int yBase1= yAxis.getItemMin(data.getYTagDatum(0, j)); + + if ( !haveLittleRow && yBase1 <= zmid ) { + littleRow.setDPosition(yBase1,yBase); + haveLittleRow= true; + this.zAxisConnector.setVisible(true); + this.zAxisConnector.repaint(); + } + + double [] binStarts= xbins.binStarts(); + double [] binStops= xbins.binStops(); + + int y0= yBase; + + int littleRowHeight= yBase - yBase1; + double zAxisMax= zAxis.getDataMaximum().doubleValue(xtysData.getZUnits()); + double zAxisMin= zAxis.getDataMinimum().doubleValue(xtysData.getZUnits()); + + if ( yBase1 >= row.getDMinimum() && yBase <= row.getDMaximum() ) { + if ( peaksIndicator==PeaksIndicator.LinePeaks && peaks!=null ) { + GeneralPath p= new GeneralPath(); + + boolean bHasBeg = false; + int nMaxInterpPx = getInterpDistance(xAxis); + int xLast = 0; + int yLast = 0; + for (int ibin=0; ibin < data.getXLength(); ibin++) { + + double zz= peaks.getDouble( ibin, j, data.getZUnits() ); + if( data.getZUnits().isFill(zz) || Double.isNaN(zz) ) + continue; + + int x0= (int)xAxis.transform(binStarts[ibin],xbins.getUnits()); + int yMax= (int)zAxis.transform( zz, data.getZUnits(), yBase, yBase1 ); + + // If there is no previous point, set one and move on. + if(!bHasBeg){ + p.moveTo( x0, Math.min( yMax, y0 ) ); + xLast = x0; + yLast = Math.min( yMax, y0 ); + bHasBeg = true; + continue; + } + + // Have data, if previous exists and is close enough, connect + // with line. If not, just put a small line at previous point + // and reset begin to here. + if((x0 - xLast) <= nMaxInterpPx){ + p.lineTo( x0, Math.min( yMax, y0 ) ); + } + else{ + p.lineTo( xLast, yLast); //Can a single point be a line? + p.moveTo( x0, Math.min(yMax, y0)); + } + xLast = x0; + yLast = Math.min( yMax, y0 ); + } + + g.draw(p); + } + for (int ibin=0; ibin < data.getXLength(); ibin++) { + int x0= (int)xAxis.transform(binStarts[ibin],xbins.getUnits()); + double zz= data.getDouble( ibin, j, data.getZUnits() ); + if ( !( data.getZUnits().isFill(zz) || Double.isNaN(zz) ) ) { + int yAvg= (int)zAxis.transform( zz, data.getZUnits(), yBase, yBase1 ); + yAvg= yAvg > ( y0 - littleRowHeight ) ? yAvg : ( y0 - littleRowHeight ); + int yHeight= (y0-yAvg)>(0) ? (y0-yAvg) : 0; + //yHeight= yHeight < littleRowHeight ? yHeight : littleRowHeight; + if ( peaks!=null ) { + double peakValue = peaks.getDouble(ibin, j, peaks.getZUnits()); + if (peakValue > zAxisMin) { + int yMax= (int)zAxis.transform( peakValue, data.getZUnits(), yBase, yBase1 ); + yMax= Math.min( yMax, y0 ); + yMax= yMax > ( y0 - littleRowHeight ) ? yMax : ( y0 - littleRowHeight ); + + if (peaksIndicator==PeaksIndicator.MaxLines) { + g.drawLine(x0,yMax,x0,yMax); + } else if ( peaksIndicator==PeaksIndicator.GrayPeaks ) { + g.setColor(Color.lightGray); + g.drawLine(x0,yMax,x0,y0); + g.setColor(BAR_COLOR); + } else if ( peaksIndicator==PeaksIndicator.RedPeaks ) { + g.setColor(Color.red); + g.drawLine(x0,yMax,x0,y0); + g.setColor(BAR_COLOR); + } else if ( peaksIndicator==PeaksIndicator.GreenPeaks ) { + g.setColor(Color.GREEN); + g.drawLine(x0,yMax,x0,y0); + g.setColor(BAR_COLOR); + } else if ( peaksIndicator==PeaksIndicator.BluePeaks ) { + g.setColor(Color.CYAN); + g.drawLine(x0,yMax,x0,y0); + g.setColor(BAR_COLOR); + } else if ( peaksIndicator==PeaksIndicator.LinePeaks ) { + //nothing here + } else if ( peaksIndicator==PeaksIndicator.BlackPeaks ) { + g.setColor(BAR_COLOR); + g.drawLine(x0,yMax,x0,y0); + } + } + } + if ( zz>=zAxisMin ) g.drawLine(x0, yAvg, x0, yAvg+yHeight ); + } + } + } + } + + g.dispose(); + this.plotImage = plotImage; + parent.setCursor(cursor0); + getParent().repaint(); + + if ( sliceRebinnedData ) super.ds= data; + + } + + @Override + public DasAxis getZAxis() { + return zAxis; + } + + public void setZTitle(String title) { + getZAxis().setLabel(title); + } + + public class Rebinner implements DataSetRebinner { + DataSetRebinner highResRebinner; + DataSetRebinner lowResRebinner; + Rebinner() { + highResRebinner= new NearestNeighborTableRebinner(); + //highResRebinner= new AveragePeakTableRebinner(); + lowResRebinner= new AveragePeakTableRebinner(); + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override + ) throws IllegalArgumentException, DasException { + Datum xwidth= (Datum)ds.getProperty( "xTagWidth" ); + if ( xwidth==null ) xwidth= DataSetUtil.guessXTagWidth((TableDataSet)ds); + Units rdUnits= x.getUnits(); + if ( rdUnits instanceof LocationUnits ) { + rdUnits= ((LocationUnits)rdUnits).getOffsetUnits(); + } + + try { + DataSet result; + if ( x.binWidth() < xwidth.doubleValue(rdUnits) ) { + logger.fine("using rebinner "+highResRebinner); + result= highResRebinner.rebin( ds, x, y, override ); + } else { + logger.fine("using rebinner "+lowResRebinner); + result= lowResRebinner.rebin( ds, x, y, override ); + } + return result; + } catch ( IllegalArgumentException | DasException e ) { + DasExceptionHandler.handle(e); + return null; + } + } + + } + + /** Getter for property peaksIndicator. + * @return Value of property peaksIndicator. + */ + public PeaksIndicator getPeaksIndicator() { + return this.peaksIndicator; + } + + /** Setter for property peaksIndicator. + * @param peaksIndicator New value of property peaksIndicator. + */ + public void setPeaksIndicator(PeaksIndicator peaksIndicator) { + this.peaksIndicator= peaksIndicator; + refreshImage(); + } + + /** Getter for property sliceRebinnedData. + * @return Value of property sliceRebinnedData. + * + */ + public boolean isSliceRebinnedData() { + return this.sliceRebinnedData; + } + + /** Setter for property sliceRebinnedData. + * @param sliceRebinnedData New value of property sliceRebinnedData. + * + */ + public void setSliceRebinnedData(boolean sliceRebinnedData) { + this.sliceRebinnedData = sliceRebinnedData; + } + protected boolean transparentBackground = false; + public static final String PROP_TRANSPARENTBACKGROUND = "transparentBackground"; + + public boolean isTransparentBackground() { + return transparentBackground; + } + + public void setTransparentBackground(boolean transparentBackground) { + boolean oldTransparentBackground = this.transparentBackground; + this.transparentBackground = transparentBackground; + propertyChangeSupport.firePropertyChange(PROP_TRANSPARENTBACKGROUND, oldTransparentBackground, transparentBackground); + } + + @Override + public Element getDOMElement(Document document) { + + Element element = document.createElement("stackedHistogram"); + element.setAttribute("zAxis", zAxis.getDasName() ); + element.setAttribute("dataSetID", getDataSetID() ); + return element; + } + + public static Renderer processStackedHistogramElement(Element element, DasPlot parent, FormBase form) + throws DasPropertyException, DasNameException, ParseException + { + String dataSetID = element.getAttribute("dataSetID"); + + Renderer renderer = new StackedHistogramRenderer( parent, (DataSetDescriptor)null, (DasAxis)null, (DasLabelAxis)parent.getYAxis() ); + try { + renderer.setDataSetID(dataSetID); + } catch (DasException de) { + DasExceptionHandler.handle(de); + } + return renderer; + } + + @Override + public String getListLabel() { + return "stacked histogram"; + } + + @Override + public Icon getListIcon() { + return null; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer_rework.java.save b/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer_rework.java.save new file mode 100644 index 000000000..477668d85 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/StackedHistogramRenderer_rework.java.save @@ -0,0 +1,690 @@ +/* File: DasStackedHistogramPlot.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.MouseModule; +import org.das2.event.LengthDragRenderer; +import org.das2.event.CrossHairMouseModule; +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.NearestNeighborTableRebinner; +import org.das2.dataset.AveragePeakTableRebinner; +import org.das2.dataset.TableDataSetConsumer; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetUtil; +import org.das2.datum.LocationUnits; +import org.das2.datum.DatumRange; +import org.das2.dasml.FormBase; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.DasException; +import org.das2.components.HorizontalSpectrogramSlicer; +import org.das2.components.VerticalSpectrogramSlicer; +import org.das2.event.HorizontalSlicerMouseModule; +import org.das2.event.VerticalSlicerMouseModule; +import org.das2.datum.Datum; +import org.das2.datum.Units; +import org.das2.components.propertyeditor.Enumeration; + +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.BufferedImage; +import java.beans.*; +import java.text.*; +import java.util.Map; +import javax.swing.*; +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class StackedHistogramRenderer extends org.das2.graph.Renderer + implements TableDataSetConsumer, PropertyChangeListener, Displayable +{ + + private DasLabelAxis yAxis= null; + private DasAxis zAxis= null; + private RowRowConnector zAxisConnector= null; + private DasRow littleRow=null; + + private RebinDescriptor xBins= null; + + private PeaksIndicator peaksIndicator; + + /** Holds value of property sliceRebinnedData. */ + private boolean sliceRebinnedData; + + Image plotImage; + DatumRange imageXRange, imageYRange; + + /** If set this will override the xTagWidth property during display */ + Datum m_rMaxXInterp = null; + + public static class PeaksIndicator implements Enumeration, Displayable { + + String id; + + PeaksIndicator(String id) { + this.id= id; + } + + @Override + public String toString() { + return this.id; + } + + public static final PeaksIndicator NoPeaks= new PeaksIndicator("None"); + /** + * draw grey bar up to max observed. + */ + public static final PeaksIndicator GrayPeaks= new PeaksIndicator("Gray Peaks"); + /** + * draw red bar up to max observed. + */ + public static final PeaksIndicator RedPeaks= new PeaksIndicator("Red Peaks"); + /** + * draw blue bar up to max observed. + */ + public static final PeaksIndicator BluePeaks= new PeaksIndicator("Blue Peaks"); + /** + * draw green bar up to max observed. + */ + public static final PeaksIndicator GreenPeaks= new PeaksIndicator("Green Peaks"); + /** + * draw a connect-a-dot line from peak to peak. + */ + public static final PeaksIndicator LinePeaks= new PeaksIndicator("Line Peaks"); + /** + * draw black bar up to max observed (only peaks are visible then). + */ + public static final PeaksIndicator BlackPeaks= new PeaksIndicator("Black Peaks"); + /** + * draw a point at the maximum observed. + */ + public static final PeaksIndicator MaxLines= new PeaksIndicator("Lines"); + + /** Get a peaks indicator object given a string. + * + * @param s + * @return null if the string doesn't match one of the pre-defined items + */ + public static PeaksIndicator fromString(String s){ + if(NoPeaks.toString().equals(s)) return NoPeaks; + if(GrayPeaks.toString().equals(s)) return GrayPeaks; + if(RedPeaks.toString().equals(s)) return RedPeaks; + if(BluePeaks.toString().equals(s)) return BluePeaks; + if(GreenPeaks.toString().equals(s)) return GreenPeaks; + if(LinePeaks.toString().equals(s)) return LinePeaks; + if(BlackPeaks.toString().equals(s)) return BlackPeaks; + if(MaxLines.toString().equals(s)) return MaxLines; + return null; + } + + @Override + public String getListLabel() { + return this.id; + } + + @Override + public javax.swing.Icon getListIcon() { + return null; + } + + } + + protected class RebinListener implements PropertyChangeListener { + @Override + public void propertyChange(PropertyChangeEvent e) { + update(); + } + } + + RebinListener rebinListener= new RebinListener(); + + public StackedHistogramRenderer( DasPlot parent, DataSetDescriptor dsd, DasAxis zAxis, DasLabelAxis yAxis ) { + super(); + + this.yAxis= yAxis; + this.zAxis= zAxis; + + zAxis.addPropertyChangeListener(rebinListener); + + setDataSetDescriptor( dsd ); + } + + + @Override + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + Graphics2D g2= (Graphics2D)g.create(); + + Point2D p; + if (getDataSet()==null && getLastException()!=null ) { + renderException(g2,xAxis,yAxis,getLastException()); + } else if (plotImage!=null) { + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR ); + p= new Point2D.Float( xAxis.getColumn().getDMinimum(), yAxis.getRow().getDMinimum() ); + + g2.drawImage( plotImage,(int)(p.getX()+0.5),(int)(p.getY()+0.5), getParent() ); + + } + g2.dispose(); + } + + @Override + protected void installRenderer() { + DasCanvas canvas= parent.getCanvas(); + littleRow= new DasRow( canvas, 0.5,0.6 ); + zAxisConnector= new RowRowConnector( canvas, littleRow, zAxis.getRow(), parent.getColumn(), zAxis.getColumn() ); + zAxisConnector.setVisible(false); + canvas.add(zAxisConnector); + + yAxis.setFloppyItemSpacing(true); + yAxis.setOutsidePadding(1); + + this.peaksIndicator= PeaksIndicator.MaxLines; + + DasMouseInputAdapter mouseAdapter = parent.getDasMouseInputAdapter(); + + //TODO: consider delaying construction of slicers until first event + VerticalSpectrogramSlicer vSlicer = VerticalSpectrogramSlicer.createSlicer( parent, this ); + VerticalSlicerMouseModule vsl = VerticalSlicerMouseModule.create(this); + vsl.addDataPointSelectionListener(vSlicer); + mouseAdapter.addMouseModule(vsl); + + HorizontalSpectrogramSlicer hSlicer = HorizontalSpectrogramSlicer.createSlicer( parent, this); + HorizontalSlicerMouseModule hsl = HorizontalSlicerMouseModule.create(this); + hsl.addDataPointSelectionListener(hSlicer); + mouseAdapter.addMouseModule(hsl); + + MouseModule ch= new CrossHairMouseModule(parent,this,parent.getXAxis(), parent.getYAxis()); + mouseAdapter.addMouseModule(ch); + + DasPlot p= parent; + mouseAdapter.addMouseModule( new MouseModule( p, new LengthDragRenderer(p,p.getXAxis(),p.getYAxis()), "Length" ) ); + } + + @Override + protected void uninstallRenderer() { + } + + + public void setZAxis(DasAxis zAxis) { + this.zAxis= zAxis; + throw new IllegalStateException("not supported"); + } + + @Override + public void propertyChange(java.beans.PropertyChangeEvent e) { + // this code was intended to make it so the zaxis component would move up and down + // with the labelAxis. + /* DasLabelAxis axis= (DasLabelAxis)getYAxis(); + + if ( axis!=null ) { + if ( getRow()!=DasRow.NULL ) { + if ( axis.getInterItemSpace() > getRow().getHeight()/3.5 ) { + System.out.println("axis spacing exceeds zAxis spacing"); + int[] labelPositions= axis.getLabelPositions(); + zAxisComponent.getAxis().getRow().setDPosition( labelPositions[0], labelPositions[1] ); + } else { + int xx2= getRow().getDMaximum(); + int xx1= getRow().getDMinimum(); + zAxisComponent.getAxis().getRow().setDPosition( xx1, xx2 ); + } + } + } */ + } + + /** + * @throws IllegalArgumentException if the yAxis is not an instance of DasLabelAxis + */ + public void setYAxis(DasAxis yAxis) { + if (yAxis instanceof DasLabelAxis) { + this.yAxis= (DasLabelAxis)yAxis; + yAxis.addPropertyChangeListener(this); + } else { + throw new IllegalArgumentException("You can't call setYAxis for stackedHistogramPlot"); + } + } + + protected String getPrimaryPeaksId(){ + String sPrimary = getDataSet().getPlaneIds()[0]; + + if(sPrimary.equals("")) // Handle un-named primary planes + return "peaks"; + else + return sPrimary + ".peaks"; // Handle named primary planes + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawAxisLines(Graphics2D g){ + + Color BAR_COLOR = getParent().getForeground(); + Color GREY_PEAKS_COLOR = Color.GRAY; + + if(j == (data.getYLength(0) - 1)){ /* Draw grey line */ + + yMin = yAxis.getItemMin(data.getYTagDatum(0, j)); + g.setColor(GREY_PEAKS_COLOR); + g.drawLine(xDMin, yMin, xDMax, yMin); + g.setColor(BAR_COLOR); + } + + yMax = yAxis.getItemMax(data.getYTagDatum(0, j)); + g.setColor(Color.lightGray); + g.drawLine(xDMin, yMax, xDMax, yMax); + g.setColor(BAR_COLOR); + + yMin = yAxis.getItemMin(data.getYTagDatum(0, j)); + + if(!haveLittleRow && yMin <= zmid){ + littleRow.setDPosition(yMin, yMax); + haveLittleRow = true; + this.zAxisConnector.setVisible(true); + this.zAxisConnector.repaint(); + + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private int getInterpDistance(DasAxis axis){ + // Get peaks dataset in data coordinates, not screen coordinates + TableDataSet ds = (TableDataSet) getDataSet().getPlanarView(getPrimaryPeaksId()); + + Datum dInterp; + if(override.containsKey(DataSet.PROPERTY_X_TAG_WIDTH)) + dInterp = (Datum)override.get(DataSet.PROPERTY_X_TAG_WIDTH); + else + dInterp = (Datum)ds.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + + // Use the start of the axis a the basis point for finding the interpolation + // distance in screen coordinates + Datum dBeg = axis.getDataMaximum(); + + // Too bad can't do this in java: Datum dEnd = dBeg + dInterp; + Datum dEnd = dBeg.add(dInterp); + int iMaxInterp = (int) (axis.transform(dEnd) - axis.transform(dBeg)); + return iMaxInterp; + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawLinePeaks(Graphics2D g, DasAxis xAxis){ + + int iMaxInterp = getInterpDistance(xAxis); + + GeneralPath p = new GeneralPath(); + + double x0data; + int x0, x1, y0, y1; + boolean bHasBeg = false; + + for(int ibin = 0; ibin < data.getXLength(); ibin++){ + // Do nothing until you encounter data + double zTmp = peaks.getDouble(ibin, j, data.getZUnits()); + if(data.getZUnits().isFill(zTmp) || Double.isNaN(zTmp)){ + continue; + } + + x1 = (int) xAxis.transform(binStarts[ibin], xbins.getUnits()); + y1 = (int) zAxis.transform(zTmp, data.getZUnits(), yMin, yMax); + + if(!bHasBeg){ + x0 = x1; + y0 = y1; + x0data = binStarts[ibin]; + p.moveTo(x0, Math.min(yMax, y0)); + bHasBeg = true; + } + else{ + //Check to see if last value is too far away, if so just draw a tiny + //2 pixel straight line + if((binStarts[ibin] - x0Data) > xInterp){ + + } + } + + double zz = peaks.getDouble(ibin, j, data.getZUnits()); + if(!(data.getZUnits().isFill(zz) || Double.isNaN(zz))){ + int yMax = (int) zAxis.transform(zz, data.getZUnits(), yMax, yBase1); + if(lastWasFill){ + p.moveTo(x0, Math.min(yMax, y0)); + } + p.lineTo(x0, Math.min(yMax, y0)); + + } + } + g.draw(p); + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawStdPeaks(Graphics2D g, DasAxis xAxis){ + + Color BAR_COLOR = getParent().getForeground(); + + for(int ibin = 0; ibin < data.getXLength(); ibin++){ + int x0 = (int) xAxis.transform(binStarts[ibin], xbins.getUnits()); + double zz = data.getDouble(ibin, j, data.getZUnits()); + if(!(data.getZUnits().isFill(zz) || Double.isNaN(zz))){ + int yAvg = (int) zAxis.transform(zz, data.getZUnits(), yMin, yMin); + yAvg = yAvg > (y0 - littleRowHeight) ? yAvg : (y0 - littleRowHeight); + int yHeight = (y0 - yAvg) > (0) ? (y0 - yAvg) : 0; + //yHeight= yHeight < littleRowHeight ? yHeight : littleRowHeight; + if(peaks != null){ + double peakValue = peaks.getDouble(ibin, j, peaks.getZUnits()); + if(peakValue > zAxisMin){ + int yMax = (int) zAxis.transform(peakValue, data.getZUnits(), yMax, yBase1); + yMax = Math.min(yMax, y0); + yMax = yMax > (y0 - littleRowHeight) ? yMax : (y0 - littleRowHeight); + + if(peaksIndicator == PeaksIndicator.MaxLines){ + g.drawLine(x0, yMax, x0, yMax); + } + else if(peaksIndicator == PeaksIndicator.GrayPeaks){ + g.setColor(Color.lightGray); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.RedPeaks){ + g.setColor(Color.red); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.GreenPeaks){ + g.setColor(Color.GREEN); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.BluePeaks){ + g.setColor(Color.CYAN); + g.drawLine(x0, yMax, x0, y0); + g.setColor(BAR_COLOR); + } + else if(peaksIndicator == PeaksIndicator.LinePeaks){ + //nothing here + } + else if(peaksIndicator == PeaksIndicator.BlackPeaks){ + g.setColor(BAR_COLOR); + g.drawLine(x0, yMax, x0, y0); + } + } + } + if(zz >= zAxisMin){ + g.drawLine(x0, yAvg, x0, yAvg + yHeight); + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////////// + // updatePlotImage helper + private void drawAverages(Graphics2D g, DasAxis xAxis){ + + } + + + @Override + synchronized public void updatePlotImage( + DasAxis xAxis, DasAxis yAxis_1, ProgressMonitor monitor + ) throws DasException{ + + super.updatePlotImage(xAxis, yAxis_1, monitor); + + Component parent = getParent(); + Cursor cursor0 = parent.getCursor(); + parent.setCursor(new Cursor(Cursor.WAIT_CURSOR)); + + DasColumn column = xAxis.getColumn(); + DasRow row = yAxis.getRow(); + + int w = column.getWidth(); + int h = row.getHeight(); + + if(w == 0) return; + + // Set up graphics context + BufferedImage plotImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = plotImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if(!isTransparentBackground()){ + g.setColor(getParent().getBackground()); + g.fillRect(0, 0, plotImage.getWidth(), plotImage.getHeight()); + } + g.translate(-column.getDMinimum(), -row.getDMinimum()); + + imageXRange = xAxis.getDatumRange(); + imageYRange = yAxis.getDatumRange(); + + int xDMax = column.getDMaximum(); + int xDMin = column.getDMinimum(); + + // Handle missing/empty datasets + TableDataSet xtysData = (TableDataSet) getDataSet(); + + if(xtysData == null){ + this.plotImage = null; + if(getLastException() == null){ + this.setLastException(new DasException("null data set")); + } + g.dispose(); + return; + } + + if(xtysData.tableCount() == 0){ + this.setLastException(new DasException("empty data set")); + this.ds = null; + g.dispose(); + return; + } + + // Get the data into pixel space + DataSetRebinner rebinner = new Rebinner(); + + RebinDescriptor xbins = new RebinDescriptor( + xAxis.getDataMinimum(), xAxis.getDataMaximum(), + (int) (Math.abs(column.getWidth()) / 1) + 1, + (xAxis.isLog()) + ); + + TableDataSet data = (TableDataSet) rebinner.rebin(xtysData, xbins, null, override); + TableDataSet peaks = (TableDataSet) data.getPlanarView(getPrimaryPeaksId()); + + DasLabelAxis yAxis = (DasLabelAxis) yAxis_1; + + int zmid = zAxis.getRow().getDMiddle(); + boolean haveLittleRow = false; + + // Loop over Y dimension generating individual histograms + for(int j = 0; j < data.getYLength(0); j++){ + + int yMax, yMin; + + // Axis lines + drawAxisLines(g); + + // Data rendering code + double[] binStarts = xbins.binStarts(); + double[] binStops = xbins.binStops(); + + int littleRowHeight = yMax - yMin; + double zAxisMax = zAxis.getDataMaximum().doubleValue(xtysData.getZUnits()); + double zAxisMin = zAxis.getDataMinimum().doubleValue(xtysData.getZUnits()); + + if(yMin >= row.getDMinimum() && yMax <= row.getDMaximum()){ + + if(peaks != null){ + if(peaksIndicator == PeaksIndicator.LinePeaks) + drawLinePeaks(g, xAxis); + else + drawStdPeaks(g, xAxis); + } + + drawAverages(g, xAxis); + } + } + + g.dispose(); + this.plotImage = plotImage; + parent.setCursor(cursor0); + getParent().repaint(); + + if(sliceRebinnedData){ + super.ds = data; + } + } + + @Override + public DasAxis getZAxis() { + return zAxis; + } + + public void setZTitle(String title) { + getZAxis().setLabel(title); + } + + public class Rebinner implements DataSetRebinner { + DataSetRebinner highResRebinner; + DataSetRebinner lowResRebinner; + Rebinner() { + highResRebinner= new NearestNeighborTableRebinner(); + //highResRebinner= new AveragePeakTableRebinner(); + lowResRebinner= new AveragePeakTableRebinner(); + } + + @Override + public DataSet rebin( + DataSet ds, RebinDescriptor x, RebinDescriptor y, Map override + ) throws IllegalArgumentException, DasException { + Datum xwidth= (Datum)ds.getProperty( "xTagWidth" ); + if ( xwidth==null ) xwidth= DataSetUtil.guessXTagWidth((TableDataSet)ds); + Units rdUnits= x.getUnits(); + if ( rdUnits instanceof LocationUnits ) { + rdUnits= ((LocationUnits)rdUnits).getOffsetUnits(); + } + + try { + DataSet result; + if ( x.binWidth() < xwidth.doubleValue(rdUnits) ) { + logger.fine("using rebinner "+highResRebinner); + result= highResRebinner.rebin( ds, x, y, override ); + } else { + logger.fine("using rebinner "+lowResRebinner); + result= lowResRebinner.rebin( ds, x, y, override ); + } + return result; + } catch ( IllegalArgumentException | DasException e ) { + DasExceptionHandler.handle(e); + return null; + } + } + + } + + /** Getter for property peaksIndicator. + * @return Value of property peaksIndicator. + */ + public PeaksIndicator getPeaksIndicator() { + return this.peaksIndicator; + } + + /** Setter for property peaksIndicator. + * @param peaksIndicator New value of property peaksIndicator. + */ + public void setPeaksIndicator(PeaksIndicator peaksIndicator) { + this.peaksIndicator= peaksIndicator; + refreshImage(); + } + + /** Getter for property sliceRebinnedData. + * @return Value of property sliceRebinnedData. + * + */ + public boolean isSliceRebinnedData() { + return this.sliceRebinnedData; + } + + /** Setter for property sliceRebinnedData. + * @param sliceRebinnedData New value of property sliceRebinnedData. + * + */ + public void setSliceRebinnedData(boolean sliceRebinnedData) { + this.sliceRebinnedData = sliceRebinnedData; + } + protected boolean transparentBackground = false; + public static final String PROP_TRANSPARENTBACKGROUND = "transparentBackground"; + + public boolean isTransparentBackground() { + return transparentBackground; + } + + public void setTransparentBackground(boolean transparentBackground) { + boolean oldTransparentBackground = this.transparentBackground; + this.transparentBackground = transparentBackground; + propertyChangeSupport.firePropertyChange(PROP_TRANSPARENTBACKGROUND, oldTransparentBackground, transparentBackground); + } + + @Override + public Element getDOMElement(Document document) { + + Element element = document.createElement("stackedHistogram"); + element.setAttribute("zAxis", zAxis.getDasName() ); + element.setAttribute("dataSetID", getDataSetID() ); + return element; + } + + public static Renderer processStackedHistogramElement(Element element, DasPlot parent, FormBase form) + throws DasPropertyException, DasNameException, ParseException + { + String dataSetID = element.getAttribute("dataSetID"); + + Renderer renderer = new StackedHistogramRenderer( parent, (DataSetDescriptor)null, (DasAxis)null, (DasLabelAxis)parent.getYAxis() ); + try { + renderer.setDataSetID(dataSetID); + } catch (DasException de) { + DasExceptionHandler.handle(de); + } + return renderer; + } + + @Override + public String getListLabel() { + return "stacked histogram"; + } + + @Override + public Icon getListIcon() { + return null; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/StippledTableRenderer.java b/dasCore/src/main/java/org/das2/graph/StippledTableRenderer.java new file mode 100644 index 000000000..1b92c81e1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/StippledTableRenderer.java @@ -0,0 +1,270 @@ +/* File: SpectrogramRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.dataset.DataSetRebinner; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DefaultTableDataSet; +import org.das2.dataset.RebinDescriptor; +import org.das2.util.DasMath; +import org.das2.DasException; +import org.das2.DasNameException; +import org.das2.DasPropertyException; +import org.das2.dasml.FormBase; +import org.das2.datum.Units; +import org.das2.util.DasDie; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.*; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.geom.*; +import java.awt.image.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.ParseException; +import java.util.Collections; +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class StippledTableRenderer extends Renderer { + + Image plotImage; + + Units zUnits= Units.dimensionless; + + protected class RebinListener implements PropertyChangeListener { + public void propertyChange(PropertyChangeEvent e) { + update(); + } + } + + RebinListener rebinListener= new RebinListener(); + + public StippledTableRenderer(DataSetDescriptor dsd ) { + super( dsd ); + } + + + /** Creates a new instance of SpectrogramRenderer + * @deprecated use {link + * #SpectrogramRenderer(org.das2.dataset.DataSetDescriptor, + * org.das2.graph.DasColorBar)} + */ + public StippledTableRenderer(DasPlot parent, DataSetDescriptor dsd ) { + this( dsd ); + this.parent = parent; + } + + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + Graphics2D g2= (Graphics2D)g.create(); + + if (getDataSet()==null && getLastException()!=null ) { + renderException(g2,xAxis,yAxis,getLastException()); + } else if (plotImage!=null) { + Point2D p; + p= new Point2D.Float( xAxis.getColumn().getDMinimum(), yAxis.getRow().getDMinimum() ); + int x= (int)(p.getX()+0.5); + int y= (int)(p.getY()+0.5); + + g2.drawImage( plotImage,x,y, getParent() ); + } + g2.dispose(); + } + + int count = 0; + + private boolean sliceRebinnedData= true; + + public void updatePlotImage( DasAxis xAxis, DasAxis yAxis, ProgressMonitor monitor ) throws DasException { + super.updatePlotImage( xAxis, yAxis, monitor ); + try { + TableDataSet rebinData; + + if (monitor != null) { + if (monitor.isCancelled()) { + return; + } else { + monitor.setTaskSize(-1); + monitor.started(); + } + } + + int w = xAxis.getColumn().getDMaximum() - xAxis.getColumn().getDMinimum(); + int h = yAxis.getRow().getDMaximum() - yAxis.getRow().getDMinimum(); + + if (getParent()==null || w<=1 || h<=1 ) { + DasDie.println("canvas not useable!!!"); + return; + } + + if ( getDataSet() == null) { + Units xUnits = getParent().getXAxis().getUnits(); + Units yUnits = getParent().getYAxis().getUnits(); + + double[] xTags, yTags; + xTags = yTags = new double[0]; + double[][] zValues = {yTags}; + rebinData = new DefaultTableDataSet(xTags, xUnits, yTags, yUnits, zValues, zUnits, Collections.EMPTY_MAP); + plotImage= null; + rebinData= null; + getParent().repaint(); + return; + } else { + RebinDescriptor xRebinDescriptor; + xRebinDescriptor = new RebinDescriptor( + xAxis.getDataMinimum(), xAxis.getDataMaximum(), + w, + xAxis.isLog()); + + RebinDescriptor yRebinDescriptor = new RebinDescriptor( + yAxis.getDataMinimum(), yAxis.getDataMaximum(), + h, + yAxis.isLog()); + + DataSetRebinner rebinner= new AverageTableRebinner(); + + rebinData = (TableDataSet)rebinner.rebin( + getDataSet(),xRebinDescriptor, yRebinDescriptor, null + ); + + //TableDataSet weights= (TableDataSet)rebinData.getPlanarView(DataSet.PROPERTY_PLANE_WEIGHTS); + int itable=0; + int ny= rebinData.getYLength(itable); + int nx= rebinData.tableEnd(itable)-rebinData.tableStart(itable); + + + BufferedImage image= new BufferedImage( w, h, BufferedImage.TYPE_4BYTE_ABGR ); + Graphics2D g= (Graphics2D)image.getGraphics(); + g.setColor( Color.BLACK ); + + Units zunits= rebinData.getZUnits(); + + double maxn=0; + for (int i=rebinData.tableStart(itable); imaxn ) maxn=n; + if ( Math.random() < n ) { + g.fillRect( i,ny-j,1,1); + } + } + } + plotImage= image; + } + + } finally { + if (monitor != null) { + if (monitor.isCancelled()) { + return; + } else { + monitor.finished(); + } + } + + getParent().repaint(); + } + } + + protected void installRenderer() { + } + + protected void uninstallRenderer() { + } + + public static SpectrogramRenderer processSpectrogramElement(Element element, DasPlot parent, FormBase form) throws DasPropertyException, DasNameException, ParseException { + String dataSetID = element.getAttribute("dataSetID"); + DasColorBar colorbar = null; + + NodeList children = element.getChildNodes(); + for (int index = 0; index < children.getLength(); index++) { + Node node = children.item(index); + if (node instanceof Element && node.getNodeName().equals("zAxis")) { + colorbar = processZAxisElement((Element)node, form); + } + } + + if (colorbar == null) { + try { + colorbar = (DasColorBar)form.checkValue(element.getAttribute("colorbar"), DasColorBar.class, ""); + } catch (DasPropertyException dpe) { + dpe.setPropertyName("colorbar"); + throw dpe; + } + } + + SpectrogramRenderer renderer = new SpectrogramRenderer(null, colorbar); + try { + renderer.setDataSetID(dataSetID); + } catch (DasException de) { + DasExceptionHandler.handle(de); + } + return renderer; + } + + private static DasColorBar processZAxisElement(Element element, FormBase form) throws DasPropertyException, DasNameException, ParseException { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if (node instanceof Element) { + } + } + return null; + } + + public Element getDOMElement(Document document) { + + Element element = document.createElement("stippledTable"); + element.setAttribute("dataSetID", getDataSetID()); + + return element; + } + + /** Getter for property sliceRebinnedData. + * @return Value of property sliceRebinnedData. + * + */ + public boolean isSliceRebinnedData() { + return this.sliceRebinnedData; + } + + /** Setter for property sliceRebinnedData. + * @param sliceRebinnedData New value of property sliceRebinnedData. + * + */ + public void setSliceRebinnedData(boolean sliceRebinnedData) { + this.sliceRebinnedData = sliceRebinnedData; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/SymColor.java b/dasCore/src/main/java/org/das2/graph/SymColor.java new file mode 100644 index 000000000..6558cd649 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/SymColor.java @@ -0,0 +1,113 @@ +/* File: SymColor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.components.propertyeditor.Enumeration; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; + +/** + * + * @author jbf + */ + +public final class SymColor extends Color implements Enumeration, Displayable { + + private String name; + private ImageIcon imageIcon; + + public static final SymColor black= new SymColor( "black",Color.black ); + public static final SymColor blue= new SymColor( "blue",Color.blue ); + public static final SymColor lightRed= new SymColor( "lightRed", new Color( 255, 128, 128 ) ); + public static final SymColor red= new SymColor( "red",Color.red ); + public static final SymColor darkRed= new SymColor( "darkRed",Color.red.darker() ); + public static final SymColor green= new SymColor( "green",Color.green ); + public static final SymColor darkGreen= new SymColor( "darkGreen",Color.green.darker() ); + public static final SymColor white= new SymColor( "white",Color.white ); + public static final SymColor gray= new SymColor( "gray",Color.gray ); + public static final SymColor magenta = new SymColor( "magenta",Color.magenta); + + /** Creates a new instance of SymColor */ + private SymColor(String name, Color color) { + this(name, color.getRGB()); + } + + /** Creates a new instance of SymColor */ + private SymColor(String name, int rgb) { + super(rgb); + this.name= name; + Image i= new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB); + Graphics g= i.getGraphics(); + g.setColor(this); + g.fillRect(0,0,10,10); + this.imageIcon= new ImageIcon(i); + } + + /** An icon can be provided that will be shown in a list + * along with the textual description of the element. + * This method should return null if there + * is no icon available. + */ + public Icon getListIcon() { + return imageIcon; + } + + public String getListLabel() { + return name; + } + + public String toString() { + return name; + } + + public Color toColor() { + return this; + } + + public static SymColor parseSymColor(String str) { + if (str.equals("black")) { + return black; + } + else if (str.equals("blue")) { + return blue; + } + else if (str.equals("red")) { + return red; + } + else if (str.equals("green")) { + return green; + } + else if (str.equals("white")) { + return white; + } + else if (str.equals("gray")) { + return gray; + } + else { + throw new IllegalArgumentException(str); + } + } +} diff --git a/dasCore/src/main/java/org/das2/graph/SymbolLineRenderer.java b/dasCore/src/main/java/org/das2/graph/SymbolLineRenderer.java new file mode 100755 index 000000000..cd9c87a74 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/SymbolLineRenderer.java @@ -0,0 +1,527 @@ +/* File: SymbolLineRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.components.propertyeditor.Displayable; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.VectorUtil; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.DatumRange; +import org.das2.datum.Datum; +import org.das2.system.DasLogger; +import org.das2.DasApplication; +import org.das2.DasProperties; +import org.das2.DasException; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.dasml.FormBase; +import org.das2.event.DasMouseInputAdapter; +import org.das2.event.LengthDragRenderer; +import org.das2.event.MouseModule; +import java.awt.image.*; +import javax.swing.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.awt.*; +import java.awt.geom.*; +import java.util.logging.Logger; +import org.das2.datum.Units; + + +/** + * + * @author jbf + */ +public class SymbolLineRenderer extends Renderer implements Displayable { + + private Psym psym = Psym.NONE; + private double symSize = 3.0; // radius in pixels + private float lineWidth = 1.0f; // width in pixels + private boolean histogram = false; + //private Stroke stroke; + private PsymConnector psymConnector = PsymConnector.SOLID; + + int renderCount=0, updateImageCount=0; + + /** Holds value of property color. */ + private Color color= Color.BLACK; + + private long lastUpdateMillis; + + /** Holds value of property antiAliased. */ + private boolean antiAliased= ("on".equals(DasProperties.getInstance().get("antiAlias"))); + + /** The 'image' of the data */ + private GeneralPath path; + + private Logger log= DasLogger.getLogger(DasLogger.GRAPHICS_LOG); + + public SymbolLineRenderer() { + super(); + } + + /** + * @deprecated use SymbolLineRenderer() and setDataSet() instead. Note that + * behavior may be slightly different since a DataLoader is created. + */ + public SymbolLineRenderer(DataSet ds) { + super(ds); + } + + /** + * @deprecated use SymbolLineRenderer() and setDataSetDescriptor() instead. + */ + public SymbolLineRenderer(DataSetDescriptor dsd) { + super(dsd); + } + + private void reportCount() { + //if ( renderCount % 100 ==0 ) { + // System.err.println(" updates: "+updateImageCount+" renders: "+renderCount ); + //} + } + + + @Override + public void render(Graphics g, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + renderCount++; + // reportCount(); + + long timer0= System.currentTimeMillis(); + + VectorDataSet dataSet= (VectorDataSet)getDataSet(); + + if ( this.ds==null && getLastException()!=null ) { + renderException(g,xAxis,yAxis,getLastException()); + return; + } + + if (dataSet == null || dataSet.getXLength() == 0) { + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("null data set"); + parent.postMessage(this, "null data set", DasPlot.INFO, null, null ); + return; + } + + DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("render data set "+dataSet); + + g.setColor(color); + + Graphics2D graphics= (Graphics2D) g.create(); + + RenderingHints hints0= graphics.getRenderingHints(); + if ( antiAliased ) { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } else { + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + } + + log.finest("drawing psym in "+color); + + // draw the stored path that we calculated in updatePlotImage + if (path != null) { + psymConnector.draw(graphics, path, lineWidth); + } + + Dimension d; + + double xmin, xmax, ymin, ymax; + + org.das2.datum.Units xUnits= xAxis.getUnits(); + org.das2.datum.Units yUnits= yAxis.getUnits(); + + Rectangle r= g.getClipBounds(); + + if ( r==null ) { + xmax= xAxis.getDataMaximum().doubleValue(xUnits); + xmin= xAxis.getDataMinimum().doubleValue(xUnits); + ymax= yAxis.getDataMaximum().doubleValue(yUnits); + ymin= yAxis.getDataMinimum().doubleValue(yUnits); + } else { + xmin= xAxis.invTransform((int)r.getX()).doubleValue(xUnits); + xmax= xAxis.invTransform((int)(r.getX()+r.getWidth())).doubleValue(xUnits); + ymin= yAxis.invTransform((int)r.getY()).doubleValue(yUnits); + ymax= yAxis.invTransform((int)(r.getY()+r.getHeight())).doubleValue(yUnits); + } + + //Support flipped axes + if (xmax < xmin) { + double tmp = xmax; + xmax = xmin; + xmin = tmp; + } + if (ymax < ymin) { + double tmp = ymax; + ymax = ymin; + ymin = tmp; + } + + if ( psym!=Psym.NONE ) { // optimize for common case + int ixmax, ixmin; + + ixmin= VectorUtil.closestXTag(dataSet,xmin,xUnits); + if ( ixmin>0 ) ixmin--; + ixmax= VectorUtil.closestXTag(dataSet,xmax,xUnits); + if ( ixmax=dataSet.getXLength() ) { + ixmax= dataSet.getXLength()-1; + } + + for (int index = ixmin; index <= ixmax; index++) { + double x = dataSet.getXTagDouble(index, xUnits); + double y = dataSet.getDouble(index, yUnits); + double i = xAxis.transform(x, xUnits); + double j = yAxis.transform(y, yUnits); + if ( yUnits.isFill( dataSet.getDouble(index,yUnits) ) || Double.isNaN(y) ) { + skippedLast = true; + } else if (skippedLast) { + newPath.moveTo((float)i, (float)j); + skippedLast = false; + } else if (Math.abs(x - x0) > xSampleWidth) { + //This should put a point on an isolated data value + newPath.lineTo((float)i0, (float)j0); + newPath.moveTo((float)i, (float)j); + skippedLast = false; + } else { + if (histogram) { + double i1 = (i0 + i)/2; + newPath.lineTo((float)i1, (float)j0); + newPath.lineTo((float)i1, (float)j); + newPath.lineTo((float)i, (float)j); + } else { + newPath.lineTo((float)i, (float)j); + } + skippedLast = false; + } + x0= x; + y0= y; + i0= i; + j0= j; + } + path = newPath; + + } else { + path= null; + } + if (getParent() != null) { + getParent().repaint(); + } + DasLogger.getLogger( DasLogger.GRAPHICS_LOG ).fine( "done updatePlotImage" ); + updating=false; + } + + + public PsymConnector getPsymConnector() { + return psymConnector; + } + + public void setPsymConnector(PsymConnector p) { + psymConnector = p; + refreshImage(); + } + + /** Getter for property psym. + * @return Value of property psym. + */ + public Psym getPsym() { + return this.psym; + } + + + /** Setter for property psym. + * @param psym New value of property psym. + */ + public void setPsym(Psym psym) { + if (psym == null) throw new NullPointerException("psym cannot be null"); + Object oldValue = this.psym; + this.psym = psym; + refreshImage(); + } + + /** Getter for property symsize. + * @return Value of property symsize. + */ + public double getSymSize() { + return this.symSize; + } + + /** Setter for property symsize. + * @param symSize New value of property symsize. + */ + public void setSymSize(double symSize) { + this.symSize= symSize; + setPsym(this.psym); + refreshImage(); + } + + /** Getter for property color. + * @return Value of property color. + */ + public Color getColor() { + return color; + } + + /** Setter for property color. + * @param color New value of property color. + */ + public void setColor(Color color) { + this.color= color; + refreshImage(); + } + + public float getLineWidth() { + return lineWidth; + } + + public void setLineWidth(float f) { + lineWidth = f; + refreshImage(); + } + + @Override + protected void installRenderer() { + if ( ! DasApplication.getDefaultApplication().isHeadless() ) { + DasMouseInputAdapter mouseAdapter = parent.mouseAdapter; + DasPlot p= parent; + mouseAdapter.addMouseModule( new MouseModule( p, new LengthDragRenderer( p,p.getXAxis(),p.getYAxis()), "Length" ) ); + } + } + + @Override + protected void uninstallRenderer() { + } + + public static SymbolLineRenderer processLinePlotElement(Element element, DasPlot parent, FormBase form) { + String dataSetID = element.getAttribute("dataSetID"); + Psym psym = Psym.parsePsym(element.getAttribute("psym")); + SymColor color = SymColor.parseSymColor(element.getAttribute("color")); + SymbolLineRenderer renderer = new SymbolLineRenderer( (VectorDataSet)null ); + parent.addRenderer(renderer); + float lineWidth = Float.parseFloat(element.getAttribute("lineWidth")); + try { + renderer.setDataSetID(dataSetID); + } catch (org.das2.DasException de) { + org.das2.util.DasExceptionHandler.handle(de); + } + renderer.setPsym(psym); + renderer.setColor(color); + renderer.setLineWidth(lineWidth); + return renderer; + } + + @Override + public Element getDOMElement(Document document) { + + Element element = document.createElement("lineplot"); + element.setAttribute("dataSetID", getDataSetID()); + element.setAttribute("psym", getPsym().toString()); + element.setAttribute("color", getColor().toString()); + + return element; + } + + /** Getter for property antiAliased. + * @return Value of property antiAliased. + * + */ + public boolean isAntiAliased() { + return this.antiAliased; + } + + /** Setter for property antiAliased. + * @param antiAliased New value of property antiAliased. + * + */ + public void setAntiAliased(boolean antiAliased) { + this.antiAliased = antiAliased; + refreshImage(); + } + + public boolean isHistogram() { + return histogram; + } + + public void setHistogram(final boolean b) { + if (b != histogram) { + histogram = b; + refreshImage(); + } + } + + @Override + public String getListLabel() { + return String.valueOf( this.getDataSetDescriptor() ); + } + + @Override + public javax.swing.Icon getListIcon() { + Image i= new BufferedImage(15,10,BufferedImage.TYPE_INT_ARGB); + Graphics2D g= (Graphics2D)i.getGraphics(); + g.setRenderingHints(DasProperties.getRenderingHints()); + + // leave transparent if not white + if ( color.equals( Color.white ) ) { + g.setColor( Color.GRAY ); + } else { + g.setColor( new Color( 0,0,0,0 ) ); + } + g.fillRect(0,0,15,10); + g.setColor(color); + Stroke stroke0= g.getStroke(); + getPsymConnector().drawLine( g, 2, 3, 13, 7, 1.5f ); + g.setStroke(stroke0); + psym.draw( g, 7, 5, 3.f ); + return new ImageIcon(i); + } + + @Override + public boolean acceptContext(int x, int y) { + return path!=null && path.intersects( x-5, y-5, 10, 10 ); + } + + private int findFirstVisibleSegment( + VectorDataSet dataSet, + Rectangle2D visibleRectangle, + Units xUnits, Units yUnits, + boolean inReverse) + { + int n = dataSet.getXLength(); + for (int i = 0; i < n-1; i++) { + int index0 = inReverse ? n-1-i : i; + int index1 = inReverse ? index0-1 : index0+1; + double x0 = dataSet.getXTagDouble(index0, xUnits); + double x1 = dataSet.getXTagDouble(index1, xUnits); + double y0 = dataSet.getDouble(index0, yUnits); + double y1 = dataSet.getDouble(index1, yUnits); + if (visibleRectangle.intersectsLine(x0, y0, x1, y1)) { + return index0; + } + } + return -1; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/TickCurveRenderer.java b/dasCore/src/main/java/org/das2/graph/TickCurveRenderer.java new file mode 100755 index 000000000..4d9a39b7f --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/TickCurveRenderer.java @@ -0,0 +1,332 @@ +/* File: TickCurveRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on November 3, 2003, 11:43 AM by __FULLNAME__ <__EMAIL__> + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.dataset.VectorUtil; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Units; +import org.das2.util.DasMath; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.components.propertyeditor.Enumeration; +import java.awt.*; +import java.awt.geom.*; + +/** + * + * @author jbf + */ +public class TickCurveRenderer extends Renderer { + + private Stroke stroke; + + TickVDescriptor tickv; + private String xplane; + private String yplane; + + private VectorDataSet xds; + private VectorDataSet yds; + private Units xunits; // xUnits of the axis + private Units yunits; // yUnits of the axis + private double[][] idata; // data transformed to pixel space + + TickLabeller tickLabeller; + + /** Holds value of property tickStyle. */ + private TickStyle tickStyle; + + /** Holds value of property lineWidth. */ + private double lineWidth; + + /** Holds value of property tickLength. */ + private float tickLength; + + public static class TickStyle implements Enumeration { + private String name; + public static TickStyle outer= new TickStyle("Outer"); + public static TickStyle both= new TickStyle("Both"); + private TickStyle(String name) { + this.name= name; + } + public String toString() { + return this.name; + } + public javax.swing.Icon getListIcon() { + return null; + } + + } + + /** The dataset be a Vector data set with planes identified + * by xplane and yplane. + */ + public TickCurveRenderer( DataSet ds, String xplane, String yplane, TickVDescriptor tickv) { + super(ds); + + setTickStyle( TickCurveRenderer.TickStyle.outer ); + setLineWidth( 1.0f ); + setTickLength( 8.0f ); + this.xplane= xplane; + this.yplane= yplane; + this.tickv= tickv; + } + + protected void uninstallRenderer() { + } + + protected void installRenderer() { + } + + private static double length( Line2D line ) { + double dx= line.getX2()-line.getX1(); + double dy= line.getY2()-line.getY1(); + double dist= Math.sqrt( dx*dx + dy*dy ); + return dist; + } + + private static Line2D normalize(Line2D line, double len) { + // make line segment length len, starting at line.getP1() + Point2D p1= line.getP1(); + double dx= line.getX2()-line.getX1(); + double dy= line.getY2()-line.getY1(); + double dist= Math.sqrt( dx*dx + dy*dy ); + Line2D result= (Line2D) line.clone(); + result.setLine( p1.getX(), p1.getY(), p1.getX() + dx/dist * len, p1.getY() + dy/dist*len ); + return result; + } + + private double turnDir( double x1, double y1, double x2, double y2, double x3, double y3 ) { + // returns positive double if turning clockwise, negative is ccw. Number is + // based on the cross product of the two difference vectors. + double dx1= x2-x1; + double dx2= x3-x2; + double dy1= y2-y1; + double dy2= y3-y2; + return dx1*dy2 - dx2*dy1; + } + + private double turnDirAt( double findex ) { + int nvert= xds.getXLength(); + int index0, index1, index2; + if ( findex<1 ) { + index0= 0; + } else if ( findex>nvert-2 ) { + index0= nvert-3; + } else { + index0= (int)Math.floor(findex)-1; + } + index1= index0+1; + index2= index1+1; + + return turnDir( xds.getDouble(index0,xunits), yds.getDouble(index0,yunits), + xds.getDouble(index1,xunits), yds.getDouble(index1,yunits), + xds.getDouble(index2,xunits), yds.getDouble(index2,yunits) ); + } + + private Line2D outsideNormalAt( double findex ) { + int nvert= xds.getXLength(); + int index0= (int)Math.floor(findex); + if ( index0==nvert-1 ) index0--; + double x1= idata[0][index0]; + double x2= idata[0][index0+1]; + double y1= idata[1][index0]; + double y2= idata[1][index0+1]; + + double xinterp= DasMath.interpolate( idata[0], findex ); + double yinterp= DasMath.interpolate( idata[1], findex ); + + double dx= x2-x1; + double dy= y2-y1; + + double turnDir= turnDirAt(findex); + // we want the turnDir of the tick to be opposite turnDir of the curve + + double dxNorm= dy; + double dyNorm= -dx; + + double turnDirTick= -1*(dx*dyNorm-dxNorm*dy); + + if ( turnDir*turnDirTick > 0 ) { // same sign, use the other perp direction. + dxNorm= -dy; + dyNorm= dx; + } + + Line2D normal; + + return normalize( new Line2D.Double(xinterp, yinterp, xinterp+dxNorm,yinterp+dyNorm ), 1. ) ; + + } + + private void drawTick( Graphics2D g, double findex ) { + float tl= getTickLength()*2/3; + Line2D tick= normalize( outsideNormalAt( findex ), tl ); + if ( tickStyle==TickStyle.both ) { + Line2D flipTick= normalize( tick, -tl ); + Line2D bothTick= new Line2D.Double( flipTick.getP2(), tick.getP2() ); + g.draw( bothTick ); + } else { + g.draw( tick ); + } + } + + private double slope( Line2D line ) { + return ( line.getY2()-line.getY1() ) / ( line.getX2()-line.getX1() ); + } + + private void drawLabelTick( Graphics2D g, double findex, int tickNumber ) { + float tl= getTickLength(); + Line2D tick= normalize( outsideNormalAt( findex ), tl ); + if ( tickStyle==TickStyle.both ) { + Line2D flipTick= normalize( tick, -tl ); + Line2D bothTick= new Line2D.Double( flipTick.getP2(), tick.getP2() ); + g.draw( bothTick ); + } else { + g.draw( tick ); + } + + tickLabeller.labelMajorTick( g, tickNumber, tick ); + + } + + public void render(java.awt.Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + if ( ds==null ) { + return; + } + + Graphics2D g= (Graphics2D)g1; + g.setStroke( stroke ); + + g.setColor( Color.black ); + + DataSet ds= getDataSet(); + xds= (VectorDataSet) ds.getPlanarView(xplane); + yds= (VectorDataSet) ds.getPlanarView(yplane); + xunits= xds.getYUnits(); + yunits= yds.getYUnits(); + + idata= new double[2][xds.getXLength()]; + for ( int i=0; i=0 && findex[i]=0 && findex[i] 0) ? (Datum) majorTicks.get(0) : (Datum) minorTicks.get(0); + Units u = d.getUnits(); + double[] major = new double[majorTicks.size()]; + for (int i = 0; i < major.length; i++) { + major[i] = ((Datum) majorTicks.get(i)).doubleValue(u); + } + double[] minor = new double[minorTicks.size()]; + for (int i = 0; i < minor.length; i++) { + minor[i] = ((Datum) minorTicks.get(i)).doubleValue(u); + } + return new TickVDescriptor(minor, major, u); + } + + public DatumVector getMajorTicks() { + return tickV; + } + + public DatumVector getMinorTicks() { + return minorTickV; + } + + public DatumFormatter getFormatter() { + return this.datumFormatter; + } + + /** + * Locates the next or previous tick starting at xDatum. + * + * @param xDatum find the tick closest to this. + * @param direction -1 previous, 1 next, 0 closest + * @param minor find closest minor tick, major if false. + * @return the closest tick. If there is no tick in the given direction, then + * the behavior is undefined. + */ + public Datum findTick(Datum xDatum, double direction, boolean minor) { + int majorLen; + int minorLen; + double[] ticks; + + // direction<0 nearest left, direction>0 nearest right, direction=0 nearest. + if (tickV == null) { + return xDatum; + } + majorLen = tickV.getLength(); + minorLen = minorTickV.getLength(); + ticks = new double[majorLen + minorLen]; + for (int i = 0; i < majorLen; i++) { + ticks[i] = tickV.doubleValue(i, units); + } + for (int i = 0; i < minorLen; i++) { + ticks[i + majorLen] = minorTickV.doubleValue(i, units); + } + + int iclose = 0; + double close = Double.MAX_VALUE; + + double x = xDatum.doubleValue(units); + + for (int i = 0; i < ticks.length; i++) { + if (direction < 0 && ticks[i] < x && x - ticks[i] < close) { + iclose = i; + close = x - ticks[i]; + } else if (direction > 0 && x < ticks[i] && ticks[i] - x < close) { + iclose = i; + close = ticks[i] - x; + } + if (direction == 0 && Math.abs(ticks[i] - x) < close) { + iclose = i; + close = Math.abs(ticks[i] - x); + } + } + + return Datum.create(ticks[iclose], units); + + } + + /** + * Defining method for getting the range close to the given range, + * but containing at least one minor(or major) tick interval. + * + * @param minor find the range from the minor ticks. + */ + public DatumRange enclosingRange(DatumRange dr, boolean minor) { + Datum s1 = findTick(dr.min(), 0, minor); + Datum s2 = findTick(dr.max(), 0, minor); + DatumRange result; + if (s1.equals(s2)) { + s1 = findTick(dr.min(), -1, true); + s2 = findTick(dr.max(), 1, true); + } + return new DatumRange(s1, s2); + } + + public void setFormatter(DatumFormatter datumFormatter) { + this.datumFormatter = datumFormatter; + } + + /** Returns a String representation of the TickVDescriptor. + * @return a String representation of the TickVDescriptor. + * + */ + public String toString() { + String s = "tickV=" + getMajorTicks(); + s += ",minor=" + getMinorTicks(); + return s; + } + + /** + * return a set of linear ticks, within the given constraints. + */ + public static TickVDescriptor bestTickVLinear(Datum min, Datum max, int nTicksMin, int nTicksMax, boolean fin) { + + TickVDescriptor res = new TickVDescriptor(); + + res.units = min.getUnits(); + double minimum = min.doubleValue(res.units); + double maximum = max.doubleValue(res.units); + + int targetTicks = Math.max(Math.min(6, nTicksMax), nTicksMin); + + double maj = (maximum - minimum) / (targetTicks - 1); + double mag = DasMath.exp10(Math.floor(DasMath.log10(maj))); + double absissa = maj / mag; + + if (absissa < 1.666) { + absissa = 1.0; + } else if (absissa < 3.333) { + absissa = 2.0; + } else if (absissa < 9.0) { + absissa = 5.0; + } else { + absissa = 1.; + mag *= 10; + } + + double axisLengthData = maximum - minimum; + + int minorPerMajor; + + if (absissa == 5.) { + minorPerMajor = 5; + } else if (absissa == 2.) { + minorPerMajor = 2; + } else { + minorPerMajor = 10; + } + + double minorTickSize = absissa * mag / minorPerMajor; + double majorTickSize = minorTickSize * minorPerMajor; + double firstTick = majorTickSize * Math.ceil((minimum - axisLengthData) / majorTickSize - 0.01); + double lastTick = majorTickSize * Math.floor((maximum + axisLengthData) / majorTickSize + 0.01); + + int nTicks = 1 + (int) Math.round((lastTick - firstTick) / majorTickSize); + + double[] result = new double[nTicks]; + for (int i = 0; i < nTicks; i++) { + result[i] = firstTick + (i * minorPerMajor * minorTickSize); + } + res.tickV = DatumVector.newDatumVector(result, res.units); + + int ifirst = nTicks / 3; + int ilast = 2 * nTicks / 3; + + res.datumFormatter = DatumUtil.bestFormatter(res.units.createDatum(result[ifirst]), + res.units.createDatum(result[ilast]), ilast - ifirst); + + double firstMinor = firstTick; + double lastMinor = lastTick; + int nMinor = (int) ((lastMinor - firstMinor) / minorTickSize + 0.5); + double[] minorTickV = new double[nMinor]; + for (int i = 0; i < nMinor; i++) { + minorTickV[i] = firstMinor + i * minorTickSize; + } + res.minorTickV = DatumVector.newDatumVector(minorTickV, res.units); + + return res; + + } + private static final DatumFormatter DEFAULT_LOG_FORMATTER; + + + static { + try { + DatumFormatterFactory factory = DefaultDatumFormatterFactory.getInstance(); + DEFAULT_LOG_FORMATTER = factory.newFormatter("0E0"); + } catch (ParseException pe) { + throw new RuntimeException(pe); + } + } + + /** + * return a set of log ticks, within the given constraints. + */ + public static TickVDescriptor bestTickVLogNew(Datum minD, Datum maxD, int nTicksMin, int nTicksMax, boolean fin) { + + TickVDescriptor ticks = new TickVDescriptor(); + ticks.units = minD.getUnits(); + double min = minD.doubleValue(ticks.units); + double max = maxD.doubleValue(ticks.units); + + if (max <= 0) { + max = 100.; + } + if (min <= 0) { + min = max / 1000.; + } + double logMin = DasMath.log10(min); + double logMax = DasMath.log10(max); + int ntick0 = (int) (Math.floor(logMax * 0.999) - Math.ceil(logMin * 1.001) + 1); + + if (ntick0 < 2) { + TickVDescriptor result = bestTickVLinear(minD, maxD, nTicksMin, nTicksMax, fin); + int ii = 0; + + DatumVector majortics = result.getMajorTicks(); + Units u = majortics.getUnits(); + + while (ii < majortics.getLength() && majortics.get(ii).doubleValue(u) <= 0) { + ii++; + } + majortics = majortics.getSubVector(ii, majortics.getLength()); + + DatumVector minortics = result.getMinorTicks(); + while (ii < minortics.getLength() && minortics.get(ii).doubleValue(u) <= 0) { + ii++; + } + minortics = minortics.getSubVector(ii, minortics.getLength()); + + DatumFormatter df = result.datumFormatter; + result = TickVDescriptor.newTickVDescriptor(majortics, minortics); + result.datumFormatter = df; + + return result; + + } + + if (ntick0 > nTicksMax) { + Units units = minD.getUnits(); + Datum logMinD = units.createDatum(DasMath.log10(min)); + Datum logMaxD = units.createDatum(DasMath.log10(max)); + TickVDescriptor linTicks = bestTickVLinear(logMinD, logMaxD, nTicksMin, nTicksMax, fin); + double[] tickV = linTicks.tickV.toDoubleArray(linTicks.units); + + // copy over the ticks into the linear space, but cull the fractional ones + int i2 = 0; + for (int i = 0; i < tickV.length; i++) { + if (tickV[i] % 1. == 0.) { + tickV[i2++] = DasMath.exp10(tickV[i]); + } + } + double[] t = tickV; + tickV = new double[i2]; + for (int i = 0; i < i2; i++) { + tickV[i] = t[i]; + } + + // now fill in the minor ticks, if there's room + int idx = 0; + double[] minorTickV; + if ((tickV[1] / tickV[0]) <= 10.00001) { + minorTickV = new double[(tickV.length + 1) * 9]; + for (int j = 2; j < 10; j++) { + minorTickV[idx++] = j * (tickV[0] / 10); + } + for (int i = 0; i < tickV.length; i++) { + for (int j = 2; j < 10; j++) { + minorTickV[idx++] = j * tickV[i]; + } + } + } else { + minorTickV = linTicks.minorTickV.toDoubleArray(linTicks.units); + for (int i = 0; i < minorTickV.length; i++) { + minorTickV[i] = DasMath.exp10(minorTickV[i]); + } + } + + linTicks.tickV = DatumVector.newDatumVector(tickV, linTicks.units); + linTicks.minorTickV = DatumVector.newDatumVector(minorTickV, linTicks.units); + linTicks.datumFormatter = DEFAULT_LOG_FORMATTER; + return linTicks; + } + + double min3 = min / (max / min); + double max3 = max * (max / min); + + double dMinTick = DasMath.roundNFractionalDigits(DasMath.log10(min3), 4); + int minTick = (int) Math.ceil(dMinTick); + double dMaxTick = DasMath.roundNFractionalDigits(DasMath.log10(max3), 4); + int maxTick = (int) Math.floor(dMaxTick); + + int nTicks = (maxTick - minTick) + 1; + + double[] major; // major ticks labels + double[] minors; // minor ticks to label -- {}, or { 2,3,4,5,6,7,8,9 }! !! + + major = new double[nTicks]; + for (int i = 0; i < nTicks; i++) { + major[i] = i + minTick; + } + minors = new double[]{2, 3, 4, 5, 6, 7, 8, 9}; + + ticks.datumFormatter = DEFAULT_LOG_FORMATTER; + + int firstMinorTickCycle = (int) Math.floor(DasMath.log10(min3)); + int lastMinorTickCycle = (int) Math.floor(DasMath.log10(max3)); + + double[] minorTickV = null; + int idx = 0; + minorTickV = new double[(lastMinorTickCycle - firstMinorTickCycle + 1) * minors.length]; + for (int i = firstMinorTickCycle; i <= lastMinorTickCycle; i++) { + for (int j = 0; j < minors.length; j++) { + minorTickV[idx++] = DasMath.exp10(i) * minors[j]; + } + } + ticks.minorTickV = DatumVector.newDatumVector(minorTickV, ticks.units); + + for (int i = 0; i < major.length; i++) { + major[i] = DasMath.exp10(major[i]); + } + ticks.tickV = DatumVector.newDatumVector(major, ticks.units); + + return ticks; + + } + + /** + * find a divider that gives the biggest divisions for unitsPerDecade. For example, we want to + * divide a pizza evenly. If the pizza has 12 pieces, then we can return 1,2,3,4 or 6. + * If the pizza has 10 pieces, then we can return 1,2,or 5. + * A minute has 60 seconds, so we can return 1,2,5,10,20,30. (why not 6 or 12? exclude param introduced) + * A day had 24 hours, so we can return 1,2,4,6,or 12. + * A circle had 360 degrees, so we can return 1,2,5,10,15,30,60,45,90, + * + * Find the biggest of tt that divides into unitsPerDecade and is less than sizeLimit. + * + * @param sizeLimit max number of pieces + * @param factors number of divisions allowed. + * @param exclude don't allow this one. + */ + private static int getMantissa(int sizeLimit, int unitsPerDecade, int exclude) { + int[] tt = new int[]{1, 2, 3, 5, 6, 10, 12, 15, 20, 25, 30, 45, 60, 90, 100, 200, 500, 1000, 2000, 5000, 10000}; + int biggest = 1; + for (int i = 0; i < tt.length && tt[i] <= sizeLimit; i++) { + if (unitsPerDecade % tt[i] == 0 && (exclude == 0 || tt[i] % exclude != 0)) { + biggest = tt[i]; + } + } + return biggest; + } + + /** + * return list of mantissas as described in getMantissa. The goal here is to + * return a list of mantissas that divide the pie into integer number of pieces + * and in a number of divisions humans like. THIS IS THE GENERAL CODE!!! + * + * If the pizza has 10 pieces, then we can return 1,2,or 5. + * A minute has 60 seconds, so we can return 1,2,5,10,20,30. (why not 6 or 12? exclude param introduced) + * A day had 24 hours, so we can return 1,2,4,6,or 12. + * A circle had 360 degrees, so we can return 1,2,5,10,15,30,60,45,90, + */ + private static List/**/ getMantissas(int divisionsPerDecade, int exclude, int include) { + int[] tt = new int[]{1, 2, 3, 5, 6, 10, 12, 15, 20, 25, 30, 45, 60, 90, 100, 200, 500, 1000, 2000, 5000, 10000}; + int biggest = 1; + ArrayList result = new ArrayList(); + for (int i = 0; i < tt.length && tt[i] < divisionsPerDecade; i++) { + boolean incl = include != 0 && tt[i] % include == 0; + boolean excl = exclude != 0 && tt[i] % exclude == 0; + if (excl && !incl) { + continue; + } + if (divisionsPerDecade % tt[i] == 0) { + result.add(new Integer(tt[i])); + } + } + return result; + } + + /** + * + * @param minD + * @param maxD + * @param units + * @param biggerUnits + * @param biggerUnitsCount kludge for when units==YEAR and biggerUnits==YEAR, so we can get "10 years" + * @param unitLengthNanos + * @param mantissa + * @param fin true when this is called for the last time, for debugging. + * @return + */ + private static TickVDescriptor countOffTicks2(Datum minD, Datum maxD, + TimeUtil.TimeDigit units, TimeUtil.TimeDigit biggerUnits, int biggerUnitsCount, + long unitLengthNanos, int mantissa, boolean fin) { + DatumRange range = new DatumRange(minD, maxD); + + Datum majorTickLength = Units.nanoseconds.createDatum(unitLengthNanos * mantissa); + + Datum first; + + /** + * next is the next major tick + */ + Datum next; + + if (units == TimeUtil.TD_YEAR) { + int iyear = TimeUtil.toTimeArray(minD)[0]; + iyear = (iyear / biggerUnitsCount) * biggerUnitsCount; // round to mantissa + first = TimeUtil.createTimeDatum(iyear, 1, 1, 0, 0, 0, 0); + } else { + int[] digits = TimeUtil.toTimeArray(minD); + first = TimeUtil.prev(CalendarTime.Step.HigerStep(units.getOrdinal()), minD); + } + + + next = TimeUtil.next(biggerUnits.getOrdinal(), first); + for (int i = 1; i < biggerUnitsCount; i++) { + next = TimeUtil.next(biggerUnits.getOrdinal(), next); + } + next = next.subtract(majorTickLength.divide(2)); // don't bump right up to it. + + ArrayList majorTicks = new ArrayList(); + ArrayList minorTicks = new ArrayList(); + Datum d = first; + + TimeUtil.TimeDigit minorUnits = units; + int minorMantissa = 1; + + TimeUtil.TimeDigit majorUnits = units; + int majorMantissa = mantissa; + + if (majorMantissa == 1) { + minorUnits = TimeUtil.TimeDigit.fromOrdinal(CalendarTime.Step.LowerStep(majorUnits.getOrdinal())); + minorMantissa = majorUnits==TimeUtil.TD_MONTH ? 10 : majorUnits.divisions() / 4; + } + + Datum nextMajorTick= TimeUtil.next( majorUnits, majorMantissa, d ); + + if ( minorMantissa==0 ) { + throw new RuntimeException("minorMantissa==0"); + } + while (d.le(maxD)) { + + while (d.le(next)) { + if (DatumRangeUtil.sloppyContains(range, d)) { + majorTicks.add(d); + } + nextMajorTick= TimeUtil.next(majorUnits, majorMantissa, d); + while ( d.lt(nextMajorTick) ) { + if (DatumRangeUtil.sloppyContains(range, d)) { + minorTicks.add(d); + } + d = TimeUtil.next(minorUnits, minorMantissa, d); + } + } + next = next.add(majorTickLength.divide(2)); // this is all to avoid March-30, April-1. + while (d.le(next)) { + while ( d.lt(nextMajorTick) ) { + if (DatumRangeUtil.sloppyContains(range, d)) { + if (d.lt(next)) { + minorTicks.add(d); // so it doesn't get added twice + } + } + d = TimeUtil.next(minorUnits, minorMantissa, d); + } + nextMajorTick= TimeUtil.next(majorUnits, majorMantissa, d); + } + d = next; + next = TimeUtil.next(majorUnits, majorMantissa, next); + next = next.subtract(majorTickLength.divide(2)); + } + + return TickVDescriptor.newTickVDescriptor(majorTicks, minorTicks); + + } + + private static boolean checkMono(DatumVector ticks) { + Datum d = ticks.get(0); + for (int i = 1; i < ticks.getLength(); i++) { + if (ticks.get(i).lt(d)) { + // System.err.println("TickVDescriptor: not mono at " + i + ": " + d + " > " + ticks.get(i)); + return false; + } + d = ticks.get(i); + } + return true; + } + + /** + * return a set of ticks counting off ordinal time ranges, such as months, years, days, etc. + */ + public static TickVDescriptor bestTickVTimeOrdinal(Datum minD, Datum maxD, int nTicksMin, int nTicksMax, boolean fin) { + + //System.err.println( "bestTimeOrdinal: "+ new DatumRange(minD,maxD) + " "+nTicksMin+" "+nTicksMax ); + Datum lengthMin = maxD.subtract(minD).divide(nTicksMax + 1); // this is the approximation--you can't simply divide + Datum lengthMax = maxD.subtract(minD).divide(Math.max(1, nTicksMin - 1)); + + long lengthNanosMax = (long) lengthMax.doubleValue(Units.nanoseconds); + double lengthDaysMax = lengthMax.doubleValue(Units.days); + long lengthNanosMin = (long) lengthMin.doubleValue(Units.nanoseconds); + double lengthDaysMin = lengthMin.doubleValue(Units.days); + + TimeUtil.TimeDigit[] units = new TimeUtil.TimeDigit[]{TimeUtil.TD_NANO, TimeUtil.TD_SECOND, + TimeUtil.TD_MINUTE, TimeUtil.TD_HOUR, TimeUtil.TD_DAY, TimeUtil.TD_MONTH, TimeUtil.TD_YEAR + }; + long[] lengths = new long[]{1, (long) 1e9, (long) 60e9, (long) 3600e9, (long) 86400e9, 30 * (long) 86400e9, 365 * (long) 86400e9}; + int[] excludeFactors = new int[]{0, 6, 6, 0, 3, 0, 0}; + int[] includeFactors = new int[]{0, 30, 30, 0, 15, 0, 0}; + + // find the range of units to try + int biggestUnitIndex, smallestUnitIndex; + int lessThanIndex = 0; + while (lessThanIndex < units.length && lengths[lessThanIndex] < lengthNanosMax) { + lessThanIndex++; + } + lessThanIndex--; + biggestUnitIndex = lessThanIndex; + + lessThanIndex = 0; + while (lessThanIndex < units.length && lengths[lessThanIndex] < lengthNanosMin) { + lessThanIndex++; + } + lessThanIndex--; + + smallestUnitIndex = lessThanIndex; + + TickVDescriptor bestTickV = null; + TickVDescriptor secondBestTickV = null; // fallback + TimeUtil.TimeDigit bestUnit = null; + TimeUtil.TimeDigit secondBestUnit = null; + + // loop over units and mantissas for each unit + for (int iunit = smallestUnitIndex; bestTickV == null && iunit <= biggestUnitIndex; iunit++) { + + int mantissa; + + TimeDigit biggerUnits; + if (units[lessThanIndex] == TimeUtil.TD_YEAR) { + biggerUnits = TimeUtil.TD_YEAR; + } else { + biggerUnits = units[lessThanIndex + 1]; + } + + List/**/ mantissas; + + if (units[iunit] != TimeUtil.TD_YEAR) { + int factors; + factors = (int) (lengths[iunit + 1] / lengths[iunit]); + mantissas = getMantissas(factors, excludeFactors[lessThanIndex], includeFactors[lessThanIndex]); + } else { + int factors = 10; + mantissas = getMantissas(factors, excludeFactors[lessThanIndex], includeFactors[lessThanIndex]); + } + + TickVDescriptor test; + + for (int imant = 0; imant < mantissas.size(); imant++) { + mantissa = ((Integer) mantissas.get(imant)).intValue(); + + int biggerUnitsCount = units[iunit] == biggerUnits ? mantissa : 1; + + DatumRange visibleRange= new DatumRange( minD, maxD ); + DatumRange ticksRange= fin ? DatumRangeUtil.rescale( visibleRange, -1.0, 2.0 ) : visibleRange; + + test = countOffTicks2( ticksRange.min(), ticksRange.max(), units[iunit], biggerUnits, biggerUnitsCount, lengths[lessThanIndex], mantissa, fin); + // // for debugging + //if (fin && test.tickV.getLength() <= nTicksMax) { + // test = countOffTicks2(ticksRange.min(), ticksRange.max(), units[iunit], biggerUnits, biggerUnitsCount, lengths[lessThanIndex], mantissa, fin); + //} + //if (!checkMono(test.getMinorTicks())) { + // test = countOffTicks2(minD, maxD, units[iunit], biggerUnits, biggerUnitsCount, lengths[lessThanIndex], mantissa, fin); + //} + int nticks= fin ? test.tickV.getLength() / 3 : test.tickV.getLength(); + if ( nticks <= nTicksMax && nticks >= nTicksMin) { + bestTickV = test; + bestUnit = units[iunit]; + break; + } + + if ( nticks >= nTicksMin) { // this is our back up. + secondBestTickV = test; + secondBestUnit = units[iunit]; + } + + } + + } + + if (bestTickV == null) { + bestUnit = secondBestUnit; + bestTickV = secondBestTickV; + } + + TickVDescriptor ticks = bestTickV; + + if (bestUnit == null) { + throw new IllegalArgumentException("failed to find best unit"); + } + ticks.datumFormatter = TimeDatumFormatter.formatterForScale(bestUnit.getOrdinal(), new DatumRange(minD, maxD)); + + return ticks; + + } + + public static TickVDescriptor bestTickVTime(Datum minD, Datum maxD, int nTicksMin, int nTicksMax, boolean fin) { + + Datum length = maxD.subtract(minD); + + Datum minute = Datum.create(60.0, Units.seconds); + if (maxD.subtract(minD).lt(minute)) { + Datum base = TimeUtil.prevMidnight(minD); + + Units offUnits = Units.seconds; + Datum offMin = minD.subtract(base).convertTo(offUnits); + Datum offMax = maxD.subtract(base).convertTo(offUnits); + TickVDescriptor offTicks = bestTickVLinear(offMin, offMax, nTicksMin, nTicksMax, fin); + + DatumVector minorTicks = offTicks.getMinorTicks().add(base); + DatumVector majorTicks = offTicks.getMajorTicks().add(base); + + TickVDescriptor result = TickVDescriptor.newTickVDescriptor(majorTicks, minorTicks); + result.datumFormatter = DatumUtil.bestFormatter(majorTicks); + return result; + } + + if (maxD.subtract(minD).gt(Datum.create(10 * 365, Units.days))) { + int yearMin = new CalendarTime(minD).year(); + int yearMax = new CalendarTime(maxD).year(); + TickVDescriptor yearTicks = bestTickVLinear(Units.dimensionless.createDatum(yearMin), + Units.dimensionless.createDatum(yearMax), nTicksMin, nTicksMax, fin); + yearTicks.units = minD.getUnits(); + double[] tickV = yearTicks.tickV.toDoubleArray(Units.dimensionless); + for (int i = 0; i < tickV.length; i++) { + int iyear = (int) tickV[i]; + tickV[i] = TimeUtil.convert(iyear, 1, 1, 0, 0, 0, (TimeLocationUnits) yearTicks.units); + } + yearTicks.tickV = DatumVector.newDatumVector(tickV, yearTicks.units); + double[] minorTickV = yearTicks.minorTickV.toDoubleArray(Units.dimensionless); + for (int i = 0; i < minorTickV.length; i++) { + int iyear = (int) minorTickV[i]; + minorTickV[i] = TimeUtil.convert(iyear, 1, 1, 0, 0, 0, (TimeLocationUnits) yearTicks.units); + } + yearTicks.minorTickV = DatumVector.newDatumVector(minorTickV, yearTicks.units); + Datum t1 = yearTicks.getMajorTicks().get(0); + int nticks = yearTicks.getMajorTicks().getLength(); + Datum t2 = yearTicks.getMajorTicks().get(nticks - 1); + yearTicks.datumFormatter = DatumUtil.bestTimeFormatter(t1, t2, nticks); + return yearTicks; + } else { + return bestTickVTimeOrdinal(minD, maxD, nTicksMin, nTicksMax, fin); + + } + } + +} + diff --git a/dasCore/src/main/java/org/das2/graph/TimeRangeLabel.java b/dasCore/src/main/java/org/das2/graph/TimeRangeLabel.java new file mode 100755 index 000000000..341b61982 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/TimeRangeLabel.java @@ -0,0 +1,298 @@ +/* File: TimeRangeLabel.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph; + +import org.das2.datum.format.DatumFormatter; +import org.das2.datum.Units; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.datum.Datum; +import org.das2.datum.UnitsConverter; +import org.das2.datum.TimeUtil; +import org.das2.DasProperties; +import org.das2.event.MouseModule; +import org.das2.util.DasMath; +import org.das2.util.DasExceptionHandler; + +import java.awt.*; +import java.awt.geom.*; +import java.beans.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.DecimalFormat; +import javax.swing.JFrame; +import javax.swing.JPanel; +import org.das2.datum.DatumRange; + +/** + * + * @author jbf + */ +public class TimeRangeLabel extends DasCanvasComponent { + + private static final DatumFormatter MINUTES; + private static final DatumFormatter SECONDS; + private static final DatumFormatter MILLISECONDS; + + private boolean rangeLabel; + + static { + try { + MINUTES = new TimeDatumFormatter("yyyy-MM-dd '('DDD')' HH:mm"); + SECONDS = new TimeDatumFormatter("yyyy-MM-dd '('DDD')' HH:mm:ss"); + MILLISECONDS = new TimeDatumFormatter("yyyy-MM-dd '('DDD')' HH:mm:ss.SSS"); + } + catch (java.text.ParseException pe) { + //If this is happening, then there is a major problem. + throw new RuntimeException(pe); + } + } + + private DataRange dataRange; + + private Datum min = TimeUtil.createTimeDatum(2000, 1, 1, 0, 0, 0, 0); + + private Datum max = TimeUtil.createTimeDatum(2000, 1, 2, 0, 0, 0, 0); + + private DatumFormatter df; + + double emOffset = 2.0; + + private class DataRangePropertyChangeListener implements PropertyChangeListener { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("minimum")) { + setMin(Datum.create(dataRange.getMinimum(), dataRange.getUnits())); + } + else if (evt.getPropertyName().equals("maximum")) { + setMax(Datum.create(dataRange.getMaximum(), dataRange.getUnits())); + } + } + } + + private boolean startOnly = false; + + /** Creates a new instance of TimeRangeLabel */ + public TimeRangeLabel(DataRange dataRange) { + this.dataRange= dataRange; + this.min = Datum.create(dataRange.getMinimum(), dataRange.getUnits()); + this.max = Datum.create(dataRange.getMaximum(), dataRange.getUnits()); + DataRangePropertyChangeListener listener = new DataRangePropertyChangeListener(); + dataRange.addPropertyChangeListener("minimum", listener); + dataRange.addPropertyChangeListener("maximum", listener); + updateFormatter(); + } + + public TimeRangeLabel() { + updateFormatter(); + } + + public DatumRange getRange() { + return new DatumRange(min, max); + } + + public void setRange(DatumRange range) { + if (range == null) { + min = max = null; + } + else { + min = range.min(); + max = range.max(); + } + repaint(); + } + + public Datum getMax() { + return max; + } + + public void setMax(Datum max) { + this.max = max; + repaint(); + } + + public Datum getMin() { + return min; + } + + public void setMin(Datum min) { + this.min = min; + repaint(); + } + + protected void paintComponent(Graphics graphics) { + Graphics2D g= (Graphics2D) graphics; + g.setRenderingHints(DasProperties.getRenderingHints()); + + FontMetrics fm= g.getFontMetrics(); + + int y = getRow().getDMinimum(); + int x = getColumn().getDMinimum(); + + g.translate(-getX(),-getY()); + + int yLevel= y - (int)(getFont().getSize()*emOffset + 0.5); + + if ( this.rangeLabel ) { + String label= getRange().toString(); + g.drawString( label, x, yLevel ); + return; + } else { + g.drawString(df.format(min), x, yLevel ); + } + + if (!startOnly) { + String label= df.format(max); + x += getColumn().getWidth() - fm.stringWidth(label); + g.drawString(label, x, yLevel ); + } + } + public void resize() { + Rectangle bounds= new Rectangle( + getColumn().getDMinimum()-30, + getRow().getDMinimum()-(int)(getFont().getSize()*(emOffset+1)+0.5), + getColumn().getWidth()+60, + getFont().getSize()*3 ); + this.setBounds( bounds ); + } + + private void updateFormatter() { + //UnitsConverter converter = Units.getConverter(dataRange.getUnits(), Units.t2000); + double min = this.min.doubleValue(Units.t2000); + double max = this.max.doubleValue(Units.t2000); + min = secondsSinceMidnight(min); + max = secondsSinceMidnight(max); + int minMS = (int)(min * 1000.); + int maxMS = (int)(max * 1000.); + if ((minMS % 1000) != 0 || (maxMS % 1000) != 0) { + df = MILLISECONDS; + } + else if ((minMS % 60000) != 0 || (maxMS % 60000) != 0) { + df = SECONDS; + } + else { + df = MINUTES; + } + } + + private double secondsSinceMidnight(double t2000) { + if (t2000 < 0) { + t2000 = t2000 % 86400; + if (t2000 == 0) { + return 0; + } else { + return 86400 + t2000; + } + } else { + return t2000 % 86400; + } + } + + public PropertyChangeListener createDataRangePropertyListener() { + return new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + String propertyName = e.getPropertyName(); + Object oldValue = e.getOldValue(); + Object newValue = e.getNewValue(); + if (propertyName.equals("log")) { + update(); + firePropertyChange("log", oldValue, newValue); + } + else if (propertyName.equals("minimum")) { + update(); + updateFormatter(); + firePropertyChange("dataMinimum", oldValue, newValue); + } + else if (propertyName.equals("maximum")) { + update(); + updateFormatter(); + firePropertyChange("dataMaximum", oldValue, newValue); + } + markDirty(); + } + }; + } + + public boolean isStartOnly() { + return startOnly; + } + + public void setStartOnly(boolean b) { + this.startOnly = b; + if (isDisplayable()) { + repaint(); + } + } + + /** + * Use strings like "2004-01-01 00:00 to 00:20" to identify times. + */ + public void setRangeLabel( boolean b ) { + this.rangeLabel= b; + repaint(); + } + + public boolean isRangeLabel( ) { + return this.rangeLabel; + } + + public double getEmOffset() { + return emOffset; + } + + public void setEmOffset(double emOffset) { + this.emOffset = emOffset; + if (getCanvas() != null) { + resize(); + repaint(); + } + } + + public static void main( String[] args ) { + JFrame jframe= new JFrame(); + jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + JPanel panel= new JPanel(); + DasCanvas canvas= new DasCanvas(300,300); + + DasRow row1= new DasRow(canvas,0.1,0.2); + DasRow row2 = new DasRow(canvas, 0.3, 0.4); + DasRow row3 = new DasRow(canvas, 0.5, 0.6); + DasRow row4 = new DasRow(canvas, 0.7, 0.8); + DasColumn column= new DasColumn(canvas,0.1,0.9); + + DataRange dataRange1= new DataRange(null,TimeUtil.createValid("1998-01-01 12:20"),TimeUtil.createValid("1999-01-01"),false); + DataRange dataRange2 = new DataRange(null, TimeUtil.createValid("1998-01-02 12:30:02"), TimeUtil.createValid("1999-01-01"),false); + DataRange dataRange3 = new DataRange(null, TimeUtil.createValid("1998-01-03 12:40:02.244"), TimeUtil.createValid("1999-01-01"),false); + + canvas.add(new TimeRangeLabel(dataRange1),row1,column); + canvas.add(new TimeRangeLabel(dataRange2),row2,column); + canvas.add(new TimeRangeLabel(dataRange3),row3,column); + + panel.setLayout(new BorderLayout()); + panel.add(canvas,BorderLayout.CENTER); + jframe.setContentPane(panel); + jframe.pack(); + jframe.setVisible(true); + + canvas.repaint(); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/XAxisDataLoader.java b/dasCore/src/main/java/org/das2/graph/XAxisDataLoader.java new file mode 100644 index 000000000..052614f64 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/XAxisDataLoader.java @@ -0,0 +1,282 @@ +/* + * DataLoader.java + * + * Created on September 10, 2005, 5:28 AM + * + * Remove the data loading responsibilities from the Renderer, and introduce + * pluggable strategies for data loading. + * + */ + +package org.das2.graph; + +import org.das2.CancelledOperationException; +import org.das2.dataset.CacheTag; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.DataSetUpdateEvent; +import org.das2.dataset.DataSetUpdateListener; +import org.das2.dataset.DataSetUtil; +import org.das2.dataset.NoDataInIntervalException; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.graph.DataLoader.Request; +import org.das2.datum.DatumUtil; +import org.das2.stream.StreamException; +import org.das2.system.DasLogger; +import org.das2.util.DasExceptionHandler; +import org.das2.util.monitor.ProgressMonitor; +import java.io.InterruptedIOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * + * @author Jeremy + */ +public class XAxisDataLoader extends DataLoader implements DataSetUpdateListener { + + DasAxis xaxis; + DataSetDescriptor dsd; + ProgressMonitor progressMonitor; + Logger logger= DasLogger.getLogger( DasLogger.GRAPHICS_LOG, "XAxisDataLoader" ); + + Request currentRequest; + List unsolicitedRequests; + + Object lockObject= new Object(); + + /** Creates a new instance of DataLoader */ + public XAxisDataLoader( Renderer r, DataSetDescriptor dsd ) { + super(r); + this.dsd= dsd; + this.logger= logger; + if ( dsd!=null ) dsd.addDataSetUpdateListener( this ); + unsolicitedRequests= new ArrayList(); + } + + public void update() { + if ( isActive() ) { + logger.finer("enter XAxisDataLoader.update"); + DasPlot p= getRenderer().getParent(); + if ( p==null ) { + logger.fine("plot is null, no need to load"); + } else { + DasAxis xAxis = p.getXAxis(); + DasAxis yAxis = p.getYAxis(); + if ( xAxis.valueIsAdjusting()==false && yAxis.valueIsAdjusting()==false ) { + loadDataSet( xAxis, yAxis ); + } + } + } else { + logger.finer("enter XAxisDataLoader.update, ignored not active"); + } + } + + /* requests a reload of data, indicating its current data set in case it's + * suitable. + */ + private void loadDataSet( DasAxis xAxis, DasAxis yAxis ) { + + logger.fine( "render requests dataset for x:"+xAxis.getMemento() + " y:"+yAxis.getMemento()); + + if ( xaxis==null ) this.xaxis= xAxis; + + if ( xaxis.getColumn()==DasColumn.NULL ) { + logger.fine("column not set yet"); + return; + } + + if ( dsd==null ) { + logger.fine("dsd is null, nothing to do"); + return; + } + + synchronized (lockObject) { + if ( currentRequest!=null ) { + synchronized (currentRequest) { + if ( ! xAxis.getMemento().equals( currentRequest.xmem ) ) { + logger.fine( "cancel old request: "+currentRequest ); + ProgressMonitor monitor= currentRequest.monitor; + currentRequest= null; + monitor.cancel(); + } else { + logger.fine( "ignore repeat request" ); + return; // ignore the repeated request + } + } + } + + Datum resolution; + Datum dataRange1 = xAxis.getDataMaximum().subtract(xAxis.getDataMinimum()); + + double deviceRange = Math.floor(xAxis.getColumn().getDMaximum() + 0.5) - Math.floor(xAxis.getColumn().getDMinimum() + 0.5); + if ( isFullResolution() ) { + resolution = null; + } else { + resolution = dataRange1.divide(deviceRange); + } + + if ( deviceRange==0.0 ) { + // this condition occurs sometimes at startup, it's not known why + return; + } + + DasPlot parent= renderer.getParent(); + + DatumRange loadRange= xAxis.getDatumRange(); + + CacheTag cacheTag= new CacheTag( loadRange, resolution ); + if ( dsd.getDataSetCache().haveStored(dsd, cacheTag) ) { + renderer.setDataSet( dsd.getDataSetCache().retrieve( dsd, cacheTag ) ); + currentRequest= null; + + } else { + + progressMonitor = getMonitor( "dsd.requestDataSet "+dsd+":"+loadRange+" @ "+ + ( resolution==null ? "intrinsic" : ""+DatumUtil.asOrderOneUnits(resolution) ) ); + + parent.repaint( 0, 0, parent.getWidth(), parent.getHeight() ); + + //if ( renderer.isOverloading() ) loadRange= loadRange.rescale(-1,2); + logger.fine("request data from dsd: "+loadRange+" @ "+resolution); + + currentRequest= new Request( progressMonitor, xAxis.getMemento(), yAxis.getMemento() ); + + dsd.requestDataSet( loadRange.min(), loadRange.max(), resolution, progressMonitor, parent.getCanvas() ); + // the request will come back with a DataSetUpdated event + } + } + } + + /* + * If an exception is handled by the Renderer putting the exception in place of the data, + * then return true here. If the exception is more exceptional and we really need to get + * user's attention, return false. + */ + private boolean rendererHandlesException( Exception e ) { + boolean result= + e instanceof InterruptedIOException || + e instanceof NoDataInIntervalException || + e instanceof StreamException || + e instanceof CancelledOperationException ; + if ( result==false ) { + result= e.getCause() instanceof InterruptedIOException; + } + return result; + } + + public void dataSetUpdated( DataSetUpdateEvent e ) { + + synchronized ( lockObject ) { + if ( renderer.getDataLoader()!=this ) return; // see bug 233 + + logger.fine("got dataset update:"+e); + // TODO make sure Exception is cleared--what if data set is non-null but Exception is as well? + if ( e.getException()!=null && e.getDataSet()!=null ) { + throw new IllegalStateException("both exception and data set"); + } else if (e.getException() != null) { + logger.fine("got dataset update exception: "+e.getException()); + Exception exception = e.getException(); + if ( !rendererHandlesException(exception) ) { + DasExceptionHandler.handle(exception); + } + + ProgressMonitor mon= e.getMonitor(); + if ( currentRequest!=null ) { + if ( mon==null || mon==currentRequest.monitor ) { + renderer.setException( exception ); + renderer.setDataSet(null); + logger.fine("current request completed w/exception: " + currentRequest ); + currentRequest=null; + } else { + logger.fine("got exception but not for currentRequest " ); + } + } else { + logger.fine("got exception but currentRequest " ); + } + + + if ( !rendererHandlesException(exception) ) { + DasExceptionHandler.handle(exception); + } + + } else if ( e.getDataSet()==null ) { + // this indicates that the DataSetDescriptor has changed, and that the + // renderer needs to reread the data. Cause this by invalidating the + // component. + logger.fine("got dataset update notification (no dataset)."); + loadDataSet( renderer.getParent().getXAxis(), renderer.getParent().getYAxis() ); + return; + } else { + if ( currentRequest==null ) { + logger.fine( "ignore update w/dataset, currentRequest=null" ); + // note this is hiding a bug. Why did the dataset continue to load after we + // cancelled it? --jbf + } else { + DataSet ds= e.getDataSet(); + ProgressMonitor mon= e.getMonitor(); + if ( mon==null || currentRequest.monitor==mon ) { + logger.fine("got dataset update w/dataset: "+ds); + if ( ds!=null ) { + if ( ds.getXLength()>0 ) { + logger.fine(" ds range: "+DataSetUtil.xRange(ds) ); + } else { + logger.fine(" ds range: (empty)" ); + } + } + renderer.setDataSet( ds ); + + logger.fine("current request completed w/dataset: " + currentRequest.xmem ); + currentRequest=null; + } else { + logger.fine("got dataset update w/dataset but not my monitor: "+ds); + } + } + } + } + } + + + public void setDataSetDescriptor( DataSetDescriptor dsd ) { + logger.fine("set dsd: "+dsd); + if ( this.dsd!=null ) this.dsd.removeDataSetUpdateListener(this); + this.dsd = dsd; + if ( dsd!=null ) dsd.addDataSetUpdateListener(this); + update(); + } + + public DataSetDescriptor getDataSetDescriptor() { + return this.dsd; + } + + public void setReloadDataSet(boolean reloadDataSet) { + super.setReloadDataSet(reloadDataSet); + this.dsd.reset(); + } + + //TODO: this shadows the same property of the super class. This should be cleaned up. + private boolean fullResolution = false; + public boolean isFullResolution() { + return fullResolution; + } + + public void setFullResolution(boolean b) { + if (fullResolution == b) return; + fullResolution = b; + } + + public Request getCurrentRequest() { + return this.currentRequest; + } + + public Request[] getUnsolicitedRequests() { + return (Request[])this.unsolicitedRequests.toArray( new Request[unsolicitedRequests.size()] ); + } + + public Request getUnsolicitedRequests( int i ) { + return (Request)unsolicitedRequests.get(i); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/ZDeformRenderer.java b/dasCore/src/main/java/org/das2/graph/ZDeformRenderer.java new file mode 100644 index 000000000..1e680df32 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/ZDeformRenderer.java @@ -0,0 +1,73 @@ +/* + * ZDeformRenderer.java + * + * Created on November 14, 2003, 8:18 PM + */ + +package org.das2.graph; + +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.TableUtil; +import org.das2.datum.Datum; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.*; +import java.awt.geom.*; + +/** + * + * @author Owner + */ +public class ZDeformRenderer extends Renderer { + + int dx= 20; + int dy= 0; // deform direction + + /** Creates a new instance of ZDeformRenderer */ + public ZDeformRenderer( DataSetDescriptor dsd ) { + super(dsd); + } + + protected void installRenderer() { + } + + public void render(java.awt.Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + Graphics2D g= (Graphics2D) g1; + TableDataSet tds= (TableDataSet) getDataSet(); + double zmax= TableUtil.tableMax(tds,tds.getZUnits()); + for ( int itable=0; itable(-1000) && ix<1000 ) { + double z0= tds.getDouble(i,0,tds.getZUnits()); + Line2D.Double line= new Line2D.Double(); + for ( int j=1; j-1e30 && z0>-1e30 ) { + line.setLine(ix+z0/zmax*dx, iys[j-1]+z0/zmax*dy, ix+z1/zmax*dx, iys[j]+z0/zmax*dy ); + g.draw(line); + } + z0= z1; + } + } + } + } + } + + protected void uninstallRenderer() { + } + + protected org.w3c.dom.Element getDOMElement(org.w3c.dom.Document document) { + return null; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvas.java b/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvas.java new file mode 100644 index 000000000..937c3fec5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvas.java @@ -0,0 +1,134 @@ +/* File: TransferableCanvas.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.dnd; + +import org.das2.graph.DasCanvas; +//import org.apache.xml.serialize.Method; +//import org.apache.xml.serialize.OutputFormat; +//import org.apache.xml.serialize.XMLSerializer; +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.io.StringWriter; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; + +/** + * + * @author eew + */ +public class TransferableCanvas implements Transferable { + + public final static DataFlavor CANVAS_FLAVOR; + static { + try { + String typeStr = DataFlavor.javaJVMLocalObjectMimeType + + ";class=org.das2.graph.DasCanvas"; + CANVAS_FLAVOR = new DataFlavor(typeStr,null,DasCanvas.class.getClassLoader()); + } + catch (ClassNotFoundException cnfe) { + throw new RuntimeException(cnfe); + } + } + + private DasCanvas canvas; + + /** Creates a new instance of TransferableCanvas */ + public TransferableCanvas(DasCanvas canvas) { + this.canvas = canvas; + } + + /** Returns an object which represents the data to be transferred. The class + * of the object returned is defined by the representation class of the flavor. + * + * @param flavor the requested flavor for the data + * @see DataFlavor#getRepresentationClass + * @exception IOException if the data is no longer available + * in the requested flavor. + * @exception UnsupportedFlavorException if the requested data flavor is + * not supported. + */ + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (flavor.equals(CANVAS_FLAVOR)) { + return canvas; + } + else if (flavor.equals(DataFlavor.stringFlavor)) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + document.appendChild(canvas.getDOMElement(document)); + StringWriter writer = new StringWriter(); + + DOMImplementationLS ls = (DOMImplementationLS) + document.getImplementation().getFeature("LS", "3.0"); + LSOutput output = ls.createLSOutput(); + output.setEncoding("UTF-8"); + output.setCharacterStream(writer); + LSSerializer serializer = ls.createLSSerializer(); + serializer.write(document, output); + + /* + OutputFormat format = new OutputFormat(Method.XML, "UTF-8", true); + format.setOmitXMLDeclaration(true); + format.setOmitDocumentType(true); + XMLSerializer serializer = new XMLSerializer(writer, format); + serializer.serialize(document); + */ + + return writer.toString(); + } + catch (ParserConfigurationException pce) { + throw new RuntimeException(pce); + } + } + throw new UnsupportedFlavorException(flavor); } + + /** Returns an array of DataFlavor objects indicating the flavors the data + * can be provided in. The array should be ordered according to preference + * for providing the data (from most richly descriptive to least descriptive). + * @return an array of data flavors in which this data can be transferred + */ + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] {CANVAS_FLAVOR, DataFlavor.stringFlavor}; + } + + /** Returns whether or not the specified data flavor is supported for + * this object. + * @param flavor the requested flavor for the data + * @return boolean indicating whether or not the data flavor is supported + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(CANVAS_FLAVOR) || flavor.equals(DataFlavor.stringFlavor); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvasComponent.java b/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvasComponent.java new file mode 100644 index 000000000..9a4b724a9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/dnd/TransferableCanvasComponent.java @@ -0,0 +1,119 @@ +/* File: TransferableCanvasComponent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.dnd; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvasComponent; +import org.das2.graph.DasColorBar; +import org.das2.graph.DasPlot; + +/** + * + * @author eew + */ +public class TransferableCanvasComponent implements Transferable { + + public static final DataFlavor CANVAS_COMPONENT_FLAVOR = localJVMFlavor(org.das2.graph.DasCanvasComponent.class); + public static final DataFlavor AXIS_FLAVOR = localJVMFlavor(org.das2.graph.DasAxis.class); + public static final DataFlavor PLOT_FLAVOR = localJVMFlavor(org.das2.graph.DasPlot.class); + public static final DataFlavor COLORBAR_FLAVOR = localJVMFlavor(org.das2.graph.DasColorBar.class); + + private List flavorList; + private DasCanvasComponent component; + + private static DataFlavor localJVMFlavor(Class cl) { + try { + String className = cl.getName(); + String x = DataFlavor.javaJVMLocalObjectMimeType; + return new DataFlavor(x + ";class=" + className,null,cl.getClassLoader()); + } + catch (ClassNotFoundException cnfe) { + throw new RuntimeException(cnfe); + } + } + + public TransferableCanvasComponent(DasAxis axis) { + flavorList = Arrays.asList(new DataFlavor[]{AXIS_FLAVOR, CANVAS_COMPONENT_FLAVOR, DataFlavor.stringFlavor}); + component = axis; + } + + public TransferableCanvasComponent(DasPlot plot) { + flavorList = Arrays.asList(new DataFlavor[]{PLOT_FLAVOR, CANVAS_COMPONENT_FLAVOR, DataFlavor.stringFlavor}); + component = plot; + } + + public TransferableCanvasComponent(DasColorBar cb) { + flavorList = Arrays.asList(new DataFlavor[]{PLOT_FLAVOR, CANVAS_COMPONENT_FLAVOR, DataFlavor.stringFlavor}); + component = cb; + } + + /** Returns an object which represents the data to be transferred. The class + * of the object returned is defined by the representation class of the flavor. + * + * @param flavor the requested flavor for the data + * @see DataFlavor#getRepresentationClass + * @exception IOException if the data is no longer available + * in the requested flavor. + * @exception UnsupportedFlavorException if the requested data flavor is + * not supported. + */ + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (isDataFlavorSupported(flavor)) { + if (flavor.equals(DataFlavor.stringFlavor)) { + throw new UnsupportedFlavorException(flavor); + } + else { + return component; + } + } + else { + throw new UnsupportedFlavorException(flavor); + } + } + + /** Returns an array of DataFlavor objects indicating the flavors the data + * can be provided in. The array should be ordered according to preference + * for providing the data (from most richly descriptive to least descriptive). + * @return an array of data flavors in which this data can be transferred + */ + public DataFlavor[] getTransferDataFlavors() { + return (DataFlavor[])flavorList.toArray(new DataFlavor[flavorList.size()]); + } + + /** Returns whether or not the specified data flavor is supported for + * this object. + * @param flavor the requested flavor for the data + * @return boolean indicating whether or not the data flavor is supported + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavorList.contains(flavor); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/dnd/TransferableRenderer.java b/dasCore/src/main/java/org/das2/graph/dnd/TransferableRenderer.java new file mode 100644 index 000000000..64f0eb12f --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/dnd/TransferableRenderer.java @@ -0,0 +1,97 @@ +/* File: TransferableRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.dnd; + +import org.das2.graph.Renderer; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + +/** + * + * @author eew + */ +public class TransferableRenderer implements Transferable { + + public static final DataFlavor RENDERER_FLAVOR = localJVMFlavor(org.das2.graph.Renderer.class); + + private static DataFlavor localJVMFlavor(Class cl) { + try { + String className = cl.getName(); + String x = DataFlavor.javaJVMLocalObjectMimeType; + return new DataFlavor(x + ";class=" + className,null,cl.getClassLoader()); + } + catch (ClassNotFoundException cnfe) { + throw new RuntimeException(cnfe); + } + } + + /** The Renderer that this tranferable encapsulates. */ + private Renderer renderer; + + /** Creates a new instance of TransferableRenderer */ + public TransferableRenderer(Renderer renderer) { + this.renderer = renderer; + } + + /** Returns an object which represents the data to be transferred. The class + * of the object returned is defined by the representation class of the flavor. + * + * @param flavor the requested flavor for the data + * @see DataFlavor#getRepresentationClass + * @exception IOException if the data is no longer available + * in the requested flavor. + * @exception UnsupportedFlavorException if the requested data flavor is + * not supported. + */ + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + if (isDataFlavorSupported(flavor)) { + if (flavor.equals(RENDERER_FLAVOR)) { + return renderer; + } + } + throw new UnsupportedFlavorException(flavor); + } + + /** Returns an array of DataFlavor objects indicating the flavors the data + * can be provided in. The array should be ordered according to preference + * for providing the data (from most richly descriptive to least descriptive). + * @return an array of data flavors in which this data can be transferred + */ + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { RENDERER_FLAVOR }; + } + + /** Returns whether or not the specified data flavor is supported for + * this object. + * @param flavor the requested flavor for the data + * @return boolean indicating whether or not the data flavor is supported + */ + public boolean isDataFlavorSupported(DataFlavor flavor) { + return flavor.equals(RENDERER_FLAVOR); + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/dnd/package.html b/dasCore/src/main/java/org/das2/graph/dnd/package.html new file mode 100644 index 000000000..33418b1d9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/dnd/package.html @@ -0,0 +1,4 @@ + + Contains Transferable classes for implementing the drop and drop in the + IDE. This is under review and may be removed in a future release. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/graph/event/DasAxisEvent.java b/dasCore/src/main/java/org/das2/graph/event/DasAxisEvent.java new file mode 100644 index 000000000..f1bd15325 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasAxisEvent.java @@ -0,0 +1,56 @@ +/* File: DasAxisEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +import java.util.EventObject; + +/** + * + * @author eew + */ +public class DasAxisEvent extends EventObject +{ + + private double minimum; + private double maximum; + + /** Creates a new instance of DasAxisEvent */ + public DasAxisEvent(Object source, double minimum, double maximum) + { + super(source); + this.minimum = minimum; + this.maximum = maximum; + } + + public double getDataMinimum() + { + return minimum; + } + + public double getDataMaximum() + { + return maximum; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/event/DasAxisListener.java b/dasCore/src/main/java/org/das2/graph/event/DasAxisListener.java new file mode 100644 index 000000000..28b7ebf5f --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasAxisListener.java @@ -0,0 +1,35 @@ +/* File: DasAxisListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +import java.util.EventListener; + +/** + * + * @author eew + */ +public interface DasAxisListener extends EventListener +{ + void dataRangeChanged(DasAxisEvent e); +} diff --git a/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionEvent.java b/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionEvent.java new file mode 100644 index 000000000..b2ab5cb96 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionEvent.java @@ -0,0 +1,53 @@ +/* File: DasDevicePositionEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +import java.util.EventObject; + +/** + * + * @author eew + */ +public class DasDevicePositionEvent extends EventObject +{ + + private double min, max; + + /** Creates a new instance of DasDevicePositionEvent */ + public DasDevicePositionEvent(Object source, double min, double max) + { + super(source); + } + + public double getMinimum() + { + return min; + } + + public double getMaximum() + { + return max; + } + +} diff --git a/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionListener.java b/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionListener.java new file mode 100644 index 000000000..b5e7c7593 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasDevicePositionListener.java @@ -0,0 +1,34 @@ +/* File: DasDevicePositionListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +/** + * + * @author eew + */ +public interface DasDevicePositionListener extends java.util.EventListener +{ + void devicePositionChanged(DasDevicePositionEvent e); + void deviceRangeChanged(DasDevicePositionEvent e); +} diff --git a/dasCore/src/main/java/org/das2/graph/event/DasUpdateEvent.java b/dasCore/src/main/java/org/das2/graph/event/DasUpdateEvent.java new file mode 100644 index 000000000..f4a7a4acd --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasUpdateEvent.java @@ -0,0 +1,37 @@ +/* File: DasUpdateEvent.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +/** + * + * @author eew + */ +public class DasUpdateEvent extends java.util.EventObject +{ + /** Creates a new instance of DasUpdateEvent */ + public DasUpdateEvent(Object source) + { + super(source); + } +} diff --git a/dasCore/src/main/java/org/das2/graph/event/DasUpdateListener.java b/dasCore/src/main/java/org/das2/graph/event/DasUpdateListener.java new file mode 100644 index 000000000..2815ece08 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/DasUpdateListener.java @@ -0,0 +1,33 @@ +/* File: DasUpdateListener.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.graph.event; + +/** + * + * @author eew + */ +public interface DasUpdateListener extends java.util.EventListener +{ + void update(DasUpdateEvent e); +} diff --git a/dasCore/src/main/java/org/das2/graph/event/package.html b/dasCore/src/main/java/org/das2/graph/event/package.html new file mode 100644 index 000000000..b14df9541 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/event/package.html @@ -0,0 +1,4 @@ + +Contains classes for implementing internal update event model of +CanvasComponents. + diff --git a/dasCore/src/main/java/org/das2/graph/package.html b/dasCore/src/main/java/org/das2/graph/package.html new file mode 100644 index 000000000..82f92c257 --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/package.html @@ -0,0 +1,18 @@ + +

    Mostly contains DasCanvasComponents, which are components that live within +the DasCanvas, and Renderers which draw on DasPlots. DasCanvas, a subclass of +JComponent, is the container for drawing graphics in das2. DasCanvasComponents +are added to the DasCanvas to set up the graphic. DasCanvasComponents include +objects like DasAxis, DasColorBar, and DasPlot, but are simply objects that +occupy a region of the canvas specified with a DasRow (vertical position) and +DasColumn (horizontal position). Note too the CanvasComponents can be +heirarchitical, for example a DasPlot contains two DasAxes. Each CanvasComponent +has a DasMouseInputAdapter that handles mouse and keyboard events for the +component. +

    +

    To add new 2-D plotting capabilities, Renderers are introduced to paint +the space defined by an x and y axis. Examples of Renderers are SymbolLineRenderer +(line plots) and SpectrogramRenderer (spectrograms). +

    + + diff --git a/dasCore/src/main/java/org/das2/graph/scratchPad.txt b/dasCore/src/main/java/org/das2/graph/scratchPad.txt new file mode 100644 index 000000000..4e719763e --- /dev/null +++ b/dasCore/src/main/java/org/das2/graph/scratchPad.txt @@ -0,0 +1,6 @@ +2006-10-4 jbf + For purposes of consistency, we define an "em" as being the contextual font's size, as in + Graphics2D g; + int em = g.getFont().getSize(); + + diff --git a/dasCore/src/main/java/org/das2/math/Contour.java b/dasCore/src/main/java/org/das2/math/Contour.java new file mode 100644 index 000000000..6c93289c9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/Contour.java @@ -0,0 +1,519 @@ +/* + * Contour.java + * See http://www.mactech.com/articles/mactech/Vol.13/13.09/ContourPlottinginJava/index.html + * Created on May 20, 2004, 7:49 PM + */ + +package org.das2.math; + +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import java.awt.*; +import java.io.*; + +/** + * Contouring based on code published in Javatech, Volume 13 Issue 9, "Contour Plotting In Java" + * by David Rand. This code is based on Fortran code implementing 1978 article by W. V. Snyder. + * W. V. Snyder, "Algorithm 531, Contour plotting [J6]," ACM Trans. Math. Softw. 4, 3 (Sept. 1978), 290-294. + * @author Owner + */ +public class Contour { + + public static final String PLANE_X="x"; + public static final String PLANE_Y="y"; + + final static public class ContourPlot { + + TableDataSet zz; + Units zunits; + long idx; /* monotonically increasing X Value */ + + public ContourPlot( TableDataSet tds, DatumVector contourValues ) { + super(); + this.zz= tds; + + xSteps = tds.getXLength(); + ySteps = tds.getYLength(0); + + zunits= tds.getZUnits(); + + ncv= contourValues.getLength(); + cv= new float[ncv]; + for ( int i=0; i0 ) { + double y1= zz.getYTagDouble(0, indx+1, zz.getYUnits() ); + return y0 + fp * ( y1 - y0 ); + } else { + return y0; + } + } + + final double getXValue( double findex ) { + findex--; /* one is first index */ + int indx= (int)findex; + if ( indx==zz.getXLength() ) indx--; + double fp= findex - indx; + double x0= zz.getXTagDouble( indx, zz.getXUnits() ); + if ( fp>0 ) { + double x1= zz.getXTagDouble( indx+1, zz.getXUnits() ); + return x0 + fp * ( x1 - x0 ); + } else { + return x0; + } + } + + //------------------------------------------------------- + // "DrawKernel" is the guts of drawing and is called + // directly or indirectly by "ContourPlotKernel" in order + // to draw a segment of a contour or to set the pen + // position "prevXY". Its action depends on "iflag": + // + // iflag == 1 means Continue a contour + // iflag == 2 means Start a contour at a boundary + // iflag == 3 means Start a contour not at a boundary + // iflag == 4 means Finish contour at a boundary + // iflag == 5 means Finish closed contour not at boundary + // iflag == 6 means Set pen position + // + // If the constant "SHOW_NUMBERS" is true then when + // completing a contour ("iflag" == 4 or 5) the contour + // index is drawn adjacent to where the contour ends. + //------------------------------------------------------- + void DrawKernel(VectorDataSetBuilder dsbuilder) { + int prevU,prevV,u,v; + + if ((iflag == 1) || (iflag == 4) || (iflag == 5)) { + + } else { + idx++ ; + } + + + dsbuilder.insertY(idx,cval); + + dsbuilder.insertY(idx,getXValue(xy[0]),PLANE_X); + dsbuilder.insertY(idx,getYValue(xy[1]),PLANE_Y); + + idx++; + + } + + //------------------------------------------------------- + // "DetectBoundary" + //------------------------------------------------------- + final void DetectBoundary() { + ix = 1; + if (ij[1-elle] != 1) { + ii = ij[0] - i1[1-elle]; + jj = ij[1] - i1[elle]; + if ( !zunits.isFill(zz.getDouble(ii-1,jj-1,zunits)) ) { + ii = ij[0] + i2[elle]; + jj = ij[1] + i2[1-elle]; + if ( !zunits.isFill(zz.getDouble(ii-1,jj-1,zunits)) ) ix = 0; + } + if (ij[1-elle] >= l1[1-elle]) { + ix = ix + 2; + return; + } + } + ii = ij[0] + i1[1-elle]; + jj = ij[1] + i1[elle]; + if ( zunits.isFill( zz.getDouble(ii-1,jj-1,zunits) ) ) { + ix = ix + 2; + return; + } + if ( zunits.isFill( zz.getDouble(ij[0],ij[1],zunits) ) ) ix = ix + 2; + } + + //------------------------------------------------------- + // "Routine_label_020" corresponds to a block of code + // starting at label 20 in Synder's subroutine "GCONTR". + //------------------------------------------------------- + boolean Routine_label_020() { + l2[0] = ij[0]; + l2[1] = ij[1]; + l2[2] = -ij[0]; + l2[3] = -ij[1]; + idir = 0; + nxidir = 1; + k = 1; + ij[0] = Math.abs(ij[0]); + ij[1] = Math.abs(ij[1]); + if ( zunits.isFill( zz.getDouble(ij[0]-1,ij[1]-1,zunits) ) ) { + elle = idir % 2; + ij[elle] = sign(ij[elle],l1[k-1]); + return true; + } + elle = 0; + return false; + } + + //------------------------------------------------------- + // "Routine_label_050" corresponds to a block of code + // starting at label 50 in Synder's subroutine "GCONTR". + //------------------------------------------------------- + boolean Routine_label_050() { + while (true) { + if (ij[elle] >= l1[elle]) { + if (++elle <= 1) continue; + elle = idir % 2; + ij[elle] = sign(ij[elle],l1[k-1]); + if (Routine_label_150()) return true; + continue; + } + ii = ij[0] + i1[elle]; + jj = ij[1] + i1[1-elle]; + if ( zunits.isFill( zz.getDouble(ii-1,jj-1,zunits) ) ) { + if (++elle <= 1) continue; + elle = idir % 2; + ij[elle] = sign(ij[elle],l1[k-1]); + if (Routine_label_150()) return true; + continue; + } + break; + } + jump = false; + return false; + } + + //------------------------------------------------------- + // "Routine_label_150" corresponds to a block of code + // starting at label 150 in Synder's subroutine "GCONTR". + //------------------------------------------------------- + boolean Routine_label_150() { + while (true) { + //------------------------------------------------ + // Lines from z[ij[0]-1][ij[1]-1] + // to z[ij[0] ][ij[1]-1] + // and z[ij[0]-1][ij[1]] + // are not satisfactory. Continue the spiral. + //------------------------------------------------ + if (ij[elle] < l1[k-1]) { + ij[elle]++; + if (ij[elle] > l2[k-1]) { + l2[k-1] = ij[elle]; + idir = nxidir; + nxidir = idir + 1; + k = nxidir; + if (nxidir > 3) nxidir = 0; + } + ij[0] = Math.abs(ij[0]); + ij[1] = Math.abs(ij[1]); + if ( zunits.isFill( zz.getDouble(ij[0]-1,ij[1]-1,zunits) ) ) { + elle = idir % 2; + ij[elle] = sign(ij[elle],l1[k-1]); + continue; + } + elle = 0; + return false; + } + if (idir != nxidir) { + nxidir++; + ij[elle] = l1[k-1]; + k = nxidir; + elle = 1 - elle; + ij[elle] = l2[k-1]; + if (nxidir > 3) nxidir = 0; + continue; + } + + if (ibkey != 0) return true; + ibkey = 1; + ij[0] = icur; + ij[1] = jcur; + if (Routine_label_020()) continue; + return false; + } + } + + //------------------------------------------------------- + // "Routine_label_200" corresponds to a block of code + // starting at label 200 in Synder's subroutine "GCONTR". + // It has return values 0, 1 or 2. + //------------------------------------------------------- + short Routine_label_200(VectorDataSetBuilder dsbuilder, boolean workSpace[]) { + while (true) { + xy[elle] = 1.0*ij[elle] + intersect[iedge-1]; + xy[1-elle] = 1.0*ij[1-elle]; + workSpace[2*(xSteps*(ySteps*cntrIndex+ij[1]-1) + +ij[0]-1) + elle] = true; + DrawKernel(dsbuilder); + if (iflag >= 4) { + icur = ij[0]; + jcur = ij[1]; + return 1; + } + ContinueContour(); + if (!workSpace[2*(xSteps*(ySteps*cntrIndex + +ij[1]-1)+ij[0]-1)+elle]) return 2; + iflag = 5; // 5. Finish a closed contour + iedge = ks + 2; + if (iedge > 4) iedge = iedge - 4; + intersect[iedge-1] = intersect[ks-1]; + } + } + + //------------------------------------------------------- + // "CrossedByContour" is true iff the current segment in + // the grid is crossed by one of the contour values and + // has not already been processed for that value. + //------------------------------------------------------- + boolean CrossedByContour(boolean workSpace[]) { + ii = ij[0] + i1[elle]; + jj = ij[1] + i1[1-elle]; + z1 = zz.getDouble(ij[0]-1,ij[1]-1,zunits); + z2 = zz.getDouble(ii-1,jj-1,zunits); + for (cntrIndex = 0; cntrIndex < ncv; cntrIndex++) { + int i = 2*(xSteps*(ySteps*cntrIndex+ij[1]-1) + ij[0]-1) + elle; + + if (!workSpace[i]) { + float x = cv[cntrIndex]; + if ((x>Math.min(z1,z2)) && (x<=Math.max(z1,z2))) { + workSpace[i] = true; + return true; + } + } + } + return false; + } + + //------------------------------------------------------- + // "ContinueContour" continues tracing a contour. Edges + // are numbered clockwise, the bottom edge being # 1. + //------------------------------------------------------- + void ContinueContour() { + short local_k; + + ni = 1; + if (iedge >= 3) { + ij[0] = ij[0] - i3[iedge-1]; + ij[1] = ij[1] - i3[iedge+1]; + } + for (local_k = 1; local_k < 5; local_k++) + if (local_k != iedge) { + ii = ij[0] + i3[local_k-1]; + jj = ij[1] + i3[local_k]; + z1 = zz.getDouble(ii-1,jj-1,zunits); + ii = ij[0] + i3[local_k]; + jj = ij[1] + i3[local_k+1]; + z2 = zz.getDouble(ii-1,jj-1,zunits); + if ((cval > Math.min(z1,z2) && (cval <= Math.max(z1,z2)))) { + if ((local_k == 1) || (local_k == 4)) { + double zz = z2; + + z2 = z1; + z1 = zz; + } + intersect[local_k-1] = (cval - z1)/(z2 - z1); + ni++; + ks = local_k; + } + } + if (ni != 2) { + //------------------------------------------------- + // The contour crosses all 4 edges of cell being + // examined. Choose lines top-to-left & bottom-to- + // right if interpolation point on top edge is + // less than interpolation point on bottom edge. + // Otherwise, choose the other pair. This method + // produces the same results if axes are reversed. + // The contour may close at any edge, but must not + // cross itself inside any cell. + //------------------------------------------------- + ks = 5 - iedge; + if (intersect[2] >= intersect[0]) { + ks = 3 - iedge; + if (ks <= 0) ks = ks + 4; + } + } + //---------------------------------------------------- + // Determine whether the contour will close or run + // into a boundary at edge ks of the current cell. + //---------------------------------------------------- + elle = ks - 1; + iflag = 1; // 1. Continue a contour + jump = true; + if (ks >= 3) { + ij[0] = ij[0] + i3[ks-1]; + ij[1] = ij[1] + i3[ks+1]; + elle = ks - 3; + } + } + + //------------------------------------------------------- + // "ContourPlotKernel" is the guts of this class and + // corresponds to Synder's subroutine "GCONTR". + //------------------------------------------------------- + void ContourPlotKernel(VectorDataSetBuilder dsbuilder, boolean workSpace[]) { + short val_label_200; + + l1[0] = xSteps; l1[1] = ySteps; + l1[2] = -1;l1[3] = -1; + i1[0] = 1; i1[1] = 0; + i2[0] = 1; i2[1] = -1; + i3[0] = 1; i3[1] = 0; i3[2] = 0; + i3[3] = 1; i3[4] = 1; i3[5] = 0; + prevXY[0] = 0.0; prevXY[1] = 0.0; + xy[0] = 1.0; xy[1] = 1.0; + cntrIndex = 0; + prevIndex = -1; + iflag = 6; + DrawKernel(dsbuilder); + icur = Math.max(1, Math.min((int)Math.floor(xy[0]), xSteps)); + jcur = Math.max(1, Math.min((int)Math.floor(xy[1]), ySteps)); + ibkey = 0; + ij[0] = icur; + ij[1] = jcur; + if (Routine_label_020() && + Routine_label_150()) return; + if (Routine_label_050()) return; + while (true) { + DetectBoundary(); + if (jump) { + if (ix != 0) iflag = 4; // Finish contour at boundary + iedge = ks + 2; + if (iedge > 4) iedge = iedge - 4; + intersect[iedge-1] = intersect[ks-1]; + val_label_200 = Routine_label_200(dsbuilder,workSpace); + if (val_label_200 == 1) { + if (Routine_label_020() && Routine_label_150()) return; + if (Routine_label_050()) return; + continue; + } + if (val_label_200 == 2) continue; + return; + } + if ((ix != 3) && (ix+ibkey != 0) && CrossedByContour(workSpace)) { + // + // An acceptable line segment has been found. + // Follow contour until it hits a + // boundary or closes. + // + iedge = elle + 1; + cval = cv[cntrIndex]; + if (ix != 1) iedge = iedge + 2; + iflag = 2 + ibkey; + intersect[iedge-1] = (cval - z1) / (z2 - z1); + val_label_200 = Routine_label_200(dsbuilder,workSpace); + if (val_label_200 == 1) { + if (Routine_label_020() && Routine_label_150()) return; + if (Routine_label_050()) return; + continue; + } + if (val_label_200 == 2) continue; + return; + } + if (++elle > 1) { + elle = idir % 2; + ij[elle] = sign(ij[elle],l1[k-1]); + if (Routine_label_150()) return; + } + if (Routine_label_050()) return; + } + } + } + + /** + * returns a rank 1 dataset, a vector dataset, listing the points + * of the contour paths. The data set will have three planes: + * X, Y and the default plane is the Z. + */ + public static VectorDataSet contour( TableDataSet tds, DatumVector levels ) { + if ( tds.tableCount()>1 ) { + throw new IllegalArgumentException("TableDataSet can only have one table"); + } + Contour.ContourPlot cp= new ContourPlot( tds, levels ); + return cp.performContour(); + } + + public static VectorDataSet contour( TableDataSet tds, Datum level ) { + if ( tds.tableCount()>1 ) { + throw new IllegalArgumentException("TableDataSet can only have one table"); + } + Units units= level.getUnits(); + double value= level.doubleValue(units); + Contour.ContourPlot cp= new ContourPlot( tds, DatumVector.newDatumVector( new double[] { value }, units ) ); + return cp.performContour(); + } + + +} diff --git a/dasCore/src/main/java/org/das2/math/Interpolate.java b/dasCore/src/main/java/org/das2/math/Interpolate.java new file mode 100644 index 000000000..a6adf6ab9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/Interpolate.java @@ -0,0 +1,169 @@ +/* + * Interpolate.java + * + * Created on March 15, 2005, 9:00 AM + */ + +package org.das2.math; + +/** + * + * @author Jeremy + */ +public class Interpolate { + + public interface DDoubleArray { + double get( int i, int j ); + void put( int i, int j, double value ); + int rows(); + int columns(); + } + + public interface FDoubleArray { + float get( int i, int j ); + void put( int i, int j, float value ); + int rows(); + int columns(); + } + + public static DDoubleArray newDDoubleArray( final double[][] array ) { + return new DDoubleArray() { + public double get( int i, int j ) { return array[i][j]; } + public void put( int i, int j, double value ) { array[i][j]= value; }; + public int rows() { return array.length; } + public int columns() { return array[0].length; } + }; + } + + public static DDoubleArray newDDoubleArray( int columns, int rows ) { + return new DDoubleArrayImpl( columns, rows ); + } + + private static final class DDoubleArrayImpl implements DDoubleArray { + // return DDoubleArray backed by single array, storing the double array column major. + double[] back; + int rows; // index by i + int columns; + static final boolean boundsCheck= false; + DDoubleArrayImpl( int columns, int rows ) { + back= new double[ rows * columns ]; + this.columns= columns; + this.rows= rows; + } + public double get( int i, int j ) { + if ( boundsCheck ) { + if ( i>=rows ) throw new ArrayIndexOutOfBoundsException("index i="+i+" out of bounds"); + if ( i<0 ) throw new ArrayIndexOutOfBoundsException("index i="+i+" out of bounds"); + if ( j>=columns ) throw new ArrayIndexOutOfBoundsException("index j="+j+" out of bounds"); + } + return back[ j*rows + i ]; + } + public void put( int i, int j, double value ) { + if ( boundsCheck ) { + if ( i>=rows ) throw new ArrayIndexOutOfBoundsException("index i="+i+" out of bounds"); + if ( i<0 ) throw new ArrayIndexOutOfBoundsException("index i="+i+" out of bounds"); + if ( j>=columns ) throw new ArrayIndexOutOfBoundsException("index j="+j+" out of bounds"); + } + back[ j*rows + i ]= value; + }; + public int rows() { return rows; } + public int columns() { return columns; } + } + + public static DDoubleArray interpolate2( DDoubleArray source, float[] xFindex, float[] yFindex ) { + + DDoubleArray result= newDDoubleArray( xFindex.length, yFindex.length ); + double[] xfp0= new double[xFindex.length]; + double[] xfp1= new double[xFindex.length]; + int[] xip0= new int[xFindex.length]; + int[] xip1= new int[xFindex.length]; + for ( int i=0; i 80. + * + * The value of bound must be adjusted to the maximal value of L. + */ + private int PoissonInver(double L, Random random ) { + + final int bound = 130; // safety bound. Must be > L + 8*sqrt(L). + double r; // uniform random number + double f; // function value + int x; // return value + + if (L != p_L_last) { // set up + p_L_last = L; + p_f0 = Math.exp(-L); + } // f(0) = probability of x=0 + + while (true) { + r = random.nextDouble(); x = 0; f = p_f0; + do { // recursive calculation: f(x) = f(x-1) * L / x + r -= f; + if (r <= 0) return x; + x++; + f *= L; + r *= x;} // instead of f /= x + while (x <= bound); + } + } + + } + + static PoissonRatioUniforms poissonRatioUniforms= new PoissonRatioUniforms(); + + /** + * This subfunction generates a random variate with the poisson + * distribution using the ratio-of-uniforms rejection method (PRUAt). + * + * Execution time does not depend on L, except that it matters whether L + * is within the range where ln(n!) is tabulated. + * + * Reference: E. Stadlober: "The ratio of uniforms approach for generating + * discrete random variates". Journal of Computational and Applied Mathematics, + * vol. 31, no. 1, 1990, pp. 181-189. + */ + static class PoissonRatioUniforms { + final double SHAT1 = 2.943035529371538573; // 8/e + final double SHAT2 = 0.8989161620588987408; // 3-sqrt(12/e) + + double p_L_last = -1.0; // previous L cache tag + double p_a; // hat center + double p_h; // hat width + double p_g; // ln(L) + double p_q; // value at mode + int p_bound; // upper bound + int mode; // mode + + private int PoissonRatioUniforms(double L, Random random ) { + double u; // uniform random + double lf; // ln(f(x)) + double x; // real sample + int k; // integer sample + + if (p_L_last != L) { + p_L_last = L; // Set-up + p_a = L + 0.5; // hat center + mode = (int)L; // mode + p_g = Math.log(L); + p_q = mode * p_g - fac.lnFac(mode); // value at mode + p_h = Math.sqrt(SHAT1 * (L+0.5)) + SHAT2; // hat width + p_bound = (int)(p_a + 6.0 * p_h); + } // safety-bound + + while(true) { + u = random.nextDouble(); + if (u == 0) continue; // avoid division by 0 + x = p_a + p_h * (random.nextDouble() - 0.5) / u; + if (x < 0 || x >= p_bound) continue; // reject if outside valid range + k = (int)(x); + lf = k * p_g - fac.lnFac(k) - p_q; + if (lf >= u * (4.0 - u) - 3.0) break; // quick acceptance + if (u * (u - lf) > 1.0) continue; // quick rejection + if (2.0 * Math.log(u) <= lf) break;} // final acceptance + return(k); + } + } + + /** + * This function generates a random variate with the poisson distribution. + * + * Uses inversion by chop-down method for L < 17, and ratio-of-uniforms + * method for L >= 17. + * + * For L < 1.E-6 numerical inaccuracy is avoided by direct calculation. + * For L > 2E9 too big--throws IllegalArgumentException + */ + public static int poisson( double L,Random random ) { + + + //------------------------------------------------------------------ + // choose method + //------------------------------------------------------------------ + if (L < 17) { + if (L < 1.E-6) { + if (L == 0) return 0; + if (L < 0) throw new IllegalArgumentException("Parameter negative in poisson function"); + + //-------------------------------------------------------------- + // calculate probabilities + //-------------------------------------------------------------- + // For extremely small L we calculate the probabilities of x = 1 + // and x = 2 (ignoring higher x). The reason for using this + // method is to prevent numerical inaccuracies in other methods. + //-------------------------------------------------------------- + return PoissonLow(L,random); + } else { + + //-------------------------------------------------------------- + // inversion method + //-------------------------------------------------------------- + // The computation time for this method grows with L. + // Gives overflow for L > 80 + //-------------------------------------------------------------- + return poissonInver.PoissonInver(L,random); + } + } + + else { + if (L > 2.E9) throw new IllegalArgumentException("Parameter too big in poisson function"); + + //---------------------------------------------------------------- + // ratio-of-uniforms method + //---------------------------------------------------------------- + // The computation time for this method does not depend on L. + // Use where other methods would be slower. + //---------------------------------------------------------------- + return poissonRatioUniforms.PoissonRatioUniforms(L,random); + } + } + + /** + * This subfunction generates a random variate with the poisson + * distribution for extremely low values of L. + * + * The method is a simple calculation of the probabilities of x = 1 + * and x = 2. Higher values are ignored. + * + * The reason for using this method is to avoid the numerical inaccuracies + * in other methods. + */ + private static int PoissonLow( double L, Random random ) { + double d, r; + d = Math.sqrt(L); + if ( random.nextDouble() >= d ) return 0; + r = random.nextDouble() * d; + if (r > L * (1.-L)) return 0; + if (r > 0.5 * L*L * (1.-L)) return 1; + return 2;} + + + +} diff --git a/dasCore/src/main/java/org/das2/math/QuadFitUtil.java b/dasCore/src/main/java/org/das2/math/QuadFitUtil.java new file mode 100644 index 000000000..4a9c0686a --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/QuadFitUtil.java @@ -0,0 +1,107 @@ +/* + * QuadFitUtil.java + * + * Created on March 8, 2005, 5:24 PM + */ + +package org.das2.math; + +import org.das2.math.matrix.ArrayMatrix; +import org.das2.math.matrix.Matrix; +import org.das2.math.matrix.MatrixUtil; +import java.util.Arrays; + +/** + * + * @author eew + */ +public class QuadFitUtil { + + private QuadFitUtil() {} + + public static double[] quadfit(double[] x, double[] y, double[] w) { + return polyfitw(x, y, w, 2); + } + + public static double[] polyfitw(double[] x, double[] y, double[] w, int degree) { + int n = x.length; + int m = degree + 1; + + Matrix a = new ArrayMatrix(m, m); + double[] b = new double[m]; + double[] z = new double[n]; + Arrays.fill(z, 1.0); + + a.set(0, 0, total(w)); + b[0] = totalMult(w, y); + + for (int p = 1; p <= 2*degree; p++) { + for (int iz = 0; iz < z.length; iz++) { + z[iz] *= x[iz]; + } + if (p < m ) { + b[p] = totalMult(w, y, z); + } + double sum = totalMult(w, z); + int degreeLTp = Math.min(degree, p); + for (int j = Math.max(0, p - degree); j <= degreeLTp; j++) { + a.set(j, p - j, sum); + } + } + + a = MatrixUtil.inverse(a); + + double[] c = new double[m]; + + MatrixUtil.multiply(new ArrayMatrix(b, 1, m), a, new ArrayMatrix(c, 1, m)); + + return c; + } + + private static double total(double[] a) { + double total = 0.0; + for (int i = 0; i < a.length; i++) { + total += a[i]; + } + return total; + } + + private static double totalMult(double[] a, double[] b) { + double total = 0.0; + for (int i = 0; i < a.length; i++) { + total += (a[i] * b[i]); + } + return total; + } + + private static double totalMult(double[] a, double[] b, double[] c) { + double total = 0.0; + for (int i = 0; i < a.length; i++) { + total += (a[i] * b[i] * c[i]); + } + return total; + } + + /** + * The peak of the quadradic. + * y = c0 + c1*x + c2x^2 + * dy/dx = c1 + 2*c2*x + * for dy/dx == 0, x = -c1/(2*c2) + */ + public static double quadPeak(double[] c) { + if (c.length != 3) { + throw new IllegalArgumentException("c must have a length of 3"); + } + return -0.5 * c[1] / c[2]; + } + + /* + * The width only depends on the x^2 coefficient. + * dy is the delta y at which the half width is measured. + * w = -((-c2*dy)^(1/2)) / c2 + */ + public static double quadHalfWidth(double[] c, double dy) { + return Math.sqrt(-c[2]*dy) / -c[2]; + } + +} diff --git a/dasCore/src/main/java/org/das2/math/Triangulator.java b/dasCore/src/main/java/org/das2/math/Triangulator.java new file mode 100644 index 000000000..47f1424fb --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/Triangulator.java @@ -0,0 +1,1600 @@ +/* + * The Triangulator - a java applet which animates some brute force + * Delaunay triangulation algorithms of varying computational + * complexity. + * + * Author: Geoff Leach. gl@cs.rmit.edu.au + * Date: 29/3/96 + * + * License to copy and use this software is granted provided that + * appropriate credit is given to both RMIT and the author. + * + * The point of the applet is educational: + * + * (a) To illustrate the importance of computational complexity. + * The three Delaunay triangulation algorithms implemented have + * computational complexities of O(n^2), O(n^3) and O(n^4). The + * user can see for themselves the improvement in speed going + * from O(n^4) to O(n^2). + * + * (b) To illustrate what Delaunay triangulations are. The Delaunay + * triangulation, and the related Voronoi diagram, is a + * particularly useful data structure for a number of problems. + * Without going into the applications the applet attempts to + * show what Delaunay triangulation algorithms are. + * + * (c) To provide a useful context for me to initially learn Java. + * + * The code still needs polishing ... + * + */ + +package org.das2.math; + +import java.applet.*; +import java.awt.*; +import java.io.PrintStream; + +/* + * Sometimes we just have to die. + */ +class Panic { + static public void panic(String m) { + System.err.println(m); + System.exit(1); + } +} + +/* + * Wrapper class for basic int type for pass by reference. + */ +class Int { + int i; + + public void Int() { + i = 0; + } + + public void Int(int i) { + this.i = i; + } + + public void setValue(int i) { + this.i = i; + } + + public int getValue() { + return i; + } +} + +/* + * Point class. RealPoint to avoid clash with java.awt.Point. + */ +class RealPoint { + float x, y; + + RealPoint() { x = y = 0.0f; } + RealPoint(float x, float y) { this.x = x; this.y = y; } + RealPoint(RealPoint p) { x = p.x; y = p.y; } + public float x() { return this.x; } + public float y() { return this.y; } + public void set(float x, float y) { this.x = x; this.y = y; } + + public float distance(RealPoint p) { + float dx, dy; + + dx = p.x - x; + dy = p.y - y; + return (float)Math.sqrt((double)(dx * dx + dy * dy)); + } + + public float distanceSq(RealPoint p) { + float dx, dy; + + dx = p.x - x; + dy = p.y - y; + return (float)(dx * dx + dy * dy); + } +} + +/* + * Edge class. Edges have two vertices, s and t, and two faces, + * l (left) and r (right). The triangulation representation and + * the Delaunay triangulation algorithms require edges. + */ +class Edge { + int s, t; + int l, r; + + Edge() { s = t = 0; } + Edge(int s, int t) { this.s =s; this.t = t; } + int s() { return this.s; } + int t() { return this.t; } + int l() { return this.l; } + int r() { return this.r; } +} + + +/* + * Vector class. A few elementary vector operations. + */ +class Vector { + float u, v; + + Vector() { u = v = 0.0f; } + Vector(RealPoint p1, RealPoint p2) { + u = p2.x() - p1.x(); + v = p2.y() - p1.y(); + } + Vector(float u, float v) { this.u = u; this.v = v; } + + float dotProduct(Vector v) { return u * v.u + this.v * v.v; } + + static float dotProduct(RealPoint p1, RealPoint p2, RealPoint p3) { + float u1, v1, u2, v2; + + u1 = p2.x() - p1.x(); + v1 = p2.y() - p1.y(); + u2 = p3.x() - p1.x(); + v2 = p3.y() - p1.y(); + + return u1 * u2 + v1 * v2; + } + + float crossProduct(Vector v) { return u * v.v - this.v * v.u; } + + static float crossProduct(RealPoint p1, RealPoint p2, RealPoint p3) { + float u1, v1, u2, v2; + + u1 = p2.x() - p1.x(); + v1 = p2.y() - p1.y(); + u2 = p3.x() - p1.x(); + v2 = p3.y() - p1.y(); + + return u1 * v2 - v1 * u2; + } + + void setRealPoints(RealPoint p1, RealPoint p2) { + u = p2.x() - p1.x(); + v = p2.y() - p1.y(); + } +} + +/* + * Circle class. Circles are fundamental to computation of Delaunay + * triangulations. In particular, an operation which computes a + * circle defined by three points is required. + */ +class Circle { + RealPoint c; + float r; + + Circle() { c = new RealPoint(); r = 0.0f; } + Circle(RealPoint c, float r) { this.c = c; this.r = r; } + public RealPoint center() { return c; } + public float radius() { return r; } + public void set(RealPoint c, float r) { this.c = c; this.r = r; } + + /* + * Tests if a point lies inside the circle instance. + */ + public boolean inside(RealPoint p) { + if (c.distanceSq(p) < r * r) + return true; + else + return false; + } + + /* + * Compute the circle defined by three points (circumcircle). + */ + public void circumCircle(RealPoint p1, RealPoint p2, RealPoint p3) { + float cp; + + cp = Vector.crossProduct(p1, p2, p3); + if (cp != 0.0) + { + float p1Sq, p2Sq, p3Sq; + float num, den; + float cx, cy; + + p1Sq = p1.x() * p1.x() + p1.y() * p1.y(); + p2Sq = p2.x() * p2.x() + p2.y() * p2.y(); + p3Sq = p3.x() * p3.x() + p3.y() * p3.y(); + num = p1Sq*(p2.y() - p3.y()) + p2Sq*(p3.y() - p1.y()) + p3Sq*(p1.y() - p2.y()); + cx = num / (2.0f * cp); + num = p1Sq*(p3.x() - p2.x()) + p2Sq*(p1.x() - p3.x()) + p3Sq*(p2.x() - p1.x()); + cy = num / (2.0f * cp); + + c.set(cx, cy); + } + + // Radius + r = c.distance(p1); + } +} + +/* + * Triangulation class. A triangulation is represented as a set of + * points and the edges which form the triangulation. + */ +class Triangulation { + static final int Undefined = -1; + static final int Universe = 0; + int nPoints; + RealPoint point[]; + int nEdges; + int maxEdges; + Edge edge[]; + + Triangulation(int nPoints) { + + // Allocate points. + this.nPoints = nPoints; + this.point = new RealPoint[nPoints]; + for (int i = 0; i < nPoints; i++) + point[i] = new RealPoint(); + + // Allocate edges. + maxEdges = 3 * nPoints - 6; // Max number of edges. + edge = new Edge[maxEdges]; + for (int i = 0; i < maxEdges; i++) + edge[i] = new Edge(); + nEdges = 0; + } + + /* + * Sets the number of points in the triangulation. Reuses already + * allocated points and edges. + */ + public void setNPoints(int nPoints) { + // Fix edge array. + Edge tmpEdge[] = edge; + int tmpMaxEdges = maxEdges; + maxEdges = 3 * nPoints - 6; // Max number of edges. + edge = new Edge[maxEdges]; + + // Which is smaller? + int minMaxEdges; + if (tmpMaxEdges < maxEdges) + minMaxEdges = tmpMaxEdges; + else + minMaxEdges = maxEdges; + + // Reuse allocated edges. + for (int i = 0; i < minMaxEdges; i++) + this.edge[i] = tmpEdge[i]; + + // Get new edges. + for (int i = minMaxEdges; i < maxEdges; i++) + this.edge[i] = new Edge(); + + // Fix point array. + RealPoint tmpPoint[] = point; + point = new RealPoint[nPoints]; + + // Which is smaller? + int minPoints; + if (nPoints < this.nPoints) + minPoints = nPoints; + else + minPoints = this.nPoints; + + // Reuse allocated points. + for (int i = 0; i < minPoints; i++) + this.point[i] = tmpPoint[i]; + + // Get new points. + for (int i = minPoints; i < nPoints; i++) + this.point[i] = new RealPoint(); + + this.nPoints = nPoints; + } + + /* + * Generates a set of random points to triangulate. + */ + public void randomPoints(RealWindow w) { + for (int i = 0; i < nPoints; i++) + { + point[i].x = (float)Math.random() * w.xMax(); + point[i].y = (float)Math.random() * w.yMax(); + } + nEdges = 0; + } + + /* + * Copies a set of points. + */ + public void copyPoints(Triangulation t) { + int n; + + if (t.nPoints < nPoints) + n = t.nPoints; + else + n = nPoints; + + for (int i = 0; i < n; i++) { + point[i].x = t.point[i].x; + point[i].y = t.point[i].y; + } + + nEdges = 0; + } + + void addTriangle(int s, int t, int u) { + addEdge(s, t); + addEdge(t, u); + addEdge(u, s); + } + + public int addEdge(int s, int t) { + return addEdge(s, t, Undefined, Undefined); + } + + /* + * Adds an edge to the triangulation. Store edges with lowest + * vertex first (easier to debug and makes no other difference). + */ + public int addEdge(int s, int t, int l, int r) { + int e; + + // Add edge if not already in the triangulation. + e = findEdge(s, t); + if (e == Undefined) + if (s < t) + { + edge[nEdges].s = s; + edge[nEdges].t = t; + edge[nEdges].l = l; + edge[nEdges].r = r; + return nEdges++; + } + else + { + edge[nEdges].s = t; + edge[nEdges].t = s; + edge[nEdges].l = r; + edge[nEdges].r = l; + return nEdges++; + } + else + return Undefined; + } + + public int findEdge(int s, int t) { + boolean edgeExists = false; + int i; + + for (i = 0; i < nEdges; i++) + if (edge[i].s == s && edge[i].t == t || + edge[i].s == t && edge[i].t == s) { + edgeExists = true; + break; + } + + if (edgeExists) + return i; + else + return Undefined; + } + + /* + * Update the left face of an edge. + */ + public void updateLeftFace(int eI, int s, int t, int f) { + if (!((edge[eI].s == s && edge[eI].t == t) || + (edge[eI].s == t && edge[eI].t == s))) + Panic.panic("updateLeftFace: adj. matrix and edge table mismatch"); + if (edge[eI].s == s && edge[eI].l == Triangulation.Undefined) + edge[eI].l = f; + else if (edge[eI].t == s && edge[eI].r == Triangulation.Undefined) + edge[eI].r = f; + else + Panic.panic("updateLeftFace: attempt to overwrite edge info"); + } + + public void draw(RealWindowGraphics rWG, Color pC, Color eC) { + drawPoints(rWG, pC); + drawEdges(rWG, eC); + } + + public void drawPoints(RealWindowGraphics rWG, Color c) { + for (int i = 0; i < nPoints; i++) + rWG.drawPoint(point[i], c); + } + + public void drawEdges(RealWindowGraphics rWG, Color c) { + for (int i = 0; i < nEdges; i++) + drawEdge(rWG, edge[i], c); + } + + public void drawEdge(RealWindowGraphics rWG, Edge e, Color c) { + rWG.drawLine(point[e.s], point[e.t], c); + } + + public void print(PrintStream p) { + printPoints(p); + printEdges(p); + } + + public void printPoints(PrintStream p) { + for (int i = 0; i < nPoints; i++) + p.println(String.valueOf(point[i].x) + " " + String.valueOf(point[i].y)); + } + + public void printEdges(PrintStream p) { + for (int i = 0; i < nEdges; i++) + p.println(String.valueOf(edge[i].s) + " " + String.valueOf(edge[i].t)); + } +} + +/* + * Rectangle class. Need rectangles for window to viewport mapping. + */ +class RealRectangle { + RealPoint ll; + RealPoint ur; + + RealRectangle() { } + + RealRectangle (RealRectangle r) { + this.ll = new RealPoint(r.ll); + this.ur = new RealPoint(r.ur); + } + + RealRectangle (RealPoint ll, RealPoint ur) { + this.ll = new RealPoint(ll); + this.ur = new RealPoint(ur); + } + + RealRectangle(float xMin, float yMin, float xMax, float yMax) { + this.ll = new RealPoint(xMin, yMin); + this.ur = new RealPoint(xMax, yMax); + } + + public float width() { return ur.x() - ll.x(); } + public float height() { return ur.y() - ll.y(); } + + public RealPoint ll() { return ll; } + public RealPoint ur() { return ur; } + + public float xMin() { return ll.x; } + public float yMin() { return ll.y; } + + public float xMax() { return ur.x; } + public float yMax() { return ur.y; } +} + +/* + * A window is essentially a rectangle. + */ +class RealWindow extends RealRectangle { + RealWindow() {} + RealWindow(float xMin, float yMin, float xMax, float yMax) { + super(xMin, yMin, xMax, yMax); + } + RealWindow(RealWindow w) { super(w.ll(), w.ur()); } +} + +/* + * RealWindowGraphics class. Has a window, a viewport and a + * graphics context into which to draw. The graphics context + * is only set after calls to repaint result in calls to update. + * Contains drawing operations, drawTriangle for example, needed + * elsewhere. + */ +class RealWindowGraphics { + RealWindow w = null; // window + Dimension v = null; // viewport + Graphics g = null; + float scale = 1.0f; + + static final float realPointRadius = 0.04f; + static final int pixelPointRadius = 4; + static final int halfPixelPointRadius = 2; + + RealWindowGraphics(RealWindow w) { + this.w = new RealWindow(w); + } + + RealWindowGraphics(RealWindow w, Dimension d, Graphics g) { + this.w = new RealWindow(w); + this.v = new Dimension(d.width, d.height); + this.g = g; + calculateScale(); + } + + public void setWindow(RealWindow w) { + this.w = new RealWindow(w); + calculateScale(); + } + + public void setViewport(Dimension d) { + this.v = new Dimension(d.width, d.height); + calculateScale(); + } + + public void setGraphics(Graphics g) { + this.g = g; + } + + public Graphics getGraphics(Graphics g) { + return g; + } + + public void calculateScale() { + float sx, sy; + + sx = v.width / w.width(); + sy = v.height / w.height(); + + if (sx < sy) + scale = sx; + else + scale = sy; + } + + public void drawTriangle(RealPoint p1, + RealPoint p2, + RealPoint p3, + Color c) { + drawLine(p1, p2, c); + drawLine(p2, p3, c); + drawLine(p3, p1, c); + } + + public void drawLine(RealPoint p1, RealPoint p2, Color c) { + int x1, y1, x2, y2; + + g.setColor(c); + x1 = (int)(p1.x() * scale); + y1 = (int)(p1.y() * scale); + x2 = (int)(p2.x() * scale); + y2 = (int)(p2.y() * scale); + g.drawLine(x1, y1, x2, y2); + } + + public void drawPoint(RealPoint p, Color c) { + g.setColor(c); + + g.fillOval((int)(scale * p.x()) - halfPixelPointRadius, + (int)(scale * p.y()) - halfPixelPointRadius, + pixelPointRadius, pixelPointRadius); + } + + public void drawCircle(Circle circle, Color c) { + drawCircle(circle.center().x(), circle.center().y(), circle.radius(), c); + } + + public void drawCircle(RealPoint p, float r, Color c) { + drawCircle(p.x(), p.y(), r, c); + } + + public void drawCircle(float x, float y, float r, Color c) { + g.setColor(c); + + g.drawOval((int)(scale * (x - r)), (int)(scale * (y - r)), + (int)(2.0f * r * scale), (int)(2.0f * r * scale)); + } + + public void fillCircle(float x, float y, float r, Color c) { + g.setColor(c); + + g.fillOval((int)(scale * (x - r)), (int)(scale * (y - r)), + (int)(2.0f * r * scale), (int)(2.0f * r * scale)); + } +} + +/* + * AlgorithmUIHeading class. Provides a heading for part of the user + * interface. + */ +class AlgorithmUIHeading extends Panel { + + public AlgorithmUIHeading() { + // Headings. + setLayout(new GridLayout(0,7)); + add(new Label("Algorithm", Label.LEFT)); + add(new Label("Run", Label.LEFT)); + add(new Label("Points", Label.LEFT)); + add(new Label("Triangles", Label.LEFT)); + add(new Label("Circles", Label.LEFT)); + add(new Label("Points", Label.LEFT)); + add(new Label("Pause (mS)", Label.LEFT)); + } +} + +/* + * TriangulationCanvas class. Each of the triangulation algorithms + * needs a canvas to draw into. + */ +@SuppressWarnings("deprecation") +class TriangulationCanvas extends Canvas { + Triangulation t; + RealWindowGraphics rWG; // Does the actual drawing. + boolean needToClear = false; + boolean newPoints = false; + TriangulationAlgorithm alg; // The algorithm which uses this canvas. + + TriangulationCanvas(Triangulation t, + RealWindow w, + TriangulationAlgorithm alg) { + this.t = t; + rWG = new RealWindowGraphics(w); + this.alg = alg; + } + + public Insets insets() { + return new Insets(2,10,2,15); + } + + public void paint(Graphics g) { + if (needToClear) { + g.clearRect(0, 0, size().width, size().height); + + needToClear = false; + } + g.drawRect(0, 0, size().width-1, size().height-1); + rWG.setGraphics(g); + rWG.setViewport(size()); + alg.draw(rWG, t); + } + + public void update(Graphics g) { + paint(g); + } +} + +/* + * AlgorithmUI class. Each algorithm has a set of user interface + * controls. This class provides them. + */ +@SuppressWarnings("deprecation") +class AlgorithmUI extends Panel { + TextField nPointsTextField; + Checkbox animateCheckBox[]; + Checkbox runCheckBox; + TextField pauseTextField; + TriangulationAlgorithm algorithm; // Algorithm which uses this UI. + + public AlgorithmUI(TriangulationAlgorithm algorithm, + String label, int nPoints, int pause) { + + this.algorithm = algorithm; + + // One set of controls per algorithm. + setLayout(new GridLayout(0,7)); + add(new Label(label, Label.LEFT)); + add(runCheckBox = new Checkbox(null, null, true)); + add(nPointsTextField = new TextField(String.valueOf(nPoints), 5)); + animateCheckBox = new Checkbox[AnimateControl.nEntities]; + animateCheckBox[AnimateControl.triangles] = new Checkbox(null, null, true); + add(animateCheckBox[AnimateControl.triangles]); + animateCheckBox[AnimateControl.circles] = new Checkbox(null, null, true); + add(animateCheckBox[AnimateControl.circles]); + animateCheckBox[AnimateControl.points] = new Checkbox(null, null, true); + add(animateCheckBox[AnimateControl.points]); + pauseTextField = new TextField(String.valueOf(pause), 5); + add(pauseTextField); + } + + public void setAlgorithm(TriangulationAlgorithm algorithm) { + this.algorithm = algorithm; + } + + // Gets the current value in a text field. + int getValue(TextField tF) { + int i; + try { + i = Integer.valueOf(tF.getText()).intValue(); + } catch (java.lang.NumberFormatException e) { + i = 0; + } + return i; + } + + public boolean handleEvent(Event evt) { + if (evt.id == Event.ACTION_EVENT) { + if (evt.target == runCheckBox) { + algorithm.control().setRun(((Boolean)evt.arg).booleanValue()); + return true; + } else if (evt.target == animateCheckBox[AnimateControl.triangles]) { + algorithm.control().setAnimate(AnimateControl.triangles, + ((Boolean)evt.arg).booleanValue()); + return true; + } else if (evt.target == animateCheckBox[AnimateControl.circles]) { + algorithm.control().setAnimate(AnimateControl.circles, + ((Boolean)evt.arg).booleanValue()); + return true; + } else if (evt.target == animateCheckBox[AnimateControl.points]) { + algorithm.control().setAnimate(AnimateControl.points, + ((Boolean)evt.arg).booleanValue()); + return true; + } else if (evt.target == pauseTextField) { + algorithm.control().setPause(getValue(pauseTextField)); + return true; + } else if (evt.target == nPointsTextField) { + algorithm.control().nPoints = getValue(nPointsTextField); + return true; + } + } + + return false; + } +} + +/* + * AnimateControl class. Each algorithm animation has various entities + * which can be displayed. This class provides the state which controls + * what is being displayed. It is manipulated by AlgorithmUI and + * accessed by the animation routines in each algorithm. + */ +class AnimateControl { + TriangulationAlgorithm triAlg; + static final int automatic = 0; + static final int manual = 1; + int animateMode = automatic; + int pause = 10; + static final int algorithm = 0; + static final int triangles = 1; + static final int points = 2; + static final int circles = 3; + static final int nEntities = 4; + boolean run; + boolean animate[]; + int nPoints; + + AnimateControl(TriangulationAlgorithm algorithm) { + triAlg = algorithm; + animate = new boolean[nEntities]; + for (int i = 0; i < nEntities; i++) + animate[i] = true; + run = true; + } + + AnimateControl(TriangulationAlgorithm algorithm, int nPoints) { + this(algorithm); + this.nPoints = nPoints; + } + + public void setAnimate(int entity, boolean v) { + animate[entity] = v; + if (!v) + triAlg.canvas().needToClear = true; + } + + public boolean animate(int entity) { + return animate[entity]; + } + + public int mode() { + return animateMode; + } + + public void setManualAnimateMode() { + animateMode = manual; + } + + public void setAutomaticAnimateMode() { + animateMode = automatic; + } + + public int getPause() { + return pause; + } + + public void setPause(int p) { + pause = p; + } + + public int getNPoints() { + return nPoints; + } + + public void setNPoints(int n) { + nPoints = n; + } + + public void setRun(boolean v) { + run = v; + } + + public boolean getRun() { + return run; + } +} + +/* + * TriangulationAlgorithm class. Absract. Superclass for + * actual algorithms. Has several abstract function members - + * including the triangulation member which actually computes + * the triangulation. + */ +abstract class TriangulationAlgorithm { + String algName; + TriangulationCanvas triCanvas; + AnimateControl aniControl; + AlgorithmUI algorithmUI; + RealWindow w; + RealWindowGraphics rWG; + + // Variables and constants for animation state. + final int nStates = 5; + boolean state[] = new boolean[nStates]; + static final int triangulationState = 0; + static final int pointState = 1; + static final int triangleState = 2; + static final int insideState = 4; + static final int edgeState = 5; + + public TriangulationAlgorithm(Triangulation t, RealWindow w, + String name, int nPoints) { + algName = name; + aniControl = new AnimateControl(this, nPoints); + algorithmUI = new AlgorithmUI(this, name, nPoints, aniControl.getPause()); + triCanvas = new TriangulationCanvas(t, w, this); + + for (int s = 0; s < nStates; s++) + state[s] = false; + triCanvas.needToClear = true; + } + + public void setCanvas(TriangulationCanvas tc) { + triCanvas = tc; + } + + public AnimateControl control() { + return aniControl; + } + + public AlgorithmUI algorithmUI() { + return algorithmUI; + } + + public TriangulationCanvas canvas() { + return triCanvas; + } + + public void setAlgorithmState(int stateVar, boolean value) { + state[stateVar] = value; + } + + public void pause() { + if (aniControl.mode() == AnimateControl.automatic) + try { + wait(aniControl.getPause()); + } catch (InterruptedException e){} + else + try {wait();} catch (InterruptedException e){} + } + + public void animate(int state) { + if ((aniControl.animate(AnimateControl.triangles) || + aniControl.animate(AnimateControl.circles)) && + state == triangulationState) + triCanvas.needToClear = true; + + setAlgorithmState(state, true); + + triCanvas.repaint(); + + pause(); + + setAlgorithmState(state, false); + } + + public void reset() { + for (int s = 0; s < nStates; s++) + state[s] = false; + triCanvas.needToClear = true; + } + + public synchronized void nextStep() { notify(); } + abstract public void triangulate(Triangulation t); + abstract public void draw(RealWindowGraphics rWG, Triangulation t); +} + +/* + * QuarticAlgorithm class. O(n^4) algorithm. The most brute-force + * of the algorithms. + */ +class QuarticAlgorithm extends TriangulationAlgorithm { + int i, j, k, l; + Circle c = new Circle(); + final static String algName = "O(n^4)"; + + public QuarticAlgorithm(Triangulation t, RealWindow w, int nPoints) { + super(t, w, algName, nPoints); + } + + public void reset() { + i = j = k = l = 0; + super.reset(); + } + + public void draw(RealWindowGraphics rWG, Triangulation t) { + if (state[triangleState]) { + if (aniControl.animate(AnimateControl.triangles)) + rWG.drawTriangle(t.point[i], t.point[j], t.point[k], Color.green); + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(c, Color.green); + } else if (state[pointState]) { + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(t.point[l], Color.orange); + } else if (state[insideState]) { + if (aniControl.animate(AnimateControl.triangles)) + rWG.drawTriangle(t.point[i], t.point[j], t.point[k], Color.red); + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(c, Color.red); + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(t.point[l], Color.red); + } else if (state[triangulationState]) { + t.draw(rWG, Color.black, Color.black); + } else { + t.draw(rWG, Color.black, Color.black); + } + } + + public synchronized void triangulate(Triangulation t) { + boolean pointFree; + int n = t.nPoints; + RealPoint p[] = t.point; + + for (i = 0; i < n-2; i++) + for (j = i + 1; j < n-1; j++) + if (j != i) + for (k = j + 1; k < n; k++) + if (k != i && k != j) + { + c.circumCircle(p[i], p[j], p[k]); + animate(triangleState); + pointFree = true; + for (l = 0; l < n; l++) + if (l != i && l != j && l != k) { + animate(pointState); + if (c.inside(p[l])) { + animate(insideState); + pointFree = false; + break; + } + } + + if (pointFree) + t.addTriangle(i, j, k); + + animate(triangulationState); + } + } +} + +/* + * CubicAlgorithm class. O(n^3) algorithm. + */ +class CubicAlgorithm extends TriangulationAlgorithm { + int s, t, u, i; + Circle bC = new Circle(); + final static String algName = "O(n^3)"; + int nFaces; + + public CubicAlgorithm(Triangulation t, RealWindow w, int nPoints) { + super(t, w, algName, nPoints); + } + + public void reset() { + nFaces = 0; + triCanvas.needToClear = true; + super.reset(); + } + + public void draw(RealWindowGraphics rWG, Triangulation tri) { + if (state[triangleState]) { + if (aniControl.animate(AnimateControl.triangles)) { + rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[u], + Color.green); + rWG.drawLine(tri.point[s], tri.point[t], Color.blue); + } + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(bC, Color.green); + } else if (state[pointState]) { + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(tri.point[i], Color.orange); + } else if (state[insideState]) { + if (aniControl.animate(AnimateControl.triangles)) { + rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[u], Color.red); + rWG.drawLine(tri.point[s], tri.point[t], Color.blue); + } + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(bC, Color.red); + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(tri.point[s], Color.red); + } else if (state[triangulationState]) { + tri.draw(rWG, Color.black, Color.black); + } else { + tri.draw(rWG, Color.black, Color.black); + } + } + + public synchronized void triangulate(Triangulation tri) { + int seedEdge, currentEdge; + int nFaces; + Int s, t; + + // Initialise. + nFaces = 0; + s = new Int(); + t = new Int(); + + // Find closest neighbours and add edge to triangulation. + findClosestNeighbours(tri.point, tri.nPoints, s, t); + + // Create seed edge and add it to the triangulation. + seedEdge = tri.addEdge(s.getValue(), t.getValue(), + Triangulation.Undefined, + Triangulation.Undefined); + + currentEdge = 0; + while (currentEdge < tri.nEdges) + { + if (tri.edge[currentEdge].l == Triangulation.Undefined) { + completeFacet(currentEdge, tri, nFaces); + animate(triangulationState); + } + if (tri.edge[currentEdge].r == Triangulation.Undefined) { + completeFacet(currentEdge, tri, nFaces); + animate(triangulationState); + } + currentEdge++; + } + } + + // Find the two closest points. + public void findClosestNeighbours(RealPoint p[], int nPoints, + Int u, Int v) { + int i, j; + float d, min; + int s, t; + + s = t = 0; + min = Float.MAX_VALUE; + for (i = 0; i < nPoints-1; i++) + for (j = i+1; j < nPoints; j++) + { + d = p[i].distanceSq(p[j]); + if (d < min) + { + s = i; + t = j; + min = d; + } + } + u.setValue(s); + v.setValue(t); + } + + /* + * Complete a facet by looking for the circle free point to the left + * of the edge "e_i". Add the facet to the triangulation. + * + * This function is a bit long and may be better split. + */ + public void completeFacet(int eI, Triangulation tri, int nFaces) { + float cP; + boolean pointFree; + Edge e[] = tri.edge; + RealPoint p[] = tri.point; + + // Cache s and t. + if (e[eI].l == Triangulation.Undefined) + { + s = e[eI].s; + t = e[eI].t; + } + else if (e[eI].r == Triangulation.Undefined) + { + s = e[eI].t; + t = e[eI].s; + } + else + // Edge already completed. + return; + + // Find point free circumcircle to the left. + for (u = 0; u < tri.nPoints; u++) + if (u != s && u != t) { + if (Vector.crossProduct(p[s], p[t], p[u]) > 0.0) { + bC.circumCircle(p[s], p[t], p[u]); + animate(triangleState); + pointFree = true; + for (i = 0; i < tri.nPoints; i++) + if (i != s && i != t && i != u) { + animate(pointState); + cP = Vector.crossProduct(p[s], p[t], p[i]); + if (cP > 0.0) + if (bC.inside(p[i])) { + animate(insideState); + pointFree = false; + break; + } + } + animate(triangulationState); + if (pointFree) + break; + } + } + + // Add new triangle or update edge info if s-t is on hull. + if (u < tri.nPoints) { + int bP = u; + + // Update face information of edge being completed. + tri.updateLeftFace(eI, s, t, nFaces); + nFaces++; + + // Add new edge or update face info of old edge. + eI = tri.findEdge(bP, s); + if (eI == Triangulation.Undefined) + // New edge. + eI = tri.addEdge(bP, s, nFaces, Triangulation.Undefined); + else + // Old edge. + tri.updateLeftFace(eI, bP, s, nFaces); + + // Add new edge or update face info of old edge. + eI = tri.findEdge(t, bP); + if (eI == Triangulation.Undefined) + // New edge. + eI = tri.addEdge(t, bP, nFaces, Triangulation.Undefined); + else + // Old edge. + tri.updateLeftFace(eI, t, bP, nFaces); + } else + tri.updateLeftFace(eI, s, t, Triangulation.Universe); + } +} + +/* + * QuadraticAlgorithm class. O(n^2) algorithm. + */ +class QuadraticAlgorithm extends TriangulationAlgorithm { + int s, t, u, bP; + Circle bC = new Circle(); + final static String algName = "O(n^2)"; + int nFaces; + + public QuadraticAlgorithm(Triangulation t, RealWindow w, int nPoints) { + super(t, w, algName, nPoints); + } + + public void reset() { + nFaces = 0; + triCanvas.needToClear = true; + super.reset(); + } + + public void draw(RealWindowGraphics rWG, Triangulation tri) { + if (state[triangleState]) { + if (aniControl.animate(AnimateControl.triangles)) { + rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[bP], + Color.green); + rWG.drawLine(tri.point[s], tri.point[t], Color.blue); + } + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(bC, Color.green); + } else if (state[pointState]) { + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(tri.point[u], Color.orange); + } else if (state[insideState]) { + if (aniControl.animate(AnimateControl.triangles)) { + rWG.drawTriangle(tri.point[s], tri.point[t], tri.point[bP], Color.red); + rWG.drawLine(tri.point[s], tri.point[t], Color.blue); + } + if (aniControl.animate(AnimateControl.circles)) + rWG.drawCircle(bC, Color.red); + if (aniControl.animate(AnimateControl.points)) + rWG.drawPoint(tri.point[s], Color.red); + } else if (state[triangulationState]) { + tri.draw(rWG, Color.black, Color.black); + } else { + tri.draw(rWG, Color.black, Color.black); + } + } + + public synchronized void triangulate(Triangulation tri) { + int seedEdge, currentEdge; + int nFaces; + Int s, t; + + // Initialise. + nFaces = 0; + s = new Int(); + t = new Int(); + + // Find closest neighbours and add edge to triangulation. + findClosestNeighbours(tri.point, tri.nPoints, s, t); + + // Create seed edge and add it to the triangulation. + seedEdge = tri.addEdge(s.getValue(), t.getValue(), + Triangulation.Undefined, + Triangulation.Undefined); + + currentEdge = 0; + while (currentEdge < tri.nEdges) { + if (tri.edge[currentEdge].l == Triangulation.Undefined) { + completeFacet(currentEdge, tri, nFaces); + animate(triangulationState); + } + if (tri.edge[currentEdge].r == Triangulation.Undefined) { + completeFacet(currentEdge, tri, nFaces); + animate(triangulationState); + } + currentEdge++; + } + } + + // Find the two closest points. + public void findClosestNeighbours(RealPoint p[], int nPoints, + Int u, Int v) { + int i, j; + float d, min; + int s, t; + + s = t = 0; + min = Float.MAX_VALUE; + for (i = 0; i < nPoints-1; i++) + for (j = i+1; j < nPoints; j++) + { + d = p[i].distanceSq(p[j]); + if (d < min) + { + s = i; + t = j; + min = d; + } + } + u.setValue(s); + v.setValue(t); + } + + /* + * Complete a facet by looking for the circle free point to the left + * of the edge "e_i". Add the facet to the triangulation. + * + * This function is a bit long and may be better split. + */ + public void completeFacet(int eI, Triangulation tri, int nFaces) { + float cP; + int i; + Edge e[] = tri.edge; + RealPoint p[] = tri.point; + + // Cache s and t. + if (e[eI].l == Triangulation.Undefined) + { + s = e[eI].s; + t = e[eI].t; + } + else if (e[eI].r == Triangulation.Undefined) + { + s = e[eI].t; + t = e[eI].s; + } + else + // Edge already completed. + return; + + + // Find a point on left of edge. + for (u = 0; u < tri.nPoints; u++) + { + if (u == s || u == t) + continue; + if (Vector.crossProduct(p[s], p[t], p[u]) > 0.0) + break; + } + + // Find best point on left of edge. + bP = u; + if (bP < tri.nPoints) + { + bC.circumCircle(p[s], p[t], p[bP]); + + animate(triangleState); + + for (u = bP+1; u < tri.nPoints; u++) + { + if (u == s || u == t) + continue; + + animate(pointState); + + cP = Vector.crossProduct(p[s], p[t], p[u]); + + if (cP > 0.0) + if (bC.inside(p[u])) + { + animate(insideState); + bP = u; + bC.circumCircle(p[s], p[t], p[u]); + animate(triangleState); + } + } + } + + // Add new triangle or update edge info if s-t is on hull. + if (bP < tri.nPoints) + { + // Update face information of edge being completed. + tri.updateLeftFace(eI, s, t, nFaces); + nFaces++; + + // Add new edge or update face info of old edge. + eI = tri.findEdge(bP, s); + if (eI == Triangulation.Undefined) + // New edge. + eI = tri.addEdge(bP, s, nFaces, Triangulation.Undefined); + else + // Old edge. + tri.updateLeftFace(eI, bP, s, nFaces); + + // Add new edge or update face info of old edge. + eI = tri.findEdge(t, bP); + if (eI == Triangulation.Undefined) + // New edge. + eI = tri.addEdge(t, bP, nFaces, Triangulation.Undefined); + else + // Old edge. + tri.updateLeftFace(eI, t, bP, nFaces); + } else + tri.updateLeftFace(eI, s, t, Triangulation.Universe); + } +} + +/* + * AppletUI class. Provides most of the user interface for the applet. + */ +class AppletUI extends Panel { + AlgorithmUI AlgorithmUI[]; + + public AppletUI(TriangulationAlgorithm algorithm[]) { + Label l; + Panel p; + + setLayout(new BorderLayout()); + + // Per algorithm controls. + p = new Panel(); + p.setLayout(new GridLayout(0,1)); + + // Headings for algorithm controls. + p.add(new AlgorithmUIHeading()); + + // One set of controls per algorithm. + for (int i = 0; i < algorithm.length; i++) + p.add(algorithm[i].algorithmUI()); + + // Add panel to controls. + add("Center", p); + + // Applet controls. + p = new Panel(); + p.setLayout(new GridLayout(0,1)); + p.add(new Button("Start")); + p.add(new Button("Stop")); + p.add(new Button("New")); + p.add(new Label("Step Mode", Label.CENTER)); + Choice c = new Choice(); + c.addItem("Auto"); + c.addItem("Manual"); + p.add(c); + + // Add panel to controls. + add("East", p); + } +} + +/* + * TriangulationApplet class. "Main Class" + */ +@SuppressWarnings("deprecation") +public class Triangulator extends Applet implements Runnable { + Thread triangulateThread[]; + int nPoints = 10; + Triangulation triangulation[]; + TriangulationAlgorithm algorithm[]; + RealWindow w; + RealWindowGraphics rWG; + AppletUI appUI; + public static final int On2 = 0; + public static final int On3 = 1; + public static final int On4 = 2; + Panel canvases; + static final int nAlgorithms = 3; + + public void init() { + + setBackground(Color.lightGray); + resize(600,350); + + // Create a rectangle in the real plane for points. + w = new RealWindow(0.0f, 0.0f, 1.0f, 1.0f); + + // Create array of triangulations, including random points. + triangulation = new Triangulation[nAlgorithms]; + triangulation[0] = new Triangulation(nPoints); + triangulation[0].randomPoints(w); + for (int i = 1; i < nAlgorithms; i++) { + triangulation[i] = new Triangulation(nPoints); + triangulation[i].copyPoints(triangulation[0]); + } + + // Create an array of triangulation algorithms. + algorithm = new TriangulationAlgorithm[nAlgorithms]; + algorithm[0] = new QuadraticAlgorithm(triangulation[0], w, nPoints); + algorithm[1] = new CubicAlgorithm(triangulation[1], w, nPoints); + algorithm[2] = new QuarticAlgorithm(triangulation[2], w, nPoints); + + // Array of thread references (one for each algorithm). + triangulateThread = new Thread[nAlgorithms]; + + // Create user interface. + Panel heading = new Panel(); + heading.setLayout(new BorderLayout()); + heading.add("Center", new Label("The Triangulator", Label.CENTER)); + Panel algHeadings = new Panel(); + algHeadings.setLayout(new GridLayout(0, nAlgorithms)); + for (int i = 0; i < nAlgorithms; i++) + algHeadings.add(new Label(algorithm[i].algName, Label.CENTER)); + heading.add("South", algHeadings); + canvases = new Panel(); + canvases.setLayout(new GridLayout(0, nAlgorithms)); + for (int i = 0; i < nAlgorithms; i++) + canvases.add(algorithm[i].canvas()); + setLayout(new BorderLayout()); + add("North", heading); + add("Center", canvases); + appUI = new AppletUI(algorithm); + add("South", appUI); + } + + /* + * Called for each algorithm thread when started. + */ + public void run() { + int algNo; + String threadName; + + // Work out which algorithm to run from the thread name. + threadName = Thread.currentThread().getName(); + algNo = Integer.parseInt(threadName.substring(threadName.length()-1)); + algorithm[algNo].triangulate(triangulation[algNo]); + } + + public Insets insets() { + // Right offset is more than left, due to Choice bug. + return new Insets(5,10,5,15); + } + + /* + * Actually start the triangulation algorithms running. + */ + private synchronized void startTriangulate() { + for (int i = 0; i < triangulateThread.length; i++) + if (triangulateThread[i] != null && triangulateThread[i].isAlive()) { + stop(); + } + for (int i = 0; i < triangulateThread.length; i++) { + if (algorithm[i].control().getRun()) { + triangulateThread[i] = new Thread(this, "Triangulation-" + String.valueOf(i)); + triangulateThread[i].setPriority(Thread.MIN_PRIORITY); + triangulateThread[i].start(); + } + } + } + + /* + * Generate new points for the algorithms. + */ + private synchronized void newPoints() { + int max, alg; + + stop(); + + // Find algorithm with max points. + max = 0; + alg = -1; + for (int i = 0; i < nAlgorithms; i++) + if (algorithm[i].control().getRun() && + algorithm[i].control().getNPoints() > max) { + max = algorithm[i].control().getNPoints(); + alg = i; + } + + // Generate maximum number of points. + if (alg != -1) { + triangulation[alg].setNPoints(algorithm[alg].control().getNPoints()); + triangulation[alg].randomPoints(w); + } + + /* Now copy points into other algorithms. This has the effect + * that algorithms with the same number of points wind up with + * the same points. + */ + for (int i = 0; i < nAlgorithms; i++) + if (algorithm[i].control().getRun() && i != alg) { + triangulation[i].setNPoints(algorithm[i].control().getNPoints()); + triangulation[i].copyPoints(triangulation[alg]); + } + + for (int i = 0; i < nAlgorithms; i++) + if (algorithm[i].control().getRun()) { + algorithm[i].reset(); + algorithm[i].canvas().repaint(); + } + } + + /* + * Stop the applet. Kill the triangulation algorithm if + * still triangulating. + */ + + public synchronized void stop() { + for (int i = 0; i < triangulateThread.length; i++) { + if (triangulateThread[i] != null) { + try { + triangulateThread[i].stop(); + } catch (IllegalThreadStateException e) {} + triangulateThread[i] = null; + } + } + } + + /* + * Gets the current value in a text field. + */ + int getValue(TextField tF) { + int i; + try { + i = Integer.valueOf(tF.getText()).intValue(); + } catch (java.lang.NumberFormatException e) { + i = 0; + } + return i; + } + + /* + * Handle main level events. + */ + public boolean handleEvent(Event evt) { + if (evt.id == Event.ACTION_EVENT) { + if ("Start".equals(evt.arg)) { + startTriangulate(); + return true; + } else if ("Stop".equals(evt.arg)) { + stop(); + return true; + } else if ("New".equals(evt.arg)) { + newPoints(); + } else if ("Manual".equals(evt.arg)) { + for (int i = 0; i < nAlgorithms; i++) + algorithm[i].control().setManualAnimateMode(); + return true; + } else if ("Auto".equals(evt.arg)) { + for (int i = 0; i < nAlgorithms; i++) + algorithm[i].control().setAutomaticAnimateMode(); + return true; + } + } else if (evt.id == Event.MOUSE_DOWN) { + // These events only occur in the canvases. + for (int i = 0; i < nAlgorithms; i++) + if (algorithm[i].control().mode() == AnimateControl.manual) + algorithm[i].nextStep(); + return true; + } else if (evt.id == Event.MOUSE_MOVE) { + return true; + } + + return false; + } +} diff --git a/dasCore/src/main/java/org/das2/math/fft/ComplexArray.java b/dasCore/src/main/java/org/das2/math/fft/ComplexArray.java new file mode 100644 index 000000000..b1e76f60d --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/ComplexArray.java @@ -0,0 +1,276 @@ +/* + * ComplexArray.java + * + * Created on November 29, 2004, 9:34 PM + */ + +package org.das2.math.fft; + +/** + * Interface for passing complex arrays to and from FFT routines. The intent is + * that the complex array can be backed by data in any format. Each elements is + * readable and writeable via get and set methods for the real and imaginary components. + * @author Jeremy + */ +public class ComplexArray { + /** + * A complex array that is accessed by floats + */ + public interface Float { + /** + * + * @param i + * @return the real component of the complex element i. + */ + float getReal(int i); + /** + * + * @param i + * @return the imaginary component of the complex element i. + */ + float getImag(int i); + /** + * + * @param i the element index + * @param value the real component, the imaginary is not changed. + */ + void setReal(int i,float value); + /** + * + * @param i the element index + * @param value the imaginary component, the real is not changed. + */ + void setImag(int i,float value); + /** + * returns the number of elements + * @return the number of elements in the array + */ + int length(); + } + + /** + * ComplexArray that is accessed by doubles + */ + public interface Double { + /** + * + * @param i + * @return the real component of the complex element i. + */ + double getReal(int i); + + /** + * + * @param i + * @return the imaginary component of the complex element i. + */ + double getImag(int i); + /** + * + * @param i the element index + * @param value the real component, the imaginary is not changed. + */ + void setReal(int i,double value); + /** + * + * @param i the element index + * @param value the imaginary component, the real is not changed. + */ + void setImag(int i,double value); + /** + * @return the number of elements in the array + */ + int length(); + } + + /** + * Implements ComplexArray that is backed by two float arrays. + */ + public static final class ComplexArrayDoubleDouble implements Double { + final double[] real; + final double[] imaginary; + private ComplexArrayDoubleDouble( final double[] real, final double[] imaginary ) { + this.real= real; + this.imaginary= imaginary; + } + + /** + * + * @param i + * @return the imaginary component of the complex element i. + */ + public double getImag(int i) { + return imaginary[i]; + } + + /** + * + * @param i + * @return the real component of the complex element i. + */ + public double getReal(int i) { + return real[i]; + } + + /** + * + * @param i the element index + * @param value the imaginary component, the real is not changed. + */ + public void setImag(int i, double value) { + imaginary[i]= value; + } + + /** + * + * @param i the element index + * @param value the real component, the imaginary is not changed. + */ + public void setReal(int i, double value) { + real[i]= value; + } + + /** + * @return the number of elements in the array + */ + public int length() { + return real.length; + } + + } + + /** + * Implements ComplexArray that is backed by two float arrays. + */ + public static final class ComplexArrayFloatFloat implements Float { + final float[] real; + final float[] imaginary; + private ComplexArrayFloatFloat( final float[] real, final float[] imaginary ) { + this.real= real; + this.imaginary= imaginary; + } + public float getImag(int i) { + return imaginary[i]; + } + + public float getReal(int i) { + return real[i]; + } + + public void setImag(int i, float value) { + imaginary[i]= value; + } + + public void setReal(int i, float value) { + real[i]= value; + } + public int length() { + return real.length; + } + + } + + /** + * Creates a new ComplexArray from an array of real numbers. The complex + * components of each element in the resulting array is zero. + */ + public static ComplexArray.Double newArray( double[] real ) { + double[] imag= new double[real.length]; + return new ComplexArrayDoubleDouble( real, imag ); + } + + /** + * Creates a new ComplexArray from a float array representing real numbers, but + * copies the original array so that it is not modified. + */ + public static ComplexArray.Double newArrayCopy( double[] real ) { + double[] imag= new double[real.length]; + double[] realCopy= new double[real.length]; + for (int i=0; iMath.pow(2,100)) throw new IllegalArgumentException("n too big or too small, n="+n); + //this.real= real; + this.real= false; // funny real,false... + if ( doublePrecision ) { + complexDoubleFFT= new ComplexDoubleFFT_Mixed(n); + } else { + complexFloatFFT= new ComplexFloatFFT_Mixed(n); + } + if ( this.real ) { + if ( n%2 != 0 ) throw new IllegalArgumentException("n must be even"); + if ( doublePrecision ) { + realDoubleFFT= new RealDoubleFFT_Even(n); + } else { + throw new UnsupportedOperationException("not implemented"); + } + } else { + if ( doublePrecision ) { + complexDoubleFFT= new ComplexDoubleFFT_Mixed(n); + } else { + complexFloatFFT= new ComplexFloatFFT_Mixed(n); + } + } + } + + /** + * creates an FFT object that operates on a ComplexArray.Float of n elements. + */ + public static GeneralFFT newFloatFFT( int n ) { + return new GeneralFFT( n, false, false ); + } + + /** + * creates an FFT object that operates on a ComplexArray.Double of n elements. + */ + public static GeneralFFT newDoubleFFT( int n ) { + return new GeneralFFT( n, true, false ); + } + + /** + * perform the forward transform on the array in situ. + */ + public void transform( ComplexArray.Double data ) { + if ( !doublePrecision ) throw new IllegalArgumentException("expected float arrays, got doubles"); + double norm; + if ( real ) { + realDoubleFFT.transform( data ); + norm= realDoubleFFT.normalization(); + } else { + complexDoubleFFT.transform( data ); + norm = complexDoubleFFT.normalization(); + } + for (int i = 0; i < n; i++) { + data.setReal( i, data.getReal(i) * norm ); + data.setImag( i, data.getImag(i) * norm ); + } + } + + /** + * perform the forward transform on the array in situ. + */ + public void transform( ComplexArray.Float data ) { + if ( doublePrecision ) throw new IllegalArgumentException("expected double arrays, got floats"); + complexFloatFFT.transform( data ); + float norm = complexFloatFFT.normalization(); + for (int i = 0; i < n; i++) { + data.setReal( i, data.getReal(i) * norm ); + data.setImag( i, data.getImag(i) * norm ); + } + } + + /** + * perform the inverse transform on the array in situ. + */ + public void invTransform( ComplexArray.Double data ) { + if ( !doublePrecision ) throw new IllegalArgumentException("expected float arrays, got doubles"); + complexDoubleFFT.inverse( data ); + } + + /** + * perform the inverse transform on the array in situ. + */ + public void invTransform( ComplexArray.Float data ) { + if ( doublePrecision ) throw new IllegalArgumentException("expected double arrays, got floats"); + complexFloatFFT.inverse( data ); + } + +} diff --git a/dasCore/src/main/java/org/das2/math/fft/SimpleFFT.java b/dasCore/src/main/java/org/das2/math/fft/SimpleFFT.java new file mode 100644 index 000000000..511ac8821 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/SimpleFFT.java @@ -0,0 +1,90 @@ +/* File: SimpleFFT.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 22, 2003, 11:29 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.math.fft; + +/** + * + * @author Edward West + */ +public final class SimpleFFT { + + private static final double LOG_2 = Math.log(2); + + /** Creates a new instance of SimpleFFT */ + private SimpleFFT() { + } + + public static double[][] fft( double[][] array ) { + double u_r,u_i, w_r,w_i, t_r,t_i; + int ln, nv2, k, l, le, le1, j, ip, i, n; + + n = array.length; + ln = (int)( Math.log( (double)n )/LOG_2 + 0.5 ); + nv2 = n / 2; + j = 1; + for (i = 1; i < n; i++ ) { + if (i < j) { + t_r = array[i - 1][0]; + t_i = array[i - 1][1]; + array[i - 1][0] = array[j - 1][0]; + array[i - 1][1] = array[j - 1][1]; + array[j - 1][0] = t_r; + array[j - 1][1] = t_i; + } + k = nv2; + while (k < j) { + j = j - k; + k = k / 2; + } + j = j + k; + } + + for (l = 1; l <= ln; l++) {/* loops thru stages */ + le = (int)(Math.exp( (double)l * LOG_2 ) + 0.5 ); + le1 = le / 2; + u_r = 1.0; + u_i = 0.0; + w_r = Math.cos( Math.PI / (double)le1 ); + w_i = -Math.sin( Math.PI / (double)le1 ); + for (j = 1; j <= le1; j++) {/* loops thru 1/2 twiddle values per stage */ + for (i = j; i <= n; i += le) {/* loops thru points per 1/2 twiddle */ + ip = i + le1; + t_r = array[ip - 1][0] * u_r - u_i * array[ip - 1][1]; + t_i = array[ip - 1][1] * u_r + u_i * array[ip - 1][0]; + + array[ip - 1][0] = array[i - 1][0] - t_r; + array[ip - 1][1] = array[i - 1][1] - t_i; + + array[i - 1][0] = array[i - 1][0] + t_r; + array[i - 1][1] = array[i - 1][1] + t_i; + } + t_r = u_r * w_r - w_i * u_i; + u_i = w_r * u_i + w_i * u_r; + u_r = t_r; + } + } + return array; + } + +} diff --git a/dasCore/src/main/java/org/das2/math/fft/WaveformToSpectrum.java b/dasCore/src/main/java/org/das2/math/fft/WaveformToSpectrum.java new file mode 100644 index 000000000..8949233bb --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/WaveformToSpectrum.java @@ -0,0 +1,222 @@ +/* + * WaveformToSpectrum.java + * + * Created on March 25, 2004, 9:09 PM + */ + +package org.das2.math.fft; + +import org.das2.dataset.ClippedVectorDataSet; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.TableDataSetBuilder; +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.UnitsUtil; + +/** + * + * @author jbf + */ +public class WaveformToSpectrum { + + static class UnitsInverter { + static Units getInverseUnit( Units unit ) { + if ( unit==Units.seconds ) { + return Units.hertz; + } else if ( unit==Units.dimensionless ) { + return Units.dimensionless; + } else { + throw new IllegalArgumentException( "units not supported: "+unit ); + } + } + } + + static DatumVector getFrequencyDomainTags( DatumVector timeDomainTags ) { + Units timeUnit= timeDomainTags.getUnits(); + double[] x= timeDomainTags.toDoubleArray(timeUnit); + double[] result= new double[x.length]; + result[0]= 0.; + double T= x[1]-x[0]; + int n= x.length; + int n21= n/2+1; + for ( int i=0; i 0.01 && rr < 0.09 ) { + return false; + } + } + return true; + } + } + + public static double[][] fft( double[][] array ) { + double u_r,u_i, w_r,w_i, t_r,t_i; + int ln, nv2, k, l, le, le1, j, ip, i, n; + + n = array[0].length; + ln = (int)( Math.log( (double)n )/LOG_2 + 0.5 ); + if ( !(Math.pow(2,ln)==n) ) { + throw new IllegalArgumentException( "input array (["+array.length+"]["+n+"]) is not [2][2^k]" ); + } + nv2 = n / 2; + j = 1; + for (i = 1; i < n; i++ ) { + if (i < j) { + t_r = array[0][i - 1]; + t_i = array[1][i - 1]; + array[0][i - 1] = array[0][j - 1]; + array[1][i - 1] = array[1][j - 1]; + array[0][j - 1] = t_r; + array[1][j - 1] = t_i; + } + k = nv2; + while (k < j) { + j = j - k; + k = k / 2; + } + j = j + k; + } + + for (l = 1; l <= ln; l++) {/* loops thru stages */ + le = (int)(Math.exp( (double)l * LOG_2 ) + 0.5 ); + le1 = le / 2; + u_r = 1.0; + u_i = 0.0; + w_r = Math.cos( Math.PI / (double)le1 ); + w_i = -Math.sin( Math.PI / (double)le1 ); + for (j = 1; j <= le1; j++) {/* loops thru 1/2 twiddle values per stage */ + for (i = j; i <= n; i += le) {/* loops thru points per 1/2 twiddle */ + ip = i + le1; + t_r = array[0][ip - 1] * u_r - u_i * array[1][ip - 1]; + t_i = array[1][ip - 1] * u_r + u_i * array[0][ip - 1]; + + array[0][ip - 1] = array[0][i - 1] - t_r; + array[1][ip - 1] = array[1][i - 1] - t_i; + + array[0][i - 1] = array[0][i - 1] + t_r; + array[1][i - 1] = array[1][i - 1] + t_i; + } + t_r = u_r * w_r - w_i * u_i; + u_i = w_r * u_i + w_i * u_r; + u_r = t_r; + } + } + return array; + } + + public static TableDataSet getTableDataSet2( VectorDataSet vds, int windowSize ) { + GeneralFFT fft= GeneralFFT.newDoubleFFT( windowSize ); + + if ( !checkXTagsGrid(vds) ) { + throw new IllegalArgumentException( "xtags don't appear to be gridded" ); + } + + Units xUnits= vds.getXUnits(); + + double[] yt= FFTUtil.getFrequencyDomainTags( 1 / ( vds.getXTagDouble(1,xUnits) - vds.getXTagDouble(0,xUnits) ), windowSize/2 ); + DatumVector yTags= DatumVector.newDatumVector(yt,UnitsUtil.getInverseUnit(xUnits.getOffsetUnits())); + + Units zUnits= vds.getYUnits(); + TableDataSetBuilder tdsb= new TableDataSetBuilder( vds.getXUnits(), yTags.getUnits(), zUnits ); + + int nTableXTags= vds.getXLength() / windowSize; + + VectorDataSet window= FFTUtil.getWindow10PercentEdgeCosine(windowSize); + double[] d= new double[windowSize/2]; + for ( int i=0; i 0.01 && rr < 0.09 ) { + return false; + } + } + return true; + } + } + + /** Creates a new instance of WindowTableDataSet */ + public WindowTableDataSet( VectorDataSet source, int windowSize ) { + this.source= source; + this.windowSize= windowSize; + if ( !checkXTagsGrid(source) ) { + throw new IllegalArgumentException("xTags don't appear to be gridded"); + } + if ( source.getXLength()method, implement the + * FFT using some particular method. + *

    + * Complex data is represented by 2 double values in sequence: the real and imaginary + * parts. Thus, in the default case (i0=0, stride=2), N data points is represented + * by a double array dimensioned to 2*N. To support 2D (and higher) transforms, + * an offset, i0 (where the first element starts) and stride (the distance from the + * real part of one value, to the next: at least 2 for complex values) can be supplied. + * The physical layout in the array data, of the mathematical data d[i] is as follows: + *

    + *    Re(d[i]) = data[i0 + stride*i]
    + *    Im(d[i]) = data[i0 + stride*i+1]
    + *
    + * The transformed data is returned in the original data array in + * wrap-around order. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + */ +public abstract class ComplexDoubleFFT { + + int n; + + /** Create an FFT for transforming n points of complex, double precision data. */ + public ComplexDoubleFFT(int n){ + if (n <= 0) + throw new IllegalArgumentException("The transform length must be >=0 : "+n); + this.n = n; } + + /** Creates an instance of a subclass of ComplexDoubleFFT appropriate for data + * of n elements.*/ + public ComplexDoubleFFT getInstance(int n){ + return new ComplexDoubleFFT_Mixed(n); } + + protected void checkData( ComplexArray.Double data, int i0, int stride){ + if (i0 < 0) + throw new IllegalArgumentException("The offset must be >=0 : "+i0); + if (stride < 1) + throw new IllegalArgumentException("The stride must be >=1 : "+stride); + if (i0+stride*(n-1) >= data.length()) + throw new IllegalArgumentException("The data array is too small for "+n+":"+ + "i0="+i0+" stride="+stride+ + " data.length="+data.length()); } + + /** Compute the Fast Fourier Transform of data leaving the result in data. + * The array data must be dimensioned (at least) 2*n, consisting of alternating + * real and imaginary parts. */ + public void transform( ComplexArray.Double data ) { + transform(data, 0,1); + } + + + /** Compute the Fast Fourier Transform of data leaving the result in data. + * The array data must contain the data points in the following locations: + *
    +     *    Re(d[i]) = data[i0 + stride*i]
    +     *    Im(d[i]) = data[i0 + stride*i+1]
    +     *
    + */ + public abstract void transform( ComplexArray.Double data, int i0, int stride ); + + public void backtransform( ComplexArray.Double data){ + backtransform(data,0,1); + } + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public abstract void backtransform( ComplexArray.Double data, int i0, int stride ); + + /** Return the normalization factor. + * Multiply the elements of the backtransform'ed data to get the normalized inverse.*/ + public double normalization(){ + return 1.0/n; } + + + /** Compute the (nomalized) inverse FFT of data, leaving it in place. + */ + public void inverse( ComplexArray.Double data ) { + backtransform(data,0,2); + + /* normalize inverse fft with 1/n */ + double norm = normalization(); + for (int i = 0; i < n; i++) { + data.setReal( i, data.getReal(i) * norm ); + data.setImag( i, data.getImag(i) * norm ); + } + } +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexDoubleFFT_Mixed.java b/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexDoubleFFT_Mixed.java new file mode 100644 index 000000000..d8ef2b367 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexDoubleFFT_Mixed.java @@ -0,0 +1,951 @@ +package org.das2.math.fft.jnt; + +import org.das2.math.fft.ComplexArray; + +/** Computes FFT's of complex, double precision data of arbitrary length n. + * This class uses the Mixed Radix method; it has special methods to handle + * factors 2, 3, 4, 5, 6 and 7, as well as a general factor. + *

    + * This method appears to be faster than the Radix2 method, when both methods apply, + * but requires extra storage (which ComplexDoubleFFT_Mixed manages itself). + *

    + * See {@link ComplexDoubleFFT ComplexDoubleFFT} for details of data layout. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + * @author Derived from GSL (Gnu Scientific Library) + * @author GSL's FFT Code by Brian Gough bjg@vvv.lanl.gov + * @author Since GSL is released under + * @author GPL, + * @author this package must also be. + */ +public class ComplexDoubleFFT_Mixed extends ComplexDoubleFFT{ + static final double PI = Math.PI; + + public ComplexDoubleFFT_Mixed(int n){ + super(n); + setup_wavetable(n); + } + + public void transform( ComplexArray.Double data, int i0, int stride) { + checkData(data,i0,stride); + transform_internal(data, i0, stride, -1); } + + public void backtransform ( ComplexArray.Double data, int i0, int stride){ + checkData(data,i0,stride); + transform_internal(data, i0, stride, +1); } + + /*______________________________________________________________________ + Setting up the Wavetable */ + + private int factors[]; + // Reversed the last 2 levels of the twiddle array compared to what the C version had. + private double twiddle[][][]; + private int available_factors[]={7, 6, 5, 4, 3, 2}; + + void setup_wavetable(int n){ + + if (n <= 0) + throw new Error("length must be positive integer : "+n); + this.n = n; + + factors = Factorize.factor(n, available_factors); + + double d_theta = -2.0 * PI / ((double) n); + int product = 1; + twiddle = new double[factors.length][][]; + for (int i = 0; i < factors.length; i++) { + int factor = factors[i]; + int product_1 = product; /* product_1 = p_(i-1) */ + product *= factor; + int q = n / product; + + twiddle[i] = new double[q+1][2*(factor-1)]; + double twid[][] = twiddle[i]; + for(int j=1; jmethod, implement the + * FFT using some particular method. + *

    + * Complex data is represented by 2 double values in sequence: the real and imaginary + * parts. Thus, in the default case (i0=0, stride=2), N data points is represented + * by a double array dimensioned to 2*N. To support 2D (and higher) transforms, + * an offset, i0 (where the first element starts) and stride (the distance from the + * real part of one value, to the next: at least 2 for complex values) can be supplied. + * The physical layout in the array data, of the mathematical data d[i] is as follows: + *

    +  *    Re(d[i]) = data[i0 + stride*i]
    +  *    Im(d[i]) = data[i0 + stride*i+1]
    +  *
    + * The transformed data is returned in the original data array in + * wrap-around order. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + */ + +public abstract class ComplexFloatFFT { + + int n; + + /** Create an FFT for transforming n points of Complex, single precision data. */ + public ComplexFloatFFT(int n){ + if (n <= 0) + throw new IllegalArgumentException("The transform length must be >=0 : "+n); + this.n = n; } + + /** Creates an instance of a subclass of ComplexFloatFFT appropriate for data + * of n elements.*/ + public ComplexFloatFFT getInstance(int n){ + return new ComplexFloatFFT_Mixed(n); } + + protected void checkData(ComplexArray.Float data, int i0, int stride){ + if (i0 < 0) + throw new IllegalArgumentException("The offset must be >=0 : "+i0); + if (stride < 1) + throw new IllegalArgumentException("The stride must be >=2 : "+stride); + if (i0+stride*(n-1)+2 > data.length()) + throw new IllegalArgumentException("The data array is too small for "+n+":"+ + "i0="+i0+" stride="+stride+ + " data.length="+data.length()); } + + /** Compute the Fast Fourier Transform of data leaving the result in data. + * The array data must be dimensioned (at least) 2*n, consisting of alternating + * real and imaginary parts. */ + public void transform ( ComplexArray.Float data ) { + transform (data, 0,1); } + + + /** Compute the Fast Fourier Transform of data leaving the result in data. + * The array data must contain the data points in the following locations: + *
    +    *    Re(d[i]) = data[i0 + stride*i]
    +    *    Im(d[i]) = data[i0 + stride*i+1]
    +    *
    + */ + public abstract void transform ( ComplexArray.Float data, int i0, int stride); + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public void backtransform ( ComplexArray.Float data ) { + backtransform(data,0,2); } + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place. + * The frequency domain data must be in wrap-around order, and be stored + * in the following locations: + *
    +    *    Re(D[i]) = data[i0 + stride*i]
    +    *    Im(D[i]) = data[i0 + stride*i+1]
    +    *
    + */ + public abstract void backtransform (ComplexArray.Float data, int i0, int stride); + + /** Return the normalization factor. + * Multiply the elements of the backtransform'ed data to get the normalized inverse.*/ + public float normalization(){ + return 1.0f/((float) n); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place.*/ + public void inverse(ComplexArray.Float data) { + inverse(data,0,2); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place. + * The frequency domain data must be in wrap-around order, and be stored + * in the following locations: + *
    +    *    Re(D[i]) = data[i0 + stride*i]
    +    *    Im(D[i]) = data[i0 + stride*i+1]
    +    *
    + */ + public void inverse (ComplexArray.Float data, int i0, int stride) { + backtransform(data,0,2); + + /* normalize inverse fft with 1/n */ + float norm = normalization(); + for (int i = 0; i < n; i++) { + data.setReal( i, data.getReal(i) * norm ); + data.setImag( i, data.getImag(i) * norm ); + } + } +} diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexFloatFFT_Mixed.java b/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexFloatFFT_Mixed.java new file mode 100644 index 000000000..d30b98d94 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/ComplexFloatFFT_Mixed.java @@ -0,0 +1,960 @@ +package org.das2.math.fft.jnt; + +import org.das2.math.fft.ComplexArray; + +/** Computes FFT's of complex, single precision data of arbitrary length n. + * This class uses the Mixed Radix method; it has special methods to handle + * factors 2, 3, 4, 5, 6 and 7, as well as a general factor. + *

    + * This method appears to be faster than the Radix2 method, when both methods apply, + * but requires extra storage (which ComplexDoubleFFT_Mixed manages itself). + * + *

    + * See {@link ComplexFloatFFT ComplexFloatFFT} for details of data layout. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + * @author Derived from GSL (Gnu Scientific Library) + * @author GSL's FFT Code by Brian Gough bjg@vvv.lanl.gov + * @author Since GSL is released under + * @author GPL, + * @author this package must also be. + */ + +public class ComplexFloatFFT_Mixed extends ComplexFloatFFT{ + static final double PI = (float) Math.PI; + static final int FORWARD = -1; + static final int BACKWARD = +1; + + public ComplexFloatFFT_Mixed(int n){ + super(n); + setup_wavetable(n); + } + + public void transform(ComplexArray.Float data, int i0, int stride) { + checkData(data,i0,stride); + transform_internal(data, i0, stride, FORWARD); } + + public void backtransform(ComplexArray.Float data, int i0, int stride){ + checkData(data,i0,stride); + transform_internal(data, i0, stride, BACKWARD); } + + /*______________________________________________________________________ + Setting up the Wavetable */ + + private int factors[]; + // Reversed the last 2 levels of the twiddle array compared to what the C version had. + private float twiddle[][][]; + private int available_factors[]={7, 6, 5, 4, 3, 2}; + + void setup_wavetable(int n){ + + if (n <= 0) + throw new Error("length must be positive integer : "+n); + this.n = n; + + factors = Factorize.factor(n, available_factors); + + double d_theta = -2.0 * PI / ((double) n); + int product = 1; + twiddle = new float[factors.length][][]; + for (int i = 0; i < factors.length; i++) { + int factor = factors[i]; + int product_1 = product; /* product_1 = p_(i-1) */ + product *= factor; + int q = n / product; + + twiddle[i] = new float[q+1][2*(factor-1)]; + float twid[][] = twiddle[i]; + for(int j=1; jGPL, + * @author this package must also be. +*/ +public class Factorize { + + /** Return the prime factors of n. + * The method first extracts any factors in fromfactors, in order (which + * needn't actually be prime). Remaining factors in increasing order follow. */ + public static int[] factor (int n, int fromfactors[]){ + int factors[] = new int[64]; // Cant be more than 64 factors. + int nf = 0; + int ntest = n; + int factor; + + if (n <= 0) // Error case + throw new Error("Number ("+n+") must be positive integer"); + + /* deal with the preferred factors first */ + for(int i = 0; i < fromfactors.length && ntest != 1; i++){ + factor = fromfactors[i]; + while ((ntest % factor) == 0) { + ntest /= factor; + factors[nf++] = factor; }} + + /* deal with any other even prime factors (there is only one) */ + factor = 2; + while ((ntest % factor) == 0 && (ntest != 1)) { + ntest /= factor; + factors[nf++] = factor; } + + /* deal with any other odd prime factors */ + factor = 3; + while (ntest != 1) { + while ((ntest % factor) != 0) { + factor += 2; } + ntest /= factor; + factors[nf++] = factor; } + + /* check that the factorization is correct */ + int product = 1; + for (int i = 0; i < nf; i++) { + product *= factors[i]; } + if (product != n) + throw new Error("factorization failed for "+n); + + /* Now, make an array of the right length containing the factors... */ + int f[] = new int[nf]; + System.arraycopy(factors,0,f,0,nf); + return f; } + + /** Return the integer log, base 2, of n, or -1 if n is not an integral power of 2.*/ + public static int log2 (int n){ + int log = 0; + + for(int k=1; k < n; k *= 2, log++); + + if (n != (1 << log)) + return -1 ; /* n is not a power of 2 */ + return log; } +} + + + diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT.java b/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT.java new file mode 100644 index 000000000..f937cc64c --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT.java @@ -0,0 +1,84 @@ +package org.das2.math.fft.jnt; + +import org.das2.math.fft.ComplexArray; +/** Abstract Class representing FFT's of real, double precision data. + * Concrete classes are typically named RealDoubleFFT_method, implement the + * FFT using some particular method. + *

    + * The physical layout of the mathematical data d[i] in the array data is as follows: + *

    +  *    d[i] = data[i0 + stride*i]
    +  *
    + * The FFT (D[i]) of real data (d[i]) is complex, but restricted by symmetry: + *
    +  *    D[n-i] = conj(D[i])
    +  *
    + * It turns out that there are still n `independent' values, so the transformation + * can still be carried out in-place. + * However, each Real FFT method tends to leave the real and imaginary parts + * distributed in the data array in its own unique arrangment. + *

    + * You must consult the documentation for the specific classes implementing + * RealDoubleFFT for the details. + * Note, however, that each class's backtransform and inverse methods understand + * thier own unique ordering of the transformed result and can invert it correctly. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + */ + +public abstract class RealDoubleFFT { + int n; + + /** Create an FFT for transforming n points of real, double precision data. */ + public RealDoubleFFT(int n){ + if (n <= 0) + throw new IllegalArgumentException("The transform length must be >=0 : "+n); + this.n = n; } + + protected void checkData(double data[], int i0, int stride){ + if (i0 < 0) + throw new IllegalArgumentException("The offset must be >=0 : "+i0); + if (stride < 1) + throw new IllegalArgumentException("The stride must be >=1 : "+stride); + if (i0+stride*(n-1)+1 > data.length) + throw new IllegalArgumentException("The data array is too small for "+n+":"+ + "i0="+i0+" stride="+stride+ + " data.length="+data.length); } + + /** Compute the Fast Fourier Transform of data leaving the result in data. */ + public void transform ( ComplexArray.Double data) { + transform (data, 0,1); } + + /** Compute the Fast Fourier Transform of data leaving the result in data. */ + public abstract void transform ( ComplexArray.Double data, int i0, int stride); + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public void backtransform (ComplexArray.Double data) { + backtransform(data,0,1); } + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public abstract void backtransform (ComplexArray.Double data, int i0, int stride); + + /** Return the normalization factor. + * Multiply the elements of the backtransform'ed data to get the normalized inverse.*/ + public double normalization(){ + return 1.0/((double) n); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place.*/ + public void inverse(ComplexArray.Double data) { + inverse(data,0,1); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place.*/ + public void inverse (ComplexArray.Double data, int i0, int stride) { + backtransform(data, i0, stride); + /* normalize inverse fft with 1/n */ + double norm = normalization(); + for (int i = 0; i < n; i++) { + data.setReal( i, data.getReal(i) * norm ); + data.setImag( i, data.getImag(i) * norm ); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT_Even.java b/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT_Even.java new file mode 100644 index 000000000..9e8b0c92f --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/RealDoubleFFT_Even.java @@ -0,0 +1,123 @@ +package org.das2.math.fft.jnt; + +import org.das2.math.fft.ComplexArray; +/** Computes FFT's of real, double precision data when n is even, by + * computing complex FFT. + * + * @author Bruce R. Miller bruce.miller@nist.gov + * @author Derived from Numerical Methods. + * @author Contribution of the National Institute of Standards and Technology, + * @author not subject to copyright. + */ + +public class RealDoubleFFT_Even extends RealDoubleFFT { + ComplexDoubleFFT fft; + + /** Create an FFT for transforming n points of real, double precision data. */ + public RealDoubleFFT_Even(int n){ + super(n); + if (true) throw new RuntimeException("Not verified--and it looks bad!"); + if (n%2 != 0) + throw new IllegalArgumentException(n+" is not even"); + fft = new ComplexDoubleFFT_Mixed(n/2); + } + + /** Compute the Fast Fourier Transform of data leaving the result in data. */ + public void transform (ComplexArray.Double data) { + fft.transform(data); + shuffle(data,+1); + } + + /** Return data in wraparound order. + * i0 and stride are used to traverse data; the new array is in + * packed (i0=0, stride=1) format. + * @see wraparound format */ + public ComplexArray.Double toWraparoundOrder(ComplexArray.Double data){ + ComplexArray.Double newdata = ComplexArray.newArray( new double[n], new double[n] ); + int nh = n/2; + newdata.setReal(0,data.getReal(0)); + newdata.setReal(1,0.0); + newdata.setReal(n,data.getReal(1)); + newdata.setImag(n,0.0); + for(int i=1; iwraparound format */ + public double[] toWraparoundOrder(ComplexArray.Double data, int i0, int stride) { + throw new Error("Not Implemented!"); } + + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public void backtransform (ComplexArray.Double data){ + shuffle(data,-1); + fft.backtransform(data); + } + + private void shuffle(ComplexArray.Double data, int sign){ + int nh = n/2; + int nq = n/4; + double c1=0.5, c2 = -0.5*sign; + double theta = sign*Math.PI/nh; + double wtemp = Math.sin(0.5*theta); + double wpr = -2.0*wtemp*wtemp; + double wpi = -Math.sin(theta); + double wr = 1.0+wpr; + double wi = wpi; + for(int i=1; i < nq; i++){ + int i1 = 2*i; + int i3 = n - i1; + double h1r = c1*(data.getReal(i1 )+data.getReal(i3)); + double h1i = c1*(data.getReal(i1+1)-data.getImag(i3)); + double h2r = -c2*(data.getReal(i1+1)+data.getImag(i3)); + double h2i = c2*(data.getReal(i1 )-data.getReal(i3)); + data.setReal(i1 ,h1r+wr*h2r-wi*h2i); + data.setImag(i1,h1i+wr*h2i+wi*h2r); + data.setReal(i3 ,h1r-wr*h2r+wi*h2i); + data.setImag(i3,-h1i+wr*h2i+wi*h2r); + wtemp = wr; + wr += wtemp*wpr-wi*wpi; + wi += wtemp*wpi+wi*wpr; } + double d0 = data.getReal(0); + if (sign == 1){ + data.setReal(0,d0+data.getReal(1)); + data.setReal(1,d0-data.getReal(1)); } + else { + data.setReal(0,c1*(d0+data.getReal(1))); + data.setReal(1,c1*(d0-data.getReal(1))); } + if (n%4==0) + data.setImag(nh, data.getImag(nh) * (-1) ); + } + + /** Compute the Fast Fourier Transform of data leaving the result in data. */ + public void transform (ComplexArray.Double data, int i0, int stride) { + throw new Error("Not Implemented!"); } + + + /** Compute the (unnomalized) inverse FFT of data, leaving it in place.*/ + public void backtransform (ComplexArray.Double data, int i0, int stride){ + throw new Error("Not Implemented!"); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place.*/ + public void inverse (ComplexArray.Double data, int i0, int stride){ + throw new Error("Not Implemented!"); } + + /** Return the normalization factor. + * Multiply the elements of the backtransform'ed data to get the normalized inverse.*/ + public double normalization(){ + return 2.0/((double) n); } + + /** Compute the (nomalized) inverse FFT of data, leaving it in place.*/ + public void inverse (ComplexArray.Double data) { + backtransform(data); + /* normalize inverse fft with 2/n */ + double norm = normalization(); + for (int i = 0; i < n; i++) + data.setReal( i, data.getReal(i) * norm ); + } + +} diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/Refactory.java b/dasCore/src/main/java/org/das2/math/fft/jnt/Refactory.java new file mode 100644 index 000000000..9cb343263 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/Refactory.java @@ -0,0 +1,160 @@ +/* + * Refactory.java + * + * Created on November 29, 2004, 10:58 PM + */ + +package org.das2.math.fft.jnt; + +import java.io.*; +import java.util.regex.*; + +/** + * + * @author Jeremy + */ +public class Refactory { + + // these are the variable names that will be affected + //static String vars="(in|out)"; // used for Complex + static String vars="(newdata|data)"; // used for Real + + /* + * replaces "double data[]" with "ComplexArray.Double data" + */ + public static void replaceComplexArrayDoubleDeclaration( String filename, String outfilename ) throws IOException { + BufferedReader in= new BufferedReader( new FileReader(filename) ); + BufferedWriter out= new BufferedWriter( new FileWriter(outfilename) ); + String line; + + Pattern setPattern= Pattern.compile("(.*)(double\\s*)"+vars+"(\\[\\])"+"(.*)"); + Matcher m; + int iline=0; + while (( line=in.readLine())!=null ) { + iline++; + if ((m= setPattern.matcher(line)).matches() ) { + String newLine= m.group(1)+"ComplexArray.Double "+m.group(3)+m.group(5); + out.write(newLine); + System.err.println(newLine); + out.newLine(); + } else { + out.write(line); + out.newLine(); + } + } + out.close(); + + } + + public static void setArrayImag( String filename, String outfilename ) throws IOException { + BufferedReader in= new BufferedReader( new FileReader(filename) ); + BufferedWriter out= new BufferedWriter( new FileWriter(outfilename) ); + String line; + Pattern setPattern= Pattern.compile("(\\s*)"+vars+"\\[(.*)\\s*\\+\\s*1\\s*\\]\\s*=\\s*(.*)\\s*;(.*)"); + Matcher m; + int iline=0; + while (( line=in.readLine())!=null ) { + iline++; + //if ( iline>836 ) { + // System.out.println(line); + //} + if ((m= setPattern.matcher(line)).matches() ) { + String newLine= m.group(1)+m.group(2)+".setImag("+m.group(3)+","+m.group(4)+");"+m.group(5); + out.write(newLine); + System.err.println(newLine); + out.newLine(); + } else { + out.write(line); + out.newLine(); + } + } + out.close(); + } + + public static void setArrayReal( String filename, String outfilename ) throws IOException { + BufferedReader in= new BufferedReader( new FileReader(filename) ); + BufferedWriter out= new BufferedWriter( new FileWriter(outfilename) ); + String line; + Pattern setPattern= Pattern.compile("(\\s*)"+vars+"\\[(.*)\\s*\\]\\s*=\\s*(.*)\\s*;(.*)"); + Matcher m; + while (( line=in.readLine())!=null ) { + if ((m= setPattern.matcher(line)).matches() ) { + String newLine= m.group(1)+m.group(2)+".setReal("+m.group(3)+","+m.group(4)+");"+m.group(5); + out.write(newLine); + System.err.println(newLine); + out.newLine(); + } else { + out.write(line); + out.newLine(); + } + } + out.close(); + } + + + public static void getArrayImag( String filename, String outfilename ) throws IOException { + BufferedReader in= new BufferedReader( new FileReader(filename) ); + BufferedWriter out= new BufferedWriter( new FileWriter(outfilename) ); + String line; + Pattern setPattern= Pattern.compile("(.*)"+vars+"\\[(.*)\\s*\\+\\s*1\\s*\\](.*)"); + Matcher m; + while (( line=in.readLine())!=null ) { + if ((m= setPattern.matcher(line)).matches() ) { + String newLine= m.group(1)+m.group(2)+".getImag("+m.group(3)+")"+m.group(4); + out.write(newLine); + System.err.println(newLine); + out.newLine(); + } else { + out.write(line); + out.newLine(); + } + } + out.close(); + } + + public static void getArrayReal( String filename, String outfilename ) throws IOException { + BufferedReader in= new BufferedReader( new FileReader(filename) ); + BufferedWriter out= new BufferedWriter( new FileWriter(outfilename) ); + String line; + Pattern setPattern= Pattern.compile("(.*)"+vars+"\\[(.*)\\s*\\](.*)"); + Matcher m; + while (( line=in.readLine())!=null ) { + if ((m= setPattern.matcher(line)).matches() ) { + String newLine= m.group(1)+m.group(2)+".getReal("+m.group(3)+")"+m.group(4); + out.write(newLine); + System.err.println(newLine); + out.newLine(); + } else { + out.write(line); + out.newLine(); + } + } + out.close(); + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + //String filename=Refactory.class.getResource("RealDoubleFFT.java").getFile(); + String filename="C:\\Documents and Settings\\Jeremy\\Desktop\\das2Brief\\working\\edu\\uiowa\\physics\\pw\\das\\math\\fft\\jnt\\RealDoubleFFT_Even.java"; + String outfilename1= filename+".1.java"; + String outfilename2= filename+".2.java"; + replaceComplexArrayDoubleDeclaration(filename,outfilename1); + replaceComplexArrayDoubleDeclaration(outfilename1,outfilename2); + setArrayImag(outfilename2,outfilename1); + setArrayImag(outfilename1,outfilename2); + setArrayImag(outfilename2,outfilename1); + setArrayReal(outfilename1,outfilename2); + setArrayReal(outfilename2,outfilename1); + getArrayImag(outfilename2,outfilename1); + getArrayImag(outfilename1,outfilename2); + getArrayReal(outfilename1,outfilename2); + getArrayReal(outfilename2,outfilename1); + getArrayReal(outfilename1,filename+".java"); + System.out.println("result is in "+filename+".java"); + System.out.flush(); + + } + +} diff --git a/dasCore/src/main/java/org/das2/math/fft/jnt/package.html b/dasCore/src/main/java/org/das2/math/fft/jnt/package.html new file mode 100644 index 000000000..4cc3fc616 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/jnt/package.html @@ -0,0 +1,4 @@ + + This is the NIST FFT package, modified to use the ComplexArray interface. Refactory is +the java code that was used to transform the code. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/math/fft/package.html b/dasCore/src/main/java/org/das2/math/fft/package.html new file mode 100644 index 000000000..0b963bf21 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/fft/package.html @@ -0,0 +1,7 @@ + + Classes for performing generalized FFT transformations. These codes are based on the + jnt.FFT package from NIST, with the interface changed so that data copies can be avoided and + to improve useability. (The NIST package had implicit specifications about how to load arrays, and + instead we use the ComplexArray interface.) Also classes are provided for common fft operations, + such as transforming a waveform into a spectrogram. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/math/matrix/ArrayMatrix.java b/dasCore/src/main/java/org/das2/math/matrix/ArrayMatrix.java new file mode 100644 index 000000000..e40585b12 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/matrix/ArrayMatrix.java @@ -0,0 +1,96 @@ +/* + * ArrayMatrix.java + * + * Created on March 7, 2005, 6:48 PM + */ + +package org.das2.math.matrix; + +/** + * + * @author eewest + */ +public class ArrayMatrix extends Matrix { + + private double[] array; + + public ArrayMatrix(int rows, int columns) { + super(rows, columns); + this.array = new double[rows * columns]; + } + + public ArrayMatrix(double[] array, int rows, int columns) { + super(rows, columns); + this.array = array; + if (rows * columns != array.length) { + throw new IllegalArgumentException("Array must be (rows * columns) in length"); + } + } + + public ArrayMatrix(Matrix m) { + super(m.rowCount(), m.columnCount()); + if (m instanceof ArrayMatrix) { + array = (double[])((ArrayMatrix)m).array.clone(); + } + else { + array = new double[nRow * nCol]; + super.copy(m); + } + } + + public void copy(Matrix m) { + if (nRow != m.rowCount() || nCol != m.columnCount()) { + throw new IllegalArgumentException(); + } + + if (m instanceof ArrayMatrix) { + System.arraycopy(((ArrayMatrix)m).array, 0, array, 0, array.length); + } + else { + super.copy(m); + } + } + + public double get(int row, int col) { + return array[row * nCol + col]; + } + + public void rowTimes(int row, double s) { + for (int iCol = 0; iCol < nCol; iCol++) { + array[iCol + row * nCol] *= s; + } + } + + public void rowTimesAddTo(int srcRow, double s, int dstRow) { + double[] tmp; + + if (srcRow == dstRow) { + rowTimes(srcRow, s + 1); + } + + for (int iCol = 0; iCol < nCol; iCol++) { + array[iCol + dstRow * nCol] += array[iCol + srcRow * nCol] * s; + } + } + + public void set(int row, int col, double d) { + array[col + row * nCol] = d; + } + + public void swapRows(int row1, int row2) { + double[] tmp; + int off1, off2; + + if (row1 == row2) { + return; + } + + tmp = new double[nCol]; + off1 = row1 * nCol; + off2 = row2 * nCol; + System.arraycopy(array, off1, tmp, 0, nCol); + System.arraycopy(array, off2, array, off1, nCol); + System.arraycopy(tmp, 0, array, off2, nCol); + } + +} diff --git a/dasCore/src/main/java/org/das2/math/matrix/CompositeMatrix.java b/dasCore/src/main/java/org/das2/math/matrix/CompositeMatrix.java new file mode 100644 index 000000000..f1bb297fc --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/matrix/CompositeMatrix.java @@ -0,0 +1,53 @@ +/* + * CompositeMatrix.java + * + * Created on March 7, 2005, 8:13 PM + */ + +package org.das2.math.matrix; + +/** All of the elementary row and column operations are applied to both + * underlying matrices. Reads are done from the first matrix. Writes + * are not allowed (except those that are side effects of elementary + * matrix operations). + * + * @author Edward West + */ +public class CompositeMatrix extends Matrix { + + private Matrix m1; + private Matrix m2; + + public CompositeMatrix(Matrix m1, Matrix m2) { + super(m1.rowCount(), m1.columnCount()); + if (m1.rowCount() != m2.rowCount() && m1.columnCount() != m2.columnCount()) { + throw new IllegalArgumentException("m1 and m2 must have the same number of rows and columns"); + } + this.m1 = m1; + this.m2 = m2; + } + + public double get(int row, int col) { + return m1.get(row, col); + } + + public void rowTimes(int row, double s) { + m1.rowTimes(row, s); + m2.rowTimes(row, s); + } + + public void rowTimesAddTo(int srcRow, double s, int dstRow) { + m1.rowTimesAddTo(srcRow, s, dstRow); + m2.rowTimesAddTo(srcRow, s, dstRow); + } + + public void set(int row, int col, double d) { + throw new UnsupportedOperationException("Setting values not supported"); + } + + public void swapRows(int row1, int row2) { + m1.swapRows(row1, row2); + m2.swapRows(row1, row2); + } + +} diff --git a/dasCore/src/main/java/org/das2/math/matrix/Matrix.java b/dasCore/src/main/java/org/das2/math/matrix/Matrix.java new file mode 100644 index 000000000..7b75d2ed0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/matrix/Matrix.java @@ -0,0 +1,63 @@ +/* + * Matrix.java + * + * Created on March 7, 2005, 6:33 PM + */ + +package org.das2.math.matrix; + +import java.text.DecimalFormat; + +/** + * + * @author eewest + */ +public abstract class Matrix { + + protected final int nRow; + + protected final int nCol; + + protected Matrix(int rows, int columns) { + nRow = rows; + nCol = columns; + } + + public int rowCount() { + return nRow; + } + + public int columnCount() { + return nCol; + } + + public void copy(Matrix m) { + for (int iRow = 0; iRow < nRow; iRow++) { + for (int iCol = 0; iCol < nCol; iCol++) { + set(iRow, iCol, m.get(iRow, iCol)); + } + } + } + + public abstract double get(int row, int col); + + public abstract void set(int row, int col, double d); + + public abstract void swapRows(int row1, int row2); + + public abstract void rowTimes(int row, double s); + + public abstract void rowTimesAddTo(int srcRow, double s, int dstRow); + + public String toString() { + StringBuffer buf= new StringBuffer(); + buf.append("\n"); + for ( int i=0; i + Matrix class and inversion routine. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/math/package.html b/dasCore/src/main/java/org/das2/math/package.html new file mode 100644 index 000000000..9271563ba --- /dev/null +++ b/dasCore/src/main/java/org/das2/math/package.html @@ -0,0 +1,8 @@ + +

    + Provides implementations of complex mathematical functions, such as +FFT, Poisson distribution generator, contouring, interpolation, fitting, etc. +Note some of these are based on open source packages and due credit, copyright, +and license is given within. +

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/persistence/DatumPersistenceDelegate.java b/dasCore/src/main/java/org/das2/persistence/DatumPersistenceDelegate.java new file mode 100644 index 000000000..43b9a7bd3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/persistence/DatumPersistenceDelegate.java @@ -0,0 +1,57 @@ +/* + * DatumRangePersistenceDelegate.java + * + * Created on August 8, 2007, 10:43 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.persistence; + +import org.das2.datum.Datum; +import org.das2.datum.Units; +import java.beans.DefaultPersistenceDelegate; +import java.beans.Encoder; +import java.beans.Expression; + +/** + * + * @author jbf + */ +public class DatumPersistenceDelegate extends DefaultPersistenceDelegate { + + /** Creates a new instance of DatumRangePersistenceDelegate */ + public DatumPersistenceDelegate() { + } + + protected Expression instantiate(Object oldInstance, Encoder out) { + Expression retValue; + + Datum field= (Datum)oldInstance; + Units u= field.getUnits(); + + return new Expression( field, this.getClass(), "newDatum", new Object[] { field.doubleValue(u), u.toString() } ); + + } + + public static Datum newDatum( double val, String units ) { + Units u= Units.lookupUnits(units); + return u.createDatum( val ); + } + + protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { + super.initialize(type, oldInstance, newInstance, out); + } + + public void writeObject(Object oldInstance, Encoder out) { + super.writeObject(oldInstance, out); + } + + protected boolean mutatesTo(Object oldInstance, Object newInstance) { + boolean retValue; + + retValue = super.mutatesTo(oldInstance, newInstance); + return retValue; + } +} diff --git a/dasCore/src/main/java/org/das2/persistence/DatumRangePersistenceDelegate.java b/dasCore/src/main/java/org/das2/persistence/DatumRangePersistenceDelegate.java new file mode 100644 index 000000000..c115431df --- /dev/null +++ b/dasCore/src/main/java/org/das2/persistence/DatumRangePersistenceDelegate.java @@ -0,0 +1,46 @@ +/* + * DatumRangePersistenceDelegate.java + * + * Created on August 8, 2007, 10:43 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.persistence; + +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import java.beans.DefaultPersistenceDelegate; +import java.beans.Encoder; +import java.beans.Expression; + +/** + * + * @author jbf + */ +public class DatumRangePersistenceDelegate extends DefaultPersistenceDelegate { + + /** Creates a new instance of DatumRangePersistenceDelegate */ + public DatumRangePersistenceDelegate() { + } + + protected Expression instantiate(Object oldInstance, Encoder out) { + Expression retValue; + + DatumRange field= (DatumRange)oldInstance; + Units u= field.getUnits(); + + return new Expression( field, this.getClass(), "newDatumRange", new Object[] { field.min().doubleValue(u), field.max().doubleValue(u), u.toString() } ); + + } + + public static DatumRange newDatumRange( double min, double max, String units ) { + Units u= Units.lookupUnits(units); + return DatumRange.newDatumRange( min, max, u ); + } + + protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { + super.initialize(type, oldInstance, newInstance, out); + } +} diff --git a/dasCore/src/main/java/org/das2/persistence/StatePersistence.java b/dasCore/src/main/java/org/das2/persistence/StatePersistence.java new file mode 100644 index 000000000..e3b55e4f9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/persistence/StatePersistence.java @@ -0,0 +1,54 @@ +/* + * StatePersistence.java + * + * Created on August 8, 2007, 10:47 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.persistence; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import java.beans.ExceptionListener; +import java.beans.XMLDecoder; +import java.beans.XMLEncoder; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Provides object serialization, using delegates to handle das2 immutable objects. + * @author jbf + */ +public class StatePersistence { + + private StatePersistence() { + } + + public static void saveState( OutputStream out, Object state ) throws IOException { + XMLEncoder e = new XMLEncoder( + new BufferedOutputStream( + out ) ); + + e.setPersistenceDelegate( DatumRange.class, new DatumRangePersistenceDelegate() ); + e.setPersistenceDelegate( Units.class, new UnitsPersistenceDelegate() ); + e.setPersistenceDelegate( Datum.class, new DatumPersistenceDelegate() ); + + e.setExceptionListener( new ExceptionListener() { + public void exceptionThrown(Exception e) { + e.printStackTrace(); + } + } ); + e.writeObject(state); + e.close(); + } + + public static Object restoreState( InputStream in ) throws IOException { + XMLDecoder decode= new XMLDecoder( in ); + return decode.readObject(); + } +} diff --git a/dasCore/src/main/java/org/das2/persistence/UnitsPersistenceDelegate.java b/dasCore/src/main/java/org/das2/persistence/UnitsPersistenceDelegate.java new file mode 100644 index 000000000..06670b947 --- /dev/null +++ b/dasCore/src/main/java/org/das2/persistence/UnitsPersistenceDelegate.java @@ -0,0 +1,52 @@ +/* + * UnitsPersistenceDelegate.java + * + * Created on August 8, 2007, 11:04 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.persistence; + +import org.das2.datum.Units; +import java.beans.DefaultPersistenceDelegate; +import java.beans.Encoder; +import java.beans.Expression; + +/** + * + * @author jbf + */ +public class UnitsPersistenceDelegate extends DefaultPersistenceDelegate { + + /** Creates a new instance of UnitsPersistenceDelegate */ + public UnitsPersistenceDelegate() { + + } + + protected Expression instantiate(Object oldInstance, Encoder out) { + Expression retValue; + + Units u= (Units)oldInstance; + + return new Expression( u, u.getClass(), "getByName", new Object[] { u.toString() } ); + + } + + protected void initialize(Class type, Object oldInstance, Object newInstance, Encoder out) { + super.initialize(type, oldInstance, newInstance, out); + } + + public void writeObject(Object oldInstance, Encoder out) { + super.writeObject(oldInstance, out); + } + + protected boolean mutatesTo(Object oldInstance, Object newInstance) { + boolean retValue; + + retValue = super.mutatesTo(oldInstance, newInstance); + return retValue; + } + +} diff --git a/dasCore/src/main/java/org/das2/pw/ClusterSpacecraft.java b/dasCore/src/main/java/org/das2/pw/ClusterSpacecraft.java new file mode 100644 index 000000000..03a04f941 --- /dev/null +++ b/dasCore/src/main/java/org/das2/pw/ClusterSpacecraft.java @@ -0,0 +1,59 @@ +package org.das2.pw; + +import java.awt.*; + +public class ClusterSpacecraft extends Spacecraft.GroupSpacecraft { + + protected ClusterSpacecraft(String id, int number) { + super( "Cluster2", id, number ); + } + + /** + * returns the Esa Number 1,2,3 or 4 for the Cluster Spacecraft + */ + public int getEsaNumber() { + return number; + } + + + /** + * returns the Cluster Spacecraft for the Esa Number 1,2,3 or 4 + */ + public static ClusterSpacecraft getByEsaNumber(int number) { + return (ClusterSpacecraft)getByNumber("Cluster2",number); + } + + /** + * returns the Cluster Spacecraft for the wideband status byteEsa 4,5,6,7 + */ + public static ClusterSpacecraft getByWbdStatusByte( int number ) { + if (true) throw new IllegalArgumentException("looks like there's a bug here"); + final int[] esaFromStatus= new int[] { 0, 0, 0, 0, 2, 3, 4, 1 }; + return getByEsaNumber( esaFromStatus[number] ); + } + + public int getInstrumentNumber() { + final int instFromEsa[] = new int[] { 0, 9, 6, 7, 8 }; + return instFromEsa[ getEsaNumber() ]; + } + + public int getDsnNumber() { + final int dsnFromEsa[] = new int[] { 0, 183, 185, 194, 196 }; + return dsnFromEsa[ getEsaNumber() ]; + } + + public String getName() { + final String[] nameFromEsa= new String[] { "", "Rumba", "Salsa", "Samba", "Tango" }; + return nameFromEsa[ getEsaNumber() ]; + } + + public Color getColor() { + final Color[] colorFromEsa= new Color[] { null, Color.BLACK, Color.RED, Color.GREEN, Color.MAGENTA }; + return colorFromEsa[ getEsaNumber() ]; + } + + public String toString() { + return "Cluster " + getEsaNumber(); + } +} + diff --git a/dasCore/src/main/java/org/das2/pw/Spacecraft.java b/dasCore/src/main/java/org/das2/pw/Spacecraft.java new file mode 100644 index 000000000..6e6ea9b39 --- /dev/null +++ b/dasCore/src/main/java/org/das2/pw/Spacecraft.java @@ -0,0 +1,70 @@ +/* + * Spacecraft.java + * + * Created on June 18, 2004, 9:59 AM + */ + +package org.das2.pw; + +import java.util.*; + +/** + * + * @author Jeremy + */ +public class Spacecraft { + + String id; + + /* useful for a set of identical spacecraft. e.g. voyager, cluster */ + protected abstract static class GroupSpacecraft extends Spacecraft { + int number; + String group; + static HashMap groups; + protected GroupSpacecraft( String group, String id, int number ) { + super(id); + this.group= group; + this.number= number; + if ( groups==null ) { + groups= new HashMap(); + } + if ( groups.containsKey(group) ) { + HashMap x= (HashMap)groups.get(group); + x.put( new Integer(number),this ); + } else { + HashMap x= new HashMap(); + x.put(new Integer(number),this); + groups.put( group, x ); + } + } + protected static Spacecraft getByNumber( String group, int number ) { + if ( groups.containsKey(group) ) { + HashMap x= (HashMap)(groups.get(group)); + return (Spacecraft)x.get(new Integer(number)); + } else { + throw new IllegalArgumentException( "No such group: "+group ); + } + } + protected int getGroupNumber() { + return this.number; + } + + } + + private Spacecraft( String id ) { + this.id= id; + } + + public static Spacecraft voyager1= new Spacecraft( "Voyager 1" ); + public static Spacecraft voyager2= new Spacecraft( "Voyager 2" ); + public static ClusterSpacecraft clusterRumba= new ClusterSpacecraft( "Rumba", 1 ); + public static ClusterSpacecraft clusterSalsa= new ClusterSpacecraft( "Salsa", 2 ); + public static ClusterSpacecraft clusterSamba= new ClusterSpacecraft( "Samba", 3 ); + public static ClusterSpacecraft clusterTango= new ClusterSpacecraft( "Tango", 4 ); + + public static void main( String[]args ) { + System.out.println( ClusterSpacecraft.getByEsaNumber(2) ); + System.out.println( clusterSalsa ); + } + +} diff --git a/dasCore/src/main/java/org/das2/pw/package.html b/dasCore/src/main/java/org/das2/pw/package.html new file mode 100644 index 000000000..1ed7ff3d4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/pw/package.html @@ -0,0 +1,6 @@ + +

    + Classes useful for Plasma-Wave group applications. This should be moved to a + separate library. +

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/reader/BadQueryException.java b/dasCore/src/main/java/org/das2/reader/BadQueryException.java new file mode 100644 index 000000000..1e7ff61ca --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/BadQueryException.java @@ -0,0 +1,24 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +/** Readers throw this type of exception when the set of selectors does not sufficiently + * nail down a dataset to return. Usually a request validator will catch bad queries + * but, this condition could conceievably still arise due to a miss configured server. + * + * @author cwp + */ +public class BadQueryException extends Exception { + + public BadQueryException(String str){ + super(str); + } + + public BadQueryException(String str, Throwable ex){ + super(str, ex); + } + +} diff --git a/dasCore/src/main/java/org/das2/reader/Constraint.java b/dasCore/src/main/java/org/das2/reader/Constraint.java new file mode 100644 index 000000000..2b1041d13 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/Constraint.java @@ -0,0 +1,109 @@ +package org.das2.reader; + +import java.text.ParseException; +import org.das2.datum.CalendarTime; + +/** + * Inner class to hold a value plus a comparison operator + */ +public final class Constraint{ + + /** Defines a comparison type, should probably be moved out to some other class */ + public static enum Op {EQ, NE, LT, GT, LE, GE; + @Override + public String toString(){ + return "."+name()+"."; + } + + static public Op fromString(String sStr){ + if(sStr.equalsIgnoreCase(".eq.")) return EQ; + if(sStr.equalsIgnoreCase(".ne.")) return NE; + if(sStr.equalsIgnoreCase(".lt.")) return LT; + if(sStr.equalsIgnoreCase(".end.")) return LT; + if(sStr.equalsIgnoreCase(".gt.")) return GT; + if(sStr.equalsIgnoreCase(".ge.")) return GE; + if(sStr.equalsIgnoreCase(".beg.")) return GE; + if(sStr.equalsIgnoreCase(".le.")) return LE; + throw new IllegalArgumentException("Unknown comparison string '"+sStr+"'"); + } + }; + + /** Defines the value format for the selector */ + public static enum Format {BOOLEAN, INTEGER, REAL, STRING, TIMEPOINT}; + + // Basic properties + private Op m_op; + private String m_sVal; + private Format m_fmt; + // Place to park conversions so they don't have to be redone over and over + private boolean m_bVal; + private int m_nVal; + private double m_dVal; + private CalendarTime m_ctVal; + + public Constraint(Op op, Format fmt, String sVal) + throws ParseException{ + + if(sVal == null) + throw new NullPointerException("Constraint value must not be null."); + m_op = op; + m_fmt = fmt; + m_sVal = sVal; + switch(m_fmt){ + case BOOLEAN: + if(sVal.equalsIgnoreCase("false") || sVal.equals("0")) + m_bVal = false; + else + m_bVal = true; + break; + case INTEGER: + m_nVal = Integer.parseInt(sVal); + break; + case REAL: + m_dVal = Double.parseDouble(sVal); + break; + case TIMEPOINT: + m_ctVal = new CalendarTime(sVal); + break; + } + } + + public Format getFormat(){ + return m_fmt; + } + + public Op getOp(){ + return m_op; + } + + public String getOpStr(){ + return m_op.toString(); + } + + public String getValue(){ return m_sVal; } + + public int getIntValue(){ + if(m_fmt != Format.TIMEPOINT) + throw new UnsupportedOperationException("Constraint format is not INTEGER"); + return m_nVal; + } + + public double getRealValue(){ + if(m_fmt != Format.REAL) + throw new UnsupportedOperationException("Constraint format is not REAL"); + return m_dVal; + } + + public boolean getBoolValue(){ + if(m_fmt != Format.BOOLEAN) + throw new UnsupportedOperationException("Constraint format is not BOOLEAN"); + return m_bVal; + } + + public CalendarTime getTimeValue(){ + if(m_fmt != Format.TIMEPOINT) + throw new UnsupportedOperationException("Constraint format is not DATETIME"); + + return m_ctVal; + } +} diff --git a/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java b/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java new file mode 100644 index 000000000..e7ebd7f30 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java @@ -0,0 +1,76 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.reader; + +import java.io.IOException; +import java.net.URL; +import java.util.EnumMap; +import java.util.Map; +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import org.xml.sax.SAXException; + +/** A validator for all Das2 client-server and server-reader XML messages + * + * @author cwp + */ +public class Das2MsgValidator { + public static enum MsgType {ROOT, PEERS, LIST, DESCRIBE, LIST_LEVEL}; + + private Map m_dValidators; + + public Das2MsgValidator(){ + m_dValidators = new EnumMap(MsgType.class); + m_dValidators.put(MsgType.ROOT, null); + m_dValidators.put(MsgType.PEERS, null); + m_dValidators.put(MsgType.LIST, null); + m_dValidators.put(MsgType.DESCRIBE, null); + m_dValidators.put(MsgType.LIST_LEVEL, null); + } + + public void validate(MsgType type, Source src) throws SAXException, IOException{ + synchronized(this){ + if(m_dValidators.get(type) == null){ + SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + URL url = null; + + switch(type){ + case ROOT: + throw new UnsupportedOperationException("Schema for message type "+ + type.toString()+" is not yet implemented"); + case PEERS: + throw new UnsupportedOperationException("Schema for message type "+ + type.toString()+" is not yet implemented"); + case LIST: + throw new UnsupportedOperationException("Schema for message type "+ + type.toString()+" is not yet implemented"); + case DESCRIBE: + url = Das2MsgValidator.class.getResource("/schema/das_dsid-2.2.xsd"); + break; + case LIST_LEVEL: + throw new UnsupportedOperationException("Schema for message type "+ + type.toString()+" is not yet implemented"); + default: + throw new IllegalArgumentException("Operation "+type.toString()+ + " is unknown."); + } + Schema schema = null; + try{ + schema = sf.newSchema(url); + } + catch(SAXException ex){ + throw new RuntimeException(ex); + } + m_dValidators.put(type, schema.newValidator()); + } + } + + m_dValidators.get(type).validate(src); + } +} diff --git a/dasCore/src/main/java/org/das2/reader/DasHdrBuf.java b/dasCore/src/main/java/org/das2/reader/DasHdrBuf.java new file mode 100644 index 000000000..8d1fbd922 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/DasHdrBuf.java @@ -0,0 +1,63 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** Tiny class to count bytes in a header packet and then write it to a stream + * + * @author cwp + */ +public final class DasHdrBuf { + private int m_nPktId; + private ByteArrayOutputStream m_buf; + + private static final Charset m_csUtf8 = Charset.forName("UTF-8"); + private static final Charset m_csAscii = Charset.forName("US-ASCII"); + + /** Make a new buffer for QStream/Das2 header data + * + * @param nPktId The ID for the packets this header describes, must be a number + * between 1 and 99. + * + */ + public DasHdrBuf(int nPktId){ + if((nPktId < 0 )||(nPktId > 99)){ + throw new IllegalArgumentException("Illegal packet id "+nPktId+". Legal "+ + "das/qstream packet IDs range from 1 and 99, inclusive."); + } + m_nPktId = nPktId; + m_buf = new ByteArrayOutputStream(); + add("\n"); + } + + /** Add more text data to the header. + * + * @param sTxt The text to add, will be encoded as UTF-8 for storage + */ + public void add(String sTxt){ + byte[] u1Tmp = sTxt.getBytes(m_csUtf8); + m_buf.write(u1Tmp, 0, u1Tmp.length); + } + + /** Send a completed header out the door. + * This has the side effect of clearing the buffer for re-use. + * + * @param fOut Any output stream you like + */ + public void send(OutputStream fOut) throws IOException{ + byte[] u1Tmp = String.format("[%02d]%06d", m_nPktId, m_buf.size()).getBytes(m_csAscii); + assert u1Tmp.length == 10; + fOut.write(u1Tmp); + m_buf.writeTo(fOut); + fOut.flush(); + m_buf = new ByteArrayOutputStream(); + add("\n"); + } +} diff --git a/dasCore/src/main/java/org/das2/reader/DasPktBuf.java b/dasCore/src/main/java/org/das2/reader/DasPktBuf.java new file mode 100644 index 000000000..81ca372d1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/DasPktBuf.java @@ -0,0 +1,186 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.LongBuffer; +import java.nio.charset.Charset; + +/** Tiny class to count packets in a Das2 or QStream and then write it to a stream + * + * @author cwp + */ +public final class DasPktBuf { + private int m_nPktId; + private OutputFormat m_fmt; + private ByteArrayOutputStream m_buf; + + private static final Charset m_csAscii = Charset.forName("US-ASCII"); + + /** Make a new buffer for Das2/QStream packet data + * This version does not prepend lengths to the packet ID tags. + * + * @param nPktId The ID for this packets. This must match an ID previously sent for + * one of the header packets and so must be a number between 1 and 99. + */ + public DasPktBuf(int nPktId){ + this(nPktId, OutputFormat.DAS2); + } + + /** Make a new buffer for Das2/QStream/Das3 packet data + * + * @param nPktId The ID for this packets. This must match an ID previously sent for + * one of the header packets and so must be a number between 1 and 99. + * + * @param fmt The format of the output data. Das3 formats expect lengths on all + * packets + */ + public DasPktBuf(int nPktId, OutputFormat fmt){ + if((nPktId < 0 )||(nPktId > 99)){ + throw new IllegalArgumentException("Illegal packet id "+nPktId+". Legal "+ + "das/qstream packet IDs range from 1 and 99, inclusive."); + } + m_nPktId = nPktId; + m_buf = new ByteArrayOutputStream(); + m_fmt = fmt; + } + + /** Add generic bytes to the packet + * + * @param bytes The bytes to add + */ + public void add(byte[] bytes){ + m_buf.write(bytes, 0, bytes.length); + } + + /** Add an array of floats to the packet with selectable byte order. + * @param lFloats the floats to add to the packet + * @param bo define whether bytes should be sent in big-endian or little endian + * format. Warning: It's up to you to make sure that the byte order + * matches the value in the 'byte_order' attribute for the stream header. + */ + public void addFloats(float[] lFloats, ByteOrder bo){ + + if((bo == ByteOrder.LITTLE_ENDIAN)&&(m_fmt == OutputFormat.DAS1)) + throw new IllegalArgumentException("DAS1 streams only support big endian values"); + + //Successfully do nothing + if(lFloats.length == 0) return; + + byte[] lBytes = new byte[lFloats.length * 4]; + ByteBuffer bbTmp = ByteBuffer.wrap( lBytes ); + bbTmp.order(bo); + FloatBuffer fbTmp = bbTmp.asFloatBuffer(); + fbTmp.put(lFloats); + m_buf.write(lBytes, 0, lBytes.length); + } + + /** Add an array of floats to the packet as ASCII values with a format specifier. + * + * NOTE: This function adds a space after every float except the last one. + * this is to allow the caller to add a new line character, if desired, to the + * output after the last value in a packet. + * + * @param lFloats the floats to add to the packet + * @param sFmt the format string to use for each float, see documentation for the + * method String.format in the standard platform library. + */ + public void addFloats(float[] lFloats, String sFmt){ + if(m_fmt == OutputFormat.DAS1) + throw new IllegalStateException("DAS1 Streams only support binary values"); + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < lFloats.length; i++){ + sb.append( String.format(sFmt, lFloats[i]) ); + if(i != lFloats.length - 1) + sb.append( " "); + } + + byte[] lBytes = sb.toString().getBytes(m_csAscii); + m_buf.write(lBytes, 0, lBytes.length); + } + + /** Add a single float to the packet with selectable byte order + * + * @param lDoubles the value to add to the packet + * @param bo define whether bytes should be sent in big-endian or little endian + * format. Warning: It's up to you to make sure that the byte order + * matches the value in the 'byte_order' attribute for the stream header. + */ + public void addDoubles(double[] lDoubles, ByteOrder bo){ + if((bo == ByteOrder.LITTLE_ENDIAN)&&(m_fmt == OutputFormat.DAS1)) + throw new IllegalArgumentException("DAS1 streams only support big endian values"); + + if(m_fmt == OutputFormat.DAS1) + throw new IllegalStateException("DAS1 streams do not support double precision values"); + + //Successfully do nothing + if(lDoubles.length == 0) return; + + byte[] lBytes = new byte[lDoubles.length * 8]; + ByteBuffer bbTmp = ByteBuffer.wrap( lBytes ); + bbTmp.order(bo); + DoubleBuffer dbTmp = bbTmp.asDoubleBuffer(); + dbTmp.put(lDoubles); + m_buf.write(lBytes, 0, lBytes.length); + + } + + public void addLongs(long[] lLongs, ByteOrder bo){ + if((bo == ByteOrder.LITTLE_ENDIAN)&&(m_fmt == OutputFormat.DAS1)) + throw new IllegalArgumentException("DAS1 streams only support big endian values"); + + if(m_fmt != OutputFormat.QSTREAM) + throw new IllegalStateException("Only QSTREAM format supports long integers."); + + //Successfully do nothing + if(lLongs.length == 0) return; + + byte[] lBytes = new byte[lLongs.length * 8]; + ByteBuffer bbTmp = ByteBuffer.wrap( lBytes ); + bbTmp.order(bo); + LongBuffer lbTmp = bbTmp.asLongBuffer(); + lbTmp.put(lLongs); + m_buf.write(lBytes, 0, lBytes.length); + } + + /** Encode a string in US-ASCII format, and add it to the output buffer + * + * @param s The string to encode + */ + public void addAscii(String s){ + byte[] lBytes = s.getBytes(m_csAscii); + m_buf.write(lBytes, 0, lBytes.length); + } + + /** Kick the encoded packet data onto the stream. + * @param fOut Where to write the packet + * @throws IOException + */ + public void send(OutputStream fOut) throws IOException{ + byte[] u1Tmp; + switch(m_fmt){ + case DAS3: + u1Tmp = String.format("|%02d|%06d", m_nPktId, m_buf.size()).getBytes(m_csAscii); + assert u1Tmp.length == 10; + break; + default: + u1Tmp = String.format(":%02d:", m_nPktId).getBytes(m_csAscii); + assert u1Tmp.length == 4; + } + fOut.write(u1Tmp); + m_buf.writeTo(fOut); + m_buf = new ByteArrayOutputStream(); + } + + +} diff --git a/dasCore/src/main/java/org/das2/reader/NoDataException.java b/dasCore/src/main/java/org/das2/reader/NoDataException.java new file mode 100644 index 000000000..5824db6ca --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/NoDataException.java @@ -0,0 +1,16 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +/** + * + * @author cwp + */ +public class NoDataException extends Exception { + public NoDataException(String str){ + super(str); + } +} diff --git a/dasCore/src/main/java/org/das2/reader/OutputFormat.java b/dasCore/src/main/java/org/das2/reader/OutputFormat.java new file mode 100644 index 000000000..7a55fd120 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/OutputFormat.java @@ -0,0 +1,13 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +/** Output format for a reader. + * This is used in the ReaderInterface definition. + * @author cwp + */ +public enum OutputFormat { DAS1, DAS2, QSTREAM, DAS3} + diff --git a/dasCore/src/main/java/org/das2/reader/QueryEditor.java b/dasCore/src/main/java/org/das2/reader/QueryEditor.java new file mode 100644 index 000000000..711caf919 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/QueryEditor.java @@ -0,0 +1,53 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +import java.awt.Dimension; +import java.awt.Rectangle; +import javax.swing.JComponent; +import javax.swing.Scrollable; + +/** + * + * @author cwp + */ +public class QueryEditor extends JComponent implements Scrollable { + + @Override + public Dimension getPreferredScrollableViewportSize(){ + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction){ + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction){ + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean getScrollableTracksViewportWidth(){ + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean getScrollableTracksViewportHeight(){ + throw new UnsupportedOperationException("Not supported yet."); + } + + public String getQuery(){ + throw new UnsupportedOperationException("Not supported yet."); + + } + + public void setQuery(String sQuery){ + + } + +} diff --git a/dasCore/src/main/java/org/das2/reader/Reader.java b/dasCore/src/main/java/org/das2/reader/Reader.java new file mode 100644 index 000000000..ba142d089 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/Reader.java @@ -0,0 +1,52 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.logging.Logger; + +/** Generic interface for readers loadable by the tomcat server, or by the standalone + * applications. + * + * @author cwp + */ +public interface Reader { + + /** Does this reader support connected operations over a BEEP channel */ + public boolean canConnect(); + + /** Does this reader support a particular stream format */ + public boolean supportsFormat(OutputFormat fmt); + + /** Outputs all data in the range denoted by the selectors into the given + * output stream. + * + * Multiple calls to the retrieve interface for the same class instance must be + * thread safe + * + * @param selectors An array of selectors, must have at least one specified + * @param format The data and header output format requested + * @param stream The stream where the bytes should be emitted. + */ + public void retrieve(List lSel, OutputFormat fmt, OutputStream fOut, + Logger log) + throws IOException, NoDataException, BadQueryException; + + /** Setup a bidirectional communications channel with a client. + * Once the channel is connected, the server may no longer be involved. + * + * @param selectors An initial array of selectors there may be not specified at + * startup, in which case the reader just reports ready. + * @param format The initial data and header output format requested + * @param beepChannel The channel onto which bytes should be written + * @returns 0 if the connection was properly closed, or a non zero value from the + * underlying beep library if an improper shutdown occurred. + */ + public int connect(List lSel, OutputFormat fmt, Object beepChannel, + Logger log) throws IOException, BadQueryException; +} diff --git a/dasCore/src/main/java/org/das2/reader/Selector.java b/dasCore/src/main/java/org/das2/reader/Selector.java new file mode 100644 index 000000000..ef32dc967 --- /dev/null +++ b/dasCore/src/main/java/org/das2/reader/Selector.java @@ -0,0 +1,127 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.reader; + +import java.util.LinkedList; +import java.util.List; +import org.das2.datum.CalendarTime; + +/** A selector encapsulates a request parameter, or parameter set, for a reader. + * The most commonly used selector is a time range. A time range selector would + * most likely have the m_type = RANGE and m_format = TIMEPOINT. + * + * Implementation Note: We may want to break this into a selector base class + * with one derived class for each value of m_type. + * + * @author cwp + */ +public class Selector { + + // Instance Data + protected String m_sKey = null; + protected Constraint.Format m_format = null; + protected List m_lConstraints = null; + + /** Construct an empty selector */ + public Selector(String sKey, Constraint.Format fmt){ + m_sKey = sKey; + m_format = fmt; + } + + /** Add a constraint to a selector */ + public void addConstraint(Constraint cons){ + if(cons.getFormat() != m_format) + throw new IllegalArgumentException(String.format("Constraint format is %s, " + + "expected %s", cons.getFormat(), m_format)); + if(m_lConstraints == null){ + m_lConstraints = new LinkedList<>(); + } + m_lConstraints.add(cons); + } + + /** Get the selection as a single string, no matter the type of selector */ + @Override + public String toString(){ + + StringBuilder sb = new StringBuilder(); + for(Constraint cons: m_lConstraints){ + if(sb.length() != 0) sb.append(' '); + sb.append(String.format("%s%s%s", m_sKey, cons.getOpStr(), cons.getValue())); + } + return sb.toString(); + } + + /** Get the key (which is just a string) for this selector. + * Note That no two selectors for the same data-source may have the same key. + */ + public String key(){ return m_sKey; } + + /** Get the data type for the selector */ + public Constraint.Format format(){ return m_format;} + + /** Get the number of constraints defined for this selector */ + public int getConstraintCount(){ + if(m_lConstraints == null) return 0; + else return m_lConstraints.size(); + } + + /** Grab a constraint to work with */ + public Constraint getConstraint(int i){ + return m_lConstraints.get(i); + } + + /** Get a constraint by type. + * + * @param op The type of comparision to check. + * @return null if the given comparison isn't one of the one's specified, otherwise + * a constraint object is returned. It will have to be queiried to find it's + * value type. + */ + public Constraint getConstraint(Constraint.Op op){ + for(Constraint cons: m_lConstraints){ + if(cons.getOp() == op){ + return cons; + } + } + return null; + } + + // Check that we can use the convenience rountines + private void ckUniqueEq(){ + if(m_lConstraints.size() != 1) + throw new UnsupportedOperationException("Selector does not contain a unique constraint"); + + if(m_lConstraints.get(0).getOp() != Constraint.Op.EQ) + throw new UnsupportedOperationException("Selector does not have an equality constraint."); + } + + /** Convenience routine to get a string when there is only a single equality + * constraint. */ + public String getValue(){ + ckUniqueEq(); + return m_lConstraints.get(0).getValue(); + } + + public int getIntValue(){ + ckUniqueEq(); + return m_lConstraints.get(0).getIntValue(); + } + + public double getRealValue(){ + ckUniqueEq(); + return m_lConstraints.get(0).getRealValue(); + } + + public boolean getBoolValue(){ + ckUniqueEq(); + return m_lConstraints.get(0).getBoolValue(); + } + + public CalendarTime getTimeValue(){ + ckUniqueEq(); + return m_lConstraints.get(0).getTimeValue(); + } +} diff --git a/dasCore/src/main/java/org/das2/stream/Das1ToDas2.java b/dasCore/src/main/java/org/das2/stream/Das1ToDas2.java new file mode 100755 index 000000000..52e2d1b57 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/Das1ToDas2.java @@ -0,0 +1,265 @@ +/* + * Das1ToDas2.java + * + * Created on December 10, 2003, 8:40 PM + */ + +package org.das2.stream; + +import org.das2.datum.Datum; +import org.das2.datum.TimeUtil; +import org.das2.DasProperties; +import org.das2.DasException; +import org.das2.util.IDLParser; +import java.io.*; +import java.util.*; + +/** + * + * @author jbf + */ +public class Das1ToDas2 { + + static Das1ToDas2 _instance; + + String createStreamDescriptor( Map dsdp, Datum start, Datum end ) { + String header= "\n"+ + " \n" + + " \n" + + " "; + } else { + throw new IllegalArgumentException("not implemented yet for anything besides x_tagged_y_scan"); + } + } + + Map getDsdfProperties( String dsdf ) throws IOException { + FileReader r= new FileReader( dsdf ); + BufferedReader in = new BufferedReader(r); + IDLParser parser = new IDLParser(); + double[] array; + String key; + String value; + String line; + int index, lineNumber; + + line = in.readLine(); + lineNumber = 1; + + HashMap properties = new HashMap(); + + while (line != null) { + //Get rid of any comments + index = line.trim().indexOf(';'); + if (index == 0) { + lineNumber++; + line = in.readLine(); + continue; + } + else if (index != -1) { + line = line.substring(0, index); + } + + //Break line into key-value pairs + index = line.indexOf('='); + key = line.substring(0,index).trim(); + value = line.substring(index+1).trim(); + + //deterimine type of value + + if (key.equals("description")) { + String description = value.substring(1, value.length()-1); + properties.put(key, description); + } + else if (key.equals("groupAccess")) { + properties.put(key, value.substring(1, value.length()-1)); + } + else if (key.equals("form")) { + properties.put(key, value); + } + else if (key.equals("reader")) { + String reader = value.substring(1, value.length()-1); + properties.put(key, reader); + } + else if (key.equals("x_parameter")) { + String x_parameter = value.substring(1, value.length()-1); + properties.put(key, x_parameter); + } + else if (key.equals("x_unit")) { + String x_unit = value.substring(1, value.length()-1); + properties.put(key, x_unit); + } + else if (key.equals("y_parameter")) { + String y_parameter = value.substring(1, value.length()-1); + properties.put(key, y_parameter); + } + else if (key.equals("y_unit")) { + String y_unit = value.substring(1, value.length()-1); + properties.put(key, y_unit); + } + else if (key.equals("z_parameter")) { + String z_parameter = value.substring(1, value.length()-1); + properties.put(key, z_parameter); + } + else if (key.equals("z_unit")) { + String z_unit = value.substring(1, value.length()-1); + properties.put(key, z_unit); + } + else if (key.equals("x_sample_width")) { + double x_sample_width = parser.parseIDLScalar(value); + if (x_sample_width == Double.NaN) + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + properties.put(key, new Double(x_sample_width)); + } + else if (key.equals("y_fill")) { + double y_fill = parser.parseIDLScalar(value); + if (y_fill == Double.NaN) + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + properties.put(key, new Double(y_fill)); + } + else if (key.equals("z_fill")) { + double z_fill = (float)parser.parseIDLScalar(value); + if (z_fill == Float.NaN) + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + properties.put(key, new Float(z_fill)); + } + else if (key.equals("y_coordinate")) { + array = parser.parseIDLArray(value); + if (array == null) { + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + } + properties.put(key, array); + } + else if (key.equals("ny")) { + int ny; + try { + ny = Integer.parseInt(value); + } + catch (NumberFormatException nfe) { + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + } + properties.put(key, new Integer(ny)); + } + else if (key.equals("items")) { + int items; + try { + items = Integer.parseInt(value); + } + catch (NumberFormatException nfe) { + throw new IOException("Could not parse \"" + value + "\" at line " + lineNumber); + } + properties.put(key, new Integer(items)); + } + else if (value.charAt(0)=='\'' && value.charAt(value.length()-1)=='\'') { + properties.put(key, value.substring(1, value.length()-1)); + } + else if (value.charAt(0)=='"' && value.charAt(value.length()-1)=='"') { + properties.put(key, value.substring(1, value.length()-1)); + } + else { + properties.put(key, value); + } + line = in.readLine(); + lineNumber++; + } + return properties; + } + + public static Das1ToDas2 getInstance() { + if ( _instance==null ) { + _instance= new Das1ToDas2(); + } + return _instance; + } + + public void das1ToDas2( String dsdf, InputStream in, OutputStream out, Datum start, Datum end ) throws DasException, IOException { + Map properties= getDsdfProperties( dsdf ); + String header= createStreamDescriptor( properties, start, end ); + String packet= createPacketDescriptor( properties, start ); + out.write( "[00]".getBytes() ); + out.write( header.getBytes() ); + out.write( "[01]".getBytes() ); + out.write( packet.getBytes() ); + + int nItems= ((double[])properties.get("y_coordinate")).length; + int bytesPerPacket= ( nItems + 1 ) * 4; + + byte[] buffer= new byte[bytesPerPacket]; + + int b= in.read(buffer); + + if ( b==-1 ) { + out.write("[xx]".getBytes()); + } + + int offset= b; + + while ( b!=-1 ) { + while ( b!=-1 && offset "); + } + + } + + +} diff --git a/dasCore/src/main/java/org/das2/stream/DasStreamFormatException.java b/dasCore/src/main/java/org/das2/stream/DasStreamFormatException.java new file mode 100644 index 000000000..12b728b89 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/DasStreamFormatException.java @@ -0,0 +1,53 @@ +/* File: DasStreamFormatException.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + + +package org.das2.stream; + +import org.das2.DasException; + +/** This exception indicates that a das2 stream is not formatted properly, + * and can indicate that a das2 stream was expected but not received. + * @author jbf + */ +public class DasStreamFormatException extends DasException { + + + + /** + * Creates a new instance of DasStreamFormatException without detail message. + */ + + public DasStreamFormatException() { + } + + + /** + * Constructs an instance of DasStreamFormatException with the specified detail message. + * @param msg the detail message. + */ + public DasStreamFormatException(String msg) { + super(msg); + } +} diff --git a/dasCore/src/main/java/org/das2/stream/DataTransferType.java b/dasCore/src/main/java/org/das2/stream/DataTransferType.java new file mode 100644 index 000000000..9654c4b9e --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/DataTransferType.java @@ -0,0 +1,267 @@ +/* File: DataTransferType.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 18, 2003, 9:01 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.TimeUtil; +import org.das2.datum.Units; +import org.das2.datum.format.TimeDatumFormatter; +import org.das2.util.FixedWidthFormatter; +import org.das2.util.NumberFormatUtil; +import java.io.UnsupportedEncodingException; +import java.text.DecimalFormat; +import java.text.ParseException; + +import java.util.Map; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author Edward West + */ +public class DataTransferType { + + private static final int I_SUN_REAL4 = 0; + private static final int I_SUN_REAL8 = 1; + private static final int I_LITTLE_ENDIAN_REAL4 = 4; + private static final int I_LITTLE_ENDIAN_REAL8 = 5; + private static final int I_ASCII = 2; + private static final int I_TIME = 3; + + private static final Map map = new HashMap(); + + public static final DataTransferType SUN_REAL4 = new DataTransferType("sun_real4", I_SUN_REAL4, 4, false); + public static final DataTransferType SUN_REAL8 = new DataTransferType("sun_real8", I_SUN_REAL8, 8, false); + + public static final DataTransferType LITTLE_ENDIAN_REAL4 = new DataTransferType("little_endian_real4", I_LITTLE_ENDIAN_REAL4, 4, false); + public static final DataTransferType LITTLE_ENDIAN_REAL8 = new DataTransferType("little_endian_real8", I_LITTLE_ENDIAN_REAL8, 8, false); + + private static final Pattern ASCII_PATTERN = Pattern.compile("ascii([1-9][0-9]?)"); + private static final Pattern TIME_PATTERN = Pattern.compile("time([1-9][0-9]?)"); + + private final String name; + + private final int sizeBytes; + + private final boolean ascii; + + private final int id; + + private DecimalFormat doubleFormatter; + + private DataTransferType(String name, int id, int sizeBytes, boolean ascii) { + this.name = name; + this.id = id; + this.sizeBytes = sizeBytes; + this.ascii = ascii; + map.put(name, this); + if (ascii) { + doubleFormatter = NumberFormatUtil.getDecimalFormat(getFormat(sizeBytes-1)); + } + } + + static class Time extends DataTransferType { + Units units; + TimeDatumFormatter formatter; + int sizeBytes; + + Time( int size ) { + super( "time"+size, I_TIME, size, true ); + this.sizeBytes= size; // yuk + this.units= Units.us2000; + this.formatter= TimeDatumFormatter.DEFAULT; + } + + public Units getUnits() { + return units; + } + + public double read(final java.nio.ByteBuffer buffer) { + try { + byte[] bytes = new byte[sizeBytes]; + buffer.get(bytes); + String str = new String(bytes, "ASCII").trim(); + double result = TimeUtil.create( str ).doubleValue(units); + return result; + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException(e); + } catch ( ParseException e ) { + throw new RuntimeException(e); + } + } + + public void write(double d, java.nio.ByteBuffer buffer) { + try { + String s = formatter.format(units.createDatum(d)); + s= s.substring(0, sizeBytes); + byte[] bytes = s.getBytes("US-ASCII"); + bytes[sizeBytes-1]= 32; // " " space + buffer.put(bytes); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException(e); + } + } + } + + public String toString() { + return name; + } + + public int getSizeBytes() { + return sizeBytes; + } + + public static DataTransferType getByName(String name) { + DataTransferType type = (DataTransferType)map.get(name); + if (type == null ) { + Matcher m = ASCII_PATTERN.matcher(name); + if (m.matches()) { + int charCount = Integer.parseInt(m.group(1)); + type = new DataTransferType(name, I_ASCII, charCount, true); + map.put(name, type); + } else { + m= TIME_PATTERN.matcher(name); + if ( m.matches()) { + int charCount = Integer.parseInt(m.group(1)); + type = new DataTransferType.Time( charCount ); + map.put(name, type); + } else { + throw new RuntimeException( "Unsupported type: "+name ); + } + } + } + return type; + } + + /** + * If type terminates a line, then use \n to delineate + */ + public boolean isAscii() { + return ascii; + } + + private static final java.nio.ByteOrder BIG_ENDIAN = java.nio.ByteOrder.BIG_ENDIAN; + private static final java.nio.ByteOrder LITTLE_ENDIAN = java.nio.ByteOrder.LITTLE_ENDIAN; + + public double read(final java.nio.ByteBuffer buffer) { + final java.nio.ByteOrder bo = buffer.order(); + try { + double result; + switch(id) { + case I_SUN_REAL4: { + buffer.order(BIG_ENDIAN); + result = buffer.getFloat(); + } break; + case I_SUN_REAL8: { + buffer.order(BIG_ENDIAN); + result = buffer.getDouble(); + } break; + case I_LITTLE_ENDIAN_REAL4: { + buffer.order(LITTLE_ENDIAN); + result = buffer.getFloat(); + } break; + case I_LITTLE_ENDIAN_REAL8: { + buffer.order(LITTLE_ENDIAN); + result = buffer.getDouble(); + } break; + case I_ASCII: { + byte[] bytes = new byte[sizeBytes]; + buffer.get(bytes); + String str = new String(bytes, "ASCII").trim(); + result = Double.parseDouble(str); + } break; + default: { + throw new IllegalStateException("Invalid id: " + id); + } + } + return result; + } catch (java.io.UnsupportedEncodingException uee) { + //NOT LIKELY TO HAPPEN + throw new RuntimeException(uee); + } finally { + buffer.order(bo); + } + } + + public void write(double d, java.nio.ByteBuffer buffer) { + final java.nio.ByteOrder bo = buffer.order(); + try { + switch(id) { + case I_SUN_REAL4: { + buffer.order(BIG_ENDIAN); + buffer.putFloat((float)d); + } break; + case I_SUN_REAL8: { + buffer.order(BIG_ENDIAN); + buffer.putDouble(d); + } break; + case I_LITTLE_ENDIAN_REAL4: { + buffer.order(LITTLE_ENDIAN); + buffer.putFloat((float)d); + } break; + case I_LITTLE_ENDIAN_REAL8: { + buffer.order(LITTLE_ENDIAN); + buffer.putDouble(d); + } break; + case I_ASCII: { + String s = doubleFormatter.format(d); + if ( s.length()==sizeBytes-2 ) { + s= " "+s+" "; // bug 232 + } else if ( s.length()==sizeBytes-1 ) { + s= s+" "; + } + if (sizeBytes < 10) { + s = FixedWidthFormatter.format(s, sizeBytes - 1); + } + byte[] bytes = s.getBytes("US-ASCII"); + buffer.put(bytes); + } break; + default: { + throw new IllegalStateException("Invalid id: " + id); + } + } + } catch (java.io.UnsupportedEncodingException uee) { + //US-ASCII encoding should be supported by all JVM implementations. + throw new RuntimeException(uee); + } finally { + buffer.order(bo); + } + } + + private static String getFormat(int length) { + if (length < 9) { + return "0.#"; + } else { + StringBuffer buffer = new StringBuffer(length); + buffer.append("+0."); + for (int i = 0; i < length - 7; i++) { + buffer.append('0'); + } + buffer.append("E00;-#"); + return buffer.toString(); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/GUnzipStreamProcessor.java b/dasCore/src/main/java/org/das2/stream/GUnzipStreamProcessor.java new file mode 100755 index 000000000..6b66d7071 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/GUnzipStreamProcessor.java @@ -0,0 +1,110 @@ +/* + * gzip.java + * + * Created on July 11, 2003, 9:39 AM + */ + +package org.das2.stream; + +import org.das2.DasException; +import org.das2.util.StreamTool; +import org.w3c.dom.*; + +import java.io.*; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * + * @author jbf + */ +public class GUnzipStreamProcessor extends StreamProcessor { + + public void process( InputStream in0, OutputStream out) throws IOException { + + PushbackInputStream in = new PushbackInputStream(in0); + byte[] header; + byte[] tag=new byte[4]; + boolean isCompressed=false; // true if input stream is already compressed + + int bytesRead= in.read( tag, 0, 4 ); + int offset= bytesRead; + while ( bytesRead!=-1 && offset<4 ) { + bytesRead= in.read( tag, offset, 4-offset ); + offset+= bytesRead; + } + if ( ! Arrays.equals(tag,"[00]".getBytes()) ) { + throw new IOException( "Expected [00], got "+new String(tag) ); + } + + header= StreamTool.readXML(in); + + try { + Reader reader = new InputStreamReader(new ByteArrayInputStream(header)); + Document document= StreamDescriptor.parseHeader(reader); + Element docNode= document.getDocumentElement(); + if ( ! docNode.getAttribute("compression").equals("") ) { + String compression= docNode.getAttribute("compression"); + isCompressed= compression.equals("gzip"); + if ( !isCompressed ) { + throw new IOException( "unsupported compression used: "+compression ); + } + } + docNode.setAttribute("compression","none"); + header= StreamDescriptor.createHeader(document).getBytes(); + + out.write("[00]".getBytes()); + out.write(header); + } catch ( DasException e ) { + out.write( getDasExceptionStream(e) ); + } + + if ( isCompressed ) { + in = new PushbackInputStream(new GZIPInputStream(in)); + } + + int ib=0; + byte[] buf= new byte[2048]; + while (ib!=-1) { + ib= in.read(buf); + if (ib>-1) out.write(buf,0, ib); + } + + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + InputStream in=null; + OutputStream out=null; + if ( args.length>0 ) { + try { + in= new FileInputStream(args[0]); + } catch ( FileNotFoundException ex) { + System.err.println("Input file not found"); + System.err.println(" file="+args[0]); + System.exit(-1); + } + } else { + in= System.in; + } + + if ( args.length>1 ) { + try { + out= new FileOutputStream(args[1]); + } catch ( FileNotFoundException ex) { + } + + } else { + out= System.out; + } + + try { + new GUnzipStreamProcessor().process(in,out); + } catch ( IOException ex) { + System.err.println(ex.getMessage()); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/GZipStreamProcessor.java b/dasCore/src/main/java/org/das2/stream/GZipStreamProcessor.java new file mode 100755 index 000000000..1aa980296 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/GZipStreamProcessor.java @@ -0,0 +1,109 @@ +/* + * gzip.java + * + * Created on July 11, 2003, 9:39 AM + */ + +package org.das2.stream; + +import org.das2.DasException; +import org.das2.util.StreamTool; +import org.w3c.dom.*; + +import java.io.*; +import java.util.*; + +/** + * + * @author jbf + */ +public class GZipStreamProcessor extends StreamProcessor { + + public void process( InputStream in0, OutputStream out) throws IOException { + + PushbackInputStream in = new PushbackInputStream(in0); + byte[] header; + byte[] tag=new byte[4]; + boolean isCompressed=false; // true if input stream is already compressed + + int bytesRead= in.read( tag, 0, 4 ); + int offset= bytesRead; + while ( bytesRead!=-1 && offset<4 ) { + bytesRead= in.read( tag, offset, 4-offset ); + offset+= bytesRead; + } + if ( ! Arrays.equals(tag,"[00]".getBytes()) ) { + throw new IOException( "Expected [00], got "+new String(tag) ); + } + + header= StreamTool.readXML(in); + + try { + Reader reader = new InputStreamReader(new ByteArrayInputStream(header)); + Document document= StreamDescriptor.parseHeader(reader); + Element docNode= document.getDocumentElement(); + if ( ! docNode.getAttribute("compression").equals("") ) { + String compression= docNode.getAttribute("compression"); + isCompressed= compression.equals("gzip"); + if ( !isCompressed ) { + throw new IOException( "unsupported compression used: "+compression ); + } + } + docNode.setAttribute("compression","gzip"); + header= StreamDescriptor.createHeader(document).getBytes(); + + out.write("[00]".getBytes()); + out.write(header); + } catch ( DasException e ) { + out.write( getDasExceptionStream(e) ); + } + + if ( ! isCompressed ) { + out= new java.util.zip.GZIPOutputStream(out); + } + + int ib=0; + byte[] buf= new byte[2048]; + while (ib!=-1) { + ib= in.read(buf); + if (ib>-1) out.write(buf,0, ib); + } + + if ( ! isCompressed ) { + ((java.util.zip.GZIPOutputStream)out).finish(); + } + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) throws Exception { + InputStream in=null; + OutputStream out=null; + if ( args.length>0 ) { + try { + in= new FileInputStream(args[0]); + } catch ( FileNotFoundException ex) { + System.err.println("Input file not found"); + System.err.println(" file="+args[0]); + System.exit(-1); + } + } else { + in= System.in; + } + + if ( args.length>1 ) { + try { + out= new FileOutputStream(args[1]); + } catch ( FileNotFoundException ex) { + } + + } else { + out= System.out; + } + + new GZipStreamProcessor().process(in,out); + + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/MicrophoneStream.java b/dasCore/src/main/java/org/das2/stream/MicrophoneStream.java new file mode 100644 index 000000000..a6b1d9800 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/MicrophoneStream.java @@ -0,0 +1,229 @@ + +package org.das2.stream; + + +import java.io.IOException; +import java.io.File; + +import javax.sound.sampled.DataLine; +import javax.sound.sampled.TargetDataLine; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.AudioFileFormat; + +public class MicrophoneStream extends Thread +{ + private TargetDataLine m_line; + private AudioFileFormat.Type m_targetType; + private AudioInputStream m_audioInputStream; + private File m_outputFile; + + + + public MicrophoneStream(TargetDataLine line, AudioFileFormat.Type targetType, File file) + { + m_line = line; + m_audioInputStream = new AudioInputStream(line); + m_targetType = targetType; + m_outputFile = file; + } + + + + /** Starts the recording. + To accomplish this, (i) the line is started and (ii) the + thread is started. + */ + public void start() + { + /* Starting the TargetDataLine. It tells the line that + we now want to read data from it. If this method + isn't called, we won't + be able to read data from the line at all. + */ + m_line.start(); + /* Starting the thread. This call results in the + method 'run()' (see below) being called. There, the + data is actually read from the line. + */ + super.start(); + } + + + /** Stops the recording. + + Note that stopping the thread explicitely is not necessary. Once + no more data can be read from the TargetDataLine, no more data + be read from our AudioInputStream. And if there is no more + data from the AudioInputStream, the method 'AudioSystem.write()' + (called in 'run()' returns. Returning from 'AudioSystem.write()' + is followed by returning from 'run()', and thus, the thread + is terminated automatically. + + It's not a good idea to call this method just 'stop()' + because stop() is a (deprecated) method of the class 'Thread'. + And we don't want to override this method. + */ + public void stopRecording() + { + m_line.stop(); + m_line.close(); + } + + + + + /** Main working method. + You may be surprised that here, just 'AudioSystem.write()' is + called. But internally, it works like this: AudioSystem.write() + contains a loop that is trying to read from the passed + AudioInputStream. Since we have a special AudioInputStream + that gets its data from a TargetDataLine, reading from the + AudioInputStream leads to reading from the TargetDataLine. The + data read this way is then written to the passed File. Before + writing of audio data starts, a header is written according + to the desired audio file type. Reading continues untill no + more data can be read from the AudioInputStream. In our case, + this happens if no more data can be read from the TargetDataLine. + This, in turn, happens if the TargetDataLine is stopped or closed + (which implies stopping). (Also see the comment above.) Then, + the file is closed and 'AudioSystem.write()' returns. + */ + public void run() + { + try + { + AudioSystem.write( + m_audioInputStream, + m_targetType, + m_outputFile); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + + + public static void main(String[] args) + { + if (args.length != 1 || args[0].equals("-h")) + { + printUsageAndExit(); + } + + /* We have made shure that there is only one command line + argument. This is taken as the filename of the soundfile + to store to. + */ + String strFilename = args[0]; + File outputFile = new File(strFilename); + + /* For simplicity, the audio data format used for recording + is hardcoded here. We use PCM 44.1 kHz, 16 bit signed, + stereo. + */ + AudioFormat audioFormat = new AudioFormat( + AudioFormat.Encoding.PCM_SIGNED, + 44100.0F, 16, 2, 4, 44100.0F, false); + + /* Now, we are trying to get a TargetDataLine. The + TargetDataLine is used later to read audio data from it. + If requesting the line was successful, we are opening + it (important!). + */ + DataLine.Info info = new DataLine.Info(TargetDataLine.class, audioFormat); + TargetDataLine targetDataLine = null; + try + { + targetDataLine = (TargetDataLine) AudioSystem.getLine(info); + targetDataLine.open(audioFormat); + } + catch (LineUnavailableException e) + { + out("unable to get a recording line"); + e.printStackTrace(); + System.exit(1); + } + + /* Again for simplicity, we've hardcoded the audio file + type, too. + */ + AudioFileFormat.Type targetType = AudioFileFormat.Type.WAVE; + + /* Now, we are creating an SimpleAudioRecorder object. It + contains the logic of starting and stopping the + recording, reading audio data from the TargetDataLine + and writing the data to a file. + */ + MicrophoneStream recorder = new MicrophoneStream( + targetDataLine, + targetType, + outputFile); + + /* We are waiting for the user to press ENTER to + start the recording. (You might find it + inconvenient if recording starts immediately.) + */ + out("Press ENTER to start the recording."); + try + { + System.in.read(); + } + catch (IOException e) + { + e.printStackTrace(); + } + /* Here, the recording is actually started. + */ + recorder.start(); + out("Recording..."); + + /* And now, we are waiting again for the user to press ENTER, + this time to signal that the recording should be stopped. + */ + out("Press ENTER to stop the recording."); + try + { + System.in.read(); + } + catch (IOException e) + { + e.printStackTrace(); + } + + /* Here, the recording is actually stopped. + */ + recorder.stopRecording(); + out("Recording stopped."); + // System.exit(0); + } + + + + private static void printUsageAndExit() + { + out("SimpleAudioRecorder: usage:"); + out("\tjava SimpleAudioRecorder -h"); + out("\tjava SimpleAudioRecorder "); + System.exit(0); + } + + + + private static void out(String strMessage) + { + System.out.println(strMessage); + } +} + + + +/*** SimpleAudioRecorder.java ***/ + + + + diff --git a/dasCore/src/main/java/org/das2/stream/PacketDescriptor.java b/dasCore/src/main/java/org/das2/stream/PacketDescriptor.java new file mode 100755 index 000000000..7ddca1af4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/PacketDescriptor.java @@ -0,0 +1,285 @@ +/* File: PacketDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.util.StreamTool; +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +/** Represents the global properties of the stream, that are accessible to + * datasets within. + * @author jbf + */ +public class PacketDescriptor implements Cloneable { + + private StreamXDescriptor xDescriptor; + private SkeletonDescriptor[] yDescriptors = new SkeletonDescriptor[6]; + private int yCount = 0; + private Map properties; + + /** Creates a new instance of StreamProperties */ + public PacketDescriptor( Element element ) throws StreamException + { + properties= new HashMap(); + if (element.getTagName().equals("packet")) { + processElement(element); + } else { + processLegacyElement(element); + } + } + + private void processElement(Element element) throws StreamException + { + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + if ( node instanceof Element ) { + Element child = (Element)node; + String name = child.getTagName(); + if ( name.equals("x") ) { + xDescriptor = new StreamXDescriptor(child); + } else if ( name.equals("y") ) { + StreamMultiYDescriptor d = new StreamMultiYDescriptor(child); + addYDescriptor(d); + } else if ( name.equals("yscan") ) { + StreamYScanDescriptor d = new StreamYScanDescriptor(child); + addYDescriptor(d); + } else if ( name.equals("properties") ) { + try { + NodeList list = element.getElementsByTagName("properties"); + if (list.getLength() != 0) { + Element propertiesElement = (Element)list.item(0); + Map m = StreamTool.processPropertiesElement(propertiesElement); + properties.putAll(m); + } + } catch ( StreamException e ) { + throw new RuntimeException(e); + } + } + } + } + } + + private void processLegacyElement(Element element) + throws StreamException + { + NodeList children= element.getChildNodes(); + for (int i=0; i= yCount) { + throw new IndexOutOfBoundsException("index = " + index + ", yCount = " + yCount); + } + return yDescriptors[index]; + } + + public int getSizeBytes() { + int sizeBytes = xDescriptor.getSizeBytes(); + for (int i = 0; i < yCount; i++) { + sizeBytes += yDescriptors[i].getSizeBytes(); + } + return sizeBytes; + } + + public DatumVector[] read(ByteBuffer input) { + DatumVector[] vectors = new DatumVector[yCount + 1]; + vectors[0] = xDescriptor.read(input); + for (int i = 0; i < yCount; i++) { + vectors[i + 1] = yDescriptors[i].read(input); + } + return vectors; + } + + public void write(Datum xTag, DatumVector[] vectors, ByteBuffer output) { + xDescriptor.writeDatum(xTag, output); + for (int i = 0; i < yCount; i++) { + yDescriptors[i].write(vectors[i], output); + } + //ASCII KLUDGE!!!! + if (yDescriptors[yCount - 1] instanceof StreamYScanDescriptor + && ((StreamYScanDescriptor)yDescriptors[yCount - 1]).getDataTransferType().isAscii() + && Character.isWhitespace((char)output.get(output.position() - 1))) { + output.put(output.position() - 1, (byte)'\n'); + } else if (yDescriptors[yCount - 1] instanceof StreamMultiYDescriptor + && ((StreamMultiYDescriptor)yDescriptors[yCount - 1]).getDataTransferType().isAscii() + && Character.isWhitespace((char)output.get(output.position() - 1))) { + output.put(output.position() - 1, (byte)'\n'); + } + } + + private static String trimComment(String line) { + int index = line.indexOf(';'); + if (index == 0) { + return ""; + } else if (index != -1) { + return line.substring(0, index); + } else { + return line; + } + } + + private static final Pattern LABEL_PATTERN = Pattern.compile("\\s*label\\((\\d+)\\)\\s*"); + + public static PacketDescriptor createLegacyPacketDescriptor(Map dsdf) { + PacketDescriptor packetDescriptor = new PacketDescriptor(); + packetDescriptor.setXDescriptor(new StreamXDescriptor()); + if (dsdf.get("form").equals("x_tagged_y_scan")) { + StreamYScanDescriptor yscan = new StreamYScanDescriptor(); + yscan.setYCoordinates((double[])dsdf.get("y_coordinate")); + packetDescriptor.addYDescriptor(yscan); + } else if (dsdf.get("form").equals("x_multi_y") && dsdf.get("ny") != null) { + StreamMultiYDescriptor y = new StreamMultiYDescriptor(); + packetDescriptor.addYDescriptor(y); + } else if (dsdf.get("form").equals("x_multi_y") && dsdf.get("items") != null) { + List planeList = (List)dsdf.get("plane-list"); + packetDescriptor.addYDescriptor(new StreamMultiYDescriptor()); + for (int index = 0; index < planeList.size(); index++) { + StreamMultiYDescriptor y = new StreamMultiYDescriptor(); + y.setName((String)planeList.get(index)); + packetDescriptor.addYDescriptor(y); + } + } + return packetDescriptor; + } + + private static String[] ensureCapacity(String[] array, int capacity) { + if (array == null) { + return new String[capacity]; + } else if (array.length >= capacity) { + return array; + } else { + String[] temp = new String[capacity]; + System.arraycopy(array, 0, temp, 0, array.length); + return temp; + } + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("packet"); + element.appendChild(xDescriptor.getDOMElement(document)); + for (int i = 0; i < yCount; i++) { + element.appendChild(yDescriptors[i].getDOMElement(document)); + } + return element; + } + + public Object getProperty(String name) { + return properties.get(name); + } + + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + public void setProperty(String name, Object value) { + properties.put(name, value); + } + + public Object clone() { + try { + PacketDescriptor clone = (PacketDescriptor)super.clone(); + clone.xDescriptor = (StreamXDescriptor)xDescriptor.clone(); + clone.yDescriptors = new SkeletonDescriptor[yCount]; + for (int i = 0; i < yCount; i++) { + clone.yDescriptors[i] = (SkeletonDescriptor)yDescriptors[i].clone(); + } + return clone; + } catch (CloneNotSupportedException cnse) { + throw new RuntimeException(cnse); + } + } + + public String toString() { + String result= "\n"; + for ( int iplane= 0; iplane + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.Units; +import java.util.HashMap; +import java.util.Map; +import org.das2.datum.CalendarTime; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; + +/** + * + * @author eew + */ +public enum PropertyType { + + DOUBLE("double"), + DOUBLE_ARRAY("doubleArray"), + DATUM("Datum"), + DATUM_RANGE("DatumRange"), + INTEGER("int"), + STRING("String"), + TIME("Time"), + TIME_RANGE("TimeRange"), + ; + + private static final Map map = new HashMap(); + static { + for (PropertyType t : values()) + map.put(t.name, t); + } + + public static PropertyType getByName(String name) { + PropertyType result= (PropertyType)map.get(name); + if ( result==null ) { + throw new IllegalArgumentException( "Unrecognized property type: "+name ); + } + return result; + } + + private final String name; + + /** Creates a new instance of PropertyType */ + private PropertyType(String name) { + this.name = name; + } + + public Object parse(String s) throws java.text.ParseException { + switch(this){ + case STRING: + { + return s; + } + case DOUBLE: + try { + return new Double(s); + } + catch (NumberFormatException nfe) { + throw new java.text.ParseException(nfe.getMessage(), 0); + } + case INTEGER: + try { + return new Integer(s); + } + catch (NumberFormatException nfe) { + throw new java.text.ParseException(nfe.getMessage(), 0); + } + case DOUBLE_ARRAY: + try { + String[] strings = s.split(","); + double[] doubles = new double[strings.length]; + for (int i = 0; i < strings.length; i++) { + doubles[i] = Double.parseDouble(strings[i]); + } + return doubles; + } + catch (NumberFormatException nfe) { + throw new java.text.ParseException(nfe.getMessage(), 0); + } + case DATUM: + { + String[] split = s.split("\\s+"); + if (split.length == 1) { + return Units.dimensionless.parse(split[0]); + } + else if (split.length == 2) { + Units units = Units.lookupUnits(split[1]); + return units.parse(split[0]); + } + else { + throw new IllegalArgumentException("Too many tokens: '" + s + "'"); + } + } + case DATUM_RANGE: + { + String[] split = s.split("\\s+"); + if (split.length < 3){ + throw new IllegalArgumentException("Too few tokens in range: '" + s + "'"); + } + if(! split[1].toLowerCase().equals("to")) + throw new java.text.ParseException("Range '"+s+"' is missing the word 'to'", 0); + + if (split.length == 3){ + Datum begin = Units.dimensionless.parse(split[0]); + Datum end = Units.dimensionless.parse(split[2]); + return new DatumRange(begin, end); + } + + // New for 2014-06-12, allow units strings to have spaces, thus + // "V**2 m**-2 Hz**-1" is legal + StringBuilder bldr = new StringBuilder(); + for(int i = 3; i < split.length; i++){ + if(i > 3) bldr.append(" "); + bldr.append(split[i]); + } + + Units units = Units.lookupUnits(bldr.toString()); + Datum begin = units.parse(split[0]); + Datum end = units.parse(split[2]); + return new DatumRange(begin, end); + + } + case TIME: + { + return new CalendarTime(s).toDatum(); + } + case TIME_RANGE: + { + return DatumRangeUtil.parseTimeRange(s); + } + default: + throw new IllegalStateException("unrecognized name: " + name); + } + } + + @Override + public String toString() { + return name; + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/SkeletonDescriptor.java b/dasCore/src/main/java/org/das2/stream/SkeletonDescriptor.java new file mode 100644 index 000000000..ee3f248cb --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/SkeletonDescriptor.java @@ -0,0 +1,48 @@ +/* File: SkeletonDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.DatumVector; +import java.nio.ByteBuffer; +import java.util.Map; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public interface SkeletonDescriptor { + + int getSizeBytes(); + + DatumVector read(ByteBuffer input); + + void write(DatumVector input, ByteBuffer output); + + Element getDOMElement(Document document); + + Object clone(); + + Object getProperty(String name); + + public Map getProperties(); + +} diff --git a/dasCore/src/main/java/org/das2/stream/Sonifier.java b/dasCore/src/main/java/org/das2/stream/Sonifier.java new file mode 100644 index 000000000..9bc1bc615 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/Sonifier.java @@ -0,0 +1,77 @@ +package org.das2.stream; + +import org.das2.util.StreamTool; +import java.io.*; +import java.io.File; +import java.io.IOException; +import java.nio.channels.*; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; + +public class Sonifier implements StreamHandler { + private static final int EXTERNAL_BUFFER_SIZE = 128000; + byte[] buffer; + SourceDataLine line = null; + int bufferInputIndex; + + + public void packet(PacketDescriptor pd, org.das2.datum.Datum xTag, org.das2.datum.DatumVector[] vectors) throws StreamException { + double max= 1.0; + buffer[bufferInputIndex++]= (byte) ( 256 * vectors[0].doubleValue(0, vectors[0].getUnits() ) ); + if ( bufferInputIndex==100 ) { + line.write(buffer, 0, bufferInputIndex); + bufferInputIndex=0; + } + } + + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + } + + public void streamClosed(StreamDescriptor sd) throws StreamException { + line.drain(); + line.close(); + } + + public void streamDescriptor(StreamDescriptor sd) throws StreamException { + AudioFormat audioFormat= new AudioFormat( 8000, 8, 1, true, false ); + + DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat); + try { + line = (SourceDataLine) AudioSystem.getLine(info); + line.open(audioFormat); + } + catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + buffer = new byte[EXTERNAL_BUFFER_SIZE]; + bufferInputIndex= 0; + line.start(); + } + + public void streamException(StreamException se) throws StreamException { + se.printStackTrace(); + System.exit(0); + } + public void streamComment( StreamComment sc ) throws StreamException { + + } + + public static void main( String[] args ) throws Exception { + InputStream in; + if ( args.length==0 ) { + in= System.in; + } else { + in= new FileInputStream(args[0]); + } + ReadableByteChannel channel= Channels.newChannel(in); + StreamTool.readStream(channel, new Sonifier() ); + } +} + + diff --git a/dasCore/src/main/java/org/das2/stream/StreamComment.java b/dasCore/src/main/java/org/das2/stream/StreamComment.java new file mode 100644 index 000000000..ccdefa2ee --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamComment.java @@ -0,0 +1,49 @@ +/* File: StreamException.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 11, 2004, 11:03 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.w3c.dom.*; + +/** + * + * @author jbf + */ +public class StreamComment { + + Element element; + + public final String TYPE_TASK_SIZE="taskSize"; + public final String TYPE_TASK_PROGRESS="taskProgress"; + public final String TYPE_LOG="log:(.*)"; + + public StreamComment( Element element ) { + this.element= element; + } + + public String getType() { return element.getAttribute("type"); } + public String getValue() { return element.getAttribute("value"); } + + @Override + public String toString() { return "stream comment: "+getType()+"="+getValue(); } +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamDescriptor.java b/dasCore/src/main/java/org/das2/stream/StreamDescriptor.java new file mode 100644 index 000000000..d3c2dc804 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamDescriptor.java @@ -0,0 +1,429 @@ +/* File: StreamDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.util.StreamTool; +import org.das2.DasIOException; +import org.das2.datum.DatumVector; +import org.das2.util.IDLParser; +import java.io.*; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +//import org.apache.xml.serialize.OutputFormat; +//import org.apache.xml.serialize.XMLSerializer; +import org.w3c.dom.*; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; +import org.xml.sax.SAXParseException; + +/** Represents the global properties of the stream, that are accessible to + * datasets within. + * @author jbf + */ +public class StreamDescriptor implements SkeletonDescriptor, Cloneable { + + private Map properties = new HashMap(); + + private StreamXDescriptor xDescriptor; + private ArrayList yDescriptors = new ArrayList(); + private String compression; + + /** Creates a new instance of StreamProperties */ + public StreamDescriptor(Element element) throws StreamException { + if (element.getTagName().equals("stream")) { + processElement(element); + } + else { + processLegacyElement(element); + } + } + + private void processElement(Element element) throws StreamException { + compression = element.getAttribute("compression"); + NodeList list = element.getElementsByTagName("properties"); + if (list.getLength() != 0) { + Element propertiesElement = (Element)list.item(0); + Map m = StreamTool.processPropertiesElement(propertiesElement); + properties.putAll(m); + } + } + + private void processLegacyElement(Element element) throws StreamException { + NodeList children= element.getChildNodes(); + for (int i=0; i= capacity) { + return array; + } + else { + String[] temp = new String[capacity]; + System.arraycopy(array, 0, temp, 0, array.length); + return temp; + } + } + + /** Getter for property compression. + * @return Value of property compression. + * + */ + public String getCompression() { + return compression; + } + + /** Setter for property compression. + * @param compression New value of property compression. + * + */ + public void setCompression(String compression) { + this.compression = compression; + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("stream"); + if (compression != null && !compression.equals("")) { + element.setAttribute("compression", compression); + } + if (!properties.isEmpty()) { + Element propertiesElement = StreamTool.processPropertiesMap( document, properties ); + element.appendChild(propertiesElement); + } + return element; + } + + public Object clone() { + try { + StreamDescriptor clone = (StreamDescriptor)super.clone(); + clone.properties = new HashMap(this.properties); + return clone; + } + catch (CloneNotSupportedException cnse) { + throw new RuntimeException(cnse); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamException.java b/dasCore/src/main/java/org/das2/stream/StreamException.java new file mode 100644 index 000000000..1b89251fb --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamException.java @@ -0,0 +1,51 @@ +/* File: StreamException.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 11, 2004, 11:03 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.DasException; +import java.io.IOException; +import org.xml.sax.SAXException; + +/** + * + * @author eew + */ +public class StreamException extends DasException { + + /** Creates a new instance of StreamException */ + public StreamException(String message) { + super(message); + } + + public StreamException(SAXException se) { + super(se.getMessage()); + initCause(se); + } + + public StreamException(IOException ioe) { + super(ioe.getMessage()); + initCause(ioe); + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamHandler.java b/dasCore/src/main/java/org/das2/stream/StreamHandler.java new file mode 100644 index 000000000..6fafbaaff --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamHandler.java @@ -0,0 +1,40 @@ +/* File: StreamHandler.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 11, 2004, 10:57 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; + +/** + * + * @author Edward E. West + */ +public interface StreamHandler { + void streamDescriptor(StreamDescriptor sd) throws StreamException; + void packetDescriptor(PacketDescriptor pd) throws StreamException; + void packet(PacketDescriptor pd, Datum xTag, DatumVector[] vectors) throws StreamException; + void streamClosed(StreamDescriptor sd) throws StreamException; + void streamException(StreamException se) throws StreamException; + void streamComment(StreamComment sc) throws StreamException; +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamMultiYDescriptor.java b/dasCore/src/main/java/org/das2/stream/StreamMultiYDescriptor.java new file mode 100755 index 000000000..cd05b6f75 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamMultiYDescriptor.java @@ -0,0 +1,180 @@ +/* File: StreamMultiYDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.stream; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import org.das2.util.StreamTool; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class StreamMultiYDescriptor implements SkeletonDescriptor, Cloneable { + + private String name = ""; + private Units units = Units.dimensionless; + private DataTransferType transferType = DataTransferType.SUN_REAL4; + + public StreamMultiYDescriptor(Element element) throws StreamException + { + if (element.getTagName().equals("y")) { + processElement(element); + } else { + processLegacyElement(element); + } + } + + private void processElement(Element element) throws StreamException { + + //element.getAttribute returns empty string if attr is not specified + //so safe to just use it directly + String name = element.getAttribute("name"); + if ( name != null) { + this.name = name; + } + String typeStr = element.getAttribute("type"); + DataTransferType type = DataTransferType.getByName(typeStr); + + String unitsString = element.getAttribute("units"); + if (unitsString != null) { + units = Units.lookupUnits(unitsString); + } + + NamedNodeMap attrs = element.getAttributes(); + for (int i = 0; i < attrs.getLength(); i++) { + Node n = attrs.item(i); + properties.put(n.getNodeName(), n.getNodeValue()); + } + + NodeList nl = element.getElementsByTagName("properties"); + for (int i = 0; i < nl.getLength(); i++) { + Element el = (Element) nl.item(i); + Map m = StreamTool.processPropertiesElement(el); + + // make sure we don't conflict with the 3 reserved properites, + // name, type and units + for(String sPropName: m.keySet()){ + if(sPropName.equals("name")||sPropName.equals("type")|| + sPropName.equals("units")) + throw new StreamException("Can't use reserved property names 'name'"+ + "'type' or 'units' in side a y-plane properties element."); + } + properties.putAll(m); + } + + if (type != null) { + transferType = type; + } else { + throw new RuntimeException("Illegal transfer type: " + typeStr); + } + } + + private void processLegacyElement(Element element) { + if (element.getAttribute("name") != null) { + name = element.getAttribute("name"); + } else { + name = ""; + } + String typeStr = element.getAttribute("type"); + DataTransferType type = DataTransferType.getByName(typeStr); + if (type != null) { + transferType = type; + } else { + throw new RuntimeException("Illegal transfer type: " + typeStr); + } + } + + public StreamMultiYDescriptor() { + } + + public Units getUnits() { + return units; + } + + public void setUnits(Units units) { + this.units = units; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public int getSizeBytes() { + return transferType.getSizeBytes(); + } + + public void setDataTransferType(DataTransferType transferType) { + this.transferType = transferType; + } + + public DataTransferType getDataTransferType() { + return transferType; + } + + public DatumVector read(ByteBuffer input) { + return DatumVector.newDatumVector(new double[]{transferType.read(input)}, units); + } + + public void write(DatumVector input, ByteBuffer output) { + transferType.write(input.doubleValue(0, units), output); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("y"); + element.setAttribute("units", units.toString()); + element.setAttribute("type", transferType.toString()); + if (!name.equals("")) { + element.setAttribute("name", name); + } + return element; + } + + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException cnse) { + throw new RuntimeException(cnse); + } + } + + Map properties = new HashMap(); + + public Object getProperty(String name) { + return properties.get(name); + } + + public Map getProperties() { + return new HashMap(properties); + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamProcessor.java b/dasCore/src/main/java/org/das2/stream/StreamProcessor.java new file mode 100755 index 000000000..8738e60c6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamProcessor.java @@ -0,0 +1,49 @@ +/* + * StreamProcessor.java + * + * Created on December 16, 2003, 10:09 AM + */ + +package org.das2.stream; + +import org.das2.DasProperties; +import java.io.*; + +/** + * + * @author Jeremy + */ +public abstract class StreamProcessor { + + public abstract void process( InputStream in, OutputStream out ) throws IOException ; + + public InputStream process(final InputStream in) throws IOException { + + final PipedOutputStream out= new PipedOutputStream(); + final PipedInputStream pin= new PipedInputStream(out); + Runnable r= new Runnable() { + public void run() { + try { + process( in, out ); + out.close(); + } catch ( IOException e ) { + try { + out.write( getDasExceptionStream(e) ); + } catch ( IOException e2 ) { + DasProperties.getLogger().severe(e2.toString()); + throw new RuntimeException(e2); + } + } + } + }; + Thread t= new Thread(r); + t.start(); + return pin; + + } + + public byte[] getDasExceptionStream( Throwable t ) { + String exceptionString= "[xx]"; + return exceptionString.getBytes(); + } +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamProducer.java b/dasCore/src/main/java/org/das2/stream/StreamProducer.java new file mode 100644 index 000000000..f58991c60 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamProducer.java @@ -0,0 +1,291 @@ +/* File: StreamProducer.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 12, 2004, 2:59 PM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import org.das2.util.DeflaterChannel; +import org.das2.util.StreamTool; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.util.*; +import java.util.IdentityHashMap; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * + * @author eew + */ +public class StreamProducer implements StreamHandler { + + private Map descriptors = new IdentityHashMap(); + private Map idMap = new HashMap(); + private WritableByteChannel stream; + private ByteBuffer bigBuffer = ByteBuffer.allocate(4096); + private byte[] six = new byte[6]; + private int nextAvail = 1; + private DocumentBuilder builder; + + private static class IdentitySet extends AbstractCollection implements Set { + + private IdentityHashMap map = new IdentityHashMap(); + private static final Object VALUE = new Object(); + + public Iterator iterator() { + return map.keySet().iterator(); + } + + public int size() { + return map.size(); + } + + } + + /** Creates a new instance of StreamProducer */ + public StreamProducer(WritableByteChannel stream) { + this.stream = stream; + try { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch (ParserConfigurationException pce) { + throw new RuntimeException(pce); + } + } + + public void packet(PacketDescriptor pd, Datum xTag, DatumVector[] vectors) throws StreamException { + try { + if (!descriptors.containsKey(pd)) { + packetDescriptor(pd); + } + String header = (String)descriptors.get(pd); + if (pd.getSizeBytes() > bigBuffer.capacity()) { + resizeBuffer(pd.getSizeBytes() + pd.getSizeBytes() >> 1); + } + if ((pd.getSizeBytes() + 4) > bigBuffer.remaining()) { + flush(); + } + six[0] = six[3] = (byte)':'; + six[1] = (byte)header.charAt(0); + six[2] = (byte)header.charAt(1); + bigBuffer.put(six, 0, 4); + pd.getXDescriptor().writeDatum(xTag, bigBuffer); + for (int i = 0; i < pd.getYCount(); i++) { + pd.getYDescriptor(i).write(vectors[i], bigBuffer); + } + //Ascii format hack + int lastChar = bigBuffer.position() - 1; + SkeletonDescriptor lastY = pd.getYDescriptor(pd.getYCount() - 1); + if (((lastY instanceof StreamYScanDescriptor + && ((StreamYScanDescriptor)lastY).getDataTransferType().isAscii()) + || (lastY instanceof StreamMultiYDescriptor + && ((StreamMultiYDescriptor)lastY).getDataTransferType().isAscii())) + && Character.isWhitespace((char)bigBuffer.get(lastChar))) { + bigBuffer.put(lastChar, (byte)'\n'); + } + //End of Ascii format hack + bigBuffer.flip(); + stream.write(bigBuffer); + bigBuffer.compact(); + } + catch (IOException ioe) { + throw new StreamException(ioe); + } + } + + private int nextAvailable() { + int result = nextAvail; + if (nextAvail == 99) { + nextAvail = 1; + } + else { + nextAvail++; + } + return result; + } + + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + try { + String id = toString2(nextAvailable()); + if (idMap.containsKey(id)) { + Object d = idMap.get(id); + descriptors.remove(d); + } + descriptors.put(pd, id); + idMap.put(id, pd); + Document document = builder.newDocument(); + Element root = pd.getDOMElement(document); + document.appendChild(root); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(out, "US-ASCII"); + StreamTool.formatHeader(document, writer); + writer.append("\n"); + writer.flush(); + byte[] header = out.toByteArray(); + int length = header.length; + if (bigBuffer.remaining() < (length + 10)) { + flush(); + } + if (bigBuffer.capacity() < (length + 10)) { + resizeBuffer(length + (length / 2) + 15); + System.err.println("length: " + length); + } + six[0] = '['; + six[1] = (byte)id.charAt(0); + six[2] = (byte)id.charAt(1); + six[3] = ']'; + bigBuffer.put(six, 0, 4); + six[0] = (byte)Character.forDigit((length / 100000) % 10, 10); + six[1] = (byte)Character.forDigit((length / 10000) % 10, 10); + six[2] = (byte)Character.forDigit((length / 1000) % 10, 10); + six[3] = (byte)Character.forDigit((length / 100) % 10, 10); + six[4] = (byte)Character.forDigit((length / 10) % 10, 10); + six[5] = (byte)Character.forDigit(length % 10, 10); + bigBuffer.put(six); + bigBuffer.put(header); + bigBuffer.flip(); + stream.write(bigBuffer); + bigBuffer.compact(); + } + catch (IOException ioe) { + throw new StreamException(ioe); + } + } + + public void resizeBuffer(int size) throws StreamException { + flush(); + System.err.println("resizeBuffer(" + size + ")"); + bigBuffer = ByteBuffer.allocate(size); + } + + public void streamClosed(StreamDescriptor sd) throws StreamException { + flush(); + try { + stream.close(); + } + catch (IOException ioe) { + throw new StreamException(ioe); + } + } + + public void streamDescriptor(StreamDescriptor sd) throws StreamException { + try { + Document document = builder.newDocument(); + Element root = sd.getDOMElement(document); + document.appendChild(root); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStreamWriter writer = new OutputStreamWriter(out, "US-ASCII"); + StreamTool.formatHeader(document, writer); + writer.append("\n"); + writer.flush(); + byte[] header = out.toByteArray(); + int length = header.length; + six[0] = '['; + six[1] = six[2] = '0'; + six[3] = ']'; + bigBuffer.put(six, 0, 4); + six[0] = (byte)Character.forDigit((length / 100000) % 10, 10); + six[1] = (byte)Character.forDigit((length / 10000) % 10, 10); + six[2] = (byte)Character.forDigit((length / 1000) % 10, 10); + six[3] = (byte)Character.forDigit((length / 100) % 10, 10); + six[4] = (byte)Character.forDigit((length / 10) % 10, 10); + six[5] = (byte)Character.forDigit(length % 10, 10); + bigBuffer.put(six); + bigBuffer.put(header); + flush(); + if ("deflate".equals(sd.getCompression())) { + stream = getDeflaterChannel(stream); + } + } + catch (IOException ioe) { + throw new StreamException(ioe); + } + } + + public void streamException(StreamException se) throws StreamException { + } + + + + public void flush() throws StreamException { + try { + bigBuffer.flip(); + while (bigBuffer.hasRemaining()) { + stream.write(bigBuffer); + } + bigBuffer.clear(); + } + catch (IOException ioe) { + throw new StreamException(ioe); + } + } + + private static String toString4(int i) { + if (i > 9999) { + throw new IllegalArgumentException("header is too big"); + } + else if (i < 10) { + return "000" + i; + } + else if (i < 100) { + return "00" + i; + } + else if (i < 1000) { + return "0" + i; + } + else { + return String.valueOf(i); + } + } + + private static String toString2(int i) { + if (i > 99) { + throw new IllegalArgumentException("header number cannot be > 99"); + } + else if (i < 10) { + return "0" + i; + } + else { + return String.valueOf(i); + } + } + + private static WritableByteChannel getDeflaterChannel(WritableByteChannel channel) throws IOException { + return new DeflaterChannel(channel); + //return Channels.newChannel(new DeflaterOutputStream(Channels.newOutputStream(channel))); + } + + public void streamComment(StreamComment sc) throws StreamException { + } + +} + diff --git a/dasCore/src/main/java/org/das2/stream/StreamRequirements.txt b/dasCore/src/main/java/org/das2/stream/StreamRequirements.txt new file mode 100755 index 000000000..b48d84b8b --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamRequirements.txt @@ -0,0 +1,55 @@ +Requirements for das2 Streams + +1. self describing + 1. the stream must contain all information needed to be "useful." + 2. dataset interface in das2 defines useful. +2. extensible + 1. metadata, annotating data ( e.g. via xml headers ) + 2. introduction of new data types ( yscan, multiy, ... ) +3. mode changes, table geometry +4. multiple data "planes" + 1. define planes as datasets that share tags + 2. e.g. Peaks and Averages +5. supports data compression + 1. compression type documented + 2. internal typeness preserved +6. allow for progress indication during transport + 1. total size attribute + 2. indication of parametric tag location + ( the tableBuilder knows the start,end parameters, plus the last paremeter + read from the stream, therefore progress can be indicated. ) +7. streamable so that there needn't be any server side storage +8. chainable operators can be built without difficulty + 1. reduction, fft, peaksAndAverages +9. non-streaming operators + 1. append +10. parallelizable + 1. divide request into set of subtasks that can be easily combined (e.g. via append) + 2. redirect to workers +11. easily appendable + 1. caching becomes trivial when a cache of streams can be collected and easily + appended to satisfy the request + 2. support for parallelizable +12. das1 streams can be wrapped to make das2 streams +13. easily identified (e.g. "das2" is first four bytes) +14. non-monotonic tags +15. indication of failure modes, heartbeats + + +Existing das2Stream lacks support for: + 2.2, 3, 6.2, 10 + +I'm providing a glossary in case I'm misusing words, and so we share a common +core vocabulary when talking about these things. + + reduction: reduction of a dataset statistically, such as bin averaging + compaction: lossless compression of a data set for transmission or storage, such as gzip + planes: datasets that share tags + tags: data that indicates the context of science data, such as a timetag. + For efficiency, tags are generally required to be monotonically increasing. + worker: server that satisfies requests of a master server + scalability: ability to handle exponentially increasing demands. + metadata: data about data + annotating data: data useful for documentation but not required for normal operations + extensibility: ability to expand functionality + streamable: not requiring temporary local storage of data. diff --git a/dasCore/src/main/java/org/das2/stream/StreamUtil.java b/dasCore/src/main/java/org/das2/stream/StreamUtil.java new file mode 100644 index 000000000..40d4af200 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamUtil.java @@ -0,0 +1,71 @@ +/* + * Util.java + * + * Created on September 23, 2005, 1:01 PM + * + * + */ + +package org.das2.stream; + +import org.das2.DasException; +import org.das2.client.DataSetStreamHandler; +import org.das2.dataset.DataSet; +import org.das2.dataset.DataSetDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.StreamTool; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.channels.ReadableByteChannel; +import java.util.HashMap; + +/** + * + * @author Jeremy + */ +public class StreamUtil { + + private static final String DATA_SET_ID_PREFIX + = "class:org.das2.stream.test.LocalFileStandardDataStreamSource?file="; + + public static TableDataSet loadTableDataSet( String filename ) { + try { + filename= URLEncoder.encode(filename,"UTF-8"); + DataSetDescriptor dsd= DataSetDescriptor.create( DATA_SET_ID_PREFIX+filename ); + dsd.setDefaultCaching(false); + DataSet ds= dsd.getDataSet(null,null,null,null); + return (TableDataSet)ds; + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException(e); + } catch ( DasException e ) { + throw new RuntimeException(e); + } + } + + public static DataSet loadDataSetNew( String filename ) throws IOException, StreamException { + FileInputStream in= new FileInputStream( filename ); + ReadableByteChannel channel = in.getChannel(); + + DataSetStreamHandler handler = new DataSetStreamHandler( new HashMap(), new NullProgressMonitor() ); + + StreamTool.readStream(channel, handler); + return handler.getDataSet(); + } + + public static DataSet loadDataSet( String filename ) { + try { + filename= URLEncoder.encode(filename,"UTF-8"); + DataSetDescriptor dsd= DataSetDescriptor.create( DATA_SET_ID_PREFIX+filename ); + dsd.setDefaultCaching(false); + DataSet ds= dsd.getDataSet(null,null,null,null); + return ds; + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException(e); + } catch ( DasException e ) { + throw new RuntimeException(e); + } + } +} diff --git a/dasCore/src/main/java/org/das2/stream/StreamXDescriptor.java b/dasCore/src/main/java/org/das2/stream/StreamXDescriptor.java new file mode 100644 index 000000000..6336c54f5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamXDescriptor.java @@ -0,0 +1,174 @@ +/* File: StreamXDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.stream; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import org.das2.datum.TimeLocationUnits; +import org.das2.datum.TimeUtil; +import org.das2.datum.Units; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class StreamXDescriptor implements SkeletonDescriptor, Cloneable { + + private Datum base; + + private Units baseUnits = Units.us2000; + + private Units units = null; + + private DataTransferType transferType = DataTransferType.SUN_REAL4; + + public StreamXDescriptor() { + } + + public StreamXDescriptor( Element element ) { + if ( element.getTagName().equals("x") ) { + processElement(element); + } + else { + processLegacyElement(element); + } + } + + private void processElement( Element element ) { + String typeStr = element.getAttribute("type"); + DataTransferType type = DataTransferType.getByName(typeStr); + if ( type != null ) { + transferType = type; + } + else { + throw new RuntimeException("Illegal transfer type: " + typeStr); + } + if ( type instanceof DataTransferType.Time ) { + units= ((DataTransferType.Time)type).getUnits(); + } else { + String unitsString = element.getAttribute("units"); + units = Units.lookupUnits(unitsString); + } + + String baseString = element.getAttribute("base"); + if (baseString != null && !baseString.equals("")) { + if (baseUnits instanceof TimeLocationUnits) { + base = TimeUtil.createValid(baseString); + } + } + } + + private void processLegacyElement( Element element ) { + String typeStr = element.getAttribute("type"); + DataTransferType type = DataTransferType.getByName(typeStr); + if (type != null) { + transferType = type; + } + else { + throw new RuntimeException("Illegal transfer type: " + typeStr); + } + } + + public Datum getBase() { + return base; + } + + public void setBase(Datum base) { + this.base = base; + } + + public int getSizeBytes() { + return transferType.getSizeBytes(); + } + + public Units getUnits() { + return units; + } + + /* Units must be set now!!! */ + public void setUnits(Units units) { + this.units = units; + } + + public void setDataTransferType(DataTransferType transferType) { + this.transferType = transferType; + if ( transferType instanceof DataTransferType.Time ) { + if ( units==null ) throw new IllegalArgumentException("please set the units first!!!"); + ((DataTransferType.Time)transferType).units= units; + } + } + + public DataTransferType getDataTransferType() { + return transferType; + } + + + public Datum readDatum(ByteBuffer input) { + return Datum.create(transferType.read(input), units); + } + + public DatumVector read(ByteBuffer input) { + return DatumVector.newDatumVector(new double[]{transferType.read(input)}, units); + } + + public void writeDatum(Datum datum, ByteBuffer output) { + transferType.write(datum.doubleValue(units), output); + } + + public void write(DatumVector input, ByteBuffer output) { + transferType.write(input.doubleValue(0, units), output); + } + + public Element getDOMElement(Document document) { + Element element = document.createElement("x"); + if (base != null) { + element.setAttribute("base", base.toString()); + } + element.setAttribute("units", units.toString()); + element.setAttribute("type", transferType.toString()); + return element; + } + + public Object clone() { + try { + return super.clone(); + } + catch (CloneNotSupportedException cnse) { + throw new RuntimeException(cnse); + } + } + + Map properties= new HashMap(); + + public Object getProperty(String name) { + return properties.get(name); + } + + public Map getProperties() { + return new HashMap(properties); + } + +} + diff --git a/dasCore/src/main/java/org/das2/stream/StreamYScanDescriptor.java b/dasCore/src/main/java/org/das2/stream/StreamYScanDescriptor.java new file mode 100755 index 000000000..91bab3a07 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StreamYScanDescriptor.java @@ -0,0 +1,240 @@ +/* File: StreamYScanDescriptor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.stream; + +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class StreamYScanDescriptor implements SkeletonDescriptor, Cloneable { + + private Units yUnits = Units.dimensionless; + private Units zUnits = Units.dimensionless; + private double[] yTags; + private int nitems; + private String name = ""; + private DataTransferType transferType = DataTransferType.SUN_REAL4; + + public StreamYScanDescriptor( Element element ) { + if ( element.getTagName().equals("yscan") ) { + processElement(element); + } + else { + processLegacyElement(element); + } + } + + private void processElement(Element element) { + nitems = Integer.parseInt(element.getAttribute("nitems")); + String yTagsText = element.getAttribute("yTags"); + if (yTagsText != null) { + yTags = new double[nitems]; + int parseInt = 0; + String[] tokens = yTagsText.split("\\s*,\\s*"); + for (int i = 0; i < nitems; i++) { + yTags[i] = Double.parseDouble(tokens[i]); + } + } + String typeStr = element.getAttribute("type"); + DataTransferType type = DataTransferType.getByName(typeStr); + if (type != null) { + transferType = type; + } + else { + throw new RuntimeException("Illegal transfer type: " + typeStr); + } + String yUnitsString = element.getAttribute("yUnits"); + if (yUnitsString != null) { + yUnits = Units.lookupUnits(yUnitsString); + } + String zUnitsString = element.getAttribute("zUnits"); + if (zUnitsString != null) { + zUnits = Units.lookupUnits(zUnitsString); + } + String name = element.getAttribute("name"); + if ( name != null ) { + this.name = name; + } + } + + private void processLegacyElement(Element element) { + try { + if ( !element.getTagName().equals("YScan") ) { + throw new IllegalArgumentException("xml tree root node is not the right type. "+ + "Node type is: "+element.getTagName()); + } + nitems= Integer.parseInt(element.getAttribute("nitems")); + if ( element.getAttribute("yCoordinate") != null ) { + String yCoordinateString= element.getAttribute("yCoordinate"); + yTags= new double[nitems]; + int parseIdx=0; + for (int i=0; i"; + } + + Map properties= new HashMap(); + + public Object getProperty(String name) { + return properties.get(name); + } + + public Map getProperties() { + return new HashMap(properties); + } + +} + diff --git a/dasCore/src/main/java/org/das2/stream/StripHeader.java b/dasCore/src/main/java/org/das2/stream/StripHeader.java new file mode 100755 index 000000000..60f3227cb --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/StripHeader.java @@ -0,0 +1,27 @@ +/* + * StripHeader.java + * + * Created on December 11, 2003, 12:35 PM + */ + +package org.das2.stream; + +import org.das2.util.StreamTool; +import java.io.*; + +/** + * + * @author Jeremy + */ +public class StripHeader { + + public static void stripHeader( InputStream in, OutputStream out ) throws IOException { + byte[] header= StreamTool.readXML(new PushbackInputStream(in)); + out.write(header); + } + + public static void main(String[] args) throws IOException { + stripHeader( System.in, System.out ); + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/TAvStreamProcessor.java b/dasCore/src/main/java/org/das2/stream/TAvStreamProcessor.java new file mode 100644 index 000000000..33622d2c9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/TAvStreamProcessor.java @@ -0,0 +1,57 @@ +/* + * gzip.java + * + * Created on July 11, 2003, 9:39 AM + */ + +package org.das2.stream; + + +import java.io.*; + + +/** + * + * @author jbf + */ +public class TAvStreamProcessor extends StreamProcessor { + + public void process(InputStream in, OutputStream out) throws IOException { + + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + InputStream in=null; + OutputStream out=null; + if ( args.length>0 ) { + try { + in= new FileInputStream(args[0]); + } catch ( FileNotFoundException ex) { + System.err.println("Input file not found"); + System.exit(-1); + } + } else { + in= System.in; + } + + if ( args.length>1 ) { + try { + out= new FileOutputStream(args[1]); + } catch ( FileNotFoundException ex) { + } + + } else { + out= System.out; + } + + try { + new TAvStreamProcessor().process(in,out); + } catch ( IOException ex) { + System.err.println(ex.getMessage()); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/ToAscii.java b/dasCore/src/main/java/org/das2/stream/ToAscii.java new file mode 100644 index 000000000..d1a4bf61c --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/ToAscii.java @@ -0,0 +1,86 @@ +/* File: ToAscii.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 17, 2004, 11:36 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream; + +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * + * @author eew + */ +public class ToAscii implements StreamHandler { + + private Map descriptors = new IdentityHashMap(); + private StreamHandler handler; + + /** Creates a new instance of ToAscii */ + public ToAscii(StreamHandler handler) { + this.handler = handler; + } + + public void packet(PacketDescriptor pd, Datum xTag, DatumVector[] vectors) throws StreamException { + PacketDescriptor outpd = (PacketDescriptor)descriptors.get(pd); + handler.packet(outpd, xTag, vectors); + } + + public void packetDescriptor(PacketDescriptor pd) throws StreamException { + DataTransferType ascii24 = DataTransferType.getByName("ascii24"); + DataTransferType ascii10 = DataTransferType.getByName("ascii10"); + PacketDescriptor outpd = (PacketDescriptor)pd.clone(); + outpd.getXDescriptor().setDataTransferType(ascii24); + for (int i = 0; i < outpd.getYCount(); i++) { + if (outpd.getYDescriptor(i) instanceof StreamMultiYDescriptor) { + ((StreamMultiYDescriptor)outpd.getYDescriptor(i)).setDataTransferType(ascii10); + } + else if (outpd.getYDescriptor(i) instanceof StreamYScanDescriptor) { + ((StreamYScanDescriptor)outpd.getYDescriptor(i)).setDataTransferType(ascii10); + } + } + handler.packetDescriptor(outpd); + descriptors.put(pd, outpd); + } + + public void streamClosed(StreamDescriptor sd) throws StreamException { + handler.streamClosed(sd); + } + + public void streamDescriptor(StreamDescriptor sd) throws StreamException { + handler.streamDescriptor(sd); + } + + public void streamException(StreamException se) throws StreamException { + handler.streamException(se); + } + + public void streamComment(StreamComment se) throws StreamException { + handler.streamComment(se); + } + + public void packet(PacketDescriptor pd, DatumVector vector) throws StreamException { + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/package.html b/dasCore/src/main/java/org/das2/stream/package.html new file mode 100644 index 000000000..a138539b8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/package.html @@ -0,0 +1,9 @@ + +

    + Provides classes for parsing and formatting das2Streams, and a few stream +proof-of-concept stream operators that are not used. das2Streams are self-describing +data streams suitable for transmitting DataSets. A C library for reading and writing +streams is available and provides a means to convey large amounts of data between +C and java processes. +

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/stream/test/LocalFileStandardDataStreamSource.java b/dasCore/src/main/java/org/das2/stream/test/LocalFileStandardDataStreamSource.java new file mode 100644 index 000000000..8fe12a195 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/test/LocalFileStandardDataStreamSource.java @@ -0,0 +1,74 @@ +/* File: LocalFileStandardDataStreamSource.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on January 14, 2004, 3:26 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream.test; + +import org.das2.client.StandardDataStreamSource; +import org.das2.client.DataSetDescriptorNotAvailableException; +import org.das2.client.StreamDataSetDescriptor; +import org.das2.DasException; +import org.das2.DasIOException; +import org.das2.dataset.DataSetDescriptor; +import org.das2.datum.Datum; + +import java.io.*; +import java.util.Map; +import org.das2.stream.StreamDescriptor; + +/** + * + * @author Edward West + */ +public class LocalFileStandardDataStreamSource implements StandardDataStreamSource { + + private File file; + + /** Creates a new instance of LocalFileStandardDataStreamSource */ + public LocalFileStandardDataStreamSource(File file) { + this.file = file; + } + + public InputStream getInputStream(StreamDataSetDescriptor dsd, Datum start, Datum end) throws DasException { + try { + return new FileInputStream(file); + } + catch (IOException ioe) { + throw new DasIOException(ioe); + } + } + + public InputStream getReducedInputStream(StreamDataSetDescriptor dsd, Datum start, Datum end, Datum timeResolution) throws DasException { + return getInputStream(dsd, start, end); + } + + public void reset() { + } + + public static DataSetDescriptor newDataSetDescriptor(Map map) throws DataSetDescriptorNotAvailableException { + String filename = (String)map.get("file"); + File file = new File(filename); + StreamDescriptor sd = new StreamDescriptor(); + return new StreamDataSetDescriptor(sd, new LocalFileStandardDataStreamSource(file)); + } + +} diff --git a/dasCore/src/main/java/org/das2/stream/test/RipplesStream.java b/dasCore/src/main/java/org/das2/stream/test/RipplesStream.java new file mode 100644 index 000000000..1a24f8a71 --- /dev/null +++ b/dasCore/src/main/java/org/das2/stream/test/RipplesStream.java @@ -0,0 +1,123 @@ +/* File: RipplesStream.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on March 29, 2004, 10:13 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.stream.test; + +import org.das2.datum.Units; +import org.das2.datum.DatumVector; +import org.das2.datum.Datum; +import java.nio.channels.*; +import java.util.*; +import org.das2.stream.DataTransferType; +import org.das2.stream.PacketDescriptor; +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.StreamProducer; +import org.das2.stream.StreamXDescriptor; +import org.das2.stream.StreamYScanDescriptor; + +/** + * + * @author eew + */ +public class RipplesStream { + + private boolean compress; + + double x1, y1, p1, x2, y2, p2; + + /** Creates a new instance of RipplesStream */ + public RipplesStream( boolean compress ) { + this( 14, 17, 10, 20, 60, 15, compress ); + } + + /** Creates a new instance of RipplesDataSetDescriptor */ + public RipplesStream( double x1, double y1, double p1, double x2, double y2, double p2, boolean compress ) { + this.x1= x1; + this.y1= y1; + this.p1= p1; + this.x2= x2; + this.y2= y2; + this.p2= p2; + this.compress = compress; + } + + public void produceRipplesStream(WritableByteChannel out) { + try { + StreamProducer producer = new StreamProducer(out); + StreamDescriptor sd = new StreamDescriptor(); + if (compress) { sd.setCompression("deflate"); } + producer.streamDescriptor(sd); + + int nx=100; + int ny=100; + + double[] y= new double[ny]; + for (int j=0; j +Classes for producing das2 Streams, useful for fun and testing only. +LocalFileStandardDataStreamSource presents + a local file to das2 innards as a StandardDataStreamSource for testing. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/system/ConsoleExceptionHandler.java b/dasCore/src/main/java/org/das2/system/ConsoleExceptionHandler.java new file mode 100644 index 000000000..461f4fb53 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/ConsoleExceptionHandler.java @@ -0,0 +1,30 @@ +/* + * ConsoleExceptionHandler.java + * + * Created on November 16, 2006, 12:34 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.system; + +/** + * ExceptionHandler that prints stack traces out to the stderr. + * @author jbf + */ +public class ConsoleExceptionHandler implements ExceptionHandler { + + /** Creates a new instance of ConsoleExceptionHandler */ + public ConsoleExceptionHandler() { + } + + public void handle(Throwable t) { + t.printStackTrace(); + } + + public void handleUncaught(Throwable t) { + t.printStackTrace(); + } + +} diff --git a/dasCore/src/main/java/org/das2/system/ContextMonitorFactory.java b/dasCore/src/main/java/org/das2/system/ContextMonitorFactory.java new file mode 100644 index 000000000..473fe2cd9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/ContextMonitorFactory.java @@ -0,0 +1,29 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.system; + +import org.das2.graph.DasCanvasComponent; +import org.das2.util.monitor.ProgressMonitor; + +/** + * + * @author eew + */ +public class ContextMonitorFactory extends DefaultMonitorFactory { + + private DasCanvasComponent context; + + public ContextMonitorFactory(DasCanvasComponent context) { + if (context == null) throw new NullPointerException(); + this.context = context; + } + + @Override + public ProgressMonitor getMonitor(String label, String description) { + return super.getMonitor(context, label, description); + } + +} diff --git a/dasCore/src/main/java/org/das2/system/DasLogger.java b/dasCore/src/main/java/org/das2/system/DasLogger.java new file mode 100644 index 000000000..24fddafca --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/DasLogger.java @@ -0,0 +1,174 @@ +/* + * DasLogger.java + * + * Created on June 6, 2005, 12:12 PM + */ + +package org.das2.system; + +import org.das2.DasApplication; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.logging.*; + +/** Das Logging Facility + * + * This class was created before Java had a standard log facility. Don't use it for new + * code. Use the standard Java Logger instead and get logger names from the LogCategory + * class. + * + * @author Jeremy + */ +@Deprecated +public class DasLogger { + + public static void reload() throws IOException { + try { + java.net.URL logConfigURL; + + File local; + if ( DasApplication.getProperty("user.name","applet").equals("Web") ) { + local= new File("/tmp"); + } else if ( DasApplication.getProperty("user.name","applet").equals("applet") ) { + return; + } else { + local= new File( DasApplication.getProperty("user.home","applet") ); + } + + File userDirectory=new File( local, ".das2" ); + File localLogConfig= new File( userDirectory, "logging.properties" ); + + if ( localLogConfig.exists() ) { + Logger.getLogger("").info("using "+localLogConfig); + logConfigURL= localLogConfig.toURI().toURL(); + } else { + logConfigURL= DasLogger.class.getResource("logging.properties"); + } + if ( logConfigURL==null ) { + System.err.println("unable to locate logging properties file logging.properties, using defaults"); + } else { + //dumpUrl(logConfigURL); + InputStream in= logConfigURL.openStream(); + LogManager.getLogManager().readConfiguration( in ); + in.close(); + //System.err.println( "read log configuration from "+logConfigURL ); + //printStatus(); + } + } catch ( MalformedURLException e ) { + throw new RuntimeException(e); // this shouldn't happen + } + } + + private static void dumpUrl( URL url ) throws IOException { + BufferedReader reader= new BufferedReader( new InputStreamReader( url.openStream() ) ); + String s= reader.readLine(); + while( s!=null ) { + System.out.println(s); + s= reader.readLine(); + } + reader.close(); + + } + + public static void printStatus() { + String[] loggers= new String[] { "", "das2.system", "das2.gui", "das2.graphics", "das2.dataOperations", "das2.dataTransfer", }; + for ( int i=0; i 50 ) + desc= "..."+description.substring( description.length() - 50 ); + return String.valueOf(monitor)+" "+desc; + } + + } + + + public ProgressMonitor getMonitor(DasCanvas canvas, String label, String description ) { + ProgressMonitor result= DasProgressPanel.createComponentPanel( canvas, label ); + putMonitor( result, label, description ); + return result; + } + + public ProgressMonitor getMonitor( DasCanvasComponent context, String label, String description ) { + ProgressMonitor result= DasProgressPanel.createComponentPanel( context, label ); + putMonitor( result, label, description ); + return result; + } + + public ProgressMonitor getMonitor( String label, String description ) { + ProgressMonitor result= DasProgressPanel.createFramed( label ); + putMonitor( result, label, description ); + return result; + } + + private void putMonitor( ProgressMonitor monitor, String label, String description ) { + Long key= new Long( System.currentTimeMillis() ); + monitors.put( key, new MonitorEntry( monitor, description ) ); + } + + public MonitorEntry[] getMonitors() { + Collection set= monitors.values(); + return (MonitorEntry[])set.toArray( new MonitorEntry[ set.size() ] ); + } + + public MonitorEntry getMonitors( int i ) { + return getMonitors()[i]; + } + + public void setClear( boolean clear ) { + if ( clear ) { + monitors.keySet().removeAll(monitors.keySet()); + } + } + + public boolean isClear() { + return false; + } + + + +} diff --git a/dasCore/src/main/java/org/das2/system/ExceptionHandler.java b/dasCore/src/main/java/org/das2/system/ExceptionHandler.java new file mode 100644 index 000000000..d648060f8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/ExceptionHandler.java @@ -0,0 +1,19 @@ +/* + * ExceptionHandler.java + * + * Created on November 16, 2006, 12:31 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.system; + +/** + * + * @author jbf + */ +public interface ExceptionHandler { + void handle(Throwable t); + void handleUncaught(Throwable t); +} diff --git a/dasCore/src/main/java/org/das2/system/LogCategory.java b/dasCore/src/main/java/org/das2/system/LogCategory.java new file mode 100644 index 000000000..3a40f50d5 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/LogCategory.java @@ -0,0 +1,40 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.system; + +/** This class takes the strings from DasLogger, and uses them as names for the + * standard Java Logging facility + * + * Original Constants by jbf, copied here by cwp + */ +public class LogCategory{ + /** messages having to do with the application-specific Das 2 Application */ + public static final String APPLICATION_LOG = ""; + + /** system messages such as RequestProcessor activity*/ + public static final String SYSTEM_LOG = "das2.system"; + + /** events, gestures, user feedback */ + public static final String GUI_LOG = "das2.gui"; + + /** renders, drawing */ + public static final String GRAPHICS_LOG = "das2.graphics"; + + /** renderer's logger */ + public static final String RENDERER_LOG = "das2.graphics"; + + /** rebinning and dataset operators */ + public static final String DATA_OPERATIONS_LOG = "das2.dataOperations"; + + /** internet transactions, file I/O */ + public static final String DATA_TRANSFER_LOG = "das2.dataTransfer"; + + /** virtual file system activities */ + public static final String FILESYSTEM_LOG = "das2.filesystem"; + + /** das2 application description files */ + public static final String DASML_LOG = "das2.dasml"; +} diff --git a/dasCore/src/main/java/org/das2/system/LoggerId.java b/dasCore/src/main/java/org/das2/system/LoggerId.java new file mode 100644 index 000000000..05924e6c6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/LoggerId.java @@ -0,0 +1,29 @@ +/* + * LoggerId.java + * + * Created on June 6, 2005, 12:24 PM + */ + +package org.das2.system; + +import java.util.logging.*; + +/** + * + * @author Jeremy + */ +public class LoggerId { + private String name; + private Logger logger; + public LoggerId( String name ) { // public to change to protected after DasApplication.getLogger factored out + this.name= name; + this.logger= Logger.getLogger(name); + this.logger.fine( name +" logging at "+this.logger.getLevel() ); + } + public String toString() { + return this.name; + } + Logger getLogger() { + return this.logger; + } +} diff --git a/dasCore/src/main/java/org/das2/system/MonitorFactory.java b/dasCore/src/main/java/org/das2/system/MonitorFactory.java new file mode 100644 index 000000000..bf4c10b04 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/MonitorFactory.java @@ -0,0 +1,35 @@ +/* + * MonitorFactory.java + * + * Created on November 13, 2006, 11:55 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.system; + +import org.das2.graph.DasCanvas; +import org.das2.graph.DasCanvasComponent; +import org.das2.util.monitor.ProgressMonitor; + +/** + * + * @author jbf + */ +public interface MonitorFactory { + + ProgressMonitor getMonitor(DasCanvas canvas, String string, String string0); + + /** + * returns a monitor in the given context. For example, if the user is waiting for a DasPlot to be drawn, then + * the context is the plot, and therefore a DasProgressPanel will be added on top of the plot. + */ + ProgressMonitor getMonitor(DasCanvasComponent context, String label, String description); + + /** + * returns a monitor without regard to context. + */ + ProgressMonitor getMonitor(String label, String description); + +} diff --git a/dasCore/src/main/java/org/das2/system/MutatorLock.java b/dasCore/src/main/java/org/das2/system/MutatorLock.java new file mode 100644 index 000000000..9b9df489c --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/MutatorLock.java @@ -0,0 +1,17 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.system; + +/** + * see DasAxis.getMutatorLock, DasDevicePosition.getMutatorLock + * + * @author jbf + */ +public interface MutatorLock { + + public void lock(); + + public void unlock(); +} diff --git a/dasCore/src/main/java/org/das2/system/NullMonitorFactory.java b/dasCore/src/main/java/org/das2/system/NullMonitorFactory.java new file mode 100644 index 000000000..c73e75981 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/NullMonitorFactory.java @@ -0,0 +1,42 @@ +/* + * NullMonitorFactory.java + * + * Created on November 13, 2006, 11:59 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.system; + +import org.das2.graph.DasCanvas; +import org.das2.graph.DasCanvasComponent; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; + +/** + * MonitorFactory implementation that always returns a null monitor. + * @author jbf + */ +public class NullMonitorFactory implements MonitorFactory { + + public NullMonitorFactory() { + } + + private ProgressMonitor createNullMonitor() { + return new NullProgressMonitor(); + } + + public ProgressMonitor getMonitor(DasCanvasComponent context, String label, String description) { + return createNullMonitor(); + } + + public ProgressMonitor getMonitor(String label, String description) { + return createNullMonitor(); + } + + public ProgressMonitor getMonitor(DasCanvas canvas, String string, String string0) { + return createNullMonitor(); + } + +} diff --git a/dasCore/src/main/java/org/das2/system/NullPreferences.java b/dasCore/src/main/java/org/das2/system/NullPreferences.java new file mode 100755 index 000000000..54eacb1da --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/NullPreferences.java @@ -0,0 +1,62 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.system; + +import java.util.HashMap; +import java.util.Map; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; + +/** + * NullPreferences object for allowing applications to work in applet environment. + * Changes that would have been stored are simply ignored. + * @author jbf + */ +public class NullPreferences extends AbstractPreferences { + + Map values; + + public NullPreferences() { + super(null, ""); + values= new HashMap(); + } + + protected void putSpi(String key, String value) { + values.put(key, value); + } + + protected String getSpi(String key) { + return values.get(key); + } + + protected void removeSpi(String key) { + // do nothing + } + + protected void removeNodeSpi() throws BackingStoreException { + // do nothing + } + + protected String[] keysSpi() throws BackingStoreException { + return values.keySet().toArray(new String[values.size()]); + } + + protected String[] childrenNamesSpi() throws BackingStoreException { + return new String[0]; + } + + protected AbstractPreferences childSpi(String name) { + return new NullPreferences(); + } + + protected void syncSpi() throws BackingStoreException { + // do nothing + } + + protected void flushSpi() throws BackingStoreException { + // do nothing + } + +} diff --git a/dasCore/src/main/java/org/das2/system/NullPreferencesFactory.java b/dasCore/src/main/java/org/das2/system/NullPreferencesFactory.java new file mode 100755 index 000000000..08db59ce1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/NullPreferencesFactory.java @@ -0,0 +1,35 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.system; + +import java.util.prefs.Preferences; +import java.util.prefs.PreferencesFactory; + +/** + * Creates NullPreferences for use with applets. The system property + * java.util.prefs.Preferences should be set to + * org.das2.system.NullPreferencesFactory to use this class. + * + * System.setProperty( "java.util.prefs.Preferences", "org.das2.system.NullPreferencesFactory" ) doesn't + * work in applets--security exception. + + * @author jbf + */ +public class NullPreferencesFactory implements PreferencesFactory { + + public NullPreferencesFactory() { + System.err.println("using NullPreferencesFactory"); + } + + public Preferences systemRoot() { + return new NullPreferences(); + } + + public Preferences userRoot() { + return new NullPreferences(); + } + +} diff --git a/dasCore/src/main/java/org/das2/system/RequestProcessor.java b/dasCore/src/main/java/org/das2/system/RequestProcessor.java new file mode 100644 index 000000000..7f9dbdbc1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/RequestProcessor.java @@ -0,0 +1,306 @@ +/* File: TableDataSet.java + * Copyright (C) 2002-2004 The University of Iowa + * + * Created on June 1, 2004, 11:46 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.system; + +import org.das2.util.DasExceptionHandler; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.logging.Logger; + +/** Utility class for synchronous execution. + * This class maintains a pool of threads that are used to execute arbitrary + * code. This class also serves as a central place to catch and handle + * unchecked exceptions. + * + * The {@link #invokeLater(java.lang.Runnable)} method is similar to the + * SwingUtilities {@link javax.swing.SwingUtilities#invokeLater(java.lang.Runnable)} + * method, except that the request is not executed on the event thread. + * + * The {@link #invokeLater(java.lang.Runnable, java.lang.Object)}, + * the {@link #invokeAfter(java.lang.Runnable, java.lang.Object)}, + * and the {@link #waitFor(java.lang.Object)} methods are designed to work + * together. Both of the first two methods execute code asynchronously with + * respect to the calling thread. Multiple requests made with a call to + * invokeLater that specified the same lock can execute at the same time, + * but not while a request made with the invokeAfter with the same lock + * is processing. Any requests made before an invokeAfter request with the + * same lock will finish before that invokeAfter request begins. An + * invokeAfter request will finish before any requests with the same lock made + * after that invokeAfter request begins. The {@link #waitFor(java.lang.Object)} + * method will cause the calling thread to block until all requests with the + * specified lock finish. + */ +public final class RequestProcessor { + + private static final BlockingRequestQueue queue = new BlockingRequestQueue(); + private static final WeakHashMap runnableQueueMap = new WeakHashMap(); + private static final Runner runner = new Runner(); + + private static int maxThreadCount = 4; + private static int threadCount = 0; + private static final Object THREAD_COUNT_LOCK = new Object(); + + private final static Logger logger= DasLogger.getLogger( DasLogger.SYSTEM_LOG ); + + private static int threadOrdinal = 0; + + private RequestProcessor() {} + + private static void setJob(Runnable job) { + RequestThread thread = (RequestThread)Thread.currentThread(); + thread.setJob(job); + } + + private static class RequestThread extends Thread { + private WeakReference job; + private RequestThread(Runnable run, String name) { + super(run, name); + } + private void setJob(Runnable job) { + this.job = new WeakReference(job); + } + private Runnable getJob() { + return (Runnable)job.get(); + } + } + + private static void newThread() { + String name = "RequestProcessor[" + (threadOrdinal++) + "]"; + RequestThread t = new RequestThread(runner, name); + t.setPriority(Thread.NORM_PRIORITY); + t.start(); + } + + /** Executes run.run() asynchronously on a thread from the thread pool. + * @param run the task to be executed. + */ + public static void invokeLater(Runnable run) { + logger.fine("invokeLater "+run); + + synchronized (THREAD_COUNT_LOCK) { + if (threadCount < maxThreadCount) { + newThread(); + } + } + queue.add(run); + } + + /** Executes run.run() asynchronously on a thread from the thread pool. + * The task will not be executed until after all requests made with + * {@link #invokeAfter(java.lang.Runnable, java.lang.Object)} with the same + * lock have finished. + * @param run the taks to be executed. + * @param lock associates run with other tasks. + */ + public static void invokeLater(Runnable run, Object lock) { + logger.fine("invokeLater "+run+" "+lock); + synchronized (THREAD_COUNT_LOCK) { + if (threadCount < maxThreadCount) { + newThread(); + } + } + synchronized (runnableQueueMap) { + RunnableQueue rq = (RunnableQueue)runnableQueueMap.get(lock); + if (rq == null) { + rq = new RunnableQueue(); + runnableQueueMap.put(lock, rq); + } + rq.add(run, false); + queue.add(rq); + } + } + + /** Executes run.run() asynchronously on a thread from the thread pool. + * The task will not be executed until after all requests made with + * {@link #invokeAfter(java.lang.Runnable, java.lang.Object)} or + * {@link #invokeLater(java.lang.Runnable, java.lang.Object)} with the same + * lock have finished. + * @param run the taks to be executed. + * @param lock associates run with other tasks. + */ + public static void invokeAfter(Runnable run, Object lock) { + logger.fine("invokeAfter "+run+" "+lock); + synchronized (THREAD_COUNT_LOCK) { + if (threadCount < maxThreadCount) { + newThread(); + } + } + synchronized (runnableQueueMap) { + RunnableQueue rq = (RunnableQueue)runnableQueueMap.get(lock); + if (rq == null) { + rq = new RunnableQueue(); + runnableQueueMap.put(lock, rq); + } + rq.add(run, true); + queue.add(rq); + } + } + + /** Blocks until all tasks with the same lock have finished. + * @param lock + * @throws InterruptedException if the current thread is + * interrupted while waiting. + */ + public static void waitFor(Object lock) throws InterruptedException { + WaitTask wt = new WaitTask(); + synchronized (wt) { + while (true) { + invokeLater(wt, lock); + wt.wait(); + return; + } + } + } + + /* + public static int getMaximumThreadCount(int i) { + return maxThreadCount; + } + + public static void setMaximumThreadCount(int i) { + if (i < 5) { + throw new IllegalArgumentException("Must be >= 5: " + i); + } + maxThreadCount = i; + } + */ + + private static class Runner implements Runnable { + public void run() { + synchronized (THREAD_COUNT_LOCK) { + threadCount++; + } + try { + while (true) { + try { + Runnable run = queue.remove(); + logger.fine("running "+run); + if (run != null) { + setJob(run); + run.run(); + logger.fine("completed "+run); + } + synchronized (THREAD_COUNT_LOCK) { + if (threadCount > maxThreadCount) { + break; + } + } + } + catch (ThreadDeath td) { + // See documentation for ThreadDeath. If this error is caught but not thrown, then the thread doesn't die. + throw td; + } + catch (Throwable t) { + logger.fine("uncaught exception "+t); + DasExceptionHandler.handleUncaught(t); + //Clear interrupted status (if set) + Thread.interrupted(); + } + } + } + finally { + synchronized (THREAD_COUNT_LOCK) { + threadCount--; + } + } + } + } + + private static class WaitTask implements Runnable { + public synchronized void run() { + notifyAll(); + } + } + + private static class RunnableQueue implements Runnable { + + private LinkedList list = new LinkedList(); + private int readCount = 0; + private Object writer; + + public void run() { + Runnable run = null; + RequestEntry entry = null; + Logger logger = DasLogger.getLogger(DasLogger.SYSTEM_LOG); + while (run == null) { + synchronized (this) { + //entry = (RequestEntry)list.removeFirst(); + entry = (RequestEntry)list.getFirst(); + if (entry.async && readCount == 0 && writer == null) { + list.removeFirst(); + writer = entry; + run = entry.run; + } + else if (!entry.async && writer == null) { + list.removeFirst(); + readCount++; + run = entry.run; + } + } + } + logger.fine("Starting :" + run); + run.run(); + logger.fine("Finished :" + run); + synchronized (this) { + if (entry.async) { + writer = null; + } + else { + readCount--; + } + notifyAll(); + } + } + + synchronized void add(Runnable run, boolean async) { + RequestEntry entry = new RequestEntry(); + entry.run = run; + entry.async = async; + list.add(entry); + } + } + + private static class RequestEntry { Runnable run; boolean async; } + + private static class BlockingRequestQueue { + private LinkedList list; + + BlockingRequestQueue() { + list = new LinkedList(); + } + + synchronized void add(Runnable r) { + list.add(r); + notify(); + } + + synchronized Runnable remove() { + while (list.isEmpty()) { + try { wait(); } catch (InterruptedException ie) {}; + } + return (Runnable)list.removeFirst(); + } + + } +} diff --git a/dasCore/src/main/java/org/das2/system/ThrowRuntimeExceptionHandler.java b/dasCore/src/main/java/org/das2/system/ThrowRuntimeExceptionHandler.java new file mode 100644 index 000000000..8cd195f81 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/ThrowRuntimeExceptionHandler.java @@ -0,0 +1,33 @@ +/* + * NullExceptionHandler.java + * + * Created on November 16, 2006, 12:59 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.system; + +/** + * ExceptionHandler that throws a RuntimeException caused by the Exception. + * This is useful for server-side applications that need to handle the + * exception externally. + * + * @author jbf + */ +public class ThrowRuntimeExceptionHandler implements ExceptionHandler { + + /** Creates a new instance of NullExceptionHandler */ + public ThrowRuntimeExceptionHandler() { + } + + public void handle(Throwable t) { + throw new RuntimeException(t); + } + + public void handleUncaught(Throwable t) { + throw new RuntimeException(t); + } + +} diff --git a/dasCore/src/main/java/org/das2/system/UserMessageCenter.java b/dasCore/src/main/java/org/das2/system/UserMessageCenter.java new file mode 100644 index 000000000..ad87eb5c9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/UserMessageCenter.java @@ -0,0 +1,165 @@ +/* + * UserMessageCenter.java + * + * Created on March 28, 2006, 2:58 PM + * + * + */ + +package org.das2.system; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.JTextPane; +import javax.swing.text.BadLocationException; + +/** + * + * @author Jeremy + */ +public class UserMessageCenter { + + private static UserMessageCenter instance; + + class MessageRecord { + JButton nextButton; + Component component; + } + + List messageRecords; + + public static UserMessageCenter getDefault() { + if ( instance==null ) { + instance= new UserMessageCenter(); + } + return instance; + } + + private UserMessageCenter() { + createComponents(); + messageRecords= new ArrayList(); + } + + HashMap sources= new HashMap(); // + + /** + * Notify the user of the message, coalescing redundant messages from the same + * source, etc. + */ + public void notifyUser( Object source, String message ) { + JTextPane textComponent= new JTextPane(); + if ( message.startsWith("" ) ) textComponent.setContentType("text/html"); + try { + textComponent.getEditorKit().read( new StringReader(message), textComponent.getDocument(), 0 ); + } catch (IOException ex) { + ex.printStackTrace(); + } catch (BadLocationException ex) { + ex.printStackTrace(); + } + notifyUser( source, textComponent); + } + + private void notifyUser( Object source, JTextPane message ) { + HashMap sourceMessages= (HashMap)sources.get( source ); + if ( sourceMessages!=null ) { + if ( sourceMessages.containsKey(message.getText() ) ) { + frame.setVisible(true); + return; + } + } + if ( sourceMessages==null ) { + sourceMessages= new HashMap(); + sources.put( source, sourceMessages ); + } + sourceMessages.put( message.getText(), null ); + + JPanel panel= new JPanel(); + panel.setLayout( new BorderLayout( ) ); + panel.add( message, BorderLayout.CENTER ); + + JButton nextButton= new JButton( getNextAction() ); + panel.add( nextButton, BorderLayout.SOUTH ); + pane.add( panel, tabCount ); + tabCount++; + + if ( tabCount>0 ) { + frame.setVisible(true); + } + + MessageRecord record= new MessageRecord(); + record.component= panel; + record.nextButton= nextButton; + + messageRecords.add( record ); + update(); + } + + private void update() { + for ( int i=0; i>" ) { + public void actionPerformed(ActionEvent e) { + next(); + } + }; + } + + int tabCount; + private void next() { + int currentTab= pane.getSelectedIndex(); + if ( currentTab<(tabCount-1) ) { + currentTab++; + pane.setSelectedIndex(currentTab); + } + } + + private void prev() { + int currentTab= pane.getSelectedIndex(); + if ( currentTab>0 ) { + currentTab--; + pane.setSelectedIndex(currentTab); + } + } + + private JTabbedPane pane; + private JFrame frame; + + private void createComponents() { + frame= new JFrame( "das2 messages" ); + frame.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE ); + pane= new JTabbedPane(); + pane.setOpaque(true); + pane.setPreferredSize( new Dimension( 400, 300 ) ); + pane.setMinimumSize( pane.getPreferredSize() ); + frame.setContentPane( pane ); + frame.pack(); + } + + /** + * Notify user that an exception occured, presumably because they are capable of handling the exception. + */ + public void notifyUser( Object source, Throwable e) { + notifyUser( source, e.getMessage() ); + } + + + +} diff --git a/dasCore/src/main/java/org/das2/system/package.html b/dasCore/src/main/java/org/das2/system/package.html new file mode 100644 index 000000000..dc191198a --- /dev/null +++ b/dasCore/src/main/java/org/das2/system/package.html @@ -0,0 +1,4 @@ + + Application-level infrastructure, such as data set caching, worker threads, progress monitors, + exception handling, logging. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/AboutUtil.java b/dasCore/src/main/java/org/das2/util/AboutUtil.java new file mode 100644 index 000000000..097575e02 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/AboutUtil.java @@ -0,0 +1,130 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; + +/** + * method for getting useful build and version information. + * TODO: Splash should call this to get version, not the other way around. + * @author jbf + */ +public class AboutUtil { + + public static String getAboutHtml() { + String dasVersion = Splash.getVersion(); + String javaVersion = System.getProperty("java.version"); // applet okay + String buildTime = "???"; + java.net.URL buildURL = AboutUtil.class.getResource("/buildTime.txt"); + if (buildURL != null) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(buildURL.openStream())); + buildTime = reader.readLine(); + reader.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + String arch = System.getProperty("os.arch"); // applet okay + DecimalFormat nf = new DecimalFormat("0.0"); + String mem = nf.format(Runtime.getRuntime().maxMemory() / (1024 * 1024)); + String aboutContent = "" + + //"
    " + + "release version: " + dasVersion + + "
    build time: " + buildTime + + "
    java version: " + javaVersion + + "
    max memory (Mb): " + mem + + "
    arch: " + arch + + "

    "; + + try { + List bis = getBuildInfos(); + for (int i = 0; i < bis.size(); i++) { + aboutContent += "
    " + bis.get(i) + ""; + } + } catch (IOException ex) { + + } + + aboutContent += ""; + + return aboutContent; + } + + /** + * searches class path for META-INF/build.txt, returns nice strings + * @return one line per jar + */ + public static List getBuildInfos() throws IOException { + ClassLoader loader= AboutUtil.class.getClassLoader(); + if ( loader==null ) loader= ClassLoader.getSystemClassLoader(); + Enumeration urls = loader.getResources("META-INF/build.txt"); + + List result = new ArrayList(); + + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + + String jar = url.toString(); + + System.err.println(jar); + + int i = jar.indexOf(".jar"); + int i0 = jar.lastIndexOf("/", i - 1); + + String name; + if (i != -1) { + name = jar.substring(i0 + 1, i + 4); + } else { + name = jar.substring(6); + } + + Properties props = new Properties(); + props.load(url.openStream()); + + String cvsTagName = props.getProperty("build.tag"); + String version; + if (cvsTagName == null || cvsTagName.length() <= 9) { + version = "untagged_version"; + } else { + version = cvsTagName.substring(6, cvsTagName.length() - 2); + } + + result.add(name + ": " + version + "(" + props.getProperty("build.timestamp") + " " + props.getProperty("build.user.name") + ")"); + + } + return result; + + } + + /** + * Identify the release version by looking a non-null build.tag. It's expected + * that the build script will insert build.tag into META-INF/build.txt + * @return build tag, which should not contain spaces, or + * null if no tag is found. + * @throws java.io.IOException + */ + public static String getReleaseTag() throws IOException { + Enumeration urls = AboutUtil.class.getClassLoader().getResources("META-INF/build.txt"); + Properties props = new Properties(); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + props.load(url.openStream()); + String tagName = props.getProperty("build.tag"); + if ( tagName!=null && tagName.trim().length()>0 ) { + return tagName; + } + } + return null; + } +} diff --git a/dasCore/src/main/java/org/das2/util/ArgumentList.java b/dasCore/src/main/java/org/das2/util/ArgumentList.java new file mode 100755 index 000000000..c22ec40d0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/ArgumentList.java @@ -0,0 +1,520 @@ +package org.das2.util; + +import org.das2.system.DasLogger; +import java.util.*; +import java.util.logging.Logger; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** + * Utility class for processing the String[] arguments passed into the main routine, + * handing positional and switch parameters. Also automatically generates the + * usage documentation. + */ +public class ArgumentList { + + int nposition; + + String programName; + + String[] positionKeys; + + HashMap values; + + HashMap descriptions; + + HashMap names; + + HashMap reverseNames; + + HashMap formUsed; + + HashMap abbrevs; + + HashMap isBoolean; + + ArrayList requireOneOfList; + + /** + * if false, then any unrecognized switch is an error. + */ + boolean allowUndefinedSwitch= false; + + private String UNSPECIFIED = "__unspecified__"; + + private String REFERENCEWITHOUTVALUE = "__referencewithoutvalue__"; + + private String UNDEFINED_SWITCH = "__undefinedSwitch__"; + + // This is supposed to be compatable with the preferences API, so "__false__" must + // become "false", ditto for "__true__" + //private String FALSE = new String("__false__"); + //private String TRUE = new String("__true__"); + private String FALSE = "false"; + private String TRUE = "true"; + + private static final Logger logger= DasLogger.getLogger( DasLogger.GUI_LOG ); + + /** + * creates the processor for the program. programName is provided + * for the usage statement. After creating the object, arguments are + * specified one by one, and then the process method is called. + */ + public ArgumentList(String programName) { + this.programName= programName; + positionKeys= new String[10]; + values= new HashMap(); + descriptions= new HashMap(); + names= new HashMap(); + reverseNames= new HashMap(); + abbrevs= new HashMap(); + formUsed= new HashMap(); + requireOneOfList= new ArrayList(); + } + + /** + * get the value for this parameter + * @return the parameter's value. + * @throws IllegalArgumentException if the parameter name was never described. + */ + public String getValue(String key) { + if ( values.containsKey(key) ) { + return (String)values.get( key ); + } else { + throw new IllegalArgumentException( "No such key: "+key ); + } + } + + /** + * returns the options as a java.util.prefs.Preferences object, for + * batch processes. The idea is that a process which grabs default + * settings from the user Preferences can instead get them from the command + * line, to support batch processes. See the Vg1pws app for an example of + * how this is used. + * + * @return a Preferences object, loaded with the command line values. + */ + public Preferences getPreferences() { + return new AbstractPreferences(null,"") { + @Override + protected void putSpi(String key, String value) { + formUsed.put(key,value); + values.put(key,value); + } + + @Override + protected String getSpi(String key) { + if ( formUsed.containsKey(key) ) { + return (String) values.get(key); + } else { + return null; + } + } + + @Override + protected void removeSpi(String key) { + // do nothing + } + + @Override + protected void removeNodeSpi() throws BackingStoreException { + // do nothing + } + + @Override + protected String[] keysSpi() throws BackingStoreException { + return (String[])values.keySet().toArray(new String[values.size()]); + } + + @Override + protected String[] childrenNamesSpi() throws BackingStoreException { + return new String[0]; + } + + @Override + protected AbstractPreferences childSpi(String name) { + return null; + } + + @Override + protected void syncSpi() throws BackingStoreException { + // do nothing + } + + @Override + protected void flushSpi() throws BackingStoreException { + // do nothing + } + }; + } + + public boolean getBooleanValue(String key) { + return values.get( key ) == TRUE; + } + + /** + * Specify the ith positional argument. + * + * @param position the position number, 0 is the first argument position after the class name. + * @param key the internal reference name to get the value specified. + * @param description a short (40 character) description of the argument. + */ + public void addPositionArgument(int position, String key, String description) { + if ( position>nposition ) { + throw new IllegalArgumentException( "Position arguments must be specified 0,1,2,3: position="+position ); + } + if ( position>positionKeys.length ) { + throw new IllegalArgumentException( "Position too big: position="+position ); + } + nposition= position+1; + positionKeys[position]= key; + descriptions.put(key, description); + values.put( key, UNSPECIFIED ); + } + + /** + * requires the user specify one of these values, otherwise the usage + * statement is printed. + * @param keyNames an array of internal key names that identify parameters. + */ + public void requireOneOf( String[] keyNames ) { + requireOneOfList.add(keyNames); + } + + /** + * Specify the ith positional argument, which may be left unspecified by + * the user. Note that all positional arguments after this one must also be + * optional. + * + * @param position the position number, 0 is the first argument position after the class name. + * @param key the internal reference name to get the value specified. + * @param defaultValue the value that is returned if a value is not provided by the user. + * @param description a short (40 character) description of the argument. + */ + public void addOptionalPositionArgument(int position, String key, String defaultValue, String description) { + addPositionArgument( position, key, description ); + values.put(key,defaultValue); + } + + /** + * specify a named switch argument that must be specified by the user. For example, --level=3 or -l=3 + * @param name the long parameter name, which the user may enter. e.g. "level" + * @param abbrev short (one letter) parameter version. e.g. "l" for -l=3 + * @param key the internal reference name to get the value specified, not necessarily but often the same as name. + * @param description a short (40 character) description of the argument. + */ + public void addSwitchArgument(String name, String abbrev, String key, String description) { + if ( abbrev==null && name==null ) { + throw new IllegalArgumentException( "both abbrev and name are null, one must be specified" ); + } + descriptions.put( key, description ); + if ( abbrev!=null ) { + if ( abbrevs.containsKey(abbrev) ) { + throw new IllegalArgumentException( "abbrev already used: "+abbrev ); + } + abbrevs.put( abbrev, key ); + } + if ( name!=null ) { + names.put( name, key ); + reverseNames.put( key, name ); + } + values.put( key, UNSPECIFIED ); + } + + /** + * specify a named switch argument that may be specified by the user. For example, --level=3 or -l=3 + * @param name the long parameter name, which the user may enter. e.g. "level" + * @param abbrev short (one letter) parameter version. e.g. "l" for -l=3 + * @param defaultValue value to return if the user doesn't specify. + * @param key the internal reference name to get the value specified, not necessarily but often the same as name. + * @param description a short (40 character) description of the argument. + */ + public void addOptionalSwitchArgument(String name, String abbrev, String key, String defaultValue, String description) { + addSwitchArgument( name, abbrev, key, description ); + values.put( key, defaultValue ); + } + + /** + * specify a named switch argument that is named, and we only care whether it was used or not. e.g. --debug + * @param name the long parameter name, which the user may enter. e.g. "level" + * @param abbrev short (one letter) parameter version. e.g. "l" for -l=3 + * @param key the internal reference name to get the value specified, not necessarily but often the same as name. + * @param description a short (40 character) description of the argument. + */ + public void addBooleanSwitchArgument(String name, String abbrev, String key, String description) { + if ( key.equals("commandLinePrefs") ) allowUndefinedSwitch=true; + addOptionalSwitchArgument( name, abbrev, key, FALSE, description ); + } + + /** + * print the usage statement out to stderr. + */ + public void printUsage() { + String s; + s= "Usage: "+this.programName+" "; + for ( int i=0; i "; + } + } + + System.err.println(s); + + Set set= names.keySet(); + Iterator i= set.iterator(); + + while ( i.hasNext() ) { + Object name= i.next(); + Object key= names.get(name); + s= " "; + Object description= descriptions.get(key); + if ( values.get(key)!=this.UNSPECIFIED ) { + if ( values.get(key)==this.FALSE || values.get(key)==this.TRUE ) { + s+= "--"+name+" "+description; + } else { + s+= "--"+name+"="+description+" "; + } + } else { + s+= "--"+name+"="+description+" (required)"; + } + System.err.println(s); + } + + set= abbrevs.keySet(); + i= set.iterator(); + + while ( i.hasNext() ) { + Object abbrev= i.next(); + Object key= abbrevs.get(abbrev); + s= " "; + Object description= descriptions.get(key); + if ( values.get(key)!=this.UNSPECIFIED ) { + if ( values.get(key)==this.FALSE || values.get(key)==this.TRUE ) { + s+= "-"+abbrev+" \t"+description; + } else { + s+= "-"+abbrev+"="+description+" "; + } + } else { + s+= "-"+abbrev+"="+description+" (required)"; + } + System.err.println(s); + } + } + + /** + * check that the user's specified arguments are valid. + */ + private void checkArgs() { + boolean error= false; + java.util.List errorList= new java.util.ArrayList(); // add strings to here + for ( int i=0; !error & i0 ) { + printUsage(); + System.err.println( "" ); + for ( int ii=0; iiEncodes and decodes to and from Base64 notation.

    + *

    Homepage: http://iharder.net/base64.

    + * + *

    + * Change Log: + *

    + *
      + *
    • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug + * when using very small files (~< 40 bytes).
    • + *
    • v2.2 - Added some helper methods for encoding/decoding directly from + * one file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 dialects: + *
        + *
      1. The default is RFC3548 format.
      2. + *
      3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html
      4. + *
      5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as described + * in http://www.faqs.org/qa/rfcc-1940.html
      6. + *
      + * Special thanks to Jim Kellerman at http://www.powerset.com/ + * for contributing the new Base64 dialects. + *
    • + * + *
    • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
    • + *
    • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
    • + *
    • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
    • + *
    • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
    • + *
    • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
    • + *
    • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
    • + *
    • v1.4 - Added helper methods to read/write files.
    • + *
    • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
    • + *
    • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
    • + *
    • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
    • + *
    • v1.3.3 - Fixed I/O streams which were totally messed up.
    • + *
    + * + *

    + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

    + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.2.1 + */ +package org.das2.util; + +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding. */ + public final static int ENCODE = 1; + + + /** Specify decoding. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed. */ + public final static int GZIP = 2; + + + /** Don't break lines when encoding (violates strict Base64 specification) */ + public final static int DONT_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "UTF-8"; + + + // I think I end up not using the BAD_ENCODING indicator. + //private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + //private final static byte[] ALPHABET; + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = + { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET; + else return _STANDARD_ALPHABET; + + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET; + else return _STANDARD_DECODABET; + + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + /** + * Encodes or decodes two files from the command line; + * feel free to delete this method (in fact you probably should) + * if you're embedding this code into a larger program. + */ + public final static void main( String[] args ) + { + if( args.length < 3 ){ + usage("Not enough arguments."); + } // end if: args.length < 3 + else { + String flag = args[0]; + String infile = args[1]; + String outfile = args[2]; + if( flag.equals( "-e" ) ){ + Base64.encodeFileToFile( infile, outfile ); + } // end if: encode + else if( flag.equals( "-d" ) ) { + Base64.decodeFileToFile( infile, outfile ); + } // end else if: decode + else { + usage( "Unknown flag: " + flag ); + } // end else + } // end else + } // end main + + /** + * Prints command line usage. + * + * @param msg A message to include with usage info. + */ + private final static void usage( String msg ) + { + System.err.println( msg ); + System.err.println( "Usage: java Base64 -e|-d inputfile outputfile" ); + } // end usage + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) + { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + *

    Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes.

    + *

    This is the lowest level of the encoding methods with + * all possible parameters.

    + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) + { + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeObject( myObj, Base64.GZIP ) or + *

    + * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + { + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.io.ObjectOutputStream oos = null; + java.util.zip.GZIPOutputStream gzos = null; + + // Isolate options + int gzip = (options & GZIP); + int dontBreakLines = (options & DONT_BREAK_LINES); + + try + { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + + // GZip? + if( gzip == GZIP ) + { + gzos = new java.util.zip.GZIPOutputStream( b64os ); + oos = new java.io.ObjectOutputStream( gzos ); + } // end if: gzip + else + oos = new java.io.ObjectOutputStream( b64os ); + + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) + { + return encodeBytes( source, 0, source.length, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeBytes( myData, Base64.GZIP ) or + *

    + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) + { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) + { + return encodeBytes( source, off, len, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeBytes( myData, Base64.GZIP ) or + *

    + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options, alphabet type is pulled from this + * (standard, url-safe, ordered) + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) + { + // Isolate options + int dontBreakLines = ( options & DONT_BREAK_LINES ); + int gzip = ( options & GZIP ); + + // Compress? + if( gzip == GZIP ) + { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + + try + { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else + { + // Convert option to boolean in way that code likes it. + boolean breakLines = dontBreakLines == 0; + + int len43 = len * 4 / 3; + byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) + { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength == MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) + { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Return value according to relevant encoding. + try + { + return new String( outBuff, 0, e, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( outBuff, 0, e ); + } // end catch + + } // end else: don't compress + + } // end encodeBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + *

    This is the lowest level of the decoding methods with + * all possible parameters.

    + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset, int options ) + { + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else + { + try{ + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + }catch( Exception e){ + System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); + System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); + System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); + System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); + return -1; + } // end catch + } + } // end decodeToBytes + + + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + { + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for( i = off; i < off+len; i++ ) + { + sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[ sbiCrop ]; + + if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better + { + if( sbiDecode >= EQUALS_SIGN_ENC ) + { + b4[ b4Posn++ ] = sbiCrop; + if( b4Posn > 3 ) + { + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( sbiCrop == EQUALS_SIGN ) + break; + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else + { + System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); + return null; + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode( String s ) + { + return decode( s, NO_OPTIONS ); + } + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode( String s, int options ) + { + byte[] bytes; + try + { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) + { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if( bytes != null && bytes.length >= 4 ) + { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) + { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try + { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) + { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) + { + // Just return originally-decoded bytes + } // end catch + finally + { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + { + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try + { + bais = new java.io.ByteArrayInputStream( objBytes ); + ois = new java.io.ObjectInputStream( bais ); + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + catch( java.lang.ClassNotFoundException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + finally + { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean encodeToFile( byte[] dataToEncode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + success = true; + } // end try + catch( java.io.IOException e ) + { + + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean decodeToFile( String dataToDecode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + success = true; + } // end try + catch( java.io.IOException e ) + { + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * @param filename Filename for reading encoded data + * @return decoded byte array or null if unsuccessful + * + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + { + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); + return null; + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error decoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * @param filename Filename for reading binary data + * @return base64-encoded string or null if unsuccessful + * + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + { + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error encoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads infile and encodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + { + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException ex ) { + ex.printStackTrace(); + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads infile and decodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + { + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException ex ) { + ex.printStackTrace(); + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream + { + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] alphabet; // Local copies to avoid extra method calls + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) + { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

    + * Valid options:

    +         *   ENCODE or DECODE: Encode or Decode as data is read.
    +         *   DONT_BREAK_LINES: don't break lines at 76 characters
    +         *     (only meaningful when encoding)
    +         *     Note: Technically, this makes your encoding non-compliant.
    +         * 
    + *

    + * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) + { + super( in ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.options = options; // Record for later, mostly to determine which alphabet to use + this.alphabet = getAlphabet(options); + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + public int read() throws java.io.IOException + { + // Do we need to get data? + if( position < 0 ) + { + if( encode ) + { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) + { + try + { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) + { + b3[i] = (byte)b; + numBinaryBytes++; + } // end if: not end of stream + + } // end try: read + catch( java.io.IOException e ) + { + // Only a problem if we got no data at all. + if( i == 0 ) + throw e; + + } // end catch + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) + { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else + { + return -1; + } // end else + } // end if: encoding + + // Else decoding + else + { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) + { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) + break; // Reads a -1 if end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) + { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else + { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) + { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ) + return -1; + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) + { + lineLength = 0; + return '\n'; + } // end if + else + { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) + position = -1; + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else + { + // When JDK1.4 is more accepted, use an assertion here. + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + public int read( byte[] dest, int off, int len ) throws java.io.IOException + { + int i; + int b; + for( i = 0; i < len; i++ ) + { + b = read(); + + //if( b < 0 && i == 0 ) + // return -1; + + if( b >= 0 ) + dest[off + i] = (byte)b; + else if( i == 0 ) + return -1; + else + break; // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream + { + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] alphabet; // Local copies to avoid extra method calls + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) + { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

    + * Valid options:

    +         *   ENCODE or DECODE: Encode or Decode as data is read.
    +         *   DONT_BREAK_LINES: don't break lines at 76 characters
    +         *     (only meaningful when encoding)
    +         *     Note: Technically, this makes your encoding non-compliant.
    +         * 
    + *

    + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) + { + super( out ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.alphabet = getAlphabet(options); + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + public void write(int theByte) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to encode. + { + out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else + { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to output. + { + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + //out.write( Base64.decode4to3( buffer ) ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) + { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + public void write( byte[] theBytes, int off, int len ) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) + { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + public void flushBase64() throws java.io.IOException + { + if( position > 0 ) + { + if( encode ) + { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else + { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + public void close() throws java.io.IOException + { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException + { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() + { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/dasCore/src/main/java/org/das2/util/ByteBufferInputStream.java b/dasCore/src/main/java/org/das2/util/ByteBufferInputStream.java new file mode 100644 index 000000000..ba87fb061 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/ByteBufferInputStream.java @@ -0,0 +1,114 @@ +/* File: ByteBufferInputStream.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on February 4, 2004, 9:34 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.InvalidMarkException; + +/** An input stream that wraps an NIO ByteBuffer. Reading from this stream + * will update the ByteBuffers position. Calling mark() on this input stream + * will set the mark on the underlying buffer. + * + * @author eew + */ +public class ByteBufferInputStream extends InputStream { + + private ByteBuffer buffer; + + /** Creates a new instance of ByteBufferInputStream */ + public ByteBufferInputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + public ByteBuffer getByteBuffer() { + return buffer; + } + + public int read() { + if (buffer.hasRemaining()) { + return 0xFF & buffer.get(); + } + else { + return -1; + } + } + + public void close() { + //Do nothing + } + + public long skip(long n) { + if (n > buffer.remaining()) { + long skipped = buffer.remaining(); + buffer.position(buffer.limit()); + return skipped; + } + else { + buffer.position(buffer.position() + (int)n); + return n; + } + + } + + public void reset() throws IOException { + try { + buffer.reset(); + } + catch (InvalidMarkException ime) { + IOException ioe = new IOException(ime.getMessage()); + ioe.initCause(ime); + throw ioe; + } + } + + public int available() { + return buffer.remaining(); + } + + public int read(byte[] b) { + return read(b, 0, b.length); + } + + public boolean markSupported() { + return true; + } + + public void mark(int readlimit) { + buffer.mark(); + } + + public int read(byte[] b, int off, int len) { + if (buffer.hasRemaining()) { + int bytesRead = Math.min(len, buffer.remaining()); + buffer.get(b, off, bytesRead); + return bytesRead; + } + else { + return -1; + } + } + +} diff --git a/dasCore/src/main/java/org/das2/util/ClassMap.java b/dasCore/src/main/java/org/das2/util/ClassMap.java new file mode 100644 index 000000000..b1ee4c6ab --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/ClassMap.java @@ -0,0 +1,95 @@ +/* + * ClassMap.java + * + * Created on April 28, 2006, 2:11 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Map that takes a Class for keys, and the get method finds the closest matching class. + * @author Jeremy + */ +public class ClassMap implements Map { + HashMap map; + + /** Creates a new instance of ClassMap */ + public ClassMap() { + map= new HashMap(); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + Object close= closestKey((Class)key); + return close!=null; + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + private Object closestKey( Object key ) { + Object result= key; + if ( map.containsKey(result) ) return result; + Class clas= (Class)key; + while ( clas!=null ) { + if ( map.containsKey(clas) ) return clas; + clas= clas.getSuperclass(); + } + return clas; + } + + public T get(Object key) { + Object close= closestKey(key); + return ( close==null ) ? null : map.get(close); + } + + public T put(Class key, T value) { + if ( key.isInterface() ) { + // System.err.println("interfaces not supported"); + } + T result= map.get(key); + map.put( key, value ); + return result; + } + + public T remove(Object key) { + return map.remove(key); + } + + public void putAll(Map t) { + if ( t instanceof ClassMap ) map.putAll(t); + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set entrySet() { + return map.entrySet(); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/CombinedTreeModel.java b/dasCore/src/main/java/org/das2/util/CombinedTreeModel.java new file mode 100644 index 000000000..d05982368 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/CombinedTreeModel.java @@ -0,0 +1,227 @@ +/* + * DataSetTreeModel.java + * + * Created on July 25, 2006, 2:17 PM + * + * + */ +package org.das2.util; + +import java.awt.EventQueue; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.WeakHashMap; +import javax.swing.event.TreeModelEvent; +import javax.swing.event.TreeModelListener; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; + +/** + * Forms a tree by connecting sub-trees. + * @author Jeremy + */ +public class CombinedTreeModel implements TreeModel { + + List treeModels; + List treeModelRoots; + List treeModelSortIndexes; + Object root = null; + WeakHashMap sourceMap; + List listeners; // generally, the model should only be modified on the event thread. + // this is useful for debugging. + final private boolean checkEvent = false; + + public CombinedTreeModel(Object root) { + this.root = root; + treeModels = new ArrayList(); + treeModelRoots = new ArrayList(); + treeModelSortIndexes = new ArrayList(); + sourceMap = new WeakHashMap(); + listeners = new ArrayList(); + } + + class SubTreeModelListener implements TreeModelListener { + + private TreeModelEvent prependTreeModelEvent(TreeModelEvent e) { + Object[] path = e.getPath(); + Object[] path2 = new Object[path.length + 1]; + path2[0] = root; + System.arraycopy(path, 0, path2, 1, path.length); + TreeModelEvent result = new TreeModelEvent(this, path2, e.getChildIndices(), e.getChildren()); + return result; + } + + public void treeNodesChanged(TreeModelEvent e) { + fireTreeNodesChanged(prependTreeModelEvent(e)); + } + + public void treeNodesInserted(TreeModelEvent e) { + fireTreeNodesInserted(prependTreeModelEvent(e)); + } + + public void treeNodesRemoved(TreeModelEvent e) { + fireTreeNodesRemoved(prependTreeModelEvent(e)); + } + + public void treeStructureChanged(TreeModelEvent e) { + fireTreeStructureChanged(prependTreeModelEvent(e)); + } + } + SubTreeModelListener myListener = new SubTreeModelListener(); + + public Object getRoot() { + return root; + } + + public void mountTree(TreeModel treeModel) { + mountTree(treeModel, -1); + } + + /** + * mounts the tree. Note each treeModel must have a unique root. + */ + @SuppressWarnings("unchecked") + public void mountTree(TreeModel treeModel, int sortIndex) { + if (checkEvent && !EventQueue.isDispatchThread()) + throw new IllegalArgumentException("must be called from AWT thread"); // useful for debugging concurrent exception + + if (treeModelRoots.contains(treeModel.getRoot())) { + int index = treeModelRoots.indexOf(treeModel.getRoot()); + treeModels.set(index, treeModel); + treeModelRoots.set(index, treeModel.getRoot()); + treeModelSortIndexes.set(index, (float)sortIndex ); + TreePath path = new TreePath(root); + treeModel.addTreeModelListener(myListener); + fireTreeNodesChanged(path, new int[]{index}, new Object[]{treeModel.getRoot()}); + } else { + int index= Collections.binarySearch( treeModelSortIndexes, sortIndex+0.5f ); + index= -1 - index; + treeModels.add( index, treeModel ); + treeModelRoots.add( index, treeModel.getRoot() ); + treeModelSortIndexes.add( index, (float)sortIndex ); + TreePath path = new TreePath(root); + treeModel.addTreeModelListener(myListener); + fireTreeNodesInserted(path, new int[]{ index }, new Object[]{treeModel.getRoot()}); + } + } + + public void unmountTree(TreeModel treeModel) { + if (checkEvent && !EventQueue.isDispatchThread()) + throw new IllegalArgumentException("must be called from AWT thread"); // useful for debugging concurrent exception + + int index = treeModelRoots.indexOf(treeModel.getRoot()); + if ( index!=-1 ) { + treeModels.remove(index); + treeModelRoots.remove(index); + treeModelSortIndexes.remove(index); + TreePath path = new TreePath(root); + fireTreeNodesRemoved( new TreeModelEvent(this, path, new int[] { index }, new Object[] { treeModel.getRoot() } ) ); + } + } + + public synchronized Object getChild(Object parent, int index) { + if (checkEvent && !EventQueue.isDispatchThread()) + throw new IllegalArgumentException("must be called from AWT thread"); // useful for debugging concurrent exception + Object result; + TreeModel mt; + if (parent == root) { + mt = (TreeModel) treeModels.get(index); + result = mt.getRoot(); + } else { + mt = (TreeModel) sourceMap.get(parent); + result = mt.getChild(parent, index); + } + sourceMap.put(result, mt); + return result; + } + + public int getChildCount(Object parent) { + if (checkEvent && !EventQueue.isDispatchThread()) + throw new IllegalArgumentException("must be called from AWT thread"); // useful for debugging concurrent exception + if (parent == root) { + return this.treeModels.size(); + } else { + TreeModel mt = (TreeModel) sourceMap.get(parent); + return mt.getChildCount(parent); + } + } + + public boolean isLeaf(Object node) { + if (node == root) { + return treeModels.size() == 0; + } else { + TreeModel mt = (TreeModel) sourceMap.get(node); + if ( mt==null ) { + System.err.println("null on "+node); + return true; + } + return mt.isLeaf(node); + } + } + + public void valueForPathChanged(TreePath path, Object newValue) { + Object parent = path.getPathComponent(path.getPathCount() - 2); + Object child = path.getPathComponent(path.getPathCount() - 1); + int index = getIndexOfChild(parent, child); + fireTreeNodesChanged(path, new int[]{index}, new Object[]{newValue}); + } + + public int getIndexOfChild(Object parent, Object child) { + if (parent == root) { + for (int i = 0; i < treeModels.size(); i++) { + if (child == ((TreeModel) treeModels.get(i)).getRoot()) + return i; + } + return -1; + } else { + TreeModel mt = (TreeModel) sourceMap.get(parent); + return mt.getIndexOfChild(parent, child); + } + } + + public void addTreeModelListener(TreeModelListener l) { + listeners.add(l); + } + + public void removeTreeModelListener(TreeModelListener l) { + listeners.remove(l); + } + + private void fireTreeNodesInserted(TreePath path, + int[] insertedIndeces, Object[] children) { + TreeModelEvent e = new TreeModelEvent(this, path, insertedIndeces, children); + fireTreeNodesInserted(e); + } + + private void fireTreeNodesChanged(TreePath path, + int[] changedIndeces, Object[] children) { + TreeModelEvent e = new TreeModelEvent(this, path, changedIndeces, children); + fireTreeNodesChanged(e); + } + + private void fireTreeNodesChanged(TreeModelEvent e) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + ((TreeModelListener) i.next()).treeNodesChanged(e); + } + } + + private void fireTreeNodesInserted(TreeModelEvent e) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + ((TreeModelListener) i.next()).treeNodesInserted(e); + } + } + + public void fireTreeNodesRemoved(TreeModelEvent e) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + ((TreeModelListener) i.next()).treeNodesRemoved(e); + } + } + + public void fireTreeStructureChanged(TreeModelEvent e) { + for (Iterator i = listeners.iterator(); i.hasNext();) { + ((TreeModelListener) i.next()).treeStructureChanged(e); + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/CredentialsDialog.form b/dasCore/src/main/java/org/das2/util/CredentialsDialog.form new file mode 100644 index 000000000..cd2cb2a8d --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/CredentialsDialog.form @@ -0,0 +1,133 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dasCore/src/main/java/org/das2/util/CredentialsDialog.java b/dasCore/src/main/java/org/das2/util/CredentialsDialog.java new file mode 100644 index 000000000..a26c5d616 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/CredentialsDialog.java @@ -0,0 +1,214 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util; + +import java.awt.Dialog; +import java.awt.event.KeyEvent; +import javax.swing.Icon; +import javax.swing.JDialog; +import javax.swing.JOptionPane; + +/** + * + * @author cwp + */ +public class CredentialsDialog extends JDialog{ + + protected int m_nRet; + protected String m_sUser; + protected String m_sPasswd; + + /** Creates new form CredentialsDialog */ + public CredentialsDialog(java.awt.Frame parent){ + super(parent, "Authentication Required", Dialog.ModalityType.APPLICATION_MODAL); + m_nRet = 0; + setResizable(true); + if(parent != null){ + setLocationRelativeTo(parent); + } + initComponents(); + } + + /** Show the dialog, get user input, and then close */ + public int runDialog(String sDesc, Icon icon, String sUser, String sPasswd){ + m_nRet = JOptionPane.CANCEL_OPTION; + + // Code halts here until some other location calls setVisible(false) on this + // dialog object + if(icon != null) lblIcon.setIcon(icon); + lblDesc.setText(sDesc); + tfUser.setText(sUser); + tfPasswd.setText(sPasswd); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + + + pack(); // Recalculate internal size of sub components + validate(); + + setVisible(true); + m_sUser = tfUser.getText(); + m_sPasswd = new String( tfPasswd.getPassword() ); + + return m_nRet; + }; + + int getReturn(){return m_nRet;} + String getUser(){return m_sUser;} + String getPasswd(){return m_sPasswd;} + + /** This method is called from within the constructor to initialize the form. WARNING: + * Do NOT modify this code. The content of this method is always regenerated by the + * Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + lblIcon = new javax.swing.JLabel(); + lblDesc = new javax.swing.JLabel(); + jLabel3 = new javax.swing.JLabel(); + jLabel4 = new javax.swing.JLabel(); + tfUser = new javax.swing.JTextField(); + tfPasswd = new javax.swing.JPasswordField(); + jSeparator1 = new javax.swing.JSeparator(); + btnOK = new javax.swing.JButton(); + btnCancel = new javax.swing.JButton(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setTitle("Authorization Required"); + setModalityType(java.awt.Dialog.ModalityType.APPLICATION_MODAL); + setName("Authorization"); // NOI18N + getContentPane().setLayout(new java.awt.GridBagLayout()); + + lblIcon.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/das2logo-64.png"))); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridheight = java.awt.GridBagConstraints.REMAINDER; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 0); + getContentPane().add(lblIcon, gridBagConstraints); + + lblDesc.setText("

    Some Long Complete Site Name

    Server: some.server.org/das
    Data Set: Some Dataset"); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridwidth = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(lblDesc, gridBagConstraints); + + jLabel3.setText("User name:"); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; + getContentPane().add(jLabel3, gridBagConstraints); + + jLabel4.setText("Password:"); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; + getContentPane().add(jLabel4, gridBagConstraints); + + tfUser.setText("someone"); + tfUser.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tfUserActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = java.awt.GridBagConstraints.RELATIVE; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.weightx = 0.67; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(tfUser, gridBagConstraints); + + tfPasswd.setText("jPasswor"); + tfPasswd.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tfPasswdActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(tfPasswd, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 5; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(jSeparator1, gridBagConstraints); + + btnOK.setText("OK"); + btnOK.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnOKActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 4; + gridBagConstraints.gridy = 4; + gridBagConstraints.ipadx = 10; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(btnOK, gridBagConstraints); + + btnCancel.setText("Cancel"); + btnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnCancelActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 4; + gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; + gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); + getContentPane().add(btnCancel, gridBagConstraints); + + pack(); + }// //GEN-END:initComponents + + private void btnOKActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOKActionPerformed + m_nRet = JOptionPane.OK_OPTION; + setVisible(false); + }//GEN-LAST:event_btnOKActionPerformed + + private void btnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCancelActionPerformed + setVisible(false); + }//GEN-LAST:event_btnCancelActionPerformed + + private void tfPasswdActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tfPasswdActionPerformed + btnOKActionPerformed(evt); + }//GEN-LAST:event_tfPasswdActionPerformed + + private void tfUserActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tfUserActionPerformed + btnOKActionPerformed(evt); + }//GEN-LAST:event_tfUserActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnCancel; + private javax.swing.JButton btnOK; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JLabel lblDesc; + private javax.swing.JLabel lblIcon; + private javax.swing.JPasswordField tfPasswd; + private javax.swing.JTextField tfUser; + // End of variables declaration//GEN-END:variables +} diff --git a/dasCore/src/main/java/org/das2/util/CredentialsManager.java b/dasCore/src/main/java/org/das2/util/CredentialsManager.java new file mode 100644 index 000000000..44bf55b6c --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/CredentialsManager.java @@ -0,0 +1,314 @@ +/* Part of the Das2 libraries, which the LGPL with class-path exception license */ +package org.das2.util; + +import java.awt.Frame; +import java.awt.Window; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.logging.Logger; +import javax.swing.ImageIcon; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import org.das2.system.LogCategory; + + +/** Provides per-resource login credentials + * + * This class maintains a map of login credentials by resource ID. The resource ID's + * themselves are just strings. The only expectation on resource ID strings is that they + * should be suitable for use as the Keys to a hash map. Otherwise no formation rules are + * assumed nor expected. User names and passwords for multiple web-sites, ftp sites, etc. + * are maintained by a single instance of this class. Call: + * + * CredentialsManager.getManager() + * + * to get a reference to that single instance. + * + * In a graphical environment this class handles presenting dialogs to the user to gather + * logon credentials. In a shell environment it will interact with the TTY to get user + * information. + * + * An example of using this class to handle Das 2.1 server authentication which html + * location information formatting follows: + * + * + * CredentialsManage cm = CrentialsManager.getMannager(); + * String sLocId = "planet.physics.uiowa.edu/das/das2Server|voyager1/pwi/SpecAnalyzer-4s-Efield"; + * + * if(!cm.hasCredentials(sLocId)){ + * DasServer svr = DasServer.create(sDsdf); + * String sDesc = String.Format("

    %s

    Server: %s

    DataSource: %s

    ", + * DasServer.getName(), "planet.physics.uiowa.edu", + * "voyager1 > pwi > SpecAnalyzer-4s-Efield"); + * cm.setDescription(sLocId, sDesc, DasServer.getLogo()); + * } + * + * String sHash = getHttpBasicHash(sLocId) + * + *
    + * + * Two previous classes, org.das2.util.filesystem.KeyChain (autoplot) and + * org.das2.client.Authenticator have approached this problem as well. However both of + * those classes make assumptions that are not valid in general. The first assumes that + * the caller somehow knows the username. The second assumes that you are talking to + * a first generation Das 2.1 server. Details of server communication are beyond the + * scope of this class. + * + * @author cwp + */ +public class CredentialsManager{ + + /////////////////////////////////////////////////////////////////////////////////// + // Static Section + + // A map of credentials managers versus lookup key + static final HashMap g_dManagers = new HashMap<>(); + static{ + g_dManagers.put(null, new CredentialsManager(null)); + } + + /** Get a reference to the authentication manager. + * + * Typically this is the function you want to use to get started. + */ + public static CredentialsManager getMannager(){ + return g_dManagers.get(null); + } + + /** Get an authentication manager associated with an associated tracking string. + * This is probably not the function you are looking for. It only exists for + * odd cases where two different credential managers are active in a single + * application at the same time. + * + * @param sWhich - A string used to differentiate this credentials manager from the + * default instance. + */ + public static CredentialsManager getMannager(String sWhich){ + if(!g_dManagers.containsKey(sWhich)){ + synchronized(g_dManagers) { + g_dManagers.put(sWhich, new CredentialsManager(sWhich)); + } + } + return g_dManagers.get(sWhich); + } + + //////////////////////////////////////////////////////////////////////////////////// + // Instance Section + + protected class Location{ + String sLocId; + String sDesc; + ImageIcon iconLogo; + String sUser; + String sPasswd; + + protected Location(String sLocationId, String sDescription, ImageIcon icon){ + sLocId = sLocationId; + sDesc = sDescription; + iconLogo = icon; + sUser = null; + sPasswd = null; + } + + protected boolean hasCredentials(){ + return (sUser != null)||(sPasswd != null); + } + } + + String m_sName; + HashMap m_dLocs; + CredentialsDialog m_dlg; + + protected CredentialsManager(String sName){ + m_sName = sName; + m_dLocs = new HashMap<>(); + m_dlg = null; + } + + /** Provide a description of a location for use in authentication dialogs. + * + * Use this function to tie a string description to a location id. This description + * will be used when interacting with the user. If no description is present, then just + * the location ID itself will be used to identify the site to the end user. + * Usually location strings aren't that easy to read so use of this function or the + * version with an icon argument is recommended, though not required. + * + * @param sLocationId The location to describe, can not be null. + * @param sDescription A string to present to a user when prompting for a credentials + * for this location, may be a basic HTML string. + */ + public void setDescription(String sLocationId, String sDescription){ + setDescription(sLocationId, sDescription, null); + } + + /** Provide a description of a location with an Image Icon + * + * Use this function to tie a string description and an Icon to a location. + * + * @param sLocationId The location to describe, can not be null. + * @param sDescription The description, may be a simply formatted HTML string. + * @param icon An icon to display for the server. + */ + public synchronized void setDescription(String sLocationId, String sDescription, + ImageIcon icon) + { + if(!m_dLocs.containsKey(sLocationId)){ + m_dLocs.put(sLocationId, new Location(sLocationId, sDescription, icon)); + } + else{ + Location loc = m_dLocs.get(sLocationId); + loc.sDesc = sDescription; + loc.iconLogo = icon; + } + } + + /** Determine if there are any stored credentials for this location + * + * If either a username or a password have been provided for the location + * then it is considered to have credentials + * + * @param sLocationId The location to describe, can not be null. + * @return true if there are stored credentials, false otherwise + */ + public boolean hasCredentials(String sLocationId){ + if(!m_dLocs.containsKey(sLocationId)) return false; + Location loc = m_dLocs.get(sLocationId); + return loc.hasCredentials(); + } + + /** Determine if a given location has been described + * + * Gathering descriptive information about a remote location may trigger communication + * with a remote site. Use this function to see if such communication is needed. + * + * @param sLocationId The location in question + * @return true if the location has been described, false otherwise + */ + public boolean hasDescription(String sLocationId){ + if(!m_dLocs.containsKey(sLocationId)) return false; + Location loc = m_dLocs.get(sLocationId); + return (loc.sDesc != null)&&(!loc.sDesc.isEmpty()); + } + + /** Determine is a site image has been set for a location ID. + * + * This function is provided because retrieving the logo for a site may trigger remote + * host communication. Use this function to see if such communication is needed. + * @param sLocationId the location in question + * @return true if the location has as attached icon logo + */ + public boolean hasIcon(String sLocationId){ + if(!m_dLocs.containsKey(sLocationId)) return false; + Location loc = m_dLocs.get(sLocationId); + return loc.iconLogo != null; + } + + /** Get credentials in the form of a hashed HTTP Basic authentication string + * + * If there are no credentials stored for the given location id, this function may + * trigger interaction with the user, such as presenting modal dialogs, or changing the + * TTY to non-echo. + * + * @param sLocationId - A unique string identifying a location. There are no formation + * rules on the string, but convenience functions are provided if a uniform naming + * convention is desired. + * + * @return The string USERNAME + ":" + PASSWORD that is then run through a base64 + * encoding. If no credentials are available for the given location ID and none can be + * gathered from the user (possibly due to the java.awt.headless being set) null is + * returned. + */ + public String getHttpBasicHash(String sLocationId){ + String sHash = null; + + if(!m_dLocs.containsKey(sLocationId)){ + synchronized(this){ + //Check again. Though unlikely, the key could have been added between + //the call above and the start of the sychronized section + if(!m_dLocs.containsKey(sLocationId)) + m_dLocs.put(sLocationId, new Location(sLocationId, null, null)); + } + } + + Location loc = m_dLocs.get(sLocationId); + if(!hasCredentials(sLocationId)){ + if("true".equals( System.getProperty("java.awt.headless"))){ + if(!getCredentialsCmdLine(loc)) return null; + } + else{ + if(!getCredentialsGUI(loc)) return null; + } + } + + String sTmp = loc.sUser + ":" + loc.sPasswd; + sHash = Base64.encodeBytes( sTmp.getBytes()); + return sHash; + } + + /** Let the credentials manager know that stored credentials for a location are invalid + * + * @param sLocationId + * @return + */ + public synchronized void invalidate(String sLocationId){ + if(!m_dLocs.containsKey(sLocationId)) return; + Location loc = m_dLocs.get(sLocationId); + loc.sUser = null; + loc.sPasswd = null; + } + + ////////////////////////// User Interaction //////////////////////////////////// + + /** Gather User Credentials + * + * @param loc The Location in question + * @return True if user hit OK, False if user canceled the operation + */ + protected synchronized boolean getCredentialsGUI(final Location loc) { + + // Check again to see if another thread managed to set the credentials before + // this method started. Need to avoid the double-authenticate dialogs problem + // I'm not sure how to prevent the double cancel problem at this time. --cwp + if( loc.hasCredentials()) return true; + + try{ + SwingUtilities.invokeAndWait( + new Runnable(){ + @Override + public void run(){ + // make the dialog if it doesn't exist + if(m_dlg == null){ + Frame wParent = null; + Window[] lTopWnds = Window.getOwnerlessWindows(); + for(Window wnd: lTopWnds){ + if(wnd.isVisible() && wnd instanceof Frame){ + wParent = (Frame)wnd; + break; + } + } + m_dlg = new CredentialsDialog(wParent); + } + String sTmp = loc.sDesc; + if((sTmp == null)||(sTmp.isEmpty())) sTmp = loc.sLocId; + m_dlg.runDialog(sTmp, loc.iconLogo, loc.sUser, loc.sPasswd); + } + } + ); + } + catch(InterruptedException | InvocationTargetException ex){ + Logger.getLogger(LogCategory.GRAPHICS_LOG).severe(ex.toString()); + return false; + } + + if(m_dlg.getReturn() == JOptionPane.CANCEL_OPTION) return false; + loc.sUser = m_dlg.getUser(); + loc.sPasswd = m_dlg.getPasswd(); + + return true; + } + + protected synchronized boolean getCredentialsCmdLine(Location loc){ + throw new UnsupportedOperationException("Command Line Credential collection is " + + "not yet implemented"); + } +} diff --git a/dasCore/src/main/java/org/das2/util/Crypt.java b/dasCore/src/main/java/org/das2/util/Crypt.java new file mode 100644 index 000000000..e7d676cfb --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/Crypt.java @@ -0,0 +1,71 @@ +/* File: crypt.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * + * @author jbf + */ +public class Crypt { + + /** Creates a new instance of crypt */ + private Crypt() { + } + + public static String crypt(java.lang.String s) { + return JCrypt.crypt("do", s); +// try { +// return new String(MessageDigest.getInstance("MD5").digest(s.getBytes())); +// } catch ( NoSuchAlgorithmException ex ) { +// RuntimeException e= new IllegalStateException( "MD5 algorythm not available" ); +// e.initCause(ex); +// throw e; +// } + } + + public static void main(String args[]) throws Exception { + String arg; + if(args.length >= 1) { + arg= args[0]; + } else { + arg= "ask1st"; + org.das2.util.DasDie.println("java crypt "); + org.das2.util.DasDie.println(" using "+arg ); + } + System.out.println( + "[" + arg + "] => [" + + Crypt.crypt(arg) + "]" + ); + + //byte[] bytes= MessageDigest.getInstance("MD5").digest(arg.getBytes()); + //String[] hex= new String[] { "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" }; + //for ( int i=0; i>4] + hex[( (bytes[i]&0x0F) )] + " "); + //} + + } +} diff --git a/dasCore/src/main/java/org/das2/util/DasDie.java b/dasCore/src/main/java/org/das2/util/DasDie.java new file mode 100644 index 000000000..fbe725888 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasDie.java @@ -0,0 +1,118 @@ +/* File: DasDie.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * + * @author jbf + */ +public class DasDie { + + public static int DEBUG=-20; // info useful only to das developers + public static int VERBOSE=-10; // info useful to end users in debugging + public static int INFORM=0; // warm-fuzzy operational messages + public static int WARN=10; // end user needs to be aware of, no action required + public static int ALARM=20; // end user needs to take action + public static int CRITICAL=30; // abnormal system condition that cannot be supressed. + + public static int verbosity; + + static { + String debugLevel= org.das2.DasProperties.getInstance().getProperty("debugLevel"); + setDebugVerbosityLevel(debugLevel); + } + + /** Creates a new instance of DasDie */ + private DasDie() { + } + + public static void setDebugVerbosityLevel(String debugLevel) { + if (debugLevel.equals("endUser")) verbosity= WARN; + else if (debugLevel.equals("dasDeveloper")) verbosity= DEBUG; + else verbosity= INFORM; + } + + private static final String calledBy() { + StringWriter sw = new StringWriter(); + new Throwable().printStackTrace( + new PrintWriter( sw ) + ); + String callStack = sw.toString(); + + int atPos = callStack.indexOf( "at" ); + //org.das2.util.DasDie.println(callStack.substring(atPos)); + atPos = callStack.indexOf( "at" , atPos+2 ); + //org.das2.util.DasDie.println(callStack.substring(atPos)); + atPos = callStack.indexOf( "at" , atPos+2 ); + //org.das2.util.DasDie.println(callStack.substring(atPos)); + atPos = callStack.indexOf( "at" , atPos+2 ); + //org.das2.util.DasDie.println(callStack.substring(atPos)); + int nextAtPos= callStack.indexOf( "at" , atPos+2 ); + + String calledBy= callStack.substring(atPos,nextAtPos-2); + return calledBy; + } + + public static final void die(String message) { + + System.out.print(calledBy()+": "); + org.das2.util.DasDie.println(CRITICAL,message); + System.exit(-1); + } + + public static final void println(java.lang.String message) { + println(DEBUG,message); + } + + public static final void println(Object o) { + println(o.toString()); + } + + public static final void println(int verbosity, java.lang.String message) { + if (verbosity>=DasDie.verbosity) { + //System.out.print(calledBy()+": "); + System.err.println(message); + } + } + + public static final void print(java.lang.String message) { + print(DEBUG,message); + } + + public static final void print(Object o) { + print(o.toString()); + } + + public static final void print(int verbosity, java.lang.String message) { + if (verbosity>=DasDie.verbosity) { + //System.out.print(calledBy()+": "); + System.out.print(message); + } + } + + +} diff --git a/dasCore/src/main/java/org/das2/util/DasExceptionHandler.java b/dasCore/src/main/java/org/das2/util/DasExceptionHandler.java new file mode 100755 index 000000000..dcd4da00e --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasExceptionHandler.java @@ -0,0 +1,175 @@ +/* File: DasExceptionHandler.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import org.das2.DasApplication; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * + * @author jbf + */ +public final class DasExceptionHandler { + + //private static JDialog dialog; + //private static JTextArea messageArea; + //private static JTextArea traceArea; + private static final String UNCAUGHT = "An unexpected error has occurred. " + + "The system may not be able to recover properly. Please report this " + + "error to the Das2 bug database at http://bugs-pw.physics.uiowa.edu/." + + " Please include all error information and a description of how you" + + " encountered the error. For your convenience, you may click the " + + "\"Show Details\" button then click the \"Save to file\" button to save" + + " all the relevant error messages to a file.\n"; + + /* this is public so that the AWT thread can create it */ + public DasExceptionHandler() { + } + + public static void handle(Throwable t) { + if ( DasApplication.getDefaultApplication().isHeadless() ) { + t.printStackTrace(); + } + else { + showExceptionDialog(t, ""); + } + } + + public static void handleUncaught(Throwable t) { + if ( DasApplication.getDefaultApplication().isHeadless() ) { + t.printStackTrace(); + } + else { + showExceptionDialog(t, UNCAUGHT); + } + } + + private static void showExceptionDialog(final Throwable t, String extraInfo) { + String errorMessage = extraInfo + t.getClass().getName() + "\n" + + (t.getMessage() == null ? "" : t.getMessage()); + final JDialog dialog = new JDialog( DasApplication.getDefaultApplication().getMainFrame() ); + dialog.setTitle("Error in das2"); + dialog.setModal(false); + dialog.setResizable(false); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + final JTextArea messageArea = new JTextArea(10, 40); + messageArea.setLineWrap(true); + messageArea.setWrapStyleWord(true); + messageArea.setEditable(false); + messageArea.setText(errorMessage); + JScrollPane message = new JScrollPane(messageArea); + message.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(message, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton ok = new JButton("Ok"); + final JToggleButton details = new JToggleButton("Show Details"); + buttonPanel.add(ok); + buttonPanel.add(details); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + dialog.getContentPane().add(mainPanel, BorderLayout.CENTER); + + final JTextArea traceArea = new JTextArea(10, 40); + traceArea.setLineWrap(false); + traceArea.setEditable(false); + traceArea.setTabSize(4); + + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + traceArea.setText(writer.toString()); + + final JPanel stackPane = new JPanel(new BorderLayout()); + stackPane.add(new JScrollPane(traceArea), BorderLayout.NORTH); + stackPane.setBorder(new javax.swing.border.EmptyBorder(10, 10, 10, 10)); + JPanel buttonPanel2 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + buttonPanel2.setBorder(new javax.swing.border.EmptyBorder(10, 0, 0, 0)); + JButton dump = new JButton("Dump to STDERR"); + JButton save = new JButton("Save to file"); + buttonPanel2.add(dump); + buttonPanel2.add(save); + stackPane.add(buttonPanel2, BorderLayout.SOUTH); + Dimension size = message.getPreferredSize(); + size.width = stackPane.getPreferredSize().width; + message.setPreferredSize(size); + + ok.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dialog.dispose(); + } + }); + + details.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (details.isSelected()) { + details.setText("Less Details"); + dialog.getContentPane().add(stackPane, BorderLayout.SOUTH); + dialog.pack(); + } + else { + details.setText("More Details"); + dialog.getContentPane().remove(stackPane); + dialog.pack(); + } + } + }); + + dump.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String text = traceArea.getText(); + System.err.print(text); + } + }); + + save.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + JFileChooser chooser = new JFileChooser(); + int result = chooser.showSaveDialog(dialog); + if (result == chooser.APPROVE_OPTION) { + File selected = chooser.getSelectedFile(); + PrintWriter out = new PrintWriter(new FileOutputStream(selected)); + t.printStackTrace(out); + out.close(); + } + } + catch (IOException ioe) { + handle(ioe); + } + } + }); + + dialog.pack(); + dialog.setLocationRelativeTo(null); + dialog.setVisible(true); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DasMath.java b/dasCore/src/main/java/org/das2/util/DasMath.java new file mode 100644 index 000000000..275f8dd60 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasMath.java @@ -0,0 +1,259 @@ +/* File: DasMath.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +/** + * + * @author jbf + */ + +public class DasMath { + + /** Creates a new instance of DasMath */ + public DasMath() { + } + + private static final double log10=Math.log(10); + + public static double log10(double x) { + return Math.log(x)/log10; + } + + public static double exp10(double x) { + double result= Math.pow(10,x); + return result; + } + + // what does this function do? + public static double exp10(int x) { + double result= Math.pow(10,x); + return result; + } + + public static double roundNFractionalDigits(double x,int n) { + double tenToN= exp10(n-1); + return Math.round( x * tenToN ) / tenToN; + } + + public static double roundNSignificantDigits(double x,int n) { + double sign= x<0 ? -1 : 1; + double exp= exp10(Math.floor(log10(sign*x))); + double mant= x/exp; + double tenToN= exp10(n-1); + mant= Math.round( mant * tenToN ) / tenToN; + return mant*exp; + } + + public static double tanh( double x ) { + double sinh = ( Math.exp(x) - Math.exp(-x) )/2; + double cosh = ( Math.exp(x) + Math.exp(-x) )/2; + double tanh = sinh / cosh; + return tanh; + } + + /** Interpolate just one point */ + public static double interpolate( double[] datay, double findex ) { + int index= (int)findex; + double alpha= findex - index; + double result; + if ( findex<0. ) return datay[0]; + if ( findex>datay.length-1. ) return datay[datay.length-1]; + if ( alpha == 0. ) { // optimization and handle findex=(data.length-1); + result= datay[index]; + } else { + result= datay[index] * ( 1.0 - alpha ) + datay[index+1] * alpha ; + } + return result; + } + + /** interpolate an array of points. */ + public static double[] interpolate( double[] datay, double[] findex ) { + double[] result= new double[ findex.length ]; + for ( int i=0; i0 && datax[index]>x ) index--; + if ( index==datax.length-1 ) index--; + return index + ( x - datax[index] ) / ( datax[index+1] - datax[index] ); + } + + public static void main(String[] args) { + double x= 1e-18; + org.das2.util.DasDie.println("x:"+x); + org.das2.util.DasDie.println("roundNDigits:"+roundNSignificantDigits(x,3)); + + double[] x1= new double[] { 1,2,3,4,5 }; + double[] y1= new double[] { 4,6,7,3,1 }; + double[] interpx= new double [] { 1.0, 1.5, 4.5, 5.0, 1.5 }; + double[] interpy= interpolate( y1, findex( x1, interpx ) ); + for ( int i=0; i= 0 ? result : t + result; + } + + /** + * just like modulo (%) function, but negative numbers return positive phase. + */ + public static int modp( int x, int t) { + int result= x % t; + return result >= 0 ? result : t + result; + } + + public static double biggerOf(double x1, double x2) { + return ( x1>x2 ) ? x1 : x2; + } + + private static double gcd( double a, double d, double error ) { + + if ( error>0 ) { + a= Math.round( a/error ); + d= Math.round( d/error ); + } + + if ( a0 ) { + return a * error; + } else { + return a; + } + } + + double r= a % d; + + int iterations=0; + + while ( r > 0 && iterations<15 ) { + d= r; + r= a % d; + iterations++; + } + + if ( error>0 ) { + return d * error; + } else { + return d; + } + } + + + /* + * Returns the greatest common divisor of a group of numbers. This is useful for + * a number of visualization techniques, for instance when you need to integerize + * your data, the binsize should be the gcd. An error parameter is provided to + * avoid numerical noise, and in case there is a granularity that needn't be + * surpassed. + */ + public static double gcd( double[] A, double error ) { + double guess= A[0]; + + double result= guess; + + for ( int i=1; i A[i] ? max : A[i] ); + } + return max; + } + + public static double min( double[] A ) { + double min= A[0]; + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * + * @author eew + */ +public abstract class DasPNGConstants { + + /* + * Chunk type constants + */ + static final String CHUNK_TYPE_IHDR = "IHDR"; + static final String CHUNK_TYPE_PLTE = "PLTE"; + static final String CHUNK_TYPE_IDAT = "IDAT"; + static final String CHUNK_TYPE_IEND = "IEND"; + static final String CHUNK_TYPE_bKGD = "bKGD"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_cHRM = "cHRM"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_gAMA = "gAMA"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_hIST = "hIST"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_pHYs = "pHYs"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_sBIT = "sBIT"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_tEXT = "tEXt"; + static final String CHUNK_TYPE_tIME = "tIME"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_tRNS = "tRNS"; //CURRENTLY UNSUPPORTED + static final String CHUNK_TYPE_zTXT = "zTXt"; //CURRENTLY UNSUPPORTED + + public static final int DEFAULT_GAMMA = 45000; //gamma of .45 (multiplied by 100000) + + public static final String KEYWORD_TITLE = "Title"; + public static final String KEYWORD_AUTHOR = "Author"; + public static final String KEYWORD_DESCRIPTION = "Description"; + public static final String KEYWORD_COPYRIGHT = "Copyright"; + public static final String KEYWORD_CREATION_TIME = "Creation Time"; + public static final String KEYWORD_SOFTWARE = "Software"; + public static final String KEYWORD_DISCLAIMER = "Disclaimer"; + public static final String KEYWORD_WARNING = "Warning"; + public static final String KEYWORD_SOURCE = "Source"; + public static final String KEYWORD_COMMENT = "Comment"; + + protected HashMap textMap = new HashMap(); + + protected int gamma = DEFAULT_GAMMA; + + DasPNGConstants() { + } + + /** Returns an unmodifiable java.util.List containing the contents of + * all of the tEXT chunks with the specified keyword. The return value + * is guaranteed to be non-null. If no tEXT chunks with the specified + * keyword exist, then an empty list is returned. + * @param keyword the specified keyword + * @return a java.util.List of the contents of tEXT chunks. + */ + public List getText(String keyword) { + List list = (List)textMap.get(keyword); + if (list == null) { + return Collections.EMPTY_LIST; + } + return Collections.unmodifiableList(list); + } + + protected static byte[] getISO8859_1Bytes(String header) { + try { + return header.getBytes("ISO-8859-1"); + } + catch (java.io.UnsupportedEncodingException uee) { + throw new AssertionError(uee); + } + } + + public int getGamma() { + return gamma; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DasPNGEncoder.java b/dasCore/src/main/java/org/das2/util/DasPNGEncoder.java new file mode 100644 index 000000000..861e0804f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasPNGEncoder.java @@ -0,0 +1,349 @@ +/* File: DasPNGEncoder.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.awt.image.BufferedImage; +import java.awt.image.IndexColorModel; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.zip.CRC32; +import java.util.zip.Deflater; + +/** + * + * @author eew + */ +public class DasPNGEncoder extends DasPNGConstants { + + + /** Creates a new instance of DasPNGEncoder */ + public DasPNGEncoder() { + } + + /** Adds a tEXT chunk with the specified keyword and content. + * @param keyword the specified keyword + * @param content the content for the tEXT chunk + */ + public void addText(String keyword, String content) { + List list = (List)textMap.get(keyword); + if (list == null) { + list = new ArrayList(); + textMap.put(keyword, list); + } + list.add(content); + } + + /** Removes the tEXT chunk with the specifed keyword and content. + * @param keyword the specified keyword + * @param content the specified content to be removed + */ + public void removeText(String keyword, String content) { + List list = (List)textMap.get(keyword); + if (list != null) { + list.remove(content); + } + } + + /** Removes all tEXT chunk with the specified keyword + * @param keyword the specified keyword. + */ + public void removeAllText(String keyword) { + textMap.remove(keyword); + } + + public void setGamma(int gamma) { + this.gamma = gamma; + } + + public void write(BufferedImage image, OutputStream out) throws IOException { + LinkedList chunkList = new LinkedList(); + int totalSize = 0; + chunkList.add(getHeaderBytes()); + chunkList.add(getIHDRBytes(image)); + gettEXtBytes(chunkList); + chunkList.add(getgAMABytes()); + chunkList.add(getPLTEBytes(image)); + chunkList.add(getIDATBytes(image)); + chunkList.add(getIENDBytes()); + Iterator iterator = chunkList.iterator(); + while (iterator.hasNext()) { + totalSize += ((byte[])iterator.next()).length; + } + ByteBuffer buffer = ByteBuffer.allocate(totalSize); + iterator = chunkList.iterator(); + while (iterator.hasNext()) { + buffer.put((byte[])iterator.next()); + } + out.write(buffer.array()); + } + + private byte[] getHeaderBytes() { + return new byte[] { + (byte)137, (byte)80, (byte)78, (byte)71, + (byte)13, (byte)10, (byte)26, (byte)10 + }; + } + + /** + * Width: 4 bytes + * Height: 4 bytes + * Bit depth: 1 byte (allowed values: 1, 2, 4, 8, 16) + * Color type: 1 byte + Color Allowed Interpretation + Type Bit Depths + 0 1,2,4,8,16 Each pixel is a grayscale sample. + 2 8,16 Each pixel is an R,G,B triple. + 3 1,2,4,8 Each pixel is a palette index; + a PLTE chunk must appear. + 4 8,16 Each pixel is a grayscale sample, + followed by an alpha sample. + 6 8,16 Each pixel is an R,G,B triple, + followed by an alpha sample. + * Compression method: 1 byte (must be 0) + * Filter method: 1 byte (must be 0) + * Interlace method: 1 byte (interlacing not supported, will be 0) + */ + private byte[] getIHDRBytes(BufferedImage image) { + byte bitDepth; + byte colorType; + int imageType = image.getType(); + switch (imageType) { + //24 bit image types + case BufferedImage.TYPE_3BYTE_BGR: + case BufferedImage.TYPE_INT_BGR: + case BufferedImage.TYPE_INT_RGB: + case BufferedImage.TYPE_USHORT_555_RGB: + case BufferedImage.TYPE_USHORT_565_RGB: + bitDepth = 8; + colorType = 2; + break; + + //32 bit alpha + case BufferedImage.TYPE_4BYTE_ABGR: + case BufferedImage.TYPE_INT_ARGB: + bitDepth = 8; + colorType = 6; + break; + + case BufferedImage.TYPE_BYTE_INDEXED: + bitDepth = 8; + colorType = 3; + break; + + case BufferedImage.TYPE_BYTE_GRAY: + bitDepth = 8; + colorType = 0; + break; + + case BufferedImage.TYPE_USHORT_GRAY: + bitDepth = 16; + colorType = 0; + break; + + //Currently unsupported types + case BufferedImage.TYPE_BYTE_BINARY: + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + case BufferedImage.TYPE_INT_ARGB_PRE: + case BufferedImage.TYPE_CUSTOM: + default: + throw new RuntimeException("Unsupported image type"); + } + byte compressionMethod = 0; + byte filterMethod = 0; + byte interlaceMethod = 0; + + byte[] array = new byte[25]; + ByteBuffer buffer = ByteBuffer.wrap(array); + buffer.putInt(13); + buffer.put(getISO8859_1Bytes(CHUNK_TYPE_IHDR)); + buffer.putInt(image.getWidth()); + buffer.putInt(image.getHeight()); + buffer.put(bitDepth); + buffer.put(colorType); + buffer.put(compressionMethod); + buffer.put(filterMethod); + buffer.put(interlaceMethod); + CRC32 crc = new CRC32(); + crc.update(array, 4, 17); + buffer.putInt((int)crc.getValue()); + return array; + } + + private byte[] getgAMABytes() { + byte[] array = new byte[16]; + ByteBuffer buffer = ByteBuffer.wrap(array); + buffer.putInt(4); + buffer.put(getISO8859_1Bytes(CHUNK_TYPE_gAMA)); + buffer.putInt(gamma); + CRC32 crc = new CRC32(); + crc.update(array, 4, 8); + buffer.putInt((int)crc.getValue()); + return array; + } + + private byte[] getPLTEBytes(BufferedImage image) { + if (image.getType() != BufferedImage.TYPE_BYTE_INDEXED) { + return new byte[0]; + } + IndexColorModel cm = (IndexColorModel)image.getColorModel(); + int colorCount = cm.getMapSize(); + throw new UnsupportedOperationException(); + } + + private byte[] getIDATBytes(BufferedImage image) { + byte[] imageData; + int imageType = image.getType(); + switch (imageType) { + case BufferedImage.TYPE_3BYTE_BGR: + case BufferedImage.TYPE_INT_BGR: + case BufferedImage.TYPE_INT_RGB: + case BufferedImage.TYPE_USHORT_555_RGB: + case BufferedImage.TYPE_USHORT_565_RGB: + imageData = getRGBBytes(image); + break; + + //32 bit alpha + case BufferedImage.TYPE_4BYTE_ABGR: + imageData = getABGRBytes(image); + break; + + case BufferedImage.TYPE_INT_ARGB: + imageData = getARGBBytes(image); + break; + + case BufferedImage.TYPE_BYTE_INDEXED: + case BufferedImage.TYPE_BYTE_GRAY: + imageData = get8BitSampleBytes(image); + break; + + case BufferedImage.TYPE_USHORT_GRAY: + imageData = get16BitSampleBytes(image); + break; + + //Currently unsupported types + case BufferedImage.TYPE_BYTE_BINARY: + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + case BufferedImage.TYPE_INT_ARGB_PRE: + case BufferedImage.TYPE_CUSTOM: + default: + throw new RuntimeException("Unsupported image type"); + } + + byte[] compressedImageData = new byte[imageData.length]; + Deflater deflater = new Deflater(); + deflater.setInput(imageData); + deflater.finish(); + int compressedSize = deflater.deflate(compressedImageData); + + byte[] array = new byte[compressedSize + 12]; + ByteBuffer buffer = ByteBuffer.wrap(array); + buffer.putInt(compressedSize); + buffer.put(getISO8859_1Bytes(CHUNK_TYPE_IDAT)); + buffer.put(compressedImageData, 0, compressedSize); + CRC32 crc = new CRC32(); + crc.update(array, 4, compressedSize + 4); + buffer.putInt((int)crc.getValue()); + return array; + } + + private byte[] getRGBBytes(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + int[] intPixels = new int[width * height]; + image.getRGB(0, 0, width, height, intPixels, 0, width); + byte[] bytePixels = new byte[intPixels.length * 3 + height]; + + for (int line = 0; line < height; line++) { + int offset = (width * 3 + 1) * line; + bytePixels[offset] = (byte)0; + for (int pixel = 0; pixel < width; pixel++) { + int intIndex = line * width + pixel; + int byteIndex = offset + (pixel * 3 + 1); + bytePixels[byteIndex] = (byte)((0xFF0000 & intPixels[intIndex]) >> 16); + bytePixels[byteIndex + 1] = (byte)((0x00FF00 & intPixels[intIndex]) >> 8); + bytePixels[byteIndex + 2] = (byte)(0x0000FF & intPixels[intIndex]); + } + } + return bytePixels; + } + + private byte[] getARGBBytes(BufferedImage image) { + throw new UnsupportedOperationException(); + } + + private byte[] getABGRBytes(BufferedImage image) { + throw new UnsupportedOperationException(); + } + + private byte[] get8BitSampleBytes(BufferedImage image) { + throw new UnsupportedOperationException(); + } + + private byte[] get16BitSampleBytes(BufferedImage image) { + throw new UnsupportedOperationException(); + } + + private byte[] getIENDBytes() { + byte[] array = new byte[12]; + byte[] typeBytes = getISO8859_1Bytes(CHUNK_TYPE_IEND); + ByteBuffer buffer = ByteBuffer.wrap(array); + buffer.putInt(0); + buffer.put(typeBytes); + CRC32 crc = new CRC32(); + crc.update(typeBytes); + buffer.putInt((int)crc.getValue()); + return array; + } + + private void gettEXtBytes(List list) { + Iterator entries = textMap.entrySet().iterator(); + while (entries.hasNext()) { + Map.Entry entry = (Map.Entry)entries.next(); + List contentList = (List)entry.getValue(); + Iterator content = contentList.iterator(); + while (content.hasNext()) { + list.add(gettEXtBytes((String)entry.getKey(), (String)content.next())); + } + } + } + + private byte[] gettEXtBytes(String keyword, String content) { + byte[] keywordBytes = getISO8859_1Bytes(keyword); + byte[] contentBytes = getISO8859_1Bytes(content); + byte[] array = new byte[keywordBytes.length + contentBytes.length + 13]; + ByteBuffer buffer = ByteBuffer.wrap(array); + buffer.putInt(keywordBytes.length + contentBytes.length + 1); + buffer.put(getISO8859_1Bytes(CHUNK_TYPE_tEXT)); + buffer.put(keywordBytes); + buffer.put((byte)0); + buffer.put(contentBytes); + CRC32 crc = new CRC32(); + crc.update(array, 4, keywordBytes.length + contentBytes.length + 5); + buffer.putInt((int)crc.getValue()); + return array; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DasProgressMonitorInputStream.java b/dasCore/src/main/java/org/das2/util/DasProgressMonitorInputStream.java new file mode 100755 index 000000000..5da3e6414 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasProgressMonitorInputStream.java @@ -0,0 +1,210 @@ +/* File: DasProgressMonitorInputStream.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 23, 2003, 5:00 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import org.das2.util.monitor.ProgressMonitor; +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.text.*; + +/** + * + * @author Edward West + */ +public class DasProgressMonitorInputStream extends java.io.FilterInputStream { + + private ProgressMonitor monitor; + private boolean started = false; + private int bytesRead = 0; + long birthTimeMilli; + long deathTimeMilli; + DecimalFormat transferRateFormat; + boolean enableProgressPosition= true; + + private long streamLength= 1000000; // this is usually close because of server side averaging. + private long taskSize= streamLength/1000; + + /** Creates a new instance of DasProgressMonitorInputStream */ + public DasProgressMonitorInputStream( InputStream in, ProgressMonitor monitor ) { + super(in); + this.monitor = monitor; + this.birthTimeMilli= System.currentTimeMillis(); + this.deathTimeMilli= -1; + } + + private void reportTransmitSpeed() { + if (transferRateFormat==null ) { + transferRateFormat= new DecimalFormat(); + transferRateFormat.setMaximumFractionDigits(2); + transferRateFormat.setMinimumFractionDigits(2); + } + monitor.setProgressMessage("("+ transferRateFormat.format(calcTransmitSpeed()/1024) +"kB/s)"); + if ( enableProgressPosition ) monitor.setTaskProgress(bytesRead/1000); + } + + + private double calcTransmitSpeed() { + // return speed in bytes/second. + long totalBytesRead= bytesRead; + long timeElapsed; + if ( deathTimeMilli>-1 ) { + timeElapsed= deathTimeMilli-birthTimeMilli; + } else { + timeElapsed= System.currentTimeMillis()-birthTimeMilli; + } + if ( timeElapsed==0 ) return Double.POSITIVE_INFINITY; + return 1000 * totalBytesRead / timeElapsed; + } + + public int read() throws IOException { + checkCancelled(); + int result = super.read(); + if (monitor != null) { + if (!started) { + started = true; + monitor.setTaskSize(taskSize); + monitor.started(); + } + if (bytesRead == -1) { + monitor.finished(); + } + else { + bytesRead++; + checkCancelled(); + reportTransmitSpeed(); + } + } + return result; + } + + public int read(byte[] b) throws IOException { + checkCancelled(); + int result = super.read(b); + if (monitor != null) { + if (!started) { + started = true; + monitor.setTaskSize(taskSize); + monitor.started(); + } + if (bytesRead == -1) { + monitor.finished(); + } + else { + bytesRead += result; + checkCancelled(); + reportTransmitSpeed(); + } + } + return result; + } + + public int read(byte[] b, int off, int len) throws IOException { + checkCancelled(); + int result = super.read(b, off, len); + if (monitor != null) { + if (!started) { + started = true; + monitor.setTaskSize( taskSize ); + monitor.started(); + } + if (bytesRead == -1) { + monitor.finished(); + } + else { + bytesRead += result; + checkCancelled(); + reportTransmitSpeed(); + } + } + return result; + } + + private void checkCancelled() throws IOException { + if (monitor != null && monitor.isCancelled()) { + close(); + throw new InterruptedIOException("Operation cancelled"); + } + } + + public void close() throws IOException { + super.close(); + deathTimeMilli= System.currentTimeMillis(); + if (monitor != null) { + monitor.finished(); + started= false; + } + } + + /** + * disable/enable setting of progress position, true by default. Transfer + * rate will still be reported. This is introduced in case another agent + * (the das2Stream reader, in particular) can set the progress position + * more accurately. + */ + public void setEnableProgressPosition( boolean value ) { + this.enableProgressPosition= value; + } + + /** + * Utility field used by bound properties. + */ + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + /** + * Getter for property taskSize. + * @return Value of property taskSize. + */ + public long getStreamLength() { + return this.streamLength; + } + + /** + * Setter for property taskSize. + * @param taskSize New value of property taskSize. + */ + public void setStreamLength(long taskSize) { + long oldTaskSize = this.streamLength; + this.streamLength = taskSize; + this.taskSize= taskSize==-1 ? taskSize : streamLength/1000; + propertyChangeSupport.firePropertyChange ("streamLength", new Long (oldTaskSize), new Long (taskSize)); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DasProgressMonitorReadableByteChannel.java b/dasCore/src/main/java/org/das2/util/DasProgressMonitorReadableByteChannel.java new file mode 100644 index 000000000..23021a212 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DasProgressMonitorReadableByteChannel.java @@ -0,0 +1,171 @@ +/* File: DasProgressMonitorInputStream.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on September 23, 2003, 5:00 PM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.util; + +import org.das2.util.monitor.ProgressMonitor; +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.text.*; + +/** + * + * @author Edward West + */ +public class DasProgressMonitorReadableByteChannel implements ReadableByteChannel { + + private ProgressMonitor monitor; + private boolean started = false; + private int bytesRead = 0; + long birthTimeMilli; + long deathTimeMilli; + DecimalFormat transferRateFormat; + boolean enableProgressPosition = true; + private long streamLength = 1000000; // this is usually close because of server side averaging. + private long taskSize = streamLength / 1000; + // we're wrapping this channel + ReadableByteChannel in; + + /** Creates a new instance of DasProgressMonitorInputStream */ + public DasProgressMonitorReadableByteChannel(ReadableByteChannel in, ProgressMonitor monitor) { + this.monitor = monitor; + this.birthTimeMilli = System.currentTimeMillis(); + this.deathTimeMilli = -1; + this.in = in; + } + + private void reportTransmitSpeed() { + if (transferRateFormat == null) { + transferRateFormat = new DecimalFormat(); + transferRateFormat.setMaximumFractionDigits(2); + transferRateFormat.setMinimumFractionDigits(2); + } + monitor.setProgressMessage("(" + transferRateFormat.format(calcTransmitSpeed() / 1024) + "kB/s)"); + if (enableProgressPosition) { + monitor.setTaskProgress(bytesRead / 1000); + } + } + + private double calcTransmitSpeed() { + // return speed in bytes/second. + long totalBytesRead = bytesRead; + long timeElapsed; + if (deathTimeMilli > -1) { + timeElapsed = deathTimeMilli - birthTimeMilli; + } else { + timeElapsed = System.currentTimeMillis() - birthTimeMilli; + } + if (timeElapsed == 0) { + return Double.POSITIVE_INFINITY; + } + return 1000 * totalBytesRead / timeElapsed; + } + + private void checkCancelled() throws IOException { + if (monitor != null && monitor.isCancelled()) { + close(); + throw new InterruptedIOException("Operation cancelled"); + } + } + + public void close() throws IOException { + in.close(); + deathTimeMilli = System.currentTimeMillis(); + if (monitor != null) { + monitor.finished(); + started = false; + } + } + + /** + * disable/enable setting of progress position, true by default. Transfer + * rate will still be reported. This is introduced in case another agent + * (the das2Stream reader, in particular) can set the progress position + * more accurately. + */ + public void setEnableProgressPosition(boolean value) { + this.enableProgressPosition = value; + } + /** + * Utility field used by bound properties. + */ + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + /** + * Getter for property taskSize. + * @return Value of property taskSize. + */ + public long getStreamLength() { + return this.streamLength; + } + + /** + * Setter for property taskSize. + * @param taskSize New value of property taskSize. + */ + public void setStreamLength(long taskSize) { + long oldTaskSize = this.streamLength; + this.streamLength = taskSize; + this.taskSize = taskSize == -1 ? taskSize : streamLength / 1000; + propertyChangeSupport.firePropertyChange("streamLength", new Long(oldTaskSize), new Long(taskSize)); + } + + public int read(ByteBuffer dst) throws IOException { + int result = in.read(dst); + + if (!started) { + started = true; + monitor.setTaskSize(taskSize); + monitor.started(); + } + if (bytesRead == -1) { + monitor.finished(); + } else { + bytesRead += result; + checkCancelled(); + reportTransmitSpeed(); + } + + return result; + } + + public boolean isOpen() { + return in.isOpen(); + } +} diff --git a/dasCore/src/main/java/org/das2/util/DeflaterChannel.java b/dasCore/src/main/java/org/das2/util/DeflaterChannel.java new file mode 100644 index 000000000..89a3313e3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DeflaterChannel.java @@ -0,0 +1,105 @@ +/* File: DeflaterChannel.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on March 26, 2004, 10:00 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; + +/** + * + * @author eew + */ +public class DeflaterChannel implements WritableByteChannel { + + private boolean closed = false; + private WritableByteChannel out; + private Deflater deflater; + private ByteBuffer buf; + private byte[] inBuf; + + public DeflaterChannel(WritableByteChannel out) { + this.out = out; + byte[] array = new byte[4096]; + buf = ByteBuffer.wrap(array); + deflater = new Deflater(); + } + + public void flush() throws IOException { + int byteCount; + deflater.finish(); + while ((byteCount = deflater.deflate(buf.array())) != 0) { + buf.position(0).limit(byteCount); + while(buf.hasRemaining()) { + out.write(buf); + } + } + } + + public synchronized void close() throws IOException { + if (!closed) { + flush(); + closed = true; + out.close(); + out = null; + deflater.end(); + deflater = null; + buf = null; + } + } + + public boolean isOpen() { + return !closed; + } + + public synchronized int write(ByteBuffer src) throws IOException { + byte[] inBuf; + int offset, length; + if (src.hasArray()) { + inBuf = src.array(); + offset = src.arrayOffset(); + length = src.remaining(); + src.position(src.position() + src.remaining()); + } + else { + if (this.inBuf == null) { + this.inBuf = new byte[4096]; + } + inBuf = this.inBuf; + offset = 0; + length = Math.min(inBuf.length, src.remaining()); + src.get(inBuf, 0, length); + } + deflater.setInput(inBuf, offset, length); + while (!deflater.needsInput()) { + int byteCount = deflater.deflate(buf.array()); + buf.position(0).limit(byteCount); + while (buf.hasRemaining()) { out.write(buf); } + } + return length; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DenseConsoleFormatter.java b/dasCore/src/main/java/org/das2/util/DenseConsoleFormatter.java new file mode 100644 index 000000000..acabd75e8 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DenseConsoleFormatter.java @@ -0,0 +1,26 @@ +/* + * NBConsoleFormatter.java + * + * Created on April 7, 2005, 12:13 PM + */ + +package org.das2.util; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * + * @author Jeremy + */ +public class DenseConsoleFormatter extends Formatter { + + public String format( LogRecord rec ) { + StackTraceElement[] st= new Throwable().getStackTrace(); + return rec.getLoggerName()+": "+rec.getLevel().getLocalizedName()+": "+rec.getMessage()+"\n"; + } + + public DenseConsoleFormatter() { + } + +} diff --git a/dasCore/src/main/java/org/das2/util/DnDSupport.java b/dasCore/src/main/java/org/das2/util/DnDSupport.java new file mode 100644 index 000000000..f3cac9b33 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/DnDSupport.java @@ -0,0 +1,204 @@ +/* File: DnDSupport.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.*; + +/** + * + * @author eew + */ +public abstract class DnDSupport { + + private Component component; + + private DnDSupport parent; + + private int ops; + + private GestureRecognizer gestureRecognizer; + + protected DnDSupport(Component component, int ops, DnDSupport parent) { + this.component = component; + this.parent = parent; + this.ops = ops; + component.setDropTarget(new DropTarget(component, ops, new DropHandler())); + } + + public void setParent(DnDSupport parent) { + this.parent = parent; + } + + protected abstract int canAccept(DataFlavor[] flavors, int x, int y, int action); + + protected abstract boolean importData(Transferable t, int x, int y, int action); + + protected abstract void done(); + + protected abstract void exportDone(Transferable t, int action); + + protected abstract Transferable getTransferable(int x, int y, int action); + + private int canAcceptInternal(DataFlavor[] flavors, int x, int y, int action) { + int result = canAccept(flavors, x, y, action); + if (result == -1 && parent != null) { + return parent.canAcceptInternal(flavors, x + component.getX(), y + component.getY(), action); + } + return result; + } + + private boolean importDataInternal(Transferable t, int x, int y, int action) { + if (canAccept(t.getTransferDataFlavors(), x, y, action) != -1) { + return importData(t, x, y, action); + } + else if (parent != null) { + return parent.importDataInternal(t, x + component.getX(), y + component.getY(), action); + } + else { + return false; + } + } + + private void doneInternal() { + done(); + if (parent != null) { + parent.doneInternal(); + } + } + + public void startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + if (gestureRecognizer == null) { + gestureRecognizer = new GestureRecognizer(component, new DragHandler()); + } + gestureRecognizer.startDrag(x, y, action, evt); + } + + private class DropHandler implements DropTargetListener { + + public void dragEnter(DropTargetDragEvent dtde) { + Point location = dtde.getLocation(); + int action = canAcceptInternal(dtde.getCurrentDataFlavors(), + location.x, location.y, + dtde.getDropAction()); + if (action == -1) { + dtde.rejectDrag(); + } + else { + dtde.acceptDrag(action); + } + } + + public void dragExit(DropTargetEvent dte) { + doneInternal(); + } + + public void dragOver(DropTargetDragEvent dtde) { + Point location = dtde.getLocation(); + int action = canAcceptInternal(dtde.getCurrentDataFlavors(), + location.x, location.y, + dtde.getDropAction()); + if (action == -1) { + dtde.rejectDrag(); + } + else { + dtde.acceptDrag(action); + } + } + + public void drop(DropTargetDropEvent dtde) { + Point location = dtde.getLocation(); + int action = canAcceptInternal(dtde.getCurrentDataFlavors(), + location.x, location.y, + dtde.getDropAction()); + if (action == -1) { + dtde.rejectDrop(); + } + else { + dtde.acceptDrop(action); + boolean success = importDataInternal(dtde.getTransferable(), + location.x, location.y, + dtde.getDropAction()); + dtde.dropComplete(success); + } + doneInternal(); + } + + public void dropActionChanged(DropTargetDragEvent dtde) { + } + } + + private class GestureRecognizer extends java.awt.dnd.DragGestureRecognizer { + + GestureRecognizer(Component component, DragHandler handler) { + super(DragSource.getDefaultDragSource(), component, ops, handler); + } + + void startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { + appendEvent(evt); + fireDragGestureRecognized(action, new Point(x, y)); + } + + protected void registerListeners() {} + + protected void unregisterListeners() {} + + } + + private class DragHandler implements DragGestureListener, DragSourceListener { + + public void dragGestureRecognized(DragGestureEvent dge) { + Point p = dge.getDragOrigin(); + int action = dge.getDragAction(); + Transferable t = getTransferable(p.x, p.y, action); + if (t != null) { + dge.startDrag(new Cursor(Cursor.HAND_CURSOR), t, this); + } + } + + public void dragDropEnd(DragSourceDropEvent dsde) { + DragSourceContext dsc = dsde.getDragSourceContext(); + if (dsde.getDropSuccess()) { + exportDone(dsc.getTransferable(), dsde.getDropAction()); + } else { + exportDone(null, DnDConstants.ACTION_NONE); + } + } + + public void dragEnter(DragSourceDragEvent dsde) { + } + + public void dragExit(DragSourceEvent dse) { + } + + public void dragOver(DragSourceDragEvent dsde) { + } + + public void dropActionChanged(DragSourceDragEvent dsde) { + } + + } +} diff --git a/dasCore/src/main/java/org/das2/util/Entities.java b/dasCore/src/main/java/org/das2/util/Entities.java new file mode 100755 index 000000000..8afe739c6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/Entities.java @@ -0,0 +1,354 @@ +/** + * From Lucene Search Engine. + * code found at http://www.koders.com, decodeEntities() added. + * + */ +package org.das2.util; + +import java.util.*; + +public class Entities { + + static final Hashtable decoder = new Hashtable(300); + static final String[] encoder = new String[0x100]; + + /** + * utility method for decoding entities like ρ into unicode. + * @param str + * @return string with unicode characters for entities. + */ + public static String decodeEntities(String str) { + int i0=0, i=0; + + int MAX_ENTITY_LEN=10; + StringBuffer result= new StringBuffer(); + while ( true ) { + i= str.indexOf("&",i0); + + if ( i==-1 ) { + result.append( str.substring(i0) ); + return result.toString(); + } else { + int i1= str.indexOf(";",i); + if ( i1!=-1 && i1-istars.length() ) { + return formatWide( s, nchars ); + } + int pad= nchars - s.length(); + if ( pad>0 ) { + s= spaces.substring(0,pad) + s; + + } else if ( s.length() > nchars ) { + s= stars.substring(0,nchars); + + } + return s; + } + + public static String formatWide( String s, int nchars ) { + if ( s.length() < nchars ) { + StringBuffer sb= new StringBuffer(nchars-s.length()); + for ( int i=0; i<(nchars-s.length()); i++ ) { + sb.append(' '); + } + s= sb.toString()+s; + } else if ( s.length() > nchars ) { + StringBuffer sb= new StringBuffer(nchars); + for ( int i=0; i<(nchars); i++ ) { + sb.append('*'); + } + s= sb.toString(); + } + return s; + } +} diff --git a/dasCore/src/main/java/org/das2/util/GrannyTextRenderer.java b/dasCore/src/main/java/org/das2/util/GrannyTextRenderer.java new file mode 100644 index 000000000..cafc679b9 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/GrannyTextRenderer.java @@ -0,0 +1,499 @@ +/* File: GrannyTextRenderer.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Stack; +import java.util.Vector; + +/** + * Renders Grandle and Nystrom strings, like "E=mc!e2" + * @author Edward West + */ +public class GrannyTextRenderer { + + public static final int LEFT_ALIGNMENT = 0; + public static final int CENTER_ALIGNMENT = 1; + public static final int RIGHT_ALIGNMENT = 2; + + private Rectangle bounds=null; + private ArrayList lineBounds; + private String str; + private String[] tokens; + private int alignment = LEFT_ALIGNMENT; + private Component parent; + + public GrannyTextRenderer( ) { + + } + + /** + * returns the bounds of the current string. The lower-left corner of + * the first character will be roughly (0,0), to be compatible with + * FontMetrics.getStringBounds(). + * + * @return a Rectangle indicating the text boundaries. + * @throws IllegalArgumentException if the string has not been set. + */ + public Rectangle getBounds() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + maybeInitBounds(); + return new Rectangle(bounds); // defensive copy + } + + private void maybeInitBounds() { + if (bounds == null) { + bounds = new Rectangle((Rectangle)lineBounds.get(0)); + for (int i = 1; i < lineBounds.size(); i++) { + bounds.add((Rectangle)lineBounds.get(i)); + } + } + } + + /** + * returns the width of the bounding box, in pixels. + * @return the width of the bounding box, in pixels. + * @throws IllegalArgumentException if the string has not been set. + */ + public double getWidth() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + maybeInitBounds(); + return bounds.getWidth(); + } + + /** + * returns the width in pixels of the first line. + * @return the width in pixels of the first line. + * @throws IllegalArgumentException if the string has not been set. + * + */ + public double getLineOneWidth() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + return getLineWidth(1); + } + + /** + * returns the calculated width each line. + * @param lineNumber the index of the line, starting with 1. + * @return the width of the bounding box, in pixels. + * @throws IndexOutOfBoundsException if no such line exists. + */ + private double getLineWidth(int lineNumber) { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + return ((Rectangle)lineBounds.get(lineNumber - 1)).getWidth(); + } + + /** + * returns the hieght of the calculated bounding box. + * @return the height of the bounding box, in pixels. + * @throws IllegalArgumentException if the string has not been set. + */ + public double getHeight() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + maybeInitBounds(); + return bounds.getHeight(); + } + + /** + * return the amount that the bounding box will go above the baseline. + * This is also the height of the first line. + * @return the amount that the bounding box will go above the baseline. + * @throws IllegalArgumentException if the string has not been set. + */ + public double getAscent() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + return -1*((Rectangle)lineBounds.get(0)).getY(); + } + + /** + * return the amount that the bounding box will go below the baseline. + * @return the amount that the bounding box will go below the baseline. + * @throws IllegalArgumentException if the string has not been set. + */ + public double getDescent() { + if ( lineBounds==null ) throw new IllegalArgumentException("string is not set"); + maybeInitBounds(); + return bounds.getHeight() + bounds.getY(); + } + + /** + * reset the current string for the GTR to draw, calculating the boundaries + * of the string. For greek and math symbols, unicode characters should be + * used. (See www.unicode.org). + * @deprecated use setString( Graphics g, String str ) instead. + * @param c the component which will provide the graphics. + * @param str the granny string, such as "E=mc!e2" + */ + public void setString( Component c, String str ) { + this.parent= c; + bounds = null; + lineBounds = new ArrayList(); + this.str = Entities.decodeEntities(str); + this.tokens = buildTokenArray(this.str); + this.draw( c.getGraphics(), c.getFont(), 0f, 0f, false ); + } + + /** + * reset the current string for the GTR to draw, calculating the boundaries + * of the string. For greek and math symbols, unicode characters should be + * used. (See www.unicode.org). + * + * @param g the graphics context which will supply the FontMetrics. + * @param str the granny string, such as "E=mc!e2" + */ + public void setString( Graphics g, String str) { + bounds = null; + lineBounds = new ArrayList(); + this.str = Entities.decodeEntities(str); + this.tokens = buildTokenArray(this.str); + this.draw( g, g.getFont(), 0f, 0f, false ); + } + + /** + * reset the current string for the GTR to draw, calculating the boundaries + * of the string. For greek and math symbols, unicode characters should be + * used. (See www.unicode.org). + * + * @param Font the font. This should be consistent + * with the Font used when drawing. + * @param str the granny string, such as "E=mc!e2" + */ + public void setString( Font font, String label) { + bounds = null; + lineBounds = new ArrayList(); + this.str = Entities.decodeEntities(label); + this.tokens = buildTokenArray(this.str); + this.draw( null, font, 0f, 0f, false ); + } + + /** + * returns the current alignment, by default LEFT_ALIGNMENT. + * @return the current alignment. + */ + public int getAlignment() { + return alignment; + } + + /** + * set the alignment for rendering, one of LEFT_ALIGNMENT CENTER_ALIGNMENT or RIGHT_ALIGNMENT. + * @param a the alignment, one of LEFT_ALIGNMENT CENTER_ALIGNMENT or RIGHT_ALIGNMENT. + */ + public void setAlignment(int a) { + if (a != LEFT_ALIGNMENT && a != CENTER_ALIGNMENT && a != RIGHT_ALIGNMENT) { + throw new IllegalArgumentException(); + } + alignment = a; + } + + /** + * draw the current string. Note the first line will be above iy, and following lines will + * be below iy. This is to be consistent with Graphics2D.drawString. + * + * @param ig Graphic object to use to render the text. + * @param ix The x position of the first character of text. + * @param iy The y position of the baseline of the first line of text. + */ + public void draw( Graphics ig, float ix, float iy ) { + this.draw( ig, ig.getFont(), ix, iy, true); + } + + + /** + * Draws the given string and/or computes its bounds. + * + * This method is intended to be called by both {@link #drawString(Graphics,String,float,float)} + * and {@link #getBounds(String,float,float,Component)}. + * + * All added string rendering capabilities should be handled here so that any changes + * will be incorporated into both the rendering algorithm and the bounds finding + * algorithm at the same time. + * + * @param ig Graphic object to use to render the text. This can be null if + * draw is false. + * @param ix The x position of the first character of text. + * @param iy The y position of the baseline of the first line of text. + * @param c The Component that the String will be rendered in. + * This can be null if draw is true + * @param draw A boolean flag indicating whether or not the drawing code should be executed. + * @throws NullPointerException if ig is null AND draw is true. + * @throws NullPointerException if c is null AND draw is false. + */ + private void draw(Graphics ig, Font baseFont, float ix, float iy, boolean draw ) { + Graphics2D g = null; + Rectangle bounds = null; + + if (draw) { + g = (Graphics2D)ig.create(); +// RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, +// RenderingHints.VALUE_ANTIALIAS_ON); +// hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); +// g.setRenderingHints(hints); + } + + final int NONE = 0; + final int SUB_U = 1; + final int SUB_D = 2; + final int SUB_L = 3; + final int EXP = 4; + final int IND = 5; + + final int LOWCAPS= 10; // not in IDL's set + final int SUB_A = 11; + final int SUB_B = 12; + + class TextPosition { + public TextPosition(int sub, int ei, float x, float y) { + this.sub = sub; this.ei = ei; this.x = x; this.y = y; } + public TextPosition(TextPosition p) { + copy(p); } + public void copy(TextPosition p) { + sub = p.sub; ei = p.ei; x = p.x; y = p.y; } + public int sub; + public int ei; + public float x; + public float y; + } + + if ( ig==null ) { + ig= getHeadlessGraphicsContext(); + } + + if ( baseFont==null ) { + baseFont= Font.decode("sans-10"); + } + + int lineNum=1; + + TextPosition current = new TextPosition(NONE, NONE, ix, iy); + if (draw) { + if (alignment == CENTER_ALIGNMENT) { + current.x += (getWidth() - getLineOneWidth()) / 2.0; + } else if (alignment == RIGHT_ALIGNMENT) { + current.x += (getWidth() - getLineOneWidth()); + } + } + + if (!draw) { + bounds= new Rectangle((int)ix,(int)iy,0,0); + } + + Stack saveStack = new Stack(); + + for (int i = 0; i < tokens.length; i++) { + if (tokens[i].charAt(0) == '!') { + switch (tokens[i].charAt(1)) { + case 'A': + case 'a': + current.sub= SUB_A; + current.ei = NONE; + break; + case 'B': + case 'b': + current.sub= SUB_B; + current.ei = NONE; + break; + case 'C': + case 'c': + lineNum++; + current.sub = NONE; + current.ei = NONE; + current.x = ix; + current.y += baseFont.getSize2D(); + if (draw) { + g.setFont(baseFont); + if (alignment == CENTER_ALIGNMENT) { + current.x += (getWidth() - getLineWidth(lineNum)) / 2.0; + } else if (alignment == RIGHT_ALIGNMENT) { + current.x += (getWidth() - getLineWidth(lineNum)); + } + } + saveStack.clear(); + if (!draw) { + lineBounds.add(bounds); + bounds = new Rectangle((int)current.x, (int)current.y, 0, 0); + } + break; + case 'U': + case 'u': + current.sub = SUB_U; + current.ei = NONE; + break; + case 'D': + case 'd': + current.sub = SUB_D; + current.ei = NONE; + break; + case 'L': + case 'l': + current.sub = SUB_L; + current.ei = NONE; + break; + case 'K': + case 'k': + current.ei = LOWCAPS; + break; + case 'E': + case 'e': + current.ei = EXP; + break; + case 'I': + case 'i': + current.ei = IND; + break; + case 'S': + case 's': + saveStack.push(new TextPosition(current)); + break; + case 'R': + case 'r': + if (saveStack.peek() == null) return; + current.copy((TextPosition)saveStack.pop()); + break; + case 'N': + case 'n': + current.sub = NONE; + current.ei = NONE; + break; + case '!': + break; + default:break; + } + } else { + Font font = baseFont; + float size = baseFont.getSize2D(); + float y = current.y; + switch (current.sub) { + case SUB_U: + font = baseFont.deriveFont(size * 0.62f); + y = y - 0.38f * size; + size = size * 0.62f; + break; + case SUB_D: + font = baseFont.deriveFont(size * 0.62f); + y = y + 0.31f * size; + size = size * 0.62f; + break; + case SUB_L: + font = baseFont.deriveFont(size * 0.62f); + y = y + 0.62f * size; + size = size * 0.62f; + break; + case SUB_A: + y= current.y - size/2; + break; + case SUB_B: + y= current.y + size/2; + break; + + default:break; + } + switch (current.ei) { + case EXP: + font = font.deriveFont(size * 0.44f); + y = y - 0.56f * size; + break; + case IND: + font = font.deriveFont(size * 0.44f); + y = y + 0.22f * size; + break; + case LOWCAPS: + font= font.deriveFont(size * 0.80f ); + break; + default:break; + } + if (draw) { + g.setFont(font); + g.drawString(tokens[i], current.x, y); + current.x += g.getFontMetrics(font).stringWidth(tokens[i]); + //bounds.translate((int)ix,(int)iy); + //g.draw(bounds); //useful for debugging + //g.drawLine((int)ix,(int)iy,(int)ix+4,(int)iy); + } else { + FontMetrics fm= ig.getFontMetrics(font); + bounds.add(current.x, y+fm.getDescent()); + bounds.add(current.x+fm.stringWidth(tokens[i]),y-fm.getAscent() ); // removed -5.0 pixels + current.x += ig.getFontMetrics(font).stringWidth(tokens[i]); + } + } + } // for (int i = 0; i < tokens.length; i++) + if (!draw) { + lineBounds.add(bounds); + } + } + + private static String[] buildTokenArray(String str) { + Vector vector = new Vector(); + int begin; + int end = 0; + int index = 0; + while(end < str.length()) { + begin = end; + if (str.charAt(begin) == '!') { + end = begin + 2; + } else { + end = str.indexOf('!', begin); + if (end == -1) end = str.length(); + } + vector.add(str.substring(begin, end)); + } + + String[] list = new String[vector.size()]; + + vector.copyInto((Object[])list); + + return list; + } + + public String toString() { + maybeInitBounds(); + StringBuffer buffer = new StringBuffer(getClass().getName()); + buffer.append(": ").append(str).append(", "); + buffer.append("bounds: "+bounds).append(", ").append("lineBounds:"+lineBounds).append(", "); + return buffer.toString(); + } + + /** + * useful for debugging. + */ + private void drawBounds(Graphics g, int ix, int iy) { + g.setColor(Color.BLUE); + g.drawRect(bounds.x + ix, bounds.y + iy, bounds.width, bounds.height); + g.setColor(Color.GREEN); + for (java.util.Iterator i = lineBounds.iterator(); i.hasNext();) { + Rectangle rc = (Rectangle)i.next(); + g.drawRect(rc.x + ix, rc.y + iy, rc.width, rc.height); + } + } + + private static Graphics headlessGraphics=null; + private static synchronized Graphics getHeadlessGraphicsContext() { + if ( headlessGraphics==null ) { + headlessGraphics= new BufferedImage(10,10,BufferedImage.TYPE_INT_ARGB).getGraphics(); + } + return headlessGraphics; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/GraphicalLogHandler.java b/dasCore/src/main/java/org/das2/util/GraphicalLogHandler.java new file mode 100644 index 000000000..b1352b324 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/GraphicalLogHandler.java @@ -0,0 +1,502 @@ +/* + * GraphicalLogFormatter.java + * + * Created on December 8, 2005, 2:32 PM + * + * + */ + +package org.das2.util; +import org.das2.util.GrannyTextRenderer; +import org.das2.util.ObjectLocator; +import org.das2.util.DenseConsoleFormatter; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.DasApplication; +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.event.BoxRenderer; +import org.das2.event.BoxSelectionEvent; +import org.das2.event.BoxSelectionListener; +import org.das2.event.BoxSelectorMouseModule; +import org.das2.event.LabelDragRenderer; +import org.das2.event.MouseModule; +import org.das2.graph.DasAxis; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import org.das2.graph.Legend; +import org.das2.graph.Renderer; +import org.das2.system.DasLogger; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + + + + +/** + * + * @author Jeremy + */ +public class GraphicalLogHandler extends Handler { + + List records= new ArrayList(); + List yAxisValues= new ArrayList(); + List times= new ArrayList(); + + Renderer renderer; + boolean updating= false; + Thread updateThread; + + long time0; + + HashMap loggerMap= new HashMap(); + HashMap yaxisMap= new HashMap(); + + private final int YAXIS_THREAD = -199; + private final int YAXIS_CLASS = -198; + //private final int yaxisDimension = YAXIS_THREAD; + private final int yaxisDimension = YAXIS_CLASS; + + DasAxis xaxis; + Legend legend; + + JFrame frame; + + // this is to avoid initialization failures + long sleepInitiallyTime= 2000; // milliseconds + + public GraphicalLogHandler() { + time0= System.currentTimeMillis(); + } + + private void createCanvas() { + if ( loggerMap.size()==0 ) { + loggerMap.put( DasLogger.getLogger(DasLogger.APPLICATION_LOG).getName(), Color.black ); + loggerMap.put( DasLogger.getLogger(DasLogger.DATA_OPERATIONS_LOG).getName(), Color.blue ); + loggerMap.put( DasLogger.getLogger(DasLogger.DATA_TRANSFER_LOG).getName(), Color.YELLOW ); + loggerMap.put( DasLogger.getLogger(DasLogger.GRAPHICS_LOG ).getName(), Color.PINK ); + loggerMap.put( DasLogger.getLogger(DasLogger.SYSTEM_LOG ).getName(), Color.gray ); + loggerMap.put( DasLogger.getLogger(DasLogger.GUI_LOG ).getName(), Color.green ); + loggerMap.put( DasLogger.getLogger(DasLogger.DASML_LOG).getName(), Color.LIGHT_GRAY ); + } + + DasCanvas canvas= new DasCanvas(800,400); + DasPlot plot= DasPlot.createPlot( new DatumRange( 0, 10, Units.seconds ) , + new DatumRange( 0, 10, Units.dimensionless ) ); + xaxis= plot.getXAxis(); + xaxis.setAnimated(false); + + renderer.setDataSetLoader(null); + plot.addRenderer( renderer ); + + canvas.add( plot, DasRow.create( canvas ), DasColumn.create(canvas) ); + + MouseModule mm= getMouseModule(); + plot.getDasMouseInputAdapter().addMouseModule( mm ); + plot.getDasMouseInputAdapter().setPrimaryModule( mm ); + + mm= getShowLogMouseModule( plot ); + plot.getDasMouseInputAdapter().addMouseModule( mm ); + plot.getDasMouseInputAdapter().setSecondaryModule( mm ); + + legend= new Legend(); + canvas.add( legend, new DasRow( canvas, 0.1, 0.5 ), new DasColumn( canvas, 0.8, 0.98 ) ); + + for ( Iterator i= loggerMap.keySet().iterator(); i.hasNext(); ) { + Object key= i.next(); + String name= String.valueOf(key); + if ( name.equals("") ) name=""; + legend.add( legend.getIcon( (Color)loggerMap.get(key) ), name ); + } + + frame= DasApplication.getDefaultApplication().createMainFrame( "GraphicalLogHandler" ); + JPanel appPanel= new JPanel( new BorderLayout() ); + appPanel.add( canvas, BorderLayout.CENTER ); + + JPanel controlPanel= new JPanel(); + controlPanel.setLayout( new BoxLayout( controlPanel, BoxLayout.X_AXIS ) ); + + JCheckBox jcb= new JCheckBox( getUpdatingAction() ); + jcb.setSelected(updating); + + startUpdateThread(); + + controlPanel.add( jcb ); + + JButton x= new JButton( getUpdateAction() ); + controlPanel.add( x ); + + appPanel.add( controlPanel, BorderLayout.SOUTH ); + + + + frame.getContentPane().add( appPanel ); + frame.setVisible( true ); + frame.pack(); + frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); + } + + private Action getUpdatingAction() { + return new AbstractAction( "Updating" ) { + public void actionPerformed( ActionEvent e ) { + JCheckBox source= (JCheckBox)e.getSource(); + updating= source.isSelected(); + if ( updating ) startUpdateThread(); + } + }; + } + + private Action getUpdateAction() { + return new AbstractAction( "Update" ) { + public void actionPerformed( ActionEvent e ) { + update(); + } + }; + } + + private void update() { + long endMillis= System.currentTimeMillis() - time0 + 2000; + if ( endMillis < 10000 ) endMillis= 10000; + Datum end= Units.seconds.createDatum( endMillis/1000. ); + DatumRange range= new DatumRange( end.subtract( xaxis.getDatumRange().width() ), end ); + xaxis.setDatumRange( range ); + } + + private void startUpdateThread() { + if ( updateThread==null ) { + updateThread= new Thread( new Runnable() { + public void run() { + while ( true ) { + try { Thread.sleep(500); } catch ( InterruptedException e ) { } + if ( updating ) update(); + } + } + }, "graphicalHandlerUpdateThread" ); + updateThread.start(); + } + } + + private boolean checkMyMessages( StackTraceElement[] st ) { + String myName= this.getClass().getName(); + boolean result= false; + for ( int i=1; i-1 ) result=true; + } + return result; + } + + public void publish( LogRecord rec ) { + StackTraceElement[] st= new Throwable().getStackTrace(); + + if ( checkMyMessages(st) ) return; + if ( Thread.currentThread().getName().equals( "graphicalHandlerUpdateThread" ) ) return; + + if ( renderer==null && + ( System.currentTimeMillis() - this.time0 ) > sleepInitiallyTime ) getRenderer(); + + String yAxisName; + if ( yaxisDimension==YAXIS_THREAD ) { + yAxisName= Thread.currentThread().getName() ; + } else if ( yaxisDimension==YAXIS_CLASS ) { + yAxisName= rec.getSourceClassName(); + } + + Integer yValue= (Integer)yaxisMap.get( yAxisName ); + if ( yValue==null ) { + yValue= new Integer( yaxisMap.size() ); + yaxisMap.put( yAxisName, yValue ); + } + synchronized (this) { + Long time= new Long( rec.getMillis() - time0 ) ; + int index= Collections.binarySearch(times, time ); + if ( index<0 ) { + index= -1-index; + } else { + int fudge=0; + while ( index>=0 ) { + fudge++; + time= new Long( rec.getMillis() - time0 + fudge ) ; + index= Collections.binarySearch(times, time ); + } + index= -1-index; + } + records.add( index, rec ); + yAxisValues.add( index, yValue ); + times.add( index, time ); + + } + + // consider how to not record it's own messages + } + + public void flush() { + if ( renderer==null ) getRenderer(); + renderer.update(); + } + + public void close() { + } + + ObjectLocator objectLocator; + public class LogRenderer extends Renderer { + String searchRegex=""; + + public String getSearchRegex() { + return searchRegex; + } + public void setSearchRegex( String regex ) { + this.searchRegex= regex; + update(); + } + + public synchronized void render(Graphics g1, DasAxis xAxis, DasAxis yAxis, ProgressMonitor mon) { + + Graphics2D g= (Graphics2D)g1; + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + + int ix0= (int) xAxis.transform( xAxis.getDataMinimum() ); + g.setColor( Color.lightGray ); + for ( Iterator iterator= yaxisMap.keySet().iterator(); iterator.hasNext(); ) { + Object name= iterator.next(); + Integer ithread= (Integer)yaxisMap.get(name); + int iy= (int)yAxis.transform( Units.dimensionless.createDatum(ithread.intValue()) ); + g.drawString( ""+name, ix0+2, iy ); + } + + synchronized(GraphicalLogHandler.this) { + + objectLocator= new ObjectLocator(); + + long minMilli= (long)xAxis.getDataMinimum().doubleValue( Units.milliseconds ); + long maxMilli= (long)xAxis.getDataMaximum().doubleValue( Units.milliseconds ); + + int firstIndex= Collections.binarySearch( times, new Long( minMilli ) ); + if ( firstIndex<0 ) firstIndex= -1 - firstIndex; + int lastIndex= Collections.binarySearch( times, new Long( maxMilli ) ); + if ( lastIndex<0 ) { + lastIndex= -1 - lastIndex; + } else { + lastIndex++; + } + + int lastX=-999; + int lastY=-999; + int collisionCount=0; + + if ( !searchRegex.equals("") ) { + for ( int i=firstIndex; i 0 ) { + return new Rectangle[] { dirtyBounds[0], myDirtyBounds[0], myDirtyBounds[1] } ; + } else { + return new Rectangle[] { myDirtyBounds[0], myDirtyBounds[1] } ; + } + } + + } + + + public MouseModule getMouseModule( ) { + DasPlot parent= renderer.getParent(); + LabelDragRenderer dr= new LookupDragRenderer( parent ); + MouseModule mouseModule= new MouseModule( parent, dr, "DataSetMonitor" ); + return mouseModule; + } + + public MouseModule getShowLogMouseModule( DasPlot plot2 ) { + BoxSelectorMouseModule result= new BoxSelectorMouseModule( plot2, plot2.getXAxis(), plot2.getYAxis(), + plot2.getRenderer(0), new BoxRenderer( plot2 ), "View Messages" ); + result.setDragEvents( false ); + result.setReleaseEvents( true ); + result.addBoxSelectionListener( new BoxSelectionListener() { + BoxSelectionListener l; + public void BoxSelected( BoxSelectionEvent e ) { + StringBuffer buf= new StringBuffer(1000); + + //Handler h= new ConsoleHandler(); + //Formatter f= h.getFormatter(); + Formatter f= new DenseConsoleFormatter(); + + ArrayList rec= new ArrayList(); + DatumRange threadsRange= e.getYRange(); + DatumRange timeRange= e.getXRange(); + + int messageCount=0; + for ( int i=0; i + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.util.StringTokenizer; +import java.util.Vector; +/** + * + * @author jbf + */ +public class IDLParser { + + public static int EXPR=1; + public static int EXPR_LIST=2; + public static int FACTOR=3; + public static int TERM=4; + public static int NUMBER=5; + public static int NOPARSER=6; + + /** Creates a new instance of idlParser */ + public IDLParser() { + } + + public String[] IDLTokenizer(String s) { + String delimiters=" \t[,]()^*/+-"; + StringTokenizer st = new StringTokenizer(s, delimiters, true); + int countTokens=st.countTokens(); + String[] tokens=new String[countTokens]; + for (int i=0; i 0) + { + String[] temp = new String[tokens.length-nullcount]; + int tIndex = 0; + for (int i = 0; i < tokens.length; i++) + { + if (tokens[i] == null) continue; + temp[tIndex] = tokens[i]; + tIndex++; + } + tokens = temp; + } + return tokens; + } + + public double parseIDLScalar(String s) { + String[] tokens= IDLTokenizer(s); + IDLValue expr= parseIDLArrayTokens(tokens,EXPR); + if (expr == null) return Double.NaN; + else return expr.toScalar(); + } + public double[] parseIDLArray(String s) { + String[] tokens= IDLTokenizer(s); + IDLValue expr= parseIDLArrayTokens(tokens,EXPR); + if (expr == null) return null; + else return expr.toArray(); + } + + private IDLValue parseIDLExprList(String[] tokens) { + int ileft= 0; + int nleft= 0; + int iright=0; + int itok; + + Vector exprs= new Vector(); + + ileft=1; + for (itok=1; itok + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import org.das2.util.DasMath; + +public class IDLValue { + + static int SCALAR = 1; + static int ARRAY = 2; + + protected double[] aValue; + + protected double sValue; + + protected int type; + + IDLValue() { + } + + public IDLValue(double a) { + sValue= a; + type= SCALAR; + } + + public IDLValue(double[] a) { + aValue= a; + type= ARRAY; + } + + public IDLValue IDLmultiply(IDLValue b) { + IDLValue result= new IDLValue(); + if ((type==SCALAR) && (b.type==SCALAR)) { + result.type=SCALAR; + result.sValue=sValue*b.sValue; + } else if ((type==ARRAY) && (b.type==SCALAR)) { + result.type=ARRAY; + result.aValue= new double[aValue.length]; + for (int i=0; i + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * + * @author eew + */ +public class InflaterChannel implements ReadableByteChannel { + + private boolean closed = false; + private boolean eof = false; + private ReadableByteChannel in; + private Inflater inflater; + private ByteBuffer buf; + private byte[] outBuf; + + /** Creates a new instance of InflaterChannel */ + public InflaterChannel(ReadableByteChannel in) { + this.in = in; + byte[] array = new byte[4096]; + buf = ByteBuffer.wrap(array); + inflater = new Inflater(); + } + + public synchronized void close() throws IOException { + if (!closed) { + closed = true; + in.close(); + in = null; + inflater.end(); + inflater = null; + buf = null; + } + } + + public boolean isOpen() { + return !closed; + } + + public synchronized int read(ByteBuffer dst) throws IOException { + int offset, length, totalOut, bytesRead; + byte[] outBuf; + try { + if (closed) { throw new ClosedChannelException(); } + if (eof && inflater.finished()) { return -1; } + if (dst.hasArray()) { + outBuf = dst.array(); + offset = dst.arrayOffset() + dst.position(); + length = dst.remaining(); + } + else { + outBuf = this.outBuf == null + ? (this.outBuf = new byte[4096]) : this.outBuf; + offset = 0; + length = Math.min(outBuf.length, dst.remaining()); + } + totalOut = 0; + while (totalOut < length) { + if (inflater.needsInput()) { + buf.clear(); + if (in.read(buf) == -1) { + eof = true; + } + buf.flip(); + inflater.setInput + (buf.array(), buf.arrayOffset(), buf.remaining()); + } + bytesRead = inflater.inflate + (outBuf, offset + totalOut, length - totalOut); + totalOut += bytesRead; + if (inflater.finished()) { break; } + } + if (dst.hasArray()) { dst.position(dst.position() + totalOut); } + else { dst.put(outBuf, 0, totalOut); } + return totalOut; + } + catch (DataFormatException dfe) { + eof = true; + close(); + IOException ioe = new IOException(dfe.getMessage()); + ioe.initCause(dfe); + throw ioe; + } + } + +} diff --git a/dasCore/src/main/java/org/das2/util/JCrypt.java b/dasCore/src/main/java/org/das2/util/JCrypt.java new file mode 100644 index 000000000..48b7bc922 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/JCrypt.java @@ -0,0 +1,631 @@ +/**************************************************************************** + * JCrypt.java + * + * Java-based implementation of the unix crypt command + * + * Based upon C source code written by Eric Young, eay@psych.uq.oz.au + * + ****************************************************************************/ + +package org.das2.util; + +public class JCrypt +{ + private JCrypt() {} + + private static final int ITERATIONS = 16; + + private static final int con_salt[] = + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, + 0x23, 0x24, 0x25, 0x20, 0x21, 0x22, 0x23, 0x24, + 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, + 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, + 0x3D, 0x3E, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static final boolean shifts2[] = + { + false, false, true, true, true, true, true, true, + false, true, true, true, true, true, true, false + }; + + private static final int skb[][] = + { + { + 0x00000000, 0x00000010, 0x20000000, 0x20000010, + 0x00010000, 0x00010010, 0x20010000, 0x20010010, + 0x00000800, 0x00000810, 0x20000800, 0x20000810, + 0x00010800, 0x00010810, 0x20010800, 0x20010810, + 0x00000020, 0x00000030, 0x20000020, 0x20000030, + 0x00010020, 0x00010030, 0x20010020, 0x20010030, + 0x00000820, 0x00000830, 0x20000820, 0x20000830, + 0x00010820, 0x00010830, 0x20010820, 0x20010830, + 0x00080000, 0x00080010, 0x20080000, 0x20080010, + 0x00090000, 0x00090010, 0x20090000, 0x20090010, + 0x00080800, 0x00080810, 0x20080800, 0x20080810, + 0x00090800, 0x00090810, 0x20090800, 0x20090810, + 0x00080020, 0x00080030, 0x20080020, 0x20080030, + 0x00090020, 0x00090030, 0x20090020, 0x20090030, + 0x00080820, 0x00080830, 0x20080820, 0x20080830, + 0x00090820, 0x00090830, 0x20090820, 0x20090830, + }, + { + /* for C bits (numbered as per FIPS 46) 7 8 10 11 12 13 */ + 0x00000000, 0x02000000, 0x00002000, 0x02002000, + 0x00200000, 0x02200000, 0x00202000, 0x02202000, + 0x00000004, 0x02000004, 0x00002004, 0x02002004, + 0x00200004, 0x02200004, 0x00202004, 0x02202004, + 0x00000400, 0x02000400, 0x00002400, 0x02002400, + 0x00200400, 0x02200400, 0x00202400, 0x02202400, + 0x00000404, 0x02000404, 0x00002404, 0x02002404, + 0x00200404, 0x02200404, 0x00202404, 0x02202404, + 0x10000000, 0x12000000, 0x10002000, 0x12002000, + 0x10200000, 0x12200000, 0x10202000, 0x12202000, + 0x10000004, 0x12000004, 0x10002004, 0x12002004, + 0x10200004, 0x12200004, 0x10202004, 0x12202004, + 0x10000400, 0x12000400, 0x10002400, 0x12002400, + 0x10200400, 0x12200400, 0x10202400, 0x12202400, + 0x10000404, 0x12000404, 0x10002404, 0x12002404, + 0x10200404, 0x12200404, 0x10202404, 0x12202404, + }, + { + /* for C bits (numbered as per FIPS 46) 14 15 16 17 19 20 */ + 0x00000000, 0x00000001, 0x00040000, 0x00040001, + 0x01000000, 0x01000001, 0x01040000, 0x01040001, + 0x00000002, 0x00000003, 0x00040002, 0x00040003, + 0x01000002, 0x01000003, 0x01040002, 0x01040003, + 0x00000200, 0x00000201, 0x00040200, 0x00040201, + 0x01000200, 0x01000201, 0x01040200, 0x01040201, + 0x00000202, 0x00000203, 0x00040202, 0x00040203, + 0x01000202, 0x01000203, 0x01040202, 0x01040203, + 0x08000000, 0x08000001, 0x08040000, 0x08040001, + 0x09000000, 0x09000001, 0x09040000, 0x09040001, + 0x08000002, 0x08000003, 0x08040002, 0x08040003, + 0x09000002, 0x09000003, 0x09040002, 0x09040003, + 0x08000200, 0x08000201, 0x08040200, 0x08040201, + 0x09000200, 0x09000201, 0x09040200, 0x09040201, + 0x08000202, 0x08000203, 0x08040202, 0x08040203, + 0x09000202, 0x09000203, 0x09040202, 0x09040203, + }, + { + /* for C bits (numbered as per FIPS 46) 21 23 24 26 27 28 */ + 0x00000000, 0x00100000, 0x00000100, 0x00100100, + 0x00000008, 0x00100008, 0x00000108, 0x00100108, + 0x00001000, 0x00101000, 0x00001100, 0x00101100, + 0x00001008, 0x00101008, 0x00001108, 0x00101108, + 0x04000000, 0x04100000, 0x04000100, 0x04100100, + 0x04000008, 0x04100008, 0x04000108, 0x04100108, + 0x04001000, 0x04101000, 0x04001100, 0x04101100, + 0x04001008, 0x04101008, 0x04001108, 0x04101108, + 0x00020000, 0x00120000, 0x00020100, 0x00120100, + 0x00020008, 0x00120008, 0x00020108, 0x00120108, + 0x00021000, 0x00121000, 0x00021100, 0x00121100, + 0x00021008, 0x00121008, 0x00021108, 0x00121108, + 0x04020000, 0x04120000, 0x04020100, 0x04120100, + 0x04020008, 0x04120008, 0x04020108, 0x04120108, + 0x04021000, 0x04121000, 0x04021100, 0x04121100, + 0x04021008, 0x04121008, 0x04021108, 0x04121108, + }, + { + /* for D bits (numbered as per FIPS 46) 1 2 3 4 5 6 */ + 0x00000000, 0x10000000, 0x00010000, 0x10010000, + 0x00000004, 0x10000004, 0x00010004, 0x10010004, + 0x20000000, 0x30000000, 0x20010000, 0x30010000, + 0x20000004, 0x30000004, 0x20010004, 0x30010004, + 0x00100000, 0x10100000, 0x00110000, 0x10110000, + 0x00100004, 0x10100004, 0x00110004, 0x10110004, + 0x20100000, 0x30100000, 0x20110000, 0x30110000, + 0x20100004, 0x30100004, 0x20110004, 0x30110004, + 0x00001000, 0x10001000, 0x00011000, 0x10011000, + 0x00001004, 0x10001004, 0x00011004, 0x10011004, + 0x20001000, 0x30001000, 0x20011000, 0x30011000, + 0x20001004, 0x30001004, 0x20011004, 0x30011004, + 0x00101000, 0x10101000, 0x00111000, 0x10111000, + 0x00101004, 0x10101004, 0x00111004, 0x10111004, + 0x20101000, 0x30101000, 0x20111000, 0x30111000, + 0x20101004, 0x30101004, 0x20111004, 0x30111004, + }, + { + /* for D bits (numbered as per FIPS 46) 8 9 11 12 13 14 */ + 0x00000000, 0x08000000, 0x00000008, 0x08000008, + 0x00000400, 0x08000400, 0x00000408, 0x08000408, + 0x00020000, 0x08020000, 0x00020008, 0x08020008, + 0x00020400, 0x08020400, 0x00020408, 0x08020408, + 0x00000001, 0x08000001, 0x00000009, 0x08000009, + 0x00000401, 0x08000401, 0x00000409, 0x08000409, + 0x00020001, 0x08020001, 0x00020009, 0x08020009, + 0x00020401, 0x08020401, 0x00020409, 0x08020409, + 0x02000000, 0x0A000000, 0x02000008, 0x0A000008, + 0x02000400, 0x0A000400, 0x02000408, 0x0A000408, + 0x02020000, 0x0A020000, 0x02020008, 0x0A020008, + 0x02020400, 0x0A020400, 0x02020408, 0x0A020408, + 0x02000001, 0x0A000001, 0x02000009, 0x0A000009, + 0x02000401, 0x0A000401, 0x02000409, 0x0A000409, + 0x02020001, 0x0A020001, 0x02020009, 0x0A020009, + 0x02020401, 0x0A020401, 0x02020409, 0x0A020409, + }, + { + /* for D bits (numbered as per FIPS 46) 16 17 18 19 20 21 */ + 0x00000000, 0x00000100, 0x00080000, 0x00080100, + 0x01000000, 0x01000100, 0x01080000, 0x01080100, + 0x00000010, 0x00000110, 0x00080010, 0x00080110, + 0x01000010, 0x01000110, 0x01080010, 0x01080110, + 0x00200000, 0x00200100, 0x00280000, 0x00280100, + 0x01200000, 0x01200100, 0x01280000, 0x01280100, + 0x00200010, 0x00200110, 0x00280010, 0x00280110, + 0x01200010, 0x01200110, 0x01280010, 0x01280110, + 0x00000200, 0x00000300, 0x00080200, 0x00080300, + 0x01000200, 0x01000300, 0x01080200, 0x01080300, + 0x00000210, 0x00000310, 0x00080210, 0x00080310, + 0x01000210, 0x01000310, 0x01080210, 0x01080310, + 0x00200200, 0x00200300, 0x00280200, 0x00280300, + 0x01200200, 0x01200300, 0x01280200, 0x01280300, + 0x00200210, 0x00200310, 0x00280210, 0x00280310, + 0x01200210, 0x01200310, 0x01280210, 0x01280310, + }, + { + /* for D bits (numbered as per FIPS 46) 22 23 24 25 27 28 */ + 0x00000000, 0x04000000, 0x00040000, 0x04040000, + 0x00000002, 0x04000002, 0x00040002, 0x04040002, + 0x00002000, 0x04002000, 0x00042000, 0x04042000, + 0x00002002, 0x04002002, 0x00042002, 0x04042002, + 0x00000020, 0x04000020, 0x00040020, 0x04040020, + 0x00000022, 0x04000022, 0x00040022, 0x04040022, + 0x00002020, 0x04002020, 0x00042020, 0x04042020, + 0x00002022, 0x04002022, 0x00042022, 0x04042022, + 0x00000800, 0x04000800, 0x00040800, 0x04040800, + 0x00000802, 0x04000802, 0x00040802, 0x04040802, + 0x00002800, 0x04002800, 0x00042800, 0x04042800, + 0x00002802, 0x04002802, 0x00042802, 0x04042802, + 0x00000820, 0x04000820, 0x00040820, 0x04040820, + 0x00000822, 0x04000822, 0x00040822, 0x04040822, + 0x00002820, 0x04002820, 0x00042820, 0x04042820, + 0x00002822, 0x04002822, 0x00042822, 0x04042822, + }, + }; + + private static final int SPtrans[][] = + { + { + /* nibble 0 */ + 0x00820200, 0x00020000, 0x80800000, 0x80820200, + 0x00800000, 0x80020200, 0x80020000, 0x80800000, + 0x80020200, 0x00820200, 0x00820000, 0x80000200, + 0x80800200, 0x00800000, 0x00000000, 0x80020000, + 0x00020000, 0x80000000, 0x00800200, 0x00020200, + 0x80820200, 0x00820000, 0x80000200, 0x00800200, + 0x80000000, 0x00000200, 0x00020200, 0x80820000, + 0x00000200, 0x80800200, 0x80820000, 0x00000000, + 0x00000000, 0x80820200, 0x00800200, 0x80020000, + 0x00820200, 0x00020000, 0x80000200, 0x00800200, + 0x80820000, 0x00000200, 0x00020200, 0x80800000, + 0x80020200, 0x80000000, 0x80800000, 0x00820000, + 0x80820200, 0x00020200, 0x00820000, 0x80800200, + 0x00800000, 0x80000200, 0x80020000, 0x00000000, + 0x00020000, 0x00800000, 0x80800200, 0x00820200, + 0x80000000, 0x80820000, 0x00000200, 0x80020200, + }, + { + /* nibble 1 */ + 0x10042004, 0x00000000, 0x00042000, 0x10040000, + 0x10000004, 0x00002004, 0x10002000, 0x00042000, + 0x00002000, 0x10040004, 0x00000004, 0x10002000, + 0x00040004, 0x10042000, 0x10040000, 0x00000004, + 0x00040000, 0x10002004, 0x10040004, 0x00002000, + 0x00042004, 0x10000000, 0x00000000, 0x00040004, + 0x10002004, 0x00042004, 0x10042000, 0x10000004, + 0x10000000, 0x00040000, 0x00002004, 0x10042004, + 0x00040004, 0x10042000, 0x10002000, 0x00042004, + 0x10042004, 0x00040004, 0x10000004, 0x00000000, + 0x10000000, 0x00002004, 0x00040000, 0x10040004, + 0x00002000, 0x10000000, 0x00042004, 0x10002004, + 0x10042000, 0x00002000, 0x00000000, 0x10000004, + 0x00000004, 0x10042004, 0x00042000, 0x10040000, + 0x10040004, 0x00040000, 0x00002004, 0x10002000, + 0x10002004, 0x00000004, 0x10040000, 0x00042000, + }, + { + /* nibble 2 */ + 0x41000000, 0x01010040, 0x00000040, 0x41000040, + 0x40010000, 0x01000000, 0x41000040, 0x00010040, + 0x01000040, 0x00010000, 0x01010000, 0x40000000, + 0x41010040, 0x40000040, 0x40000000, 0x41010000, + 0x00000000, 0x40010000, 0x01010040, 0x00000040, + 0x40000040, 0x41010040, 0x00010000, 0x41000000, + 0x41010000, 0x01000040, 0x40010040, 0x01010000, + 0x00010040, 0x00000000, 0x01000000, 0x40010040, + 0x01010040, 0x00000040, 0x40000000, 0x00010000, + 0x40000040, 0x40010000, 0x01010000, 0x41000040, + 0x00000000, 0x01010040, 0x00010040, 0x41010000, + 0x40010000, 0x01000000, 0x41010040, 0x40000000, + 0x40010040, 0x41000000, 0x01000000, 0x41010040, + 0x00010000, 0x01000040, 0x41000040, 0x00010040, + 0x01000040, 0x00000000, 0x41010000, 0x40000040, + 0x41000000, 0x40010040, 0x00000040, 0x01010000, + }, + { + /* nibble 3 */ + 0x00100402, 0x04000400, 0x00000002, 0x04100402, + 0x00000000, 0x04100000, 0x04000402, 0x00100002, + 0x04100400, 0x04000002, 0x04000000, 0x00000402, + 0x04000002, 0x00100402, 0x00100000, 0x04000000, + 0x04100002, 0x00100400, 0x00000400, 0x00000002, + 0x00100400, 0x04000402, 0x04100000, 0x00000400, + 0x00000402, 0x00000000, 0x00100002, 0x04100400, + 0x04000400, 0x04100002, 0x04100402, 0x00100000, + 0x04100002, 0x00000402, 0x00100000, 0x04000002, + 0x00100400, 0x04000400, 0x00000002, 0x04100000, + 0x04000402, 0x00000000, 0x00000400, 0x00100002, + 0x00000000, 0x04100002, 0x04100400, 0x00000400, + 0x04000000, 0x04100402, 0x00100402, 0x00100000, + 0x04100402, 0x00000002, 0x04000400, 0x00100402, + 0x00100002, 0x00100400, 0x04100000, 0x04000402, + 0x00000402, 0x04000000, 0x04000002, 0x04100400, + }, + { + /* nibble 4 */ + 0x02000000, 0x00004000, 0x00000100, 0x02004108, + 0x02004008, 0x02000100, 0x00004108, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x00004100, + 0x02000108, 0x02004008, 0x02004100, 0x00000000, + 0x00004100, 0x02000000, 0x00004008, 0x00000108, + 0x02000100, 0x00004108, 0x00000000, 0x02000008, + 0x00000008, 0x02000108, 0x02004108, 0x00004008, + 0x02004000, 0x00000100, 0x00000108, 0x02004100, + 0x02004100, 0x02000108, 0x00004008, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x02000100, + 0x02000000, 0x00004100, 0x02004108, 0x00000000, + 0x00004108, 0x02000000, 0x00000100, 0x00004008, + 0x02000108, 0x00000100, 0x00000000, 0x02004108, + 0x02004008, 0x02004100, 0x00000108, 0x00004000, + 0x00004100, 0x02004008, 0x02000100, 0x00000108, + 0x00000008, 0x00004108, 0x02004000, 0x02000008, + }, + { + /* nibble 5 */ + 0x20000010, 0x00080010, 0x00000000, 0x20080800, + 0x00080010, 0x00000800, 0x20000810, 0x00080000, + 0x00000810, 0x20080810, 0x00080800, 0x20000000, + 0x20000800, 0x20000010, 0x20080000, 0x00080810, + 0x00080000, 0x20000810, 0x20080010, 0x00000000, + 0x00000800, 0x00000010, 0x20080800, 0x20080010, + 0x20080810, 0x20080000, 0x20000000, 0x00000810, + 0x00000010, 0x00080800, 0x00080810, 0x20000800, + 0x00000810, 0x20000000, 0x20000800, 0x00080810, + 0x20080800, 0x00080010, 0x00000000, 0x20000800, + 0x20000000, 0x00000800, 0x20080010, 0x00080000, + 0x00080010, 0x20080810, 0x00080800, 0x00000010, + 0x20080810, 0x00080800, 0x00080000, 0x20000810, + 0x20000010, 0x20080000, 0x00080810, 0x00000000, + 0x00000800, 0x20000010, 0x20000810, 0x20080800, + 0x20080000, 0x00000810, 0x00000010, 0x20080010, + }, + { + /* nibble 6 */ + 0x00001000, 0x00000080, 0x00400080, 0x00400001, + 0x00401081, 0x00001001, 0x00001080, 0x00000000, + 0x00400000, 0x00400081, 0x00000081, 0x00401000, + 0x00000001, 0x00401080, 0x00401000, 0x00000081, + 0x00400081, 0x00001000, 0x00001001, 0x00401081, + 0x00000000, 0x00400080, 0x00400001, 0x00001080, + 0x00401001, 0x00001081, 0x00401080, 0x00000001, + 0x00001081, 0x00401001, 0x00000080, 0x00400000, + 0x00001081, 0x00401000, 0x00401001, 0x00000081, + 0x00001000, 0x00000080, 0x00400000, 0x00401001, + 0x00400081, 0x00001081, 0x00001080, 0x00000000, + 0x00000080, 0x00400001, 0x00000001, 0x00400080, + 0x00000000, 0x00400081, 0x00400080, 0x00001080, + 0x00000081, 0x00001000, 0x00401081, 0x00400000, + 0x00401080, 0x00000001, 0x00001001, 0x00401081, + 0x00400001, 0x00401080, 0x00401000, 0x00001001, + }, + { + /* nibble 7 */ + 0x08200020, 0x08208000, 0x00008020, 0x00000000, + 0x08008000, 0x00200020, 0x08200000, 0x08208020, + 0x00000020, 0x08000000, 0x00208000, 0x00008020, + 0x00208020, 0x08008020, 0x08000020, 0x08200000, + 0x00008000, 0x00208020, 0x00200020, 0x08008000, + 0x08208020, 0x08000020, 0x00000000, 0x00208000, + 0x08000000, 0x00200000, 0x08008020, 0x08200020, + 0x00200000, 0x00008000, 0x08208000, 0x00000020, + 0x00200000, 0x00008000, 0x08000020, 0x08208020, + 0x00008020, 0x08000000, 0x00000000, 0x00208000, + 0x08200020, 0x08008020, 0x08008000, 0x00200020, + 0x08208000, 0x00000020, 0x00200020, 0x08008000, + 0x08208020, 0x00200000, 0x08200000, 0x08000020, + 0x00208000, 0x00008020, 0x08008020, 0x08200000, + 0x00000020, 0x08208000, 0x00208020, 0x00000000, + 0x08000000, 0x08200020, 0x00008000, 0x00208020 + } + }; + + private static final int cov_2char[] = + { + 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, + 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, + 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A + }; + + private static final int byteToUnsigned(byte b) + { + int value = (int)b; + + return(value >= 0 ? value : value + 256); + } + + private static int fourBytesToInt(byte b[], int offset) + { + int value; + + value = byteToUnsigned(b[offset++]); + value |= (byteToUnsigned(b[offset++]) << 8); + value |= (byteToUnsigned(b[offset++]) << 16); + value |= (byteToUnsigned(b[offset++]) << 24); + + return(value); + } + + private static final void intToFourBytes(int iValue, byte b[], int offset) + { + b[offset++] = (byte)((iValue) & 0xff); + b[offset++] = (byte)((iValue >>> 8 ) & 0xff); + b[offset++] = (byte)((iValue >>> 16) & 0xff); + b[offset++] = (byte)((iValue >>> 24) & 0xff); + } + + private static final void PERM_OP(int a, int b, int n, int m, int results[]) + { + int t; + + t = ((a >>> n) ^ b) & m; + a ^= t << n; + b ^= t; + + results[0] = a; + results[1] = b; + } + + private static final int HPERM_OP(int a, int n, int m) + { + int t; + + t = ((a << (16 - n)) ^ a) & m; + a = a ^ t ^ (t >>> (16 - n)); + + return(a); + } + + private static int [] des_set_key(byte key[]) + { + int schedule[] = new int[ITERATIONS * 2]; + + int c = fourBytesToInt(key, 0); + int d = fourBytesToInt(key, 4); + + int results[] = new int[2]; + + PERM_OP(d, c, 4, 0x0f0f0f0f, results); + d = results[0]; c = results[1]; + + c = HPERM_OP(c, -2, 0xcccc0000); + d = HPERM_OP(d, -2, 0xcccc0000); + + PERM_OP(d, c, 1, 0x55555555, results); + d = results[0]; c = results[1]; + + PERM_OP(c, d, 8, 0x00ff00ff, results); + c = results[0]; d = results[1]; + + PERM_OP(d, c, 1, 0x55555555, results); + d = results[0]; c = results[1]; + + d = (((d & 0x000000ff) << 16) | (d & 0x0000ff00) | + ((d & 0x00ff0000) >>> 16) | ((c & 0xf0000000) >>> 4)); + c &= 0x0fffffff; + + int s, t; + int j = 0; + + for(int i = 0; i < ITERATIONS; i ++) + { + if(shifts2[i]) + { + c = (c >>> 2) | (c << 26); + d = (d >>> 2) | (d << 26); + } + else + { + c = (c >>> 1) | (c << 27); + d = (d >>> 1) | (d << 27); + } + + c &= 0x0fffffff; + d &= 0x0fffffff; + + s = skb[0][ (c ) & 0x3f ]| + skb[1][((c >>> 6) & 0x03) | ((c >>> 7) & 0x3c)]| + skb[2][((c >>> 13) & 0x0f) | ((c >>> 14) & 0x30)]| + skb[3][((c >>> 20) & 0x01) | ((c >>> 21) & 0x06) | + ((c >>> 22) & 0x38)]; + + t = skb[4][ (d ) & 0x3f ]| + skb[5][((d >>> 7) & 0x03) | ((d >>> 8) & 0x3c)]| + skb[6][ (d >>>15) & 0x3f ]| + skb[7][((d >>>21) & 0x0f) | ((d >>> 22) & 0x30)]; + + schedule[j++] = ((t << 16) | (s & 0x0000ffff)) & 0xffffffff; + s = ((s >>> 16) | (t & 0xffff0000)); + + s = (s << 4) | (s >>> 28); + schedule[j++] = s & 0xffffffff; + } + return(schedule); + } + + private static final int D_ENCRYPT + ( + int L, int R, int S, int E0, int E1, int s[] + ) + { + int t, u, v; + + v = R ^ (R >>> 16); + u = v & E0; + v = v & E1; + u = (u ^ (u << 16)) ^ R ^ s[S]; + t = (v ^ (v << 16)) ^ R ^ s[S + 1]; + t = (t >>> 4) | (t << 28); + + L ^= SPtrans[1][(t ) & 0x3f] | + SPtrans[3][(t >>> 8) & 0x3f] | + SPtrans[5][(t >>> 16) & 0x3f] | + SPtrans[7][(t >>> 24) & 0x3f] | + SPtrans[0][(u ) & 0x3f] | + SPtrans[2][(u >>> 8) & 0x3f] | + SPtrans[4][(u >>> 16) & 0x3f] | + SPtrans[6][(u >>> 24) & 0x3f]; + + return(L); + } + + private static final int [] body(int schedule[], int Eswap0, int Eswap1) + { + int left = 0; + int right = 0; + int t = 0; + + for(int j = 0; j < 25; j ++) + { + for(int i = 0; i < ITERATIONS * 2; i += 4) + { + left = D_ENCRYPT(left, right, i, Eswap0, Eswap1, schedule); + right = D_ENCRYPT(right, left, i + 2, Eswap0, Eswap1, schedule); + } + t = left; + left = right; + right = t; + } + + t = right; + + right = (left >>> 1) | (left << 31); + left = (t >>> 1) | (t << 31); + + left &= 0xffffffff; + right &= 0xffffffff; + + int results[] = new int[2]; + + PERM_OP(right, left, 1, 0x55555555, results); + right = results[0]; left = results[1]; + + PERM_OP(left, right, 8, 0x00ff00ff, results); + left = results[0]; right = results[1]; + + PERM_OP(right, left, 2, 0x33333333, results); + right = results[0]; left = results[1]; + + PERM_OP(left, right, 16, 0x0000ffff, results); + left = results[0]; right = results[1]; + + PERM_OP(right, left, 4, 0x0f0f0f0f, results); + right = results[0]; left = results[1]; + + int out[] = new int[2]; + + out[0] = left; out[1] = right; + + return(out); + } + + public static final String crypt(String salt, String original) + { + while(salt.length() < 2) + salt += "A"; + + StringBuffer buffer = new StringBuffer(" "); + + char charZero = salt.charAt(0); + char charOne = salt.charAt(1); + + buffer.setCharAt(0, charZero); + buffer.setCharAt(1, charOne); + + int Eswap0 = con_salt[(int)charZero]; + int Eswap1 = con_salt[(int)charOne] << 4; + + byte key[] = new byte[8]; + + for(int i = 0; i < key.length; i ++) + key[i] = (byte)0; + + for(int i = 0; i < key.length && i < original.length(); i ++) + { + int iChar = (int)original.charAt(i); + + key[i] = (byte)(iChar << 1); + } + + int schedule[] = des_set_key(key); + int out[] = body(schedule, Eswap0, Eswap1); + + byte b[] = new byte[9]; + + intToFourBytes(out[0], b, 0); + intToFourBytes(out[1], b, 4); + b[8] = 0; + + for(int i = 2, y = 0, u = 0x80; i < 13; i ++) + { + for(int j = 0, c = 0; j < 6; j ++) + { + c <<= 1; + + if(((int)b[y] & u) != 0) + c |= 1; + + u >>>= 1; + + if(u == 0) + { + y++; + u = 0x80; + } + buffer.setCharAt(i, (char)cov_2char[c]); + } + } + return(buffer.toString()); + } + + public static void main(String args[]) + { + if(args.length >= 2) + { + org.das2.util.DasDie.println + ( + "[" + args[0] + "] [" + args[1] + "] => [" + + JCrypt.crypt(args[0], args[1]) + "]" + ); + } else { + org.das2.util.DasDie.println("java crypt "); + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/MemoryPreferences.java b/dasCore/src/main/java/org/das2/util/MemoryPreferences.java new file mode 100644 index 000000000..aeb624205 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/MemoryPreferences.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2008, SQL Power Group Inc. + * + * This file is part of SQL Power Library. + * + * SQL Power Library 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. + * + * SQL Power Library 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 . + * + * Trivial Modifications at the University of Iowa. 2012. + */ +package org.das2.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** + * A java.util.prefs.Preferences that does NOT persist anything, so it has no + * effect on (nor is affected by!) any use of the "regular" Preferences. + *

    + * To use, run with Java command-line option + * -Djava.util.prefs.PreferencesFactory=org.das2.util.PreferencesFactory + */ +public class MemoryPreferences extends AbstractPreferences{ + + private static final Logger logger = Logger.getLogger(MemoryPreferences.class.getName()); + /** + * The map of all data in this particular node. + */ + final Map values = new HashMap(); + /** The map of all Preferences nodes immediately below this node + */ + final Map children = new HashMap(); + public final static String SYSTEM_PROPS_ERROR_MESSAGE = + "Did you remember to run with -D" + + MemoryPreferencesFactory.PREFS_FACTORY_SYSTEM_PROPERTY + + "=" + MemoryPreferencesFactory.MY_CLASS_NAME + "?"; + + /** + * Constructor, non-public, only for use by my PrefencesFactory; should only be called from + * the PreferencesFactory and from node() below; node() takes care of finding the full path + * if the incoming path is relative. + * @param fullPath + */ + MemoryPreferences(AbstractPreferences parent, String name){ + super(parent, name); + + // note, logger should never be null because it's statically initialised. However, + // it comes out null every time we run the MatchMaker SwingSessionContextTest! Hmm... + if(logger != null && logger.isLoggable(Level.FINE)){ + logger.fine(String.format("MemoryPreferences.MemoryPreferences(%s, %s)", parent, name)); + } + } + + @Override + protected void putSpi(String key, String value){ + values.put(key, value); + } + + @Override + protected String getSpi(String key){ + String value = values.get(key); + logger.fine(String.format("get: %s=%s", key, value)); + return value; + } + + @Override + protected void removeSpi(String key){ + values.remove(key); + } + + @Override + protected void removeNodeSpi() throws BackingStoreException{ + // nothing to do here? + } + + @Override + protected String[] keysSpi() throws BackingStoreException{ + return values.keySet().toArray(new String[values.size()]); + } + + @Override + protected String[] childrenNamesSpi() throws BackingStoreException{ + return children.keySet().toArray(new String[children.size()]); + } + + @Override + protected AbstractPreferences childSpi(String name){ + logger.fine(String.format("MemoryPreferences.node(%s)", name)); + AbstractPreferences n = new MemoryPreferences(this, name); + children.put(name, n); + return n; + } + + @Override + protected void syncSpi() throws BackingStoreException{ + // nothing to do + } + + @Override + protected void flushSpi() throws BackingStoreException{ + // nothing to do + } +} diff --git a/dasCore/src/main/java/org/das2/util/MemoryPreferencesFactory.java b/dasCore/src/main/java/org/das2/util/MemoryPreferencesFactory.java new file mode 100644 index 000000000..5b8687f45 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/MemoryPreferencesFactory.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2008, SQL Power Group Inc. + * + * This file is part of SQL Power Library. + * + * SQL Power Library 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. + * + * SQL Power Library 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 . + */ +package org.das2.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import java.util.prefs.PreferencesFactory; + +/** + * A java.util.prefs.PreferencesFactory that lets us use the MemoryPreferences + * so that tests will not affect (nor be affected by) preferences previously created + * by the user running the tests. + */ +public class MemoryPreferencesFactory implements PreferencesFactory{ + + public static final String PREFS_FACTORY_SYSTEM_PROPERTY = "java.util.prefs.PreferencesFactory"; + public static final String MY_CLASS_NAME = "org.das2.util.PreferencesFactory"; + private static final Logger logger = Logger.getLogger(MemoryPreferencesFactory.class.getName()); + final static Map systemNodes = new HashMap(); + final Map userNodes = new HashMap(); + /** + * There is always only one System Root node + */ + final MemoryPreferences systemRoot = new MemoryPreferences(null, ""); + + public Preferences systemRoot(){ + logger.fine("PreferencesFactory.systemRoot()"); + return systemRoot; + } + /** + * In this implementation there is only one UserRoot, because this + * implementation is only used for in-memory testing. + */ + final MemoryPreferences userRoot = new MemoryPreferences(null, ""); + + public Preferences userRoot(){ + logger.fine("PreferencesFactory.userRoot()"); + return userRoot; + } +} diff --git a/dasCore/src/main/java/org/das2/util/MessageBox.java b/dasCore/src/main/java/org/das2/util/MessageBox.java new file mode 100644 index 000000000..ed6ab8de0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/MessageBox.java @@ -0,0 +1,172 @@ +/* File: MessageBox.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * + * @author eew + */ +public class MessageBox extends Dialog { + + public static final int OK = 1; + public static final int CANCEL = 2; + public static final int YES = 4; + public static final int NO = 8; + + public static final int YES_NO = 12; + public static final int YES_NO_CANCEL = 14; + public static final int OK_CANCEL = 3; + public static final int DEFAULT = 3; + + private int result; + private int type; + private Button yes; + private Button no; + private Button ok; + private Button cancel; + + /** Creates a new instance of MessageBox */ + private MessageBox(Frame owner) { + super(owner); + } + + private MessageBoxListener createListener() + { + return new MessageBoxListener(); + } + + public static int showModalMessage(Frame owner, int type, String title, String message) + { + return showModalMessage(owner, type, title, breakLines(message)); + } + + public static int showModalMessage(Frame owner, int type, String title, String[] message) + { + MessageBox mb = new MessageBox(owner); + MessageBoxListener mbl = mb.createListener(); + Panel messagePanel, buttonPanel; + + if (type == 0) type = OK_CANCEL; + mb.type = type; + + mb.setTitle(title); + mb.setModal(true); + mb.setLayout(new BorderLayout()); + mb.addWindowListener(mbl); + + messagePanel = new Panel(new GridLayout(0,1)); + for (int i = 0; i < message.length; i++) + { + messagePanel.add(new Label(message[i])); + } + + mb.add(messagePanel, "Center"); + + buttonPanel = new Panel(new FlowLayout(FlowLayout.RIGHT)); + if ((type & OK) == OK) + { + mb.ok = new Button("Ok"); + mb.ok.addActionListener(mbl); + buttonPanel.add(mb.ok); + } + if ((type & YES) == YES) + { + mb.yes = new Button("Yes"); + mb.yes.addActionListener(mbl); + buttonPanel.add(mb.yes); + } + if ((type & NO) == NO) + { + mb.no = new Button("No"); + mb.no.addActionListener(mbl); + buttonPanel.add(mb.no); + } + if ((type & CANCEL) == CANCEL) + { + mb.cancel = new Button("Cancel"); + mb.cancel.addActionListener(mbl); + buttonPanel.add(mb.cancel); + } + + mb.add(messagePanel, "Center"); + mb.add(buttonPanel, "South"); + + mb.pack(); + Dimension od = owner.getSize(); + Point op = owner.getLocation(); + Dimension md = mb.getSize(); + mb.setLocation(op.x + (od.width - md.width)/2, op.y + (od.height - md.height)/2); + + mb.setVisible(true); + + return mb.result; + + } + + private static String[] breakLines(String s) + { + java.util.StringTokenizer st = new java.util.StringTokenizer(s, "\n", false); + int lines = st.countTokens(); + String[] list = new String[lines]; + for (int i = 0; i < lines; i++) + list[i] = st.nextToken(); + return list; + } + + private class MessageBoxListener extends WindowAdapter implements ActionListener + { + public void windowClosing(WindowEvent e) + { + result = CANCEL; + setVisible(false); + } + + public void actionPerformed(ActionEvent e) + { + if (e.getSource() == ok) + { + result = OK; + } + else if (e.getSource() == cancel) + { + result = CANCEL; + } + else if (e.getSource() == yes) + { + result = YES; + } + else if (e.getSource() == no) + { + result = NO; + } + setVisible(true); + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/NBConsoleFormatter.java b/dasCore/src/main/java/org/das2/util/NBConsoleFormatter.java new file mode 100644 index 000000000..532a52e3f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/NBConsoleFormatter.java @@ -0,0 +1,42 @@ +/* + * NBConsoleFormatter.java + * + * Created on April 7, 2005, 12:13 PM + */ + +package org.das2.util; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * + * @author Jeremy + */ +public class NBConsoleFormatter extends Formatter { + boolean coalesce=true; + String lastMessage=null; + int coalesceHits=0; + + public String format( LogRecord rec ) { + if ( coalesce && lastMessage!=null && lastMessage.equals(rec.getMessage()) ) { + coalesceHits++; + return ""; + } else { + StackTraceElement[] st= new Throwable().getStackTrace(); + String result= rec.getLoggerName()+" ["+Thread.currentThread().getName()+"]\n"+rec.getLevel().getLocalizedName()+": "+rec.getMessage() + +"\n\tat "+st[7] + +( st.length>8 ? "\n\tat "+st[8]+"\n" : "\n" ); + if ( coalesceHits>0 ) { + result= "(Last message repeats "+(coalesceHits+1)+" times)\n"+result; + } + coalesceHits= 0; + lastMessage= rec.getMessage(); + return result; + } + } + + public NBConsoleFormatter() { + } + +} diff --git a/dasCore/src/main/java/org/das2/util/NumberFormatUtil.java b/dasCore/src/main/java/org/das2/util/NumberFormatUtil.java new file mode 100644 index 000000000..02a7d29d6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/NumberFormatUtil.java @@ -0,0 +1,44 @@ +/* + * NumberFormatUtil.java + * + * Created on 2. listopad 2007, 16:27 + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +/** + * + * @author jbf + */ +public class NumberFormatUtil { + + /** + * handles the localization problem (bug 0000294) by always returning a DecimalFormat + * for Locale.US. (Sorry, rest of world.) + * + * @throws ClassCastException if for some reason, NumberFormat.getInstance doesn't return DecimalFormat. + */ + public static DecimalFormat getDecimalFormat( ) { + // see doc for DecimalFormat, which recommends this practice. + DecimalFormat result= (DecimalFormat) NumberFormat.getInstance( Locale.US ); + return result; + } + + /** + * handles the localization problem (bug 0000294) by always returning a DecimalFormat + * for Locale.US. (Sorry, rest of world.) + * @throws ClassCastException if for some reason, NumberFormat.getInstance doesn't return DecimalFormat. + */ + public static DecimalFormat getDecimalFormat( String spec ) { + DecimalFormat result= (DecimalFormat) NumberFormat.getInstance( Locale.US ); + result.applyPattern(spec); + return result; + } +} diff --git a/dasCore/src/main/java/org/das2/util/ObjectLocator.java b/dasCore/src/main/java/org/das2/util/ObjectLocator.java new file mode 100644 index 000000000..3a4823554 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/ObjectLocator.java @@ -0,0 +1,70 @@ +/* + * ObjectLocator.java + * + * Created on February 2, 2006, 4:01 PM + * + * + */ + +package org.das2.util; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.GeneralPath; +import java.util.ArrayList; +import java.util.List; + +/** + * Keeps track of shapes and Objects located at each point, and can quickly find the closest. + * consider Use spheres of influence. Brute force implementation presently + */ +public class ObjectLocator { + List shapes= new ArrayList(); + List objects= new ArrayList(); + public void addObject( Shape bounds, Object object ) { + Shape clone= (Shape)new GeneralPath(bounds); + shapes.add( clone ); + objects.add( object ); + } + public void removeObject( Object object ) { + int i= objects.indexOf(object); + objects.remove(i); + shapes.remove(i); + } + + public Object closestObject( Point p ) { + int i; + for ( i=shapes.size()-1; i>=0; i-- ) { + Shape s= (Shape)shapes.get(i); + if ( s.contains( p ) ) { + break; + } + } + if ( i==-1 ) { + return null; + } else { + return objects.get(i); + } + } + + public Shape closestShape( Point p ) { + int i; + for ( i=shapes.size()-1; i>=0; i-- ) { + Shape s= (Shape)shapes.get(i); + if ( s.contains( p ) ) { + break; + } + } + if ( i==-1 ) { + return null; + } else { + return (Shape)shapes.get(i); + } + } + + public Object getObject( Shape shape ) { + int i= shapes.indexOf( shape ); + return objects.get(i); + } +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/PersistentStateSupport.java b/dasCore/src/main/java/org/das2/util/PersistentStateSupport.java new file mode 100644 index 000000000..aa11fcc1e --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/PersistentStateSupport.java @@ -0,0 +1,520 @@ +/* + * PersistentStateSupport.java + * + * Created on April 20, 2006, 1:23 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util; + +import org.das2.util.monitor.ProgressMonitor; +import org.das2.components.DasProgressPanel; +import org.das2.dasml.SerializeUtil; +import org.das2.dasml.DOMBuilder; +import org.das2.graph.DasCanvas; +import org.das2.util.filesystem.Glob; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.prefs.Preferences; +import java.util.regex.Pattern; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.filechooser.FileFilter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +//import org.apache.xml.serialize.OutputFormat; +//import org.apache.xml.serialize.XMLSerializer; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * + * @author Jeremy + */ +public class PersistentStateSupport { + + String ext; + private File currentFile; + JMenu openRecentMenu; + SerializationStrategy strategy; + Component component; + + private JMenuItem saveMenuItem; + private JLabel currentFileLabel; + private List recentFiles; + + /** state has been modified and needs to be saved */ + private boolean dirty; + + public static final String PROPERTY_OPENING="opening"; + public static final String PROPERTY_SAVING="saving"; + public static final String PROPERTY_DIRTY="dirty"; + public static final String PROPERTY_CURRENT_FILE="currentFile"; + + public interface SerializationStrategy { + // give me a document to serialize + public Element serialize( Document document, ProgressMonitor monitor ) throws IOException; + + // here's a document you gave me + public void deserialize( Document doc, ProgressMonitor monitor ); + } + + private static SerializationStrategy getCanvasStrategy( final DasCanvas canvas ) { + return new SerializationStrategy() { + public Element serialize(Document document, ProgressMonitor monitor) { + DOMBuilder builder= new DOMBuilder( canvas ); + Element element= builder.serialize( document, DasProgressPanel.createFramed("Serializing Canvas") ); + return element; + } + + public void deserialize(Document document, ProgressMonitor monitor) { + Element element= document.getDocumentElement(); + SerializeUtil.processElement(element,canvas ); + } + }; + } + + + /** + * Provides a means for saving the application persistently, undo/redo support (TODO). + * canvas is the canvas to be serialized, extension identifies the application. Note that + * internal changes to das may break saved files. + */ + public PersistentStateSupport( DasCanvas canvas, String extension ) { + this( canvas, getCanvasStrategy( canvas ), extension ); + + } + + private void refreshRecentFilesMenu() { + if ( openRecentMenu!=null ) { + openRecentMenu.removeAll(); + for ( int i=0; i7) { + recentFiles.remove(7); + } + Preferences prefs= Preferences.userNodeForPackage(PersistentStateSupport.class); + prefs.put( "PersistentStateSupport"+ext+"_recent", getRencentFilesString() ); + refreshRecentFilesMenu(); + } + + public Action createOpenAction() { + return new AbstractAction("Open...") { + public void actionPerformed( ActionEvent ev ) { + try { + JFileChooser chooser = new JFileChooser(); + if ( getCurrentFile()!=null ) chooser.setCurrentDirectory(getCurrentFile().getParentFile()); + chooser.setFileFilter( simpleFilter( "*"+ext ) ); + int result = chooser.showOpenDialog(component); + if (result == JFileChooser.APPROVE_OPTION) { + open( chooser.getSelectedFile() ); + addToRecent(getCurrentFile()); + if ( saveMenuItem!=null ) saveMenuItem.setText("Save"); + } + } catch ( Exception e ) { + throw new RuntimeException(e); + } + } + }; + } + + /** + * override me. If open fails, throw an exception. + */ + protected void openImpl( File file ) throws Exception { + Document document= readDocument( file ); + strategy.deserialize( document, DasProgressPanel.createFramed("deserializing") ); + } + + private void open( final File file ) { + setOpening( true ); + Runnable run = new Runnable() { + public void run() { + try { + if ( !file.exists() ) { + JOptionPane.showMessageDialog(component,"File not found: "+file, "File not found", JOptionPane.WARNING_MESSAGE ); + return; + } + openImpl(file); + setOpening( false ); + setDirty(false); + setCurrentFile(file); + setCurrentFileOpened(true); + update(); + } catch ( IOException e ) { + throw new RuntimeException(e); + } catch ( ParserConfigurationException e ) { + throw new RuntimeException(e); + } catch ( SAXException e ) { + throw new RuntimeException(e); + } catch ( Exception e ) { + throw new RuntimeException(e); + } + } + }; + new Thread( run, "PersistentStateSupport.open" ).start(); + } + + /** + * @deprecated. What is the purpose of this method? + */ + public void close() { + setCurrentFile(null); + } + + public void markDirty() { + this.setDirty( true ); + update(); + } + + private void update() { + if ( currentFileLabel!=null ) this.currentFileLabel.setText( getCurrentFile() + ( dirty ? " *" : "" ) ); + } + /** Creates a new instance of PersistentStateSupport */ + public PersistentStateSupport() { + } + + /** + * Utility field used by bound properties. + */ + private java.beans.PropertyChangeSupport propertyChangeSupport = new java.beans.PropertyChangeSupport(this); + + /** + * Adds a PropertyChangeListener to the listener list. + * @param l The listener to add. + */ + public void addPropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.addPropertyChangeListener(l); + } + + /** + * Removes a PropertyChangeListener from the listener list. + * @param l The listener to remove. + */ + public void removePropertyChangeListener(java.beans.PropertyChangeListener l) { + propertyChangeSupport.removePropertyChangeListener(l); + } + + /** + * Getter for property dirty. + * @return Value of property dirty. + */ + public boolean isDirty() { + return this.dirty; + } + + /** + * Setter for property dirty. + * @param dirty New value of property dirty. + */ + public void setDirty(boolean dirty) { + boolean oldDirty = this.dirty; + this.dirty = dirty; + propertyChangeSupport.firePropertyChange ( PROPERTY_DIRTY, new Boolean (oldDirty), new Boolean (dirty)); + } + + public File getCurrentFile() { + return currentFile; + } + + public void setCurrentFile(File currentFile) { + File oldFile = this.currentFile; + this.currentFile = currentFile; + propertyChangeSupport.firePropertyChange ( PROPERTY_CURRENT_FILE, oldFile, currentFile ); + } + + /** + * Holds value of property loading. + */ + private boolean opening; + + /** + * Property loading is true when a load operation is being performed. + * @return Value of property loading. + */ + public boolean isOpening() { + return this.opening; + } + + /** + * Holds value of property saving. + */ + private boolean saving; + + /** + * Property saving is true when a save operation is being performed. + * @return Value of property saving. + */ + public boolean isSaving() { + return this.saving; + } + + private void setOpening(boolean b) { + boolean old= this.opening; + this.opening= b; + propertyChangeSupport.firePropertyChange( PROPERTY_OPENING, Boolean.valueOf(old), Boolean.valueOf(b) ); + + } + + + private void setSaving(boolean b) { + boolean old= this.saving; + this.saving= b; + propertyChangeSupport.firePropertyChange( PROPERTY_SAVING, Boolean.valueOf(old), Boolean.valueOf(b) ); + + } + + /** + * Holds value of property currentFileOpened. + */ + private boolean currentFileOpened; + + /** + * Property currentFileOpened indicates if the current file has ever been opened. This + * is to handle the initial state where the current file is set, but should not be + * displayed because it has not been opened. + * @return Value of property currentFileOpened. + */ + public boolean isCurrentFileOpened() { + return this.currentFileOpened; + } + + /** + * Setter for property currentFileOpened. + * @param currentFileOpened New value of property currentFileOpened. + */ + public void setCurrentFileOpened(boolean currentFileOpened) { + boolean oldCurrentFileOpened = this.currentFileOpened; + this.currentFileOpened = currentFileOpened; + propertyChangeSupport.firePropertyChange ("currentFileOpened", new Boolean (oldCurrentFileOpened), new Boolean (currentFileOpened)); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/Probe.java b/dasCore/src/main/java/org/das2/util/Probe.java new file mode 100644 index 000000000..acafbcc8b --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/Probe.java @@ -0,0 +1,594 @@ +/* + * Probe.java + * + * Created on September 22, 2004, 1:21 PM + */ + +package org.das2.util; + +import org.das2.graph.SymbolLineRenderer; +import org.das2.graph.Leveler; +import org.das2.graph.DasColumn; +import org.das2.graph.Psym; +import org.das2.graph.DasAnnotation; +import org.das2.graph.DasCanvas; +import org.das2.graph.SymColor; +import org.das2.graph.DasRow; +import org.das2.graph.DasAxis; +import org.das2.graph.DasPlot; +import org.das2.graph.Legend; +import org.das2.event.DumpToFileMouseModule; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.dataset.DataSet; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.datum.Datum; +import org.das2.datum.DatumRangeUtil; +import org.das2.DasApplication; +import org.das2.system.DasLogger; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +/** + * + * @author Jeremy + */ +public class Probe { + + final long refreshRateMillis=1000; + + HashMap histograms; + + HashMap agents; + + boolean ignoring=false; + String triggerName=null; + double triggerMin; + double triggerMax; + + //DasPlot plot; + Legend legend; + Leveler leveler; + DasCanvas canvas; + DasAnnotation titleAnnotation; + + DatumRange xrange; + DatumRange yrange; + + DasColumn column; + DasAxis xAxis; + + boolean isWidgetCreated; + JFrame frame; + + String title; + + boolean isNull; + + long t0millis; + boolean needUpdate=false; + boolean updating=true; + + /** + * Holds value of property xsize. + */ + private int xsize; + + /** + * Holds value of property ysize. + */ + private int ysize; + + /* agent has responsibility of conveying info */ + private class Agent { + VectorDataSetBuilder builder; + SymbolLineRenderer renderer; + DasPlot plot; + DatumRange xrange, yrange; + boolean yrangeSet= false; + boolean histogram= false; + + int hits; + Legend legend; + String name; + + Agent( String name, int index, Agent underplotAgent ) { + builder= new VectorDataSetBuilder(Units.dimensionless,Units.dimensionless); + renderer= new SymbolLineRenderer(); + this.name= name; + xrange= new DatumRange( Datum.create(0), Datum.create(1) ); + yrange= DatumRangeUtil.newDimensionless(0,0.000001); + + + DasAxis thisXAxis; + if ( index==0 ) { + xAxis= new DasAxis( xrange, DasAxis.HORIZONTAL); + thisXAxis= xAxis; + } else { + thisXAxis= xAxis.createAttachedAxis(); + thisXAxis.setTickLabelsVisible(false); + } + + if ( underplotAgent==null ) { + //plot= org.das2.graph.Util.newDasPlot(canvas, xrange, yrange ); + plot= new DasPlot( thisXAxis, new DasAxis( yrange, DasAxis.VERTICAL ) ); + plot.addMouseModule( new DumpToFileMouseModule( plot, renderer, thisXAxis, plot.getYAxis() ) ); + } else { + plot= underplotAgent.plot; + } + + renderer.setPsym(Psym.NONE); + // renderer.setPsymConnector(PsymConnector.NONE); + //renderer.setSymSize(2.0); + + final SymColor[] symColors= { SymColor.black, SymColor.blue, SymColor.lightRed, SymColor.red, SymColor.darkGreen, SymColor.gray }; + renderer.setColor(symColors[index%symColors.length]); + + plot.getXAxis().setAnimated(true); + plot.getYAxis().setAnimated(true); + plot.getYAxis().setLabel(name); + plot.addRenderer(renderer); + + if ( underplotAgent==null ) { + DasRow row= leveler.addRow(0.); + canvas.add( plot, row, column ); + } else { + underplotAgent.addToLegend(this); + } + + hits= 0; + } + + void addToLegend( Agent overplotAgent ) { + if ( legend==null ) { + DasCanvas c= plot.getCanvas(); + legend= new Legend(); + legend.add( renderer, name ); + c.add( legend, plot.getRow().createAttachedRow(0.2,0.95), plot.getColumn().createAttachedColumn(0.7,0.99) ); + } + legend.add( overplotAgent.renderer, overplotAgent.name ); + } + + void add( double value ) { + double xvalue= ++hits; + synchronized( builder ) { + builder.insertY(xvalue,value); + } + if ( !xrange.contains( Units.dimensionless.createDatum(xvalue)) ) { + xrange= include( xrange, Units.dimensionless.createDatum(xvalue)) ; + if ( !xrange.width().isFinite() ) { + throw new IllegalStateException(); + } + } + + if ( !yrangeSet ) { + yrange= new DatumRange( value, value+1e-7, Units.dimensionless ); + yrangeSet= true; + } + if ( !yrange.contains( Units.dimensionless.createDatum(value) ) ) { + yrange= include( yrange, Units.dimensionless.createDatum(value) ); + if ( !yrange.width().isFinite() ) { + throw new IllegalStateException(); + } + } + + needUpdate=true; + } + + void add( int value ) { + histogram= true; + this.add((float)value); + } + + void update() { + DataSet ds; + synchronized( builder ) { + ds= builder.toVectorDataSet(); + } + plot.getXAxis().setDatumRange( xrange ); + plot.getYAxis().setDatumRange( yrange ); + renderer.setHistogram(histogram); + renderer.setDataSet(ds); + + } + + void destroy() { + if ( isWidgetCreated ) { + plot.removeRenderer(renderer); + } + } + + } + + public DasCanvas getCanvas() { + if ( isNull ) throw new IllegalArgumentException("getCanvas called for null canvas"); + return this.canvas; + } + + public void reset() { + if ( isNull ) return; + if ( agents==null ) agents= new HashMap(); + + for ( Iterator i=agents.keySet().iterator(); i.hasNext(); ) { + Object key= i.next(); + Agent a= (Agent)agents.get(key); + a.destroy(); + } + + agents= new HashMap(); + + t0millis= System.currentTimeMillis(); + needUpdate= true; + } + + private DatumRange include( DatumRange dr, Datum d ) { + if ( dr.contains(d) ) { + return dr; + } + + Datum w= dr.width(); + if ( w.doubleValue( w.getUnits() ) == 0. ) { + dr= dr.include(d); + return dr; + } else if ( d.lt(dr.min()) ) { + while ( !dr.contains(d) ) dr= DatumRangeUtil.rescale(dr,-1,1); + } else { + while ( !dr.contains(d) ) dr= DatumRangeUtil.rescale(dr,0,2); + } + DasLogger.getLogger( DasLogger.SYSTEM_LOG ).fine( "dr="+dr ); + return dr; + } + + /* public synchronized void add( String name, double value, String name2, double value2 ) { + // this doesn't work. + VectorDataSetBuilder builder; + org.das2.graph.CurveRenderer renderer; + if ( ! builders.containsKey(name) ) { + maybeCreateWidget(); + builder= new VectorDataSetBuilder(Units.seconds,Units.dimensionless); + builder.addPlane( name2, Units.dimensionless ); + builders.put(name, builder); + renderer= new CurveRenderer( null, "", name2 ); + + renderers.put( name, renderer ); + + plot.addRenderer(renderer); + + } else { + builder= (VectorDataSetBuilder)builders.get(name); + } + + //long c= ((Long)currentCount.get(name)).longValue(); + double seconds= ( System.currentTimeMillis() - t0millis ) / 1000.; + builder.insertY(seconds,value); + builder.insertY( seconds, value2, name2 ); + //currentCount.put(name,new Long(++c)); + + if ( !xrange.contains( Units.seconds.createDatum(seconds)) ) { + xrange= include( xrange, Units.seconds.createDatum(seconds)) ; + plot.getXAxis().setDataRange( xrange ); + } + if ( !yrange.contains( Units.dimensionless.createDatum(value) ) ) { + yrange= include( yrange, Units.dimensionless.createDatum(value) ); + plot.getYAxis().setDataRange( yrange ); + } + + needUpdate=true; + }*/ + + /* public synchronized void addHistogram( String name, double value ) { + int[] histogram; + if ( !histograms.containsKey(name) ) { + maybeCreateWidget(); + histogram= new int[50]; + histograms.put(name,histogram); + org.das2.graph.SymbolLineRenderer renderer; + renderer= new SymbolLineRenderer((DataSet)null); + renderer.setHistogram(true); + renderer.setPsym( Psym.NONE ); + renderers.put( name, renderer ); + plot.addRenderer(renderer); + legend.add( renderer, name ); + } else { + histogram= (int[])histograms.get(name); + } + int ivalue= (int)value; + if ( ivalue < 0 ) ivalue=0; + + if ( (int)value > histogram.length ) { + int[] histNew= new int[ 2*(int)value ]; + System.arraycopy(histogram, 0, histNew, 0, histogram.length ); + histogram= histNew; + histograms.put( name, histogram ); + } + histogram[ivalue]++; + + if ( !xrange.contains( Units.seconds.createDatum(histogram.length)) ) { + xrange= include( xrange, Units.seconds.createDatum(histogram.length)) ; + plot.getXAxis().setDataRange( xrange ); + } + if ( !yrange.contains( Units.dimensionless.createDatum(histogram[ivalue]) ) ) { + yrange= include( yrange, Units.dimensionless.createDatum(histogram[ivalue]) ); + plot.getYAxis().setDataRange( yrange ); + } + + needUpdate= true; + + } */ + + private void checkTrigger( String name, double value ) { + if ( this.triggerName==null ) return; + if ( this.triggerName.equals(name) ) { + boolean notIgnoring= this.triggerMin < value && value < this.triggerMax; + ignoring= !notIgnoring; + } + } + + public boolean isTriggered() { + return !ignoring; + } + + public synchronized void add( String name, int value ) { + if ( isNull ) return; + checkTrigger( name, value ); + if ( ignoring ) return; + if ( Double.isInfinite(value) ) { + throw new IllegalStateException("value is not finite: "+name); + } + VectorDataSetBuilder builder; + org.das2.graph.SymbolLineRenderer renderer; + Agent a; + if ( ! agents.containsKey(name) ) { + maybeCreateWidget(); + a= new Agent( name, agents.size(), null ); + agents.put( name, a ); + } else { + a= (Agent)agents.get(name); + } + + a.add(value); + + } + + public synchronized void add( String name, double value ) { + if ( isNull ) return; + checkTrigger( name, value ); + if ( ignoring ) return; + if ( Double.isInfinite(value) ) { + throw new IllegalStateException("value is not finite: "+name); + } + VectorDataSetBuilder builder; + Agent a; + if ( ! agents.containsKey(name) ) { + maybeCreateWidget(); + a= new Agent( name, agents.size(), null ); + agents.put( name, a ); + } else { + a= (Agent)agents.get(name); + } + + a.add(value); + } + + public synchronized void addOverplot( String overplotName, String underplotName, double value ) { + if ( isNull ) return; + checkTrigger( overplotName, value ); + if ( ignoring ) return; + if ( Double.isInfinite(value) ) { + throw new IllegalStateException("value is not finite: "+overplotName); + } + VectorDataSetBuilder builder; + Agent a; + if ( ! agents.containsKey(overplotName) ) { + maybeCreateWidget(); + Agent underplotAgent= (Agent)agents.get(underplotName); + if ( underplotAgent==null ) { + a= new Agent( overplotName, agents.size(), null ); + } else { + a= new Agent( overplotName, agents.size(), underplotAgent ); + } + agents.put( overplotName, a ); + } else { + a= (Agent)agents.get(overplotName); + } + + a.add(value); + } + + public synchronized void addOverplot( String overplotName, String underplotName, int value ) { + if ( isNull ) return; + checkTrigger( overplotName, value ); + if ( ignoring ) return; + VectorDataSetBuilder builder; + Agent a; + if ( ! agents.containsKey(overplotName) ) { + maybeCreateWidget(); + Agent underplotAgent= (Agent)agents.get(underplotName); + if ( underplotAgent==null ) { + a= new Agent( overplotName, agents.size(), null ); + } else { + a= new Agent( overplotName, agents.size(), underplotAgent ); + } + agents.put( overplotName, a ); + } else { + a= (Agent)agents.get(overplotName); + } + + a.add(value); + } + + public void setTrigger( String name, double min, double max ) { + if ( name!=this.triggerName || min!=this.triggerMin || max!=this.triggerMax ) { + this.triggerName= name; + this.triggerMin= min; + this.triggerMax= max; + this.ignoring= true; + } + } + + private void startUpdateThread() { + if ( updating ) { + new Thread( new Runnable() { + public void run() { + while ( true ) { + try { Thread.sleep(refreshRateMillis); } catch ( InterruptedException e ) { throw new RuntimeException(e); } + if ( updating & isWidgetCreated ) update(); + } + } + }, "probeUpdateThread" ).start(); + } + } + + public synchronized void update() { + if ( isNull ) return; + if ( isWidgetCreated && needUpdate ) { + for (Iterator i=agents.keySet().iterator(); i.hasNext(); ) { + Object name= i.next(); + Agent a= (Agent) agents.get(name); + a.update(); + } + } + needUpdate= false; + } + + public synchronized void pause() { + updating= false; + } + + private Action getUpdateAction() { + return new AbstractAction("Update") { + public void actionPerformed( ActionEvent e ) { + update(); + } + }; + } + + private Action getPauseAction() { + return new AbstractAction("Pause") { + public void actionPerformed( ActionEvent e ) { + pause(); + } + }; + } + + private void maybeCreateWidget() { + if ( isWidgetCreated ) { + return; + } else { + + frame= DasApplication.getDefaultApplication().createMainFrame("Das 2"); + JPanel panel= new JPanel(); + panel.setLayout( new BorderLayout()); + + canvas= new DasCanvas(xsize,ysize); + + panel.add(canvas, BorderLayout.CENTER ); + + Box box= Box.createHorizontalBox(); + box.add( new JButton( getUpdateAction() ) ); + box.add( new JButton( getPauseAction() ) ); + + panel.add( box, BorderLayout.NORTH ); + + column= new DasColumn( canvas, 0.15, 0.9 ); + + frame.setContentPane(panel); + + frame.setVisible(true); + frame.pack(); + + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + //xrange= new DatumRange( Datum.create(0), Datum.create(1) ); + //yrange= DatumRangeUtil.newDimensionless(0,0.000001); + + //plot= org.das2.graph.Util.newDasPlot(canvas, xrange, yrange ); + // MouseModule mm= new MouseModule( plot, new PointSlopeDragRenderer( plot, plot.getXAxis(), plot.getYAxis() ), "Point,Slope" ); + // plot.addMouseModule( mm ); + + // plot.addMouseModule( new BoxRangeSelectorMouseModule( plot, plot.getXAxis(), plot.getYAxis() ) ); + // plot.getMouseAdapter().setPrimaryModule(mm); + + // plot.getMouseAdapter().addMouseModule( new MouseModule( plot, new LengthDragRenderer( plot, plot.getXAxis(), plot.getYAxis() ), "Length" ) ); + + // plot.getXAxis().setAnimated(true); + // plot.getYAxis().setAnimated(true); + + // legend= new Legend(); + // no attachedRow method! + // canvas.add( legend, plot.getRow().createSubRow(0.95,0.80), plot.getColumn().createAttachedColumn( 0.5, 0.8 ) ); + leveler= new Leveler(canvas); + titleAnnotation= new DasAnnotation( title ); + canvas.add( titleAnnotation, new DasRow(canvas,0.,0.05), new DasColumn( canvas, 0., 1. ) ); + canvas.revalidate(); + + isWidgetCreated= true; + startUpdateThread(); + } + + } + + public void setUpdating( boolean updating ) { + this.updating= updating; + } + + private Probe( String title, boolean notNull, int xsize, int ysize ) { + this.title= title; + this.isWidgetCreated= false; + this.updating= notNull; + this.isNull= !notNull; + this.xsize= xsize; + this.ysize= ysize; + reset(); + } + + public static Probe newProbe( String title ) { + return new Probe(title,true, 400, 600 ); + } + + public static Probe newProbe( int xsize, int ysize ) { + return new Probe( "Probe", true, xsize, ysize ); + } + + public static Probe nullProbe() { + return new Probe("",false, 400,400 ); + } + + public static void main(String[] args) throws Exception { + Probe p= new Probe("Test of Probe with fake data",true,400,600); + Thread.sleep(500); + for ( int i=0; i<500; i++ ) { + Thread.sleep(200); + p.add("i", i); + p.add("j", i % 10 ); + } + } + + public int getXsize() { + return this.xsize; + } + + public void setXsize(int xsize) { + this.xsize = xsize; + } + + public int getYsize() { + return this.ysize; + } + + public void setYsize(int ysize) { + this.ysize = ysize; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + if ( titleAnnotation!=null ) titleAnnotation.setText(title); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/Splash.java b/dasCore/src/main/java/org/das2/util/Splash.java new file mode 100644 index 000000000..58a27dc6b --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/Splash.java @@ -0,0 +1,132 @@ +/* File: Splash.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.util.logging.*; +import javax.swing.*; +import java.awt.*; +import java.net.URL; + +/** + * + * @author jbf + */ +public class Splash extends JWindow { + + public static Splash instance=null; + + private Handler handler; + private JLabel messageLabel; + + public static String getVersion() { + //DName: das_20030505_01_beta D + String cvsTagName= "$Name$"; + String version; + if (cvsTagName.length()<=9) { + version="untagged_version"; + } else { + version= cvsTagName.substring(6,cvsTagName.length()-2); + } + return version; + } + + public Handler getLogHandler() { + if ( handler==null ) { + handler= createhandler(); + } + return handler; + } + + private Handler createhandler() { + Handler result= new Handler() { + Handler handler; + public void publish( LogRecord logRecord ) { + System.out.println( logRecord.getMessage() ); + messageLabel.setText(logRecord.getMessage() ); + } + public void flush() {} + public void close() {} + }; + return result; + } + + private static ImageIcon getSplashImage() { + URL url= Splash.class.getResource("/images/dasSplash.gif"); + if ( url==null ) return null; + return new ImageIcon(url); + } + + public static Splash getInstance() { + if ( instance==null ) { + instance= new Splash(); + } + return instance; + } + + public static void showSplash() { + getInstance(); + instance.setVisible(true); + } + + public static void hideSplash() { + getInstance(); + instance.setVisible(false); + } + + /** Creates a new instance of Splash */ + public Splash() { + super(); + JPanel panel= new JPanel(new BorderLayout()); + panel.add(new JLabel(getSplashImage()),BorderLayout.CENTER); + + Box bottomPanel= Box.createHorizontalBox(); + + messageLabel= new JLabel(""); + messageLabel.setMinimumSize( new Dimension( 200, 10 ) ); + bottomPanel.add( messageLabel ); + bottomPanel.add( Box.createHorizontalGlue() ); + bottomPanel.add( new JLabel("version "+getVersion()+" ",JLabel.RIGHT) ); + + panel.add( bottomPanel, BorderLayout.SOUTH ); + this.setContentPane(panel); + this.pack(); + //this.setLocation(300,300); + this.setLocationRelativeTo(null); + } + + public static void main( String[] args ) { + System.out.println("This is das2 version "+getVersion()); + Splash.showSplash(); + Logger.getLogger("").addHandler( Splash.getInstance().getLogHandler() ); + try { + for ( int i=0; i<6; i++ ) { + Thread.sleep(500); + Logger.getLogger("").fine("i="+i); + //Splash.getInstance().messageLabel.setText( "ii-="+i ); + } + } catch ( java.lang.InterruptedException e ) {} + Splash.hideSplash(); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/StreamTool.java b/dasCore/src/main/java/org/das2/util/StreamTool.java new file mode 100755 index 000000000..bef5b5cee --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/StreamTool.java @@ -0,0 +1,656 @@ +/* File: StreamTool.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package org.das2.util; + +import org.das2.stream.StreamComment; +import org.das2.stream.StreamDescriptor; +import org.das2.stream.StreamException; +import org.das2.stream.StreamHandler; +import org.das2.stream.PropertyType; +import org.das2.stream.PacketDescriptor; +import org.das2.util.InflaterChannel; +import org.das2.util.ByteBufferInputStream; +import org.das2.datum.Datum; +import org.das2.datum.DatumVector; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +/** + * + * @author jbf + */ +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +//import org.apache.xml.serialize.Method; +//import org.apache.xml.serialize.OutputFormat; +//import org.apache.xml.serialize.XMLSerializer; +import org.w3c.dom.*; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +public class StreamTool { + + /** Creates a new instance of StreamTool */ + public StreamTool() { + } + + public static class DelimeterNotFoundException extends Exception { + } + + public static byte[] advanceTo(InputStream in, byte[] delim) throws IOException, DelimeterNotFoundException { + + // Read from stream to delimeter, leaving the InputStream immediately after + // and returning bytes from stream. + + byte[] data = new byte[4096]; + + ArrayList list = new ArrayList(); + + int bytesMatched = 0; + int matchIndex = 0; + + int streamIndex = 0; // offset in bytes from the beginning of the stream + + int index = 0; + boolean notDone = true; + + int unreadOffset = -99999; + int unreadLength = -99999; + + int totalBytesRead = 0; + int offset = 0; // offset within byte[4096] + + while (notDone) { + + int byteRead = in.read(); + totalBytesRead++; + + if (byteRead == -1) { + notDone = false; + + } else { + + data[offset] = (byte) byteRead; + + if (delim[bytesMatched] == byteRead) { + bytesMatched++; + } else { + bytesMatched = 0; + } + if (bytesMatched == delim.length) { + notDone = false; + index = totalBytesRead - delim.length; + } + } + + if (notDone) { + offset++; + if (offset == 4096) { + list.add(data); + offset = 0; + data = new byte[4096]; + } + } + } + + + if (bytesMatched != delim.length) { + throw new StreamTool.DelimeterNotFoundException(); + } + + byte[] result = new byte[index]; + for (int i = 0; i < list.size(); i++) { + System.arraycopy(list.get(i), 0, result, i * 4096, 4096); + } + System.arraycopy(data, 0, result, list.size() * 4096, index - (list.size() * 4096)); + return result; + + } + + /** Read off XML data from the InputStream up to the termination of the XML. + * XML data is returned in a byte array. The InputStream is left just + * following the XML terminator. Processing is done with as little interpretation + * as possible, so invalid XML will cause problems with little feedback about + * what's wrong. + */ + public static byte[] readXML(PushbackInputStream in) throws IOException { + ReadableByteChannel channel = Channels.newChannel(in); + byte[] back = new byte[4096]; + ByteBuffer buffer = ByteBuffer.wrap(back); + channel.read(buffer); + buffer.flip(); + ByteBuffer xml = readXML(buffer); + byte[] bytes = new byte[xml.remaining()]; + xml.get(bytes); + return bytes; + } + + private static void eatWhiteSpace(ByteBuffer buffer) { + while (buffer.hasRemaining()) { + char c = (char) (0xFF & buffer.get()); + if (!Character.isWhitespace(c)) { + buffer.position(buffer.position() - 1); + return; + } + } + } + + public static ByteBuffer readXML(ByteBuffer input) throws IOException { + int gtCount = 0; + int tagCount = 0; + int bufidx = 0; + char lastChar; + boolean inQuotes = false; + boolean inTag = false; + boolean tagContainsSlash = false; + int b; + ByteBuffer buffer = input.duplicate(); + buffer.mark(); + + eatWhiteSpace(buffer); + + b = 0xFF & buffer.get(); + + if (((char) b) != '<') { + throw new IOException("found '" + ((char) b) + "', expected '<' at offset=" + buffer.position() + ".\n"); + } else { + gtCount++; + tagContainsSlash = false; + } + + while (buffer.hasRemaining() && (gtCount > 0 || tagCount > 0)) { + lastChar = (char) b; + b = 0xFF & buffer.get(); + + if (inQuotes && ((char) b) == '"' && lastChar != '\\') { + inQuotes = false; + } else if (((char) b) == '<') { + gtCount++; + inTag = true; + tagContainsSlash = false; /* until proven otherwise */ + } else if (b == (int) '>') { + gtCount--; + inTag = false; + if (lastChar != '/') { + if (tagContainsSlash) { + tagCount--; + } else { + tagCount++; + } + } + } else if (b == (int) '/') { + if (lastChar == (int) '<') { + tagContainsSlash = true; + } + } else if (((char) b) == '"' && inTag) { + inQuotes = true; + } + } + + if (b == -1) { + throw new IOException("unexpected end of file before xml termination\n"); + } + + eatWhiteSpace(buffer); + + int limit = buffer.limit(); + buffer.limit(buffer.position()); + buffer.reset(); + + ByteBuffer result = buffer.slice(); + + buffer.position(buffer.limit()); + buffer.limit(limit); + + return result; + } + + private static class ReadStreamStructure { + + private ReadableByteChannel stream; + private ByteBuffer bigBuffer = ByteBuffer.allocate(4096); + private byte[] four = new byte[4]; + private StreamHandler handler; + private Map descriptors = new HashMap(); + private int byteOffset = 0; // byte offset into file of the end of the buffer. + private int descriptorCount = 0; // successfully read descriptors + private int packetCount = 0; // successfully read packets + + private ReadStreamStructure(ReadableByteChannel stream, StreamHandler handler) { + this.stream = stream; + this.handler = handler; + } + public String toString() { + return "\ndescriptorCount="+descriptorCount+ + "\npacketCount="+packetCount+ + "\nbyteOffset="+byteOffset+ + "\ncarotPos="+(byteOffset-bigBuffer.limit()+bigBuffer.position())+ + "\nbuffer="+String.valueOf(bigBuffer); + } + } + + public static void readStream(ReadableByteChannel stream, StreamHandler handler) throws StreamException { + ReadStreamStructure struct = new ReadStreamStructure(stream, handler); + try { + StreamDescriptor sd = getStreamDescriptor(struct); + if ("deflate".equals(sd.getCompression())) { + stream = getInflaterChannel(stream); + } + handler.streamDescriptor(sd); + struct.descriptorCount++; + int bytesRead; + int totalBytesRead = 0; + while ((bytesRead = stream.read(struct.bigBuffer)) != -1) { + struct.byteOffset += struct.bigBuffer.position(); + struct.bigBuffer.flip(); + + totalBytesRead += bytesRead; + //System.err.println("d2s bytesRead="+bytesRead+" total="+totalBytesRead ); + //if ( totalBytesRead>318260 ) { + // System.err.println("here"); + //} + while (getChunk(struct)) { + // this block is empty + } + struct.bigBuffer.compact(); + } + handler.streamClosed(sd); + } catch (StreamException se) { + handler.streamException(se); + throw se; + } catch (IOException ioe) { + StreamException se = new StreamException(ioe); + handler.streamException(se); + throw se; + } + } + + private static StreamDescriptor getStreamDescriptor(ReadStreamStructure struct) throws StreamException, IOException { + struct.bigBuffer.clear().limit(10); + while (struct.bigBuffer.hasRemaining() && struct.stream.read(struct.bigBuffer) != -1) { + ; + } + if (struct.bigBuffer.hasRemaining()) { + throw new StreamException("Reached end of stream before encountering stream descriptor"); + } + struct.byteOffset += struct.bigBuffer.position(); + struct.bigBuffer.flip(); + struct.bigBuffer.get(struct.four); + if (isStreamDescriptorHeader(struct.four)) { + int contentLength = getContentLength(struct.bigBuffer); + if (contentLength == 0) { + throw new StreamException("streamDescriptor content length is 0."); + } + struct.byteOffset += struct.bigBuffer.position(); + struct.bigBuffer.clear().limit(contentLength); + while (struct.bigBuffer.hasRemaining() && struct.stream.read(struct.bigBuffer) != -1) { + ; + } + if (struct.bigBuffer.hasRemaining()) { + throw new StreamException("Reached end of stream before encountering stream descriptor"); + } + struct.byteOffset += struct.bigBuffer.position(); + struct.bigBuffer.flip(); + + try { + Document doc = getXMLDocument(struct.bigBuffer, contentLength); + Element root = doc.getDocumentElement(); + if (root.getTagName().equals("stream")) { + StreamDescriptor sd = new StreamDescriptor(doc.getDocumentElement()); + struct.bigBuffer.clear(); + return sd; + } else if (root.getTagName().equals("exception")) { + throw exception(root); + } else { + throw new StreamException("Unexpected xml header, expecting stream or exception, received: " + root.getTagName()); + } + } catch (SAXException ex) { + String msg = getSAXParseExceptionMessage(ex, struct, contentLength ); + throw new StreamException(msg); + } + } else { + String s = readMore(struct); + throw new StreamException("Expecting stream descriptor header, found: '" + asciiBytesToString(struct.four, 0, 4) + "' beginning \n'" + s + "'"); + } + } + + /** + * call this after error, to get another 100 bytes off the stream + */ + private static String readMore(ReadStreamStructure struct) throws IOException { + struct.bigBuffer.position(0); + struct.bigBuffer.limit(10); + byte[] bytes10 = new byte[10]; + struct.bigBuffer.get(bytes10); + String s = new String(bytes10); + struct.bigBuffer.limit(1000); + struct.bigBuffer.position(0); + while (struct.bigBuffer.hasRemaining() && struct.stream.read(struct.bigBuffer) != -1) { + ; + } + int p = struct.bigBuffer.position(); + byte[] bytes = new byte[p]; + struct.bigBuffer.flip(); + struct.bigBuffer.get(bytes); + s = s + new String(bytes); + return s; + } + + private static String getSAXParseExceptionMessage(final SAXException ex, final ReadStreamStructure struct,int contentLength) { + String loc = null; + if (ex instanceof SAXParseException) { + SAXParseException spe = (SAXParseException) ex; + loc = "Relative to packet start, line number is " + spe.getLineNumber() + ", column is " + spe.getColumnNumber(); + } + + int bufOffset= struct.byteOffset - struct.bigBuffer.limit(); + + String msg = "xml parser fails with the message: \"" + ex.getMessage() + + "\" within the packet ending at byte offset " + ( bufOffset + struct.bigBuffer.position() ) + "."; + if (ex.getMessage().contains("trailing")) { + msg += "\nNon-whitespace data found after xml closing tag, probably caused by content length error."; + int i; + // find the end of the closing xml tag. + for (i = bufOffset + struct.bigBuffer.position() - 1; i > 0; i--) { + int bpos= i - bufOffset ; + if (struct.bigBuffer.get( bpos ) == '>' ) { + break; + } else { + System.err.println( (char)struct.bigBuffer.get(bpos) ); + } + } + for ( ; i < bufOffset + struct.bigBuffer.position() ; i++) { + int bpos= i - bufOffset; + if (struct.bigBuffer.get(bpos) == '[' || struct.bigBuffer.get(bpos)==':' ) { + break; + } + } + if (i > 0) { + int error= i - ( struct.bigBuffer.position() + bufOffset ); + //int error= ( i + bufOffset ) - ( struct.byteOffset - 10 ); + NumberFormat nf = new DecimalFormat("000000"); + msg += "\nContent length was " + nf.format(contentLength) + + ", maybe it should have been " + + nf.format(contentLength+error) + "."; + } + } + + if (loc != null) { + msg += " " + loc; + } + return msg; + } + + private static final StreamException exception(Element exception) { + String type = exception.getAttribute("type"); + return new StreamException(type); + } + + private static boolean getChunk(ReadStreamStructure struct) throws StreamException, IOException { + struct.bigBuffer.mark(); + if (struct.bigBuffer.remaining() < 4) { + return false; + } + struct.bigBuffer.get(struct.four); + if (isPacketDescriptorHeader(struct.four)) { + if (struct.bigBuffer.remaining() < 6) { + struct.bigBuffer.reset(); + return false; + } + int contentLength = getContentLength(struct.bigBuffer); + if (contentLength == 0) { + throw new StreamException("packetDescriptor content length is 0."); + } + if (struct.bigBuffer.capacity() < contentLength) { + struct.bigBuffer.reset(); + ByteBuffer temp = ByteBuffer.allocate(8 + contentLength + contentLength / 10); + temp.put(struct.bigBuffer); + temp.flip(); + struct.bigBuffer = temp; + return false; + } else if (struct.bigBuffer.remaining() < contentLength) { + struct.bigBuffer.reset(); + return false; + } + + try { + Document doc = getXMLDocument(struct.bigBuffer, contentLength); + Element root = doc.getDocumentElement(); + if (root.getTagName().equals("packet")) { + PacketDescriptor pd = new PacketDescriptor(doc.getDocumentElement()); + struct.handler.packetDescriptor(pd); + struct.descriptors.put(asciiBytesToString(struct.four, 1, 2), pd); + } else if (root.getTagName().equals("exception")) { + throw exception(root); + } else if (root.getTagName().equals("comment")) { + struct.handler.streamComment(new StreamComment(doc.getDocumentElement())); + } else { + throw new StreamException("Unexpected xml header, expecting stream or exception, received: " + root.getTagName()); + } + struct.descriptorCount++; + } catch (SAXException ex) { + String msg = getSAXParseExceptionMessage(ex, struct, contentLength ); + throw new StreamException(msg); + } + } else if (isPacketHeader(struct.four)) { + String key = asciiBytesToString(struct.four, 1, 2); + PacketDescriptor pd = (PacketDescriptor) struct.descriptors.get(key); + int contentLength = pd.getSizeBytes(); + if (struct.bigBuffer.remaining() < contentLength) { + struct.bigBuffer.reset(); + return false; + } + int yCount = pd.getYCount(); + Datum xTag = pd.getXDescriptor().readDatum(struct.bigBuffer); + DatumVector[] vectors = new DatumVector[yCount]; + for (int i = 0; i < yCount; i++) { + vectors[i] = pd.getYDescriptor(i).read(struct.bigBuffer); + } + struct.handler.packet(pd, xTag, vectors); + struct.packetCount++; + } else { + String msg = "Expected four byte header, found '"; + String s = new String(struct.four); + s = s.replaceAll("\n", "\\\\n"); // TODO: what's the right wat to say this? + msg += s; + msg += "' at byteOffset=" + (struct.byteOffset + struct.bigBuffer.position() - 4); + msg += " after reading " + struct.descriptorCount + " descriptors and " + struct.packetCount + " packets."; + throw new StreamException(msg); + } + return true; + } + + private static ByteBuffer sliceBuffer(ByteBuffer buffer, int length) { + ByteBuffer dup = buffer.duplicate(); + dup.limit(dup.position() + length); + return dup.slice(); + } + + private static String asciiBytesToString(byte[] bytes, int offset, int length) { + try { + return new String(bytes, offset, length, "US-ASCII"); + } catch (UnsupportedEncodingException uee) { + //All JVM implementations are required to support US-ASCII + throw new RuntimeException(uee); + } + } + + private static boolean isStreamDescriptorHeader(byte[] four) { + return four[0] == (byte) '[' && four[1] == (byte) '0' && four[2] == (byte) '0' && four[3] == (byte) ']'; + } + + private static boolean isPacketDescriptorHeader(byte[] four) { + return four[0] == (byte) '[' && four[3] == (byte) ']' && (Character.isDigit((char) four[1]) && Character.isDigit((char) four[2]) || (char) four[1] == 'x' && (char) four[2] == 'x'); + } + + private static boolean isPacketHeader(byte[] four) { + return four[0] == (byte) ':' && four[3] == (byte) ':' && Character.isDigit((char) four[1]) && Character.isDigit((char) four[2]); + } + + private static Document getXMLDocument(ByteBuffer buffer, int contentLength) throws StreamException, IOException, SAXException { + ByteBuffer xml = buffer.duplicate(); + xml.limit(xml.position() + contentLength); + buffer.position(buffer.position() + contentLength); + final boolean DEBUG = false; + if (DEBUG) { + int pos = xml.position(); + byte[] bytes = new byte[xml.limit() - xml.position()]; + xml.get(bytes); + xml.position(pos); + System.err.println(new String(bytes)); + } + ByteBufferInputStream bbin = new ByteBufferInputStream(xml); + InputStreamReader isr = new InputStreamReader(bbin); + + try { + DocumentBuilder builder; + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + InputSource source = new InputSource(isr); + Document document = builder.parse(source); + return document; + } catch (ParserConfigurationException ex) { + throw new RuntimeException(ex); + } + + } + + private static int getContentLength(ByteBuffer buffer) throws StreamException { + int contentLength = 0; + for (int i = 0; i < 6; i++) { + char c = (char) (0xFF & buffer.get()); + if (c == ' ') { + continue; + } + if (!Character.isDigit(c)) { + throw new StreamException("Invalid character in contentLength: '" + c + "'"); + } + int digit = Character.digit(c, 10); + contentLength = contentLength * 10 + digit; + } + return contentLength; + } + + public static void formatHeader(Document document, Writer writer) throws StreamException { + DOMImplementationLS ls = (DOMImplementationLS) + document.getImplementation().getFeature("LS", "3.0"); + LSOutput output = ls.createLSOutput(); + + output.setCharacterStream(writer); + LSSerializer serializer = ls.createLSSerializer(); + try { + if ( serializer.getDomConfig().canSetParameter( "format-pretty-print", Boolean.TRUE ) ) { + serializer.getDomConfig().setParameter( "format-pretty-print", Boolean.TRUE ); + } + } catch ( Error e ) { + e.printStackTrace(); + } + serializer.write(document, output); + /* + try { + OutputFormat format = new OutputFormat(Method.XML, "US-ASCII", true); + XMLSerializer serializer = new XMLSerializer(writer, format); + serializer.serialize(document); + } catch (IOException ioe) { + throw new StreamException(ioe); + } + */ + } + + public static Map processPropertiesElement(Element element) throws StreamException { + try { + if (!element.getTagName().equals("properties")) { + // TODO maybe this should be a RuntimeException + throw new StreamException("expecting 'properties' element, encountered '" + element.getTagName() + "'"); + } + HashMap map = new HashMap(); + NamedNodeMap attributes = element.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Attr attr = (Attr) attributes.item(i); + String name = attr.getName(); + String[] split = name.split(":"); + if (split.length == 1) { + map.put(name, attr.getValue()); + } else if (split.length == 2) { + try { + PropertyType type = PropertyType.getByName(split[0]); + Object value = type.parse(attr.getValue()); + map.put(split[1], value); + } + catch (IllegalArgumentException ex) { + map.put(name, attr.getValue()); + } + } else { + throw new IllegalArgumentException("Invalid typed name: " + name); + } + } + return map; + } catch (ParseException pe) { + StreamException se = new StreamException(pe.getMessage()); + se.initCause(pe); + throw se; + } + } + private static HashMap typesMap; + + static { + typesMap = new HashMap(); + typesMap.put(Datum.class, "Datum"); + typesMap.put(Datum.Double.class, "Datum"); + typesMap.put(Integer.class, "int"); + } + + public static Element processPropertiesMap(Document document, Map properties) { + Element propertiesElement = document.createElement("properties"); + for (Iterator i = properties.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + String key = (String) entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + continue; + } + if (typesMap.containsKey(value.getClass())) { + key = (String) typesMap.get(value.getClass()) + ":" + key; + } + propertiesElement.setAttribute(key, value.toString()); + } + return propertiesElement; + } + + private static ReadableByteChannel getInflaterChannel(ReadableByteChannel channel) throws IOException { + return new InflaterChannel(channel); + //return Channels.newChannel(new InflaterInputStream(Channels.newInputStream(channel))); + } +} diff --git a/dasCore/src/main/java/org/das2/util/TextUtil.java b/dasCore/src/main/java/org/das2/util/TextUtil.java new file mode 100644 index 000000000..cc2968396 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/TextUtil.java @@ -0,0 +1,101 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.util; + +import java.util.Enumeration; +import java.util.Vector; + +/** A grab bag of text processing utilities + * + * @author cwp + */ +public class TextUtil { + /** Line wrap function, taken from + * http://progcookbook.blogspot.com/2006/02/text-wrapping-function-for-java.html + * and then customized a little + * + * @param sText - The text to wrap + * @param nLineLen - The length of each lines text area + * @param sPrefix - A prefix string, to be added to each line, if not null + * @return + */ + public static String[] wrapText(String sText, int nLineLen, String sPrefix){ + // return empty array for null text + if(sText == null){ + return new String[]{}; + } + + sText = sText.trim(); + + // Strip out all the newlines and tabs that might happen to be in the text + sText = sText.replaceAll("\t\r\n", ""); + + // Collapse 2+ spaces to a single space + sText = sText.replaceAll("\\s+", " "); + + // return text if len is zero or less + if(nLineLen <= 0){ + return new String[]{sText}; + } + + // return text if less than length + if(sText.length() <= nLineLen){ + if(sPrefix == null) + return new String[]{sText}; + else + return new String[]{sPrefix + sText}; + } + + char[] chars = sText.toCharArray(); + @SuppressWarnings("UseOfObsoleteCollectionType") + Vector lines = new Vector(); + StringBuffer line = new StringBuffer(); + StringBuffer word = new StringBuffer(); + + for(int i = 0; i < chars.length; i++){ + word.append(chars[i]); + + if(chars[i] == ' '){ + if((line.length() + word.length()) > nLineLen){ + lines.add(line.toString()); + line.delete(0, line.length()); + } + + line.append(word); + word.delete(0, word.length()); + } + } + + // handle any extra chars in current word + if(word.length() > 0){ + if((line.length() + word.length()) > nLineLen){ + lines.add(line.toString()); + line.delete(0, line.length()); + } + line.append(word); + } + + // handle extra line + if(line.length() > 0){ + lines.add(line.toString()); + } + + String[] lRet = new String[lines.size()]; + int c = 0; // counter + if(sPrefix == null){ + for(Enumeration e = lines.elements(); e.hasMoreElements(); c++){ + lRet[c] = (String) e.nextElement(); + } + } + else{ + for(Enumeration e = lines.elements(); e.hasMoreElements(); c++){ + lRet[c] = sPrefix + (String) e.nextElement(); + } + } + + return lRet; + } +} diff --git a/dasCore/src/main/java/org/das2/util/ThreadDenseConsoleFormatter.java b/dasCore/src/main/java/org/das2/util/ThreadDenseConsoleFormatter.java new file mode 100644 index 000000000..01da98110 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/ThreadDenseConsoleFormatter.java @@ -0,0 +1,28 @@ +/* + * NBConsoleFormatter.java + * + * Created on April 7, 2005, 12:13 PM + */ + +package org.das2.util; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * + * @author Jeremy + */ +public class ThreadDenseConsoleFormatter extends Formatter { + + public String format( LogRecord rec ) { + Thread t= Thread.currentThread(); + StackTraceElement[] st= new Throwable().getStackTrace(); +// return rec.getLoggerName()+": "+t+": "+rec.getLevel().getLocalizedName()+": "+rec.getMessage()+"\n"+"\tat "+st[7]+"\n\tat "+st[8]+"\n"; + return rec.getLoggerName()+": "+t+": "+rec.getLevel().getLocalizedName()+": "+rec.getMessage()+"\n"; + } + + public ThreadDenseConsoleFormatter() { + } + +} diff --git a/dasCore/src/main/java/org/das2/util/TimeParser.java b/dasCore/src/main/java/org/das2/util/TimeParser.java new file mode 100644 index 000000000..a8d3f42ea --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/TimeParser.java @@ -0,0 +1,591 @@ +/* + * TimeParser.java + * + * Created on January 27, 2006, 3:51 PM + * + * + */ +package org.das2.util; + +import org.das2.datum.Datum; +import org.das2.datum.DatumRange; +import org.das2.datum.TimeUtil; +import org.das2.datum.CalendarTime; +import org.das2.datum.Units; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * TimeParser designed to quickly parse strings with a specified format. This parser has been + * shown to perform around 20 times faster than that the generalized parser. + * + * @author Jeremy + */ +public class TimeParser { + + final static Logger logger = Logger.getLogger("TimeParser"); + /** + * %Y-%m-%dT%H:%M:%S.%{milli}Z + */ + public static final String TIMEFORMAT_Z = "%Y-%m-%dT%H:%M:%S.%{milli}Z"; + CalendarTime time; + int[] timeWidth; + int ndigits; + String[] valid_formatCodes = new String[]{"Y", "y", "j", "m", "d", "H", "M", "S", "milli", "micro", "p", "z", "ignore", "b"}; + String[] formatName = new String[]{"Year", "2-digit-year", "day-of-year", "month", "day", "Hour", "Minute", "Second", "millisecond", "microsecond", + "am/pm", "RFC-822 numeric time zone", "ignore", "3-char-month-name", + }; + int[] formatCode_lengths = new int[]{4, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 5, -1, 3}; + int[] precision = new int[]{0, 0, 2, 1, 2, 3, 4, 5, 6, 7, -1, -1, -1, 1}; + int[] handlers; + /** + * set of custom handlers to allow for extension + */ + Map/**/ fieldHandlers; + /** + * positions of each digit, within the string to be parsed. If position is -1, then we need to + * compute it along the way. + */ + int[] offsets; + int[] lengths; + String[] delims; + String[] fc; + String regex; + String formatString; + /** + * Least significant digit in format. + *0=year, 1=month, 2=day, 3=hour, 4=min, 5=sec, 6=milli, 7=micro + */ + int lsd; + + public interface FieldHandler { + + public void handleValue(String fieldContent, CalendarTime startTime, int timeWidth[]); + } + + /** + * must contain T or space to delimit date and time. + * @param exampleTime "1992-353T02:00" + * @return "%Y-%jT%H%M" etc. + */ + public static String iso8601String(String exampleTime) { + int i = exampleTime.indexOf("T"); + if (i == -1) { + i = exampleTime.indexOf(" "); + } + char dateTimeDelim = exampleTime.charAt(i); + + String date = null, time = null; + if (i != -1) { + String datePart = exampleTime.substring(0, i); + boolean hasDelim = !datePart.matches("\\d+"); + char delim = 0; + if (hasDelim) { + delim = datePart.charAt(4); + } + switch (datePart.length()) { + case 10: + date = "%Y" + delim + "%m" + delim + "%d"; + break; + case 9: + date = "%Y" + delim + "%j"; + break; + case 8: + date = hasDelim ? "%Y" + delim + "%j" : "%Y%m%d"; + break; + case 7: + date = "%Y%j"; + break; + default: + throw new IllegalArgumentException("unable to identify date format for " + exampleTime); + } + + String timePart = exampleTime.substring(i + 1); + if ( timePart.endsWith("Z") ) timePart= timePart.substring(0,timePart.length()-1); // see below + hasDelim = !timePart.matches("\\d+"); + delim = 0; + if (hasDelim) { + delim = timePart.charAt(2); + } + switch (timePart.length()) { + case 4: + time = "%H%M"; + break; + case 5: + time = "%H" + delim + "%M"; + break; + case 6: + time = "%H%M%S"; + break; + case 8: + time = "%H" + delim + "%M" + delim + "%S"; + break; + case 12: + time = "%H" + delim + "%M" + delim + "%S.%{milli}"; + break; + case 15: + time = "%H" + delim + "%M" + delim + "%S.%{milli}%{micro}"; + break; + default: + throw new IllegalArgumentException("unable to identify time format for " + exampleTime); + } + if ( timePart.endsWith("Z") ) time+= "Z"; + return date + dateTimeDelim + time; + + } else { + throw new IllegalArgumentException("example time must contain T or space."); + } + } + + private TimeParser(String formatString, Map/**/ fieldHandlers) { + time = new CalendarTime(); + this.fieldHandlers = fieldHandlers; + this.formatString = formatString; + + String[] ss = formatString.split("%"); + fc = new String[ss.length]; + String[] delim = new String[ss.length + 1]; + + ndigits = ss.length; + + StringBuffer regex = new StringBuffer(100); + regex.append(ss[0]); + + lengths = new int[ndigits]; + for (int i = 0; i < lengths.length; i++) { + lengths[i] = -1; // -1 indicates not known, but we'll figure out as many as we can. + + } + + delim[0] = ss[0]; + for (int i = 1; i < ndigits; i++) { + int pp = 0; + while (Character.isDigit(ss[i].charAt(pp)) || ss[i].charAt(pp)=='-' ) { + pp++; + } + if (pp > 0) { + lengths[i] = Integer.parseInt(ss[i].substring(0, pp)); + } else { + lengths[i] = 0; // determine later by field type + } + + if (ss[i].charAt(pp) != '{') { + fc[i] = ss[i].substring(pp, pp + 1); + delim[i] = ss[i].substring(pp + 1); + } else { + int endIndex = ss[i].indexOf('}', pp); + int comma= ss[i].indexOf(",",pp); + if ( comma!=-1 ) { + fc[i] = ss[i].substring(pp + 1, comma); + } else { + fc[i] = ss[i].substring(pp + 1, endIndex); + } + delim[i] = ss[i].substring(endIndex + 1); + } + } + + handlers = new int[ndigits]; + offsets = new int[ndigits]; + + int pos = 0; + offsets[0] = pos; + + lsd = -1; + for (int i = 1; i < ndigits; i++) { + if (pos != -1) { + pos += delim[i - 1].length(); + } + int handler = 9999; + + for (int j = 0; j < valid_formatCodes.length; j++) { + if (valid_formatCodes[j].equals(fc[i])) { + handler = j; + break; + } + } + + if (handler == 9999) { + if (fieldHandlers == null || !fieldHandlers.containsKey(fc[i])) { + throw new IllegalArgumentException("bad format code: \"" + fc[i] + "\""); + } else { + lsd = 100; + handler = 100; + handlers[i] = 100; + offsets[i] = pos; + if (lengths[i] < 1 || pos == -1) { // 0->indetermined as well, allows user to force indeterminate + + pos = -1; + lengths[i] = -1; + } else { + pos += lengths[i]; + } + } + } else { + handlers[i] = handler; + if (lengths[i] == 0) { + lengths[i] = formatCode_lengths[handler]; + } + offsets[i] = pos; + if (lengths[i] < 1 || pos == -1) { + pos = -1; + lengths[i] = -1; + } else { + pos += lengths[i]; + } + } + + if (handler < 100) { + if (precision[handler] > lsd) { + lsd = precision[handler]; + } + } + String dots = "........."; + if (lengths[i] == -1) { + regex.append("(.*)"); + } else { + regex.append("(" + dots.substring(0, lengths[i]) + ")"); + } + regex.append(delim[i]); + + } + + timeWidth = new int[]{0,0,0,0,0,0,0}; + switch (lsd) { + case 0: + timeWidth[0] = 1; + break; + case 1: + timeWidth[1] = 1; + break; + case 2: + timeWidth[2] = 1; + break; + case 3: + timeWidth[3] = 1; + break; + case 4: + timeWidth[4] = 1; + break; + case 5: + timeWidth[5] = 1; + break; + case 6: + timeWidth[6] = 1000000; + break; + case 7: + timeWidth[7] = 1000; + break; + case 100: /* do nothing */ break; + } + + this.delims = delim; + this.regex = regex.toString(); + } + + /** + *

    +     *  %[fieldLength]<1-char code>  or
    +     *  %[fieldLength]{}
    +     *
    +     *  fieldLength=0 --> makes field length indeterminate, deliminator must follow.
    +     *
    +     *  %Y   4-digit year
    +     *  %y    2-digit year
    +     *  %j     3-digit day of year
    +     *  %m   2-digit month
    +     *  %b   3-char month name
    +     *  %d    2-digit day
    +     *  %H    2-digit hour
    +     *  %M    2-digit minute
    +     *  %S     2-digit second
    +     *  %{milli}  3-digit milliseconds
    +     *  
    + * + */ + public static TimeParser create(String formatString) { + return new TimeParser(formatString, null); + } + + /** + * This version allows for extension by specifying an external handler. + * + * %3{fieldName} 2 characters are passed to the handler + * %Y 4-digit year + * %y 2-digit year + * %m month + * %2m 2-digit month + * %d 2-digit day + * %H 2-digit hour + * %M 2-digit minute + * %S 2-digit second + * %{milli} 3-digit milliseconds + */ + public static TimeParser create(String formatString, String fieldName, FieldHandler handler) { + HashMap map = new HashMap(); + map.put(fieldName, handler); + return new TimeParser(formatString, map); + } + + private double toUs2000(CalendarTime d) { + int year = d.year(); + int month = d.month(); + int day = d.day(); + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + int hour = d.hour(); + int minute = d.minute(); + double seconds = d.second() + hour * (float) 3600.0 + minute * (float) 60.0; + int mjd1958 = (jd - 2436205); + double us2000 = (mjd1958 - 15340) * 86400000000. + seconds * 1e6 + d.nanosecond() / 1000; + return us2000; + } + + private double toUs1980(CalendarTime d) { + int year = d.year(); + int month = d.month(); + int day = d.day(); + int jd = 367 * year - 7 * (year + (month + 9) / 12) / 4 - + 3 * ((year + (month - 9) / 7) / 100 + 1) / 4 + + 275 * month / 9 + day + 1721029; + int hour = d.hour(); + int minute = d.minute(); + double seconds = d.second() + hour * (float) 3600.0 + minute * (float) 60.0; + double us1980 = (jd - 2436205 - 8035) * 86400000000. + seconds * 1e6 + d.nanosecond() / 1000; + return us1980; + } + + /** + * reset the seconds register. setDigit( String formatCode, double val ) accumulates + * fractional part in the seconds. + */ + public void resetSeconds() { + time.setSecond(0); + } + + /** + * force the parser to look for delimiters + */ + public void sloppyColumns() { + this.lengths[0]=-1; + for (int i=1; i 8) { + int idx= 5; + while ( idx<8 && st[idx].getClassName().contains("java.util.logging.Logger") ) idx++; + source = String.valueOf(st[idx].getClassName()); + if (source.startsWith("org.das2")) { + source = source.substring("org.das2".length()); + if (source.length() < spaces.length()) { + source = spaces.substring(source.length()) + source; + } + } + } + + long t = System.currentTimeMillis() - t0; + + String threadId= Thread.currentThread().getName(); + threadId = fixedColumn(threadId, 20); + + return nf.format(t) + ":" + fixedColumn(rec.getLoggerName(),20) + ": " + source + ": " + threadId+":" + rec.getLevel().getLocalizedName() + ": " + String.valueOf(message) + "\n"; + } + + public void setResetMessage( String msg ) { + this.resetMessage= msg; + } + + public TimerConsoleFormatter() { + t0 = System.currentTimeMillis(); + nf = new DecimalFormat("00000"); + } + + String spaces= " "; + private String fixedColumn(String threadId, int sp) { + try { + if (threadId.length() > sp) threadId = threadId.substring(threadId.length()-sp, threadId.length()); + if (threadId.length() < sp) threadId = spaces.substring(0, sp - threadId.length()) + threadId; + return threadId; + } catch ( StringIndexOutOfBoundsException ex ) { + return threadId; + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/URLBuddy.java b/dasCore/src/main/java/org/das2/util/URLBuddy.java new file mode 100644 index 000000000..bff3b57c1 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/URLBuddy.java @@ -0,0 +1,141 @@ +/* File: URLBuddy.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on April 7, 2004, 11:34 AM + * by Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util; + +import java.util.*; +import java.util.regex.*; + +/** + * + * @author eew + */ +public class URLBuddy { + + private static final String ALPHA = "[A-Za-z]"; + private static final String DIGIT = "[0-9]"; + private static final String HEX = "[" + DIGIT + "A-F]"; + + public static final Pattern VALID_QUERY_NAME = Pattern.compile( + ALPHA + "[" + ALPHA + DIGIT + "-_:.]*" + ); + public static final Pattern VALID_QUERY_VALUE = Pattern.compile( + "(?:[" + ALPHA + DIGIT + "\\.\\-\\*\\_\\+]|\\%(" + HEX + HEX + "))*" + ); + + /** Creates a new instance of URLBuddy */ + public URLBuddy() { + } + + public static String encodeUTF8(String str) { + try { + return java.net.URLEncoder.encode(str, "UTF-8"); + } + catch (java.io.UnsupportedEncodingException uee) { + //All JVM's are required to support UTF-8 + throw new RuntimeException(uee); + } + } + + public static String decodeUTF8(String str) { + try { + return java.net.URLDecoder.decode(str, "UTF-8"); + } + catch (java.io.UnsupportedEncodingException uee) { + //All JVM's are required to support UTF-8 + throw new RuntimeException(uee); + } + } + + + /** Returns an unmodifiable map representing the query string passed in. + * each key is a name from the string and each value is the url encoded + * value for the key. + *@param str an URLEncoded query string + */ + public static Map parseQueryString(String str) { + HashMap map = new HashMap(); + String[] tokens = str.split("\\&"); + for (int i = 0; i < tokens.length; i++) { + int eqIndex = tokens[i].indexOf('='); + if (eqIndex == -1) { + throwUnexpectedToken(tokens[i], str, "name/value pair"); + } + String name = tokens[i].substring(0, eqIndex); + String value = tokens[i].substring(eqIndex + 1); + if (!validName(name)) { + throwUnexpectedToken(name, str, "valid name"); + } + if (!validValue(value)) { + throwUnexpectedToken(name, str, "url encoded value"); + } + value = decodeUTF8(value); + map.put(name, value); + } + return Collections.unmodifiableMap(map); + } + + private static final void throwUnexpectedToken(String token, String input, String expecting) { + int index = input.indexOf(token); + StringBuffer messageBuffer = new StringBuffer(); + messageBuffer.append("Error parsing query string: Expecting "); + messageBuffer.append(expecting).append(", found '"); + messageBuffer.append(token).append("'\n"); + messageBuffer.append("Input: ").append(input).append('\n'); + messageBuffer.append(" "); + for (int i = 0; i < index; i++) { + messageBuffer.append('.'); + } + messageBuffer.append('^'); + throw new IllegalArgumentException(messageBuffer.toString()); + } + + public static String formatQueryString(Map m) { + StringBuffer query = new StringBuffer(); + for (Iterator i = m.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry)i.next(); + String name = (String)entry.getKey(); + String value = (String)entry.getValue(); + if (!validName(name)) { + throw new IllegalArgumentException("'" + name + "' is not a valid query name."); + } + value = encodeUTF8(value); + query.append(name).append('=').append(value).append('&'); + } + if (query.charAt(query.length() - 1) == '&') { + query.deleteCharAt(query.length() - 1); + } + return query.toString(); + } + + private static boolean validName(String name) { + Matcher m = VALID_QUERY_NAME.matcher(name); + return m.matches(); + } + + private static boolean validValue(String value) { + Matcher m = VALID_QUERY_VALUE.matcher(value); + return m.matches(); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/WeakListenerManager.java b/dasCore/src/main/java/org/das2/util/WeakListenerManager.java new file mode 100644 index 000000000..aeae4e3a0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/WeakListenerManager.java @@ -0,0 +1,114 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.util; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.EventListener; + +/** + * + * @author eew + */ +public class WeakListenerManager { + + public static L weakListener( + Object target, + Class listenerClass, + L listener) + { + String removeMethod = "remove"+listenerClass.getSimpleName(); + return weakListener(target, removeMethod, listenerClass, listener); + } + + public static L weakListener( + Object target, + String removeMethod, + Class listenerClass, + L listener) + { + for (Method m : target.getClass().getMethods()) { + Class[] params = m.getParameterTypes(); + if (m.getName().equals(removeMethod) + && params.length == 1 && params[0] == listenerClass) + { + return weakListener(target, m, listenerClass, listener); + } + } + throw new IllegalArgumentException("No suitable remove method found"); + } + + public static L weakListener( + Object target, + Method removeMethod, + Class listenerClass, + L listener) + { + Handler h = new Handler(target, removeMethod, listener); + Class[] type = { listenerClass }; + Object proxy = Proxy.newProxyInstance(null, type, h); + return listenerClass.cast(proxy); + } + + private static class Handler implements InvocationHandler { + + private final Object target; + private final Method removeMethod; + private final WeakReference listener; + + private Handler(Object target, Method removeMethod, Object listener) { + this.target = target; + this.removeMethod = removeMethod; + this.listener = new WeakReference(listener); + } + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable + { + Object l = listener.get(); + + if (method.getDeclaringClass() == Object.class) { + String name = method.getName(); + if (name.equals("equals")) { + return proxy == args[0]; + } + else if (name.equals("toString")) { + return "Proxy@"+System.identityHashCode(proxy) + +"["+listener.get()+"]"; + } + else if (name.equals("hashCode")) { + return this.hashCode(); + } + else { + return null; + } + } + else if (l == null) { + try { + removeMethod.invoke(target, proxy); + } + catch (IllegalAccessException ex) { + ex.printStackTrace(); + } + catch (IllegalArgumentException ex) { + ex.printStackTrace(); + } + catch (InvocationTargetException ex) { + ex.printStackTrace(); + } + return null; + } + else { + return method.invoke(l, args); + } + } + + } + +} diff --git a/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker.java b/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker.java new file mode 100644 index 000000000..621c899ee --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker.java @@ -0,0 +1,122 @@ +/* + * EventQueueBlocker.java + * + * Created on May 25, 2006, 8:31 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util.awt; + +import org.das2.event.DasUpdateEvent; +import org.das2.system.DasLogger; +import java.awt.AWTEvent; +import java.awt.EventQueue; +import java.awt.Toolkit; +import java.lang.reflect.InvocationTargetException; +import java.util.EmptyStackException; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +/** + * + * @author Jeremy + */ +public final class EventQueueBlocker { + + private static final Logger logger= DasLogger.getLogger(DasLogger.SYSTEM_LOG); + + /** Creates a new instance of EventQueueBlocker */ + private EventQueueBlocker() { + } + + + static class MyEventQueue extends EventQueue { + protected void dispatchEvent(AWTEvent event) { + super.dispatchEvent(event); + if (myEventQueue.peekEvent(DasUpdateEvent.DAS_UPDATE_EVENT_ID) == null) { + synchronized (this) { + pop(); + this.notifyAll(); + } + } + } + /* + protected void pop() throws EmptyStackException { + super.pop(); + } + */ + } + + static MyEventQueue myEventQueue= new MyEventQueue(); + + private static void clearEventQueueImmediately() { + System.err.println( Thread.currentThread().getName() ); + + synchronized (myEventQueue) { + Toolkit.getDefaultToolkit().getSystemEventQueue().push( myEventQueue ); + try { + myEventQueue.wait(); + } catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + + /* + while (myEventQueue.peekEvent(DasUpdateEvent.DAS_UPDATE_EVENT_ID) != null) { + try { + Thread.sleep(40); + } + catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + + myEventQueue.pop(); + */ + logger.fine("no more pending events"); + } + + + private static DasUpdateEvent findUpdateEvent( EventQueue eventQueue ) { + Queue queue= new LinkedList(); + AWTEvent evt=null; + DasUpdateEvent result=null; + while ( eventQueue.peekEvent()!=null ) { + try { + evt= eventQueue.getNextEvent(); + if ( evt instanceof DasUpdateEvent ) { + result= (DasUpdateEvent)evt; + } + } catch (InterruptedException ex) { + } + queue.add(evt); + } + while ( queue.size() > 0 ) { + eventQueue.postEvent((AWTEvent)queue.remove()); + } + return result; + } + + public static synchronized void clearEventQueue() throws InterruptedException { + + if ( SwingUtilities.isEventDispatchThread() ) { + clearEventQueueImmediately(); + } else { + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + clearEventQueueImmediately(); + } + }); + } catch (InvocationTargetException ex) { + throw new RuntimeException(ex); + } + logger.finer("waiting for lockObject to indicate eventQueue is clear"); + logger.finer("event queue task complete"); + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker_1.java b/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker_1.java new file mode 100644 index 000000000..c6ea7a8a0 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/EventQueueBlocker_1.java @@ -0,0 +1,95 @@ +/* + * EventQueueBlocker.java + * + * Created on May 25, 2006, 8:31 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util.awt; + +import org.das2.event.DasUpdateEvent; +import org.das2.system.DasLogger; +import java.awt.AWTEvent; +import java.awt.EventQueue; +import java.awt.Toolkit; +import java.io.PrintStream; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; + +/** + * + * @author Jeremy + */ +public final class EventQueueBlocker_1 { + + private static final Logger logger= DasLogger.getLogger(DasLogger.SYSTEM_LOG); + + private static Object lockObject= new String("EQB_1"); + + /** Creates a new instance of EventQueueBlocker */ + private EventQueueBlocker_1() { + } + + private static Runnable clearEventQueueImmediatelyRunnable= new Runnable() { + public void run() { + clearEventQueueImmediately(); + } + }; + + private static void clearEventQueueImmediately() { + DasUpdateEvent evt; + evt= (DasUpdateEvent) Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent(DasUpdateEvent.DAS_UPDATE_EVENT_ID) ; + if ( evt != null ) { + logger.finer("pending update event: "+evt); + EventQueue.invokeLater( clearEventQueueImmediatelyRunnable ); + } else { + logger.finer("no update events found "); + synchronized(lockObject) { + lockObject.notify(); + } + } + } + + public static void clearEventQueue() throws InterruptedException { + + if ( SwingUtilities.isEventDispatchThread() ) { + throw new IllegalStateException( "must not be called on the EventQueue"); + } + synchronized(lockObject) { + if ( Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent(DasUpdateEvent.DAS_UPDATE_EVENT_ID) != null ) { + EventQueue.invokeLater( clearEventQueueImmediatelyRunnable ); + logger.finer("waiting for lockObject to indicate eventQueue is clear"); + lockObject.wait(); + } else { + logger.finer("no update events found, no runnable submitted "); + } + } + } + + /** + * method for inspecting the event queue. + */ + public static void dumpEventQueue( PrintStream out ) { + + EventQueue eventQueue= Toolkit.getDefaultToolkit().getSystemEventQueue(); + + Queue queue= new LinkedList(); + AWTEvent evt=null; + DasUpdateEvent result=null; + while ( eventQueue.peekEvent()!=null ) { + try { + evt= eventQueue.getNextEvent(); + out.println(evt); + } catch (InterruptedException ex) { + } + queue.add(evt); + } + while ( queue.size() > 0 ) { + eventQueue.postEvent((AWTEvent)queue.remove()); + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/awt/GraphicsOutput.java b/dasCore/src/main/java/org/das2/util/awt/GraphicsOutput.java new file mode 100644 index 000000000..e3736f1b6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/GraphicsOutput.java @@ -0,0 +1,25 @@ +/* + * GraphicsOutput.java + * + * Created on January 28, 2005, 5:06 PM + */ + +package org.das2.util.awt; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author eew + */ +public interface GraphicsOutput { + public Graphics getGraphics(); + public Graphics2D getGraphics2D(); + public void setOutputStream(OutputStream out); + public void setSize(int width, int height); + public void start(); + public void finish() throws IOException; +} diff --git a/dasCore/src/main/java/org/das2/util/awt/LoggingEventQueue.java b/dasCore/src/main/java/org/das2/util/awt/LoggingEventQueue.java new file mode 100644 index 000000000..104b24762 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/LoggingEventQueue.java @@ -0,0 +1,73 @@ +/* + * LoggingEventQueue.java + * + * Created on October 31, 2005, 1:02 PM + * + * + */ + +package org.das2.util.awt; + +import org.das2.system.DasLogger; +import java.awt.AWTEvent; +import java.awt.EventQueue; +import java.awt.event.InvocationEvent; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Logger; + +/** + * Tool for debugging event queue stuff. This can be used to log the event + * queue, or insert breakpoints, etc. + * Toolkit.getDefaultToolkit().getSystemEventQueue().push(new LoggingEventQueue()); + * + * @author Jeremy + * + */ +public class LoggingEventQueue extends EventQueue { + + final static Logger logger= DasLogger.getLogger(DasLogger.GUI_LOG); + + Object lockObject= new String("lockObjectLEQ"); + + private LoggingEventQueue() { + + } + + public void postEvent(java.awt.AWTEvent theEvent) { + if (theEvent instanceof InvocationEvent) { + // logger.info("XXX: "+theEvent); + } else { + // logger.info(""+theEvent); + } + super.postEvent(theEvent); + } + + private static LoggingEventQueue instance; + public static LoggingEventQueue getInstance() { + if ( instance==null ) { + instance= new LoggingEventQueue(); + } + return instance; + } + + public static synchronized void dumpPendingEvents() { + System.err.println("---------------------------------------------------------------"); + Queue queue= new LinkedList(); + AWTEvent evt; + while ( instance.peekEvent()!=null ) { + try { + evt= instance.getNextEvent(); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + System.err.println("[ "+evt); + queue.add(evt); + } + System.err.println("-----e--n--d-----------------------------------------------------"); + while ( queue.size() > 0 ) { + instance.postEvent((AWTEvent)queue.remove()); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/util/awt/PdfGraphicsOutput.java b/dasCore/src/main/java/org/das2/util/awt/PdfGraphicsOutput.java new file mode 100644 index 000000000..b26383b7b --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/PdfGraphicsOutput.java @@ -0,0 +1,76 @@ +/* + * PDFGraphicsOutput.java + * + * Created on January 28, 2005, 5:27 PM + */ + +package org.das2.util.awt; + +import com.lowagie.text.Document; +import com.lowagie.text.DocumentException; +import com.lowagie.text.Rectangle; +import com.lowagie.text.pdf.PdfContentByte; +import com.lowagie.text.pdf.PdfWriter; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.io.IOException; +import java.io.OutputStream; + +/** + * + * @author eew + */ +public class PdfGraphicsOutput implements GraphicsOutput { + + private float width; + private float height; + private OutputStream out; + private Document doc; + private PdfWriter writer; + private PdfContentByte cb; + private Graphics2D graphics; + + + /** Creates a new instance of PDFGraphicsOutput */ + public PdfGraphicsOutput() {} + + public Graphics2D getGraphics2D() { + if (graphics != null) { + return graphics; + } + return (graphics = cb.createGraphics(width, height)); + } + + public void finish() throws IOException { + graphics.dispose(); + cb.restoreState(); + doc.close(); + } + + public Graphics getGraphics() { + return getGraphics2D(); + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setSize(int width, int height) { + this.width = (float)width; + this.height = (float)height; + } + public void start() { + try { + Rectangle rect = new Rectangle(width, height); + doc = new Document(rect, 0f, 0f, 0f, 0f); + writer = PdfWriter.getInstance(doc, out); + doc.open(); + cb = writer.getDirectContent(); + cb.saveState(); + } + catch (DocumentException de) { + throw new RuntimeException(de); + } + } + +} diff --git a/dasCore/src/main/java/org/das2/util/awt/PngGraphicsOutput.java b/dasCore/src/main/java/org/das2/util/awt/PngGraphicsOutput.java new file mode 100644 index 000000000..9c3736c70 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/PngGraphicsOutput.java @@ -0,0 +1,60 @@ +/* + * PngGraphicsOutput.java + * + * Created on January 31, 2005, 5:10 PM + */ + +package org.das2.util.awt; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import javax.imageio.ImageIO; + +/** + * + * @author eew + */ +public class PngGraphicsOutput implements GraphicsOutput { + + private OutputStream out; + private int width; + private int height; + private Graphics2D graphics; + private BufferedImage image; + + /** Creates a new instance of PngGraphicsOutput */ + public PngGraphicsOutput() {} + + public void finish() throws IOException { + graphics.dispose(); + ImageIO.write(image, "PNG", out); + } + + public Graphics getGraphics() { + return getGraphics2D(); + } + + public Graphics2D getGraphics2D() { + if (graphics == null) { + graphics = image.createGraphics(); + } + return graphics; + } + + public void setOutputStream(OutputStream out) { + this.out = out; + } + + public void setSize(int width, int height) { + this.width = width; + this.height = height; + } + + public void start() { + image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/awt/SvgGraphicsOutput.java b/dasCore/src/main/java/org/das2/util/awt/SvgGraphicsOutput.java new file mode 100644 index 000000000..bbfba6942 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/SvgGraphicsOutput.java @@ -0,0 +1,81 @@ +/* + * SVGGraphicsOutput.java + * + * Created on January 28, 2005, 5:13 PM + */ + +package org.das2.util.awt; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.batik.svggen.SVGGraphics2D; +import org.w3c.dom.Document; + +/** + * + * @author eew + */ +public class SvgGraphicsOutput implements GraphicsOutput { + + private Writer writer; + private Document document; + private SVGGraphics2D graphics; + private int width; + private int height; + + /** Creates a new instance of SVGGraphicsOutput */ + public SvgGraphicsOutput() {} + + public Graphics getGraphics() { + return getGraphics2D(); + } + + public Graphics2D getGraphics2D() { + if (graphics != null) { + return graphics; + } + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.newDocument(); + graphics = new SVGGraphics2D(document); + graphics.setSVGCanvasSize(new Dimension(width, height)); + return graphics; + } + catch (ParserConfigurationException pce) { + throw new RuntimeException(pce); + } + } + + public void finish() throws IOException { + graphics.stream(writer, false); + writer.close(); + } + + public void setOutputStream(OutputStream out) { + try { + this.writer = new OutputStreamWriter(out, "UTF-8"); + } + catch (UnsupportedEncodingException uee) { + throw new RuntimeException(uee); + } + } + + public void setSize(int width, int height) { + this.width = width; + this.height = height; + } + + public void start() { + } + +} diff --git a/dasCore/src/main/java/org/das2/util/awt/package.html b/dasCore/src/main/java/org/das2/util/awt/package.html new file mode 100644 index 000000000..56518e64f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/awt/package.html @@ -0,0 +1,7 @@ + + Utility classes for awt functions. GraphicsOutput is an interface that allows + various graphics output libraries (pdf, svg, png) to be used with the same interface. + LoggingEventQueue is used for debugging and posts log messages as events are posted + on the EventQueue. EventQueueBlocker blocks a thread until all the events on the + EventQueue are processed. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/fft/SimpleFFT.java b/dasCore/src/main/java/org/das2/util/fft/SimpleFFT.java new file mode 100644 index 000000000..8e795e122 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/fft/SimpleFFT.java @@ -0,0 +1,90 @@ +/* File: SimpleFFT.java + * Copyright (C) 2002-2003 The University of Iowa + * + * Created on December 22, 2003, 11:29 AM + * by Edward West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util.fft; + +/** + * + * @author Edward West + */ +public final class SimpleFFT { + + private static final double LOG_2 = Math.log(2); + + /** Creates a new instance of SimpleFFT */ + private SimpleFFT() { + } + + public static double[][] fft( double[][] array ) { + double u_r,u_i, w_r,w_i, t_r,t_i; + int ln, nv2, k, l, le, le1, j, ip, i, n; + + n = array.length; + ln = (int)( Math.log( (double)n )/LOG_2 + 0.5 ); + nv2 = n / 2; + j = 1; + for (i = 1; i < n; i++ ) { + if (i < j) { + t_r = array[i - 1][0]; + t_i = array[i - 1][1]; + array[i - 1][0] = array[j - 1][0]; + array[i - 1][1] = array[j - 1][1]; + array[j - 1][0] = t_r; + array[j - 1][1] = t_i; + } + k = nv2; + while (k < j) { + j = j - k; + k = k / 2; + } + j = j + k; + } + + for (l = 1; l <= ln; l++) {/* loops thru stages */ + le = (int)(Math.exp( (double)l * LOG_2 ) + 0.5 ); + le1 = le / 2; + u_r = 1.0; + u_i = 0.0; + w_r = Math.cos( Math.PI / (double)le1 ); + w_i = -Math.sin( Math.PI / (double)le1 ); + for (j = 1; j <= le1; j++) {/* loops thru 1/2 twiddle values per stage */ + for (i = j; i <= n; i += le) {/* loops thru points per 1/2 twiddle */ + ip = i + le1; + t_r = array[ip - 1][0] * u_r - u_i * array[ip - 1][1]; + t_i = array[ip - 1][1] * u_r + u_i * array[ip - 1][0]; + + array[ip - 1][0] = array[i - 1][0] - t_r; + array[ip - 1][1] = array[i - 1][1] - t_i; + + array[i - 1][0] = array[i - 1][0] + t_r; + array[i - 1][1] = array[i - 1][1] + t_i; + } + t_r = u_r * w_r - w_i * u_i; + u_i = w_r * u_i + w_i * u_r; + u_r = t_r; + } + } + return array; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/fft/package.html b/dasCore/src/main/java/org/das2/util/fft/package.html new file mode 100644 index 000000000..d044f9b3f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/fft/package.html @@ -0,0 +1,4 @@ + +Contains a 2^k FFT that was written early on. See +org.das2.math.fft for additional FFT classes. + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/filesystem/AppletHttpProtocol.java b/dasCore/src/main/java/org/das2/util/filesystem/AppletHttpProtocol.java new file mode 100755 index 000000000..f8ddbb56f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/AppletHttpProtocol.java @@ -0,0 +1,71 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util.filesystem; + +import org.das2.util.DasProgressMonitorInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * uses HTTP, and doesn't download resources to cache + * @author jbf + */ +public class AppletHttpProtocol implements WebProtocol { + + public InputStream getInputStream(WebFileObject fo, org.das2.util.monitor.ProgressMonitor mon) throws IOException { + HttpURLConnection connect = (HttpURLConnection) fo.wfs.getURL(fo.pathname).openConnection(); + connect.connect(); + int len = connect.getContentLength(); + DasProgressMonitorInputStream in = new DasProgressMonitorInputStream(connect.getInputStream(), mon); + if (len != -1) + in.setStreamLength(len); + return in; + } + + public Map getMetadata(WebFileObject fo) throws IOException { + String realName = fo.pathname; + boolean exists; + + URL ur = new URL(fo.wfs.root, realName); + HttpURLConnection connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + HttpURLConnection.setFollowRedirects(false); + connect.connect(); + HttpURLConnection.setFollowRedirects(true); + // check for rename, which means we'll do another request + if (connect.getResponseCode() == 303) { + String surl = connect.getHeaderField("Location"); + if (surl.startsWith(fo.wfs.root.toString())) { + realName = surl.substring(fo.wfs.root.toString().length()); + } + connect.disconnect(); + ur = new URL(fo.wfs.root, realName); + connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + connect.connect(); + } + exists = connect.getResponseCode() != 404; + + Map result = new HashMap(); + + Map> fields = connect.getHeaderFields(); + for (String key : fields.keySet()) { + List value = fields.get(key); + result.put(key, value.get(0)); + } + + result.put( META_EXIST, String.valueOf(exists) ); + + connect.disconnect(); + + return result; + + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/DefaultHttpProtocol.java b/dasCore/src/main/java/org/das2/util/filesystem/DefaultHttpProtocol.java new file mode 100644 index 000000000..f501037d3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/DefaultHttpProtocol.java @@ -0,0 +1,87 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util.filesystem; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.das2.util.monitor.ProgressMonitor; + +/** + * This is the old way that uses subclasses of WebFileObject. This should + * go away. + * + * @author jbf + */ +public class DefaultHttpProtocol implements WebProtocol { + + public InputStream getInputStream(WebFileObject fo, ProgressMonitor mon) throws IOException { + if (fo.isFolder) { + throw new IllegalArgumentException("is a folder"); + } + if (!fo.localFile.exists()) { + File partFile = new File(fo.localFile.toString() + ".part"); + fo.wfs.downloadFile(fo.pathname, fo.localFile, partFile, mon); + } + return new FileInputStream(fo.localFile); + } + + public Map getMetadata(WebFileObject fo) throws IOException { + boolean exists; + + String realName = fo.pathname; + if ( realName.length()>0 && realName.charAt(0)=='/' ) { + realName= realName.substring(1); + } + URL ur = new URL(fo.wfs.root, realName); + HttpURLConnection connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + HttpURLConnection.setFollowRedirects(false); + connect.connect(); + HttpURLConnection.setFollowRedirects(true); + // check for rename, which means we'll do another request + if (connect.getResponseCode() == 303) { + String surl = connect.getHeaderField("Location"); + if (surl.startsWith(fo.wfs.root.toString())) { + realName = surl.substring(fo.wfs.root.toString().length()); + } + connect.disconnect(); + ur = new URL(fo.wfs.root, realName); + connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + connect.connect(); + } + exists = connect.getResponseCode() != 404; + + Map result = new HashMap(); + + Map> fields = connect.getHeaderFields(); + for (String key : fields.keySet()) { + List value = fields.get(key); + result.put(key, value.get(0)); + } + + + if ( result.get("Last-Modified")==null ) { + result.put( META_LAST_MODIFIED, new Date( ).toString() ); + } else { + result.put( META_LAST_MODIFIED, new Date( Date.parse( result.get("Last-Modified") ) ).toString() ); + } + + result.put(META_EXIST, String.valueOf(exists)); + + connect.disconnect(); + + return result; + + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/DefaultWebProtocol.java b/dasCore/src/main/java/org/das2/util/filesystem/DefaultWebProtocol.java new file mode 100755 index 000000000..031a666c7 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/DefaultWebProtocol.java @@ -0,0 +1,95 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util.filesystem; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.das2.util.monitor.ProgressMonitor; + +/** + * This is the old way that uses subclasses of WebFileObject. This should + * go away. + * + * @author jbf + */ +public class DefaultWebProtocol implements WebProtocol { + + public InputStream getInputStream(WebFileObject fo, ProgressMonitor mon) throws IOException { + if (fo.isFolder) { + throw new IllegalArgumentException("is a folder"); + } + if (!fo.localFile.exists()) { + File partFile = new File(fo.localFile.toString() + ".part"); + fo.wfs.downloadFile(fo.pathname, fo.localFile, partFile, mon); + } + return new FileInputStream(fo.localFile); + } + + public Map getMetadata(WebFileObject fo) throws IOException { + if (!fo.wfs.getRootURL().getProtocol().equals("ftp")) { + String realName = fo.pathname; + boolean exists; + + URL ur = new URL(fo.wfs.root, realName); + HttpURLConnection connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + HttpURLConnection.setFollowRedirects(false); + connect.connect(); + HttpURLConnection.setFollowRedirects(true); + // check for rename, which means we'll do another request + if (connect.getResponseCode() == 303) { + String surl = connect.getHeaderField("Location"); + if (surl.startsWith(fo.wfs.root.toString())) { + realName = surl.substring(fo.wfs.root.toString().length()); + } + connect.disconnect(); + ur = new URL(fo.wfs.root, realName); + connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + connect.connect(); + } + exists = connect.getResponseCode() != 404; + + Map result = new HashMap(); + + Map> fields = connect.getHeaderFields(); + for (String key : fields.keySet()) { + List value = fields.get(key); + result.put(key, value.get(0)); + } + + result.put(META_EXIST, String.valueOf(exists)); + + connect.disconnect(); + + return result; + + } else { + + Map result = new HashMap(); + + URL url= new URL( fo.wfs.getRootURL(), fo.pathname ); + URLConnection urlc = url.openConnection(); + try { + urlc.connect(); + urlc.getInputStream().close(); + result.put( META_EXIST, "true" ); + + } catch ( IOException ex ) { + result.put( META_EXIST, "false" ); + } + return result; + + } + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FTPFileSystem.java b/dasCore/src/main/java/org/das2/util/filesystem/FTPFileSystem.java new file mode 100644 index 000000000..5a9758873 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FTPFileSystem.java @@ -0,0 +1,153 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * FTPFileSystem.java + * + * Created on August 17, 2005, 3:33 PM + * + */ + +package org.das2.util.filesystem; + +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Jeremy + */ +public class FTPFileSystem extends WebFileSystem { + FTPFileSystem( URL root ) { + super( root, localRoot(root) ); + } + + public boolean isDirectory(String filename) { + return filename.endsWith("/"); + } + + private String[] parseLsl( String dir, File listing ) throws IOException { + InputStream in= new FileInputStream( listing ); + + BufferedReader reader= new BufferedReader( new InputStreamReader( in ) ); + + String aline= reader.readLine(); + + boolean done= aline==null; + + String types="d-"; + + long bytesRead= 0; + long totalSize; + long sumSize=0; + + List result= new ArrayList( 20 ); + while ( ! done ) { + + bytesRead= bytesRead+ aline.length() + 1; + + aline= aline.trim(); + + if ( aline.length() == 0 ) { + done=true; + } else { + + char type= aline.charAt(0); + if ( type == 't' ) { + if ( aline.indexOf( "total" ) == 0 ) { + //totalSize= Long.parseLong( aline.substring( 5 ).trim() ); + } + } + + if ( types.indexOf(type)!=-1 ) { + int i= aline.lastIndexOf( ' ' ); + String name= aline.substring( i+1 ); + //long size= Long.parseLong( aline.substring( 31, 31+12 ) ); // tested on: linux + boolean isFolder= type=='d'; + + result.add( name + ( isFolder ? "/" : "" ) ); + + //sumSize= sumSize + size; + + } + + aline= reader.readLine(); + done= aline==null; + + } // while + + } + return (String[])result.toArray(new String[result.size()]); + } + + public String[] listDirectory(String directory) { + directory= toCanonicalFolderName( directory ); + + try { + new File( localRoot, directory ).mkdirs(); + File listing= new File( localRoot, directory + ".listing" ); + if ( !listing.canRead() ) { + File partFile= listing; + downloadFile( directory, listing, partFile, new NullProgressMonitor() ); + } + listing.deleteOnExit(); + return parseLsl( directory, listing ); + } catch ( IOException e ) { + throw new RuntimeException(e); + } + } + + protected void downloadFile(String filename, java.io.File targetFile, File partFile, ProgressMonitor monitor ) throws java.io.IOException { + FileOutputStream out=null; + InputStream is= null; + try { + filename= toCanonicalFilename( filename ); + URL url= new URL( root + filename.substring(1) ); + + URLConnection urlc = url.openConnection(); + + int i= urlc.getContentLength(); + monitor.setTaskSize( i ); + out= new FileOutputStream( partFile ); + is = urlc.getInputStream(); // To download + monitor.started(); + copyStream(is, out, monitor ); + monitor.finished(); + out.close(); + is.close(); + partFile.renameTo( targetFile ); + } catch ( IOException e ) { + if ( out!=null ) out.close(); + if ( is!=null ) is.close(); + partFile.delete(); + throw e; + } + + } + +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FileObject.java b/dasCore/src/main/java/org/das2/util/filesystem/FileObject.java new file mode 100755 index 000000000..d06a24c2d --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FileObject.java @@ -0,0 +1,196 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * WebFile.java + * + * Created on May 14, 2004, 10:06 AM + */ + +package org.das2.util.filesystem; + +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import java.io.*; +import java.nio.channels.*; + +/** + *

    Class for describing and accessing files in file systems. This + * is similar to java.io.File, except that it can describe files on remote + * file systems like an ftp site.

    + * + *

    Note: this is modeled after the NetBeans fileSystem FileObject, with the + * thought that we might use it later.

    + * + *

    TODO: investigate using javax.tools.FileObject. + * + * @author Jeremy + */ +public abstract class FileObject { + + /** + * returns true if the file can be read by the client. + * @return true if the file can be read (see getInputStream) + */ + public abstract boolean canRead(); + + /** + * returns objects within a folder. + * @return an array of all FileObjects with the folder. + */ + public abstract FileObject[] getChildren() throws IOException; + + /** + * opens an inputStream, perhaps transferring the file to a + * cache first. + * @param monitor for monitoring the download. The monitor won't be used when the access + * is immediate, for example with local FileObjects. + * @throws FileNotFoundException if the file doesn't exist. + * @throws IOException + * @return an InputStream + */ + public abstract InputStream getInputStream( ProgressMonitor monitor ) throws FileNotFoundException, IOException; + + /** + * opens an inputStream, perhaps transferring the file to a + * cache first. Note no monitor is used but this may block at sub-interactive + * time until the file is downloaded. + * + * @throws FileNotFoundException if the file doesn't exist. + * @throws IOException + * @return an InputStream + */ + public InputStream getInputStream() throws FileNotFoundException, IOException { + return getInputStream( new NullProgressMonitor() ); + } + + /** + * opens a Channel, perhaps transferring the file to a local cache first. monitor + * is used to monitor the download. + * @param monitor for monitoring the download. The monitor won't be used when the access + * is immediate, for example with local FileObjects. + * @throws FileNotFoundException if the file doesn't exist. + * @throws IOException + * @return a java.nio.channels.Channel for fast IO reads. + */ + public abstract ReadableByteChannel getChannel( ProgressMonitor monitor ) throws FileNotFoundException, IOException; + + /** + * opens a Channel, but without a monitor. Note this may block at sub-interactive time + * if the FileObject needs to be downloaded before access. + * @throws FileNotFoundException if the file doesn't exist. + * @throws IOException + * @return a java.nio.channels.Channel for fast IO reads. + */ + public ReadableByteChannel getChannel() throws FileNotFoundException, IOException { + return getChannel( new NullProgressMonitor() ); + } + + /** + * gets a File object that can be opened by the client. This may download a remote file, so + * a progress monitor can be used to monitor the download. + * @return a reference to a File that can be opened. + * @param monitor for monitoring the download. The monitor won't be used when the access + * is immediate, for example with local FileObjects. + * @throws java.io.FileNotFoundException if the file doesn't exist. + * @throws IOException if the file cannot be made local + * @throws NullPointerException if the monitor is null. + */ + public abstract File getFile( ProgressMonitor monitor ) throws FileNotFoundException, IOException; + + /** + * gets a File object that can be opened by the client. Note this may block at sub-interactive time + * if the remote file needs to be downloaded before access. + * @return a reference to a File that can be opened. + * @throws java.io.FileNotFoundException if the file doesn't exist. + */ + public File getFile() throws FileNotFoundException, IOException { + return getFile( new NullProgressMonitor() ); + } + + /** + * returns the parent FileObject (a folder). + * If the fileObject is root, then null should be returned. + * @return the parent folder of this object. + */ + public abstract FileObject getParent(); + + /** + * returns the size of the file. + * @return the size in bytes of the file, and -1 if the size is unknown. + */ + public abstract long getSize(); + + /** + * returns true if the file is a data file that to be used + * reading or writing data. (And not a folder.) + * @return true if the file is a data file + */ + public abstract boolean isData(); + + /** + * indicates the type of FileObject + * @return true if the object is a folder (directory). + */ + public abstract boolean isFolder(); + + /** + * true is the file is read-only. + * @return true if the file is read-only + */ + public abstract boolean isReadOnly(); + + /** + * returns true if this is the root of the filesystem it came from. + * @return true if this is the root of the filesystem it came from. + */ + public abstract boolean isRoot(); + + /** + * returns true if the file is locally available, meaning clients can + * call getFile() and the readble File reference will be available in + * interactive time. Note that isLocal does not imply exists(). Also, + * This may result in side effects such as a website hit. + */ + public abstract boolean isLocal(); + + /** + * returns true if the file exists. This may have the side effect of + * downloading the file. + * @return true if the file exists + */ + public abstract boolean exists(); + + /** + * returns the canonical name of the file within the filesystem. For example, + * in the local filesystem /mnt/data/steven/, /mnt/data/steven/jan/01.dat + * would be /jan/01.dat. + * + * For example, /a/b/c.dat. + * @return the name of the file within the FileSystem. + */ + public abstract String getNameExt(); + + /** + * returns the Date when the file was last modified. or new Date(0L) if the date is + * not available. + * + * @return the last modified Date, or new Date(0) if it is not available. + */ + public abstract java.util.Date lastModified(); + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FileSystem.java b/dasCore/src/main/java/org/das2/util/filesystem/FileSystem.java new file mode 100755 index 000000000..6466ffeda --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FileSystem.java @@ -0,0 +1,304 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * FileSystem.java + * + * Created on May 14, 2004, 12:43 PM + */ + +package org.das2.util.filesystem; +import java.io.*; +import java.net.*; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.das2.util.monitor.NullProgressMonitor; +import org.das2.util.monitor.ProgressMonitor; + +/** + * Filesystems provide an abstraction layer so that clients can access + * any heirarchy of files in a implementation-independent way. For example, + * remote filesystems accessible via http are accessible through the same + * interface as a local filesystem. + * + * @author Jeremy + */ + + +public abstract class FileSystem { + + URL root; + protected static Logger logger= Logger.getLogger( "das2.filesystem" ); + + public static class FileSystemOfflineException extends IOException { + public FileSystemOfflineException() { + super(); + } + public FileSystemOfflineException( String message ) { + super( message ); + } + public FileSystemOfflineException( IOException e ) { + super( e.getMessage() ); + initCause(e); + } + } + + public static FileSystem create(URL root) throws FileSystemOfflineException { + return create(root, new NullProgressMonitor()); + } + /** + * Creates a FileSystem by parsing the URL and creating the correct FS type. + * Presently, file, http, and ftp are supported. If the URL contains a folder + * ending in .zip and a FileSystemFactory is registered as handling .zip, then + * The zip file will be transferred and the zip file mounted. + */ + public static FileSystem create( URL root, ProgressMonitor mon ) throws FileSystemOfflineException { + logger.fine("create filesystem "+root); + FileSystemFactory factory; + if ( root.getPath().contains(".zip") && registry.containsKey("zip") ) { + try { + String surl= root.toString(); + int i= surl.indexOf(".zip"); + String[] ss= FileSystem.splitUrl( surl.substring(0,i+4) ); + URL parent = new URL(ss[2]); //getparent + String zipname = ss[3].substring(ss[2].length()); + String subdir = surl.substring(i+4); + FileSystem remote = FileSystem.create(parent); + File localZipFile = remote.getFileObject(zipname).getFile(mon); + factory = (FileSystemFactory) registry.get("zip"); + FileSystem zipfs = factory.createFileSystem(localZipFile.toURI().toURL()); + if ( subdir.equals("") || subdir.equals("/") ) { + return zipfs; + } else { + return new SubFileSystem(zipfs, subdir); + } + } catch (IOException ex) { + throw new FileSystemOfflineException(ex); + } + } else { + factory= (FileSystemFactory) registry.get(root.getProtocol()); + } + if ( factory==null ) { + throw new IllegalArgumentException( "unsupported protocol: "+root ); + } else { + return factory.createFileSystem(root); + } + } + + public static FileSystemSettings settings() { + return settings; + } + + private static FileSystemSettings settings= new FileSystemSettings(); + + static HashMap registry; + static { + registry= new HashMap(); + registry.put("file",new LocalFileSystemFactory() ); + registry.put("http",new HttpFileSystemFactory() ); + registry.put("https",new HttpFileSystemFactory() ); + registry.put("ftp",new FtpFileSystemFactory() ); + } + + public static void registerFileSystemFactory( String proto, FileSystemFactory factory ) { + registry.put( proto, factory ); + } + + protected FileSystem( URL root ) { + if ( !root.toString().endsWith("/" ) ) { + String s= root.toString(); + try { + root= new URL( s+"/" ); + } catch ( MalformedURLException e ) { + throw new RuntimeException(e); + } + } + this.root= root; + } + + public URL getRootURL() { + return root; + } + + private static String getRegexFromGlob( String glob ) { + final String regex= glob.replaceAll("\\.","\\\\.").replaceAll("\\*","\\.\\*").replaceAll("\\?","\\."); + return regex; + } + + + /** + * returns the canonical name /a/b/c.dat of a string that + * contains backslashes and might not have the leading / + * and trailing slashes. Also, double slashes (//) are + * removed. Note this is the name of the FileObject + * within the FileSystem. + */ + protected static String toCanonicalFilename( String filename ) { + filename= filename.replaceAll( "\\\\", "/" ); + if ( filename.length()==0 || filename.charAt(0)!='/' ) { + filename= "/"+filename; + } + filename= filename.replaceAll( "//", "/" ); + return filename; + } + + protected static String toCanonicalFolderName( String name ) { + name= toCanonicalFilename( name ); + if ( !name.endsWith("/") ) name= name + "/"; + return name; + } + + /** + * return the FileObject that corresponds to the name. + */ + abstract public FileObject getFileObject( String filename ); + + abstract public boolean isDirectory( String filename ) throws IOException; + + /** + * returns a list of the names of the files in a directory. Names ending + * in "/" are themselves directories, and the "/" is not part of the name. + * This is optional, and a directory may or may not be tagged with the trailing + * slash. + */ + abstract public String[] listDirectory( String directory ) throws IOException; + + /** + * returns a list of the names of the files in a directory that match regex. + * Trailing slashes on directory names are not part of the name and need + * not be part of the regex. + */ + abstract public String[] listDirectory( String directory, String regex ) throws IOException; + + /** + * Boolean.TRUE if the filesystem ignores case, such as Windows local filesystem. + */ + public static final String PROP_CASE_INSENSITIVE= "caseInsensitive"; + + protected HashMap properties= new HashMap(5); + + public Object getProperty( String name ) { + return properties.get(name); + } + + /** + * return the folder that is a local copy of the filesystem. + * For LocalFilesystem, this is the same as the filesystem. For remote + * filesystems, this is a folder within their home directory. + * Note File.getAbsolutePath() returns the string representation of this root. + * @return the folder that is a local copy of the filesystem. + */ + abstract public File getLocalRoot(); + + /** + * create a new filesystem that is a part of this filesystem, rooted at + * directory. + */ + public FileSystem createFileSystem( String directory ) { + try { + return new SubFileSystem( this, toCanonicalFolderName(directory) ); + } catch ( MalformedURLException e ) { + throw new IllegalArgumentException("invalid directory: "+directory); + } + } + + /** + * returns a String[5]: + * [0] is proto "http://" + * [1] will be the host + * [2] is proto + path + * [3] is proto + path + file + * [4] is file ext + * [5] is params, not including ?. + * @param surl an url string to parse. + */ + public static String[] splitUrl( String surl ) { + + if ( !( surl.startsWith("file:/") || surl.startsWith("ftp://") || surl.startsWith("http://") || surl.startsWith("https://") ) ) { + surl= "file://"+ ( ( surl.charAt(0)=='/' ) ? surl : ( '/' + surl ) ); // Windows c: + } + + int i; + + String params=null; + + int fileEnd; + // check for just one ? + i= surl.indexOf( "?" ); + if ( i != -1 ) { + fileEnd= i; + params= surl.substring(i+1); + i= surl.indexOf("?",i+1); + if ( i!=-1 ) { + throw new IllegalArgumentException("too many ??'s!"); + } + } else { + fileEnd= surl.length(); + } + + i= surl.lastIndexOf("/"); + String surlDir= surl.substring(0,i); + + String file= surl.substring(i,fileEnd); + i= file.lastIndexOf('.'); + String ext; + if ( i!=-1 ) { + ext= file.substring(i+1); + } else { + ext= ""; + } + + // let i2 be the end if the protocol and the beginning of the file. + int i2= surl.indexOf("://")+3; + if ( surl.indexOf("://")==-1 && surl.startsWith("file:/" ) ) i2=5; + int i3= surl.indexOf("/",i2+1); + if ( i3==-1 ) i3= i2; + String[] result= new String[6]; + result[0]= surl.substring(0,i2); + result[1]= surl.substring(0,i3); + result[2]= surlDir+"/"; + result[3]= surl.substring(0,fileEnd); + result[4]= ext; + result[5]= params; + + return result; + + } + + /** + * DirectoryEntry defines a structure for containing directory entry data. + */ + public class DirectoryEntry { + /** + * the name within the context of the directory. + */ + public String name; + /** + * the type of entry. d=directory, f=file + */ + public char type; + /** + * the length in bytes of the entry + */ + public long size; + /** + * modified date, in seconds since 1970. + */ + public long modified; + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FileSystemFactory.java b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemFactory.java new file mode 100644 index 000000000..0f413c693 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemFactory.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * FileSystemFactory.java + * + * Created on November 15, 2007, 9:26 AM + */ + +package org.das2.util.filesystem; + +import org.das2.util.filesystem.FileSystem.FileSystemOfflineException; +import java.net.URL; + +/** + * creates a new instance of a type of filesystem + * @author jbf + */ +public interface FileSystemFactory { + FileSystem createFileSystem( URL root ) throws FileSystemOfflineException; +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FileSystemSettings.java b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemSettings.java new file mode 100755 index 000000000..7ace8b20e --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemSettings.java @@ -0,0 +1,110 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2.util.filesystem; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import org.das2.DasApplication; + +/** + * controls for file systems. + * @author jbf + */ +public class FileSystemSettings { + + protected FileSystemSettings() { + File local; + if ( !DasApplication.hasAllPermission() ) { + local= new File("applet_mode"); // this should not be opened. + } else { + if (System.getProperty("user.name").equals("Web")) { + local = new File("/tmp"); + } else { + local = new File(System.getProperty("user.home")); + } + local = new File(local, ".das2/fsCache/wfs/"); + } + localCacheDir = local; + } + + public enum Persistence { + /** + * No persistence. No files are cached locally. + */ + NONE, + /** + * Within a session, files are cached locally. This is the default. + */ + SESSION, + /** + * Files persist until a new version is available on the remote cache. + */ + EXPIRES, + /** + * Files persist indefinately, and the server is only contacted when a file + * is not available locally. + */ + ALWAYS + } + + protected File localCacheDir = null; + /** + * setting for the location of where the local cache is kept. + */ + public static final String PROP_LOCALCACHEDIR = "localCacheDir"; + + public File getLocalCacheDir() { + return localCacheDir; + } + + public void setLocalCacheDir(File localCacheDir) { + File oldLocalCacheDir = this.localCacheDir; + this.localCacheDir = localCacheDir; + propertyChangeSupport.firePropertyChange(PROP_LOCALCACHEDIR, oldLocalCacheDir, localCacheDir); + } + protected Persistence persistence = Persistence.SESSION; + /** + * setting for how long files should be kept and using in the cache. + */ + public static final String PROP_PERSISTENCE = "persistence"; + + public Persistence getPersistence() { + return persistence; + } + + public void setPersistence(Persistence persistence) { + Persistence oldPersistence = this.persistence; + this.persistence = persistence; + propertyChangeSupport.firePropertyChange(PROP_PERSISTENCE, oldPersistence, persistence); + } + protected boolean allowOffline = false; + /** + * allow use of persistent, cached files when the file system is not accessible. + * FileSystem implementations will throw FileNotFound exception when remote + * resources are not available, and FileSystemOfflineExceptions are not + * thrown. + */ + public static final String PROP_ALLOWOFFLINE = "allowOffline"; + + public boolean isAllowOffline() { + return allowOffline; + } + + public void setAllowOffline(boolean allowOffline) { + boolean oldAllowOffline = allowOffline; + this.allowOffline = allowOffline; + propertyChangeSupport.firePropertyChange(PROP_ALLOWOFFLINE, oldAllowOffline, allowOffline); + } + private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FileSystemUtil.java b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemUtil.java new file mode 100644 index 000000000..4502dd6ea --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FileSystemUtil.java @@ -0,0 +1,56 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.util.filesystem; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channel; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +/** + * + * @author jbf + */ +public class FileSystemUtil { + + /** + * Dump the contents of the inputstream into a file. If the inputStream comes + * from a file, then java.nio is used to transfer the data quickly. + * @param in + * @param f + * @throws java.io.FileNotFoundException + * @throws java.io.IOException + */ + public static void dumpToFile( InputStream in, File f ) throws FileNotFoundException, IOException { + ReadableByteChannel ic = Channels.newChannel(in); + FileChannel oc = new FileOutputStream(f).getChannel(); + if ( ic instanceof FileChannel ) { + FileChannel fic= (FileChannel)ic; + fic.transferTo(0, fic.size(), oc); + fic.close(); + oc.close(); + } else { + ByteBuffer buf= ByteBuffer.allocateDirect( 16*1024 ); + while ( ic.read(buf) >= 0 || buf.position() != 0 ) { + buf.flip(); + oc.write(buf); + buf.compact(); + } + } + } + + public static void main( String[] args ) throws Exception { + InputStream in= new ByteArrayInputStream( "Hello there".getBytes() ); + dumpToFile( in, new File( "/home/jbf/text.txt" ) ); + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/FtpFileSystemFactory.java b/dasCore/src/main/java/org/das2/util/filesystem/FtpFileSystemFactory.java new file mode 100644 index 000000000..e31842b1f --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/FtpFileSystemFactory.java @@ -0,0 +1,42 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * FtpFileSystemFactory.java + * + * Created on November 15, 2007, 9:31 AM + */ + +package org.das2.util.filesystem; + +import java.net.URL; + +/** + * + * @author jbf + */ +public class FtpFileSystemFactory implements FileSystemFactory { + + /** Creates a new instance of FtpFileSystemFactory */ + public FtpFileSystemFactory() { + } + + public FileSystem createFileSystem(URL root) throws FileSystem.FileSystemOfflineException { + return FTPFileSystem.create( root ); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/Glob.java b/dasCore/src/main/java/org/das2/util/filesystem/Glob.java new file mode 100644 index 000000000..dc5c71fe6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/Glob.java @@ -0,0 +1,149 @@ +/* File: Glob.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util.filesystem; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import javax.swing.filechooser.FileFilter; + +/** + * known bug: *.java matches ".java". The unix glob behavior is to + * require that a leading . must be explicitly matched. + * @author jbf + */ +public class Glob { + + /** + * + * @param regex + * @return + */ + private static String getParentDirectory( String regex ) { + String[] s= regex.split( "/" ); + String dirRegex; + if ( s.length>1 ) { + dirRegex= s[0]; + for ( int i=1; i .*\.dat + */ + public static Pattern getPattern( String glob ) { + final String regex= getRegex( glob ); + final Pattern absPattern= Pattern.compile(regex); + return absPattern; + } + + /** + * converts a glob into a regex. + */ + public static String getRegex( String glob ) { + return glob.replaceAll("\\.","\\\\.").replaceAll("\\*","\\.\\*").replaceAll("\\?","\\."); + } + + /** + * unglob the glob into an array of the matching FileObjects. + * @param glob + * @return an array of FileObjects that match the glob. + */ + public static FileObject[] unGlob( FileSystem fs, String glob ) throws IOException { + return unGlob( fs, glob, false ); + } + + private static FileObject[] unGlob( FileSystem fs, String glob, final boolean directoriesOnly ) throws IOException { + if ( File.separatorChar=='\\' ) glob= glob.replaceAll( "\\\\", "/" ); + String parentGlob= getParentDirectory( glob ); + FileObject[] files; + if ( parentGlob!=null ) { + if ( isRoot( parentGlob ) ) { + FileObject rootFile= fs.getFileObject(parentGlob); + if ( rootFile.exists() ) { + files= new FileObject[] { rootFile }; + } else { + throw new IllegalArgumentException("root does not exist: "+glob); + } + } else { + files= unGlob( fs, parentGlob, true ); + } + } else { + throw new IllegalArgumentException("absolute files only"); + } + + final String regex= getRegex( glob ); + final Pattern absPattern= Pattern.compile(regex); + List list= new ArrayList(); + for ( int i=0; i utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * HtmlUtil.java + * + * Created on May 14, 2004, 9:06 AM + */ + +package org.das2.util.filesystem; + +import org.das2.util.Base64; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author Jeremy + */ +public class HtmlUtil { + + public static boolean isDirectory( URL url ) { + String file= url.getFile(); + return file.charAt(file.length()-1) != '/'; + } + + public static URL[] getDirectoryListing( URL url ) throws IOException { + FileSystem.logger.finer("listing "+url); + + String file= url.getFile(); + if ( file.charAt(file.length()-1)!='/' ) { + url= new URL( url.toString()+'/' ); + } + + ArrayList urlList= new ArrayList(); + + long t0= System.currentTimeMillis(); + URLConnection urlConnection = url.openConnection(); + urlConnection.setAllowUserInteraction(false); + + int contentLength=10000; + + //System.err.println("connected in "+( System.currentTimeMillis() - t0 )+" millis" ); + if ( url.getUserInfo()!=null ) { + String encode= new String( Base64.encodeBytes(url.getUserInfo().getBytes()) ); + urlConnection.setRequestProperty("Authorization", "Basic " + encode); + } + InputStream urlStream = urlConnection.getInputStream(); + + // search the input stream for links + // first, read in the entire URL + byte b[] = new byte[10000]; + int numRead = urlStream.read(b); + StringBuffer contentBuffer = new StringBuffer( contentLength ); + contentBuffer.append( new String( b, 0, numRead ) ); + while (numRead != -1) { + FileSystem.logger.finest("download listing"); + numRead = urlStream.read(b); + if (numRead != -1) { + String newContent = new String(b, 0, numRead); + contentBuffer.append( newContent ); + } + } + urlStream.close(); + + // System.err.println("read listing data in "+( System.currentTimeMillis() - t0 )+" millis" ); + String content= contentBuffer.toString(); + + String hrefRegex= "(?i)href\\s*=\\s*([\"'])(.+?)\\1"; + Pattern hrefPattern= Pattern.compile( hrefRegex ); + + Matcher matcher= hrefPattern.matcher( content ); + + while ( matcher.find() ) { + FileSystem.logger.finest("parse listing"); + String strLink= matcher.group(2); + URL urlLink= null; + + try { + urlLink = new URL(url, strLink); + strLink = urlLink.toString(); + } catch (MalformedURLException e) { + System.err.println("bad URL: "+url+" "+strLink); + continue; + } + + if ( urlLink.toString().startsWith(url.toString()) && null==urlLink.getQuery() ) { + urlList.add( urlLink ); + } + } + + return (URL[]) urlList.toArray( new URL[urlList.size()] ); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystem.java b/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystem.java new file mode 100755 index 000000000..5c512ce40 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystem.java @@ -0,0 +1,350 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * WebFileSystem.java + * + * Created on May 13, 2004, 1:22 PM + * + * A WebFileSystem allows web files to be opened just as if they were + * local files, since it manages the transfer of the file to a local + * file system. + */ +package org.das2.util.filesystem; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.das2.DasApplication; +import org.das2.util.Base64; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.filesystem.FileSystem.FileSystemOfflineException; +import java.net.*; +import java.util.*; +import java.util.regex.*; + +/** + * + * @author Jeremy + */ +public class HttpFileSystem extends WebFileSystem { + + private HashMap listings; + private static HashMap instances = new HashMap(); + /** + * Keep track of active downloads. This handles, for example, the case + * where the same file is requested several times by different threads. + */ + private HashMap downloads = new HashMap(); + private String userPass; + + /** Creates a new instance of WebFileSystem */ + private HttpFileSystem(URL root, File localRoot) { + super(root, localRoot); + listings = new HashMap(); + } + + public static synchronized HttpFileSystem createHttpFileSystem( URL root ) throws FileSystemOfflineException { + if (instances.containsKey(root.toString())) { + logger.finer("reusing " + root); + return (HttpFileSystem) instances.get(root.toString()); + } else { + try { + // verify URL is valid and accessible + HttpURLConnection urlc = (HttpURLConnection) root.openConnection(); + urlc.setRequestMethod("HEAD"); + if (root.getUserInfo() != null) { + String encode = Base64.encodeBytes(root.getUserInfo().getBytes()); + // xerces String encode= new String( Base64.encode(root.getUserInfo().getBytes()) ); + urlc.setRequestProperty("Authorization", "Basic " + encode); + } + + boolean offline= true; + urlc.connect(); + if ( urlc.getResponseCode() != HttpURLConnection.HTTP_OK && urlc.getResponseCode()!=HttpURLConnection.HTTP_FORBIDDEN ) { + if ( FileSystem.settings().isAllowOffline() ) { + logger.fine("remote filesystem is offline, allowing access to local cache."); + } else { + throw new FileSystemOfflineException("" + urlc.getResponseCode() + ": " + urlc.getResponseMessage()); + } + } else { + offline= false; + } + + File local; + + if ( DasApplication.hasAllPermission() ) { + local = localRoot(root); + logger.finer("initializing httpfs " + root + " at " + local); + } else { + local= null; + logger.finer("initializing httpfs " + root + " in applet mode" ); + } + HttpFileSystem result = new HttpFileSystem(root, local); + result.offline= offline; + + instances.put(root.toString(), result); + return result; + + } catch (FileSystemOfflineException e) { + throw e; + } catch (IOException e) { + throw new FileSystemOfflineException(e); + } + } + } + + protected void downloadFile(String filename, File f, File partFile, ProgressMonitor monitor) throws IOException { + + logger.fine("downloadFile(" + filename + ")"); + + boolean waitForAnother; + synchronized (downloads) { + ProgressMonitor mon = (ProgressMonitor) downloads.get(filename); + if (mon != null) { // the httpFS is already loading this file, so wait. + + monitor.setProgressMessage("Waiting for file to download"); + while (mon != null) { + while (!mon.isStarted()) { + try { + downloads.wait(100); + } catch (InterruptedException e) { + } + } + monitor.setTaskSize(mon.getTaskSize()); + monitor.started(); + if (monitor.isCancelled()) { + mon.cancel(); + } + monitor.setTaskProgress(mon.getTaskProgress()); + try { + downloads.wait(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + mon = (ProgressMonitor) downloads.get(filename); + logger.finest("waiting for download"); + + } + monitor.finished(); + if (f.exists()) { + return; + } else { + throw new FileNotFoundException("expected to find " + f); + } + } else { + downloads.put(filename, monitor); + waitForAnother = false; + } + } + + try { + logger.fine("downloadFile " + filename); + + URL remoteURL = new URL(root.toString() + filename); + + URLConnection urlc = remoteURL.openConnection(); + if (root.getUserInfo() != null) { + String encode = new String(Base64.encodeBytes(root.getUserInfo().getBytes())); + urlc.setRequestProperty("Authorization", "Basic " + encode); + } + + HttpURLConnection hurlc = (HttpURLConnection) urlc; + if ( hurlc.getResponseCode()==404 ) { + logger.fine("" + hurlc.getResponseCode() + " URL: " + remoteURL); + throw new FileNotFoundException("not found: "+remoteURL ); + } else if (hurlc.getResponseCode() != 200) { + logger.fine("" + hurlc.getResponseCode() + " URL: " + remoteURL); + throw new IOException(hurlc.getResponseMessage()); + } + + monitor.setTaskSize(urlc.getContentLength()); + + if (!f.getParentFile().exists()) { + logger.fine("make dirs " + f.getParentFile()); + f.getParentFile().mkdirs(); + } + if (partFile.exists()) { + logger.fine("clobber file " + f); + if (!partFile.delete()) { + logger.fine("Unable to clobber file " + f + ", better use it for now."); + return; + } + } + + if (partFile.createNewFile()) { + InputStream in; + in = urlc.getInputStream(); + + logger.fine("transferring bytes of " + filename); + monitor.setLabel("downloading file"); + monitor.started(); + + FileOutputStream out = new FileOutputStream(partFile); + + try { + copyStream(in, out, monitor); + monitor.finished(); + out.close(); + in.close(); + partFile.renameTo(f); + } catch (IOException e) { + out.close(); + in.close(); + partFile.delete(); + throw e; + } + } else { + throw new IOException("couldn't create local file: " + f); + } + } finally { + synchronized (downloads) { + downloads.remove(filename); + downloads.notifyAll(); + } + } + } + + /** + * this is introduced to support checking if the symbol foo/bar is a folder by checking + * for a 303 redirect. + * EXIST->Boolean + * REAL_NAME->String + * @param f + * @throws java.io.IOException + */ + protected Map getHeadMeta( String f ) throws IOException { + String realName= f; + boolean exists; + try { + URL ur = new URL(this.root, f); + HttpURLConnection connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + HttpURLConnection.setFollowRedirects(false); + connect.connect(); + HttpURLConnection.setFollowRedirects(true); + // check for rename, which means we'll do another request + if ( connect.getResponseCode()==303 ) { + String surl= connect.getHeaderField("Location"); + if ( surl.startsWith(root.toString()) ) { + realName= surl.substring(root.toString().length()); + } + connect.disconnect(); + ur = new URL(this.root, realName); + connect = (HttpURLConnection) ur.openConnection(); + connect.setRequestMethod("HEAD"); + connect.connect(); + } + exists= connect.getResponseCode()!=404; + + Map result= new HashMap(); + result.putAll( connect.getHeaderFields() ); + connect.disconnect(); + + return result; + + } catch (MalformedURLException ex) { + throw new IllegalArgumentException(ex); + } + + } + + + /** dumb method looks for / in parent directory's listing. Since we have + * to list the parent, then IOException can be thrown. + * + */ + public boolean isDirectory(String filename) throws IOException { + + if ( localRoot==null ) return filename.endsWith("/"); + + File f = new File(localRoot, filename); + if (f.exists()) { + return f.isDirectory(); + } else { + if (filename.endsWith("/")) { + return true; + } else { + File parentFile = f.getParentFile(); + String parent = getLocalName(parentFile); + if (!parent.endsWith("/")) { + parent = parent + "/"; + } + String[] list = listDirectory(parent); + String lookFor; + if (filename.startsWith("/")) { + lookFor = filename.substring(1) + "/"; + } else { + lookFor = filename + "/"; + } + for (int i = 0; i < list.length; i++) { + if (list[i].equals(lookFor)) { + return true; + } + } + return false; + } + } + } + + public String[] listDirectory(String directory) throws IOException { + directory = this.toCanonicalFilename(directory); + if (!isDirectory(directory)) { + throw new IllegalArgumentException("is not a directory: " + directory); + } + + if (!directory.endsWith("/")) { + directory = directory + "/"; + } + synchronized (listings) { + if (listings.containsKey(directory)) { + return (String[]) listings.get(directory); + } else { + + URL[] list = HtmlUtil.getDirectoryListing(getURL(directory)); + String[] result = new String[list.length]; + int n = directory.length(); + for (int i = 0; i < list.length; i++) { + URL url = list[i]; + result[i] = getLocalName(url).substring(n); + } + listings.put(directory, result); + return result; + } + } + } + + public String[] listDirectory(String directory, String regex) throws IOException { + directory = toCanonicalFilename(directory); + if (!isDirectory(directory)) { + throw new IllegalArgumentException("is not a directory: " + directory); + } + + String[] listing = listDirectory(directory); + Pattern pattern = Pattern.compile(regex + "/?"); + ArrayList result = new ArrayList(); + for (int i = 0; i < listing.length; i++) { + if (pattern.matcher(listing[i]).matches()) { + result.add(listing[i]); + } + } + return (String[]) result.toArray(new String[result.size()]); + + } +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystemFactory.java b/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystemFactory.java new file mode 100644 index 000000000..510eb61db --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/HttpFileSystemFactory.java @@ -0,0 +1,47 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * HttpFileSystemFactory.java + * + * Created on November 15, 2007, 9:28 AM + * + */ + +package org.das2.util.filesystem; + +import org.das2.DasApplication; +import org.das2.util.filesystem.FileSystem.FileSystemOfflineException; +import java.net.URL; + +/** + * + * @author jbf + */ +public class HttpFileSystemFactory implements FileSystemFactory { + + /** Creates a new instance of HttpFileSystemFactory */ + public HttpFileSystemFactory() { + } + + public FileSystem createFileSystem(URL root) throws FileSystemOfflineException { + HttpFileSystem hfs= HttpFileSystem.createHttpFileSystem( root ); + if ( ! DasApplication.hasAllPermission() ) hfs.setAppletMode(true); + return hfs; + } + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/LocalFileObject.java b/dasCore/src/main/java/org/das2/util/filesystem/LocalFileObject.java new file mode 100755 index 000000000..69e0d6cf4 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/LocalFileObject.java @@ -0,0 +1,125 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * LocalFileObject.java + * + * Created on May 25, 2004, 6:01 PM + */ + +package org.das2.util.filesystem; + +import org.das2.util.monitor.ProgressMonitor; +import java.io.*; +import org.das2.util.monitor.NullProgressMonitor; + +/** + * + * @author Jeremy + */ +public class LocalFileObject extends FileObject { + + File localFile; + File localRoot; + LocalFileSystem lfs; + + protected LocalFileObject( LocalFileSystem lfs, File localRoot, String filename ) { + this.lfs= lfs; + this.localFile= new File( localRoot, filename ); + this.localRoot= localRoot; + } + + public boolean canRead() { + return localFile.canRead(); + } + + public FileObject[] getChildren() { + File[] files= localFile.listFiles(); + LocalFileObject[] result= new LocalFileObject[files.length]; + for ( int i=0; i utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * FileWebFileSystem.java + * + * Created on May 14, 2004, 1:02 PM + */ + +package org.das2.util.filesystem; + +import org.das2.util.filesystem.FileSystem.FileSystemOfflineException; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.regex.*; + +/** + * + * @author Jeremy + */ +public class LocalFileSystem extends FileSystem { + + File localRoot; + + /** + * Note the String used to create the URL should have either one or three slashes: + * file:/home/jbf or file:///home/jbf but not + * file://home/jbf + * Also, on Windows, /c:/documents and settings/jbf/ is okay. + */ + protected LocalFileSystem(URL root) throws FileSystemOfflineException { + super( root ); + if ( !("file".equals(root.getProtocol()) ) ) { + throw new IllegalArgumentException("protocol not file: "+root); + } + String surl= root.toString(); + if ( !surl.endsWith("/") ) surl+="/"; + String[] split= FileSystem.splitUrl( surl ); + + localRoot=new File( split[2].substring(split[0].length()) ); + if ( !localRoot.exists() ) { + File[] roots= File.listRoots(); + if ( Arrays.asList(roots).contains(localRoot) ) { + throw new FileSystemOfflineException(); + } else { + throw new IllegalArgumentException( "root does not exist: "+root ); + } + } + boolean b= new File("xxx").equals(new File("XXX")); + properties.put( PROP_CASE_INSENSITIVE, Boolean.valueOf( b ) ); + } + + public boolean isDirectory(String filename) { + return new File( localRoot, filename ).isDirectory(); + } + + String getLocalName( File file ) { + if ( !file.toString().startsWith(localRoot.toString() ) ) { + throw new IllegalArgumentException( "file \""+file+"\"is not of this web file system" ); + } + String filename= file.toString().substring(localRoot.toString().length() ); + filename= filename.replaceAll( "\\\\", "/" ); + return filename; + } + + public String[] listDirectory(String directory) { + File f= new File( localRoot, directory ); + File[] files= f.listFiles(); + String[] result= new String[files.length]; + for ( int i=0; i utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * LocalFileSystemFactory.java + * + * Created on November 15, 2007, 9:30 AM + */ + +package org.das2.util.filesystem; + +import java.net.URL; + +/** + * + * @author jbf + */ +public class LocalFileSystemFactory implements FileSystemFactory { + + /** Creates a new instance of LocalFileSystemFactory */ + public LocalFileSystemFactory() { + } + + public FileSystem createFileSystem(URL root) throws FileSystem.FileSystemOfflineException { + return new LocalFileSystem(root); + } + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/SubFileSystem.java b/dasCore/src/main/java/org/das2/util/filesystem/SubFileSystem.java new file mode 100644 index 000000000..199579bc3 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/SubFileSystem.java @@ -0,0 +1,70 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * SubFileSystem.java + * + * Created on January 16, 2007, 2:19 PM + * + * + */ + +package org.das2.util.filesystem; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * + * @author Jeremy + */ +public class SubFileSystem extends FileSystem { + FileSystem parent; + String dir; + + protected SubFileSystem( FileSystem parent, String dir ) throws MalformedURLException { + super( new URL( parent.getRootURL(), dir ) ); + this.parent= parent; + this.dir= dir; + + } + + public FileObject getFileObject(String filename) { + return parent.getFileObject( dir + filename ); + } + + public boolean isDirectory(String filename) throws IOException { + return parent.isDirectory( dir + filename ); + } + + public String[] listDirectory(String directory) throws IOException { + return parent.listDirectory( dir + directory ); + } + + public String[] listDirectory(String directory, String regex) throws IOException { + return parent.listDirectory( dir + directory, regex ); + } + + @Override + public File getLocalRoot() { + return new File( parent.getLocalRoot(), dir ); + } + + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/WebFileObject.java b/dasCore/src/main/java/org/das2/util/filesystem/WebFileObject.java new file mode 100644 index 000000000..262384b97 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/WebFileObject.java @@ -0,0 +1,324 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * WebFile.java + * + * Created on May 14, 2004, 10:06 AM + */ +package org.das2.util.filesystem; + +import java.text.ParseException; +import java.util.logging.Level; +import org.das2.util.monitor.ProgressMonitor; +import org.das2.util.monitor.NullProgressMonitor; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.channels.Channels; +import java.text.DateFormat; +import java.util.*; +import java.util.logging.Logger; + +/** + * + * @author Jeremy + * + * This is a refactoring of the HttpFileObject, generalized for use with FTP and HTTP file objects. Note that + * the HttpFileObject has not been refactored to use this. + * + */ +public class WebFileObject extends FileObject { + + WebFileSystem wfs; + String pathname; + File localFile; + Date modifiedDate; + boolean isRoot; + boolean isFolder; + Map metadata; + + /** + * true if we know if it's a folder or not. + */ + boolean isFolderResolved = false; + + public boolean canRead() { + return true; + } + + synchronized void maybeLoadMetadata() throws IOException { + if ( metadata==null ) { + metadata= wfs.protocol.getMetadata( this ); + } + } + + public FileObject[] getChildren() throws IOException { + if (!isFolder) { + throw new IllegalArgumentException(toString() + "is not a folder"); + } + String[] list = wfs.listDirectory(pathname); + FileObject[] result = new FileObject[list.length]; + for (int i = 0; i < result.length; i++) { + result[i] = new WebFileObject(wfs, list[i], new Date(System.currentTimeMillis())); + } + return result; + } + + public InputStream getInputStream(ProgressMonitor monitor) throws FileNotFoundException, IOException { + if ( wfs.protocol !=null ) { + return wfs.protocol.getInputStream(this, monitor); + } + if (isFolder) { + throw new IllegalArgumentException("is a folder"); + } + if (!localFile.exists()) { + File partFile = new File(localFile.toString() + ".part"); + wfs.downloadFile(pathname, localFile, partFile, monitor); + } + return new FileInputStream(localFile); + } + + /** + * + * @return a WebFileObject referencing the parent directory. + */ + public FileObject getParent() { + return new WebFileObject(wfs, wfs.getLocalName(localFile.getParentFile()), new Date(System.currentTimeMillis())); + } + + public long getSize() { + if (isFolder) { + throw new IllegalArgumentException("is a folder"); + } + return localFile.length(); + } + + public boolean isData() { + return !this.isFolder; + } + + public boolean isFolder() { + if ( this.isFolderResolved ) { + return this.isFolder; + } else { + //TODO: make HttpFileObject that does HEAD requests to properly answer these questions. See HttpFileSystem.getHeadMeta() + throw new RuntimeException("IOException in constructor prevented us from resolving"); + } + } + + public boolean isReadOnly() { + return true; + } + + public boolean isRoot() { + return this.isRoot; + } + + public java.util.Date lastModified() { + if (modifiedDate == null) { + try { + Map meta = wfs.protocol.getMetadata(this); + String stime = meta.get(WebProtocol.META_LAST_MODIFIED); + if (stime == null) { + modifiedDate = new Date(); + } else { + modifiedDate = new Date(Date.parse(stime)); + return modifiedDate; + } + } catch (IOException ex) { + Logger.getLogger(WebFileObject.class.getName()).log(Level.SEVERE, null, ex); + return new Date(); + } + + } + return modifiedDate; + } + + /** + * returns the File that corresponds to the remote file. This may or may + * not exist, depending on whether it's been downloaded yet. + */ + protected File getLocalFile() { + return this.localFile; + } + + public boolean exists() { + if (localFile.exists()) { + return true; + } else { + try { + if ( wfs.protocol!=null ) { + maybeLoadMetadata(); + return "true".equals( metadata.get( WebProtocol.META_EXIST ) ); + } else { + // TODO: use HTTP HEAD, etc + Logger.getLogger("das2.filesystem").fine("This implementation of WebFileObject.exists() is not optimal"); + File partFile = new File(localFile.toString() + ".part"); + wfs.downloadFile(pathname, localFile, partFile, new NullProgressMonitor()); + return localFile.exists(); + } + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + // I'm going to assume that it's because the file was not found. 404's from pw's server end up here + return false; + } + } + } + + protected WebFileObject( WebFileSystem wfs, String pathname, Date modifiedDate ) { + + this.modifiedDate = modifiedDate; + + this.wfs = wfs; + this.pathname = pathname; + this.isFolderResolved = false; + + if ( ! wfs.isAppletMode() ) { + this.localFile = new File(wfs.getLocalRoot(), pathname); + + if ( FileSystem.settings().getPersistence()==FileSystemSettings.Persistence.SESSION ) this.localFile.deleteOnExit(); + + try { + if (!localFile.canRead()) { + if (wfs.isDirectory(pathname)) { + localFile.mkdirs(); + this.isFolder = true; + if ("".equals(pathname)) { + this.isRoot = true; + } + } else { + this.isFolder = false; + } + } else { + this.isFolder = localFile.isDirectory(); + } + this.isFolderResolved= true; + } catch (IOException ex) { + this.isFolderResolved = false; + } + } + } + + public String toString() { + return "[" + wfs + "]" + getNameExt(); + } + + public String getNameExt() { + return pathname; + } + + /** + * return a Channel for the resource. If the resource can be made locally available, a FileChannel is returned. + * @param monitor + * @return + * @throws java.io.FileNotFoundException + * @throws java.io.IOException + */ + public java.nio.channels.ReadableByteChannel getChannel(ProgressMonitor monitor) throws FileNotFoundException, IOException { + InputStream in= getInputStream(monitor); + return Channels.newChannel(in); + } + + public File getFile(ProgressMonitor monitor) throws FileNotFoundException, IOException { + + if ( wfs.isAppletMode() ) throw new SecurityException("getFile cannot be used with applets."); + + boolean download = false; + + if ( monitor==null ) throw new NullPointerException("monitor may not be null"); + Date remoteDate; + if (wfs instanceof HttpFileSystem) { + URL url = wfs.getURL(this.getNameExt()); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("HEAD"); + connection.connect(); + remoteDate = new Date(connection.getLastModified()); + } else { + // this is the old logic + remoteDate = new Date(localFile.lastModified()); + } + + if (localFile.exists()) { + Date localFileLastModified = new Date(localFile.lastModified()); + if (remoteDate.after(localFileLastModified)) { + FileSystem.logger.fine("remote file is newer than local copy of " + this.getNameExt() + ", download."); + download = true; + } + } else { + download = true; + } + + if (download) { + try { + if (!localFile.getParentFile().exists()) { + localFile.getParentFile().mkdirs(); + } + File partFile = new File(localFile.toString() + ".part"); + wfs.downloadFile(pathname, localFile, partFile, monitor); + } catch (FileNotFoundException e) { + // TODO: do something with part file. + throw e; + } + } + + return localFile; + + } + + /** + * returns true is the file is locally available, meaning clients can + * call getFile() and the readble File reference will be available in + * interactive time. For FileObjects from HttpFileSystem, a HEAD request + * is made to ensure that the local file is as new as the website one. + */ + public boolean isLocal() { + if ( wfs.isAppletMode() ) return false; + try { + boolean download = false; + + if (localFile.exists()) { + Date remoteDate; + if (wfs instanceof HttpFileSystem) { + URL url = wfs.getURL(this.getNameExt()); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("HEAD"); + connection.connect(); + remoteDate = new Date(connection.getLastModified()); + } else { + // this is the old logic + remoteDate = new Date(localFile.lastModified()); + } + + Date localFileLastModified = new Date(localFile.lastModified()); + if (remoteDate.after(localFileLastModified)) { + FileSystem.logger.fine("remote file is newer than local copy of " + this.getNameExt() + ", download."); + download = true; + } + + } else { + download = true; + } + + return !download; + + } catch (IOException e) { + return false; + } + } +} \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/filesystem/WebFileSystem.java b/dasCore/src/main/java/org/das2/util/filesystem/WebFileSystem.java new file mode 100644 index 000000000..47cdd38d6 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/WebFileSystem.java @@ -0,0 +1,228 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * WebFileSystem.java + * + * Created on May 13, 2004, 1:22 PM + * + * A WebFileSystem allows web files to be opened just as if they were + * local files, since it manages the transfer of the file to a local + * file system. + */ + +package org.das2.util.filesystem; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import org.das2.util.monitor.ProgressMonitor; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.regex.*; + +/** + * Base class for HTTP and FTP-based filesystems. A local cache is kept of + * the files. + * + * @author Jeremy + */ +public abstract class WebFileSystem extends FileSystem { + + public static File getDownloadDirectory() { + File local; + if (System.getProperty("user.name").equals("Web")) { + local = new File("/tmp"); + } else { + local = new File(System.getProperty("user.home")); + } + local = new File(local, ".das2/fsCache/wfs/"); + + return local; + } + + protected final File localRoot; + + /** + * if true, then don't download to local cache. Instead, provide inputStream + * and getFile throws exception. + */ + private boolean applet; + + /** + * plug-in template for implementation. if non-null, use this. + */ + protected WebProtocol protocol; + + protected boolean offline = true; + + /** + * if true, then the remote filesystem is not accessible, but local cache + * copies may be accessed. See FileSystemSettings.allowOffline + */ + public static final String PROP_OFFLINE = "offline"; + + public boolean isOffline() { + return offline; + } + + public void setOffline(boolean offline) { + boolean oldOffline = offline; + this.offline = offline; + propertyChangeSupport.firePropertyChange(PROP_OFFLINE, oldOffline, offline); + } + + private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + /** Creates a new instance of WebFileSystem */ + protected WebFileSystem(URL root, File localRoot) { + super( root ); + this.localRoot= localRoot; + if ( localRoot==null ) { + if ( root.getProtocol().equals("http") ) { + this.protocol= new AppletHttpProtocol(); + } + } else { + if ( root.getProtocol().equals("http") ) { + this.protocol= new DefaultHttpProtocol(); + } + } + } + + static protected File localRoot( URL root ) { + + File local= FileSystem.settings().getLocalCacheDir(); + + String s= root.getProtocol() + "/"+ root.getHost() + "/" + root.getFile(); + + local= new File( local, s ); + + local.mkdirs(); + return local; + } + + /** + * Transfers the file from the remote store to a local copy f. This should only be + * used within the class and subclasses, clients should use getFileObject( String ).getFile(). + * Subclasses implementing this should download data to partfile, then rename partfile to + * f after the download is complete. + * + * @param partfile the temporary file during download. + */ + protected abstract void downloadFile( String filename, File f, File partfile, ProgressMonitor monitor ) throws IOException; + + /** Get the root of the local file cache + * @deprecated use getLocalRoot().getAbsolutePath() + */ + public String getLocalRootAbsPath(){ return this.localRoot.getAbsolutePath(); } + + public File getLocalRoot() { + return this.localRoot; + } + + abstract public boolean isDirectory( String filename ) throws IOException; + + abstract public String[] listDirectory( String directory ) throws IOException; + + public String[] listDirectory( String directory, String regex ) throws IOException { + String[] names= listDirectory( directory ); + Pattern pattern= Pattern.compile(regex); + ArrayList result= new ArrayList(); + for ( int i=0; i-1 ) { + if ( monitor.isCancelled() ) throw new InterruptedIOException( ); + monitor.setTaskProgress( totalBytesRead ); + out.write( buffer, 0, bytesRead ); + bytesRead= is.read( buffer, 0, 2048 ); + totalBytesRead+= bytesRead; + logger.finest( "transferring data" ); + } + } + + public String toString() { + return "wfs "+root; + } + + public boolean isAppletMode() { + return applet; + } + + public void setAppletMode( boolean applet ) { + this.applet= applet; + } + + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/WebProtocol.java b/dasCore/src/main/java/org/das2/util/filesystem/WebProtocol.java new file mode 100755 index 000000000..a6d020377 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/WebProtocol.java @@ -0,0 +1,31 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.das2.util.filesystem; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * template for web-based protocols to implement FileSystems + * @author jbf + */ +public interface WebProtocol { + public static final String META_EXIST="exist"; + public static final String META_LAST_MODIFIED="Last-Modified"; + + + public InputStream getInputStream( WebFileObject fo, org.das2.util.monitor.ProgressMonitor mon ) throws IOException; + + /** + * returns metadata for the object. For property names, see META_LAST_MODIFIED, META_EXIST, etc. + * @param fo + * @return + * @throws java.io.IOException + */ + public Map getMetadata( WebFileObject fo ) throws IOException; + +} diff --git a/dasCore/src/main/java/org/das2/util/filesystem/package.html b/dasCore/src/main/java/org/das2/util/filesystem/package.html new file mode 100644 index 000000000..630f2115b --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/filesystem/package.html @@ -0,0 +1,18 @@ + +

    The fileSystem package introduces a filesystem abstraction that allows various +types of file systems to be accessed in a convenient and uniform way. (A file +system is a heirarchical set of files.) This allows clients to "mount" an +ftp site and access the files easily and in the same way a local file system +would be. +

    +

    FileStorageModel is a class that models how filenames are constructed within +a filesystem from times when files are used to implement a database. +

    +

    FileSystem defines the interface clients see. Implementations of this include + HTTPFileSystem, FTPFileSystem, and LocalFileSystem. FileObjects provide access +to Files within the FileSystem, and metadata about the file.

    +

    HtmlUtil provides generally +useful methods, such as scraping all the URL references from a stream. GlobUtil +converts os globs like *.dat to regular expressions. +

    + \ No newline at end of file diff --git a/dasCore/src/main/java/org/das2/util/monitor/NullProgressMonitor.java b/dasCore/src/main/java/org/das2/util/monitor/NullProgressMonitor.java new file mode 100644 index 000000000..e6b2c95cf --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/monitor/NullProgressMonitor.java @@ -0,0 +1,111 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * NullProgressMonitor.java + * + * Created on October 23, 2007, 10:11 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.das2.util.monitor; + +/** + * This is a progress monitor to use when we don't care about the progress. + * This replaces ProgressMonitor.NULL, which has the problem that it was + * stateless and there assume that progress monitors have state. + * + * Further, this can act as a base class for other monitor types. + * + * @author jbf + */ +public class NullProgressMonitor implements ProgressMonitor { + + public NullProgressMonitor() { + } + + private long taskSize=-1 ; + + public void setTaskSize(long taskSize) { + this.taskSize= taskSize; + } + + public long getTaskSize( ) { + return taskSize; + } + + public void setProgressMessage( String message ) {} ; + + private long position=0; + + public void setTaskProgress(long position) throws IllegalArgumentException { + this.position= position; + } + + public long getTaskProgress() { + return position; + } + + private boolean started= false; + + public void started() { + this.started= false; + } + + public boolean isStarted() { + return started; + } + + private boolean finished= false; + + public void finished() { + finished= true; + } + + public boolean isFinished() { + return finished; + } + + private boolean cancelled= false; + + public void cancel() { + cancelled= true; + } + + public boolean isCancelled() { + return cancelled; + } + + @Deprecated + public void setAdditionalInfo(String s) { }; + + private String label; + + public void setLabel( String s ) { + this.label= label; + } + + public String getLabel() { + return label; + } + + public String toString() { + return "" + this.position + " of "+ this.taskSize; + } +} diff --git a/dasCore/src/main/java/org/das2/util/monitor/ProgressMonitor.java b/dasCore/src/main/java/org/das2/util/monitor/ProgressMonitor.java new file mode 100755 index 000000000..1c5d8b0ea --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/monitor/ProgressMonitor.java @@ -0,0 +1,179 @@ +/* File: ProgressMonitor.java + * Copyright (C) 2002-2003 The University of Iowa + * Created by: Jeremy Faden + * Jessica Swanner + * Edward E. West + * + * This file is part of the das2 library. + * + * das2 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package org.das2.util.monitor; + +/** ProgressMonitor defines a set of methods that are useful for + * keeping track of the progress of an operation. This interface also allows + * the operation being tracked to be notified if the user wishes to cancel the + * operation. Code using this interface to track progress should call + * {@link #isCancelled()} prior to calling {@link #setTaskProgress(long)}. + * Implementations of this interface should throw an + * IllegalArgumentException when setTaskProgress(int) + * is called after the operation has been cancelled. + *

    + * Code using the ProgressMonitor should call {@link #started()} + * before setTaskProgress(long) is called for the first time. + * setTaskProgress() should not be called after + * cancel() or finished() has been called. Therefore, + * monitored processes should check isCancelled() before setTaskProgress(long) + * is called. An + * implementation may throw an IllegalArgumentException if + * setTaskProgress(int) is called before started() or + * after finished() is called. + * + *

    A client codes receiving a monitor must do one of two things. + * It should either call setTaskSize(long), started(), setTaskProgress(long) zero or more times, then + * finished(); or it should do nothing with the monitor, possibly passing the + * monitor to a subprocess. This is to ensure that it's easy to see that + * the monitor lifecycle is properly performed.

    + * + * TODO: check this, I think it's legal now for a process to ignore cancelled, + * and the monitor should disable the client's ability to cancel in this case. + * + * TODO: what about exceptions and the monitor lifecycle? + * + * @author jbf + */ +public interface ProgressMonitor { + + /** + * Use NULL when you do not need or wish to use a progressMonitor. It simply + * ignores the progress messages. + * @deprecated this should not be used. Instead use new NullProgressMonitor(); + */ + public static final ProgressMonitor NULL= new ProgressMonitor() { + public void setTaskSize(long taskSize) {} ; + public long getTaskSize( ) { return 1; } + public void setTaskProgress(long position) throws IllegalArgumentException {}; + public void setProgressMessage( String message ) {} ; + public long getTaskProgress() { return 0; }; + public void started() { }; + public boolean isStarted() { + return true; + } + public void finished() {}; + public boolean isFinished() { + return false; + } + public void cancel() {}; + public boolean isCancelled() { return false; }; + public void setAdditionalInfo(String s) { }; + public void setLabel( String s ) { }; + public String getLabel() { return ""; } + }; + + public final static long SIZE_INDETERMINATE= -1; + + /** Sets the maximum value for the task progress of this + * ProgressMonitor. + * @param taskSize maximum value for the task progress. A taskSize of -1 indicates the taskSize is indeterminate. + */ + void setTaskSize(long taskSize); + + /** Notifies the ProgressMonitor of a change in the progress + * of the task. + * @param position the current task position + * @throws IllegalArgumentException if {@link #isCancelled()} returns true or, + * possibly if started() has not been called or + * finished() has been called. + */ + void setTaskProgress(long position) throws IllegalArgumentException; + + /** + * Provides additional feedback as to what's going on in the process. + * This message should be set by the service provider, not the client, + * and refer to the implementation of the task. e.g. "Reading file myData.dat" + * @param message the message describing the state of progress. + */ + void setProgressMessage( String message ); + + /** Returns the current progress of the monitored task. + * @return the current progress of the monitored task. + */ + long getTaskProgress(); + + /** + * Set a consise string that describes the task being performed. Monitors + * don't necessarily need to display this label, and this request may be + * ignored. It is only provided so a process can describe the task that + * is going on. This is usually set by the client of the process to indicate + * what service we are waiting for. e.g. "Loading Data" + */ + public void setLabel( String label ); + + /** + * Return the label string displayed. This is primarily to aid in debugging, + * and this method need not return the string set by setLabel. + */ + public String getLabel(); + + long getTaskSize(); + + /** Notifies the ProgressMonitor that the task + * being monitored has started. If the ProgressMonitor + * is in a cancelled state when this method is called, that + * ProgressMonitor should be 'uncancelled'. + */ + void started(); + + /** Notifies the ProgressMonitor that the task + * being monitored has finished. + */ + void finished(); + + /** Notifies the ProgressMonitor that the task + * being monitored should be canceled. After this method is + * called, implementations should return true on + * any subsequent calls to {@link #isCancelled()} and should + * throw an IllegalStateException on any subsequent calls to + * {@link #setTaskProgress(long)}. + */ + void cancel(); + + /** Returns true if the operation being tracked + * should be cancelled. + * @return true if the operation being tracked + * should be cancelled. + */ + boolean isCancelled(); + + /** additional information to be displayed alongside the progress. That + * might be of interest. + * "85 of 100 (50KB/s)" + * @deprecated setProgressMessage should be used by the service provider + * to indicate how the process is being implemented. + */ + public void setAdditionalInfo(String s); + + /** + * true if the process has indicated that it has started. + */ + boolean isStarted(); + + /** + * true if the process has indicated that it is finished + */ + boolean isFinished(); + +} diff --git a/dasCore/src/main/java/org/das2/util/monitor/SubTaskMonitor.java b/dasCore/src/main/java/org/das2/util/monitor/SubTaskMonitor.java new file mode 100644 index 000000000..379de59bb --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/monitor/SubTaskMonitor.java @@ -0,0 +1,123 @@ +/* Copyright (C) 2003-2008 The University of Iowa + * + * This file is part of the Das2 utilities library. + * + * Das2 utilities are free software: you can redistribute and/or modify them + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Das2 utilities are distributed in the hope that they 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 + * as well as the GNU General Public License along with Das2 utilities. If + * not, see . + * + * SubTaskMonitor.java + * + * Created on August 18, 2005, 4:01 PM + */ + +package org.das2.util.monitor; + + +/** + * creates a ProgressMonitor that maps its progress to a parent's progress. + * For example, if a process takes a progress monitor, but is implemented in + * two steps that each take a progress monitor, then two subtask monitors can + * be created to monitor each step, and the client who passed in the monitor + * will see the whole deal as one process. + * + * @author Jeremy + */ +public class SubTaskMonitor implements ProgressMonitor { + + ProgressMonitor parent; + long min, max, progress, size; + String label; + + private SubTaskMonitor( ProgressMonitor parent, long min, long max ) { + this.parent= parent; + this.min= min; + this.max= max; + this.size= -1; + } + + public static SubTaskMonitor create( ProgressMonitor parent, long min, long max ) { + return new SubTaskMonitor( parent, min, max ); + } + + public void cancel() { + parent.cancel(); + } + + private boolean finished= false; + + public void finished() { + this.finished= true; // only to support the bean property + } + + public boolean isFinished() { + return this.finished; + } + + public long getTaskProgress() { + return progress; + } + + public boolean isCancelled() { + return parent.isCancelled(); + } + + @Deprecated + public void setAdditionalInfo(String s) { + // ignore + } + + public void setTaskProgress(long position) throws IllegalArgumentException { + this.progress= position; + if ( size==-1 ) { + parent.setTaskProgress( min ); + } else { + parent.setTaskProgress( min + ( max - min ) * position / size ); + } + } + + public void setTaskSize(long taskSize) { + this.size= taskSize; + } + + public long getTaskSize() { + return this.size; + } + + boolean started= false; + + public void started() { + this.started= true; + if ( parent.isStarted()==false ) parent.started(); + } + + public boolean isStarted() { + return started; + } + + public void setLabel(String label) { + this.label= label; + } + + public String getLabel() { + return label; + } + + public String toString() { + return parent.toString()+">"+label; + } + + public void setProgressMessage(String message) { + //parent.setProgressMessage(message); + } +} diff --git a/dasCore/src/main/java/org/das2/util/package.html b/dasCore/src/main/java/org/das2/util/package.html new file mode 100644 index 000000000..323e029f7 --- /dev/null +++ b/dasCore/src/main/java/org/das2/util/package.html @@ -0,0 +1,14 @@ + +

    Utility classes useful for the implementation of other packages, but +not necessarily part of any one package. Also, ideally no class +in util creates a dependence on another package. A good rule of thumb is that +classes that are useful to more than one package that do not depend on any of +those packages should go here. +

    +

    For example, here are some examples of classes in the package: TimeParser, + a generally useful class for parsing formatted time strings. Another is Crypt, + a class for encrypting passwords with the crypt algorith. Probe breaks the + stated rules, because it utilizes the package graph to plot data. It should find a new + home. +

    + \ No newline at end of file From bb8b53894f0a070d072fee7388d80278d9eb16b0 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Thu, 14 Jul 2016 23:11:59 +0000 Subject: [PATCH 17/46] Moved old dasCore images directory under the maven project svn path=/core/stable/dasCore/; revision=9329 --- .../src/main/resources/images/das2logo-130.png | Bin 0 -> 9885 bytes .../src/main/resources/images/das2logo-64.png | Bin 0 -> 4662 bytes .../src/main/resources/images/dasSplash.gif | Bin 0 -> 49491 bytes .../src/main/resources/images/dasSplash.png | Bin 0 -> 120265 bytes .../main/resources/images/icons/cancel14.png | Bin 0 -> 612 bytes .../resources/images/icons/cancelGrey14.png | Bin 0 -> 488 bytes .../images/icons/contoursRenderer.png | Bin 0 -> 273 bytes .../images/icons/rebin.binAverage.png | Bin 0 -> 711 bytes .../images/icons/rebin.nearestNeighbor.png | Bin 0 -> 267 bytes .../images/icons/rebin.noInterpolate.png | Bin 0 -> 316 bytes .../icons/rebin.noInterpolateNoEnlarge.png | Bin 0 -> 251 bytes .../images/icons/rebinnerBinAverage.png | Bin 0 -> 504 bytes .../images/icons/showDataMouseModule.png | Bin 0 -> 117 bytes .../main/resources/images/toolbar/button.png | Bin 0 -> 1108 bytes .../main/resources/images/toolbar/checkbox.png | Bin 0 -> 1087 bytes .../main/resources/images/toolbar/choice.png | Bin 0 -> 1104 bytes .../resources/images/toolbar/new_column.png | Bin 0 -> 1068 bytes .../main/resources/images/toolbar/new_row.png | Bin 0 -> 1052 bytes .../resources/images/toolbar/radiobutton.png | Bin 0 -> 1105 bytes .../images/toolbar/row_column_select.png | Bin 0 -> 1130 bytes .../main/resources/images/toolbar/select.png | Bin 0 -> 1094 bytes .../src/main/resources/images/toolbar/text.png | Bin 0 -> 1088 bytes .../resources/images/toolbar/textfield.png | Bin 0 -> 1110 bytes .../src/main/resources/images/toolbox/axis.gif | Bin 0 -> 107 bytes .../main/resources/images/toolbox/button.gif | Bin 0 -> 146 bytes .../resources/images/toolbox/buttongroup.gif | Bin 0 -> 146 bytes .../main/resources/images/toolbox/canvas.gif | Bin 0 -> 182 bytes .../main/resources/images/toolbox/checkbox.gif | Bin 0 -> 916 bytes .../main/resources/images/toolbox/checkbox.png | Bin 0 -> 1087 bytes .../main/resources/images/toolbox/choice.gif | Bin 0 -> 145 bytes .../main/resources/images/toolbox/colorbar.png | Bin 0 -> 260 bytes .../resources/images/toolbox/dragpointer.gif | Bin 0 -> 125 bytes .../src/main/resources/images/toolbox/line.gif | Bin 0 -> 879 bytes .../main/resources/images/toolbox/panel.gif | Bin 0 -> 110 bytes .../src/main/resources/images/toolbox/plot.gif | Bin 0 -> 127 bytes .../resources/images/toolbox/radiobutton.gif | Bin 0 -> 123 bytes .../resources/images/toolbox/spectrogram.gif | Bin 0 -> 1176 bytes .../images/toolbox/spectrogram_plot.gif | Bin 0 -> 265 bytes .../src/main/resources/images/toolbox/tab.gif | Bin 0 -> 113 bytes .../main/resources/images/toolbox/taxis.gif | Bin 0 -> 128 bytes .../src/main/resources/images/toolbox/text.gif | Bin 0 -> 107 bytes .../resources/images/toolbox/textfield.gif | Bin 0 -> 138 bytes .../main/resources/images/toolbox/window.gif | Bin 0 -> 130 bytes 43 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 dasCore/src/main/resources/images/das2logo-130.png create mode 100644 dasCore/src/main/resources/images/das2logo-64.png create mode 100644 dasCore/src/main/resources/images/dasSplash.gif create mode 100644 dasCore/src/main/resources/images/dasSplash.png create mode 100755 dasCore/src/main/resources/images/icons/cancel14.png create mode 100755 dasCore/src/main/resources/images/icons/cancelGrey14.png create mode 100755 dasCore/src/main/resources/images/icons/contoursRenderer.png create mode 100644 dasCore/src/main/resources/images/icons/rebin.binAverage.png create mode 100644 dasCore/src/main/resources/images/icons/rebin.nearestNeighbor.png create mode 100644 dasCore/src/main/resources/images/icons/rebin.noInterpolate.png create mode 100644 dasCore/src/main/resources/images/icons/rebin.noInterpolateNoEnlarge.png create mode 100644 dasCore/src/main/resources/images/icons/rebinnerBinAverage.png create mode 100644 dasCore/src/main/resources/images/icons/showDataMouseModule.png create mode 100644 dasCore/src/main/resources/images/toolbar/button.png create mode 100644 dasCore/src/main/resources/images/toolbar/checkbox.png create mode 100644 dasCore/src/main/resources/images/toolbar/choice.png create mode 100644 dasCore/src/main/resources/images/toolbar/new_column.png create mode 100644 dasCore/src/main/resources/images/toolbar/new_row.png create mode 100644 dasCore/src/main/resources/images/toolbar/radiobutton.png create mode 100644 dasCore/src/main/resources/images/toolbar/row_column_select.png create mode 100644 dasCore/src/main/resources/images/toolbar/select.png create mode 100644 dasCore/src/main/resources/images/toolbar/text.png create mode 100644 dasCore/src/main/resources/images/toolbar/textfield.png create mode 100644 dasCore/src/main/resources/images/toolbox/axis.gif create mode 100644 dasCore/src/main/resources/images/toolbox/button.gif create mode 100644 dasCore/src/main/resources/images/toolbox/buttongroup.gif create mode 100644 dasCore/src/main/resources/images/toolbox/canvas.gif create mode 100644 dasCore/src/main/resources/images/toolbox/checkbox.gif create mode 100644 dasCore/src/main/resources/images/toolbox/checkbox.png create mode 100644 dasCore/src/main/resources/images/toolbox/choice.gif create mode 100644 dasCore/src/main/resources/images/toolbox/colorbar.png create mode 100644 dasCore/src/main/resources/images/toolbox/dragpointer.gif create mode 100644 dasCore/src/main/resources/images/toolbox/line.gif create mode 100644 dasCore/src/main/resources/images/toolbox/panel.gif create mode 100644 dasCore/src/main/resources/images/toolbox/plot.gif create mode 100644 dasCore/src/main/resources/images/toolbox/radiobutton.gif create mode 100644 dasCore/src/main/resources/images/toolbox/spectrogram.gif create mode 100644 dasCore/src/main/resources/images/toolbox/spectrogram_plot.gif create mode 100644 dasCore/src/main/resources/images/toolbox/tab.gif create mode 100644 dasCore/src/main/resources/images/toolbox/taxis.gif create mode 100644 dasCore/src/main/resources/images/toolbox/text.gif create mode 100644 dasCore/src/main/resources/images/toolbox/textfield.gif create mode 100644 dasCore/src/main/resources/images/toolbox/window.gif diff --git a/dasCore/src/main/resources/images/das2logo-130.png b/dasCore/src/main/resources/images/das2logo-130.png new file mode 100644 index 0000000000000000000000000000000000000000..010564be38cfd8b615c101327981d07adf2563b5 GIT binary patch literal 9885 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 VFRO^qPuVemvZ97Ul{_;1{{g&Jyv6_k literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/das2logo-64.png b/dasCore/src/main/resources/images/das2logo-64.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfb7ab9f1d1c4f6f47643d6833f5da58c196790 GIT binary patch literal 4662 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 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/dasSplash.gif b/dasCore/src/main/resources/images/dasSplash.gif new file mode 100644 index 0000000000000000000000000000000000000000..4ec74d2ca439fec4d07f91e9ef506fe0b187a60e GIT binary patch literal 49491 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≫3${&3njeds!hr8;(-> zsE*rUnn{yeM@*^6wkiSUl72kXd{rb|G6^d7*I}_rP!5Z zz6bYg2?-Oaw(Izu-UjCqzX1=s>*VG=VjfQ?>2bpd19~9Y^q|ac(@!Wd-AJZbhBMGG zpA$|5iAX`%fQYZg;U+-lJlj&u0a9Uej(AX?QiPLomowX<{X@QtE$A{H(!MYHMn6w_ zrXh<>V!A71JvWdMG}jUlS#zxb5vUa`uhc|Hi|@{d36B!K_R+G+N#1dN1lCm0*FMc3 zoJ+>tt1PJx<<;H45D#n}{pt&yUR0I{IQd>oc|1PuZuTU1!J@jubv{z@ zBt5=fPWWVb3FHoNN6oR$#g4lsP@Q&z=VUEYV&LhX`Q~mQja)DN@`s%lW8>>c6i-O4 zFW>L9lT0g6*h6}L?JWPBXzyrm@gmP|dR__^34!e4WT z&aTXRHE1rD6ju7x{wf)(w3Mr`NViAY+YiUKDfXQ~Q!sj8Q) z5A4fw#*m@y1ekNgf{mITqUd>|y{M0QYd#$nxByYbE-&e|PiEV3CLMK)sXDF+93W>u zju*KQ9c>WYp;Iz*9g||Iq23}!m6kmH6WX3S36E({)T$7H$R!)3qwM+>9&W%*h(`G? zwZm2{z3H(iD*qM82iX_Q;qG91M|qr?;F;xt^O*(w-v$DOQ@KrT{pYp&wkQIJSqc-9 z9W|QvQqq{f5MCFS+8vLX4Oz`do42gh2SS1;F>7_GfY_F7PJkvn&X87Uyjak>RwX>i zcfIlb7!gl#iUd|yh@e__je{fKya>M÷@|Oi;x!F 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 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/dasSplash.png b/dasCore/src/main/resources/images/dasSplash.png new file mode 100644 index 0000000000000000000000000000000000000000..86928d3c48b4d63dae546aa7eb4bb01a61e58b4c GIT binary patch literal 120265 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

    ^Gx`1O*DMf|4fX&L(zWEl4nZtVmg6AsJ_dLRvlT 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 XtN>R!5b4uz00000NkvXXu0mjf8ZUJr literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/icons/rebin.binAverage.png b/dasCore/src/main/resources/images/icons/rebin.binAverage.png new file mode 100644 index 0000000000000000000000000000000000000000..5555f911e957e2b80c6b0345c4903a6cd3ab4f34 GIT binary patch literal 711 zcmV;&0yzDNP)004R=004l4008;_004mK004C`008P>0026d000+nm#LZ50006~ zNkl)*2tmac0I@U>n=>%D@j%SAV_*OXAQlFO(?}YT=vbg3n*|sc zPJuXcfLI=g6@Z$6ntg%HP#_k6!vGLKKm)F$*$x66K=qP9#S%~h#DQ1_;sT(pU<1To z0Ezny009Iv-~p!X47^}TumL?YfNTkfA$maNwjjrRfygu5VE_mq24E2W{|Lm7f%qyA z?+4;!AZGZ_1H`5O8FGLsKmq;#H4vWz;wB*W1Y(*0>_8GAfEfQD04nAL8paBZ0T7mB zV3-EvXETUEZ32bU4Jdmv&@(p}{D3S=AO;8^#{V5a1NfjCK{PisyfhdX{FxXSHUS|6 z!v!F|%mDJPHqfwSAPZ!G@=pc^fB<4)xCYhC4K;uZ8Vo>Jg(`v!xBzv*ZLpt1p%EbS z2}rzTU;qdp#{cC&7r>m(1vN+p8g5by4A+2R_Yuf^4a6Wl4j}2DP!13S1P}{@GLXRx z#4JcOC^FsxF)*;To&jkdsKJ5^kTmxlN&~$J5I`&pDnQ0xsI&h;**~EM{Dc|?N{qrl zEDE)R35tPifB=HH;3pFP2E<>1_#F_xfYOpsZ~cL~1m;~J4G=&q$K@c=0k-iI)C(`6 zo_-F*PoakXf!Ypo4L>y6fqZ}f0x1Vu%#Iw80#I9jLcJsk#X?Bo!3nVpAb`LIfGie; t8SoDpouUkglnpb0AF7!PVHrSx0RZQ1dcrW^iEGXz-`s;n6>bsi|Kap1xkX@RPn?8c?h(A`Tuyjaz)gN zNj?90FHYIK=)=GJ{|}F7C&@e+GB>MlRH#}!O-JP*1G~gTe~DWM4fX**Qa literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/icons/rebin.noInterpolate.png b/dasCore/src/main/resources/images/icons/rebin.noInterpolate.png new file mode 100644 index 0000000000000000000000000000000000000000..b40644c4199fc83a9edd3ee93fd6ca8aee8c8397 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`)Ym%P6qN7l0AZa z85pWm85kOx85n;42huMX7)lKo7+xhXFj!4zU=YurzH;UjpxWb}E{-7;bCMDg68`WX zX6KXn(cjLaR2}S literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/icons/rebin.noInterpolateNoEnlarge.png b/dasCore/src/main/resources/images/icons/rebin.noInterpolateNoEnlarge.png new file mode 100644 index 0000000000000000000000000000000000000000..ce1a7ab7da51be19636ad7290107ddfcf51a5000 GIT binary patch literal 251 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|Ea{HEjtmUzPnffIy#(?lOI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+5i>xdb&7WN`^Q#}*bU1uR3w9pWK}UVMNYGU%xkP_R&2R-7=mgi*Xh zbESYi3r{t6T9c%b(ZAw7n()K}##m)-)%I`d(C z^VPa#;}UJN%(~UEu6p4awy_um6jW?+=k}zV7fs1z`t1iCj z3nt=DmeFVLoYn97DBxK%_Fz(6)P@msA&s}_#7BkAYnq)XpxPcKXFS%385Opo1uxNr zH|WC;9f{WntI3)Df{t`<-!SyR1YSI??)xYaU?=e?(HDOevY|02{=0*3ns8tLgb_Y)=TY#z6*kttkc^bBn(N P&0+9#^>bP0l+XkK5hNn8 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/button.png b/dasCore/src/main/resources/images/toolbar/button.png new file mode 100644 index 0000000000000000000000000000000000000000..94f163e3d6a02b4c5914e28c03bfb96f76417a4d GIT binary patch literal 1108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3Jauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2Z|i@ba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3Jnqe9$sJ&aM0n2NR{}{z#_ms;egsld4-08TZ{q@ z0-S1U29r5GITRWWs9fG(pCEP9s)2z?`!BmF^DD-etgqQQ6vRTB{~t7EViE8VO5qcp zrOCs1@|u6{1-oD!M<0f};= literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/checkbox.png b/dasCore/src/main/resources/images/toolbar/checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..27c9aa4eb95c7d4c336f03744a7416384d2bd88f GIT binary patch literal 1087 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3Jauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2a2rmba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3JnEb3@ifNEx+0qiYYWK5b`kUsXvg!z<7k|`C-Zb z2Spqh7T#uj&deep%vZ?Pz~Gc**m&e&86%Lts;WgAZsx)l4&Qhk7z`f@qyr5Xdvl0~ zhk=o0azcuWC`e!h%jNn1Gk{heX>vQv2y_BZSp&l%O-2C+3snXtj^!*44U7zXxAJ(% SmUq1YMVhCppUXO@geCwU)M~5% literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/choice.png b/dasCore/src/main/resources/images/toolbar/choice.png new file mode 100644 index 0000000000000000000000000000000000000000..ad1d71642418f98455bf0e0ba1b96b1d718e73d5 GIT binary patch literal 1104 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3Jauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2a4?Vba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3Jnqe9$sJ&aM0n2NR{}{z#_ms;egsld4-08TZ{q@ z37l$bENzSp3`vZg|C1Av8j=K9S^i5hFmbs2J)9=zz+l)|YM^NOR-T2KMZm?dYhHbV zB~WoA*Z=05Oji$s%=Oq6_=iit!QwB_I31pz8#U(+fP{VO|0Pcax9NUmdJc+ep2c>UWKVAf|~@O1TaS?83{1OT18aghK3 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/new_column.png b/dasCore/src/main/resources/images/toolbar/new_column.png new file mode 100644 index 0000000000000000000000000000000000000000..f88d6812be1338f0420113f49d952aa62f3bf417 GIT binary patch literal 1068 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3E{-7;bCOch5`G*%aHN6fKurUK zlc*CD17k}tlR`s*7XyocA7=x@Ax%aB2MbjOrViF_l^Og34hoO{*N1Q#$OF|#u?jfo z2$;?g0%>A*U~nukSagd~iGhh@IbQ=q(!l`!r_3w@9>@3BuMkSGb6|LA!iCLbj^!*s hxhV`B3Uh!au`#SS<}rEfV9p4NCr?*Dmvv4FO#nahSFZp7 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/new_row.png b/dasCore/src/main/resources/images/toolbar/new_row.png new file mode 100644 index 0000000000000000000000000000000000000000..8cce202ab83721b6f226a168bf06a01e0b38970d GIT binary patch literal 1052 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3bloL$+SJauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2a4?Tba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3JnEb3@ifNO+WcvWgQqcDzyA}%vizUz;ICI-{0f} z7H{LfjU0?D0*5r*)K(MB9D z?s5w_WXQQNG%z%IGL-&f-zoXdv4O$qljRY41I7l1B*%=u%}GWM42m}!kFYQ`FdTXr k10*yV1sp6?Atp93G8C>5_kVN6PahPGp00i_>zopr0AvPo!vFvP literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/row_column_select.png b/dasCore/src/main/resources/images/toolbar/row_column_select.png new file mode 100644 index 0000000000000000000000000000000000000000..064db81b4b0b03cff771f399c69074ef1c2499cb GIT binary patch literal 1130 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3*2Ga=M=Fn0>*~{|zh-42DiR?3oEE zF56@j8fFO03iM^>P*C9vk!)aan&vQ(kwZa*GX=z%>M#k&G2v|CabVcEfTLOFkNl%7 zp!lSOKl~FL8GyDq1@^KqFmbG7ev-d{wICBDBIM4&cq6CB$Z^mqjvd^{d$ zFeo%U5EAfgDP{y(znay7LBK)dpTjjK0f!fIEzAuJhdwBr2>@C=)nOC2LPLQRt3U!n X2EUBS#OY_1LDA>w>gTe~DWM4f|D$FH literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/text.png b/dasCore/src/main/resources/images/toolbar/text.png new file mode 100644 index 0000000000000000000000000000000000000000..44ae25d477a43c91caf784a706e633addfada1af GIT binary patch literal 1088 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3T|NswPKFcVxq#S6?%3>!RM978JRB&8%B;A3Fom~O%B!0>Pi z1Bb#KAqK{lU?zoz0xt#@0YA=m8a70rGqjL>)|lS*m@-U;bxC zKy@BXf*k*ijg0=KGbkxApGZvrnj|LD+-TGjVCTSaP*s8H|3f1N#v?4D=QuVt3#Ld# sm^&~SJ}MFe8dw4{6_3>xstim#32XBKE(ArNr>mdKI;Vst0Q?AFj{pDw literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbar/textfield.png b/dasCore/src/main/resources/images/toolbar/textfield.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca3a5dfbd37ad30fc782a6180d0497e0fe0e7e2 GIT binary patch literal 1110 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAv7|ftIx;Y9?5p`5I2p)SNcITw zWnidMWngG%W?=aFA4tDoU???UV0e|lz+g3Jauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2Z|i_ba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3JnEb3@iet?Pa8Z@~w=kc^=C*FdRx0Q3fh(RGHD( zA$f*9SU}Bz!Lh{P|KHO+3l1-^7im(waX?`^O+t1YG^RqG`BjI-Bk`4v}225SIp C!Yd^J literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/button.gif b/dasCore/src/main/resources/images/toolbox/button.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d8b3f6fb524eb2abc301d9363d24dab0f6b2906 GIT binary patch literal 146 zcmZ?wbhEHblwgoxSi}GV|ABzv|Cuvq($dmEOfXRV$->CMz{;Ql;(^pMFo#F%y7SNA zl;`TbDzDF0>$xcOrDXCY7_7;8z02j-xnh}33=RBMxB1OP^RNE?8F6r-_wh3y=XffA otc+2e>Z-M_{H$fW@3RkH+RUlXHu1j5EY|z5qsDLjd0_@?0F^&G;{X5v literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/buttongroup.gif b/dasCore/src/main/resources/images/toolbox/buttongroup.gif new file mode 100644 index 0000000000000000000000000000000000000000..9ac4e2eb8f332084b0febd739b9708904ab0e1cf GIT binary patch literal 146 zcmZ?wbhEHblwgoxn8*ME|A7EZDE?$&WME)s&;f~pl=f((+n(fA_4M=S`WZ=PhbJTfMn;Bx;ce+ZV;QX)3D4kyUn!U0-N;a%Y7f rT#@tp=+=V8apzul?_Ht3_}UVetS2FvEAs9KsQfwch~FZBfx#L8Jm@~J literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/canvas.gif b/dasCore/src/main/resources/images/toolbox/canvas.gif new file mode 100644 index 0000000000000000000000000000000000000000..792179bc72d34bc59c58bf44ab4b5b156bae5869 GIT binary patch literal 182 zcmZ?wbhEHblwgoxSi}GV|ABzvKad7N2T{rAn`h8pRowF}I-qh?rtS+v-L0Y2URv|azi)`;HnTzhd zT#5Ub{Jl3^BxNREQh5EvmLp!k!8k%57gK?met zP@Z7mC}iN~knz~C;9xU{uvW~84GRyq3&`@#@YwjsrHz>>@Jauke)zz7L}f?VbkK(Dfu1o;Jna>9Rx&HI<^2a2rmba4!+n3I%}dVr6C zse`lKg4u!L;S>fAg*ieDj4i=T3JnEb3@ifNEx+0qiYYWK5b`kUsXvg!z<7k|`C-Zb z2Spqh7T#uj&deep%vZ?Pz~Gc**m&e&86%Lts;WgAZsx)l4&Qhk7z`f@qyr5Xdvl0~ zhk=o0azcuWC`e!h%jNn1Gk{heX>vQv2y_BZSp&l%O-2C+3snXtj^!*44U7zXxAJ(% SmUq1YMVhCppUXO@geCwU)M~5% literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/choice.gif b/dasCore/src/main/resources/images/toolbox/choice.gif new file mode 100644 index 0000000000000000000000000000000000000000..cc677c5a93dbf88a4dd56cefe0335aa20a1a1bd6 GIT binary patch literal 145 zcmZ?wbhEHblwgoxSi}GV|ABzv|Cuvq($dmEOfXRV$->CMz{;Ql;(^pMFo#9#y7SNA zl;`TbDzDF0>$xcOrDXCY7_7;8z02j-xnh}h3=OZH{cdZ`Td#iSDT9Md;c^wZ$-6W* oAGhLLa9MHv%o4r*>-BPLrcOM*ZFTzDojdM-*ij?r&%j^}06JVdB>(^b literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/colorbar.png b/dasCore/src/main/resources/images/toolbox/colorbar.png new file mode 100644 index 0000000000000000000000000000000000000000..55416ab3ca599defa8f8951a974e8d0112ac8244 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAc?bA}xB}__K=l8=_J4*75Nj3# z!-oG085mOjGyMPlKLjY^`2P-2)tzHN*_}WP6gkE4UxeX*E(8Dn|5XhC4>A1z!SLUb z;lQk?lYz2gC9V-A!TD(=<%vb94C$F^Mg@843PyS+dPa-m-1&eif;?RuLn>~So;2rc zFkm=v;DWUv=iC4KsT(3T>^M2e`KR)pzypulj&t-g%gNgKRh;>-Xc>4GRBOcSy32Vw%LQroC7wcf!(FoK1kY%`5YK(rLB z>{bvZVZhc_^d7`&eCE9d2bOPVXXeZ~XTH6=zq@n&{zMb~QO9(ArsFXsn|93S^WyhX zjR#M@5&ZW5!X%eM%HBj~CUaTHQdV7ble;|RDX;DgQ@A1&si+=JQ@S#gscfbyQ@JWs z3GD`MXs{TjyLwNDn}u1LRj(y;HxKhPuVz6p+#)Q}qM8)F>6T%cmIX&ft=uZC(u%FH z1`N`$gcdaxgAFuT3|9;4oCx=Huhz#}vIvj#sFn*0iW#2iS*V8;y(#BMj@;LgEW)Sf(MJi2DkH*P*pedWX{=bM__Qv!=Ij>E`=#q1$L4Rm*kZWo^bV&*Q#9xGc?x+ho7no*N`ryH F8UR~@Dw6;J literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/plot.gif b/dasCore/src/main/resources/images/toolbox/plot.gif new file mode 100644 index 0000000000000000000000000000000000000000..83c18fe72f236bbafd4156d9741e5bb92465c726 GIT binary patch literal 127 zcmZ?wbhEHblwgoxn8*ME|A7EZDE?$&WME)s&;f~posY|AIhveFYmXxwMv8KP1$4KPS;RP^={2b=j18BlpPK4 WEC^uFlL~mUKBCEQ_4OA_4AuaxWil)P literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/radiobutton.gif b/dasCore/src/main/resources/images/toolbox/radiobutton.gif new file mode 100644 index 0000000000000000000000000000000000000000..b5f2bec7a3a97afc52887433b00a30021ea07a9d GIT binary patch literal 123 zcmZ?wbhEHblwgoxSi}GV|ABzv|CuvqKr{>}{$ycfU|?a;0SSQAGB7)M?7H*M;FRa; zy%xd;8lO2z6o?D(y%4CHYFWZ?%1`gi$&lQ~B_H0cpL1S}o2%aZvEkHNPrHnkvo2d1 Nwf@GMZxb09tO4|tEUoav`2l9|a(L75_GY>&l zGtCuPq-0@U8R}%ACaBm&sF~JE%^_Bc*;m`!y76D=rkCHvd-vmcU!MK(ap4hZB8XtY z>JT_KI9S1D!8b#=2+t+sM6v;v#4+qW&Ci zWkZ{RW(H3a&>f3c5qP}|gJGEVfl-VP-dOO!k}DR4F!S-<8NVFCa$(`YZYDSoxCAG{ z8p2wFD?v=~CioJ(h7dy7K?t*BLj)nh200;yAh$wk#cmV!bR&tyF&5c`a)O4?K)6fL z5}FAQ2rmeamT+eR?Mrw`7$l4n`b~I87&T#X0Y3>J=Rt(U1$>_e`x#^eI|=q3%m%P} z{L*8l1-^dKEoCFk@*NTvM$tOUn@&1UH-ujAE*?|6Gs`H(ZJtUeXjdb`o~=3U|6E)+RidwL$IRwEGIZ)MCzl9 z*LvvtD;u^XylG5%ZX7oGT{cHOR!3THZq1xvHiqsOh-7)!i&|pm_<5g(doGlWuc;PC z7fy8cO{)&w+kK`hOKlM}ReMJltS~+KS!$`G_*tx)q_{&eKfwl$~bYTbDhCG`gabu mDn!3>vMTh8qQWz9Ccz-wIUwWK$Q^#B*Tm{a%RM+8ME(I4hDta9 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/spectrogram_plot.gif b/dasCore/src/main/resources/images/toolbox/spectrogram_plot.gif new file mode 100644 index 0000000000000000000000000000000000000000..1bddbb090d84ed8877361865afa1d08a1ac4e5f7 GIT binary patch literal 265 zcmZ?wbhEHblwgoxIKlt||ABzvKagf%IK#j&lOYYr1EMsb0F-%#;r~pC07x-d6S0Ki zPZmZ71_1^gkOq*Q46JPmDt#%L^DjY5R}Y_m^ne=ibQLWWWt)u9?SI3-B?iQn4-R-Ye&S1G<%^ko2fI{bmZzA8=6Bn z+KO#EyE;SI`)ZpewivKYEv}eRIx~c2ZlYj#+IfIhr17x z78g!wH+}SD$%B)9*{7=%bxwWBydSeJI{&@YnTEOY6U$AQCVHp#|15d6AUNPvS82_* I4kiX`0HjYaMF0Q* literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/taxis.gif b/dasCore/src/main/resources/images/toolbox/taxis.gif new file mode 100644 index 0000000000000000000000000000000000000000..d222133ce31eec3c5c774874d371c644048ae0aa GIT binary patch literal 128 zcmZ?wbhEHblwgoxn8*ME|A7EZDE?$&WME)s&;f~pCMz{;Ql;(^pMF#AXBy7SNA zl;`Tb5|hsE{vYkgl9D;^g~Fm7?=_4LOq)EXmwxCgeE)mvoxC*byLn3<>ZecUOmuwQ g(-va2(&5SE>#x*YnJ4_1tU3Gqi!D`mJs23Q0sAvKSO5S3 literal 0 HcmV?d00001 diff --git a/dasCore/src/main/resources/images/toolbox/window.gif b/dasCore/src/main/resources/images/toolbox/window.gif new file mode 100644 index 0000000000000000000000000000000000000000..88fa24c09bed479dbaff0bb0eedae1d2bf1a4e44 GIT binary patch literal 130 zcmZ?wbhEHblwgoxn8*ME|A7EZDE?$&WME)s&;f~peSLR%jV^t|9!dmLB{g1i{V1GZnKuPeeQIN@KfKEwKOUA a*4kCCbsG;~|8V_}O!euEwBOMT4Aua#aX97x literal 0 HcmV?d00001 From 6a7da135ff19366dddadceceee03a583d3733a13 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Thu, 14 Jul 2016 23:14:17 +0000 Subject: [PATCH 18/46] Moved old dasCore test code to maven project svn path=/core/stable/dasCore/; revision=9330 --- .../AverageTableRebinnerBenchmark.java | 40 +++ .../test/java/graph/ContoursRendererDemo.java | 52 ++++ .../src/test/java/graph/DemoAPBug1129.java | 228 ++++++++++++++++++ dasCore/src/test/java/graph/DemoResize.java | 44 ++++ .../src/test/java/graph/GraphLegendDemo.java | 67 +++++ .../src/test/java/graph/GraphUtilDemo.java | 80 ++++++ dasCore/src/test/java/graph/PlotDemo.java | 66 +++++ .../java/graph/PlotSymbolRendererDemo.java | 117 +++++++++ .../test/java/stream/TestDas2StreamRead.java | 160 ++++++++++++ 9 files changed, 854 insertions(+) create mode 100644 dasCore/src/test/java/dataset/AverageTableRebinnerBenchmark.java create mode 100755 dasCore/src/test/java/graph/ContoursRendererDemo.java create mode 100644 dasCore/src/test/java/graph/DemoAPBug1129.java create mode 100644 dasCore/src/test/java/graph/DemoResize.java create mode 100755 dasCore/src/test/java/graph/GraphLegendDemo.java create mode 100755 dasCore/src/test/java/graph/GraphUtilDemo.java create mode 100644 dasCore/src/test/java/graph/PlotDemo.java create mode 100644 dasCore/src/test/java/graph/PlotSymbolRendererDemo.java create mode 100644 dasCore/src/test/java/stream/TestDas2StreamRead.java diff --git a/dasCore/src/test/java/dataset/AverageTableRebinnerBenchmark.java b/dasCore/src/test/java/dataset/AverageTableRebinnerBenchmark.java new file mode 100644 index 000000000..a5f8755cc --- /dev/null +++ b/dasCore/src/test/java/dataset/AverageTableRebinnerBenchmark.java @@ -0,0 +1,40 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package test.dataset; + +import org.das2.DasException; +import org.das2.dataset.AverageTableRebinner; +import org.das2.dataset.RebinDescriptor; +import org.das2.dataset.TableDataSet; +import org.das2.dataset.test.RipplesDataSet; +import org.das2.datum.Units; + +/** + * + * @author jbf + */ +public class AverageTableRebinnerBenchmark { + public static void main( String[] args ) throws DasException { + TableDataSet tds= new RipplesDataSet( 2, 3, 1, 13, 15, 2, 30, 30 ); + Units u= Units.dimensionless; + RebinDescriptor ddx= new RebinDescriptor( u.createDatum(0), u.createDatum(30), 1000, false ); + RebinDescriptor ddy= new RebinDescriptor( u.createDatum(0), u.createDatum(30), 1000, false ); + + AverageTableRebinner rebin= new AverageTableRebinner(); + + long t0= System.currentTimeMillis(); + long totalMillis=0; + for ( int j=0; j<30.; j++ ) { + rebin.rebin(tds, ddy, ddy, java.util.Collections.EMPTY_MAP); + long t1= System.currentTimeMillis() - t0; + System.err.println( t1 ); + totalMillis+= t1; + t0= System.currentTimeMillis(); + } + System.err.println("average: "+totalMillis/30.); + + } +} diff --git a/dasCore/src/test/java/graph/ContoursRendererDemo.java b/dasCore/src/test/java/graph/ContoursRendererDemo.java new file mode 100755 index 000000000..be6872cae --- /dev/null +++ b/dasCore/src/test/java/graph/ContoursRendererDemo.java @@ -0,0 +1,52 @@ +/* + * ContoursRendererDemo.java + * + * Created on December 7, 2007, 3:22 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package test.graph; + +import org.das2.dataset.TableDataSet; +import org.das2.dataset.test.RipplesDataSet; +import org.das2.event.AnnotatorMouseModule; +import org.das2.event.MouseModule; +import org.das2.event.PointSlopeDragRenderer; +import org.das2.graph.GraphUtil; +import org.das2.graph.Renderer; +import org.das2.graph.ContoursRenderer; +import org.das2.graph.DasPlot; + +/** + * + * @author jbf + */ +public class ContoursRendererDemo { + + /** Creates a new instance of ContoursRendererDemo */ + public ContoursRendererDemo() { + super(); + Renderer rend= new ContoursRenderer(); + + TableDataSet tds= new RipplesDataSet(50,50,20,70,70,30,100,100); + + DasPlot p= GraphUtil.visualize( tds ); + p.getXAxis().setAnimated(false); + p.getYAxis().setAnimated(false); + + MouseModule mm= new MouseModule( p, new PointSlopeDragRenderer(p, p.getXAxis(), p.getYAxis() ), "Slope" ); + + p.addMouseModule( mm ); + + rend.setDataSet( tds ); + + p.addRenderer( rend ); + + } + + public static void main( String[] args ) { + new ContoursRendererDemo(); + } +} diff --git a/dasCore/src/test/java/graph/DemoAPBug1129.java b/dasCore/src/test/java/graph/DemoAPBug1129.java new file mode 100644 index 000000000..59cecd409 --- /dev/null +++ b/dasCore/src/test/java/graph/DemoAPBug1129.java @@ -0,0 +1,228 @@ + +package test.graph; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import javax.swing.JFrame; +import javax.swing.JPanel; +import org.das2.dataset.DataSet; +import org.das2.dataset.TableDataSetBuilder; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.DatumRange; +import org.das2.datum.DatumRangeUtil; +import org.das2.datum.DatumVector; +import org.das2.datum.Units; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColorBar; +import org.das2.graph.DasPlot; +import org.das2.graph.GraphUtil; +import org.das2.graph.SeriesRenderer; +import org.das2.graph.SpectrogramRenderer; +import org.das2.util.FileUtil; + +/** + * This was a bug that showed on the community branch used with + * Autoplot, and I wanted to see if the same bug would show here. + * + * The bug also shows on the production branch. Run this and see /tmp/ap/*.png. + * About one of every 20 images shows where the spectrogram location is painted + * incorrectly. + * + * @author jbf + */ +public class DemoAPBug1129 { + JPanel contentPane; + private DasCanvas canvas; + protected DasPlot plot; + private DasColorBar cb; + + private synchronized JPanel getContentPane() { + if (contentPane == null) { + contentPane = new JPanel(); + } + return contentPane; + } + + public JFrame showFrame() { + JFrame frame= new JFrame( "Axis Demo"); + frame.getContentPane().add(getContentPane()); + frame.pack(); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + frame.setVisible(true); + return frame; + } + + private class MyHandler extends Handler { + + @Override + public void publish(LogRecord rec) { + Object[] parms= rec.getParameters(); + + String recMsg; + if ( parms==null || parms.length==0 ) { + recMsg = rec.getMessage(); + } else { + recMsg = MessageFormat.format( rec.getMessage(), parms ); + } + System.err.println( recMsg ); + } + + @Override + public void flush() { + + } + + @Override + public void close() throws SecurityException { + + } + + } + + + public DemoAPBug1129() { + int width = 400; + int height = 200; + + Logger.getLogger("das2.graphics").setLevel(Level.FINER); + Handler h= new MyHandler(); + h.setLevel( Level.ALL ); + Logger.getLogger("das2.graphics").addHandler( h ); + + getContentPane().setLayout(new BorderLayout()); + + canvas = new DasCanvas(width, height); + + getContentPane().add(canvas, BorderLayout.CENTER ); + + DatumRange xrange= DatumRange.newDatumRange(-5,35,Units.dimensionless); + DatumRange yrange= DatumRange.newDatumRange(-5,35, Units.dimensionless); + DatumRange zrange= DatumRange.newDatumRange(-0.2,1.2,Units.dimensionless); + + cb= new DasColorBar( zrange.min(), zrange.max(), false ); + plot= GraphUtil.newDasPlot(canvas, xrange, yrange); + + canvas.add( cb, plot.getRow(), plot.getColumn().createAttachedColumn(1.05,1.10) ); + plot.setPreviewEnabled(true); + + plot.addRenderer( new SpectrogramRenderer(null,cb) ); + SeriesRenderer r= new SeriesRenderer(); + r.setAntiAliased(true); + r.setColor( Color.WHITE ); + + plot.addRenderer( r ); + + plot.getColumn().setEmMaximum(-10); + + } + + private void writeImage( int j, char a ) { + try { + canvas.waitUntilIdle(); + canvas.writeToPng( String.format("/tmp/ap/%05d%s.png",j,a) ); + // canvas.waitUntilIdle(true); When the fault occurs, there are no pending events. + } catch (IOException ex) { + Logger.getLogger(DemoAPBug1129.class.getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + Logger.getLogger(DemoAPBug1129.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private DataSet getDataSet() { + TableDataSetBuilder build= new TableDataSetBuilder( Units.dimensionless, Units.dimensionless, Units.dimensionless ); + double[] yback= new double[30]; + for ( int i=0; i<30; i++ ) { + yback[i]= i; + } + double[] zback= new double[30]; + for ( int i=0; i<30; i++ ) { + zback[i]= 0.5; + } + DatumVector ytags= DatumVector.newDatumVector( yback, Units.dimensionless ); + for ( int i=0;i<30; i++ ) { + zback[i]= 0.; + if ( i>0 ) zback[i-1]= 0.5; + build.insertYScan( Units.dimensionless.createDatum(i), + ytags, + DatumVector.newDatumVector( zback, Units.dimensionless ) ); + } + return build.toTableDataSet(); + //ArrayDataSet ds= ArrayDataSet.copy( Ops.replicate(0.5,30,30) ); + //for ( int i=0;i<30;i++ ) { + // ds.putValue(i,i,0); + //} + //return ds; + } + + private DataSet getLineDataSet() { + VectorDataSetBuilder build= new VectorDataSetBuilder( Units.dimensionless, Units.dimensionless ); + build.insertY( 3, 3 ); + build.insertY( 15, 15 ); + return build.toVectorDataSet(); + } + + private void initalize() { + //ArrayDataSet ds= ArrayDataSet.copy( Ops.ripples(30,30) ); + DataSet ds= getDataSet(); + plot.getRenderer(0).setDataSet(ds); + plot.getRenderer(1).setDataSet(getLineDataSet()); + } + + private void stressIt() { + + DatumRange xr1= DatumRangeUtil.rescale(plot.getXAxis().getDatumRange(), -0.02, 0.98 ); + plot.getColumn().setMinimum(0.1); + plot.getColumn().setMaximum(0.9); + DatumRange xr2= DatumRangeUtil.rescale(plot.getXAxis().getDatumRange(), 0.02, 1.02 ); + + if ( new File("/tmp/ap/").exists() ) { + if ( !FileUtil.deleteFileTree(new File("/tmp/ap/")) ) { + throw new RuntimeException("unable to delete old tree"); + } + } + + if ( !new File("/tmp/ap/").mkdirs() ) { + throw new RuntimeException("mkdirs"); + } + + int j=0; + while ( true ) { + plot.getColumn().setMinimum(0.1); + plot.getColumn().setMaximum(0.9); + plot.getXAxis().setDatumRange( xr1 ); + writeImage(j,'a'); + //if ( canvas.broken!=null ) { + //break; + //} + plot.getXAxis().setDatumRange( xr2 ); + //ds.putValue( j % 30, ( j % 900) /30, 0.6+Math.random()/10 ); + plot.getColumn().setMinimum(0.12); + plot.getColumn().setMaximum(0.92); + writeImage(j,'b'); + //if ( canvas.broken!=null ) { + //break; + //} + //plot.getRenderer(0).setDataSet(ds); + //writeImage(j,'c'); + j=j+1; + //if ( ( j % 900) ==0 ) { + // ds= getDataSet(); + //} + } + } + + public static void main( String[] args ) { + DemoAPBug1129 app= new DemoAPBug1129(); + + app.showFrame(); + app.initalize(); + app.stressIt(); + } +} diff --git a/dasCore/src/test/java/graph/DemoResize.java b/dasCore/src/test/java/graph/DemoResize.java new file mode 100644 index 000000000..2c55be635 --- /dev/null +++ b/dasCore/src/test/java/graph/DemoResize.java @@ -0,0 +1,44 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package test.graph; + +import javax.swing.JFrame; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import org.das2.graph.SeriesRenderer; + +/** + * + * @author jbf + */ +public class DemoResize { + public static void main(String[] args ) throws InterruptedException { + + DasCanvas c = new DasCanvas(); + + DasPlot p = DasPlot.createDummyPlot(); + p.setDrawMinorGrid(true); + c.add(p, DasRow.create(c), DasColumn.create(c)); + + SeriesRenderer rend = new SeriesRenderer(); + p.addRenderer(rend); + + JFrame frame= new JFrame(); + frame.getContentPane().add(c); + frame.pack(); + frame.setVisible(true); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + p.getRow().setEmMinimum(12); + p.getRow().setEmMinimum(10); + + + Thread.sleep(1000); + p.getRow().setEmMinimum(5.5); + } +} diff --git a/dasCore/src/test/java/graph/GraphLegendDemo.java b/dasCore/src/test/java/graph/GraphLegendDemo.java new file mode 100755 index 000000000..e1c3d5d7c --- /dev/null +++ b/dasCore/src/test/java/graph/GraphLegendDemo.java @@ -0,0 +1,67 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package test.graph; + +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.Units; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasColumn; +import org.das2.graph.DasPlot; +import org.das2.graph.DasRow; +import org.das2.graph.SeriesRenderer; +import java.util.Random; +import javax.swing.JApplet; +import javax.swing.JFrame; + +/** + * + * @author jbf + */ +public class GraphLegendDemo extends JApplet { + + /** + * Initialization method that will be called after the applet is loaded + * into the browser. + */ + public void init() { + DasCanvas c = new DasCanvas(); + add(c); + + DasPlot p = DasPlot.createDummyPlot(); + c.add(p, DasRow.create(c), DasColumn.create(c)); + + SeriesRenderer rend = new SeriesRenderer(); + rend.setDataSet(getFunDataSet()); + + p.addRenderer(rend); + + } + + private DataSet getFunDataSet() { + VectorDataSetBuilder build = new VectorDataSetBuilder(Units.dimensionless, Units.dimensionless); + double dx = 0; + double dy = 0; + Random r = new Random(0); + for (int i = 0; i < 1000; i++) { + dx += r.nextGaussian(); + dy += r.nextGaussian(); + build.insertY(dx + r.nextGaussian(), dy + r.nextGaussian()); + } + return build.toVectorDataSet(); + + } // TODO overwrite start(), stop() and destroy() methods + + public static void main( String[] args ) { + GraphLegendDemo demo= new GraphLegendDemo(); + demo.init(); + JFrame frame= new JFrame(); + frame.getContentPane().add(demo); + frame.pack(); + frame.setVisible(true); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + } +} diff --git a/dasCore/src/test/java/graph/GraphUtilDemo.java b/dasCore/src/test/java/graph/GraphUtilDemo.java new file mode 100755 index 000000000..b8f1a6594 --- /dev/null +++ b/dasCore/src/test/java/graph/GraphUtilDemo.java @@ -0,0 +1,80 @@ +/* + * GraphUtilDemo.java + * + * Created on December 8, 2007, 7:17 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package test.graph; + +import org.das2.graph.GraphUtil; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Ellipse2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.Point2D; +import javax.swing.JFrame; +import javax.swing.JPanel; + +/** + * + * @author jbf + */ +public class GraphUtilDemo { + + public static void pointsAlongACurveDemo() { + Ellipse2D e= new Ellipse2D.Double(50,50,100,100); + final GeneralPath p= new GeneralPath(e); + + //final GeneralPath p= new GeneralPath( new Rectangle( 20, 20, 100, 100 ) ); + + + double[] points= new double[] { 10, 20, 30, 100, 200 }; + final Point2D.Double[] result= new Point2D.Double[points.length]; + final double[] orientation= new double[points.length]; + + double len= GraphUtil.pointsAlongCurve( p.getPathIterator(null,0.001), null, null, null, false ); + points[points.length-1]=len; + GraphUtil.pointsAlongCurve( p.getPathIterator(null,0.001), points, result, orientation, false ); + + JPanel panel= new JPanel() { + public void paintComponent( Graphics g1 ) { + Graphics2D g= (Graphics2D) g1; + g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + ((Graphics2D)g).draw(p); + for ( int i=0; i 0 ? f.length() : -1; + + System.err.println(" " + (taskSize / dt) + " Kb/s"); + + } + + { + t0 = System.currentTimeMillis(); + ProgressMonitor mon = DasProgressPanel.createFramed("reading data"); + + eatData(dataUrl, mon); + + long dt = (System.currentTimeMillis() - t0); + System.err.println("done in " + dt + " millis"); + + + File f = new File(dataUrl.getPath()); + long taskSize = f.length() > 0 ? f.length() : -1; + + System.err.println(" " + (taskSize / dt) + " Kb/s"); + } + } + + /** + * eats up the data, ignoring the content. This is the optimal speed. + * @param url + * @param mon + * @throws java.io.IOException + */ + public static void eatData(URL url, ProgressMonitor mon) throws IOException { + long taskSize = -1; + + URLConnection connect = url.openConnection(); + InputStream in = connect.getInputStream(); + + System.err.println("reading data from " + url); + + if (connect instanceof HttpURLConnection) { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + taskSize = connection.getContentLength(); + + } else if (url.getProtocol().equals("file")) { + File f = new File(url.getPath()); + taskSize = f.length() > 0 ? f.length() : -1; + } else { + taskSize = -1; + } + + System.err.println(" taskSize=" + taskSize); + + DasProgressMonitorInputStream min = new DasProgressMonitorInputStream(in, mon); + min.setStreamLength(taskSize); + in = min; + + DataSetStreamHandler handler = new DataSetStreamHandler(new HashMap(), new NullProgressMonitor()); + + ReadableByteChannel channel = Channels.newChannel(in); + + ByteBuffer buf = ByteBuffer.allocateDirect(84); + + int bytesRead = 84; + while (bytesRead == 84 ) { + bytesRead = channel.read(buf); + buf.position(4); + DoubleBuffer dbuf= buf.slice().asDoubleBuffer(); + for ( int i=0; i<10; i++ ) { + double d= dbuf.get(i); + } + buf.position(0); + } + + in.close(); + } + + public static VectorDataSet getDataSet(URL url, ProgressMonitor mon) throws IOException, StreamException { + + long taskSize = -1; + + URLConnection connect = url.openConnection(); + InputStream in = connect.getInputStream(); + + System.err.println("reading data from " + url); + + if (connect instanceof HttpURLConnection) { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + taskSize = connection.getContentLength(); + + } else if (url.getProtocol().equals("file")) { + File f = new File(url.getPath()); + taskSize = f.length() > 0 ? f.length() : -1; + } else { + taskSize = -1; + } + + System.err.println(" taskSize=" + taskSize); + + DasProgressMonitorInputStream min = new DasProgressMonitorInputStream(in, mon); + min.setStreamLength(taskSize); + in = min; + + DataSetStreamHandler handler = new DataSetStreamHandler(new HashMap(), new NullProgressMonitor()); + + ReadableByteChannel channel = Channels.newChannel(in); + + StreamTool.readStream(channel, handler); + + in.close(); + + return (VectorDataSet) handler.getDataSet(); + } +} From d6d85c51059c82315c526b66b16a27b2e60fbf1a Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Thu, 28 Jul 2016 18:14:43 +0000 Subject: [PATCH 19/46] add abs() and value() from Autoplot community branch. svn path=/core/stable/dasCore/; revision=9375 --- .../src/main/java/org/das2/datum/Datum.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dasCore/src/main/java/org/das2/datum/Datum.java b/dasCore/src/main/java/org/das2/datum/Datum.java index f88f28def..4cdb87669 100644 --- a/dasCore/src/main/java/org/das2/datum/Datum.java +++ b/dasCore/src/main/java/org/das2/datum/Datum.java @@ -99,6 +99,40 @@ public double doubleValue(Units units) { } } + /** + * returns the double value without the unit, as long as the Units indicate this is a ratio measurement, and there is a meaningful 0. + * For example "5 Kg" -> 5, but "2012-02-16T00:00" would throw an IllegalArgumentException. Note this was introduced because often we just need + * to check to see if a value is zero. + * @return + */ + public double value() { + if ( UnitsUtil.isRatioMeasurement(units) ) { + return this.doubleValue( this.getUnits() ); + } else { + throw new IllegalArgumentException("datum is not ratio measurement: "+this ); + } + } + + /** + * return the absolute value (magnitude) of this Datum. If this + * datum is fill then the result is fill. + * @return + * @throws IllegalArgumentException if the datum is not a ratio measurement (like a timetag). + */ + public Datum abs() { + if ( UnitsUtil.isRatioMeasurement(units) ) { + if ( this.getUnits().isFill(value) ) { + return this; + } else if ( this.value.doubleValue()>=0 ) { + return this; + } else { + return this.getUnits().createDatum( Math.abs(value.doubleValue()) ); + } + } else { + throw new IllegalArgumentException("datum is not ratio measurement: "+this ); + } + } + /** * returns the resolution (or precision) of the datum. This is metadata for the datum, used * primarily to limit the number of decimal places displayed in a string representation, From 4b5946fcbdcd43d93129867cd3f2df71e79b02f6 Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Thu, 28 Jul 2016 18:19:11 +0000 Subject: [PATCH 20/46] add selectClosest svn path=/core/stable/dasCore/; revision=9376 --- .../das2/components/DataPointRecorder.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java index 367d38a63..f7aab941b 100755 --- a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java +++ b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java @@ -278,6 +278,28 @@ public VectorDataSet getSelectedDataSet() { return builder.toVectorDataSet(); } } + + /** + * Selects all the points within the DatumRange + * @param x the x value + * @param y the y value (may not be used, currently isn't). + */ + public void selectClosest( Datum x, Datum y ) { + int close= -1; + Datum closex= null; + Datum closedx= null; + for (int i = 0; i < dataPoints.size(); i++) { + DataPoint p = (DataPoint) dataPoints.get(i); + Datum thisx= p.get(0); + Datum dist= thisx.subtract(x).abs(); + if ( closex==null || dist.lt(closedx) ) { + close= i; + closex= thisx; + closedx= dist; + } + } + table.getSelectionModel().addSelectionInterval(close,close); + } /** * Selects all the points within the DatumRange From d365aaa14efe4a7cfbc3fece7517978428d06126 Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Thu, 28 Jul 2016 18:32:36 +0000 Subject: [PATCH 21/46] add selectClosest svn path=/core/stable/dasCore/; revision=9377 --- dasCore/src/main/java/org/das2/components/DataPointRecorder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java index f7aab941b..cc21c84a6 100755 --- a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java +++ b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java @@ -298,6 +298,7 @@ public void selectClosest( Datum x, Datum y ) { closedx= dist; } } + table.getSelectionModel().clearSelection(); table.getSelectionModel().addSelectionInterval(close,close); } From 63f754df656b066cfbb8f6fb653cb615258aae8a Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Mon, 1 Aug 2016 17:34:49 +0000 Subject: [PATCH 22/46] bugfix: Enumeration unit branch can't use Units.lookupUnits, because it doesn't know about enumeration units. fireTableDataChanged after the model is loaded, so a third column will show. svn path=/core/stable/dasCore/; revision=9384 --- .../main/java/org/das2/components/DataPointRecorder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java index cc21c84a6..a36f008ff 100755 --- a/dasCore/src/main/java/org/das2/components/DataPointRecorder.java +++ b/dasCore/src/main/java/org/das2/components/DataPointRecorder.java @@ -427,7 +427,7 @@ public void loadFromFile(File file) throws IOException { planesArray[i] = m.group(1).trim(); String sunits= m.group(2).trim(); try { - unitsArray[i] = Units.lookupUnits(sunits); + unitsArray[i] = Units.getByName(sunits); // does it exist already } catch ( IllegalArgumentException ex ) { unitsArray[i] = new EnumerationUnits(sunits); } @@ -489,7 +489,7 @@ public void loadFromFile(File file) throws IOException { u.createDatum(field); planes.put(planesArray[i], unitsArray[i].parse(field)); } else { - throw new RuntimeException(e); + throw new RuntimeException("Parse exception at line "+linenum+" of "+file, e ); } } } @@ -499,7 +499,9 @@ public void loadFromFile(File file) throws IOException { e = new DataPointSelectionEvent(this, x, y, planes); dataPointSelected(e); } catch (ParseException e) { - throw new RuntimeException(e); + throw new RuntimeException("Parse exception at line "+linenum+" of "+file, e); + } catch ( NumberFormatException e ) { + throw new RuntimeException("Number format exception at line "+linenum+" of "+file, e ); } @@ -521,6 +523,7 @@ public void loadFromFile(File file) throws IOException { //table.getColumnModel().getColumn(0).setPreferredWidth(200); table.getColumnModel(); + myTableModel.fireTableStructureChanged(); table.repaint(); } From 5875b6121e4f9e35aec296fbbc1ea0cda4b631f9 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Wed, 28 Sep 2016 16:05:09 +0000 Subject: [PATCH 23/46] Checking in SVN changes so I can checkout source on a local disk so maybe DDD will work. Uggg, the suns are close to dead svn path=/core/stable/dasCore/; revision=9488 --- .../src/test/java/graph/SymbolLineSpeed.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 dasCore/src/test/java/graph/SymbolLineSpeed.java diff --git a/dasCore/src/test/java/graph/SymbolLineSpeed.java b/dasCore/src/test/java/graph/SymbolLineSpeed.java new file mode 100755 index 000000000..4f231d103 --- /dev/null +++ b/dasCore/src/test/java/graph/SymbolLineSpeed.java @@ -0,0 +1,109 @@ +/* + * Main.java + * + * Created on June 28, 2007, 9:34 PM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package graph; + +import org.das2.dataset.DataSet; +import org.das2.dataset.VectorDataSet; +import org.das2.dataset.VectorDataSetBuilder; +import org.das2.datum.DatumRange; +import org.das2.datum.Units; +import org.das2.graph.DasCanvas; +import org.das2.graph.DasPlot; +import org.das2.graph.GraphUtil; +import org.das2.graph.SeriesRenderer; +import org.das2.util.monitor.ProgressMonitor; +import java.awt.Graphics; +import javax.swing.JFrame; + +/** + * + * @author jbf + */ +public class SymbolLineSpeed { + + /** Creates a new instance of Main */ + public SymbolLineSpeed() { + } + + private VectorDataSet getDataSet( ProgressMonitor mon ) { + System.err.println("enter getDataSet"); + long t0= System.currentTimeMillis(); + + VectorDataSetBuilder vbd= new VectorDataSetBuilder( Units.dimensionless, Units.dimensionless ); + double y= 0; + for ( int i=0; i<1000000; i+=4 ) { + y+= Math.random() - 0.5; + if ( i%100==10 ) y= Units.dimensionless.getFillDouble(); + vbd.insertY( i, y ); + } + vbd.setProperty( DataSet.PROPERTY_X_MONOTONIC, Boolean.TRUE ); + VectorDataSet vds= vbd.toVectorDataSet(); + System.err.println("done getDataSet in "+(System.currentTimeMillis() - t0 ) + " ms" ); + return vds; + } + + private DasPlot getPlot() { + JFrame frame= new JFrame() ; + DasCanvas canvas= new DasCanvas( 400, 400 ); + DasPlot p= GraphUtil.newDasPlot( canvas, DatumRange.newDatumRange( 0, 100000, Units.dimensionless ), DatumRange.newDatumRange( -250, 250, Units.dimensionless ) ); + frame.getContentPane().add( canvas ); + frame.pack(); + frame.setVisible( true ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + + return p; + } + + public void run() { + + DasPlot plot= getPlot(); + plot.getXAxis().setPlot(plot); + plot.getYAxis().setPlot(plot); + + //SymbolLineRenderer rend1= new SymbolLineRenderer(); + SeriesRenderer rend1= new SeriesRenderer(); + + plot.getXAxis().setDataMaximum( Units.dimensionless.createDatum( 1000010 ) ); + plot.getYAxis().setDatumRange( DatumRange.newDatumRange( -250, 250, Units.dimensionless ) ); + plot.addRenderer( rend1 ); + + VectorDataSet ds= getDataSet( ProgressMonitor.NULL); + + rend1.setDataSet( ds ); + + for ( int i=0; i<40; i++ ) { + long t0= System.currentTimeMillis(); + Graphics g= plot.getGraphics(); + g.translate( -plot.getX(), -plot.getY() ); + rend1.updatePlotImage( plot.getXAxis(), plot.getYAxis(), ProgressMonitor.NULL ); + plot.repaint(); + System.err.println( ""+i+"time to update\t"+(System.currentTimeMillis()-t0) ); + } + + for ( int i=0; i<40; i++ ) { + long t0= System.currentTimeMillis(); + Graphics g= plot.getGraphics(); + g.translate( -plot.getX(), -plot.getY() ); + rend1.render( g, plot.getXAxis(), plot.getYAxis(), ProgressMonitor.NULL ); + plot.repaint(); + System.err.println( ""+i+"time to render\t"+(System.currentTimeMillis()-t0) ); + } + + } + + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + new SymbolLineSpeed().run(); + } + +} From 8180eda6f0440e09c42618f802dabb2318f152ef Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Mon, 10 Oct 2016 18:39:06 +0000 Subject: [PATCH 24/46] bugfix: getInputStream, which calls the server without any server-side reduction, didn't include the parameters. svn path=/core/stable/dasCore/; revision=9512 --- .../java/org/das2/client/WebStandardDataStreamSource.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java b/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java index 35fa94ebb..e8eb94b07 100755 --- a/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java +++ b/dasCore/src/main/java/org/das2/client/WebStandardDataStreamSource.java @@ -81,6 +81,10 @@ public InputStream getInputStream(StreamDataSetDescriptor dsd, Datum start, Datu StringBuffer formData = new StringBuffer(); formData.append("server=").append(serverType); + if (extraParameters != null) { + formData.append("¶ms=").append(extraParameters); //Should already be url encoded. + } + InputStream in= openURLConnection( dsd, start, end, formData ); in= DasApplication.getDefaultApplication().getInputStreamMeter().meterInputStream(in); return in; From 0c56c244e54be5ce4313c026120d79854019b90f Mon Sep 17 00:00:00 2001 From: Edward West Date: Wed, 12 Oct 2016 20:16:40 +0000 Subject: [PATCH 25/46] VerticalRangeSelectorMouseModule need to be made aware that axes can be flipped svn path=/core/stable/dasCore/; revision=9527 --- .../das2/event/VerticalRangeSelectorMouseModule.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java index 9601fe39f..d8697452a 100644 --- a/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java +++ b/dasCore/src/main/java/org/das2/event/VerticalRangeSelectorMouseModule.java @@ -67,8 +67,14 @@ public void mouseRangeSelected(MouseDragEvent e0) { Datum nnMin; Datum nnMax; MouseRangeSelectionEvent e= (MouseRangeSelectionEvent)e0; - min= axis.invTransform(e.getMaximum()); - max= axis.invTransform(e.getMinimum()); + if (axis.isFlipped()) { + max= axis.invTransform(e.getMaximum()); + min= axis.invTransform(e.getMinimum()); + } + else { + min= axis.invTransform(e.getMaximum()); + max= axis.invTransform(e.getMinimum()); + } DatumRange dr= new DatumRange( min, max ); DatumRange nndr= axis.getTickV().enclosingRange(dr, true); DataRangeSelectionEvent te= From ee98e1aeb8d83b76c9d806ad190b25483291f331 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Mon, 17 Oct 2016 23:10:10 +0000 Subject: [PATCH 26/46] New pom without javadocs, yay I guess svn path=/core/stable/dasCore/; revision=9531 --- dasCore/pom.xml | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 dasCore/pom.xml diff --git a/dasCore/pom.xml b/dasCore/pom.xml new file mode 100644 index 000000000..61beb0ad8 --- /dev/null +++ b/dasCore/pom.xml @@ -0,0 +1,104 @@ + + 4.0.0 + + org.das2 + dasCore + 2.2 + jar + + dasCore + http://maven.apache.org + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.0.2 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.6 + + + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + + + + + http://repo1.maven.org/maven2/ + swing-layout + default + Repository for library Library[swing-layout] + + + + UTF-8 + + + + + junit + junit + 3.8.1 + test + + + org.swinglabs + swing-layout + 1.0.3 + + + com.lowagie + itext + 2.0.7 + + + batik + batik-svggen + 1.5 + + + batik + batik-awt-util + 1.5 + + + batik + batik-util + 1.5 + + + From 8fc0c7b61d9a9518df6d83ceb2a26351e0217813 Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Thu, 10 Nov 2016 20:52:35 +0000 Subject: [PATCH 27/46] print information about the jar used. svn path=/core/stable/dasCore/; revision=9582 --- dasCore/src/main/java/org/das2/v20161010a.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 dasCore/src/main/java/org/das2/v20161010a.java diff --git a/dasCore/src/main/java/org/das2/v20161010a.java b/dasCore/src/main/java/org/das2/v20161010a.java new file mode 100644 index 000000000..4893a713c --- /dev/null +++ b/dasCore/src/main/java/org/das2/v20161010a.java @@ -0,0 +1,16 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.das2; + +/** + * + * @author jbf + */ +public class v20161010a { + public static void main(String[] args) { + System.err.println("v20161010a"); + } +} From 9e1727b450076dd619e91a924a662f270adc723f Mon Sep 17 00:00:00 2001 From: Jeremy Faden Date: Wed, 18 Jan 2017 22:41:38 +0000 Subject: [PATCH 28/46] don't show NUM LOCK warning. svn path=/core/stable/dasCore/; revision=9657 --- dasCore/src/main/java/org/das2/client/Authenticator.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dasCore/src/main/java/org/das2/client/Authenticator.java b/dasCore/src/main/java/org/das2/client/Authenticator.java index 972446950..b09a965ac 100755 --- a/dasCore/src/main/java/org/das2/client/Authenticator.java +++ b/dasCore/src/main/java/org/das2/client/Authenticator.java @@ -120,11 +120,7 @@ public void actionPerformed( ActionEvent e ) { if ( Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ) ) { lockingKeyWarning+= ", CAPS LOCK is on"; } - - if ( Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_NUM_LOCK ) ) { - lockingKeyWarning+= ", NUM LOCK is on"; - } - + if ( !"".equals( lockingKeyWarning ) ) { feedbackLabel.setText(lockingKeyWarning.substring(2)); } From 0cb579e8674017b51a419370354a70399a67d60e Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Thu, 9 Feb 2017 00:50:07 +0000 Subject: [PATCH 29/46] Updates to support Generic Time series digitizer (formally Cutoff Digitizer) svn path=/core/stable/dasCore/; revision=9693 --- .../components/VerticalSpectrogramSlicer.java | 11 +- .../org/das2/event/CutoffMouseModule.java | 351 +++++++++++++----- .../java/org/das2/graph/AttachedLabel.java | 6 +- .../main/java/org/das2/graph/DasCanvas.java | 1 + .../org/das2/graph/DasCanvasComponent.java | 2 +- 5 files changed, 268 insertions(+), 103 deletions(-) diff --git a/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java index cbabcaf6a..750b0cbd3 100755 --- a/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java +++ b/dasCore/src/main/java/org/das2/components/VerticalSpectrogramSlicer.java @@ -56,6 +56,7 @@ public class VerticalSpectrogramSlicer private long eventBirthMilli; private SymbolLineRenderer renderer; private Color yMarkColor = new Color(230,230,230); + private String frameTitle = "Vertical Slicer"; protected VerticalSpectrogramSlicer(DasPlot parent, DasAxis xAxis, DasAxis yAxis) { super( xAxis, yAxis); @@ -103,6 +104,14 @@ private void showPopupImpl() { } popupWindow.setVisible(true); } + + /* Would have done this in a base class but I didn't see one that handled the + * popup window */ + public void setFrameTitle(String s){ + frameTitle = s; + if(popupWindow != null) popupWindow.setTitle(frameTitle); + } + public String getFrameTitle(){ return frameTitle; } /** This method should ONLY be called by the AWT event thread */ private void createPopup() { @@ -140,7 +149,7 @@ else if (parentWindow instanceof Dialog) { else { popupWindow = new JDialog(); } - popupWindow.setTitle("Vertical Slicer"); + popupWindow.setTitle(frameTitle); popupWindow.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); popupWindow.setContentPane(content); popupWindow.pack(); diff --git a/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java b/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java index 214364252..c99ea6839 100644 --- a/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java +++ b/dasCore/src/main/java/org/das2/event/CutoffMouseModule.java @@ -36,14 +36,20 @@ import org.das2.graph.SymbolLineRenderer; import org.das2.util.monitor.ProgressMonitor; import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; import java.beans.PropertyChangeEvent; import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JFrame; +import org.das2.util.monitor.NullProgressMonitor; /** - * + * CutoffMouseModule contains Ondrej's code for selecting the cutoff, and allows + * operator to graphically adjust the control parameters. * @author Jeremy */ public class CutoffMouseModule extends BoxSelectorMouseModule { @@ -56,29 +62,47 @@ public class CutoffMouseModule extends BoxSelectorMouseModule { CutoffSlicer cutoffSlicer; DasApplication application; + private static final Logger logger= org.das2.system.DasLogger.getLogger( + org.das2.system.DasLogger.DATA_OPERATIONS_LOG ); + public CutoffMouseModule( DasPlot parent, DataSetConsumer consumer ) { - super( parent, parent.getXAxis(), parent.getYAxis(), consumer, new BoxRenderer(parent,true), "Cutoff" ); + super( parent, parent.getXAxis(), parent.getYAxis(), consumer, + new BoxRenderer(parent,true), "Cutoff" ); application= parent.getCanvas().getApplication(); this.dataSetConsumer= consumer; } - public static final String CONFIG_VOYAGER_HR_LOWER= "Ondrej: min=-4. slopeMin=0.26 nave=3 cutoff=lower xres=1s"; - public static final String CONFIG_GALILEO_LOWER= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=120s"; - public static final String CONFIG_GALILEO_LOWER_60= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=60s"; - public static final String CONFIG_GALILEO_LOWER_30= "Ondrej: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=30s"; - - /** - * see CONFIG_VOYAGER_HR_LOWER, etc. + public static final String ALGO_ONDREJ = "Ondrej: "; + + /** Set the configuration of slope calculation and digitizer output using an + * algorithm selection and configuration string. At present only one algorithm is + * supported: + * + * ALGO_ONDREJ + * + * Parameter settings for the Ondrej algorithm are: + * min: (TODO explain) + * slopeMin (TODO explain) + * nave (TODO explain) + * cutoff (TODO explain) + * xres (TODO explain) + * + * Example configuration strings known to work are: + * + * For Voyager FFT'ed Waveforms: min=-4. slopeMin=0.26 nave=3 cutoff=lower xres=1s + * For Galileo Survey Spectra: min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=120s + * min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=60s + * min=1.78 slopeMin=0.072 nave=3 cutoff=lower xres=30s * @param config */ - public void setConfig( String config ) throws ParseException { - if ( !config.startsWith("Ondrej:") ) { - throw new IllegalArgumentException("config must start with Ondrej"); - } + public void setConfig(String algo, String config ) throws ParseException { + if ( !algo.equals(ALGO_ONDREJ) ) + throw new IllegalArgumentException("Only Ondrej's cutoff algorithim has been" + + " implemented at this time"); Pattern p= Pattern.compile("(\\S+)=(\\S+)"); - Matcher m= p.matcher(config.substring(7) ); + Matcher m= p.matcher(config); while ( m.find() ) { String name= m.group(1); @@ -97,6 +121,7 @@ public void setConfig( String config ) throws ParseException { } } + @Override protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) { DatumRange xrange0= xrange; @@ -107,20 +132,28 @@ protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) { if ( event.getPlane("keyChar")!=null ) { lastComment= (String)event.getPlane("keyChar"); } else { - lastComment= null; + if ( xrange.width().lt( getXResolution().multiply(5) ) ) { + super.fireBoxSelectionListenerBoxSelected(event); + } + return; } - try { - recalculateSoon( ); - } catch ( RuntimeException ex ) { - xrange= xrange0; - yrange= yrange0; - throw ex; + String keyChar= String.valueOf( event.getPlane("keyChar") ); + if ( keyChar.equals("!") ) { // note null becomes "null" + assertChannel(new NullProgressMonitor()); + } else { + try { + recalculateSoon( ); + } catch ( RuntimeException ex ) { + xrange= xrange0; + yrange= yrange0; + throw ex; + } } } /** - * return RebinDescriptor that is on descrete, repeatable boundaries. + * return RebinDescriptor that is on discrete, repeatable boundaries. * get us2000, divide by resolution, truncate, multiply by resolution. */ private RebinDescriptor getRebinDescriptor( DatumRange range ) { @@ -137,14 +170,62 @@ private RebinDescriptor getRebinDescriptor( DatumRange range ) { private void recalculateSoon( ) { Runnable run= new Runnable() { + @Override public void run() { - ProgressMonitor mon= application.getMonitorFactory().getMonitor( parent, "calculating cutoffs", "calculating cutoffs" ); + ProgressMonitor mon= application.getMonitorFactory().getMonitor( + parent, "calculating cutoffs", "calculating cutoffs" ); recalculate( mon ); } }; new Thread( run, "digitizer recalculate" ).start(); } + private synchronized void assertChannel( ProgressMonitor mon ) { + TableDataSet tds= (TableDataSet)dataSetConsumer.getConsumedDataSet(); + if ( tds==null ) return; + if ( xrange==null ) return; + + tds= new ClippedTableDataSet( tds, xrange, yrange ); + + if ( xResolution.value()>0. ) { + // average the data down to xResolution + DataSetRebinner rebinner= new AverageTableRebinner(); + DatumRange range= DataSetUtil.xRange( tds ); + RebinDescriptor ddx= getRebinDescriptor( range ); + try { + tds= (TableDataSet)rebinner.rebin( tds, ddx, null, null ); + } catch (IllegalArgumentException | DasException ex) { + Logger.getLogger(CutoffMouseModule.class.getName()).log(Level.SEVERE, null, ex); + } + } + + VectorDataSetBuilder builder= new VectorDataSetBuilder( tds.getXUnits(), tds.getYUnits() ); + mon.setTaskSize( tds.getXLength() ); + mon.started(); + + Datum level= tds.getYTagDatum( 0, tds.getYLength(0)/2 ); + + for ( int i=0; i0 ) { + builder.setProperty( DataSet.PROPERTY_X_TAG_WIDTH, this.xResolution ); + } + VectorDataSet vds= builder.toVectorDataSet(); + + fireDataSetUpdateListenerDataSetUpdated( new DataSetUpdateEvent( this,vds ) ); + + + } + private synchronized void recalculate( ProgressMonitor mon) { TableDataSet tds= (TableDataSet)dataSetConsumer.getConsumedDataSet(); if ( tds==null ) return; @@ -153,16 +234,17 @@ private synchronized void recalculate( ProgressMonitor mon) { tds= new ClippedTableDataSet( tds, xrange, yrange ); // average the data down to xResolution - DataSetRebinner rebinner= new AverageTableRebinner(); - DatumRange range= DataSetUtil.xRange( tds ); - RebinDescriptor ddx= getRebinDescriptor( range ); - - try { - //TODO: why does rebin throw DasException? - tds= (TableDataSet)rebinner.rebin( tds, ddx, null, null ); - } catch ( DasException e ) { - throw new RuntimeException(e); + if ( xResolution.value()>0. ) { + // average the data down to xResolution + DataSetRebinner rebinner= new AverageTableRebinner(); + DatumRange range= DataSetUtil.xRange( tds ); + RebinDescriptor ddx= getRebinDescriptor( range ); + try { + tds= (TableDataSet)rebinner.rebin( tds, ddx, null, null ); + } catch (IllegalArgumentException | DasException ex) { + Logger.getLogger(CutoffMouseModule.class.getName()).log(Level.SEVERE, null, ex); + } } VectorDataSetBuilder builder= new VectorDataSetBuilder( tds.getXUnits(), tds.getYUnits() ); @@ -174,10 +256,10 @@ private synchronized void recalculate( ProgressMonitor mon) { mon.setTaskProgress( i ); if ( mon.isCancelled() ) break; VectorDataSet spec= DataSetUtil.log10( tds.getXSlice(i) ); - int icutoff= cutoff( spec, slopeMin, nave, isLowCutoff() ? 1 : -1, levelMin ); + int icutoff= cutoff( spec, slopeMin, nave, isLowCutoff() ? 1 : -1, levelMin, cutoffSlicer ); if ( icutoff>-1 ) { builder.insertY( tds.getXTagDatum(i), tds.getYTagDatum( tds.tableOfIndex(i), icutoff ) ); - } else { + } else if ( icutoff<0 ) { Units yunits=tds.getYUnits(); builder.insertY( tds.getXTagDatum(i), yunits.createDatum(yunits.getFillDouble()) ); } @@ -192,7 +274,9 @@ private synchronized void recalculate( ProgressMonitor mon) { comment= lastComment + " "+comment; } builder.setProperty("comment",comment); - builder.setProperty( DataSet.PROPERTY_X_TAG_WIDTH, this.xResolution ); + if ( this.xResolution.value()>0 ) { + builder.setProperty( DataSet.PROPERTY_X_TAG_WIDTH, this.xResolution ); + } VectorDataSet vds= builder.toVectorDataSet(); fireDataSetUpdateListenerDataSetUpdated( new DataSetUpdateEvent( this,vds ) ); @@ -200,14 +284,25 @@ private synchronized void recalculate( ProgressMonitor mon) { } /** - * slopeMin in the y units of ds. - * levelMin in the y units of ds. - * mult=-1 high cutoff, =1 low cutoff + * @param ds PSD vector (spectrum) + * @param slopeMin required PSD slope per frequency bin + * @param nave required bandwidth + * @param mult -1 for lower cutoff, 1 for upper cutoff (check this, I think Ondrej's + * got this backwards.) + * @param levelMin + * @param cutoffSlicer if available, render data to here for diagnostics + * @return the index of the cutoff. */ - public int cutoff( VectorDataSet ds, Datum slopeMin, int nave, int mult, Datum levelMin ) { + public static int cutoff( + VectorDataSet ds, Datum slopeMin, int nave, int mult, Datum levelMin, + CutoffSlicer cutoffSlicer + ) { + assert mult==-1 || mult==1; + int nfr= ds.getXLength(); if ( nfr < (nave+1) ) { - throw new IllegalArgumentException("DataSet doesn't contain enough elements"); + logger.fine( "DataSet doesn't contain enough elements" ); + return 0; } double[] cumul= new double[nfr]; Units units= ds.getYUnits(); @@ -228,7 +323,8 @@ public int cutoff( VectorDataSet ds, Datum slopeMin, int nave, int mult, Datum l icof[0]= false; // let's be explicit icof[nfr-1]= false; // the tests can't reach this one as well. - for ( int k=1; k<=nave; k++ ) { + //TODO: describe what is happening here! + for ( int k=1; k<=nave; k++ ) { // TODO: rewrite. This may be correct but it is opaque. double[] ave= new double[nfr]; ave[0]= cumul[k-1]/k; for ( int j=0; j0 ? ave[j+k] : ave[j]; if ( uave <= level ) icof[j]=false; - icofBuilder.insertY( ds.getXTagDatum(j), icof[j] ? units.dimensionless.createDatum(1) : units.dimensionless.createDatum(0) ); + icofBuilder.insertY( ds.getXTagDatum(j), + icof[j] ? units.dimensionless.createDatum(1) : units.dimensionless.createDatum(0) ); } } @@ -253,17 +350,27 @@ public int cutoff( VectorDataSet ds, Datum slopeMin, int nave, int mult, Datum l int icutOff=-1; - for ( int j= ( mult<0 ? nfr-1 : 0 ); j>=0 && j=0; j-- ) { + if ( icof[j] ) { + icutOff= j; + break; + } + } + + } else { + for ( int j= 0; j0 ) { + DatumRange range= DataSetUtil.xRange( tds ); + RebinDescriptor ddx= getRebinDescriptor( range ); + + try { + tds= (TableDataSet)rebinner.rebin( tds, ddx, null, null ); + } catch ( DasException e ) { + throw new RuntimeException(e); + } } int i= DataSetUtil.closestColumn( tds, event.getX() ); @@ -472,7 +618,7 @@ public void dataPointSelected(org.das2.event.DataPointSelectionEvent event) { VectorDataSet spec= DataSetUtil.log10( tds.getXSlice(i) ); - int icutoff= cutoff( spec, slopeMin, nave, isLowCutoff() ? 1 : -1, levelMin ); + int icutoff= cutoff( spec, slopeMin, nave, isLowCutoff() ? 1 : -1, levelMin, cutoffSlicer ); if ( icutoff==-1 ) { cutoff= spec.getXUnits().getFillDatum(); } else { @@ -497,38 +643,12 @@ public DataPointSelectionListener getSlicer( DasPlot plot, TableDataSetConsumer cutoffSlicer= new CutoffSlicer( plot, xAxis ); return cutoffSlicer; - } - - private void testCutoff() { - // see /home/jbf/voyager/cutoff/input.txt - double[] spec= new double[] { - -12.7093, -12.8479, -13.0042, -13.1509, -13.3007, -13.4671, - -13.5536, -13.6603, -13.8000, -13.8873, -13.9908, -14.1162, - -14.2016, -14.2694, -14.2844, -14.3126, -14.3507, -14.3841, - -14.4252, -14.4779, -14.4972, -14.5226, -14.6059, -14.6517, - -14.6545, -14.2863, -13.9616, -13.6898, -13.7407, -13.8821, - -14.1541, -14.4287, -14.6663, -14.8647, -15.0540, -15.0863, - -15.1190, -15.1464, -15.1479, -15.1399, -15.1284, -15.2001, - -15.2780, -15.3611, -15.3976, -15.4230, -15.4467, -15.4879, - -15.5437, -15.6058, -15.6501, -15.6606, -15.6737, -15.6867, - -15.6955, -15.7425, -15.8222, -15.9376, -16.0174, -16.0091, - }; - double[] tags= new double[ spec.length ]; - for ( int i=0; i< tags.length; i++ ) { tags[i]= i+1; } - double slope= 0.266692; - int nave=3; - int mult= isLowCutoff() ? 1 : -1; - double level= -14; - int icut= cutoff( - new DefaultVectorDataSet( spec, Units.hertz, spec, Units.v2pm2Hz, new HashMap() ), - Units.v2pm2Hz.createDatum(slope), nave, mult, Units.v2pm2Hz.createDatum(level) ); - System.out.println("icut="+icut+" should be 25"); - } - + } private transient java.util.ArrayList dataSetUpdateListenerList; - public synchronized void addDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) { + public synchronized void addDataSetUpdateListener(org.das2.dataset.DataSetUpdateListener listener) + { if (dataSetUpdateListenerList == null ) { dataSetUpdateListenerList = new java.util.ArrayList(); } @@ -547,9 +667,9 @@ private void fireDataSetUpdateListenerDataSetUpdated(org.das2.dataset.DataSetUpd if (dataSetUpdateListenerList == null) return; list = (java.util.ArrayList)dataSetUpdateListenerList.clone(); } - for (int i = 0; i < list.size(); i++) { - ((org.das2.dataset.DataSetUpdateListener)list.get(i)).dataSetUpdated(event); - } + for(Object listener : list){ + ((org.das2.dataset.DataSetUpdateListener) listener).dataSetUpdated(event); + } } /** @@ -631,7 +751,8 @@ public void setNave(int nave) { int oldVal= this.nave; if ( this.nave!=nave ) { this.nave = nave; - PropertyChangeEvent e= new PropertyChangeEvent( this, "nave", new Integer(oldVal), new Integer(nave) ); + PropertyChangeEvent e= new PropertyChangeEvent( + this, "nave", new Integer(oldVal), new Integer(nave) ); firePropertyChangeListenerPropertyChange( e ); recalculateSoon(); } @@ -647,7 +768,8 @@ public void setXResolution(Datum xResolution) { Datum oldVal= this.xResolution; if ( !this.xResolution.equals( xResolution ) ) { this.xResolution = xResolution; - PropertyChangeEvent e= new PropertyChangeEvent( this, "timeResolution", oldVal, this.xResolution ); + PropertyChangeEvent e= new PropertyChangeEvent( + this, "timeResolution", oldVal, this.xResolution ); firePropertyChangeListenerPropertyChange( e ); recalculateSoon(); } @@ -673,10 +795,11 @@ public boolean isLowCutoff() { * @param lowCutoff New value of property lowCutoff. */ public void setLowCutoff(boolean lowCutoff) { - Boolean oldVal= Boolean.valueOf( this.lowCutoff ); + Boolean oldVal= this.lowCutoff; if ( this.lowCutoff!=lowCutoff ) { this.lowCutoff = lowCutoff; - PropertyChangeEvent e= new PropertyChangeEvent( this, "lowCutoff", oldVal, Boolean.valueOf(lowCutoff) ); + PropertyChangeEvent e= new PropertyChangeEvent( + this, "lowCutoff", oldVal, Boolean.valueOf(lowCutoff) ); firePropertyChangeListenerPropertyChange( e ); recalculateSoon(); if ( this.cutoffSlicer != null ) this.cutoffSlicer.slopePlot.repaint(); @@ -722,4 +845,32 @@ private void firePropertyChangeListenerPropertyChange(java.beans.PropertyChangeE } } + + private static void testCutoff() { + // see /home/jbf/voyager/cutoff/input.txt + double[] spec= new double[] { + -12.7093, -12.8479, -13.0042, -13.1509, -13.3007, -13.4671, + -13.5536, -13.6603, -13.8000, -13.8873, -13.9908, -14.1162, + -14.2016, -14.2694, -14.2844, -14.3126, -14.3507, -14.3841, + -14.4252, -14.4779, -14.4972, -14.5226, -14.6059, -14.6517, + -14.6545, -14.2863, -13.9616, -13.6898, -13.7407, -13.8821, + -14.1541, -14.4287, -14.6663, -14.8647, -15.0540, -15.0863, + -15.1190, -15.1464, -15.1479, -15.1399, -15.1284, -15.2001, + -15.2780, -15.3611, -15.3976, -15.4230, -15.4467, -15.4879, + -15.5437, -15.6058, -15.6501, -15.6606, -15.6737, -15.6867, + -15.6955, -15.7425, -15.8222, -15.9376, -16.0174, -16.0091, + }; + double[] tags= new double[ spec.length ]; + for ( int i=0; i< tags.length; i++ ) { tags[i]= i+1; } + double slope= 0.266692; + + int nave=3; + int mult= -1; // low cutoff + double level= -14; + int icut= cutoff( + new DefaultVectorDataSet( spec, Units.hertz, spec, Units.v2pm2Hz, new HashMap() ), + Units.v2pm2Hz.createDatum(slope), nave, mult, Units.v2pm2Hz.createDatum(level), null ); + System.out.println("icut="+icut+" should be 25"); + } + } diff --git a/dasCore/src/main/java/org/das2/graph/AttachedLabel.java b/dasCore/src/main/java/org/das2/graph/AttachedLabel.java index ae1ebc4ed..5c3f403f8 100644 --- a/dasCore/src/main/java/org/das2/graph/AttachedLabel.java +++ b/dasCore/src/main/java/org/das2/graph/AttachedLabel.java @@ -67,6 +67,8 @@ public class AttachedLabel extends DasCanvasComponent implements Cloneable { private double emOffset; private boolean flipLabel = false; + + private Font font = null; // Stays null unless setLabelFont is called /* DEBUGGING INSTANCE MEMBERS */ private static final boolean DEBUG_GRAPHICS = false; @@ -343,6 +345,7 @@ public void setLabelFlipped(boolean flipLabel) { * @return Font of the component. */ public Font getLabelFont() { + if(font != null) return font; return this.getFont(); } @@ -350,7 +353,8 @@ public Font getLabelFont() { * @param labelFont Font for the component. Currently this is ignored. */ public void setLabelFont(Font labelFont) { - // TODO: whah?--jbf + font = labelFont; + repaint(); } /** clones the component diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvas.java b/dasCore/src/main/java/org/das2/graph/DasCanvas.java index 5a7d8f6ec..675ab01dc 100755 --- a/dasCore/src/main/java/org/das2/graph/DasCanvas.java +++ b/dasCore/src/main/java/org/das2/graph/DasCanvas.java @@ -45,6 +45,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Container; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; diff --git a/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java b/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java index 075f906f0..4fd00dd25 100755 --- a/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java +++ b/dasCore/src/main/java/org/das2/graph/DasCanvasComponent.java @@ -81,7 +81,7 @@ public void actionPerformed(ActionEvent e) { private ResizeListener rl; protected DasMouseInputAdapter mouseAdapter; private String dasName; - + /** * constructs a DasCanvasComponent, creating the * DasMouseInputAdapter for it and assigning a From 2204f2d903605eec792f1566b8483aa616ac7e80 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Fri, 10 Feb 2017 07:48:02 +0000 Subject: [PATCH 30/46] Backported function form dasQCore for setting datum range from arbitrary string svn path=/core/stable/dasCore/; revision=9699 --- .../java/org/das2/datum/DatumRangeUtil.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java b/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java index 04a2fc6ba..d139180e4 100644 --- a/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java +++ b/dasCore/src/main/java/org/das2/datum/DatumRangeUtil.java @@ -936,6 +936,54 @@ public static DatumRange parseDatumRange( String str, Units units ) throws Parse } } } + + + /** Temporary Hack until apps are moved over to the new dasQCore --cwp + * + * This provides unambiguous rules for parsing all types datum ranges strictly + * from strings, with no out of band information. This was introduced to + * support das2stream parsing. + * + * Examples include: "2013 to 2015 UTC" "3 to 4 kg" "2015-05-05T00:00/2015-06-02T00:00" + * @param str the string representing a time. + * @return the DatumRange interpreted. + * @throws java.text.ParseException + */ + public static DatumRange parseDatumRange(String str) throws ParseException { + str= str.trim(); + if ( str.endsWith("UTC" ) ) { + return parseTimeRange(str.substring(0,str.length()-3)); + // Version in DasQCore can look for times without UTC appended, this version + // can't + } else { + // consider Patterns -- dash not handled because of negative sign. + // 0to4 apples -> 0 to 4 units=apples + // 0 to 35 sector -> 0 to 35 units=sector note "to" in sector. + String[] ss= str.split("to",2); + if ( ss.length==1 ) { + ss= str.split("\u2013"); + } + if ( ss.length==1 ) { + return parseTimeRange(ss[0]); + } else if ( ss.length != 2 ) { + throw new ParseException("failed to parse: "+str,0); + } + + Datum d2; + Datum d1; + try { + d2= DatumUtil.parse(ss[1]); + d1= d2.getUnits().parse( ss[0] ); + return new DatumRange( d1, d2 ); + } catch ( ParseException ex ) { + try { + return parseTimeRange(str); + } catch ( ParseException ex2 ) { + throw ex; + } + } + } + } public static DatumRange parseDatumRange( String str, DatumRange orig ) throws ParseException { return parseDatumRange( str, orig.getUnits() ); From 7e34c41c1b061a0f103933bb89be693112ff7804 Mon Sep 17 00:00:00 2001 From: Edward West Date: Fri, 3 Mar 2017 22:09:32 +0000 Subject: [PATCH 31/46] BoxRangeSelectorMouseModule now checks for isFlipped() on y axis svn path=/core/stable/dasCore/; revision=9760 --- .../das2/event/BoxRangeSelectorMouseModule.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java b/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java index 6b2cff539..5c9584830 100755 --- a/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java +++ b/dasCore/src/main/java/org/das2/event/BoxRangeSelectorMouseModule.java @@ -45,7 +45,10 @@ public class BoxRangeSelectorMouseModule extends MouseModule { private EventListenerList listenerList = new javax.swing.event.EventListenerList(); /** + * @param parent the DasCanvasComponent this module will be associated with * @param consumer is the source context of the data set selection + * @param xAxis the horizontal axis to use when transforming from pixel to data space + * @param yAxis the vertical axis to use when transforming from pixel to data space */ public BoxRangeSelectorMouseModule(DasCanvasComponent parent, DataSetConsumer consumer, DasAxis xAxis, DasAxis yAxis) { super( parent, new BoxGesturesRenderer(parent), "Box Selection" ); @@ -66,14 +69,22 @@ public static BoxRangeSelectorMouseModule create(DasPlot parent) { return result; } + @Override public void mouseRangeSelected(MouseDragEvent e0) { if (e0 instanceof MouseBoxEvent) { MouseBoxEvent e= (MouseBoxEvent)e0; Datum xMin = xAxis.invTransform(e.getXMinimum()); Datum xMax = xAxis.invTransform(e.getXMaximum()); - Datum yMin = yAxis.invTransform(e.getYMaximum()); - Datum yMax = yAxis.invTransform(e.getYMinimum()); + Datum yMin, yMax; + if (yAxis.isFlipped()) { + yMin = yAxis.invTransform(e.getYMinimum()); + yMax = yAxis.invTransform(e.getYMaximum()); + } + else { + yMin = yAxis.invTransform(e.getYMaximum()); + yMax = yAxis.invTransform(e.getYMinimum()); + } BoxSelectionEvent evt = new BoxSelectionEvent(this, new DatumRange( xMin, xMax ), new DatumRange( yMin, yMax) ); if (consumer != null) { evt.setDataSet(consumer.getConsumedDataSet()); From c596b20af14278f4515399b022366b401081b601 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Tue, 22 May 2018 20:25:09 +0000 Subject: [PATCH 32/46] Updated plane class to support making ytags from a sequence svn path=/core/stable/dasCore/; revision=10668 --- dasCore/pom.xml | 51 +++++++++++++++++-- .../das2/components/TearoffTabbedPane.java | 26 +++++++++- .../dataset/NearestNeighborTableDataSet.java | 11 ++-- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/dasCore/pom.xml b/dasCore/pom.xml index 61beb0ad8..3872be979 100644 --- a/dasCore/pom.xml +++ b/dasCore/pom.xml @@ -36,7 +36,6 @@ - - + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar + + + + + + @@ -63,7 +75,17 @@ default Repository for library Library[swing-layout] + + + + true + false + org.das2.releases + Das2 Releases + http://das2.org/maven2/releases + + UTF-8 @@ -101,4 +123,25 @@ 1.5 + + + + + org.das2.releases + Das2 Public Component Repository + file:///opt/project/das2/repositories/maven2/releases + + + + + org.das2.snapshots + Das2 Daily Build Component Repository + file:///opt/project/das2/repositories/maven2/snapshots + + + diff --git a/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java b/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java index 86226e6cd..b0513bab9 100644 --- a/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java +++ b/dasCore/src/main/java/org/das2/components/TearoffTabbedPane.java @@ -63,7 +63,7 @@ public class TearoffTabbedPane extends JTabbedPane { private final static Logger logger= Logger.getLogger( TearoffTabbedPane.class.getCanonicalName() ); - HashMap tabs = new HashMap(); + HashMap tabs = new HashMap<>(); int lastSelected; /* keep track of selected index before context menu */ private static void copyInputMap(JFrame parent, JFrame babySitter) { @@ -136,6 +136,7 @@ private TearoffTabbedPane(TearoffTabbedPane parent) { private MouseMotionListener getMouseMotionListener() { return new MouseMotionListener() { + @Override public void mouseDragged(MouseEvent e) { if (selectedTab == -1) { return; @@ -168,6 +169,7 @@ public void mouseDragged(MouseEvent e) { } } + @Override public void mouseMoved(MouseEvent e) { } }; @@ -195,6 +197,7 @@ private MouseAdapter getChildMouseAdapter() { { dockMenu.add(new JMenuItem(new AbstractAction("dock") { + @Override public void actionPerformed(ActionEvent event) { TabDesc desc = null; @@ -221,6 +224,7 @@ public void actionPerformed(ActionEvent event) { })); } + @Override public void mousePressed(MouseEvent event) { selectedTab = TearoffTabbedPane.this.indexAtLocation(event.getX(), event.getY()); if (event.isPopupTrigger()) { @@ -253,12 +257,14 @@ private MouseAdapter getParentMouseAdapter() { { tearOffMenu.add(new JMenuItem(new AbstractAction("undock") { + @Override public void actionPerformed(ActionEvent event) { TearoffTabbedPane.this.tearOffIntoFrame(selectedTab); } })); tearOffMenu.add(new JMenuItem(new AbstractAction("slide right") { + @Override public void actionPerformed(ActionEvent event) { TearoffTabbedPane.this.slideRight(selectedTab); } @@ -270,6 +276,7 @@ public void actionPerformed(ActionEvent event) { { dockMenu.add(new JMenuItem(new AbstractAction("show") { + @Override public void actionPerformed(ActionEvent event) { TabDesc desc = null; Component babyComponent = null; @@ -297,6 +304,7 @@ public void actionPerformed(ActionEvent event) { })); dockMenu.add(new JMenuItem(new AbstractAction("dock") { + @Override public void actionPerformed(ActionEvent event) { TabDesc desc = null; Component babyComponent = null; @@ -326,6 +334,7 @@ public void actionPerformed(ActionEvent event) { })); } + @Override public void mousePressed(MouseEvent event) { selectedTab = TearoffTabbedPane.this.indexAtLocation(event.getX(), event.getY()); if (event.isPopupTrigger()) { @@ -394,6 +403,7 @@ public ComponentListener getFrameComponentListener( Component activeComponent; long activeComponentTime=0; + @Override public void componentResized(ComponentEvent e) { long t= System.currentTimeMillis(); if ( ( t-activeComponentTime ) > 100 ) { @@ -405,6 +415,7 @@ public void componentResized(ComponentEvent e) { } } + @Override public void componentMoved(ComponentEvent e) { long t= System.currentTimeMillis(); if ( ( t-activeComponentTime ) > 100 ) { @@ -416,9 +427,11 @@ public void componentMoved(ComponentEvent e) { } } + @Override public void componentShown(ComponentEvent e) { } + @Override public void componentHidden(ComponentEvent e) { } }; @@ -550,6 +563,7 @@ protected JFrame tearOffIntoFrame(int tabIndex) { babySitter.setIconImage( parent.getIconImage() ); final WindowStateListener listener = new WindowStateListener() { + @Override public void windowStateChanged(WindowEvent e) { babySitter.setExtendedState(parent.getExtendedState()); } @@ -560,6 +574,7 @@ public void windowStateChanged(WindowEvent e) { babySitter.setLocation(p); babySitter.addWindowListener(new WindowAdapter() { + @Override public void windowClosing(WindowEvent e) { parent.removeWindowStateListener(listener); dock(c); @@ -591,30 +606,37 @@ public void dock(Component c) { setSelectedIndex(selectedIndex); } + @Override public void addTab(String title, Icon icon, Component component) { super.addTab(title, icon, component); TabDesc td = new TabDesc(title, icon, component, null, indexOfComponent(component)); tabs.put(component, td); } + @Override public void addTab(String title, Component component) { super.addTab(title, component); TabDesc td = new TabDesc(title, null, component, null, indexOfComponent(component)); tabs.put(component, td); } + @Override public void insertTab(String title, Icon icon, Component component, String tip, int index) { super.insertTab(title, icon, component, tip, index); TabDesc td = new TabDesc(title, icon, component, tip, index); tabs.put(component, td); } + @Override public void addTab(String title, Icon icon, Component component, String tip) { super.addTab(title, icon, component, tip); TabDesc td = new TabDesc(title, icon, component, tip, indexOfComponent(component)); tabs.put(component, td); } + // This implementation looks backwords. It looks like an implementation of + // getComponentByIndex, not getTabComponentByIndex + private Component getTabComponentByIndex(int index) { for (Component key : tabs.keySet()) { TabDesc td = tabs.get(key); @@ -635,6 +657,7 @@ private Component getTabComponentByTitle(String title) { return null; } + @Override public void removeTabAt(int index) { Component c = getTabComponentByIndex(index); super.removeTabAt(index); @@ -653,6 +676,7 @@ public void removeTabAt(int index) { } + @Override public void setSelectedIndex(int index) { if (index != getSelectedIndex()) { lastSelected = getSelectedIndex(); diff --git a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java index 3b174863a..d9a997f03 100755 --- a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java +++ b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java @@ -50,13 +50,16 @@ public class NearestNeighborTableDataSet implements TableDataSet { } else { - // Cascade getting the interpolation widths - Datum xTagWidth = (Datum)m_override.get(DataSet.PROPERTY_X_TAG_WIDTH); - if(xTagWidth == null) - xTagWidth= (Datum)source.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + // Cascade getting the interpolation widths. If the given or calculated + // x-tag width is greater than the over-ride width, use that + Datum xTagWidth= (Datum)source.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); if ( xTagWidth==null ) xTagWidth= DataSetUtil.guessXTagWidth(source); + Datum xOverRideWidth = (Datum)m_override.get(DataSet.PROPERTY_X_TAG_WIDTH); + if(xTagWidth == null || xTagWidth.compareTo(xOverRideWidth) < 0) + xTagWidth = xOverRideWidth; + Datum yTagWidth = (Datum)m_override.get(DataSet.PROPERTY_Y_TAG_WIDTH); if(yTagWidth == null) yTagWidth = (Datum)source.getProperty(DataSet.PROPERTY_Y_TAG_WIDTH); From 91b8823a9f1aadc9e3a3fb5f46baacb2310a28d5 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Sat, 29 Sep 2018 16:17:38 +0000 Subject: [PATCH 33/46] Updated string representation of CalTime svn path=/core/stable/dasCore/; revision=10898 --- dasCore/Linux.mak | 143 ++++++++++++++++++ .../java/org/das2/datum/CalendarTime.java | 4 +- 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 dasCore/Linux.mak diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak new file mode 100644 index 000000000..7edcb950e --- /dev/null +++ b/dasCore/Linux.mak @@ -0,0 +1,143 @@ +# A mostly generic makefile that assumes there is one or more app jars in +# +# ./target +# +# and 0 or more library jars +# +# ./target/lib +# +# Since mave does most of the work, you're pom.xml will have to configure +# the maven-dependency-plugin to put the needed jars into ./target/lib + + +# Generic definitions that have to be formed in a platform specific way: + +ifeq ($(H_ARCH),) +JAVAVER:=$(shell javac platver.java && java -cp . platver 2>/dev/null) +ifeq ($(JAVAVER),) +$(error Couldn't determine your java platform version) +endif +H_ARCH=java$(JAVAVER) +endif + +ifeq ($(INST_HOST_LIB),) +INST_HOST_LIB=$(PREFIX)/lib/$(H_ARCH) +endif +export INST_HOST_LIB + +ifeq ($(INST_EXT_LIB),) +INST_EXT_LIB=$(PREFIX)/lib/$(N_ARCH)/$(H_ARCH) +endif + +# Project Targets ############################################################ + +LIB_JAR=dasCore-2.2.jar + +DEPEND_JARS=batik-awt-util-1.5.jar batik-svggen-1.5.jar \ + batik-util-1.5.jar bcmail-jdk14-138.jar bcprov-jdk14-138.jar \ + itext-2.0.7.jar junit-3.8.1.jar swing-layout-1.0.3.jar + +#SCRIPTS=das2_runrdr + +# Targets with autopaths ##################################################### + +BUILD_JAR=$(patsubst %.jar, target/%.jar, $(LIB_JAR)) + +#BUILD_SCRIPTS=$(patsubst %, target/%, $(SCRIPTS)) + +BUILD_DEPEND_JARS=$(patsubst %.jar, target/lib/%.jar, $(DEPEND_JARS)) + +INST_LIB_JARS=$(patsubst %.jar, $(INST_HOST_LIB)/%.jar, $(LIB_JAR)) \ + $(patsubst %.jar, $(INST_HOST_LIB)/%.jar, $(DEPEND_JARS)) + +#INST_SCRIPTS=$(patsubst %, $(INST_NAT_BIN)/%, $(SCRIPTS)) + +# Implicit Rules ############################################################# + + +# Building a bash script +#target/%:src/main/sh/%.in +# ./envsubst.py $< $@ + +# Installing a script +#$(INST_NAT_BIN)/%:target/% +# install -D -m 775 $< $@ + +# Installing libary jar +$(INST_HOST_LIB)/%.jar:target/%.jar + install -D -m 0664 $< $@ + +# Installing dependency libaries +$(INST_HOST_LIB)/%.jar:target/lib/%.jar + install -D -m 0664 $< $@ + + + +# Explicit rules ############################################################# + +# Maven is a development tool, not a deployment tool, hence it is being run +# by make as if it were just a compliler tool. Then install steps are handled +# here + +.PHONY : build package test install clean distclean + +build: $(BUILD_JAR) $(BUILD_DEPEND_JARS) $(BUILD_SCRIPTS) \ + target/site/apidocs/index.html + +$(BUILD_JAR) $(BUILD_DEPEND_JARS): + mvn -Dmaven.javadoc.skip=true package + +target/site/apidocs/index.html: + mvn generate-sources javadoc:javadoc + +test: + mvn integration-test + +show: + @echo BUILD_JARS: $(BUILD_JAR) $(BUILD_DEPEND_JARS) + @echo INST_LIB_JARS: $(INST_LIB_JARS) + + +install: $(INST_LIB_JARS) $(INST_SCRIPTS) + +$(INST_DOC)/dasCore/index.html:target/site/apidocs/index.html + if [ ! -e $(INST_DOC)/dasCore ]; then mkdir -p $(INST_DOC)/dasCore; fi + cp --remove-destination -r target/site/apidocs/* $(INST_DOC)/dasCore + chmod -R g+w $(INST_DOC)/dasCore + +clean: + mvn clean + + +# Need to get *.java file dependencies in here... + +distclean: + rm -r target + + +# maven build phases are (from apach.org): +# +# validate - validate the project is correct and all necessary information +# is available +# +# compile - compile the source code of the project +# +# test - test the compiled source code using a suitable unit testing +# framework. These tests should not require the code be packaged or +# deployed +# +# package - take the compiled code and package it in its distributable format, +# such as a JAR. +# +# integration-test - process and deploy the package if necessary into an +# environment where integration tests can be run +# +# verify - run any checks to verify the package is valid and meets quality +# criteria +# +# install - install the package into the local repository, for use as a +# dependency in other projects locally +# +# deploy - done in an integration or release environment, copies the final +# package to the remote repository for sharing with other developers +# and projects. diff --git a/dasCore/src/main/java/org/das2/datum/CalendarTime.java b/dasCore/src/main/java/org/das2/datum/CalendarTime.java index db6161068..d720f843e 100644 --- a/dasCore/src/main/java/org/das2/datum/CalendarTime.java +++ b/dasCore/src/main/java/org/das2/datum/CalendarTime.java @@ -529,8 +529,8 @@ public CalendarTime(Datum datum){ //////////////////////////////////////////////////////////////////////////////////// @Override public String toString(){ - return m_nYear + "/" + m_nMonth + "/" + m_nDom + " " + m_nHour + ":" + m_nMinute + ":" + m_nSecond + - "." + m_nNanoSecond; + return String.format("%04d-%02d-%02dT%02d:%02d%02d.%09d", m_nYear, m_nMonth, + m_nDom, m_nHour, m_nMinute, m_nSecond, m_nNanoSecond); } public boolean isLeapYear(){ return TimeUtil.isLeapYear(m_nYear); } From 181224404d5b690690b6d203f53058d6ad9cfaff Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Tue, 22 Jan 2019 23:07:34 +0000 Subject: [PATCH 34/46] Bug fix for instances where xTagWidth did not have an override value selected for table datasets svn path=/core/stable/dasCore/; revision=11119 --- .../das2/dataset/NearestNeighborTableDataSet.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java index d9a997f03..1b6895075 100755 --- a/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java +++ b/dasCore/src/main/java/org/das2/dataset/NearestNeighborTableDataSet.java @@ -50,15 +50,19 @@ public class NearestNeighborTableDataSet implements TableDataSet { } else { - // Cascade getting the interpolation widths. If the given or calculated - // x-tag width is greater than the over-ride width, use that - Datum xTagWidth= (Datum)source.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); + // Cascade getting the interpolation widths as follows: + // + // xOverRide = null : Use xTagWidth + // xOverRide > xTagWidth: Use xOverRide else use xTagWidth + Datum xTagWidth= (Datum)source.getProperty(DataSet.PROPERTY_X_TAG_WIDTH); if ( xTagWidth==null ) xTagWidth= DataSetUtil.guessXTagWidth(source); Datum xOverRideWidth = (Datum)m_override.get(DataSet.PROPERTY_X_TAG_WIDTH); - if(xTagWidth == null || xTagWidth.compareTo(xOverRideWidth) < 0) - xTagWidth = xOverRideWidth; + if(xOverRideWidth != null){ + xTagWidth = xTagWidth.compareTo(xOverRideWidth) < 0 ? + xOverRideWidth : xTagWidth ; + } Datum yTagWidth = (Datum)m_override.get(DataSet.PROPERTY_Y_TAG_WIDTH); if(yTagWidth == null) From 2e14ebcddb1dca8d7a67ffd4fde2a7d7d393fd5f Mon Sep 17 00:00:00 2001 From: Edward West Date: Thu, 30 May 2019 19:49:00 +0000 Subject: [PATCH 35/46] Simplified logic for turning interpolation off when bin is outside of data range. svn path=/core/stable/dasCore/; revision=11524 --- .../main/java/org/das2/dataset/AverageTableRebinner.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dasCore/src/main/java/org/das2/dataset/AverageTableRebinner.java b/dasCore/src/main/java/org/das2/dataset/AverageTableRebinner.java index 81bcf2a92..bab1c22eb 100755 --- a/dasCore/src/main/java/org/das2/dataset/AverageTableRebinner.java +++ b/dasCore/src/main/java/org/das2/dataset/AverageTableRebinner.java @@ -606,9 +606,12 @@ static void fillInterpolateY(final double[][] data, final double[][] weights, Re boolean doInterp; if ( i1[j]!= -1 && i2[j] != -1) { doInterp= ( yTagTemp[i2[j]]-yTagTemp[i1[j]] ) < ySampleWidth*2; - } else { - //kludge for bug 000321 - doInterp= Math.min(i1[j] == -1 ? Double.MAX_VALUE : (yTagTemp[j] - yTagTemp[i1[j]]), i2[j] == -1 ? Double.MAX_VALUE : (yTagTemp[i2[j]] - yTagTemp[j])) < ySampleWidth / 2; + } else if ( i1[j] != -1 ) { + doInterp= (yTagTemp[j] - yTagTemp[i1[j]]) < ySampleWidth / 2; + } else if ( i2[j] != -1 ) { + doInterp= (yTagTemp[i2[j]] - yTagTemp[j]) < ySampleWidth / 2; + } else { // If we reach this branch then both i1[j] and i2[j] equal -1 + doInterp= false; } if ( doInterp ) { int idx; From 710e157d3162f4fe64672e37d610a2923f018836 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Fri, 1 May 2020 05:08:45 +0000 Subject: [PATCH 36/46] Deploy into local .m2 repository step missing svn path=/core/stable/dasCore/; revision=11879 --- dasCore/Linux.mak | 1 + 1 file changed, 1 insertion(+) diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index 7edcb950e..d70d190a9 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -99,6 +99,7 @@ show: install: $(INST_LIB_JARS) $(INST_SCRIPTS) + mvn install $(INST_DOC)/dasCore/index.html:target/site/apidocs/index.html if [ ! -e $(INST_DOC)/dasCore ]; then mkdir -p $(INST_DOC)/dasCore; fi From f1e7ed1877c45a1b690feb020dc1c1579cfab777 Mon Sep 17 00:00:00 2001 From: cpiker Date: Mon, 12 Jul 2021 11:44:32 -0500 Subject: [PATCH 37/46] Hid netbeans project folder for maven project autodetect --- .gitignore | 3 +++ dasCore/{nbproject => nbproject.hide}/.cvsignore | 0 dasCore/{nbproject => nbproject.hide}/build-impl.xml | 0 dasCore/{nbproject => nbproject.hide}/genfiles.properties | 0 dasCore/{nbproject => nbproject.hide}/profiler-build-impl.xml | 0 dasCore/{nbproject => nbproject.hide}/project.properties | 0 dasCore/{nbproject => nbproject.hide}/project.xml | 0 7 files changed, 3 insertions(+) create mode 100644 .gitignore rename dasCore/{nbproject => nbproject.hide}/.cvsignore (100%) rename dasCore/{nbproject => nbproject.hide}/build-impl.xml (100%) rename dasCore/{nbproject => nbproject.hide}/genfiles.properties (100%) rename dasCore/{nbproject => nbproject.hide}/profiler-build-impl.xml (100%) rename dasCore/{nbproject => nbproject.hide}/project.properties (100%) rename dasCore/{nbproject => nbproject.hide}/project.xml (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f637f25c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/dasCore/nbproject/private +/dasCore/nbproject.hide/private +/dasCore/target diff --git a/dasCore/nbproject/.cvsignore b/dasCore/nbproject.hide/.cvsignore similarity index 100% rename from dasCore/nbproject/.cvsignore rename to dasCore/nbproject.hide/.cvsignore diff --git a/dasCore/nbproject/build-impl.xml b/dasCore/nbproject.hide/build-impl.xml similarity index 100% rename from dasCore/nbproject/build-impl.xml rename to dasCore/nbproject.hide/build-impl.xml diff --git a/dasCore/nbproject/genfiles.properties b/dasCore/nbproject.hide/genfiles.properties similarity index 100% rename from dasCore/nbproject/genfiles.properties rename to dasCore/nbproject.hide/genfiles.properties diff --git a/dasCore/nbproject/profiler-build-impl.xml b/dasCore/nbproject.hide/profiler-build-impl.xml similarity index 100% rename from dasCore/nbproject/profiler-build-impl.xml rename to dasCore/nbproject.hide/profiler-build-impl.xml diff --git a/dasCore/nbproject/project.properties b/dasCore/nbproject.hide/project.properties similarity index 100% rename from dasCore/nbproject/project.properties rename to dasCore/nbproject.hide/project.properties diff --git a/dasCore/nbproject/project.xml b/dasCore/nbproject.hide/project.xml similarity index 100% rename from dasCore/nbproject/project.xml rename to dasCore/nbproject.hide/project.xml From a1a013dc312649d062d1e586228e2356c7f9bef1 Mon Sep 17 00:00:00 2001 From: cpiker Date: Tue, 13 Jul 2021 09:36:17 -0500 Subject: [PATCH 38/46] Removed Javadoc build from default makefile --- .gitignore | 2 + dasCore/Linux.mak | 4 +- dasCore/Makefile | 46 +++++++++++++++ dasCore/SunOS.mak | 134 +++++++++++++++++++++++++++++++++++++++++++ dasCore/platver.java | 9 +++ 5 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 dasCore/Makefile create mode 100644 dasCore/SunOS.mak create mode 100644 dasCore/platver.java diff --git a/.gitignore b/.gitignore index f637f25c0..10df782f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /dasCore/nbproject/private /dasCore/nbproject.hide/private /dasCore/target +/dasCore/platver.class + diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index d70d190a9..05aecaefa 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -81,8 +81,8 @@ $(INST_HOST_LIB)/%.jar:target/lib/%.jar .PHONY : build package test install clean distclean -build: $(BUILD_JAR) $(BUILD_DEPEND_JARS) $(BUILD_SCRIPTS) \ - target/site/apidocs/index.html +build: $(BUILD_JAR) $(BUILD_DEPEND_JARS) $(BUILD_SCRIPTS) #\ +# target/site/apidocs/index.html $(BUILD_JAR) $(BUILD_DEPEND_JARS): mvn -Dmaven.javadoc.skip=true package diff --git a/dasCore/Makefile b/dasCore/Makefile new file mode 100644 index 000000000..631a3a366 --- /dev/null +++ b/dasCore/Makefile @@ -0,0 +1,46 @@ +############################################################################## +# Generic definitions for: Native + Hosted Java + +ifeq ($(PREFIX),) +ifeq ($(HOME),) +PREFIX=$(USERPROFILE) +else +PREFIX=$(HOME) +endif +endif + +ifeq ($(INST_ETC),) +INST_ETC=$(PREFIX)/etc +endif + +ifeq ($(INST_SHARE),) +INST_SHARE=$(PREFIX)/share +endif + +ifeq ($(INST_DOC),) +INST_DOC=$(INST_SHARE)/doc +endif + +ifeq ($(INST_INC),) +INST_INC=$(PREFIX)/include +endif + +ifeq ($(N_ARCH),) +N_ARCH=$(shell uname -s).$(shell uname -p) +endif + +ifeq ($(INST_NAT_BIN),) +INST_NAT_BIN=$(PREFIX)/bin/$(N_ARCH) +endif + +ifeq ($(INST_NAT_LIB),) +INST_NAT_LIB=$(PREFIX)/lib/$(N_ARCH) +endif + +############################################################################## +# Explicit Rules + +UNAME = $(shell uname) + +include $(UNAME).mak + diff --git a/dasCore/SunOS.mak b/dasCore/SunOS.mak new file mode 100644 index 000000000..8ba374ed6 --- /dev/null +++ b/dasCore/SunOS.mak @@ -0,0 +1,134 @@ +# A mostly generic makefile that assumes there is one or more app jars in +# +# ./target +# +# and 0 or more library jars +# +# ./target/lib +# +# Since mave does most of the work, you're pom.xml will have to configure +# the maven-dependency-plugin to put the needed jars into ./target/lib + + +# Generic definitions that have to be formed in a platform specific way: + +ifeq ($(H_ARCH),) +JAVAVER:=$(shell javac platver.java && java -cp . platver 2>/dev/null) +ifeq ($(JAVAVER),) +$(error Couldn't determine your java platform version) +endif +H_ARCH=java$(JAVAVER) +endif + +ifeq ($(INST_HOST_LIB),) +INST_HOST_LIB=$(PREFIX)/lib/$(H_ARCH) +endif +export INST_HOST_LIB + +ifeq ($(INST_EXT_LIB),) +INST_EXT_LIB=$(PREFIX)/lib/$(N_ARCH)/$(H_ARCH) +endif + +# Project Targets ############################################################ + +LIB_JAR=dasCore-1.0-SNAPSHOT.jar + +DEPEND_JARS=batik-awt-util-1.5.jar batik-svggen-1.5.jar \ + batik-util-1.5.jar bcmail-jdk14-138.jar bcprov-jdk14-138.jar \ + itext-2.0.7.jar junit-3.8.1.jar swing-layout-1.0.3.jar + +#SCRIPTS=das2_runrdr + +# Targets with autopaths ##################################################### + +BUILD_JAR=$(patsubst %.jar, target/%.jar, $(LIB_JAR)) + +#BUILD_SCRIPTS=$(patsubst %, target/%, $(SCRIPTS)) + +BUILD_DEPEND_JARS=$(patsubst %.jar, target/lib/%.jar, $(DEPEND_JARS)) + +INST_LIB_JARS=$(patsubst %.jar, $(INST_HOST_LIB)/%.jar, $(LIB_JAR)) \ + $(patsubst %.jar, $(INST_HOST_LIB)/%.jar, $(DEPEND_JARS)) + +#INST_SCRIPTS=$(patsubst %, $(INST_NAT_BIN)/%, $(SCRIPTS)) + +# Implicit Rules ############################################################# + + +# Building a bash script +#target/%:src/main/sh/%.in +# ./envsubst.py $< $@ + +# Installing a script +#$(INST_NAT_BIN)/%:target/% +# install -D -m 775 $< $@ + +# Installing libary jar +$(INST_HOST_LIB)/%.jar:target/%.jar + install -D -m 0664 $< $@ + +# Installing dependency libaries +$(INST_HOST_LIB)/%.jar:target/lib/%.jar + install -D -m 0664 $< $@ + + + +# Explicit rules ############################################################# + +# Maven is a development tool, not a deployment tool, hence it is being run +# by make as if it were just a compliler tool. Then install steps are handled +# here + +.PHONY : build package test install clean distclean + +build: $(BUILD_JAR) $(BUILD_DEPEND_JARS) $(BUILD_SCRIPTS) + +$(BUILD_JAR) $(BUILD_DEPEND_JARS): + mvn package + +test: + mvn integration-test + +show: + @echo BUILD_JARS: $(BUILD_JAR) $(BUILD_DEPEND_JARS) + @echo INST_LIB_JARS: $(INST_LIB_JARS) + + +install: $(INST_LIB_JARS) $(INST_SCRIPTS) + +clean: + mvn clean + + +# Need to get *.java file dependencies in here... + +distclean: + rm -r target + + +# maven build phases are (from apach.org): +# +# validate - validate the project is correct and all necessary information +# is available +# +# compile - compile the source code of the project +# +# test - test the compiled source code using a suitable unit testing +# framework. These tests should not require the code be packaged or +# deployed +# +# package - take the compiled code and package it in its distributable format, +# such as a JAR. +# +# integration-test - process and deploy the package if necessary into an +# environment where integration tests can be run +# +# verify - run any checks to verify the package is valid and meets quality +# criteria +# +# install - install the package into the local repository, for use as a +# dependency in other projects locally +# +# deploy - done in an integration or release environment, copies the final +# package to the remote repository for sharing with other developers +# and projects. diff --git a/dasCore/platver.java b/dasCore/platver.java new file mode 100644 index 000000000..1a8a613c8 --- /dev/null +++ b/dasCore/platver.java @@ -0,0 +1,9 @@ +public class platver { + + public static void main(String[] args){ + + String sVersion = System.getProperty("java.specification.version"); + System.out.print(sVersion + '\n'); + System.exit(0); + } +} From b666ec33f9b09e5a1937ea0200147b1c33b8ae15 Mon Sep 17 00:00:00 2001 From: cpiker Date: Tue, 13 Jul 2021 18:42:56 -0500 Subject: [PATCH 39/46] DSID validator moved to das2dsid gitlab project --- .../org/das2/reader/Das2MsgValidator.java | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java diff --git a/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java b/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java deleted file mode 100644 index e7ebd7f30..000000000 --- a/dasCore/src/main/java/org/das2/reader/Das2MsgValidator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package org.das2.reader; - -import java.io.IOException; -import java.net.URL; -import java.util.EnumMap; -import java.util.Map; -import javax.xml.XMLConstants; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; -import org.xml.sax.SAXException; - -/** A validator for all Das2 client-server and server-reader XML messages - * - * @author cwp - */ -public class Das2MsgValidator { - public static enum MsgType {ROOT, PEERS, LIST, DESCRIBE, LIST_LEVEL}; - - private Map m_dValidators; - - public Das2MsgValidator(){ - m_dValidators = new EnumMap(MsgType.class); - m_dValidators.put(MsgType.ROOT, null); - m_dValidators.put(MsgType.PEERS, null); - m_dValidators.put(MsgType.LIST, null); - m_dValidators.put(MsgType.DESCRIBE, null); - m_dValidators.put(MsgType.LIST_LEVEL, null); - } - - public void validate(MsgType type, Source src) throws SAXException, IOException{ - synchronized(this){ - if(m_dValidators.get(type) == null){ - SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - URL url = null; - - switch(type){ - case ROOT: - throw new UnsupportedOperationException("Schema for message type "+ - type.toString()+" is not yet implemented"); - case PEERS: - throw new UnsupportedOperationException("Schema for message type "+ - type.toString()+" is not yet implemented"); - case LIST: - throw new UnsupportedOperationException("Schema for message type "+ - type.toString()+" is not yet implemented"); - case DESCRIBE: - url = Das2MsgValidator.class.getResource("/schema/das_dsid-2.2.xsd"); - break; - case LIST_LEVEL: - throw new UnsupportedOperationException("Schema for message type "+ - type.toString()+" is not yet implemented"); - default: - throw new IllegalArgumentException("Operation "+type.toString()+ - " is unknown."); - } - Schema schema = null; - try{ - schema = sf.newSchema(url); - } - catch(SAXException ex){ - throw new RuntimeException(ex); - } - m_dValidators.put(type, schema.newValidator()); - } - } - - m_dValidators.get(type).validate(src); - } -} From bacf9e6eba5de156d511a2e55945d7d70b0d568f Mon Sep 17 00:00:00 2001 From: cpiker <7530814+cpiker@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:46:15 -0600 Subject: [PATCH 40/46] Removed hard-coded distribution paths --- dasCore/pom.xml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/dasCore/pom.xml b/dasCore/pom.xml index 3872be979..f9bae8512 100644 --- a/dasCore/pom.xml +++ b/dasCore/pom.xml @@ -124,24 +124,4 @@ - - - - org.das2.releases - Das2 Public Component Repository - file:///opt/project/das2/repositories/maven2/releases - - - - - org.das2.snapshots - Das2 Daily Build Component Repository - file:///opt/project/das2/repositories/maven2/snapshots - - - From 047d518e5b7a429d8806652b17a1d45335f767cc Mon Sep 17 00:00:00 2001 From: cpiker <7530814+cpiker@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:49:09 -0600 Subject: [PATCH 41/46] A basic readme --- dasCore/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 dasCore/README.md diff --git a/dasCore/README.md b/dasCore/README.md new file mode 100644 index 000000000..308831a2c --- /dev/null +++ b/dasCore/README.md @@ -0,0 +1,7 @@ +# DasCore, original version + +This is an older edition of the dasCore java library. It's not used in +Autoplot but it is needed to build some special purpose programs such as +the MARSIS AIS Ionogram tool. It builds as a standard Apache Maven project. + + From 51e06166efc11041f15285d55773e79f7de4d632 Mon Sep 17 00:00:00 2001 From: cpiker <7530814+cpiker@users.noreply.github.com> Date: Mon, 14 Feb 2022 21:50:36 -0600 Subject: [PATCH 42/46] Moved readme to top level --- dasCore/README.md => README.md | 1 + 1 file changed, 1 insertion(+) rename dasCore/README.md => README.md (78%) diff --git a/dasCore/README.md b/README.md similarity index 78% rename from dasCore/README.md rename to README.md index 308831a2c..aaa735be3 100644 --- a/dasCore/README.md +++ b/README.md @@ -4,4 +4,5 @@ This is an older edition of the dasCore java library. It's not used in Autoplot but it is needed to build some special purpose programs such as the MARSIS AIS Ionogram tool. It builds as a standard Apache Maven project. +To learn more about this library visit [das2.org](https://das2.org). From 9f8ee98ffd96f9176b3ac882a039934aecf50e61 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Mon, 10 Apr 2023 12:15:37 -0500 Subject: [PATCH 43/46] Work around javadoc issues --- dasCore/Linux.mak | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index 05aecaefa..50c471f6f 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -99,7 +99,7 @@ show: install: $(INST_LIB_JARS) $(INST_SCRIPTS) - mvn install + -mvn install $(INST_DOC)/dasCore/index.html:target/site/apidocs/index.html if [ ! -e $(INST_DOC)/dasCore ]; then mkdir -p $(INST_DOC)/dasCore; fi @@ -114,7 +114,7 @@ clean: distclean: rm -r target - + # maven build phases are (from apach.org): # From 989a9c043094eee195156b1028d82475a6d983cb Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Thu, 15 Jun 2023 13:37:18 -0500 Subject: [PATCH 44/46] Change default maven operation --- dasCore/Linux.mak | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index 50c471f6f..7bda9307d 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -85,7 +85,7 @@ build: $(BUILD_JAR) $(BUILD_DEPEND_JARS) $(BUILD_SCRIPTS) #\ # target/site/apidocs/index.html $(BUILD_JAR) $(BUILD_DEPEND_JARS): - mvn -Dmaven.javadoc.skip=true package + mvn -Dmaven.javadoc.skip=true install target/site/apidocs/index.html: mvn generate-sources javadoc:javadoc @@ -99,7 +99,8 @@ show: install: $(INST_LIB_JARS) $(INST_SCRIPTS) - -mvn install + +#-mvn -Dmaven.javadoc.skip=true install $(INST_DOC)/dasCore/index.html:target/site/apidocs/index.html if [ ! -e $(INST_DOC)/dasCore ]; then mkdir -p $(INST_DOC)/dasCore; fi From 74f66c747c8775c840ff8e738eb5da975e690b97 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Sun, 30 Jul 2023 12:45:51 -0500 Subject: [PATCH 45/46] Allow selecting javac not on PATH --- dasCore/Linux.mak | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index 7bda9307d..87d83a4bc 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -12,11 +12,14 @@ # Generic definitions that have to be formed in a platform specific way: -ifeq ($(H_ARCH),) +ifeq ($(JAVAVER),) JAVAVER:=$(shell javac platver.java && java -cp . platver 2>/dev/null) ifeq ($(JAVAVER),) $(error Couldn't determine your java platform version) endif +endif + +ifeq ($(H_ARCH),) H_ARCH=java$(JAVAVER) endif From a35c2d6c19a8f1c3e67d08e2b3e08a711c24e6e0 Mon Sep 17 00:00:00 2001 From: Chris Piker Date: Fri, 1 Dec 2023 15:27:28 -0600 Subject: [PATCH 46/46] Release spec set to java8 for consistant ByteBuffer interface --- dasCore/Linux.mak | 12 +++++++----- dasCore/pom.xml | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dasCore/Linux.mak b/dasCore/Linux.mak index 87d83a4bc..21656b143 100644 --- a/dasCore/Linux.mak +++ b/dasCore/Linux.mak @@ -12,14 +12,16 @@ # Generic definitions that have to be formed in a platform specific way: -ifeq ($(JAVAVER),) -JAVAVER:=$(shell javac platver.java && java -cp . platver 2>/dev/null) -ifeq ($(JAVAVER),) -$(error Couldn't determine your java platform version) -endif +ifeq ($(JAVA_HOME),) +$(error set JAVA_HOME first, ex: JAVA_HOME=/usr/lib/jvm/java-11-openjdk) endif + ifeq ($(H_ARCH),) +JAVAVER:=$(shell $(JAVA_HOME)/bin/javac platver.java && $(JAVA_HOME)/bin/java -cp . platver 2>/dev/null) +ifeq ($(JAVAVER),) +$(error Couldn't determine your java platform version) +endif H_ARCH=java$(JAVAVER) endif diff --git a/dasCore/pom.xml b/dasCore/pom.xml index f9bae8512..bf129f33a 100644 --- a/dasCore/pom.xml +++ b/dasCore/pom.xml @@ -18,6 +18,7 @@ 1.8 1.8 + 1.8 @@ -123,5 +124,4 @@ 1.5 -