propane 3.6.0-java → 3.10.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/.mvn/extensions.xml +1 -1
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +5 -1
  5. data/README.md +6 -13
  6. data/Rakefile +7 -6
  7. data/lib/java/japplemenubar/JAppleMenuBar.java +88 -0
  8. data/lib/java/japplemenubar/libjAppleMenuBar.jnilib +0 -0
  9. data/lib/java/monkstone/ColorUtil.java +127 -0
  10. data/lib/java/monkstone/MathToolModule.java +287 -0
  11. data/lib/java/monkstone/PropaneLibrary.java +46 -0
  12. data/lib/java/monkstone/core/LibraryProxy.java +136 -0
  13. data/lib/java/monkstone/fastmath/DegLutTables.java +111 -0
  14. data/lib/java/monkstone/fastmath/Deglut.java +71 -0
  15. data/lib/java/monkstone/fastmath/package-info.java +6 -0
  16. data/lib/java/monkstone/filechooser/Chooser.java +39 -0
  17. data/lib/java/monkstone/noise/FastTerrain.java +874 -0
  18. data/lib/java/monkstone/noise/Noise.java +90 -0
  19. data/lib/java/monkstone/noise/NoiseGenerator.java +75 -0
  20. data/lib/java/monkstone/noise/NoiseMode.java +28 -0
  21. data/lib/java/monkstone/noise/OpenSimplex2F.java +881 -0
  22. data/lib/java/monkstone/noise/OpenSimplex2S.java +1106 -0
  23. data/lib/java/monkstone/noise/SmoothTerrain.java +1099 -0
  24. data/lib/java/monkstone/slider/CustomHorizontalSlider.java +164 -0
  25. data/lib/java/monkstone/slider/CustomVerticalSlider.java +178 -0
  26. data/lib/java/monkstone/slider/SimpleHorizontalSlider.java +145 -0
  27. data/lib/java/monkstone/slider/SimpleSlider.java +166 -0
  28. data/lib/java/monkstone/slider/SimpleVerticalSlider.java +157 -0
  29. data/lib/java/monkstone/slider/Slider.java +61 -0
  30. data/lib/java/monkstone/slider/SliderBar.java +245 -0
  31. data/lib/java/monkstone/slider/SliderGroup.java +56 -0
  32. data/lib/java/monkstone/slider/WheelHandler.java +35 -0
  33. data/lib/java/monkstone/vecmath/GfxRender.java +86 -0
  34. data/lib/java/monkstone/vecmath/JRender.java +56 -0
  35. data/lib/java/monkstone/vecmath/ShapeRender.java +87 -0
  36. data/lib/java/monkstone/vecmath/package-info.java +20 -0
  37. data/lib/java/monkstone/vecmath/vec2/Vec2.java +802 -0
  38. data/lib/java/monkstone/vecmath/vec2/package-info.java +6 -0
  39. data/lib/java/monkstone/vecmath/vec3/Vec3.java +727 -0
  40. data/lib/java/monkstone/vecmath/vec3/package-info.java +6 -0
  41. data/lib/java/monkstone/videoevent/CaptureEvent.java +27 -0
  42. data/lib/java/monkstone/videoevent/MovieEvent.java +32 -0
  43. data/lib/java/monkstone/videoevent/package-info.java +20 -0
  44. data/lib/java/processing/awt/PGraphicsJava2D.java +3040 -0
  45. data/lib/java/processing/awt/PImageAWT.java +377 -0
  46. data/lib/java/processing/awt/PShapeJava2D.java +387 -0
  47. data/lib/java/processing/awt/PSurfaceAWT.java +1581 -0
  48. data/lib/java/processing/awt/ShimAWT.java +581 -0
  49. data/lib/java/processing/core/PApplet.java +15156 -0
  50. data/lib/java/processing/core/PConstants.java +523 -0
  51. data/lib/java/processing/core/PFont.java +1126 -0
  52. data/lib/java/processing/core/PGraphics.java +8600 -0
  53. data/lib/java/processing/core/PImage.java +3377 -0
  54. data/lib/java/processing/core/PMatrix.java +208 -0
  55. data/lib/java/processing/core/PMatrix2D.java +562 -0
  56. data/lib/java/processing/core/PMatrix3D.java +890 -0
  57. data/lib/java/processing/core/PShape.java +3561 -0
  58. data/lib/java/processing/core/PShapeOBJ.java +483 -0
  59. data/lib/java/processing/core/PShapeSVG.java +2016 -0
  60. data/lib/java/processing/core/PStyle.java +63 -0
  61. data/lib/java/processing/core/PSurface.java +198 -0
  62. data/lib/java/processing/core/PSurfaceNone.java +431 -0
  63. data/lib/java/processing/core/PVector.java +1066 -0
  64. data/lib/java/processing/core/ThinkDifferent.java +115 -0
  65. data/lib/java/processing/data/DoubleDict.java +850 -0
  66. data/lib/java/processing/data/DoubleList.java +928 -0
  67. data/lib/java/processing/data/FloatDict.java +847 -0
  68. data/lib/java/processing/data/FloatList.java +936 -0
  69. data/lib/java/processing/data/IntDict.java +807 -0
  70. data/lib/java/processing/data/IntList.java +936 -0
  71. data/lib/java/processing/data/JSONArray.java +1260 -0
  72. data/lib/java/processing/data/JSONObject.java +2282 -0
  73. data/lib/java/processing/data/JSONTokener.java +435 -0
  74. data/lib/java/processing/data/LongDict.java +802 -0
  75. data/lib/java/processing/data/LongList.java +937 -0
  76. data/lib/java/processing/data/Sort.java +46 -0
  77. data/lib/java/processing/data/StringDict.java +613 -0
  78. data/lib/java/processing/data/StringList.java +800 -0
  79. data/lib/java/processing/data/Table.java +4936 -0
  80. data/lib/java/processing/data/TableRow.java +198 -0
  81. data/lib/java/processing/data/XML.java +1156 -0
  82. data/lib/java/processing/dxf/RawDXF.java +404 -0
  83. data/lib/java/processing/event/Event.java +125 -0
  84. data/lib/java/processing/event/KeyEvent.java +70 -0
  85. data/lib/java/processing/event/MouseEvent.java +114 -0
  86. data/lib/java/processing/event/TouchEvent.java +57 -0
  87. data/lib/java/processing/javafx/PGraphicsFX2D.java +32 -0
  88. data/lib/java/processing/javafx/PSurfaceFX.java +173 -0
  89. data/lib/java/processing/net/Client.java +744 -0
  90. data/lib/java/processing/net/Server.java +388 -0
  91. data/lib/java/processing/opengl/FontTexture.java +378 -0
  92. data/lib/java/processing/opengl/FrameBuffer.java +513 -0
  93. data/lib/java/processing/opengl/LinePath.java +627 -0
  94. data/lib/java/processing/opengl/LineStroker.java +681 -0
  95. data/lib/java/processing/opengl/PGL.java +3483 -0
  96. data/lib/java/processing/opengl/PGraphics2D.java +615 -0
  97. data/lib/java/processing/opengl/PGraphics3D.java +281 -0
  98. data/lib/java/processing/opengl/PGraphicsOpenGL.java +13753 -0
  99. data/lib/java/processing/opengl/PJOGL.java +2008 -0
  100. data/lib/java/processing/opengl/PShader.java +1484 -0
  101. data/lib/java/processing/opengl/PShapeOpenGL.java +5269 -0
  102. data/lib/java/processing/opengl/PSurfaceJOGL.java +1385 -0
  103. data/lib/java/processing/opengl/Texture.java +1696 -0
  104. data/lib/java/processing/opengl/VertexBuffer.java +88 -0
  105. data/lib/java/processing/opengl/cursors/arrow.png +0 -0
  106. data/lib/java/processing/opengl/cursors/cross.png +0 -0
  107. data/lib/java/processing/opengl/cursors/hand.png +0 -0
  108. data/lib/java/processing/opengl/cursors/license.txt +27 -0
  109. data/lib/java/processing/opengl/cursors/move.png +0 -0
  110. data/lib/java/processing/opengl/cursors/text.png +0 -0
  111. data/lib/java/processing/opengl/cursors/wait.png +0 -0
  112. data/lib/java/processing/opengl/shaders/ColorFrag.glsl +32 -0
  113. data/lib/java/processing/opengl/shaders/ColorVert.glsl +34 -0
  114. data/lib/java/processing/opengl/shaders/LightFrag.glsl +33 -0
  115. data/lib/java/processing/opengl/shaders/LightVert.glsl +151 -0
  116. data/lib/java/processing/opengl/shaders/LineFrag.glsl +32 -0
  117. data/lib/java/processing/opengl/shaders/LineVert.glsl +100 -0
  118. data/lib/java/processing/opengl/shaders/MaskFrag.glsl +40 -0
  119. data/lib/java/processing/opengl/shaders/PointFrag.glsl +32 -0
  120. data/lib/java/processing/opengl/shaders/PointVert.glsl +56 -0
  121. data/lib/java/processing/opengl/shaders/TexFrag.glsl +37 -0
  122. data/lib/java/processing/opengl/shaders/TexLightFrag.glsl +37 -0
  123. data/lib/java/processing/opengl/shaders/TexLightVert.glsl +157 -0
  124. data/lib/java/processing/opengl/shaders/TexVert.glsl +38 -0
  125. data/lib/java/processing/pdf/PGraphicsPDF.java +581 -0
  126. data/lib/java/processing/svg/PGraphicsSVG.java +378 -0
  127. data/lib/propane/app.rb +9 -10
  128. data/lib/propane/runner.rb +10 -12
  129. data/lib/propane/version.rb +1 -1
  130. data/library/pdf/pdf.rb +7 -0
  131. data/library/svg/svg.rb +7 -0
  132. data/mvnw +3 -3
  133. data/mvnw.cmd +2 -2
  134. data/pom.rb +30 -3
  135. data/pom.xml +54 -3
  136. data/propane.gemspec +7 -3
  137. data/src/main/java/monkstone/FastNoiseModuleJava.java +127 -0
  138. data/src/main/java/monkstone/MathToolModule.java +30 -30
  139. data/src/main/java/monkstone/PropaneLibrary.java +2 -0
  140. data/src/main/java/monkstone/SmoothNoiseModuleJava.java +127 -0
  141. data/src/main/java/monkstone/fastmath/DegLutTables.java +111 -0
  142. data/src/main/java/monkstone/fastmath/Deglut.java +6 -56
  143. data/src/main/java/monkstone/filechooser/Chooser.java +1 -1
  144. data/src/main/java/monkstone/noise/OpenSimplex2F.java +813 -0
  145. data/src/main/java/monkstone/noise/OpenSimplex2S.java +1138 -0
  146. data/src/main/java/monkstone/slider/WheelHandler.java +1 -1
  147. data/src/main/java/monkstone/vecmath/JRender.java +6 -6
  148. data/src/main/java/monkstone/vecmath/vec2/Vec2.java +20 -19
  149. data/src/main/java/monkstone/vecmath/vec3/Vec3.java +12 -12
  150. data/src/main/java/processing/awt/PGraphicsJava2D.java +11 -3
  151. data/src/main/java/processing/core/PApplet.java +13242 -13374
  152. data/src/main/java/processing/core/PConstants.java +155 -163
  153. data/src/main/java/processing/core/PGraphics.java +118 -111
  154. data/src/main/java/processing/opengl/PJOGL.java +6 -5
  155. data/src/main/java/processing/pdf/PGraphicsPDF.java +581 -0
  156. data/src/main/java/processing/svg/PGraphicsSVG.java +378 -0
  157. data/test/deglut_spec_test.rb +2 -2
  158. data/vendors/Rakefile +1 -1
  159. metadata +146 -17
  160. data/library/simplex_noise/simplex_noise.rb +0 -5
  161. data/src/main/java/monkstone/noise/SimplexNoise.java +0 -436
@@ -0,0 +1,4936 @@
1
+ /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
+
3
+ /*
4
+ Part of the Processing project - http://processing.org
5
+
6
+ Copyright (c) 2011-13 Ben Fry and Casey Reas
7
+ Copyright (c) 2006-11 Ben Fry
8
+
9
+ This library is free software; you can redistribute it and/or
10
+ modify it under the terms of the GNU Lesser General Public
11
+ License version 2.1 as published by the Free Software Foundation.
12
+
13
+ This library is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General
19
+ Public License along with this library; if not, write to the
20
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21
+ Boston, MA 02111-1307 USA
22
+ */
23
+
24
+ package processing.data;
25
+
26
+ import java.io.*;
27
+ import java.lang.reflect.*;
28
+ import java.nio.charset.Charset;
29
+ import java.sql.ResultSet;
30
+ import java.sql.ResultSetMetaData;
31
+ import java.sql.SQLException;
32
+ import java.sql.Types;
33
+ import java.util.*;
34
+ import java.util.concurrent.ExecutorService;
35
+ import java.util.concurrent.Executors;
36
+ import java.util.zip.ZipEntry;
37
+ import java.util.zip.ZipInputStream;
38
+ import java.util.zip.ZipOutputStream;
39
+
40
+ import javax.xml.parsers.ParserConfigurationException;
41
+
42
+ import org.xml.sax.SAXException;
43
+
44
+ import processing.core.PApplet;
45
+ import processing.core.PConstants;
46
+
47
+
48
+ /**
49
+ * <p>Generic class for handling tabular data, typically from a CSV, TSV, or
50
+ * other sort of spreadsheet file.</p>
51
+ * <p>CSV files are
52
+ * <a href="http://en.wikipedia.org/wiki/Comma-separated_values">comma separated values</a>,
53
+ * often with the data in quotes. TSV files use tabs as separators, and usually
54
+ * don't bother with the quotes.</p>
55
+ * <p>File names should end with .csv if they're comma separated.</p>
56
+ * <p>A rough "spec" for CSV can be found <a href="http://tools.ietf.org/html/rfc4180">here</a>.</p>
57
+ *
58
+ * @webref data:composite
59
+ * @see PApplet#loadTable(String)
60
+ * @see PApplet#saveTable(Table, String)
61
+ * @see TableRow
62
+ */
63
+ public class Table {
64
+ protected int rowCount;
65
+ protected int allocCount;
66
+
67
+ // protected boolean skipEmptyRows = true;
68
+ // protected boolean skipCommentLines = true;
69
+ // protected String extension = null;
70
+ // protected boolean commaSeparatedValues = false;
71
+ // protected boolean awfulCSV = false;
72
+
73
+ protected String missingString = null;
74
+ protected int missingInt = 0;
75
+ protected long missingLong = 0;
76
+ protected float missingFloat = Float.NaN;
77
+ protected double missingDouble = Double.NaN;
78
+ protected int missingCategory = -1;
79
+
80
+ String[] columnTitles;
81
+ HashMapBlows[] columnCategories;
82
+ HashMap<String, Integer> columnIndices;
83
+
84
+ protected Object[] columns; // [column]
85
+
86
+ // accessible for advanced users
87
+ static public final int STRING = 0;
88
+ static public final int INT = 1;
89
+ static public final int LONG = 2;
90
+ static public final int FLOAT = 3;
91
+ static public final int DOUBLE = 4;
92
+ static public final int CATEGORY = 5;
93
+ int[] columnTypes;
94
+
95
+ protected RowIterator rowIterator;
96
+
97
+ // 0 for doubling each time, otherwise the number of rows to increment on
98
+ // each expansion.
99
+ protected int expandIncrement;
100
+
101
+
102
+ /**
103
+ * Creates a new, empty table. Use addRow() to add additional rows.
104
+ */
105
+ public Table() {
106
+ init();
107
+ }
108
+
109
+ /**
110
+ * @nowebref
111
+ */
112
+ public Table(File file) throws IOException {
113
+ this(file, null);
114
+ }
115
+
116
+
117
+ /**
118
+ * version that uses a File object; future releases (or data types)
119
+ * may include additional optimizations here
120
+ *
121
+ * @nowebref
122
+ */
123
+ public Table(File file, String options) throws IOException {
124
+ // uses createInput() to handle .gz (and eventually .bz2) files
125
+ init();
126
+ parse(PApplet.createInput(file),
127
+ extensionOptions(true, file.getName(), options));
128
+ }
129
+
130
+ /**
131
+ * @nowebref
132
+ */
133
+ public Table(InputStream input) throws IOException {
134
+ this(input, null);
135
+ }
136
+
137
+
138
+ /**
139
+ * Read the table from a stream. Possible options include:
140
+ * <ul>
141
+ * <li>csv - parse the table as comma-separated values
142
+ * <li>tsv - parse the table as tab-separated values
143
+ * <li>newlines - this CSV file contains newlines inside individual cells
144
+ * <li>header - this table has a header (title) row
145
+ * </ul>
146
+ *
147
+ * @nowebref
148
+ * @param input
149
+ * @param options
150
+ * @throws IOException
151
+ */
152
+ public Table(InputStream input, String options) throws IOException {
153
+ init();
154
+ parse(input, options);
155
+ }
156
+
157
+
158
+ public Table(Iterable<TableRow> rows) {
159
+ init();
160
+
161
+ int row = 0;
162
+ int alloc = 10;
163
+
164
+ for (TableRow incoming : rows) {
165
+ if (row == 0) {
166
+ setColumnTypes(incoming.getColumnTypes());
167
+ setColumnTitles(incoming.getColumnTitles());
168
+ // Do this after setting types, otherwise it'll attempt to parse the
169
+ // allocated but empty rows, and drive CATEGORY columns nutso.
170
+ setRowCount(alloc);
171
+ // sometimes more columns than titles (and types?)
172
+ setColumnCount(incoming.getColumnCount());
173
+
174
+ } else if (row == alloc) {
175
+ // Far more efficient than re-allocating all columns and doing a copy
176
+ alloc *= 2;
177
+ setRowCount(alloc);
178
+ }
179
+
180
+ //addRow(row);
181
+ // try {
182
+ setRow(row++, incoming);
183
+ // } catch (ArrayIndexOutOfBoundsException aioobe) {
184
+ // for (int i = 0; i < incoming.getColumnCount(); i++) {
185
+ // System.out.format("[%d] %s%n", i, incoming.getString(i));
186
+ // }
187
+ // throw aioobe;
188
+ // }
189
+ }
190
+ // Shrink the table to only the rows that were used
191
+ if (row != alloc) {
192
+ setRowCount(row);
193
+ }
194
+ }
195
+
196
+
197
+ /**
198
+ * @nowebref
199
+ */
200
+ public Table(ResultSet rs) {
201
+ init();
202
+ try {
203
+ ResultSetMetaData rsmd = rs.getMetaData();
204
+
205
+ int columnCount = rsmd.getColumnCount();
206
+ setColumnCount(columnCount);
207
+
208
+ for (int col = 0; col < columnCount; col++) {
209
+ setColumnTitle(col, rsmd.getColumnName(col + 1));
210
+
211
+ int type = rsmd.getColumnType(col + 1);
212
+ switch (type) { // TODO these aren't tested. nor are they complete.
213
+ case Types.INTEGER:
214
+ case Types.TINYINT:
215
+ case Types.SMALLINT:
216
+ setColumnType(col, INT);
217
+ break;
218
+ case Types.BIGINT:
219
+ setColumnType(col, LONG);
220
+ break;
221
+ case Types.FLOAT:
222
+ setColumnType(col, FLOAT);
223
+ break;
224
+ case Types.DECIMAL:
225
+ case Types.DOUBLE:
226
+ case Types.REAL:
227
+ setColumnType(col, DOUBLE);
228
+ break;
229
+ }
230
+ }
231
+
232
+ int row = 0;
233
+ while (rs.next()) {
234
+ for (int col = 0; col < columnCount; col++) {
235
+ switch (columnTypes[col]) {
236
+ case STRING: setString(row, col, rs.getString(col+1)); break;
237
+ case INT: setInt(row, col, rs.getInt(col+1)); break;
238
+ case LONG: setLong(row, col, rs.getLong(col+1)); break;
239
+ case FLOAT: setFloat(row, col, rs.getFloat(col+1)); break;
240
+ case DOUBLE: setDouble(row, col, rs.getDouble(col+1)); break;
241
+ default: throw new IllegalArgumentException("column type " + columnTypes[col] + " not supported.");
242
+ }
243
+ }
244
+ row++;
245
+ // String[] row = new String[columnCount];
246
+ // for (int col = 0; col < columnCount; col++) {
247
+ // row[col] = rs.get(col + 1);
248
+ // }
249
+ // addRow(row);
250
+ }
251
+
252
+ } catch (SQLException s) {
253
+ throw new RuntimeException(s);
254
+ }
255
+ }
256
+
257
+
258
+ public Table typedParse(InputStream input, String options) throws IOException {
259
+ Table table = new Table();
260
+ table.setColumnTypes(this);
261
+ table.parse(input, options);
262
+ return table;
263
+ }
264
+
265
+
266
+ protected void init() {
267
+ columns = new Object[0];
268
+ columnTypes = new int[0];
269
+ columnCategories = new HashMapBlows[0];
270
+ }
271
+
272
+
273
+ /*
274
+ protected String checkOptions(File file, String options) throws IOException {
275
+ String extension = null;
276
+ String filename = file.getName();
277
+ int dotIndex = filename.lastIndexOf('.');
278
+ if (dotIndex != -1) {
279
+ extension = filename.substring(dotIndex + 1).toLowerCase();
280
+ if (!extension.equals("csv") &&
281
+ !extension.equals("tsv") &&
282
+ !extension.equals("html") &&
283
+ !extension.equals("bin")) {
284
+ // ignore extension
285
+ extension = null;
286
+ }
287
+ }
288
+ if (extension == null) {
289
+ if (options == null) {
290
+ throw new IOException("This table filename has no extension, and no options are set.");
291
+ }
292
+ } else { // extension is not null
293
+ if (options == null) {
294
+ options = extension;
295
+ } else {
296
+ // prepend the extension, it will be overridden if there's an option for it.
297
+ options = extension + "," + options;
298
+ }
299
+ }
300
+ return options;
301
+ }
302
+ */
303
+
304
+
305
+ static final String[] loadExtensions = { "csv", "tsv", "ods", "bin" };
306
+ static final String[] saveExtensions = { "csv", "tsv", "ods", "bin", "html" };
307
+
308
+ static public String extensionOptions(boolean loading, String filename, String options) {
309
+ String extension = PApplet.checkExtension(filename);
310
+ if (extension != null) {
311
+ for (String possible : loading ? loadExtensions : saveExtensions) {
312
+ if (extension.equals(possible)) {
313
+ if (options == null) {
314
+ return extension;
315
+ } else {
316
+ // prepend the extension to the options (will be replaced by other
317
+ // options that override it later in the load loop)
318
+ return extension + "," + options;
319
+ }
320
+ }
321
+ }
322
+ }
323
+ return options;
324
+ }
325
+
326
+
327
+ protected void parse(InputStream input, String options) throws IOException {
328
+ // boolean awfulCSV = false;
329
+ boolean header = false;
330
+ String extension = null;
331
+ boolean binary = false;
332
+ String encoding = "UTF-8";
333
+
334
+ String worksheet = null;
335
+ final String sheetParam = "worksheet=";
336
+
337
+ String[] opts = null;
338
+ if (options != null) {
339
+ opts = PApplet.trim(PApplet.split(options, ','));
340
+ for (String opt : opts) {
341
+ if (opt.equals("tsv")) {
342
+ extension = "tsv";
343
+ } else if (opt.equals("csv")) {
344
+ extension = "csv";
345
+ } else if (opt.equals("ods")) {
346
+ extension = "ods";
347
+ } else if (opt.equals("newlines")) {
348
+ //awfulCSV = true;
349
+ //extension = "csv";
350
+ throw new IllegalArgumentException("The 'newlines' option is no longer necessary.");
351
+ } else if (opt.equals("bin")) {
352
+ binary = true;
353
+ extension = "bin";
354
+ } else if (opt.equals("header")) {
355
+ header = true;
356
+ } else if (opt.startsWith(sheetParam)) {
357
+ worksheet = opt.substring(sheetParam.length());
358
+ } else if (opt.startsWith("dictionary=")) {
359
+ // ignore option, this is only handled by PApplet
360
+ } else if (opt.startsWith("encoding=")) {
361
+ encoding = opt.substring(9);
362
+ } else {
363
+ throw new IllegalArgumentException("'" + opt + "' is not a valid option for loading a Table");
364
+ }
365
+ }
366
+ }
367
+
368
+ if (extension == null) {
369
+ throw new IllegalArgumentException("No extension specified for this Table");
370
+ }
371
+
372
+ if (binary) {
373
+ loadBinary(input);
374
+
375
+ } else if (extension.equals("ods")) {
376
+ odsParse(input, worksheet, header);
377
+
378
+ } else {
379
+ InputStreamReader isr = new InputStreamReader(input, encoding);
380
+ BufferedReader reader = new BufferedReader(isr);
381
+
382
+ // strip out the Unicode BOM, if present
383
+ reader.mark(1);
384
+ int c = reader.read();
385
+ // if not the BOM, back up to the beginning again
386
+ if (c != '\uFEFF') {
387
+ reader.reset();
388
+ }
389
+
390
+ /*
391
+ if (awfulCSV) {
392
+ parseAwfulCSV(reader, header);
393
+ } else if ("tsv".equals(extension)) {
394
+ parseBasic(reader, header, true);
395
+ } else if ("csv".equals(extension)) {
396
+ parseBasic(reader, header, false);
397
+ }
398
+ */
399
+ parseBasic(reader, header, "tsv".equals(extension));
400
+ }
401
+ }
402
+
403
+
404
+ protected void parseBasic(BufferedReader reader,
405
+ boolean header, boolean tsv) throws IOException {
406
+ String line = null;
407
+ int row = 0;
408
+ if (rowCount == 0) {
409
+ setRowCount(10);
410
+ }
411
+ //int prev = 0; //-1;
412
+ try {
413
+ while ((line = reader.readLine()) != null) {
414
+ if (row == getRowCount()) {
415
+ setRowCount(row << 1);
416
+ }
417
+ if (row == 0 && header) {
418
+ setColumnTitles(tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader));
419
+ header = false;
420
+ } else {
421
+ setRow(row, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader));
422
+ row++;
423
+ }
424
+
425
+ if (row % 10000 == 0) {
426
+ /*
427
+ // this is problematic unless we're going to calculate rowCount first
428
+ if (row < rowCount) {
429
+ int pct = (100 * row) / rowCount;
430
+ if (pct != prev) { // also prevents "0%" from showing up
431
+ System.out.println(pct + "%");
432
+ prev = pct;
433
+ }
434
+ }
435
+ */
436
+ try {
437
+ // Sleep this thread so that the GC can catch up
438
+ Thread.sleep(10);
439
+ } catch (InterruptedException e) {
440
+ e.printStackTrace();
441
+ }
442
+ }
443
+ }
444
+ } catch (Exception e) {
445
+ throw new RuntimeException("Error reading table on line " + row, e);
446
+ }
447
+ // shorten or lengthen based on what's left
448
+ if (row != getRowCount()) {
449
+ setRowCount(row);
450
+ }
451
+ }
452
+
453
+
454
+ // public void convertTSV(BufferedReader reader, File outputFile) throws IOException {
455
+ // convertBasic(reader, true, outputFile);
456
+ // }
457
+
458
+
459
+ /*
460
+ protected void parseAwfulCSV(BufferedReader reader,
461
+ boolean header) throws IOException {
462
+ char[] c = new char[100];
463
+ int count = 0;
464
+ boolean insideQuote = false;
465
+
466
+ int alloc = 100;
467
+ setRowCount(100);
468
+
469
+ int row = 0;
470
+ int col = 0;
471
+ int ch;
472
+ while ((ch = reader.read()) != -1) {
473
+ if (insideQuote) {
474
+ if (ch == '\"') {
475
+ // this is either the end of a quoted entry, or a quote character
476
+ reader.mark(1);
477
+ if (reader.read() == '\"') {
478
+ // it's "", which means a quote character
479
+ if (count == c.length) {
480
+ c = PApplet.expand(c);
481
+ }
482
+ c[count++] = '\"';
483
+ } else {
484
+ // nope, just the end of a quoted csv entry
485
+ reader.reset();
486
+ insideQuote = false;
487
+ // TODO nothing here that prevents bad csv data from showing up
488
+ // after the quote and before the comma...
489
+ // set(row, col, new String(c, 0, count));
490
+ // count = 0;
491
+ // col++;
492
+ // insideQuote = false;
493
+ }
494
+ } else { // inside a quote, but the character isn't a quote
495
+ if (count == c.length) {
496
+ c = PApplet.expand(c);
497
+ }
498
+ c[count++] = (char) ch;
499
+ }
500
+ } else { // not inside a quote
501
+ if (ch == '\"') {
502
+ insideQuote = true;
503
+
504
+ } else if (ch == '\r' || ch == '\n') {
505
+ if (ch == '\r') {
506
+ // check to see if next is a '\n'
507
+ reader.mark(1);
508
+ if (reader.read() != '\n') {
509
+ reader.reset();
510
+ }
511
+ }
512
+ setString(row, col, new String(c, 0, count));
513
+ count = 0;
514
+ row++;
515
+ if (row == 1 && header) {
516
+ // Use internal row removal (efficient because only one row).
517
+ removeTitleRow();
518
+ // Un-set the header variable so that next time around, we don't
519
+ // just get stuck into a loop, removing the 0th row repeatedly.
520
+ header = false;
521
+ // Reset the number of rows (removeTitleRow() won't reset our local 'row' counter)
522
+ row = 0;
523
+ }
524
+ // if (row % 1000 == 0) {
525
+ // PApplet.println(PApplet.nfc(row));
526
+ // }
527
+ if (row == alloc) {
528
+ alloc *= 2;
529
+ setRowCount(alloc);
530
+ }
531
+ col = 0;
532
+
533
+ } else if (ch == ',') {
534
+ setString(row, col, new String(c, 0, count));
535
+ count = 0;
536
+ // starting a new column, make sure we have room
537
+ col++;
538
+ ensureColumn(col);
539
+
540
+ } else { // just a regular character, add it
541
+ if (count == c.length) {
542
+ c = PApplet.expand(c);
543
+ }
544
+ c[count++] = (char) ch;
545
+ }
546
+ }
547
+ }
548
+ // catch any leftovers
549
+ if (count > 0) {
550
+ setString(row, col, new String(c, 0, count));
551
+ }
552
+ row++; // set row to row count (the current row index + 1)
553
+ if (alloc != row) {
554
+ setRowCount(row); // shrink to the actual size
555
+ }
556
+ }
557
+ */
558
+
559
+
560
+ static class CommaSeparatedLine {
561
+ char[] c;
562
+ String[] pieces;
563
+ int pieceCount;
564
+
565
+ // int offset;
566
+ int start; //, stop;
567
+
568
+ String[] handle(String line, BufferedReader reader) throws IOException {
569
+ // PApplet.println("handle() called for: " + line);
570
+ start = 0;
571
+ pieceCount = 0;
572
+ c = line.toCharArray();
573
+
574
+ // get tally of number of columns and allocate the array
575
+ int cols = 1; // the first comma indicates the second column
576
+ boolean quote = false;
577
+ for (int i = 0; i < c.length; i++) {
578
+ if (!quote && (c[i] == ',')) {
579
+ cols++;
580
+ } else if (c[i] == '\"') {
581
+ // double double quotes (escaped quotes like "") will simply toggle
582
+ // this back and forth, so it should remain accurate
583
+ quote = !quote;
584
+ }
585
+ }
586
+ pieces = new String[cols];
587
+
588
+ // while (offset < c.length) {
589
+ // start = offset;
590
+ while (start < c.length) {
591
+ boolean enough = ingest();
592
+ while (!enough) {
593
+ // found a newline inside the quote, grab another line
594
+ String nextLine = reader.readLine();
595
+ // System.out.println("extending to " + nextLine);
596
+ if (nextLine == null) {
597
+ // System.err.println(line);
598
+ throw new IOException("Found a quoted line that wasn't terminated properly.");
599
+ }
600
+ // for simplicity, not bothering to skip what's already been read
601
+ // from c (and reset the offset to 0), opting to make a bigger array
602
+ // with both lines.
603
+ char[] temp = new char[c.length + 1 + nextLine.length()];
604
+ PApplet.arrayCopy(c, temp, c.length);
605
+ // NOTE: we're converting to \n here, which isn't perfect
606
+ temp[c.length] = '\n';
607
+ nextLine.getChars(0, nextLine.length(), temp, c.length + 1);
608
+ // c = temp;
609
+ return handle(new String(temp), reader);
610
+ //System.out.println(" full line is now " + new String(c));
611
+ //stop = nextComma(c, offset);
612
+ //System.out.println("stop is now " + stop);
613
+ //enough = ingest();
614
+ }
615
+ }
616
+
617
+ // Make any remaining entries blanks instead of nulls. Empty columns from
618
+ // CSV are always "" not null, so this handles successive commas in a line
619
+ for (int i = pieceCount; i < pieces.length; i++) {
620
+ pieces[i] = "";
621
+ }
622
+ // PApplet.printArray(pieces);
623
+ return pieces;
624
+ }
625
+
626
+ protected void addPiece(int start, int stop, boolean quotes) {
627
+ if (quotes) {
628
+ int dest = start;
629
+ for (int i = start; i < stop; i++) {
630
+ if (c[i] == '\"') {
631
+ ++i; // step over the quote
632
+ }
633
+ if (i != dest) {
634
+ c[dest] = c[i];
635
+ }
636
+ dest++;
637
+ }
638
+ pieces[pieceCount++] = new String(c, start, dest - start);
639
+
640
+ } else {
641
+ pieces[pieceCount++] = new String(c, start, stop - start);
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Returns the next comma (not inside a quote) in the specified array.
647
+ * @param c array to search
648
+ * @param index offset at which to start looking
649
+ * @return index of the comma, or -1 if line ended inside an unclosed quote
650
+ */
651
+ protected boolean ingest() {
652
+ boolean hasEscapedQuotes = false;
653
+ // not possible
654
+ // if (index == c.length) { // we're already at the end
655
+ // return c.length;
656
+ // }
657
+ boolean quoted = c[start] == '\"';
658
+ if (quoted) {
659
+ start++; // step over the quote
660
+ }
661
+ int i = start;
662
+ while (i < c.length) {
663
+ // PApplet.println(c[i] + " i=" + i);
664
+ if (c[i] == '\"') {
665
+ // if this fella started with a quote
666
+ if (quoted) {
667
+ if (i == c.length-1) {
668
+ // closing quote for field; last field on the line
669
+ addPiece(start, i, hasEscapedQuotes);
670
+ start = c.length;
671
+ return true;
672
+
673
+ } else if (c[i+1] == '\"') {
674
+ // an escaped quote inside a quoted field, step over it
675
+ hasEscapedQuotes = true;
676
+ i += 2;
677
+
678
+ } else if (c[i+1] == ',') {
679
+ // that was our closing quote, get outta here
680
+ addPiece(start, i, hasEscapedQuotes);
681
+ start = i+2;
682
+ return true;
683
+
684
+ } else {
685
+ // This is a lone-wolf quote, occasionally seen in exports.
686
+ // It's a single quote in the middle of some other text,
687
+ // and not escaped properly. Pray for the best!
688
+ i++;
689
+ }
690
+
691
+ } else { // not a quoted line
692
+ if (i == c.length-1) {
693
+ // we're at the end of the line, can't have an unescaped quote
694
+ throw new RuntimeException("Unterminated quote at end of line");
695
+
696
+ } else if (c[i+1] == '\"') {
697
+ // step over this crummy quote escape
698
+ hasEscapedQuotes = true;
699
+ i += 2;
700
+
701
+ } else {
702
+ throw new RuntimeException("Unterminated quoted field mid-line");
703
+ }
704
+ }
705
+ } else if (!quoted && c[i] == ',') {
706
+ addPiece(start, i, hasEscapedQuotes);
707
+ start = i+1;
708
+ return true;
709
+
710
+ } else if (!quoted && i == c.length-1) {
711
+ addPiece(start, c.length, hasEscapedQuotes);
712
+ start = c.length;
713
+ return true;
714
+
715
+ } else { // nothing all that interesting
716
+ i++;
717
+ }
718
+ }
719
+ // if (!quote && (c[i] == ',')) {
720
+ // // found a comma, return this location
721
+ // return i;
722
+ // } else if (c[i] == '\"') {
723
+ // // if it's a quote, then either the next char is another quote,
724
+ // // or if this is a quoted entry, it better be a comma
725
+ // quote = !quote;
726
+ // }
727
+ // }
728
+
729
+ // if still inside a quote, indicate that another line should be read
730
+ if (quoted) {
731
+ return false;
732
+ }
733
+
734
+ // // made it to the end of the array with no new comma
735
+ // return c.length;
736
+
737
+ throw new RuntimeException("not sure how...");
738
+ }
739
+ }
740
+
741
+
742
+ CommaSeparatedLine csl;
743
+
744
+ /**
745
+ * Parse a line of text as comma-separated values, returning each value as
746
+ * one entry in an array of String objects. Remove quotes from entries that
747
+ * begin and end with them, and convert 'escaped' quotes to actual quotes.
748
+ * @param line line of text to be parsed
749
+ * @return an array of the individual values formerly separated by commas
750
+ */
751
+ protected String[] splitLineCSV(String line, BufferedReader reader) throws IOException {
752
+ if (csl == null) {
753
+ csl = new CommaSeparatedLine();
754
+ }
755
+ return csl.handle(line, reader);
756
+ }
757
+
758
+
759
+ /**
760
+ * Returns the next comma (not inside a quote) in the specified array.
761
+ * @param c array to search
762
+ * @param index offset at which to start looking
763
+ * @return index of the comma, or -1 if line ended inside an unclosed quote
764
+ */
765
+ /*
766
+ static protected int nextComma(char[] c, int index) {
767
+ if (index == c.length) { // we're already at the end
768
+ return c.length;
769
+ }
770
+ boolean quoted = c[index] == '\"';
771
+ if (quoted) {
772
+ index++; // step over the quote
773
+ }
774
+ for (int i = index; i < c.length; i++) {
775
+ if (c[i] == '\"') {
776
+ // if this fella started with a quote
777
+ if (quoted) {
778
+ if (i == c.length-1) {
779
+ //return -1; // ran out of chars
780
+ // closing quote for field; last field on the line
781
+ return c.length;
782
+ } else if (c[i+1] == '\"') {
783
+ // an escaped quote inside a quoted field, step over it
784
+ i++;
785
+ } else if (c[i+1] == ',') {
786
+ // that's our closing quote, get outta here
787
+ return i+1;
788
+ }
789
+
790
+ } else { // not a quoted line
791
+ if (i == c.length-1) {
792
+ // we're at the end of the line, can't have an unescaped quote
793
+ //return -1; // ran out of chars
794
+ throw new RuntimeException("Unterminated quoted field at end of line");
795
+ } else if (c[i+1] == '\"') {
796
+ // step over this crummy quote escape
797
+ ++i;
798
+ } else {
799
+ throw new RuntimeException("Unterminated quoted field mid-line");
800
+ }
801
+ }
802
+ } else if (!quoted && c[i] == ',') {
803
+ return i;
804
+ }
805
+ if (!quote && (c[i] == ',')) {
806
+ // found a comma, return this location
807
+ return i;
808
+ } else if (c[i] == '\"') {
809
+ // if it's a quote, then either the next char is another quote,
810
+ // or if this is a quoted entry, it better be a comma
811
+ quote = !quote;
812
+ }
813
+ }
814
+ // if still inside a quote, indicate that another line should be read
815
+ if (quote) {
816
+ return -1;
817
+ }
818
+ // made it to the end of the array with no new comma
819
+ return c.length;
820
+ }
821
+ */
822
+
823
+
824
+ /**
825
+ * Read a .ods (OpenDoc spreadsheet) zip file from an InputStream, and
826
+ * return the InputStream for content.xml contained inside.
827
+ */
828
+ private InputStream odsFindContentXML(InputStream input) {
829
+ ZipInputStream zis = new ZipInputStream(input);
830
+ ZipEntry entry = null;
831
+ try {
832
+ while ((entry = zis.getNextEntry()) != null) {
833
+ if (entry.getName().equals("content.xml")) {
834
+ return zis;
835
+ }
836
+ }
837
+ } catch (IOException e) {
838
+ e.printStackTrace();
839
+ }
840
+ return null;
841
+ }
842
+
843
+
844
+ protected void odsParse(InputStream input, String worksheet, boolean header) {
845
+ try {
846
+ InputStream contentStream = odsFindContentXML(input);
847
+ XML xml = new XML(contentStream);
848
+
849
+ // table files will have multiple sheets..
850
+ // <table:table table:name="Sheet1" table:style-name="ta1" table:print="false">
851
+ // <table:table table:name="Sheet2" table:style-name="ta1" table:print="false">
852
+ // <table:table table:name="Sheet3" table:style-name="ta1" table:print="false">
853
+ XML[] sheets =
854
+ xml.getChildren("office:body/office:spreadsheet/table:table");
855
+
856
+ boolean found = false;
857
+ for (XML sheet : sheets) {
858
+ // System.out.println(sheet.getAttribute("table:name"));
859
+ if (worksheet == null || worksheet.equals(sheet.getString("table:name"))) {
860
+ odsParseSheet(sheet, header);
861
+ found = true;
862
+ if (worksheet == null) {
863
+ break; // only read the first sheet
864
+ }
865
+ }
866
+ }
867
+ if (!found) {
868
+ if (worksheet == null) {
869
+ throw new RuntimeException("No worksheets found in the ODS file.");
870
+ } else {
871
+ throw new RuntimeException("No worksheet named " + worksheet +
872
+ " found in the ODS file.");
873
+ }
874
+ }
875
+ } catch (UnsupportedEncodingException e) {
876
+ e.printStackTrace();
877
+ } catch (IOException e) {
878
+ e.printStackTrace();
879
+ } catch (ParserConfigurationException e) {
880
+ e.printStackTrace();
881
+ } catch (SAXException e) {
882
+ e.printStackTrace();
883
+ }
884
+ }
885
+
886
+
887
+ /**
888
+ * Parses a single sheet of XML from this file.
889
+ * @param The XML object for a single worksheet from the ODS file
890
+ */
891
+ private void odsParseSheet(XML sheet, boolean header) {
892
+ // Extra <p> or <a> tags inside the text tag for the cell will be stripped.
893
+ // Different from showing formulas, and not quite the same as 'save as
894
+ // displayed' option when saving from inside OpenOffice. Only time we
895
+ // wouldn't want this would be so that we could parse hyperlinks and
896
+ // styling information intact, but that's out of scope for the p5 version.
897
+ final boolean ignoreTags = true;
898
+
899
+ XML[] rows = sheet.getChildren("table:table-row");
900
+ //xml.getChildren("office:body/office:spreadsheet/table:table/table:table-row");
901
+
902
+ int rowIndex = 0;
903
+ for (XML row : rows) {
904
+ int rowRepeat = row.getInt("table:number-rows-repeated", 1);
905
+ // if (rowRepeat != 1) {
906
+ // System.out.println(rowRepeat + " " + rowCount + " " + (rowCount + rowRepeat));
907
+ // }
908
+ boolean rowNotNull = false;
909
+ XML[] cells = row.getChildren();
910
+ int columnIndex = 0;
911
+
912
+ for (XML cell : cells) {
913
+ int cellRepeat = cell.getInt("table:number-columns-repeated", 1);
914
+
915
+ // <table:table-cell table:formula="of:=SUM([.E7:.E8])" office:value-type="float" office:value="4150">
916
+ // <text:p>4150.00</text:p>
917
+ // </table:table-cell>
918
+
919
+ String cellData = ignoreTags ? cell.getString("office:value") : null;
920
+
921
+ // if there's an office:value in the cell, just roll with that
922
+ if (cellData == null) {
923
+ int cellKids = cell.getChildCount();
924
+ if (cellKids != 0) {
925
+ XML[] paragraphElements = cell.getChildren("text:p");
926
+ if (paragraphElements.length != 1) {
927
+ for (XML el : paragraphElements) {
928
+ System.err.println(el.toString());
929
+ }
930
+ throw new RuntimeException("found more than one text:p element");
931
+ }
932
+ XML textp = paragraphElements[0];
933
+ String textpContent = textp.getContent();
934
+ // if there are sub-elements, the content shows up as a child element
935
+ // (for which getName() returns null.. which seems wrong)
936
+ if (textpContent != null) {
937
+ cellData = textpContent; // nothing fancy, the text is in the text:p element
938
+ } else {
939
+ XML[] textpKids = textp.getChildren();
940
+ StringBuilder cellBuffer = new StringBuilder();
941
+ for (XML kid : textpKids) {
942
+ String kidName = kid.getName();
943
+ if (kidName == null) {
944
+ odsAppendNotNull(kid, cellBuffer);
945
+
946
+ } else if (kidName.equals("text:s")) {
947
+ int spaceCount = kid.getInt("text:c", 1);
948
+ for (int space = 0; space < spaceCount; space++) {
949
+ cellBuffer.append(' ');
950
+ }
951
+ } else if (kidName.equals("text:span")) {
952
+ odsAppendNotNull(kid, cellBuffer);
953
+
954
+ } else if (kidName.equals("text:a")) {
955
+ // <text:a xlink:href="http://blah.com/">blah.com</text:a>
956
+ if (ignoreTags) {
957
+ cellBuffer.append(kid.getString("xlink:href"));
958
+ } else {
959
+ odsAppendNotNull(kid, cellBuffer);
960
+ }
961
+
962
+ } else {
963
+ odsAppendNotNull(kid, cellBuffer);
964
+ System.err.println(getClass().getName() + ": don't understand: " + kid);
965
+ //throw new RuntimeException("I'm not used to this.");
966
+ }
967
+ }
968
+ cellData = cellBuffer.toString();
969
+ }
970
+ //setString(rowIndex, columnIndex, c); //text[0].getContent());
971
+ //columnIndex++;
972
+ }
973
+ }
974
+ for (int r = 0; r < cellRepeat; r++) {
975
+ if (cellData != null) {
976
+ //System.out.println("setting " + rowIndex + "," + columnIndex + " to " + cellData);
977
+ setString(rowIndex, columnIndex, cellData);
978
+ }
979
+ columnIndex++;
980
+ if (cellData != null) {
981
+ // if (columnIndex > columnMax) {
982
+ // columnMax = columnIndex;
983
+ // }
984
+ rowNotNull = true;
985
+ }
986
+ }
987
+ }
988
+ if (header) {
989
+ removeTitleRow(); // efficient enough on the first row
990
+ header = false; // avoid infinite loop
991
+
992
+ } else {
993
+ if (rowNotNull && rowRepeat > 1) {
994
+ String[] rowStrings = getStringRow(rowIndex);
995
+ for (int r = 1; r < rowRepeat; r++) {
996
+ addRow(rowStrings);
997
+ }
998
+ }
999
+ rowIndex += rowRepeat;
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+
1005
+ private void odsAppendNotNull(XML kid, StringBuilder buffer) {
1006
+ String content = kid.getContent();
1007
+ if (content != null) {
1008
+ buffer.append(content);
1009
+ }
1010
+ }
1011
+
1012
+
1013
+ // A 'Class' object is used here, so the syntax for this function is:
1014
+ // Table t = loadTable("cars3.tsv", "header");
1015
+ // Record[] records = (Record[]) t.parse(Record.class);
1016
+ // While t.parse("Record") might be nicer, the class is likely to be an
1017
+ // inner class (another tab in a PDE sketch) or even inside a package,
1018
+ // so additional information would be needed to locate it. The name of the
1019
+ // inner class would be "SketchName$Record" which isn't acceptable syntax
1020
+ // to make people use. Better to just introduce the '.class' syntax.
1021
+
1022
+ // Unlike the Table class itself, this accepts char and boolean fields in
1023
+ // the target class, since they're much more prevalent, and don't require
1024
+ // a zillion extra methods and special cases in the rest of the class here.
1025
+
1026
+ // since this is likely an inner class, needs a reference to its parent,
1027
+ // because that's passed to the constructor parameter (inserted by the
1028
+ // compiler) of an inner class by the runtime.
1029
+
1030
+ /** incomplete, do not use */
1031
+ public void parseInto(Object enclosingObject, String fieldName) {
1032
+ Class<?> target = null;
1033
+ Object outgoing = null;
1034
+ Field targetField = null;
1035
+ try {
1036
+ // Object targetObject,
1037
+ // Class target -> get this from the type of fieldName
1038
+ // Class sketchClass = sketch.getClass();
1039
+ Class<?> sketchClass = enclosingObject.getClass();
1040
+ targetField = sketchClass.getDeclaredField(fieldName);
1041
+ // PApplet.println("found " + targetField);
1042
+ Class<?> targetArray = targetField.getType();
1043
+ if (!targetArray.isArray()) {
1044
+ // fieldName is not an array
1045
+ } else {
1046
+ target = targetArray.getComponentType();
1047
+ outgoing = Array.newInstance(target, getRowCount());
1048
+ }
1049
+ } catch (NoSuchFieldException e) {
1050
+ e.printStackTrace();
1051
+ } catch (SecurityException e) {
1052
+ e.printStackTrace();
1053
+ }
1054
+
1055
+ // Object enclosingObject = sketch;
1056
+ // PApplet.println("enclosing obj is " + enclosingObject);
1057
+ Class<?> enclosingClass = target.getEnclosingClass();
1058
+ Constructor<?> con = null;
1059
+
1060
+ try {
1061
+ if (enclosingClass == null) {
1062
+ con = target.getDeclaredConstructor(); //new Class[] { });
1063
+ // PApplet.println("no enclosing class");
1064
+ } else {
1065
+ con = target.getDeclaredConstructor(enclosingClass);
1066
+ // PApplet.println("enclosed by " + enclosingClass.getName());
1067
+ }
1068
+ if (!con.canAccess(null)) {
1069
+ // System.out.println("setting constructor to public");
1070
+ con.setAccessible(true);
1071
+ }
1072
+ } catch (SecurityException e) {
1073
+ e.printStackTrace();
1074
+ } catch (NoSuchMethodException e) {
1075
+ e.printStackTrace();
1076
+ }
1077
+
1078
+ Field[] fields = target.getDeclaredFields();
1079
+ ArrayList<Field> inuse = new ArrayList<>();
1080
+ for (Field field : fields) {
1081
+ String name = field.getName();
1082
+ if (getColumnIndex(name, false) != -1) {
1083
+ inuse.add(field);
1084
+ } else {
1085
+ // System.out.println("skipping field " + name);
1086
+ }
1087
+ }
1088
+
1089
+ int index = 0;
1090
+ try {
1091
+ for (TableRow row : rows()) {
1092
+ Object item = null;
1093
+ if (enclosingClass == null) {
1094
+ //item = target.newInstance();
1095
+ item = con.newInstance();
1096
+ } else {
1097
+ item = con.newInstance(enclosingObject);
1098
+ }
1099
+
1100
+ // Only needed once
1101
+ if (index == 0) {
1102
+ for (Field field : inuse) {
1103
+ if (!field.canAccess(item)) {
1104
+ // PApplet.println(" changing field access");
1105
+ field.setAccessible(true);
1106
+ }
1107
+ }
1108
+ }
1109
+
1110
+ //Object item = defaultCons.newInstance(new Object[] { });
1111
+ for (Field field : inuse) {
1112
+ String name = field.getName();
1113
+ // PApplet.println("gonna set field " + name);
1114
+
1115
+ if (field.getType() == String.class) {
1116
+ field.set(item, row.getString(name));
1117
+
1118
+ } else if (field.getType() == Integer.TYPE) {
1119
+ field.setInt(item, row.getInt(name));
1120
+
1121
+ } else if (field.getType() == Long.TYPE) {
1122
+ field.setLong(item, row.getLong(name));
1123
+
1124
+ } else if (field.getType() == Float.TYPE) {
1125
+ field.setFloat(item, row.getFloat(name));
1126
+
1127
+ } else if (field.getType() == Double.TYPE) {
1128
+ field.setDouble(item, row.getDouble(name));
1129
+
1130
+ } else if (field.getType() == Boolean.TYPE) {
1131
+ String content = row.getString(name);
1132
+ if (content != null) {
1133
+ // Only bother setting if it's true,
1134
+ // otherwise false by default anyway.
1135
+ if (content.toLowerCase().equals("true") ||
1136
+ content.equals("1")) {
1137
+ field.setBoolean(item, true);
1138
+ }
1139
+ }
1140
+ // if (content == null) {
1141
+ // field.setBoolean(item, false); // necessary?
1142
+ // } else if (content.toLowerCase().equals("true")) {
1143
+ // field.setBoolean(item, true);
1144
+ // } else if (content.equals("1")) {
1145
+ // field.setBoolean(item, true);
1146
+ // } else {
1147
+ // field.setBoolean(item, false); // necessary?
1148
+ // }
1149
+ } else if (field.getType() == Character.TYPE) {
1150
+ String content = row.getString(name);
1151
+ if (content != null && content.length() > 0) {
1152
+ // Otherwise set to \0 anyway
1153
+ field.setChar(item, content.charAt(0));
1154
+ }
1155
+ }
1156
+ }
1157
+ // list.add(item);
1158
+ Array.set(outgoing, index++, item);
1159
+ }
1160
+ if (!targetField.canAccess(enclosingObject)) {
1161
+ // PApplet.println("setting target field to public");
1162
+ targetField.setAccessible(true);
1163
+ }
1164
+ // Set the array in the sketch
1165
+ // targetField.set(sketch, outgoing);
1166
+ targetField.set(enclosingObject, outgoing);
1167
+
1168
+ } catch (InstantiationException e) {
1169
+ e.printStackTrace();
1170
+ } catch (IllegalAccessException e) {
1171
+ e.printStackTrace();
1172
+ } catch (IllegalArgumentException e) {
1173
+ e.printStackTrace();
1174
+ } catch (InvocationTargetException e) {
1175
+ e.printStackTrace();
1176
+ }
1177
+ }
1178
+
1179
+
1180
+ public boolean save(File file, String options) throws IOException {
1181
+ return save(PApplet.createOutput(file),
1182
+ Table.extensionOptions(false, file.getName(), options));
1183
+ }
1184
+
1185
+
1186
+ public boolean save(OutputStream output, String options) {
1187
+ PrintWriter writer = PApplet.createWriter(output);
1188
+ String extension = null;
1189
+ if (options == null) {
1190
+ throw new IllegalArgumentException("No extension specified for saving this Table");
1191
+ }
1192
+
1193
+ String[] opts = PApplet.trim(PApplet.split(options, ','));
1194
+ // Only option for save is the extension, so we can safely grab the last
1195
+ extension = opts[opts.length - 1];
1196
+ boolean found = false;
1197
+ for (String ext : saveExtensions) {
1198
+ if (extension.equals(ext)) {
1199
+ found = true;
1200
+ break;
1201
+ }
1202
+ }
1203
+ // Not providing a fallback; let's make users specify an extension
1204
+ if (!found) {
1205
+ throw new IllegalArgumentException("'" + extension + "' not available for Table");
1206
+ }
1207
+
1208
+ if (extension.equals("csv")) {
1209
+ writeCSV(writer);
1210
+ } else if (extension.equals("tsv")) {
1211
+ writeTSV(writer);
1212
+ } else if (extension.equals("ods")) {
1213
+ try {
1214
+ saveODS(output);
1215
+ } catch (IOException e) {
1216
+ e.printStackTrace();
1217
+ return false;
1218
+ }
1219
+ } else if (extension.equals("html")) {
1220
+ writeHTML(writer);
1221
+ } else if (extension.equals("bin")) {
1222
+ try {
1223
+ saveBinary(output);
1224
+ } catch (IOException e) {
1225
+ e.printStackTrace();
1226
+ return false;
1227
+ }
1228
+ }
1229
+ writer.flush();
1230
+ writer.close();
1231
+ return true;
1232
+ }
1233
+
1234
+
1235
+ protected void writeTSV(PrintWriter writer) {
1236
+ if (columnTitles != null) {
1237
+ for (int col = 0; col < columns.length; col++) {
1238
+ if (col != 0) {
1239
+ writer.print('\t');
1240
+ }
1241
+ if (columnTitles[col] != null) {
1242
+ writer.print(columnTitles[col]);
1243
+ }
1244
+ }
1245
+ writer.println();
1246
+ }
1247
+ for (int row = 0; row < rowCount; row++) {
1248
+ for (int col = 0; col < getColumnCount(); col++) {
1249
+ if (col != 0) {
1250
+ writer.print('\t');
1251
+ }
1252
+ String entry = getString(row, col);
1253
+ // just write null entries as blanks, rather than spewing 'null'
1254
+ // all over the spreadsheet file.
1255
+ if (entry != null) {
1256
+ writer.print(entry);
1257
+ }
1258
+ }
1259
+ writer.println();
1260
+ }
1261
+ writer.flush();
1262
+ }
1263
+
1264
+
1265
+ protected void writeCSV(PrintWriter writer) {
1266
+ if (columnTitles != null) {
1267
+ for (int col = 0; col < getColumnCount(); col++) {
1268
+ if (col != 0) {
1269
+ writer.print(',');
1270
+ }
1271
+ try {
1272
+ if (columnTitles[col] != null) { // col < columnTitles.length &&
1273
+ writeEntryCSV(writer, columnTitles[col]);
1274
+ }
1275
+ } catch (ArrayIndexOutOfBoundsException e) {
1276
+ PApplet.printArray(columnTitles);
1277
+ PApplet.printArray(columns);
1278
+ throw e;
1279
+ }
1280
+ }
1281
+ writer.println();
1282
+ }
1283
+ for (int row = 0; row < rowCount; row++) {
1284
+ for (int col = 0; col < getColumnCount(); col++) {
1285
+ if (col != 0) {
1286
+ writer.print(',');
1287
+ }
1288
+ String entry = getString(row, col);
1289
+ // just write null entries as blanks, rather than spewing 'null'
1290
+ // all over the spreadsheet file.
1291
+ if (entry != null) {
1292
+ writeEntryCSV(writer, entry);
1293
+ }
1294
+ }
1295
+ // Prints the newline for the row, even if it's missing
1296
+ writer.println();
1297
+ }
1298
+ writer.flush();
1299
+ }
1300
+
1301
+
1302
+ protected void writeEntryCSV(PrintWriter writer, String entry) {
1303
+ if (entry != null) {
1304
+ if (entry.indexOf('\"') != -1) { // convert quotes to double quotes
1305
+ char[] c = entry.toCharArray();
1306
+ writer.print('\"');
1307
+ for (int i = 0; i < c.length; i++) {
1308
+ if (c[i] == '\"') {
1309
+ writer.print("\"\"");
1310
+ } else {
1311
+ writer.print(c[i]);
1312
+ }
1313
+ }
1314
+ writer.print('\"');
1315
+
1316
+ // add quotes if commas or CR/LF are in the entry
1317
+ } else if (entry.indexOf(',') != -1 ||
1318
+ entry.indexOf('\n') != -1 ||
1319
+ entry.indexOf('\r') != -1) {
1320
+ writer.print('\"');
1321
+ writer.print(entry);
1322
+ writer.print('\"');
1323
+
1324
+
1325
+ // add quotes if leading or trailing space
1326
+ } else if ((entry.length() > 0) &&
1327
+ (entry.charAt(0) == ' ' ||
1328
+ entry.charAt(entry.length() - 1) == ' ')) {
1329
+ writer.print('\"');
1330
+ writer.print(entry);
1331
+ writer.print('\"');
1332
+
1333
+ } else {
1334
+ writer.print(entry);
1335
+ }
1336
+ }
1337
+ }
1338
+
1339
+
1340
+ protected void writeHTML(PrintWriter writer) {
1341
+ writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">");
1342
+ // writer.println("<!DOCTYPE html>");
1343
+ // writer.println("<meta charset=\"utf-8\">");
1344
+
1345
+ writer.println("<html>");
1346
+ writer.println("<head>");
1347
+ writer.println(" <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />");
1348
+ writer.println("</head>");
1349
+
1350
+ writer.println("<body>");
1351
+ writer.println(" <table>");
1352
+
1353
+ if (hasColumnTitles()) {
1354
+ writer.println(" <tr>");
1355
+ for (String entry : getColumnTitles()) {
1356
+ writer.print(" <th>");
1357
+ if (entry != null) {
1358
+ writeEntryHTML(writer, entry);
1359
+ }
1360
+ writer.println("</th>");
1361
+ }
1362
+ writer.println(" </tr>");
1363
+ }
1364
+
1365
+ for (int row = 0; row < getRowCount(); row++) {
1366
+ writer.println(" <tr>");
1367
+ for (int col = 0; col < getColumnCount(); col++) {
1368
+ String entry = getString(row, col);
1369
+ writer.print(" <td>");
1370
+ if (entry != null) {
1371
+ // probably not a great idea to mess w/ the export
1372
+ // if (entry.startsWith("<") && entry.endsWith(">")) {
1373
+ // writer.print(entry);
1374
+ // } else {
1375
+ writeEntryHTML(writer, entry);
1376
+ // }
1377
+ }
1378
+ writer.println("</td>");
1379
+ }
1380
+ writer.println(" </tr>");
1381
+ }
1382
+ writer.println(" </table>");
1383
+ writer.println("</body>");
1384
+
1385
+ writer.println("</html>");
1386
+ writer.flush();
1387
+ }
1388
+
1389
+
1390
+ protected void writeEntryHTML(PrintWriter writer, String entry) {
1391
+ //char[] chars = entry.toCharArray();
1392
+ for (char c : entry.toCharArray()) { //chars) {
1393
+ if (c == '<') {
1394
+ writer.print("&lt;");
1395
+ } else if (c == '>') {
1396
+ writer.print("&gt;");
1397
+ } else if (c == '&') {
1398
+ writer.print("&amp;");
1399
+ // } else if (c == '\'') { // only in XML
1400
+ // writer.print("&apos;");
1401
+ } else if (c == '"') {
1402
+ writer.print("&quot;");
1403
+
1404
+ } else if (c < 32 || c > 127) { // keep in ASCII or Tidy complains
1405
+ writer.print("&#");
1406
+ writer.print((int) c);
1407
+ writer.print(';');
1408
+
1409
+ } else {
1410
+ writer.print(c);
1411
+ }
1412
+ }
1413
+ }
1414
+
1415
+
1416
+ protected void saveODS(OutputStream os) throws IOException {
1417
+ ZipOutputStream zos = new ZipOutputStream(os);
1418
+
1419
+ final String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
1420
+
1421
+ ZipEntry entry = new ZipEntry("META-INF/manifest.xml");
1422
+ String[] lines = new String[] {
1423
+ xmlHeader,
1424
+ "<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">",
1425
+ " <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.spreadsheet\" manifest:version=\"1.2\" manifest:full-path=\"/\"/>",
1426
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>",
1427
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>",
1428
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>",
1429
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"settings.xml\"/>",
1430
+ "</manifest:manifest>"
1431
+ };
1432
+ zos.putNextEntry(entry);
1433
+ zos.write(PApplet.join(lines, "\n").getBytes());
1434
+ zos.closeEntry();
1435
+
1436
+ /*
1437
+ entry = new ZipEntry("meta.xml");
1438
+ lines = new String[] {
1439
+ xmlHeader,
1440
+ "<office:document-meta office:version=\"1.0\"" +
1441
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1442
+ };
1443
+ zos.putNextEntry(entry);
1444
+ zos.write(PApplet.join(lines, "\n").getBytes());
1445
+ zos.closeEntry();
1446
+
1447
+ entry = new ZipEntry("meta.xml");
1448
+ lines = new String[] {
1449
+ xmlHeader,
1450
+ "<office:document-settings office:version=\"1.0\"" +
1451
+ " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" +
1452
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1453
+ " xmlns:ooo=\"http://openoffice.org/2004/office\"" +
1454
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />"
1455
+ };
1456
+ zos.putNextEntry(entry);
1457
+ zos.write(PApplet.join(lines, "\n").getBytes());
1458
+ zos.closeEntry();
1459
+
1460
+ entry = new ZipEntry("settings.xml");
1461
+ lines = new String[] {
1462
+ xmlHeader,
1463
+ "<office:document-settings office:version=\"1.0\"" +
1464
+ " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" +
1465
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1466
+ " xmlns:ooo=\"http://openoffice.org/2004/office\"" +
1467
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />"
1468
+ };
1469
+ zos.putNextEntry(entry);
1470
+ zos.write(PApplet.join(lines, "\n").getBytes());
1471
+ zos.closeEntry();
1472
+
1473
+ entry = new ZipEntry("styles.xml");
1474
+ lines = new String[] {
1475
+ xmlHeader,
1476
+ "<office:document-styles office:version=\"1.0\"" +
1477
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1478
+ };
1479
+ zos.putNextEntry(entry);
1480
+ zos.write(PApplet.join(lines, "\n").getBytes());
1481
+ zos.closeEntry();
1482
+ */
1483
+
1484
+ final String[] dummyFiles = new String[] {
1485
+ "meta.xml", "settings.xml", "styles.xml"
1486
+ };
1487
+ lines = new String[] {
1488
+ xmlHeader,
1489
+ "<office:document-meta office:version=\"1.0\"" +
1490
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1491
+ };
1492
+ byte[] dummyBytes = PApplet.join(lines, "\n").getBytes();
1493
+ for (String filename : dummyFiles) {
1494
+ entry = new ZipEntry(filename);
1495
+ zos.putNextEntry(entry);
1496
+ zos.write(dummyBytes);
1497
+ zos.closeEntry();
1498
+ }
1499
+
1500
+ //
1501
+
1502
+ entry = new ZipEntry("mimetype");
1503
+ zos.putNextEntry(entry);
1504
+ zos.write("application/vnd.oasis.opendocument.spreadsheet".getBytes());
1505
+ zos.closeEntry();
1506
+
1507
+ //
1508
+
1509
+ entry = new ZipEntry("content.xml");
1510
+ zos.putNextEntry(entry);
1511
+ //lines = new String[] {
1512
+ writeUTF(zos, xmlHeader,
1513
+ "<office:document-content" +
1514
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1515
+ " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"" +
1516
+ " xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"" +
1517
+ " office:version=\"1.2\">",
1518
+ " <office:body>",
1519
+ " <office:spreadsheet>",
1520
+ " <table:table table:name=\"Sheet1\" table:print=\"false\">");
1521
+ //zos.write(PApplet.join(lines, "\n").getBytes());
1522
+
1523
+ byte[] rowStart = " <table:table-row>\n".getBytes();
1524
+ byte[] rowStop = " </table:table-row>\n".getBytes();
1525
+
1526
+ if (hasColumnTitles()) {
1527
+ zos.write(rowStart);
1528
+ for (int i = 0; i < getColumnCount(); i++) {
1529
+ saveStringODS(zos, columnTitles[i]);
1530
+ }
1531
+ zos.write(rowStop);
1532
+ }
1533
+
1534
+ for (TableRow row : rows()) {
1535
+ zos.write(rowStart);
1536
+ for (int i = 0; i < getColumnCount(); i++) {
1537
+ if (columnTypes[i] == STRING || columnTypes[i] == CATEGORY) {
1538
+ saveStringODS(zos, row.getString(i));
1539
+ } else {
1540
+ saveNumberODS(zos, row.getString(i));
1541
+ }
1542
+ }
1543
+ zos.write(rowStop);
1544
+ }
1545
+
1546
+ //lines = new String[] {
1547
+ writeUTF(zos, " </table:table>",
1548
+ " </office:spreadsheet>",
1549
+ " </office:body>",
1550
+ "</office:document-content>");
1551
+ //zos.write(PApplet.join(lines, "\n").getBytes());
1552
+ zos.closeEntry();
1553
+
1554
+ zos.flush();
1555
+ zos.close();
1556
+ }
1557
+
1558
+
1559
+ void saveStringODS(OutputStream output, String text) throws IOException {
1560
+ // At this point, I should have just used the XML library. But this does
1561
+ // save us from having to create the entire document in memory again before
1562
+ // writing to the file. So while it's dorky, the outcome is still useful.
1563
+ StringBuilder sanitized = new StringBuilder();
1564
+ if (text != null) {
1565
+ char[] array = text.toCharArray();
1566
+ for (char c : array) {
1567
+ if (c == '&') {
1568
+ sanitized.append("&amp;");
1569
+ } else if (c == '\'') {
1570
+ sanitized.append("&apos;");
1571
+ } else if (c == '"') {
1572
+ sanitized.append("&quot;");
1573
+ } else if (c == '<') {
1574
+ sanitized.append("&lt;");
1575
+ } else if (c == '>') {
1576
+ sanitized.append("&rt;");
1577
+ } else if (c < 32 || c > 127) {
1578
+ sanitized.append("&#" + ((int) c) + ";");
1579
+ } else {
1580
+ sanitized.append(c);
1581
+ }
1582
+ }
1583
+ }
1584
+
1585
+ writeUTF(output,
1586
+ " <table:table-cell office:value-type=\"string\">",
1587
+ " <text:p>" + sanitized + "</text:p>",
1588
+ " </table:table-cell>");
1589
+ }
1590
+
1591
+
1592
+ void saveNumberODS(OutputStream output, String text) throws IOException {
1593
+ writeUTF(output,
1594
+ " <table:table-cell office:value-type=\"float\" office:value=\"" + text + "\">",
1595
+ " <text:p>" + text + "</text:p>",
1596
+ " </table:table-cell>");
1597
+ }
1598
+
1599
+
1600
+ static Charset utf8;
1601
+
1602
+ static void writeUTF(OutputStream output, String... lines) throws IOException {
1603
+ if (utf8 == null) {
1604
+ utf8 = Charset.forName("UTF-8");
1605
+ }
1606
+ for (String str : lines) {
1607
+ output.write(str.getBytes(utf8));
1608
+ output.write('\n');
1609
+ }
1610
+ }
1611
+
1612
+
1613
+ protected void saveBinary(OutputStream os) throws IOException {
1614
+ DataOutputStream output = new DataOutputStream(new BufferedOutputStream(os));
1615
+ output.writeInt(0x9007AB1E); // version
1616
+ output.writeInt(getRowCount());
1617
+ output.writeInt(getColumnCount());
1618
+ if (columnTitles != null) {
1619
+ output.writeBoolean(true);
1620
+ for (String title : columnTitles) {
1621
+ output.writeUTF(title);
1622
+ }
1623
+ } else {
1624
+ output.writeBoolean(false);
1625
+ }
1626
+ for (int i = 0; i < getColumnCount(); i++) {
1627
+ //System.out.println(i + " is " + columnTypes[i]);
1628
+ output.writeInt(columnTypes[i]);
1629
+ }
1630
+
1631
+ for (int i = 0; i < getColumnCount(); i++) {
1632
+ if (columnTypes[i] == CATEGORY) {
1633
+ columnCategories[i].write(output);
1634
+ }
1635
+ }
1636
+ if (missingString == null) {
1637
+ output.writeBoolean(false);
1638
+ } else {
1639
+ output.writeBoolean(true);
1640
+ output.writeUTF(missingString);
1641
+ }
1642
+ output.writeInt(missingInt);
1643
+ output.writeLong(missingLong);
1644
+ output.writeFloat(missingFloat);
1645
+ output.writeDouble(missingDouble);
1646
+ output.writeInt(missingCategory);
1647
+
1648
+ for (TableRow row : rows()) {
1649
+ for (int col = 0; col < getColumnCount(); col++) {
1650
+ switch (columnTypes[col]) {
1651
+ case STRING:
1652
+ String str = row.getString(col);
1653
+ if (str == null) {
1654
+ output.writeBoolean(false);
1655
+ } else {
1656
+ output.writeBoolean(true);
1657
+ output.writeUTF(str);
1658
+ }
1659
+ break;
1660
+ case INT:
1661
+ output.writeInt(row.getInt(col));
1662
+ break;
1663
+ case LONG:
1664
+ output.writeLong(row.getLong(col));
1665
+ break;
1666
+ case FLOAT:
1667
+ output.writeFloat(row.getFloat(col));
1668
+ break;
1669
+ case DOUBLE:
1670
+ output.writeDouble(row.getDouble(col));
1671
+ break;
1672
+ case CATEGORY:
1673
+ String peace = row.getString(col);
1674
+ if (peace.equals(missingString)) {
1675
+ output.writeInt(missingCategory);
1676
+ } else {
1677
+ output.writeInt(columnCategories[col].index(peace));
1678
+ }
1679
+ break;
1680
+ }
1681
+ }
1682
+ }
1683
+
1684
+ output.flush();
1685
+ output.close();
1686
+ }
1687
+
1688
+
1689
+ protected void loadBinary(InputStream is) throws IOException {
1690
+ DataInputStream input = new DataInputStream(new BufferedInputStream(is));
1691
+
1692
+ int magic = input.readInt();
1693
+ if (magic != 0x9007AB1E) {
1694
+ throw new IOException("Not a compatible binary table (magic was " + PApplet.hex(magic) + ")");
1695
+ }
1696
+ int rowCount = input.readInt();
1697
+ setRowCount(rowCount);
1698
+ int columnCount = input.readInt();
1699
+ setColumnCount(columnCount);
1700
+
1701
+ boolean hasTitles = input.readBoolean();
1702
+ if (hasTitles) {
1703
+ columnTitles = new String[getColumnCount()];
1704
+ for (int i = 0; i < columnCount; i++) {
1705
+ //columnTitles[i] = input.readUTF();
1706
+ setColumnTitle(i, input.readUTF());
1707
+ }
1708
+ }
1709
+ for (int column = 0; column < columnCount; column++) {
1710
+ int newType = input.readInt();
1711
+ columnTypes[column] = newType;
1712
+ switch (newType) {
1713
+ case INT:
1714
+ columns[column] = new int[rowCount];
1715
+ break;
1716
+ case LONG:
1717
+ columns[column] = new long[rowCount];
1718
+ break;
1719
+ case FLOAT:
1720
+ columns[column] = new float[rowCount];
1721
+ break;
1722
+ case DOUBLE:
1723
+ columns[column] = new double[rowCount];
1724
+ break;
1725
+ case STRING:
1726
+ columns[column] = new String[rowCount];
1727
+ break;
1728
+ case CATEGORY:
1729
+ columns[column] = new int[rowCount];
1730
+ break;
1731
+ default:
1732
+ throw new IllegalArgumentException(newType + " is not a valid column type.");
1733
+ }
1734
+ }
1735
+
1736
+ for (int i = 0; i < columnCount; i++) {
1737
+ if (columnTypes[i] == CATEGORY) {
1738
+ columnCategories[i] = new HashMapBlows(input);
1739
+ }
1740
+ }
1741
+
1742
+ if (input.readBoolean()) {
1743
+ missingString = input.readUTF();
1744
+ } else {
1745
+ missingString = null;
1746
+ }
1747
+ missingInt = input.readInt();
1748
+ missingLong = input.readLong();
1749
+ missingFloat = input.readFloat();
1750
+ missingDouble = input.readDouble();
1751
+ missingCategory = input.readInt();
1752
+
1753
+ for (int row = 0; row < rowCount; row++) {
1754
+ for (int col = 0; col < columnCount; col++) {
1755
+ switch (columnTypes[col]) {
1756
+ case STRING:
1757
+ String str = null;
1758
+ if (input.readBoolean()) {
1759
+ str = input.readUTF();
1760
+ }
1761
+ setString(row, col, str);
1762
+ break;
1763
+ case INT:
1764
+ setInt(row, col, input.readInt());
1765
+ break;
1766
+ case LONG:
1767
+ setLong(row, col, input.readLong());
1768
+ break;
1769
+ case FLOAT:
1770
+ setFloat(row, col, input.readFloat());
1771
+ break;
1772
+ case DOUBLE:
1773
+ setDouble(row, col, input.readDouble());
1774
+ break;
1775
+ case CATEGORY:
1776
+ int index = input.readInt();
1777
+ //String name = columnCategories[col].key(index);
1778
+ setInt(row, col, index);
1779
+ break;
1780
+ }
1781
+ }
1782
+ }
1783
+
1784
+ input.close();
1785
+ }
1786
+
1787
+
1788
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1789
+
1790
+
1791
+ /**
1792
+ * @webref table:method
1793
+ * @brief Adds a new column to a table
1794
+ * @see Table#removeColumn(String)
1795
+ */
1796
+ public void addColumn() {
1797
+ addColumn(null, STRING);
1798
+ }
1799
+
1800
+
1801
+ /**
1802
+ * @param title the title to be used for the new column
1803
+ */
1804
+ public void addColumn(String title) {
1805
+ addColumn(title, STRING);
1806
+ }
1807
+
1808
+
1809
+ /**
1810
+ * @param type the type to be used for the new column: INT, LONG, FLOAT, DOUBLE, or STRING
1811
+ */
1812
+ public void addColumn(String title, int type) {
1813
+ insertColumn(columns.length, title, type);
1814
+ }
1815
+
1816
+
1817
+ public void insertColumn(int index) {
1818
+ insertColumn(index, null, STRING);
1819
+ }
1820
+
1821
+
1822
+ public void insertColumn(int index, String title) {
1823
+ insertColumn(index, title, STRING);
1824
+ }
1825
+
1826
+
1827
+ public void insertColumn(int index, String title, int type) {
1828
+ if (title != null && columnTitles == null) {
1829
+ columnTitles = new String[columns.length];
1830
+ }
1831
+ if (columnTitles != null) {
1832
+ columnTitles = PApplet.splice(columnTitles, title, index);
1833
+ columnIndices = null;
1834
+ }
1835
+ columnTypes = PApplet.splice(columnTypes, type, index);
1836
+
1837
+ // columnCategories = (HashMapBlows[])
1838
+ // PApplet.splice(columnCategories, new HashMapBlows(), index);
1839
+ HashMapBlows[] catTemp = new HashMapBlows[columns.length + 1];
1840
+ // Faster than arrayCopy for a dozen or so entries
1841
+ for (int i = 0; i < index; i++) {
1842
+ catTemp[i] = columnCategories[i];
1843
+ }
1844
+ catTemp[index] = new HashMapBlows();
1845
+ for (int i = index; i < columns.length; i++) {
1846
+ catTemp[i+1] = columnCategories[i];
1847
+ }
1848
+ columnCategories = catTemp;
1849
+
1850
+ Object[] temp = new Object[columns.length + 1];
1851
+ System.arraycopy(columns, 0, temp, 0, index);
1852
+ System.arraycopy(columns, index, temp, index+1, columns.length - index);
1853
+ columns = temp;
1854
+
1855
+ switch (type) {
1856
+ case INT: columns[index] = new int[rowCount]; break;
1857
+ case LONG: columns[index] = new long[rowCount]; break;
1858
+ case FLOAT: columns[index] = new float[rowCount]; break;
1859
+ case DOUBLE: columns[index] = new double[rowCount]; break;
1860
+ case STRING: columns[index] = new String[rowCount]; break;
1861
+ case CATEGORY: columns[index] = new int[rowCount]; break;
1862
+ }
1863
+ }
1864
+
1865
+ /**
1866
+ * @webref table:method
1867
+ * @brief Removes a column from a table
1868
+ * @param columnName the title of the column to be removed
1869
+ * @see Table#addColumn()
1870
+ */
1871
+ public void removeColumn(String columnName) {
1872
+ removeColumn(getColumnIndex(columnName));
1873
+ }
1874
+
1875
+ /**
1876
+ * @param column the index number of the column to be removed
1877
+ */
1878
+ public void removeColumn(int column) {
1879
+ int newCount = columns.length - 1;
1880
+
1881
+ Object[] columnsTemp = new Object[newCount];
1882
+ HashMapBlows[] catTemp = new HashMapBlows[newCount];
1883
+
1884
+ for (int i = 0; i < column; i++) {
1885
+ columnsTemp[i] = columns[i];
1886
+ catTemp[i] = columnCategories[i];
1887
+ }
1888
+ for (int i = column; i < newCount; i++) {
1889
+ columnsTemp[i] = columns[i+1];
1890
+ catTemp[i] = columnCategories[i+1];
1891
+ }
1892
+
1893
+ columns = columnsTemp;
1894
+ columnCategories = catTemp;
1895
+
1896
+ if (columnTitles != null) {
1897
+ String[] titlesTemp = new String[newCount];
1898
+ for (int i = 0; i < column; i++) {
1899
+ titlesTemp[i] = columnTitles[i];
1900
+ }
1901
+ for (int i = column; i < newCount; i++) {
1902
+ titlesTemp[i] = columnTitles[i+1];
1903
+ }
1904
+ columnTitles = titlesTemp;
1905
+ columnIndices = null;
1906
+ }
1907
+ }
1908
+
1909
+
1910
+ /**
1911
+ * @webref table:method
1912
+ * @brief Gets the number of columns in a table
1913
+ * @see Table#getRowCount()
1914
+ */
1915
+ public int getColumnCount() {
1916
+ return columns.length;
1917
+ }
1918
+
1919
+
1920
+ /**
1921
+ * Change the number of columns in this table. Resizes all rows to ensure
1922
+ * the same number of columns in each row. Entries in the additional (empty)
1923
+ * columns will be set to null.
1924
+ * @param newCount
1925
+ */
1926
+ public void setColumnCount(int newCount) {
1927
+ int oldCount = columns.length;
1928
+ if (oldCount != newCount) {
1929
+ columns = (Object[]) PApplet.expand(columns, newCount);
1930
+ // create new columns, default to String as the data type
1931
+ for (int c = oldCount; c < newCount; c++) {
1932
+ columns[c] = new String[rowCount];
1933
+ }
1934
+
1935
+ if (columnTitles != null) {
1936
+ columnTitles = PApplet.expand(columnTitles, newCount);
1937
+ }
1938
+ columnTypes = PApplet.expand(columnTypes, newCount);
1939
+ columnCategories = (HashMapBlows[])
1940
+ PApplet.expand(columnCategories, newCount);
1941
+ }
1942
+ }
1943
+
1944
+
1945
+ public void setColumnType(String columnName, String columnType) {
1946
+ setColumnType(checkColumnIndex(columnName), columnType);
1947
+ }
1948
+
1949
+
1950
+ static int parseColumnType(String columnType) {
1951
+ columnType = columnType.toLowerCase();
1952
+ int type = -1;
1953
+ if (columnType.equals("string")) {
1954
+ type = STRING;
1955
+ } else if (columnType.equals("int")) {
1956
+ type = INT;
1957
+ } else if (columnType.equals("long")) {
1958
+ type = LONG;
1959
+ } else if (columnType.equals("float")) {
1960
+ type = FLOAT;
1961
+ } else if (columnType.equals("double")) {
1962
+ type = DOUBLE;
1963
+ } else if (columnType.equals("category")) {
1964
+ type = CATEGORY;
1965
+ } else {
1966
+ throw new IllegalArgumentException("'" + columnType + "' is not a valid column type.");
1967
+ }
1968
+ return type;
1969
+ }
1970
+
1971
+
1972
+ /**
1973
+ * Set the data type for a column so that using it is more efficient.
1974
+ * @param column the column to change
1975
+ * @param columnType One of int, long, float, double, string, or category.
1976
+ */
1977
+ public void setColumnType(int column, String columnType) {
1978
+ setColumnType(column, parseColumnType(columnType));
1979
+ }
1980
+
1981
+
1982
+ public void setColumnType(String columnName, int newType) {
1983
+ setColumnType(checkColumnIndex(columnName), newType);
1984
+ }
1985
+
1986
+
1987
+ /**
1988
+ * Sets the column type. If data already exists, then it'll be converted to
1989
+ * the new type.
1990
+ * @param column the column whose type should be changed
1991
+ * @param newType something fresh, maybe try an int or a float for size?
1992
+ */
1993
+ public void setColumnType(int column, int newType) {
1994
+ switch (newType) {
1995
+ case INT: {
1996
+ int[] intData = new int[rowCount];
1997
+ for (int row = 0; row < rowCount; row++) {
1998
+ String s = getString(row, column);
1999
+ intData[row] = (s == null) ? missingInt : PApplet.parseInt(s, missingInt);
2000
+ }
2001
+ columns[column] = intData;
2002
+ break;
2003
+ }
2004
+ case LONG: {
2005
+ long[] longData = new long[rowCount];
2006
+ for (int row = 0; row < rowCount; row++) {
2007
+ String s = getString(row, column);
2008
+ try {
2009
+ longData[row] = (s == null) ? missingLong : Long.parseLong(s);
2010
+ } catch (NumberFormatException nfe) {
2011
+ longData[row] = missingLong;
2012
+ }
2013
+ }
2014
+ columns[column] = longData;
2015
+ break;
2016
+ }
2017
+ case FLOAT: {
2018
+ float[] floatData = new float[rowCount];
2019
+ for (int row = 0; row < rowCount; row++) {
2020
+ String s = getString(row, column);
2021
+ floatData[row] = (s == null) ? missingFloat : PApplet.parseFloat(s, missingFloat);
2022
+ }
2023
+ columns[column] = floatData;
2024
+ break;
2025
+ }
2026
+ case DOUBLE: {
2027
+ double[] doubleData = new double[rowCount];
2028
+ for (int row = 0; row < rowCount; row++) {
2029
+ String s = getString(row, column);
2030
+ try {
2031
+ doubleData[row] = (s == null) ? missingDouble : Double.parseDouble(s);
2032
+ } catch (NumberFormatException nfe) {
2033
+ doubleData[row] = missingDouble;
2034
+ }
2035
+ }
2036
+ columns[column] = doubleData;
2037
+ break;
2038
+ }
2039
+ case STRING: {
2040
+ if (columnTypes[column] != STRING) {
2041
+ String[] stringData = new String[rowCount];
2042
+ for (int row = 0; row < rowCount; row++) {
2043
+ stringData[row] = getString(row, column);
2044
+ }
2045
+ columns[column] = stringData;
2046
+ }
2047
+ break;
2048
+ }
2049
+ case CATEGORY: {
2050
+ int[] indexData = new int[rowCount];
2051
+ HashMapBlows categories = new HashMapBlows();
2052
+ for (int row = 0; row < rowCount; row++) {
2053
+ String s = getString(row, column);
2054
+ indexData[row] = categories.index(s);
2055
+ }
2056
+ columnCategories[column] = categories;
2057
+ columns[column] = indexData;
2058
+ break;
2059
+ }
2060
+ default: {
2061
+ throw new IllegalArgumentException("That's not a valid column type.");
2062
+ }
2063
+ }
2064
+ // System.out.println("new type is " + newType);
2065
+ columnTypes[column] = newType;
2066
+ }
2067
+
2068
+
2069
+ /**
2070
+ * Set the entire table to a specific data type.
2071
+ */
2072
+ public void setTableType(String type) {
2073
+ for (int col = 0; col < getColumnCount(); col++) {
2074
+ setColumnType(col, type);
2075
+ }
2076
+ }
2077
+
2078
+
2079
+ public void setColumnTypes(int[] types) {
2080
+ ensureColumn(types.length - 1);
2081
+ for (int col = 0; col < types.length; col++) {
2082
+ setColumnType(col, types[col]);
2083
+ }
2084
+ }
2085
+
2086
+
2087
+ /**
2088
+ * Set the titles (and if a second column is present) the data types for
2089
+ * this table based on a file loaded separately. This will look for the
2090
+ * title in column 0, and the type in column 1. Better yet, specify a
2091
+ * column named "title" and another named "type" in the dictionary table
2092
+ * to future-proof the code.
2093
+ * @param dictionary
2094
+ */
2095
+ public void setColumnTypes(final Table dictionary) {
2096
+ ensureColumn(dictionary.getRowCount() - 1);
2097
+ int titleCol = 0;
2098
+ int typeCol = 1;
2099
+ if (dictionary.hasColumnTitles()) {
2100
+ titleCol = dictionary.getColumnIndex("title", true);
2101
+ typeCol = dictionary.getColumnIndex("type", true);
2102
+ }
2103
+ setColumnTitles(dictionary.getStringColumn(titleCol));
2104
+ final String[] typeNames = dictionary.getStringColumn(typeCol);
2105
+
2106
+ if (dictionary.getColumnCount() > 1) {
2107
+ if (getRowCount() > 1000) {
2108
+ int proc = Runtime.getRuntime().availableProcessors();
2109
+ ExecutorService pool = Executors.newFixedThreadPool(proc/2);
2110
+ for (int i = 0; i < dictionary.getRowCount(); i++) {
2111
+ final int col = i;
2112
+ pool.execute(new Runnable() {
2113
+ public void run() {
2114
+ setColumnType(col, typeNames[col]);
2115
+ }
2116
+ });
2117
+ }
2118
+ pool.shutdown();
2119
+ while (!pool.isTerminated()) {
2120
+ Thread.yield();
2121
+ }
2122
+
2123
+ } else {
2124
+ for (int col = 0; col < dictionary.getRowCount(); col++) {
2125
+ // setColumnType(i, dictionary.getString(i, typeCol));
2126
+ setColumnType(col, typeNames[col]);
2127
+ }
2128
+ }
2129
+ }
2130
+ }
2131
+
2132
+
2133
+ public int getColumnType(String columnName) {
2134
+ return getColumnType(getColumnIndex(columnName));
2135
+ }
2136
+
2137
+
2138
+ /** Returns one of Table.STRING, Table.INT, etc... */
2139
+ public int getColumnType(int column) {
2140
+ return columnTypes[column];
2141
+ }
2142
+
2143
+
2144
+ public int[] getColumnTypes() {
2145
+ return columnTypes;
2146
+ }
2147
+
2148
+
2149
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2150
+
2151
+
2152
+ /**
2153
+ * Remove the first row from the data set, and use it as the column titles.
2154
+ * Use loadTable("table.csv", "header") instead.
2155
+ */
2156
+ @Deprecated
2157
+ public String[] removeTitleRow() {
2158
+ String[] titles = getStringRow(0);
2159
+ removeRow(0);
2160
+ setColumnTitles(titles);
2161
+ return titles;
2162
+ }
2163
+
2164
+
2165
+ public void setColumnTitles(String[] titles) {
2166
+ if (titles != null) {
2167
+ ensureColumn(titles.length - 1);
2168
+ }
2169
+ columnTitles = titles;
2170
+ columnIndices = null; // remove the cache
2171
+ }
2172
+
2173
+
2174
+ public void setColumnTitle(int column, String title) {
2175
+ ensureColumn(column);
2176
+ if (columnTitles == null) {
2177
+ columnTitles = new String[getColumnCount()];
2178
+ }
2179
+ columnTitles[column] = title;
2180
+ columnIndices = null; // reset these fellas
2181
+ }
2182
+
2183
+
2184
+ public boolean hasColumnTitles() {
2185
+ return columnTitles != null;
2186
+ }
2187
+
2188
+
2189
+ public String[] getColumnTitles() {
2190
+ return columnTitles;
2191
+ }
2192
+
2193
+
2194
+ public String getColumnTitle(int col) {
2195
+ return (columnTitles == null) ? null : columnTitles[col];
2196
+ }
2197
+
2198
+
2199
+ public int getColumnIndex(String columnName) {
2200
+ return getColumnIndex(columnName, true);
2201
+ }
2202
+
2203
+
2204
+ /**
2205
+ * Get the index of a column.
2206
+ * @param name Name of the column.
2207
+ * @param report Whether to throw an exception if the column wasn't found.
2208
+ * @return index of the found column, or -1 if not found.
2209
+ */
2210
+ protected int getColumnIndex(String name, boolean report) {
2211
+ if (columnTitles == null) {
2212
+ if (report) {
2213
+ throw new IllegalArgumentException("This table has no header, so no column titles are set.");
2214
+ }
2215
+ return -1;
2216
+ }
2217
+ // only create this on first get(). subsequent calls to set the title will
2218
+ // also update this array, but only if it exists.
2219
+ if (columnIndices == null) {
2220
+ columnIndices = new HashMap<>();
2221
+ for (int col = 0; col < columns.length; col++) {
2222
+ columnIndices.put(columnTitles[col], col);
2223
+ }
2224
+ }
2225
+ Integer index = columnIndices.get(name);
2226
+ if (index == null) {
2227
+ if (report) {
2228
+ // Throws an exception here because the name is known and therefore most useful.
2229
+ // (Rather than waiting for it to fail inside, say, getInt())
2230
+ throw new IllegalArgumentException("This table has no column named '" + name + "'");
2231
+ }
2232
+ return -1;
2233
+ }
2234
+ return index.intValue();
2235
+ }
2236
+
2237
+
2238
+ /**
2239
+ * Same as getColumnIndex(), but creates the column if it doesn't exist.
2240
+ * Named this way to not conflict with checkColumn(), an internal function
2241
+ * used to ensure that a columns exists, and also to denote that it returns
2242
+ * an int for the column index.
2243
+ * @param title column title
2244
+ * @return index of a new or previously existing column
2245
+ */
2246
+ public int checkColumnIndex(String title) {
2247
+ int index = getColumnIndex(title, false);
2248
+ if (index != -1) {
2249
+ return index;
2250
+ }
2251
+ addColumn(title);
2252
+ return getColumnCount() - 1;
2253
+ }
2254
+
2255
+
2256
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2257
+
2258
+ /**
2259
+ * @webref table:method
2260
+ * @brief Gets the number of rows in a table
2261
+ * @see Table#getColumnCount()
2262
+ */
2263
+ public int getRowCount() {
2264
+ return rowCount;
2265
+ }
2266
+
2267
+
2268
+ public int lastRowIndex() {
2269
+ return getRowCount() - 1;
2270
+ }
2271
+
2272
+
2273
+ /**
2274
+ * @webref table:method
2275
+ * @brief Removes all rows from a table
2276
+ * @see Table#addRow()
2277
+ * @see Table#removeRow(int)
2278
+ */
2279
+ public void clearRows() {
2280
+ setRowCount(0);
2281
+ }
2282
+
2283
+
2284
+ public void setRowCount(int newCount) {
2285
+ if (newCount != rowCount) {
2286
+ if (newCount > 1000000) {
2287
+ System.out.print("Note: setting maximum row count to " + PApplet.nfc(newCount));
2288
+ }
2289
+ long t = System.currentTimeMillis();
2290
+ for (int col = 0; col < columns.length; col++) {
2291
+ switch (columnTypes[col]) {
2292
+ case INT: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
2293
+ case LONG: columns[col] = PApplet.expand((long[]) columns[col], newCount); break;
2294
+ case FLOAT: columns[col] = PApplet.expand((float[]) columns[col], newCount); break;
2295
+ case DOUBLE: columns[col] = PApplet.expand((double[]) columns[col], newCount); break;
2296
+ case STRING: columns[col] = PApplet.expand((String[]) columns[col], newCount); break;
2297
+ case CATEGORY: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
2298
+ }
2299
+ if (newCount > 1000000) {
2300
+ try {
2301
+ Thread.sleep(10); // gc time!
2302
+ } catch (InterruptedException e) {
2303
+ e.printStackTrace();
2304
+ }
2305
+ }
2306
+ }
2307
+ if (newCount > 1000000) {
2308
+ int ms = (int) (System.currentTimeMillis() - t);
2309
+ System.out.println(" (resize took " + PApplet.nfc(ms) + " ms)");
2310
+ }
2311
+ }
2312
+ rowCount = newCount;
2313
+ }
2314
+
2315
+
2316
+ /**
2317
+ * @webref table:method
2318
+ * @brief Adds a row to a table
2319
+ * @see Table#removeRow(int)
2320
+ * @see Table#clearRows()
2321
+ */
2322
+ public TableRow addRow() {
2323
+ //if (rowIncrement == 0) {
2324
+ setRowCount(rowCount + 1);
2325
+ return new RowPointer(this, rowCount - 1);
2326
+ }
2327
+
2328
+
2329
+ /**
2330
+ * @param source a reference to the original row to be duplicated
2331
+ */
2332
+ public TableRow addRow(TableRow source) {
2333
+ return setRow(rowCount, source);
2334
+ }
2335
+
2336
+
2337
+ public TableRow setRow(int row, TableRow source) {
2338
+ // Make sure there are enough columns to add this data
2339
+ ensureBounds(row, source.getColumnCount() - 1);
2340
+
2341
+ for (int col = 0; col < Math.min(source.getColumnCount(), columns.length); col++) {
2342
+ switch (columnTypes[col]) {
2343
+ case INT:
2344
+ setInt(row, col, source.getInt(col));
2345
+ break;
2346
+ case LONG:
2347
+ setLong(row, col, source.getLong(col));
2348
+ break;
2349
+ case FLOAT:
2350
+ setFloat(row, col, source.getFloat(col));
2351
+ break;
2352
+ case DOUBLE:
2353
+ setDouble(row, col, source.getDouble(col));
2354
+ break;
2355
+ case STRING:
2356
+ setString(row, col, source.getString(col));
2357
+ break;
2358
+ case CATEGORY:
2359
+ int index = source.getInt(col);
2360
+ setInt(row, col, index);
2361
+ if (!columnCategories[col].hasCategory(index)) {
2362
+ columnCategories[col].setCategory(index, source.getString(col));
2363
+ }
2364
+ break;
2365
+
2366
+ default:
2367
+ throw new RuntimeException("no types");
2368
+ }
2369
+ }
2370
+ return new RowPointer(this, row);
2371
+ }
2372
+
2373
+
2374
+ /**
2375
+ * @nowebref
2376
+ */
2377
+ public TableRow addRow(Object[] columnData) {
2378
+ setRow(getRowCount(), columnData);
2379
+ return new RowPointer(this, rowCount - 1);
2380
+ }
2381
+
2382
+
2383
+ public void addRows(Table source) {
2384
+ int index = getRowCount();
2385
+ setRowCount(index + source.getRowCount());
2386
+ for (TableRow row : source.rows()) {
2387
+ setRow(index++, row);
2388
+ }
2389
+ }
2390
+
2391
+
2392
+ public void insertRow(int insert, Object[] columnData) {
2393
+ for (int col = 0; col < columns.length; col++) {
2394
+ switch (columnTypes[col]) {
2395
+ case CATEGORY:
2396
+ case INT: {
2397
+ int[] intTemp = new int[rowCount+1];
2398
+ System.arraycopy(columns[col], 0, intTemp, 0, insert);
2399
+ System.arraycopy(columns[col], insert, intTemp, insert+1, rowCount - insert);
2400
+ columns[col] = intTemp;
2401
+ break;
2402
+ }
2403
+ case LONG: {
2404
+ long[] longTemp = new long[rowCount+1];
2405
+ System.arraycopy(columns[col], 0, longTemp, 0, insert);
2406
+ System.arraycopy(columns[col], insert, longTemp, insert+1, rowCount - insert);
2407
+ columns[col] = longTemp;
2408
+ break;
2409
+ }
2410
+ case FLOAT: {
2411
+ float[] floatTemp = new float[rowCount+1];
2412
+ System.arraycopy(columns[col], 0, floatTemp, 0, insert);
2413
+ System.arraycopy(columns[col], insert, floatTemp, insert+1, rowCount - insert);
2414
+ columns[col] = floatTemp;
2415
+ break;
2416
+ }
2417
+ case DOUBLE: {
2418
+ double[] doubleTemp = new double[rowCount+1];
2419
+ System.arraycopy(columns[col], 0, doubleTemp, 0, insert);
2420
+ System.arraycopy(columns[col], insert, doubleTemp, insert+1, rowCount - insert);
2421
+ columns[col] = doubleTemp;
2422
+ break;
2423
+ }
2424
+ case STRING: {
2425
+ String[] stringTemp = new String[rowCount+1];
2426
+ System.arraycopy(columns[col], 0, stringTemp, 0, insert);
2427
+ System.arraycopy(columns[col], insert, stringTemp, insert+1, rowCount - insert);
2428
+ columns[col] = stringTemp;
2429
+ break;
2430
+ }
2431
+ }
2432
+ }
2433
+ // Need to increment before setRow(), because it calls ensureBounds()
2434
+ // https://github.com/processing/processing/issues/5406
2435
+ ++rowCount;
2436
+ setRow(insert, columnData);
2437
+ }
2438
+
2439
+
2440
+ /**
2441
+ * @webref table:method
2442
+ * @brief Removes a row from a table
2443
+ * @param row ID number of the row to remove
2444
+ * @see Table#addRow()
2445
+ * @see Table#clearRows()
2446
+ */
2447
+ public void removeRow(int row) {
2448
+ for (int col = 0; col < columns.length; col++) {
2449
+ switch (columnTypes[col]) {
2450
+ case CATEGORY:
2451
+ case INT: {
2452
+ int[] intTemp = new int[rowCount-1];
2453
+ // int[] intData = (int[]) columns[col];
2454
+ // System.arraycopy(intData, 0, intTemp, 0, dead);
2455
+ // System.arraycopy(intData, dead+1, intTemp, dead, (rowCount - dead) + 1);
2456
+ System.arraycopy(columns[col], 0, intTemp, 0, row);
2457
+ System.arraycopy(columns[col], row+1, intTemp, row, (rowCount - row) - 1);
2458
+ columns[col] = intTemp;
2459
+ break;
2460
+ }
2461
+ case LONG: {
2462
+ long[] longTemp = new long[rowCount-1];
2463
+ // long[] longData = (long[]) columns[col];
2464
+ // System.arraycopy(longData, 0, longTemp, 0, dead);
2465
+ // System.arraycopy(longData, dead+1, longTemp, dead, (rowCount - dead) + 1);
2466
+ System.arraycopy(columns[col], 0, longTemp, 0, row);
2467
+ System.arraycopy(columns[col], row+1, longTemp, row, (rowCount - row) - 1);
2468
+ columns[col] = longTemp;
2469
+ break;
2470
+ }
2471
+ case FLOAT: {
2472
+ float[] floatTemp = new float[rowCount-1];
2473
+ // float[] floatData = (float[]) columns[col];
2474
+ // System.arraycopy(floatData, 0, floatTemp, 0, dead);
2475
+ // System.arraycopy(floatData, dead+1, floatTemp, dead, (rowCount - dead) + 1);
2476
+ System.arraycopy(columns[col], 0, floatTemp, 0, row);
2477
+ System.arraycopy(columns[col], row+1, floatTemp, row, (rowCount - row) - 1);
2478
+ columns[col] = floatTemp;
2479
+ break;
2480
+ }
2481
+ case DOUBLE: {
2482
+ double[] doubleTemp = new double[rowCount-1];
2483
+ // double[] doubleData = (double[]) columns[col];
2484
+ // System.arraycopy(doubleData, 0, doubleTemp, 0, dead);
2485
+ // System.arraycopy(doubleData, dead+1, doubleTemp, dead, (rowCount - dead) + 1);
2486
+ System.arraycopy(columns[col], 0, doubleTemp, 0, row);
2487
+ System.arraycopy(columns[col], row+1, doubleTemp, row, (rowCount - row) - 1);
2488
+ columns[col] = doubleTemp;
2489
+ break;
2490
+ }
2491
+ case STRING: {
2492
+ String[] stringTemp = new String[rowCount-1];
2493
+ System.arraycopy(columns[col], 0, stringTemp, 0, row);
2494
+ System.arraycopy(columns[col], row+1, stringTemp, row, (rowCount - row) - 1);
2495
+ columns[col] = stringTemp;
2496
+ }
2497
+ }
2498
+ }
2499
+ rowCount--;
2500
+ }
2501
+
2502
+
2503
+ /*
2504
+ public void setRow(int row, String[] pieces) {
2505
+ checkSize(row, pieces.length - 1);
2506
+ // pieces.length may be less than columns.length, so loop over pieces
2507
+ for (int col = 0; col < pieces.length; col++) {
2508
+ setRowCol(row, col, pieces[col]);
2509
+ }
2510
+ }
2511
+
2512
+
2513
+ protected void setRowCol(int row, int col, String piece) {
2514
+ switch (columnTypes[col]) {
2515
+ case STRING:
2516
+ String[] stringData = (String[]) columns[col];
2517
+ stringData[row] = piece;
2518
+ break;
2519
+ case INT:
2520
+ int[] intData = (int[]) columns[col];
2521
+ intData[row] = PApplet.parseInt(piece, missingInt);
2522
+ break;
2523
+ case LONG:
2524
+ long[] longData = (long[]) columns[col];
2525
+ try {
2526
+ longData[row] = Long.parseLong(piece);
2527
+ } catch (NumberFormatException nfe) {
2528
+ longData[row] = missingLong;
2529
+ }
2530
+ break;
2531
+ case FLOAT:
2532
+ float[] floatData = (float[]) columns[col];
2533
+ floatData[row] = PApplet.parseFloat(piece, missingFloat);
2534
+ break;
2535
+ case DOUBLE:
2536
+ double[] doubleData = (double[]) columns[col];
2537
+ try {
2538
+ doubleData[row] = Double.parseDouble(piece);
2539
+ } catch (NumberFormatException nfe) {
2540
+ doubleData[row] = missingDouble;
2541
+ }
2542
+ break;
2543
+ case CATEGORY:
2544
+ int[] indexData = (int[]) columns[col];
2545
+ indexData[row] = columnCategories[col].index(piece);
2546
+ break;
2547
+ default:
2548
+ throw new IllegalArgumentException("That's not a valid column type.");
2549
+ }
2550
+ }
2551
+ */
2552
+
2553
+
2554
+ public void setRow(int row, Object[] pieces) {
2555
+ ensureBounds(row, pieces.length - 1);
2556
+ // pieces.length may be less than columns.length, so loop over pieces
2557
+ for (int col = 0; col < pieces.length; col++) {
2558
+ setRowCol(row, col, pieces[col]);
2559
+ }
2560
+ }
2561
+
2562
+
2563
+ protected void setRowCol(int row, int col, Object piece) {
2564
+ switch (columnTypes[col]) {
2565
+ case STRING:
2566
+ String[] stringData = (String[]) columns[col];
2567
+ if (piece == null) {
2568
+ stringData[row] = null;
2569
+ // } else if (piece instanceof String) {
2570
+ // stringData[row] = (String) piece;
2571
+ } else {
2572
+ // Calls toString() on the object, which is 'return this' for String
2573
+ stringData[row] = String.valueOf(piece);
2574
+ }
2575
+ break;
2576
+ case INT:
2577
+ int[] intData = (int[]) columns[col];
2578
+ //intData[row] = PApplet.parseInt(piece, missingInt);
2579
+ if (piece == null) {
2580
+ intData[row] = missingInt;
2581
+ } else if (piece instanceof Integer) {
2582
+ intData[row] = (Integer) piece;
2583
+ } else {
2584
+ intData[row] = PApplet.parseInt(String.valueOf(piece), missingInt);
2585
+ }
2586
+ break;
2587
+ case LONG:
2588
+ long[] longData = (long[]) columns[col];
2589
+ if (piece == null) {
2590
+ longData[row] = missingLong;
2591
+ } else if (piece instanceof Long) {
2592
+ longData[row] = (Long) piece;
2593
+ } else {
2594
+ try {
2595
+ longData[row] = Long.parseLong(String.valueOf(piece));
2596
+ } catch (NumberFormatException nfe) {
2597
+ longData[row] = missingLong;
2598
+ }
2599
+ }
2600
+ break;
2601
+ case FLOAT:
2602
+ float[] floatData = (float[]) columns[col];
2603
+ if (piece == null) {
2604
+ floatData[row] = missingFloat;
2605
+ } else if (piece instanceof Float) {
2606
+ floatData[row] = (Float) piece;
2607
+ } else {
2608
+ floatData[row] = PApplet.parseFloat(String.valueOf(piece), missingFloat);
2609
+ }
2610
+ break;
2611
+ case DOUBLE:
2612
+ double[] doubleData = (double[]) columns[col];
2613
+ if (piece == null) {
2614
+ doubleData[row] = missingDouble;
2615
+ } else if (piece instanceof Double) {
2616
+ doubleData[row] = (Double) piece;
2617
+ } else {
2618
+ try {
2619
+ doubleData[row] = Double.parseDouble(String.valueOf(piece));
2620
+ } catch (NumberFormatException nfe) {
2621
+ doubleData[row] = missingDouble;
2622
+ }
2623
+ }
2624
+ break;
2625
+ case CATEGORY:
2626
+ int[] indexData = (int[]) columns[col];
2627
+ if (piece == null) {
2628
+ indexData[row] = missingCategory;
2629
+ } else {
2630
+ String peace = String.valueOf(piece);
2631
+ if (peace.equals(missingString)) { // missingString might be null
2632
+ indexData[row] = missingCategory;
2633
+ } else {
2634
+ indexData[row] = columnCategories[col].index(peace);
2635
+ }
2636
+ }
2637
+ break;
2638
+ default:
2639
+ throw new IllegalArgumentException("That's not a valid column type.");
2640
+ }
2641
+ }
2642
+
2643
+
2644
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2645
+
2646
+ /**
2647
+ * @webref table:method
2648
+ * @brief Gets a row from a table
2649
+ * @param row ID number of the row to get
2650
+ * @see Table#rows()
2651
+ * @see Table#findRow(String, int)
2652
+ * @see Table#findRows(String, int)
2653
+ * @see Table#matchRow(String, int)
2654
+ * @see Table#matchRows(String, int)
2655
+ */
2656
+ public TableRow getRow(int row) {
2657
+ return new RowPointer(this, row);
2658
+ }
2659
+
2660
+
2661
+ /**
2662
+ * Note that this one iterator instance is shared by any calls to iterate
2663
+ * the rows of this table. This is very efficient, but not thread-safe.
2664
+ * If you want to iterate in a multi-threaded manner, don't use the iterator.
2665
+ *
2666
+ * @webref table:method
2667
+ * @brief Gets multiple rows from a table
2668
+ * @see Table#getRow(int)
2669
+ * @see Table#findRow(String, int)
2670
+ * @see Table#findRows(String, int)
2671
+ * @see Table#matchRow(String, int)
2672
+ * @see Table#matchRows(String, int)
2673
+ */
2674
+ public Iterable<TableRow> rows() {
2675
+ return new Iterable<TableRow>() {
2676
+ public Iterator<TableRow> iterator() {
2677
+ if (rowIterator == null) {
2678
+ rowIterator = new RowIterator(Table.this);
2679
+ } else {
2680
+ rowIterator.reset();
2681
+ }
2682
+ return rowIterator;
2683
+ }
2684
+ };
2685
+ }
2686
+
2687
+ /**
2688
+ * @nowebref
2689
+ */
2690
+ public Iterable<TableRow> rows(final int[] indices) {
2691
+ return new Iterable<TableRow>() {
2692
+ public Iterator<TableRow> iterator() {
2693
+ return new RowIndexIterator(Table.this, indices);
2694
+ }
2695
+ };
2696
+ }
2697
+
2698
+
2699
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2700
+
2701
+
2702
+ static class RowPointer implements TableRow {
2703
+ Table table;
2704
+ int row;
2705
+
2706
+ public RowPointer(Table table, int row) {
2707
+ this.table = table;
2708
+ this.row = row;
2709
+ }
2710
+
2711
+ public void setRow(int row) {
2712
+ this.row = row;
2713
+ }
2714
+
2715
+ public String getString(int column) {
2716
+ return table.getString(row, column);
2717
+ }
2718
+
2719
+ public String getString(String columnName) {
2720
+ return table.getString(row, columnName);
2721
+ }
2722
+
2723
+ public int getInt(int column) {
2724
+ return table.getInt(row, column);
2725
+ }
2726
+
2727
+ public int getInt(String columnName) {
2728
+ return table.getInt(row, columnName);
2729
+ }
2730
+
2731
+ public long getLong(int column) {
2732
+ return table.getLong(row, column);
2733
+ }
2734
+
2735
+ public long getLong(String columnName) {
2736
+ return table.getLong(row, columnName);
2737
+ }
2738
+
2739
+ public float getFloat(int column) {
2740
+ return table.getFloat(row, column);
2741
+ }
2742
+
2743
+ public float getFloat(String columnName) {
2744
+ return table.getFloat(row, columnName);
2745
+ }
2746
+
2747
+ public double getDouble(int column) {
2748
+ return table.getDouble(row, column);
2749
+ }
2750
+
2751
+ public double getDouble(String columnName) {
2752
+ return table.getDouble(row, columnName);
2753
+ }
2754
+
2755
+ public void setString(int column, String value) {
2756
+ table.setString(row, column, value);
2757
+ }
2758
+
2759
+ public void setString(String columnName, String value) {
2760
+ table.setString(row, columnName, value);
2761
+ }
2762
+
2763
+ public void setInt(int column, int value) {
2764
+ table.setInt(row, column, value);
2765
+ }
2766
+
2767
+ public void setInt(String columnName, int value) {
2768
+ table.setInt(row, columnName, value);
2769
+ }
2770
+
2771
+ public void setLong(int column, long value) {
2772
+ table.setLong(row, column, value);
2773
+ }
2774
+
2775
+ public void setLong(String columnName, long value) {
2776
+ table.setLong(row, columnName, value);
2777
+ }
2778
+
2779
+ public void setFloat(int column, float value) {
2780
+ table.setFloat(row, column, value);
2781
+ }
2782
+
2783
+ public void setFloat(String columnName, float value) {
2784
+ table.setFloat(row, columnName, value);
2785
+ }
2786
+
2787
+ public void setDouble(int column, double value) {
2788
+ table.setDouble(row, column, value);
2789
+ }
2790
+
2791
+ public void setDouble(String columnName, double value) {
2792
+ table.setDouble(row, columnName, value);
2793
+ }
2794
+
2795
+ public int getColumnCount() {
2796
+ return table.getColumnCount();
2797
+ }
2798
+
2799
+ public int getColumnType(String columnName) {
2800
+ return table.getColumnType(columnName);
2801
+ }
2802
+
2803
+ public int getColumnType(int column) {
2804
+ return table.getColumnType(column);
2805
+ }
2806
+
2807
+ public int[] getColumnTypes() {
2808
+ return table.getColumnTypes();
2809
+ }
2810
+
2811
+ public String getColumnTitle(int column) {
2812
+ return table.getColumnTitle(column);
2813
+ }
2814
+
2815
+ public String[] getColumnTitles() {
2816
+ return table.getColumnTitles();
2817
+ }
2818
+
2819
+ public void print() {
2820
+ write(new PrintWriter(System.out));
2821
+ }
2822
+
2823
+ public void write(PrintWriter writer) {
2824
+ for (int i = 0 ; i < getColumnCount(); i++) {
2825
+ if (i != 0) {
2826
+ writer.print('\t');
2827
+ }
2828
+ writer.print(getString(i));
2829
+ }
2830
+ }
2831
+ }
2832
+
2833
+
2834
+ static class RowIterator implements Iterator<TableRow> {
2835
+ Table table;
2836
+ RowPointer rp;
2837
+ int row;
2838
+
2839
+ public RowIterator(Table table) {
2840
+ this.table = table;
2841
+ row = -1;
2842
+ rp = new RowPointer(table, row);
2843
+ }
2844
+
2845
+ public void remove() {
2846
+ table.removeRow(row);
2847
+ }
2848
+
2849
+ public TableRow next() {
2850
+ rp.setRow(++row);
2851
+ return rp;
2852
+ }
2853
+
2854
+ public boolean hasNext() {
2855
+ return row+1 < table.getRowCount();
2856
+ }
2857
+
2858
+ public void reset() {
2859
+ row = -1;
2860
+ }
2861
+ }
2862
+
2863
+
2864
+ static class RowIndexIterator implements Iterator<TableRow> {
2865
+ Table table;
2866
+ RowPointer rp;
2867
+ int[] indices;
2868
+ int index;
2869
+
2870
+ public RowIndexIterator(Table table, int[] indices) {
2871
+ this.table = table;
2872
+ this.indices = indices;
2873
+ index = -1;
2874
+ // just set to something arbitrary
2875
+ rp = new RowPointer(table, -1);
2876
+ }
2877
+
2878
+ public void remove() {
2879
+ table.removeRow(indices[index]);
2880
+ }
2881
+
2882
+ public TableRow next() {
2883
+ rp.setRow(indices[++index]);
2884
+ return rp;
2885
+ }
2886
+
2887
+ public boolean hasNext() {
2888
+ //return row+1 < table.getRowCount();
2889
+ return index + 1 < indices.length;
2890
+ }
2891
+
2892
+ public void reset() {
2893
+ index = -1;
2894
+ }
2895
+ }
2896
+
2897
+
2898
+ /*
2899
+ static public Iterator<TableRow> createIterator(final ResultSet rs) {
2900
+ return new Iterator<TableRow>() {
2901
+ boolean already;
2902
+
2903
+ public boolean hasNext() {
2904
+ already = true;
2905
+ try {
2906
+ return rs.next();
2907
+ } catch (SQLException e) {
2908
+ throw new RuntimeException(e);
2909
+ }
2910
+ }
2911
+
2912
+
2913
+ public TableRow next() {
2914
+ if (!already) {
2915
+ try {
2916
+ rs.next();
2917
+ } catch (SQLException e) {
2918
+ throw new RuntimeException(e);
2919
+ }
2920
+ } else {
2921
+ already = false;
2922
+ }
2923
+
2924
+ return new TableRow() {
2925
+ public double getDouble(int column) {
2926
+ try {
2927
+ return rs.getDouble(column);
2928
+ } catch (SQLException e) {
2929
+ throw new RuntimeException(e);
2930
+ }
2931
+ }
2932
+
2933
+ public double getDouble(String columnName) {
2934
+ try {
2935
+ return rs.getDouble(columnName);
2936
+ } catch (SQLException e) {
2937
+ throw new RuntimeException(e);
2938
+ }
2939
+ }
2940
+
2941
+ public float getFloat(int column) {
2942
+ try {
2943
+ return rs.getFloat(column);
2944
+ } catch (SQLException e) {
2945
+ throw new RuntimeException(e);
2946
+ }
2947
+ }
2948
+
2949
+ public float getFloat(String columnName) {
2950
+ try {
2951
+ return rs.getFloat(columnName);
2952
+ } catch (SQLException e) {
2953
+ throw new RuntimeException(e);
2954
+ }
2955
+ }
2956
+
2957
+ public int getInt(int column) {
2958
+ try {
2959
+ return rs.getInt(column);
2960
+ } catch (SQLException e) {
2961
+ throw new RuntimeException(e);
2962
+ }
2963
+ }
2964
+
2965
+ public int getInt(String columnName) {
2966
+ try {
2967
+ return rs.getInt(columnName);
2968
+ } catch (SQLException e) {
2969
+ throw new RuntimeException(e);
2970
+ }
2971
+ }
2972
+
2973
+ public long getLong(int column) {
2974
+ try {
2975
+ return rs.getLong(column);
2976
+ } catch (SQLException e) {
2977
+ throw new RuntimeException(e);
2978
+ }
2979
+ }
2980
+
2981
+ public long getLong(String columnName) {
2982
+ try {
2983
+ return rs.getLong(columnName);
2984
+ } catch (SQLException e) {
2985
+ throw new RuntimeException(e);
2986
+ }
2987
+ }
2988
+
2989
+ public String getString(int column) {
2990
+ try {
2991
+ return rs.getString(column);
2992
+ } catch (SQLException e) {
2993
+ throw new RuntimeException(e);
2994
+ }
2995
+ }
2996
+
2997
+ public String getString(String columnName) {
2998
+ try {
2999
+ return rs.getString(columnName);
3000
+ } catch (SQLException e) {
3001
+ throw new RuntimeException(e);
3002
+ }
3003
+ }
3004
+
3005
+ public void setString(int column, String value) { immutable(); }
3006
+ public void setString(String columnName, String value) { immutable(); }
3007
+ public void setInt(int column, int value) { immutable(); }
3008
+ public void setInt(String columnName, int value) { immutable(); }
3009
+ public void setLong(int column, long value) { immutable(); }
3010
+ public void setLong(String columnName, long value) { immutable(); }
3011
+ public void setFloat(int column, float value) { immutable(); }
3012
+ public void setFloat(String columnName, float value) { immutable(); }
3013
+ public void setDouble(int column, double value) { immutable(); }
3014
+ public void setDouble(String columnName, double value) { immutable(); }
3015
+
3016
+ private void immutable() {
3017
+ throw new IllegalArgumentException("This TableRow cannot be modified.");
3018
+ }
3019
+
3020
+ public int getColumnCount() {
3021
+ try {
3022
+ return rs.getMetaData().getColumnCount();
3023
+ } catch (SQLException e) {
3024
+ e.printStackTrace();
3025
+ return -1;
3026
+ }
3027
+ }
3028
+
3029
+
3030
+ public int getColumnType(String columnName) {
3031
+ // unimplemented
3032
+ }
3033
+
3034
+
3035
+ public int getColumnType(int column) {
3036
+ // unimplemented
3037
+ }
3038
+
3039
+ };
3040
+ }
3041
+
3042
+ public void remove() {
3043
+ throw new IllegalArgumentException("remove() not supported");
3044
+ }
3045
+ };
3046
+ }
3047
+ */
3048
+
3049
+
3050
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3051
+
3052
+
3053
+ /**
3054
+ * @webref table:method
3055
+ * @brief Get an integer value from the specified row and column
3056
+ * @param row ID number of the row to reference
3057
+ * @param column ID number of the column to reference
3058
+ * @see Table#getFloat(int, int)
3059
+ * @see Table#getString(int, int)
3060
+ * @see Table#getStringColumn(String)
3061
+ * @see Table#setInt(int, int, int)
3062
+ * @see Table#setFloat(int, int, float)
3063
+ * @see Table#setString(int, int, String)
3064
+ */
3065
+ public int getInt(int row, int column) {
3066
+ checkBounds(row, column);
3067
+ if (columnTypes[column] == INT ||
3068
+ columnTypes[column] == CATEGORY) {
3069
+ int[] intData = (int[]) columns[column];
3070
+ return intData[row];
3071
+ }
3072
+ String str = getString(row, column);
3073
+ return (str == null || str.equals(missingString)) ?
3074
+ missingInt : PApplet.parseInt(str, missingInt);
3075
+ }
3076
+
3077
+ /**
3078
+ * @param columnName title of the column to reference
3079
+ */
3080
+ public int getInt(int row, String columnName) {
3081
+ return getInt(row, getColumnIndex(columnName));
3082
+ }
3083
+
3084
+
3085
+ public void setMissingInt(int value) {
3086
+ missingInt = value;
3087
+ }
3088
+
3089
+
3090
+ /**
3091
+ * @webref table:method
3092
+ * @brief Store an integer value in the specified row and column
3093
+ * @param row ID number of the target row
3094
+ * @param column ID number of the target column
3095
+ * @param value value to assign
3096
+ * @see Table#setFloat(int, int, float)
3097
+ * @see Table#setString(int, int, String)
3098
+ * @see Table#getInt(int, int)
3099
+ * @see Table#getFloat(int, int)
3100
+ * @see Table#getString(int, int)
3101
+ * @see Table#getStringColumn(String)
3102
+ */
3103
+ public void setInt(int row, int column, int value) {
3104
+ if (columnTypes[column] == STRING) {
3105
+ setString(row, column, String.valueOf(value));
3106
+
3107
+ } else {
3108
+ ensureBounds(row, column);
3109
+ if (columnTypes[column] != INT &&
3110
+ columnTypes[column] != CATEGORY) {
3111
+ throw new IllegalArgumentException("Column " + column + " is not an int column.");
3112
+ }
3113
+ int[] intData = (int[]) columns[column];
3114
+ intData[row] = value;
3115
+ }
3116
+ }
3117
+
3118
+ /**
3119
+ * @param columnName title of the target column
3120
+ */
3121
+ public void setInt(int row, String columnName, int value) {
3122
+ setInt(row, getColumnIndex(columnName), value);
3123
+ }
3124
+
3125
+
3126
+
3127
+ public int[] getIntColumn(String name) {
3128
+ int col = getColumnIndex(name);
3129
+ return (col == -1) ? null : getIntColumn(col);
3130
+ }
3131
+
3132
+
3133
+ public int[] getIntColumn(int col) {
3134
+ int[] outgoing = new int[rowCount];
3135
+ for (int row = 0; row < rowCount; row++) {
3136
+ outgoing[row] = getInt(row, col);
3137
+ }
3138
+ return outgoing;
3139
+ }
3140
+
3141
+
3142
+ public int[] getIntRow(int row) {
3143
+ int[] outgoing = new int[columns.length];
3144
+ for (int col = 0; col < columns.length; col++) {
3145
+ outgoing[col] = getInt(row, col);
3146
+ }
3147
+ return outgoing;
3148
+ }
3149
+
3150
+
3151
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3152
+
3153
+
3154
+ public long getLong(int row, int column) {
3155
+ checkBounds(row, column);
3156
+ if (columnTypes[column] == LONG) {
3157
+ long[] longData = (long[]) columns[column];
3158
+ return longData[row];
3159
+ }
3160
+ String str = getString(row, column);
3161
+ if (str == null || str.equals(missingString)) {
3162
+ return missingLong;
3163
+ }
3164
+ try {
3165
+ return Long.parseLong(str);
3166
+ } catch (NumberFormatException nfe) {
3167
+ return missingLong;
3168
+ }
3169
+ }
3170
+
3171
+
3172
+ public long getLong(int row, String columnName) {
3173
+ return getLong(row, getColumnIndex(columnName));
3174
+ }
3175
+
3176
+
3177
+ public void setMissingLong(long value) {
3178
+ missingLong = value;
3179
+ }
3180
+
3181
+
3182
+ public void setLong(int row, int column, long value) {
3183
+ if (columnTypes[column] == STRING) {
3184
+ setString(row, column, String.valueOf(value));
3185
+
3186
+ } else {
3187
+ ensureBounds(row, column);
3188
+ if (columnTypes[column] != LONG) {
3189
+ throw new IllegalArgumentException("Column " + column + " is not a 'long' column.");
3190
+ }
3191
+ long[] longData = (long[]) columns[column];
3192
+ longData[row] = value;
3193
+ }
3194
+ }
3195
+
3196
+
3197
+ public void setLong(int row, String columnName, long value) {
3198
+ setLong(row, getColumnIndex(columnName), value);
3199
+ }
3200
+
3201
+
3202
+ public long[] getLongColumn(String name) {
3203
+ int col = getColumnIndex(name);
3204
+ return (col == -1) ? null : getLongColumn(col);
3205
+ }
3206
+
3207
+
3208
+ public long[] getLongColumn(int col) {
3209
+ long[] outgoing = new long[rowCount];
3210
+ for (int row = 0; row < rowCount; row++) {
3211
+ outgoing[row] = getLong(row, col);
3212
+ }
3213
+ return outgoing;
3214
+ }
3215
+
3216
+
3217
+ public long[] getLongRow(int row) {
3218
+ long[] outgoing = new long[columns.length];
3219
+ for (int col = 0; col < columns.length; col++) {
3220
+ outgoing[col] = getLong(row, col);
3221
+ }
3222
+ return outgoing;
3223
+ }
3224
+
3225
+
3226
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3227
+
3228
+
3229
+ /**
3230
+ * Get a float value from the specified row and column. If the value is null
3231
+ * or not parseable as a float, the "missing" value is returned. By default,
3232
+ * this is Float.NaN, but can be controlled with setMissingFloat().
3233
+ *
3234
+ * @webref table:method
3235
+ * @brief Get a float value from the specified row and column
3236
+ * @param row ID number of the row to reference
3237
+ * @param column ID number of the column to reference
3238
+ * @see Table#getInt(int, int)
3239
+ * @see Table#getString(int, int)
3240
+ * @see Table#getStringColumn(String)
3241
+ * @see Table#setInt(int, int, int)
3242
+ * @see Table#setFloat(int, int, float)
3243
+ * @see Table#setString(int, int, String)
3244
+ */
3245
+ public float getFloat(int row, int column) {
3246
+ checkBounds(row, column);
3247
+ if (columnTypes[column] == FLOAT) {
3248
+ float[] floatData = (float[]) columns[column];
3249
+ return floatData[row];
3250
+ }
3251
+ String str = getString(row, column);
3252
+ if (str == null || str.equals(missingString)) {
3253
+ return missingFloat;
3254
+ }
3255
+ return PApplet.parseFloat(str, missingFloat);
3256
+ }
3257
+
3258
+ /**
3259
+ * @param columnName title of the column to reference
3260
+ */
3261
+ public float getFloat(int row, String columnName) {
3262
+ return getFloat(row, getColumnIndex(columnName));
3263
+ }
3264
+
3265
+
3266
+ public void setMissingFloat(float value) {
3267
+ missingFloat = value;
3268
+ }
3269
+
3270
+
3271
+ /**
3272
+ * @webref table:method
3273
+ * @brief Store a float value in the specified row and column
3274
+ * @param row ID number of the target row
3275
+ * @param column ID number of the target column
3276
+ * @param value value to assign
3277
+ * @see Table#setInt(int, int, int)
3278
+ * @see Table#setString(int, int, String)
3279
+ * @see Table#getInt(int, int)
3280
+ * @see Table#getFloat(int, int)
3281
+ * @see Table#getString(int, int)
3282
+ * @see Table#getStringColumn(String)
3283
+ */
3284
+ public void setFloat(int row, int column, float value) {
3285
+ if (columnTypes[column] == STRING) {
3286
+ setString(row, column, String.valueOf(value));
3287
+
3288
+ } else {
3289
+ ensureBounds(row, column);
3290
+ if (columnTypes[column] != FLOAT) {
3291
+ throw new IllegalArgumentException("Column " + column + " is not a float column.");
3292
+ }
3293
+ float[] longData = (float[]) columns[column];
3294
+ longData[row] = value;
3295
+ }
3296
+ }
3297
+
3298
+ /**
3299
+ * @param columnName title of the target column
3300
+ */
3301
+ public void setFloat(int row, String columnName, float value) {
3302
+ setFloat(row, getColumnIndex(columnName), value);
3303
+ }
3304
+
3305
+
3306
+ public float[] getFloatColumn(String name) {
3307
+ int col = getColumnIndex(name);
3308
+ return (col == -1) ? null : getFloatColumn(col);
3309
+ }
3310
+
3311
+
3312
+ public float[] getFloatColumn(int col) {
3313
+ float[] outgoing = new float[rowCount];
3314
+ for (int row = 0; row < rowCount; row++) {
3315
+ outgoing[row] = getFloat(row, col);
3316
+ }
3317
+ return outgoing;
3318
+ }
3319
+
3320
+
3321
+ public float[] getFloatRow(int row) {
3322
+ float[] outgoing = new float[columns.length];
3323
+ for (int col = 0; col < columns.length; col++) {
3324
+ outgoing[col] = getFloat(row, col);
3325
+ }
3326
+ return outgoing;
3327
+ }
3328
+
3329
+
3330
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3331
+
3332
+
3333
+ public double getDouble(int row, int column) {
3334
+ checkBounds(row, column);
3335
+ if (columnTypes[column] == DOUBLE) {
3336
+ double[] doubleData = (double[]) columns[column];
3337
+ return doubleData[row];
3338
+ }
3339
+ String str = getString(row, column);
3340
+ if (str == null || str.equals(missingString)) {
3341
+ return missingDouble;
3342
+ }
3343
+ try {
3344
+ return Double.parseDouble(str);
3345
+ } catch (NumberFormatException nfe) {
3346
+ return missingDouble;
3347
+ }
3348
+ }
3349
+
3350
+
3351
+ public double getDouble(int row, String columnName) {
3352
+ return getDouble(row, getColumnIndex(columnName));
3353
+ }
3354
+
3355
+
3356
+ public void setMissingDouble(double value) {
3357
+ missingDouble = value;
3358
+ }
3359
+
3360
+
3361
+ public void setDouble(int row, int column, double value) {
3362
+ if (columnTypes[column] == STRING) {
3363
+ setString(row, column, String.valueOf(value));
3364
+
3365
+ } else {
3366
+ ensureBounds(row, column);
3367
+ if (columnTypes[column] != DOUBLE) {
3368
+ throw new IllegalArgumentException("Column " + column + " is not a 'double' column.");
3369
+ }
3370
+ double[] doubleData = (double[]) columns[column];
3371
+ doubleData[row] = value;
3372
+ }
3373
+ }
3374
+
3375
+
3376
+ public void setDouble(int row, String columnName, double value) {
3377
+ setDouble(row, getColumnIndex(columnName), value);
3378
+ }
3379
+
3380
+
3381
+ public double[] getDoubleColumn(String name) {
3382
+ int col = getColumnIndex(name);
3383
+ return (col == -1) ? null : getDoubleColumn(col);
3384
+ }
3385
+
3386
+
3387
+ public double[] getDoubleColumn(int col) {
3388
+ double[] outgoing = new double[rowCount];
3389
+ for (int row = 0; row < rowCount; row++) {
3390
+ outgoing[row] = getDouble(row, col);
3391
+ }
3392
+ return outgoing;
3393
+ }
3394
+
3395
+
3396
+ public double[] getDoubleRow(int row) {
3397
+ double[] outgoing = new double[columns.length];
3398
+ for (int col = 0; col < columns.length; col++) {
3399
+ outgoing[col] = getDouble(row, col);
3400
+ }
3401
+ return outgoing;
3402
+ }
3403
+
3404
+
3405
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3406
+
3407
+
3408
+ //public long getTimestamp(String rowName, int column) {
3409
+ //return getTimestamp(getRowIndex(rowName), column);
3410
+ //}
3411
+
3412
+
3413
+ /**
3414
+ * Returns the time in milliseconds by parsing a SQL Timestamp at this cell.
3415
+ */
3416
+ // public long getTimestamp(int row, int column) {
3417
+ // String str = get(row, column);
3418
+ // java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf(str);
3419
+ // return timestamp.getTime();
3420
+ // }
3421
+
3422
+
3423
+ // public long getExcelTimestamp(int row, int column) {
3424
+ // return parseExcelTimestamp(get(row, column));
3425
+ // }
3426
+
3427
+
3428
+ // static protected DateFormat excelDateFormat;
3429
+
3430
+ // static public long parseExcelTimestamp(String timestamp) {
3431
+ // if (excelDateFormat == null) {
3432
+ // excelDateFormat = new SimpleDateFormat("MM/dd/yy HH:mm");
3433
+ // }
3434
+ // try {
3435
+ // return excelDateFormat.parse(timestamp).getTime();
3436
+ // } catch (ParseException e) {
3437
+ // e.printStackTrace();
3438
+ // return -1;
3439
+ // }
3440
+ // }
3441
+
3442
+
3443
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3444
+
3445
+
3446
+ // public void setObject(int row, int column, Object value) {
3447
+ // if (value == null) {
3448
+ // data[row][column] = null;
3449
+ // } else if (value instanceof String) {
3450
+ // set(row, column, (String) value);
3451
+ // } else if (value instanceof Float) {
3452
+ // setFloat(row, column, ((Float) value).floatValue());
3453
+ // } else if (value instanceof Integer) {
3454
+ // setInt(row, column, ((Integer) value).intValue());
3455
+ // } else {
3456
+ // set(row, column, value.toString());
3457
+ // }
3458
+ // }
3459
+
3460
+
3461
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3462
+
3463
+
3464
+ /**
3465
+ * Get a String value from the table. If the row is longer than the table
3466
+ *
3467
+ * @webref table:method
3468
+ * @brief Get an String value from the specified row and column
3469
+ * @param row ID number of the row to reference
3470
+ * @param column ID number of the column to reference
3471
+ * @see Table#getInt(int, int)
3472
+ * @see Table#getFloat(int, int)
3473
+ * @see Table#getStringColumn(String)
3474
+ * @see Table#setInt(int, int, int)
3475
+ * @see Table#setFloat(int, int, float)
3476
+ * @see Table#setString(int, int, String)
3477
+ */
3478
+ public String getString(int row, int column) {
3479
+ checkBounds(row, column);
3480
+ if (columnTypes[column] == STRING) {
3481
+ String[] stringData = (String[]) columns[column];
3482
+ return stringData[row];
3483
+ } else if (columnTypes[column] == CATEGORY) {
3484
+ int cat = getInt(row, column);
3485
+ if (cat == missingCategory) {
3486
+ return missingString;
3487
+ }
3488
+ return columnCategories[column].key(cat);
3489
+ } else if (columnTypes[column] == FLOAT) {
3490
+ if (Float.isNaN(getFloat(row, column))) {
3491
+ return null;
3492
+ }
3493
+ } else if (columnTypes[column] == DOUBLE) {
3494
+ if (Double.isNaN(getFloat(row, column))) {
3495
+ return null;
3496
+ }
3497
+ }
3498
+ return String.valueOf(Array.get(columns[column], row));
3499
+ }
3500
+
3501
+
3502
+ /**
3503
+ * @param columnName title of the column to reference
3504
+ */
3505
+ public String getString(int row, String columnName) {
3506
+ return getString(row, getColumnIndex(columnName));
3507
+ }
3508
+
3509
+
3510
+ /**
3511
+ * Treat entries with this string as "missing". Also used for categorial.
3512
+ */
3513
+ public void setMissingString(String value) {
3514
+ missingString = value;
3515
+ }
3516
+
3517
+
3518
+ /**
3519
+ * @webref table:method
3520
+ * @brief Store a String value in the specified row and column
3521
+ * @param row ID number of the target row
3522
+ * @param column ID number of the target column
3523
+ * @param value value to assign
3524
+ * @see Table#setInt(int, int, int)
3525
+ * @see Table#setFloat(int, int, float)
3526
+ * @see Table#getInt(int, int)
3527
+ * @see Table#getFloat(int, int)
3528
+ * @see Table#getString(int, int)
3529
+ * @see Table#getStringColumn(String)
3530
+ */
3531
+ public void setString(int row, int column, String value) {
3532
+ ensureBounds(row, column);
3533
+ if (columnTypes[column] != STRING) {
3534
+ throw new IllegalArgumentException("Column " + column + " is not a String column.");
3535
+ }
3536
+ String[] stringData = (String[]) columns[column];
3537
+ stringData[row] = value;
3538
+ }
3539
+
3540
+ /**
3541
+ * @param columnName title of the target column
3542
+ */
3543
+ public void setString(int row, String columnName, String value) {
3544
+ int column = checkColumnIndex(columnName);
3545
+ setString(row, column, value);
3546
+ }
3547
+
3548
+ /**
3549
+ * @webref table:method
3550
+ * @brief Gets all values in the specified column
3551
+ * @param columnName title of the column to search
3552
+ * @see Table#getInt(int, int)
3553
+ * @see Table#getFloat(int, int)
3554
+ * @see Table#getString(int, int)
3555
+ * @see Table#setInt(int, int, int)
3556
+ * @see Table#setFloat(int, int, float)
3557
+ * @see Table#setString(int, int, String)
3558
+ */
3559
+ public String[] getStringColumn(String columnName) {
3560
+ int col = getColumnIndex(columnName);
3561
+ return (col == -1) ? null : getStringColumn(col);
3562
+ }
3563
+
3564
+
3565
+ /**
3566
+ * @param column ID number of the column to search
3567
+ */
3568
+ public String[] getStringColumn(int column) {
3569
+ String[] outgoing = new String[rowCount];
3570
+ for (int i = 0; i < rowCount; i++) {
3571
+ outgoing[i] = getString(i, column);
3572
+ }
3573
+ return outgoing;
3574
+ }
3575
+
3576
+
3577
+ public String[] getStringRow(int row) {
3578
+ String[] outgoing = new String[columns.length];
3579
+ for (int col = 0; col < columns.length; col++) {
3580
+ outgoing[col] = getString(row, col);
3581
+ }
3582
+ return outgoing;
3583
+ }
3584
+
3585
+
3586
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3587
+
3588
+
3589
+ /**
3590
+ * Return the row that contains the first String that matches.
3591
+ * @param value the String to match
3592
+ * @param column ID number of the column to search
3593
+ */
3594
+ public int findRowIndex(String value, int column) {
3595
+ checkColumn(column);
3596
+ if (columnTypes[column] == STRING) {
3597
+ String[] stringData = (String[]) columns[column];
3598
+ if (value == null) {
3599
+ for (int row = 0; row < rowCount; row++) {
3600
+ if (stringData[row] == null) return row;
3601
+ }
3602
+ } else {
3603
+ for (int row = 0; row < rowCount; row++) {
3604
+ if (stringData[row] != null && stringData[row].equals(value)) {
3605
+ return row;
3606
+ }
3607
+ }
3608
+ }
3609
+ } else { // less efficient, includes conversion as necessary
3610
+ for (int row = 0; row < rowCount; row++) {
3611
+ String str = getString(row, column);
3612
+ if (str == null) {
3613
+ if (value == null) {
3614
+ return row;
3615
+ }
3616
+ } else if (str.equals(value)) {
3617
+ return row;
3618
+ }
3619
+ }
3620
+ }
3621
+ return -1;
3622
+ }
3623
+
3624
+
3625
+ /**
3626
+ * Return the row that contains the first String that matches.
3627
+ * @param value the String to match
3628
+ * @param columnName title of the column to search
3629
+ */
3630
+ public int findRowIndex(String value, String columnName) {
3631
+ return findRowIndex(value, getColumnIndex(columnName));
3632
+ }
3633
+
3634
+
3635
+ /**
3636
+ * Return a list of rows that contain the String passed in. If there are no
3637
+ * matches, a zero length array will be returned (not a null array).
3638
+ * @param value the String to match
3639
+ * @param column ID number of the column to search
3640
+ */
3641
+ public int[] findRowIndices(String value, int column) {
3642
+ int[] outgoing = new int[rowCount];
3643
+ int count = 0;
3644
+
3645
+ checkColumn(column);
3646
+ if (columnTypes[column] == STRING) {
3647
+ String[] stringData = (String[]) columns[column];
3648
+ if (value == null) {
3649
+ for (int row = 0; row < rowCount; row++) {
3650
+ if (stringData[row] == null) {
3651
+ outgoing[count++] = row;
3652
+ }
3653
+ }
3654
+ } else {
3655
+ for (int row = 0; row < rowCount; row++) {
3656
+ if (stringData[row] != null && stringData[row].equals(value)) {
3657
+ outgoing[count++] = row;
3658
+ }
3659
+ }
3660
+ }
3661
+ } else { // less efficient, includes conversion as necessary
3662
+ for (int row = 0; row < rowCount; row++) {
3663
+ String str = getString(row, column);
3664
+ if (str == null) {
3665
+ if (value == null) {
3666
+ outgoing[count++] = row;
3667
+ }
3668
+ } else if (str.equals(value)) {
3669
+ outgoing[count++] = row;
3670
+ }
3671
+ }
3672
+ }
3673
+ return PApplet.subset(outgoing, 0, count);
3674
+ }
3675
+
3676
+
3677
+ /**
3678
+ * Return a list of rows that contain the String passed in. If there are no
3679
+ * matches, a zero length array will be returned (not a null array).
3680
+ * @param value the String to match
3681
+ * @param columnName title of the column to search
3682
+ */
3683
+ public int[] findRowIndices(String value, String columnName) {
3684
+ return findRowIndices(value, getColumnIndex(columnName));
3685
+ }
3686
+
3687
+
3688
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3689
+
3690
+ /**
3691
+ * @webref table:method
3692
+ * @brief Finds a row that contains the given value
3693
+ * @param value the value to match
3694
+ * @param column ID number of the column to search
3695
+ * @see Table#getRow(int)
3696
+ * @see Table#rows()
3697
+ * @see Table#findRows(String, int)
3698
+ * @see Table#matchRow(String, int)
3699
+ * @see Table#matchRows(String, int)
3700
+ */
3701
+ public TableRow findRow(String value, int column) {
3702
+ int row = findRowIndex(value, column);
3703
+ return (row == -1) ? null : new RowPointer(this, row);
3704
+ }
3705
+
3706
+
3707
+ /**
3708
+ * @param columnName title of the column to search
3709
+ */
3710
+ public TableRow findRow(String value, String columnName) {
3711
+ return findRow(value, getColumnIndex(columnName));
3712
+ }
3713
+
3714
+
3715
+ /**
3716
+ * @webref table:method
3717
+ * @brief Finds multiple rows that contain the given value
3718
+ * @param value the value to match
3719
+ * @param column ID number of the column to search
3720
+ * @see Table#getRow(int)
3721
+ * @see Table#rows()
3722
+ * @see Table#findRow(String, int)
3723
+ * @see Table#matchRow(String, int)
3724
+ * @see Table#matchRows(String, int)
3725
+ */
3726
+ public Iterable<TableRow> findRows(final String value, final int column) {
3727
+ return new Iterable<TableRow>() {
3728
+ public Iterator<TableRow> iterator() {
3729
+ return findRowIterator(value, column);
3730
+ }
3731
+ };
3732
+ }
3733
+
3734
+
3735
+ /**
3736
+ * @param columnName title of the column to search
3737
+ */
3738
+ public Iterable<TableRow> findRows(final String value, final String columnName) {
3739
+ return findRows(value, getColumnIndex(columnName));
3740
+ }
3741
+
3742
+
3743
+ /**
3744
+ * @brief Finds multiple rows that contain the given value
3745
+ * @param value the value to match
3746
+ * @param column ID number of the column to search
3747
+ */
3748
+ public Iterator<TableRow> findRowIterator(String value, int column) {
3749
+ return new RowIndexIterator(this, findRowIndices(value, column));
3750
+ }
3751
+
3752
+
3753
+ /**
3754
+ * @param columnName title of the column to search
3755
+ */
3756
+ public Iterator<TableRow> findRowIterator(String value, String columnName) {
3757
+ return findRowIterator(value, getColumnIndex(columnName));
3758
+ }
3759
+
3760
+
3761
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3762
+
3763
+
3764
+ /**
3765
+ * Return the row that contains the first String that matches.
3766
+ * @param regexp the String to match
3767
+ * @param column ID number of the column to search
3768
+ */
3769
+ public int matchRowIndex(String regexp, int column) {
3770
+ checkColumn(column);
3771
+ if (columnTypes[column] == STRING) {
3772
+ String[] stringData = (String[]) columns[column];
3773
+ for (int row = 0; row < rowCount; row++) {
3774
+ if (stringData[row] != null &&
3775
+ PApplet.match(stringData[row], regexp) != null) {
3776
+ return row;
3777
+ }
3778
+ }
3779
+ } else { // less efficient, includes conversion as necessary
3780
+ for (int row = 0; row < rowCount; row++) {
3781
+ String str = getString(row, column);
3782
+ if (str != null &&
3783
+ PApplet.match(str, regexp) != null) {
3784
+ return row;
3785
+ }
3786
+ }
3787
+ }
3788
+ return -1;
3789
+ }
3790
+
3791
+
3792
+ /**
3793
+ * Return the row that contains the first String that matches.
3794
+ * @param what the String to match
3795
+ * @param columnName title of the column to search
3796
+ */
3797
+ public int matchRowIndex(String what, String columnName) {
3798
+ return matchRowIndex(what, getColumnIndex(columnName));
3799
+ }
3800
+
3801
+
3802
+ /**
3803
+ * Return a list of rows that contain the String passed in. If there are no
3804
+ * matches, a zero length array will be returned (not a null array).
3805
+ * @param regexp the String to match
3806
+ * @param column ID number of the column to search
3807
+ */
3808
+ public int[] matchRowIndices(String regexp, int column) {
3809
+ int[] outgoing = new int[rowCount];
3810
+ int count = 0;
3811
+
3812
+ checkColumn(column);
3813
+ if (columnTypes[column] == STRING) {
3814
+ String[] stringData = (String[]) columns[column];
3815
+ for (int row = 0; row < rowCount; row++) {
3816
+ if (stringData[row] != null &&
3817
+ PApplet.match(stringData[row], regexp) != null) {
3818
+ outgoing[count++] = row;
3819
+ }
3820
+ }
3821
+ } else { // less efficient, includes conversion as necessary
3822
+ for (int row = 0; row < rowCount; row++) {
3823
+ String str = getString(row, column);
3824
+ if (str != null &&
3825
+ PApplet.match(str, regexp) != null) {
3826
+ outgoing[count++] = row;
3827
+ }
3828
+ }
3829
+ }
3830
+ return PApplet.subset(outgoing, 0, count);
3831
+ }
3832
+
3833
+
3834
+ /**
3835
+ * Return a list of rows that match the regex passed in. If there are no
3836
+ * matches, a zero length array will be returned (not a null array).
3837
+ * @param what the String to match
3838
+ * @param columnName title of the column to search
3839
+ */
3840
+ public int[] matchRowIndices(String what, String columnName) {
3841
+ return matchRowIndices(what, getColumnIndex(columnName));
3842
+ }
3843
+
3844
+
3845
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3846
+
3847
+ /**
3848
+ * @webref table:method
3849
+ * @brief Finds a row that matches the given expression
3850
+ * @param regexp the regular expression to match
3851
+ * @param column ID number of the column to search
3852
+ * @see Table#getRow(int)
3853
+ * @see Table#rows()
3854
+ * @see Table#findRow(String, int)
3855
+ * @see Table#findRows(String, int)
3856
+ * @see Table#matchRows(String, int)
3857
+ */
3858
+ public TableRow matchRow(String regexp, int column) {
3859
+ int row = matchRowIndex(regexp, column);
3860
+ return (row == -1) ? null : new RowPointer(this, row);
3861
+ }
3862
+
3863
+
3864
+ /**
3865
+ * @param columnName title of the column to search
3866
+ */
3867
+ public TableRow matchRow(String regexp, String columnName) {
3868
+ return matchRow(regexp, getColumnIndex(columnName));
3869
+ }
3870
+
3871
+
3872
+ /**
3873
+ * @webref table:method
3874
+ * @brief Finds multiple rows that match the given expression
3875
+ * @param regexp the regular expression to match
3876
+ * @param column ID number of the column to search
3877
+ * @see Table#getRow(int)
3878
+ * @see Table#rows()
3879
+ * @see Table#findRow(String, int)
3880
+ * @see Table#findRows(String, int)
3881
+ * @see Table#matchRow(String, int)
3882
+ */
3883
+ public Iterable<TableRow> matchRows(final String regexp, final int column) {
3884
+ return new Iterable<TableRow>() {
3885
+ public Iterator<TableRow> iterator() {
3886
+ return matchRowIterator(regexp, column);
3887
+ }
3888
+ };
3889
+ }
3890
+
3891
+
3892
+ /**
3893
+ * @param columnName title of the column to search
3894
+ */
3895
+ public Iterable<TableRow> matchRows(String regexp, String columnName) {
3896
+ return matchRows(regexp, getColumnIndex(columnName));
3897
+ }
3898
+
3899
+
3900
+ /**
3901
+ * @webref table:method
3902
+ * @brief Finds multiple rows that match the given expression
3903
+ * @param value the regular expression to match
3904
+ * @param column ID number of the column to search
3905
+ */
3906
+ public Iterator<TableRow> matchRowIterator(String value, int column) {
3907
+ return new RowIndexIterator(this, matchRowIndices(value, column));
3908
+ }
3909
+
3910
+
3911
+ /**
3912
+ * @param columnName title of the column to search
3913
+ */
3914
+ public Iterator<TableRow> matchRowIterator(String value, String columnName) {
3915
+ return matchRowIterator(value, getColumnIndex(columnName));
3916
+ }
3917
+
3918
+
3919
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3920
+
3921
+
3922
+ /**
3923
+ * Replace a String with another. Set empty entries null by using
3924
+ * replace("", null) or use replace(null, "") to go the other direction.
3925
+ * If this is a typed table, only String columns will be modified.
3926
+ * @param orig
3927
+ * @param replacement
3928
+ */
3929
+ public void replace(String orig, String replacement) {
3930
+ for (int col = 0; col < columns.length; col++) {
3931
+ replace(orig, replacement, col);
3932
+ }
3933
+ }
3934
+
3935
+
3936
+ public void replace(String orig, String replacement, int col) {
3937
+ if (columnTypes[col] == STRING) {
3938
+ String[] stringData = (String[]) columns[col];
3939
+
3940
+ if (orig != null) {
3941
+ for (int row = 0; row < rowCount; row++) {
3942
+ if (orig.equals(stringData[row])) {
3943
+ stringData[row] = replacement;
3944
+ }
3945
+ }
3946
+ } else { // null is a special case (and faster anyway)
3947
+ for (int row = 0; row < rowCount; row++) {
3948
+ if (stringData[row] == null) {
3949
+ stringData[row] = replacement;
3950
+ }
3951
+ }
3952
+ }
3953
+ }
3954
+ }
3955
+
3956
+
3957
+ public void replace(String orig, String replacement, String colName) {
3958
+ replace(orig, replacement, getColumnIndex(colName));
3959
+ }
3960
+
3961
+
3962
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3963
+
3964
+
3965
+ public void replaceAll(String regex, String replacement) {
3966
+ for (int col = 0; col < columns.length; col++) {
3967
+ replaceAll(regex, replacement, col);
3968
+ }
3969
+ }
3970
+
3971
+
3972
+ public void replaceAll(String regex, String replacement, int column) {
3973
+ checkColumn(column);
3974
+ if (columnTypes[column] == STRING) {
3975
+ String[] stringData = (String[]) columns[column];
3976
+ for (int row = 0; row < rowCount; row++) {
3977
+ if (stringData[row] != null) {
3978
+ stringData[row] = stringData[row].replaceAll(regex, replacement);
3979
+ }
3980
+ }
3981
+ } else {
3982
+ throw new IllegalArgumentException("replaceAll() can only be used on String columns");
3983
+ }
3984
+ }
3985
+
3986
+
3987
+ /**
3988
+ * Run String.replaceAll() on all entries in a column.
3989
+ * Only works with columns that are already String values.
3990
+ * @param regex the String to match
3991
+ * @param columnName title of the column to search
3992
+ */
3993
+ public void replaceAll(String regex, String replacement, String columnName) {
3994
+ replaceAll(regex, replacement, getColumnIndex(columnName));
3995
+ }
3996
+
3997
+
3998
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3999
+
4000
+
4001
+ /**
4002
+ * Remove any of the specified characters from the entire table.
4003
+ *
4004
+ * @webref table:method
4005
+ * @brief Removes characters from the table
4006
+ * @param tokens a list of individual characters to be removed
4007
+ * @see Table#trim()
4008
+ */
4009
+ public void removeTokens(String tokens) {
4010
+ for (int col = 0; col < getColumnCount(); col++) {
4011
+ removeTokens(tokens, col);
4012
+ }
4013
+ }
4014
+
4015
+
4016
+ /**
4017
+ * Removed any of the specified characters from a column. For instance,
4018
+ * the following code removes dollar signs and commas from column 2:
4019
+ * <pre>
4020
+ * table.removeTokens(",$", 2);
4021
+ * </pre>
4022
+ *
4023
+ * @param column ID number of the column to process
4024
+ */
4025
+ public void removeTokens(String tokens, int column) {
4026
+ for (int row = 0; row < rowCount; row++) {
4027
+ String s = getString(row, column);
4028
+ if (s != null) {
4029
+ char[] c = s.toCharArray();
4030
+ int index = 0;
4031
+ for (int j = 0; j < c.length; j++) {
4032
+ if (tokens.indexOf(c[j]) == -1) {
4033
+ if (index != j) {
4034
+ c[index] = c[j];
4035
+ }
4036
+ index++;
4037
+ }
4038
+ }
4039
+ if (index != c.length) {
4040
+ setString(row, column, new String(c, 0, index));
4041
+ }
4042
+ }
4043
+ }
4044
+ }
4045
+
4046
+ /**
4047
+ * @param columnName title of the column to process
4048
+ */
4049
+ public void removeTokens(String tokens, String columnName) {
4050
+ removeTokens(tokens, getColumnIndex(columnName));
4051
+ }
4052
+
4053
+
4054
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4055
+
4056
+
4057
+ /**
4058
+ * @webref table:method
4059
+ * @brief Trims whitespace from values
4060
+ * @see Table#removeTokens(String)
4061
+ */
4062
+ public void trim() {
4063
+ columnTitles = PApplet.trim(columnTitles);
4064
+ for (int col = 0; col < getColumnCount(); col++) {
4065
+ trim(col);
4066
+ }
4067
+ // remove empty columns
4068
+ int lastColumn = getColumnCount() - 1;
4069
+ //while (isEmptyColumn(lastColumn) && lastColumn >= 0) {
4070
+ while (isEmptyArray(getStringColumn(lastColumn)) && lastColumn >= 0) {
4071
+ lastColumn--;
4072
+ }
4073
+ setColumnCount(lastColumn + 1);
4074
+
4075
+ // trim() works from both sides
4076
+ while (getColumnCount() > 0 && isEmptyArray(getStringColumn(0))) {
4077
+ removeColumn(0);
4078
+ }
4079
+
4080
+ // remove empty rows (starting from the end)
4081
+ int lastRow = lastRowIndex();
4082
+ //while (isEmptyRow(lastRow) && lastRow >= 0) {
4083
+ while (isEmptyArray(getStringRow(lastRow)) && lastRow >= 0) {
4084
+ lastRow--;
4085
+ }
4086
+ setRowCount(lastRow + 1);
4087
+
4088
+ while (getRowCount() > 0 && isEmptyArray(getStringRow(0))) {
4089
+ removeRow(0);
4090
+ }
4091
+ }
4092
+
4093
+
4094
+ protected boolean isEmptyArray(String[] contents) {
4095
+ for (String entry : contents) {
4096
+ if (entry != null && entry.length() > 0) {
4097
+ return false;
4098
+ }
4099
+ }
4100
+ return true;
4101
+ }
4102
+
4103
+
4104
+ /*
4105
+ protected boolean isEmptyColumn(int column) {
4106
+ String[] contents = getStringColumn(column);
4107
+ for (String entry : contents) {
4108
+ if (entry != null && entry.length() > 0) {
4109
+ return false;
4110
+ }
4111
+ }
4112
+ return true;
4113
+ }
4114
+
4115
+
4116
+ protected boolean isEmptyRow(int row) {
4117
+ String[] contents = getStringRow(row);
4118
+ for (String entry : contents) {
4119
+ if (entry != null && entry.length() > 0) {
4120
+ return false;
4121
+ }
4122
+ }
4123
+ return true;
4124
+ }
4125
+ */
4126
+
4127
+
4128
+ /**
4129
+ * @param column ID number of the column to trim
4130
+ */
4131
+ public void trim(int column) {
4132
+ if (columnTypes[column] == STRING) {
4133
+ String[] stringData = (String[]) columns[column];
4134
+ for (int row = 0; row < rowCount; row++) {
4135
+ if (stringData[row] != null) {
4136
+ stringData[row] = PApplet.trim(stringData[row]);
4137
+ }
4138
+ }
4139
+ }
4140
+ }
4141
+
4142
+ /**
4143
+ * @param columnName title of the column to trim
4144
+ */
4145
+ public void trim(String columnName) {
4146
+ trim(getColumnIndex(columnName));
4147
+ }
4148
+
4149
+
4150
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4151
+
4152
+
4153
+ /** Make sure this is a legit column, and if not, expand the table. */
4154
+ protected void ensureColumn(int col) {
4155
+ if (col >= columns.length) {
4156
+ setColumnCount(col + 1);
4157
+ }
4158
+ }
4159
+
4160
+
4161
+ /** Make sure this is a legit row, and if not, expand the table. */
4162
+ protected void ensureRow(int row) {
4163
+ if (row >= rowCount) {
4164
+ setRowCount(row + 1);
4165
+ }
4166
+ }
4167
+
4168
+
4169
+ /** Make sure this is a legit row and column. If not, expand the table. */
4170
+ protected void ensureBounds(int row, int col) {
4171
+ ensureRow(row);
4172
+ ensureColumn(col);
4173
+ }
4174
+
4175
+
4176
+ /** Throw an error if this row doesn't exist. */
4177
+ protected void checkRow(int row) {
4178
+ if (row < 0 || row >= rowCount) {
4179
+ throw new ArrayIndexOutOfBoundsException("Row " + row + " does not exist.");
4180
+ }
4181
+ }
4182
+
4183
+
4184
+ /** Throw an error if this column doesn't exist. */
4185
+ protected void checkColumn(int column) {
4186
+ if (column < 0 || column >= columns.length) {
4187
+ throw new ArrayIndexOutOfBoundsException("Column " + column + " does not exist.");
4188
+ }
4189
+ }
4190
+
4191
+
4192
+ /** Throw an error if this entry is out of bounds. */
4193
+ protected void checkBounds(int row, int column) {
4194
+ checkRow(row);
4195
+ checkColumn(column);
4196
+ }
4197
+
4198
+
4199
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4200
+
4201
+
4202
+ static class HashMapBlows {
4203
+ HashMap<String,Integer> dataToIndex = new HashMap<>();
4204
+ ArrayList<String> indexToData = new ArrayList<>();
4205
+
4206
+ HashMapBlows() { }
4207
+
4208
+ HashMapBlows(DataInputStream input) throws IOException {
4209
+ read(input);
4210
+ }
4211
+
4212
+ /** gets the index, and creates one if it doesn't already exist. */
4213
+ int index(String key) {
4214
+ Integer value = dataToIndex.get(key);
4215
+ if (value != null) {
4216
+ return value;
4217
+ }
4218
+
4219
+ int v = dataToIndex.size();
4220
+ dataToIndex.put(key, v);
4221
+ indexToData.add(key);
4222
+ return v;
4223
+ }
4224
+
4225
+ String key(int index) {
4226
+ return indexToData.get(index);
4227
+ }
4228
+
4229
+ boolean hasCategory(int index) {
4230
+ return index < size() && indexToData.get(index) != null;
4231
+ }
4232
+
4233
+ void setCategory(int index, String name) {
4234
+ while (indexToData.size() <= index) {
4235
+ indexToData.add(null);
4236
+ }
4237
+ indexToData.set(index, name);
4238
+ dataToIndex.put(name, index);
4239
+ }
4240
+
4241
+ int size() {
4242
+ return dataToIndex.size();
4243
+ }
4244
+
4245
+ void write(DataOutputStream output) throws IOException {
4246
+ output.writeInt(size());
4247
+ for (String str : indexToData) {
4248
+ output.writeUTF(str);
4249
+ }
4250
+ }
4251
+
4252
+ private void writeln(PrintWriter writer) throws IOException {
4253
+ for (String str : indexToData) {
4254
+ writer.println(str);
4255
+ }
4256
+ writer.flush();
4257
+ writer.close();
4258
+ }
4259
+
4260
+ void read(DataInputStream input) throws IOException {
4261
+ int count = input.readInt();
4262
+ //System.out.println("found " + count + " entries in category map");
4263
+ dataToIndex = new HashMap<>(count);
4264
+ for (int i = 0; i < count; i++) {
4265
+ String str = input.readUTF();
4266
+ //System.out.println(i + " " + str);
4267
+ dataToIndex.put(str, i);
4268
+ indexToData.add(str);
4269
+ }
4270
+ }
4271
+ }
4272
+
4273
+
4274
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4275
+
4276
+
4277
+ // class HashMapSucks extends HashMap<String,Integer> {
4278
+ //
4279
+ // void increment(String what) {
4280
+ // Integer value = get(what);
4281
+ // if (value == null) {
4282
+ // put(what, 1);
4283
+ // } else {
4284
+ // put(what, value + 1);
4285
+ // }
4286
+ // }
4287
+ //
4288
+ // void check(String what) {
4289
+ // if (get(what) == null) {
4290
+ // put(what, 0);
4291
+ // }
4292
+ // }
4293
+ // }
4294
+
4295
+
4296
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4297
+
4298
+ /**
4299
+ * Sorts (orders) a table based on the values in a column.
4300
+ *
4301
+ * @webref table:method
4302
+ * @brief Orders a table based on the values in a column
4303
+ * @param columnName the name of the column to sort
4304
+ * @see Table#trim()
4305
+ */
4306
+ public void sort(String columnName) {
4307
+ sort(getColumnIndex(columnName), false);
4308
+ }
4309
+
4310
+ /**
4311
+ * @param column the column ID, e.g. 0, 1, 2
4312
+ */
4313
+ public void sort(int column) {
4314
+ sort(column, false);
4315
+ }
4316
+
4317
+
4318
+ public void sortReverse(String columnName) {
4319
+ sort(getColumnIndex(columnName), true);
4320
+ }
4321
+
4322
+
4323
+ public void sortReverse(int column) {
4324
+ sort(column, true);
4325
+ }
4326
+
4327
+
4328
+ protected void sort(final int column, final boolean reverse) {
4329
+ final int[] order = IntList.fromRange(getRowCount()).array();
4330
+ Sort s = new Sort() {
4331
+
4332
+ @Override
4333
+ public int size() {
4334
+ return getRowCount();
4335
+ }
4336
+
4337
+ @Override
4338
+ public int compare(int index1, int index2) {
4339
+ int a = reverse ? order[index2] : order[index1];
4340
+ int b = reverse ? order[index1] : order[index2];
4341
+
4342
+ switch (getColumnType(column)) {
4343
+ case INT:
4344
+ return getInt(a, column) - getInt(b, column);
4345
+ case LONG:
4346
+ long diffl = getLong(a, column) - getLong(b, column);
4347
+ return diffl == 0 ? 0 : (diffl < 0 ? -1 : 1);
4348
+ case FLOAT:
4349
+ float difff = getFloat(a, column) - getFloat(b, column);
4350
+ return difff == 0 ? 0 : (difff < 0 ? -1 : 1);
4351
+ case DOUBLE:
4352
+ double diffd = getDouble(a, column) - getDouble(b, column);
4353
+ return diffd == 0 ? 0 : (diffd < 0 ? -1 : 1);
4354
+ case STRING:
4355
+ String string1 = getString(a, column);
4356
+ if (string1 == null) {
4357
+ string1 = ""; // avoid NPE when cells are left empty
4358
+ }
4359
+ String string2 = getString(b, column);
4360
+ if (string2 == null) {
4361
+ string2 = "";
4362
+ }
4363
+ return string1.compareToIgnoreCase(string2);
4364
+ case CATEGORY:
4365
+ return getInt(a, column) - getInt(b, column);
4366
+ default:
4367
+ throw new IllegalArgumentException("Invalid column type: " + getColumnType(column));
4368
+ }
4369
+ }
4370
+
4371
+ @Override
4372
+ public void swap(int a, int b) {
4373
+ int temp = order[a];
4374
+ order[a] = order[b];
4375
+ order[b] = temp;
4376
+ }
4377
+
4378
+ };
4379
+ s.run();
4380
+
4381
+ //Object[] newColumns = new Object[getColumnCount()];
4382
+ for (int col = 0; col < getColumnCount(); col++) {
4383
+ switch (getColumnType(col)) {
4384
+ case INT:
4385
+ case CATEGORY:
4386
+ int[] oldInt = (int[]) columns[col];
4387
+ int[] newInt = new int[rowCount];
4388
+ for (int row = 0; row < getRowCount(); row++) {
4389
+ newInt[row] = oldInt[order[row]];
4390
+ }
4391
+ columns[col] = newInt;
4392
+ break;
4393
+ case LONG:
4394
+ long[] oldLong = (long[]) columns[col];
4395
+ long[] newLong = new long[rowCount];
4396
+ for (int row = 0; row < getRowCount(); row++) {
4397
+ newLong[row] = oldLong[order[row]];
4398
+ }
4399
+ columns[col] = newLong;
4400
+ break;
4401
+ case FLOAT:
4402
+ float[] oldFloat = (float[]) columns[col];
4403
+ float[] newFloat = new float[rowCount];
4404
+ for (int row = 0; row < getRowCount(); row++) {
4405
+ newFloat[row] = oldFloat[order[row]];
4406
+ }
4407
+ columns[col] = newFloat;
4408
+ break;
4409
+ case DOUBLE:
4410
+ double[] oldDouble = (double[]) columns[col];
4411
+ double[] newDouble = new double[rowCount];
4412
+ for (int row = 0; row < getRowCount(); row++) {
4413
+ newDouble[row] = oldDouble[order[row]];
4414
+ }
4415
+ columns[col] = newDouble;
4416
+ break;
4417
+ case STRING:
4418
+ String[] oldString = (String[]) columns[col];
4419
+ String[] newString = new String[rowCount];
4420
+ for (int row = 0; row < getRowCount(); row++) {
4421
+ newString[row] = oldString[order[row]];
4422
+ }
4423
+ columns[col] = newString;
4424
+ break;
4425
+ }
4426
+ }
4427
+ }
4428
+
4429
+
4430
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4431
+
4432
+
4433
+ public String[] getUnique(String columnName) {
4434
+ return getUnique(getColumnIndex(columnName));
4435
+ }
4436
+
4437
+
4438
+ public String[] getUnique(int column) {
4439
+ StringList list = new StringList(getStringColumn(column));
4440
+ return list.getUnique();
4441
+ }
4442
+
4443
+
4444
+ public IntDict getTally(String columnName) {
4445
+ return getTally(getColumnIndex(columnName));
4446
+ }
4447
+
4448
+
4449
+ public IntDict getTally(int column) {
4450
+ StringList list = new StringList(getStringColumn(column));
4451
+ return list.getTally();
4452
+ }
4453
+
4454
+
4455
+ public IntDict getOrder(String columnName) {
4456
+ return getOrder(getColumnIndex(columnName));
4457
+ }
4458
+
4459
+
4460
+ public IntDict getOrder(int column) {
4461
+ StringList list = new StringList(getStringColumn(column));
4462
+ return list.getOrder();
4463
+ }
4464
+
4465
+
4466
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4467
+
4468
+
4469
+ public IntList getIntList(String columnName) {
4470
+ return new IntList(getIntColumn(columnName));
4471
+ }
4472
+
4473
+
4474
+ public IntList getIntList(int column) {
4475
+ return new IntList(getIntColumn(column));
4476
+ }
4477
+
4478
+
4479
+ public FloatList getFloatList(String columnName) {
4480
+ return new FloatList(getFloatColumn(columnName));
4481
+ }
4482
+
4483
+
4484
+ public FloatList getFloatList(int column) {
4485
+ return new FloatList(getFloatColumn(column));
4486
+ }
4487
+
4488
+
4489
+ public StringList getStringList(String columnName) {
4490
+ return new StringList(getStringColumn(columnName));
4491
+ }
4492
+
4493
+
4494
+ public StringList getStringList(int column) {
4495
+ return new StringList(getStringColumn(column));
4496
+ }
4497
+
4498
+
4499
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4500
+
4501
+
4502
+ public IntDict getIntDict(String keyColumnName, String valueColumnName) {
4503
+ return new IntDict(getStringColumn(keyColumnName),
4504
+ getIntColumn(valueColumnName));
4505
+ }
4506
+
4507
+
4508
+ public IntDict getIntDict(int keyColumn, int valueColumn) {
4509
+ return new IntDict(getStringColumn(keyColumn),
4510
+ getIntColumn(valueColumn));
4511
+ }
4512
+
4513
+
4514
+ public FloatDict getFloatDict(String keyColumnName, String valueColumnName) {
4515
+ return new FloatDict(getStringColumn(keyColumnName),
4516
+ getFloatColumn(valueColumnName));
4517
+ }
4518
+
4519
+
4520
+ public FloatDict getFloatDict(int keyColumn, int valueColumn) {
4521
+ return new FloatDict(getStringColumn(keyColumn),
4522
+ getFloatColumn(valueColumn));
4523
+ }
4524
+
4525
+
4526
+ public StringDict getStringDict(String keyColumnName, String valueColumnName) {
4527
+ return new StringDict(getStringColumn(keyColumnName),
4528
+ getStringColumn(valueColumnName));
4529
+ }
4530
+
4531
+
4532
+ public StringDict getStringDict(int keyColumn, int valueColumn) {
4533
+ return new StringDict(getStringColumn(keyColumn),
4534
+ getStringColumn(valueColumn));
4535
+ }
4536
+
4537
+
4538
+ public Map<String, TableRow> getRowMap(String columnName) {
4539
+ int col = getColumnIndex(columnName);
4540
+ return (col == -1) ? null : getRowMap(col);
4541
+ }
4542
+
4543
+
4544
+ /**
4545
+ * Return a mapping that connects the entry from a column back to the row
4546
+ * from which it came. For instance:
4547
+ * <pre>
4548
+ * Table t = loadTable("country-data.tsv", "header");
4549
+ * // use the contents of the 'country' column to index the table
4550
+ * Map<String, TableRow> lookup = t.getRowMap("country");
4551
+ * // get the row that has "us" in the "country" column:
4552
+ * TableRow usRow = lookup.get("us");
4553
+ * // get an entry from the 'population' column
4554
+ * int population = usRow.getInt("population");
4555
+ * </pre>
4556
+ */
4557
+ public Map<String, TableRow> getRowMap(int column) {
4558
+ Map<String, TableRow> outgoing = new HashMap<>();
4559
+ for (int row = 0; row < getRowCount(); row++) {
4560
+ String id = getString(row, column);
4561
+ outgoing.put(id, new RowPointer(this, row));
4562
+ }
4563
+ // for (TableRow row : rows()) {
4564
+ // String id = row.getString(column);
4565
+ // outgoing.put(id, row);
4566
+ // }
4567
+ return outgoing;
4568
+ }
4569
+
4570
+
4571
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4572
+
4573
+
4574
+ // /**
4575
+ // * Return an object that maps the String values in one column back to the
4576
+ // * row from which they came. For instance, if the "name" of each row is
4577
+ // * found in the first column, getColumnRowLookup(0) would return an object
4578
+ // * that would map each name back to its row.
4579
+ // */
4580
+ // protected HashMap<String,Integer> getRowLookup(int col) {
4581
+ // HashMap<String,Integer> outgoing = new HashMap<String, Integer>();
4582
+ // for (int row = 0; row < getRowCount(); row++) {
4583
+ // outgoing.put(getString(row, col), row);
4584
+ // }
4585
+ // return outgoing;
4586
+ // }
4587
+
4588
+
4589
+ // incomplete, basically this is silly to write all this repetitive code when
4590
+ // it can be implemented in ~3 lines of code...
4591
+ // /**
4592
+ // * Return an object that maps the data from one column to the data of found
4593
+ // * in another column.
4594
+ // */
4595
+ // public HashMap<?,?> getLookup(int col1, int col2) {
4596
+ // HashMap outgoing = null;
4597
+ //
4598
+ // switch (columnTypes[col1]) {
4599
+ // case INT: {
4600
+ // if (columnTypes[col2] == INT) {
4601
+ // outgoing = new HashMap<Integer, Integer>();
4602
+ // for (int row = 0; row < getRowCount(); row++) {
4603
+ // outgoing.put(getInt(row, col1), getInt(row, col2));
4604
+ // }
4605
+ // } else if (columnTypes[col2] == LONG) {
4606
+ // outgoing = new HashMap<Integer, Long>();
4607
+ // for (int row = 0; row < getRowCount(); row++) {
4608
+ // outgoing.put(getInt(row, col1), getLong(row, col2));
4609
+ // }
4610
+ // } else if (columnTypes[col2] == FLOAT) {
4611
+ // outgoing = new HashMap<Integer, Float>();
4612
+ // for (int row = 0; row < getRowCount(); row++) {
4613
+ // outgoing.put(getInt(row, col1), getFloat(row, col2));
4614
+ // }
4615
+ // } else if (columnTypes[col2] == DOUBLE) {
4616
+ // outgoing = new HashMap<Integer, Double>();
4617
+ // for (int row = 0; row < getRowCount(); row++) {
4618
+ // outgoing.put(getInt(row, col1), getDouble(row, col2));
4619
+ // }
4620
+ // } else if (columnTypes[col2] == STRING) {
4621
+ // outgoing = new HashMap<Integer, String>();
4622
+ // for (int row = 0; row < getRowCount(); row++) {
4623
+ // outgoing.put(getInt(row, col1), get(row, col2));
4624
+ // }
4625
+ // }
4626
+ // break;
4627
+ // }
4628
+ // }
4629
+ // return outgoing;
4630
+ // }
4631
+
4632
+
4633
+ // public StringIntPairs getColumnRowLookup(int col) {
4634
+ // StringIntPairs sc = new StringIntPairs();
4635
+ // String[] column = getStringColumn(col);
4636
+ // for (int i = 0; i < column.length; i++) {
4637
+ // sc.set(column[i], i);
4638
+ // }
4639
+ // return sc;
4640
+ // }
4641
+
4642
+
4643
+ // public String[] getUniqueEntries(int column) {
4644
+ //// HashMap indices = new HashMap();
4645
+ //// for (int row = 0; row < rowCount; row++) {
4646
+ //// indices.put(data[row][column], this); // 'this' is a dummy
4647
+ //// }
4648
+ // StringIntPairs sc = getStringCount(column);
4649
+ // return sc.keys();
4650
+ // }
4651
+ //
4652
+ //
4653
+ // public StringIntPairs getStringCount(String columnName) {
4654
+ // return getStringCount(getColumnIndex(columnName));
4655
+ // }
4656
+ //
4657
+ //
4658
+ // public StringIntPairs getStringCount(int column) {
4659
+ // StringIntPairs outgoing = new StringIntPairs();
4660
+ // for (int row = 0; row < rowCount; row++) {
4661
+ // String entry = data[row][column];
4662
+ // if (entry != null) {
4663
+ // outgoing.increment(entry);
4664
+ // }
4665
+ // }
4666
+ // return outgoing;
4667
+ // }
4668
+ //
4669
+ //
4670
+ // /**
4671
+ // * Return an object that maps the String values in one column back to the
4672
+ // * row from which they came. For instance, if the "name" of each row is
4673
+ // * found in the first column, getColumnRowLookup(0) would return an object
4674
+ // * that would map each name back to its row.
4675
+ // */
4676
+ // public StringIntPairs getColumnRowLookup(int col) {
4677
+ // StringIntPairs sc = new StringIntPairs();
4678
+ // String[] column = getStringColumn(col);
4679
+ // for (int i = 0; i < column.length; i++) {
4680
+ // sc.set(column[i], i);
4681
+ // }
4682
+ // return sc;
4683
+ // }
4684
+
4685
+
4686
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4687
+
4688
+
4689
+ // TODO naming/whether to include
4690
+ protected Table createSubset(int[] rowSubset) {
4691
+ Table newbie = new Table();
4692
+ newbie.setColumnTitles(columnTitles); // also sets columns.length
4693
+ newbie.columnTypes = columnTypes;
4694
+ newbie.setRowCount(rowSubset.length);
4695
+
4696
+ for (int i = 0; i < rowSubset.length; i++) {
4697
+ int row = rowSubset[i];
4698
+ for (int col = 0; col < columns.length; col++) {
4699
+ switch (columnTypes[col]) {
4700
+ case STRING: newbie.setString(i, col, getString(row, col)); break;
4701
+ case INT: newbie.setInt(i, col, getInt(row, col)); break;
4702
+ case LONG: newbie.setLong(i, col, getLong(row, col)); break;
4703
+ case FLOAT: newbie.setFloat(i, col, getFloat(row, col)); break;
4704
+ case DOUBLE: newbie.setDouble(i, col, getDouble(row, col)); break;
4705
+ }
4706
+ }
4707
+ }
4708
+ return newbie;
4709
+ }
4710
+
4711
+
4712
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4713
+
4714
+
4715
+ /**
4716
+ * Searches the entire table for float values.
4717
+ * Returns missing float (Float.NaN by default) if no valid numbers found.
4718
+ */
4719
+ protected float getMaxFloat() {
4720
+ boolean found = false;
4721
+ float max = PConstants.MIN_FLOAT;
4722
+ for (int row = 0; row < getRowCount(); row++) {
4723
+ for (int col = 0; col < getColumnCount(); col++) {
4724
+ float value = getFloat(row, col);
4725
+ if (!Float.isNaN(value)) { // TODO no, this should be comparing to the missing value
4726
+ if (!found) {
4727
+ max = value;
4728
+ found = true;
4729
+ } else if (value > max) {
4730
+ max = value;
4731
+ }
4732
+ }
4733
+ }
4734
+ }
4735
+ return found ? max : missingFloat;
4736
+ }
4737
+
4738
+
4739
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4740
+
4741
+
4742
+ // converts a TSV or CSV file to binary.. do not use
4743
+ protected void convertBasic(BufferedReader reader, boolean tsv,
4744
+ File outputFile) throws IOException {
4745
+ FileOutputStream fos = new FileOutputStream(outputFile);
4746
+ BufferedOutputStream bos = new BufferedOutputStream(fos, 16384);
4747
+ DataOutputStream output = new DataOutputStream(bos);
4748
+ output.writeInt(0); // come back for row count
4749
+ output.writeInt(getColumnCount());
4750
+ if (columnTitles != null) {
4751
+ output.writeBoolean(true);
4752
+ for (String title : columnTitles) {
4753
+ output.writeUTF(title);
4754
+ }
4755
+ } else {
4756
+ output.writeBoolean(false);
4757
+ }
4758
+ for (int type : columnTypes) {
4759
+ output.writeInt(type);
4760
+ }
4761
+
4762
+ String line = null;
4763
+ //setRowCount(1);
4764
+ int prev = -1;
4765
+ int row = 0;
4766
+ while ((line = reader.readLine()) != null) {
4767
+ convertRow(output, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader));
4768
+ row++;
4769
+
4770
+ if (row % 10000 == 0) {
4771
+ if (row < rowCount) {
4772
+ int pct = (100 * row) / rowCount;
4773
+ if (pct != prev) {
4774
+ System.out.println(pct + "%");
4775
+ prev = pct;
4776
+ }
4777
+ }
4778
+ // try {
4779
+ // Thread.sleep(5);
4780
+ // } catch (InterruptedException e) {
4781
+ // e.printStackTrace();
4782
+ // }
4783
+ }
4784
+ }
4785
+ // shorten or lengthen based on what's left
4786
+ // if (row != getRowCount()) {
4787
+ // setRowCount(row);
4788
+ // }
4789
+
4790
+ // has to come afterwards, since these tables get built out during the conversion
4791
+ int col = 0;
4792
+ for (HashMapBlows hmb : columnCategories) {
4793
+ if (hmb == null) {
4794
+ output.writeInt(0);
4795
+ } else {
4796
+ hmb.write(output);
4797
+ hmb.writeln(PApplet.createWriter(new File(columnTitles[col] + ".categories")));
4798
+ // output.writeInt(hmb.size());
4799
+ // for (Map.Entry<String,Integer> e : hmb.entrySet()) {
4800
+ // output.writeUTF(e.getKey());
4801
+ // output.writeInt(e.getValue());
4802
+ // }
4803
+ }
4804
+ col++;
4805
+ }
4806
+
4807
+ output.flush();
4808
+ output.close();
4809
+
4810
+ // come back and write the row count
4811
+ RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
4812
+ raf.writeInt(rowCount);
4813
+ raf.close();
4814
+ }
4815
+
4816
+
4817
+ protected void convertRow(DataOutputStream output, String[] pieces) throws IOException {
4818
+ if (pieces.length > getColumnCount()) {
4819
+ throw new IllegalArgumentException("Row with too many columns: " +
4820
+ PApplet.join(pieces, ","));
4821
+ }
4822
+ // pieces.length may be less than columns.length, so loop over pieces
4823
+ for (int col = 0; col < pieces.length; col++) {
4824
+ switch (columnTypes[col]) {
4825
+ case STRING:
4826
+ output.writeUTF(pieces[col]);
4827
+ break;
4828
+ case INT:
4829
+ output.writeInt(PApplet.parseInt(pieces[col], missingInt));
4830
+ break;
4831
+ case LONG:
4832
+ try {
4833
+ output.writeLong(Long.parseLong(pieces[col]));
4834
+ } catch (NumberFormatException nfe) {
4835
+ output.writeLong(missingLong);
4836
+ }
4837
+ break;
4838
+ case FLOAT:
4839
+ output.writeFloat(PApplet.parseFloat(pieces[col], missingFloat));
4840
+ break;
4841
+ case DOUBLE:
4842
+ try {
4843
+ output.writeDouble(Double.parseDouble(pieces[col]));
4844
+ } catch (NumberFormatException nfe) {
4845
+ output.writeDouble(missingDouble);
4846
+ }
4847
+ break;
4848
+ case CATEGORY:
4849
+ String peace = pieces[col];
4850
+ if (peace.equals(missingString)) {
4851
+ output.writeInt(missingCategory);
4852
+ } else {
4853
+ output.writeInt(columnCategories[col].index(peace));
4854
+ }
4855
+ break;
4856
+ }
4857
+ }
4858
+ for (int col = pieces.length; col < getColumnCount(); col++) {
4859
+ switch (columnTypes[col]) {
4860
+ case STRING:
4861
+ output.writeUTF("");
4862
+ break;
4863
+ case INT:
4864
+ output.writeInt(missingInt);
4865
+ break;
4866
+ case LONG:
4867
+ output.writeLong(missingLong);
4868
+ break;
4869
+ case FLOAT:
4870
+ output.writeFloat(missingFloat);
4871
+ break;
4872
+ case DOUBLE:
4873
+ output.writeDouble(missingDouble);
4874
+ break;
4875
+ case CATEGORY:
4876
+ output.writeInt(missingCategory);
4877
+ break;
4878
+
4879
+ }
4880
+ }
4881
+ }
4882
+
4883
+
4884
+ /*
4885
+ private void convertRowCol(DataOutputStream output, int row, int col, String piece) {
4886
+ switch (columnTypes[col]) {
4887
+ case STRING:
4888
+ String[] stringData = (String[]) columns[col];
4889
+ stringData[row] = piece;
4890
+ break;
4891
+ case INT:
4892
+ int[] intData = (int[]) columns[col];
4893
+ intData[row] = PApplet.parseInt(piece, missingInt);
4894
+ break;
4895
+ case LONG:
4896
+ long[] longData = (long[]) columns[col];
4897
+ try {
4898
+ longData[row] = Long.parseLong(piece);
4899
+ } catch (NumberFormatException nfe) {
4900
+ longData[row] = missingLong;
4901
+ }
4902
+ break;
4903
+ case FLOAT:
4904
+ float[] floatData = (float[]) columns[col];
4905
+ floatData[row] = PApplet.parseFloat(piece, missingFloat);
4906
+ break;
4907
+ case DOUBLE:
4908
+ double[] doubleData = (double[]) columns[col];
4909
+ try {
4910
+ doubleData[row] = Double.parseDouble(piece);
4911
+ } catch (NumberFormatException nfe) {
4912
+ doubleData[row] = missingDouble;
4913
+ }
4914
+ break;
4915
+ default:
4916
+ throw new IllegalArgumentException("That's not a valid column type.");
4917
+ }
4918
+ }
4919
+ */
4920
+
4921
+
4922
+ /** Make a copy of the current table */
4923
+ public Table copy() {
4924
+ return new Table(rows());
4925
+ }
4926
+
4927
+
4928
+ public void write(PrintWriter writer) {
4929
+ writeTSV(writer);
4930
+ }
4931
+
4932
+
4933
+ public void print() {
4934
+ writeTSV(new PrintWriter(System.out));
4935
+ }
4936
+ }