picrate 0.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +47 -0
  3. data/.mvn/extensions.xml +9 -0
  4. data/.mvn/wrapper/maven-wrapper.properties +1 -0
  5. data/.travis.yml +10 -0
  6. data/CHANGELOG.md +4 -0
  7. data/LICENSE.md +165 -0
  8. data/README.md +51 -0
  9. data/Rakefile +59 -0
  10. data/bin/picrate +8 -0
  11. data/docs/.gitignore +6 -0
  12. data/docs/_config.yml +30 -0
  13. data/docs/_includes/footer.html +38 -0
  14. data/docs/_includes/head.html +16 -0
  15. data/docs/_includes/header.html +27 -0
  16. data/docs/_includes/icon-github.html +1 -0
  17. data/docs/_includes/icon-github.svg +1 -0
  18. data/docs/_includes/icon-twitter.html +1 -0
  19. data/docs/_includes/icon-twitter.svg +1 -0
  20. data/docs/_includes/navigation.html +24 -0
  21. data/docs/_layouts/default.html +20 -0
  22. data/docs/_layouts/page.html +14 -0
  23. data/docs/_layouts/post.html +15 -0
  24. data/docs/_posts/2018-05-06-getting_started.md +8 -0
  25. data/docs/_posts/2018-05-06-install_jruby.md +35 -0
  26. data/docs/_sass/_base.scss +206 -0
  27. data/docs/_sass/_layout.scss +242 -0
  28. data/docs/_sass/_syntax-highlighting.scss +71 -0
  29. data/docs/about.md +10 -0
  30. data/docs/css/main.scss +38 -0
  31. data/docs/favicon.ico +0 -0
  32. data/docs/feed.xml +30 -0
  33. data/docs/index.html +38 -0
  34. data/lib/picrate.rb +10 -0
  35. data/lib/picrate/app.rb +187 -0
  36. data/lib/picrate/creators/sketch_class.rb +57 -0
  37. data/lib/picrate/creators/sketch_factory.rb +12 -0
  38. data/lib/picrate/creators/sketch_writer.rb +21 -0
  39. data/lib/picrate/helper_methods.rb +214 -0
  40. data/lib/picrate/helpers/numeric.rb +9 -0
  41. data/lib/picrate/library.rb +69 -0
  42. data/lib/picrate/library_loader.rb +53 -0
  43. data/lib/picrate/native_folder.rb +35 -0
  44. data/lib/picrate/native_loader.rb +27 -0
  45. data/lib/picrate/runner.rb +81 -0
  46. data/lib/picrate/version.rb +4 -0
  47. data/library/boids/boids.rb +209 -0
  48. data/library/chooser/chooser.rb +19 -0
  49. data/library/control_panel/control_panel.rb +182 -0
  50. data/library/library_proxy/README.md +99 -0
  51. data/library/library_proxy/library_proxy.rb +14 -0
  52. data/library/slider/slider.rb +42 -0
  53. data/library/vector_utils/vector_utils.rb +69 -0
  54. data/library/video_event/video_event.rb +3 -0
  55. data/license.txt +508 -0
  56. data/picrate.gemspec +35 -0
  57. data/pom.rb +122 -0
  58. data/pom.xml +214 -0
  59. data/src/main/java/japplemenubar/JAppleMenuBar.java +88 -0
  60. data/src/main/java/japplemenubar/libjAppleMenuBar.jnilib +0 -0
  61. data/src/main/java/monkstone/ColorUtil.java +115 -0
  62. data/src/main/java/monkstone/MathToolModule.java +236 -0
  63. data/src/main/java/monkstone/PicrateLibrary.java +47 -0
  64. data/src/main/java/monkstone/core/LibraryProxy.java +127 -0
  65. data/src/main/java/monkstone/fastmath/Deglut.java +122 -0
  66. data/src/main/java/monkstone/fastmath/package-info.java +6 -0
  67. data/src/main/java/monkstone/filechooser/Chooser.java +48 -0
  68. data/src/main/java/monkstone/noise/SimplexNoise.java +465 -0
  69. data/src/main/java/monkstone/slider/CustomHorizontalSlider.java +168 -0
  70. data/src/main/java/monkstone/slider/CustomVerticalSlider.java +182 -0
  71. data/src/main/java/monkstone/slider/SimpleHorizontalSlider.java +149 -0
  72. data/src/main/java/monkstone/slider/SimpleSlider.java +196 -0
  73. data/src/main/java/monkstone/slider/SimpleVerticalSlider.java +163 -0
  74. data/src/main/java/monkstone/slider/Slider.java +67 -0
  75. data/src/main/java/monkstone/slider/SliderBar.java +277 -0
  76. data/src/main/java/monkstone/slider/SliderGroup.java +78 -0
  77. data/src/main/java/monkstone/slider/WheelHandler.java +35 -0
  78. data/src/main/java/monkstone/vecmath/AppRender.java +87 -0
  79. data/src/main/java/monkstone/vecmath/JRender.java +56 -0
  80. data/src/main/java/monkstone/vecmath/ShapeRender.java +87 -0
  81. data/src/main/java/monkstone/vecmath/package-info.java +20 -0
  82. data/src/main/java/monkstone/vecmath/vec2/Vec2.java +757 -0
  83. data/src/main/java/monkstone/vecmath/vec2/package-info.java +6 -0
  84. data/src/main/java/monkstone/vecmath/vec3/Vec3.java +727 -0
  85. data/src/main/java/monkstone/vecmath/vec3/package-info.java +6 -0
  86. data/src/main/java/monkstone/videoevent/VideoInterface.java +42 -0
  87. data/src/main/java/monkstone/videoevent/package-info.java +20 -0
  88. data/src/main/java/processing/awt/PGraphicsJava2D.java +3098 -0
  89. data/src/main/java/processing/awt/PShapeJava2D.java +401 -0
  90. data/src/main/java/processing/awt/PSurfaceAWT.java +1660 -0
  91. data/src/main/java/processing/core/PApplet.java +17647 -0
  92. data/src/main/java/processing/core/PConstants.java +1033 -0
  93. data/src/main/java/processing/core/PFont.java +1250 -0
  94. data/src/main/java/processing/core/PGraphics.java +9614 -0
  95. data/src/main/java/processing/core/PImage.java +3608 -0
  96. data/src/main/java/processing/core/PMatrix.java +347 -0
  97. data/src/main/java/processing/core/PMatrix2D.java +694 -0
  98. data/src/main/java/processing/core/PMatrix3D.java +1153 -0
  99. data/src/main/java/processing/core/PShape.java +4332 -0
  100. data/src/main/java/processing/core/PShapeOBJ.java +544 -0
  101. data/src/main/java/processing/core/PShapeSVG.java +1987 -0
  102. data/src/main/java/processing/core/PStyle.java +208 -0
  103. data/src/main/java/processing/core/PSurface.java +242 -0
  104. data/src/main/java/processing/core/PSurfaceNone.java +479 -0
  105. data/src/main/java/processing/core/PVector.java +1140 -0
  106. data/src/main/java/processing/data/FloatDict.java +829 -0
  107. data/src/main/java/processing/data/FloatList.java +912 -0
  108. data/src/main/java/processing/data/IntDict.java +796 -0
  109. data/src/main/java/processing/data/IntList.java +913 -0
  110. data/src/main/java/processing/data/JSONArray.java +1260 -0
  111. data/src/main/java/processing/data/JSONObject.java +2282 -0
  112. data/src/main/java/processing/data/JSONTokener.java +435 -0
  113. data/src/main/java/processing/data/Sort.java +46 -0
  114. data/src/main/java/processing/data/StringDict.java +601 -0
  115. data/src/main/java/processing/data/StringList.java +775 -0
  116. data/src/main/java/processing/data/Table.java +4923 -0
  117. data/src/main/java/processing/data/TableRow.java +198 -0
  118. data/src/main/java/processing/data/XML.java +1149 -0
  119. data/src/main/java/processing/event/Event.java +108 -0
  120. data/src/main/java/processing/event/KeyEvent.java +70 -0
  121. data/src/main/java/processing/event/MouseEvent.java +149 -0
  122. data/src/main/java/processing/event/TouchEvent.java +57 -0
  123. data/src/main/java/processing/javafx/PGraphicsFX2D.java +354 -0
  124. data/src/main/java/processing/opengl/FontTexture.java +379 -0
  125. data/src/main/java/processing/opengl/FrameBuffer.java +503 -0
  126. data/src/main/java/processing/opengl/LinePath.java +623 -0
  127. data/src/main/java/processing/opengl/LineStroker.java +685 -0
  128. data/src/main/java/processing/opengl/PGL.java +3366 -0
  129. data/src/main/java/processing/opengl/PGraphics2D.java +615 -0
  130. data/src/main/java/processing/opengl/PGraphics3D.java +281 -0
  131. data/src/main/java/processing/opengl/PGraphicsOpenGL.java +13634 -0
  132. data/src/main/java/processing/opengl/PJOGL.java +1966 -0
  133. data/src/main/java/processing/opengl/PShader.java +1478 -0
  134. data/src/main/java/processing/opengl/PShapeOpenGL.java +5234 -0
  135. data/src/main/java/processing/opengl/PSurfaceJOGL.java +1315 -0
  136. data/src/main/java/processing/opengl/Texture.java +1670 -0
  137. data/src/main/java/processing/opengl/VertexBuffer.java +88 -0
  138. data/src/main/java/processing/opengl/cursors/arrow.png +0 -0
  139. data/src/main/java/processing/opengl/cursors/cross.png +0 -0
  140. data/src/main/java/processing/opengl/cursors/hand.png +0 -0
  141. data/src/main/java/processing/opengl/cursors/license.txt +27 -0
  142. data/src/main/java/processing/opengl/cursors/move.png +0 -0
  143. data/src/main/java/processing/opengl/cursors/text.png +0 -0
  144. data/src/main/java/processing/opengl/cursors/wait.png +0 -0
  145. data/src/main/java/processing/opengl/shaders/ColorFrag.glsl +32 -0
  146. data/src/main/java/processing/opengl/shaders/ColorVert.glsl +34 -0
  147. data/src/main/java/processing/opengl/shaders/LightFrag.glsl +33 -0
  148. data/src/main/java/processing/opengl/shaders/LightVert-vc4.glsl +154 -0
  149. data/src/main/java/processing/opengl/shaders/LightVert.glsl +151 -0
  150. data/src/main/java/processing/opengl/shaders/LineFrag.glsl +32 -0
  151. data/src/main/java/processing/opengl/shaders/LineVert.glsl +100 -0
  152. data/src/main/java/processing/opengl/shaders/MaskFrag.glsl +40 -0
  153. data/src/main/java/processing/opengl/shaders/PointFrag.glsl +32 -0
  154. data/src/main/java/processing/opengl/shaders/PointVert.glsl +56 -0
  155. data/src/main/java/processing/opengl/shaders/TexFrag.glsl +37 -0
  156. data/src/main/java/processing/opengl/shaders/TexLightFrag.glsl +37 -0
  157. data/src/main/java/processing/opengl/shaders/TexLightVert-vc4.glsl +160 -0
  158. data/src/main/java/processing/opengl/shaders/TexLightVert.glsl +157 -0
  159. data/src/main/java/processing/opengl/shaders/TexVert.glsl +38 -0
  160. data/src/main/resources/icon/icon-1024.png +0 -0
  161. data/src/main/resources/icon/icon-128.png +0 -0
  162. data/src/main/resources/icon/icon-16.png +0 -0
  163. data/src/main/resources/icon/icon-256.png +0 -0
  164. data/src/main/resources/icon/icon-32.png +0 -0
  165. data/src/main/resources/icon/icon-48.png +0 -0
  166. data/src/main/resources/icon/icon-512.png +0 -0
  167. data/src/main/resources/icon/icon-64.png +0 -0
  168. data/src/main/resources/license.txt +508 -0
  169. data/test/create_test.rb +68 -0
  170. data/test/deglut_spec_test.rb +24 -0
  171. data/test/helper_methods_test.rb +58 -0
  172. data/test/math_tool_test.rb +75 -0
  173. data/test/respond_to_test.rb +215 -0
  174. data/test/sketches/key_event.rb +37 -0
  175. data/test/sketches/library/my_library/my_library.rb +32 -0
  176. data/test/test_helper.rb +3 -0
  177. data/test/vecmath_spec_test.rb +522 -0
  178. data/vendors/Rakefile +127 -0
  179. metadata +289 -0
@@ -0,0 +1,4923 @@
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(new Class[] { enclosingClass });
1066
+ // PApplet.println("enclosed by " + enclosingClass.getName());
1067
+ }
1068
+ if (!con.isAccessible()) {
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
+ // System.out.println("found field " + name);
1084
+ if (!field.isAccessible()) {
1085
+ // PApplet.println(" changing field access");
1086
+ field.setAccessible(true);
1087
+ }
1088
+ inuse.add(field);
1089
+ } else {
1090
+ // System.out.println("skipping field " + name);
1091
+ }
1092
+ }
1093
+
1094
+ int index = 0;
1095
+ try {
1096
+ for (TableRow row : rows()) {
1097
+ Object item = null;
1098
+ if (enclosingClass == null) {
1099
+ //item = target.newInstance();
1100
+ item = con.newInstance();
1101
+ } else {
1102
+ item = con.newInstance(new Object[] { enclosingObject });
1103
+ }
1104
+ //Object item = defaultCons.newInstance(new Object[] { });
1105
+ for (Field field : inuse) {
1106
+ String name = field.getName();
1107
+ //PApplet.println("gonna set field " + name);
1108
+
1109
+ if (field.getType() == String.class) {
1110
+ field.set(item, row.getString(name));
1111
+
1112
+ } else if (field.getType() == Integer.TYPE) {
1113
+ field.setInt(item, row.getInt(name));
1114
+
1115
+ } else if (field.getType() == Long.TYPE) {
1116
+ field.setLong(item, row.getLong(name));
1117
+
1118
+ } else if (field.getType() == Float.TYPE) {
1119
+ field.setFloat(item, row.getFloat(name));
1120
+
1121
+ } else if (field.getType() == Double.TYPE) {
1122
+ field.setDouble(item, row.getDouble(name));
1123
+
1124
+ } else if (field.getType() == Boolean.TYPE) {
1125
+ String content = row.getString(name);
1126
+ if (content != null) {
1127
+ // Only bother setting if it's true,
1128
+ // otherwise false by default anyway.
1129
+ if (content.toLowerCase().equals("true") ||
1130
+ content.equals("1")) {
1131
+ field.setBoolean(item, true);
1132
+ }
1133
+ }
1134
+ // if (content == null) {
1135
+ // field.setBoolean(item, false); // necessary?
1136
+ // } else if (content.toLowerCase().equals("true")) {
1137
+ // field.setBoolean(item, true);
1138
+ // } else if (content.equals("1")) {
1139
+ // field.setBoolean(item, true);
1140
+ // } else {
1141
+ // field.setBoolean(item, false); // necessary?
1142
+ // }
1143
+ } else if (field.getType() == Character.TYPE) {
1144
+ String content = row.getString(name);
1145
+ if (content != null && content.length() > 0) {
1146
+ // Otherwise set to \0 anyway
1147
+ field.setChar(item, content.charAt(0));
1148
+ }
1149
+ }
1150
+ }
1151
+ // list.add(item);
1152
+ Array.set(outgoing, index++, item);
1153
+ }
1154
+ if (!targetField.isAccessible()) {
1155
+ // PApplet.println("setting target field to public");
1156
+ targetField.setAccessible(true);
1157
+ }
1158
+ // Set the array in the sketch
1159
+ // targetField.set(sketch, outgoing);
1160
+ targetField.set(enclosingObject, outgoing);
1161
+
1162
+ } catch (InstantiationException e) {
1163
+ e.printStackTrace();
1164
+ } catch (IllegalAccessException e) {
1165
+ e.printStackTrace();
1166
+ } catch (IllegalArgumentException e) {
1167
+ e.printStackTrace();
1168
+ } catch (InvocationTargetException e) {
1169
+ e.printStackTrace();
1170
+ }
1171
+ }
1172
+
1173
+
1174
+ public boolean save(File file, String options) throws IOException {
1175
+ return save(PApplet.createOutput(file),
1176
+ Table.extensionOptions(false, file.getName(), options));
1177
+ }
1178
+
1179
+
1180
+ public boolean save(OutputStream output, String options) {
1181
+ PrintWriter writer = PApplet.createWriter(output);
1182
+ String extension = null;
1183
+ if (options == null) {
1184
+ throw new IllegalArgumentException("No extension specified for saving this Table");
1185
+ }
1186
+
1187
+ String[] opts = PApplet.trim(PApplet.split(options, ','));
1188
+ // Only option for save is the extension, so we can safely grab the last
1189
+ extension = opts[opts.length - 1];
1190
+ boolean found = false;
1191
+ for (String ext : saveExtensions) {
1192
+ if (extension.equals(ext)) {
1193
+ found = true;
1194
+ break;
1195
+ }
1196
+ }
1197
+ // Not providing a fallback; let's make users specify an extension
1198
+ if (!found) {
1199
+ throw new IllegalArgumentException("'" + extension + "' not available for Table");
1200
+ }
1201
+
1202
+ if (extension.equals("csv")) {
1203
+ writeCSV(writer);
1204
+ } else if (extension.equals("tsv")) {
1205
+ writeTSV(writer);
1206
+ } else if (extension.equals("ods")) {
1207
+ try {
1208
+ saveODS(output);
1209
+ } catch (IOException e) {
1210
+ e.printStackTrace();
1211
+ return false;
1212
+ }
1213
+ } else if (extension.equals("html")) {
1214
+ writeHTML(writer);
1215
+ } else if (extension.equals("bin")) {
1216
+ try {
1217
+ saveBinary(output);
1218
+ } catch (IOException e) {
1219
+ e.printStackTrace();
1220
+ return false;
1221
+ }
1222
+ }
1223
+ writer.flush();
1224
+ writer.close();
1225
+ return true;
1226
+ }
1227
+
1228
+
1229
+ protected void writeTSV(PrintWriter writer) {
1230
+ if (columnTitles != null) {
1231
+ for (int col = 0; col < columns.length; col++) {
1232
+ if (col != 0) {
1233
+ writer.print('\t');
1234
+ }
1235
+ if (columnTitles[col] != null) {
1236
+ writer.print(columnTitles[col]);
1237
+ }
1238
+ }
1239
+ writer.println();
1240
+ }
1241
+ for (int row = 0; row < rowCount; row++) {
1242
+ for (int col = 0; col < getColumnCount(); col++) {
1243
+ if (col != 0) {
1244
+ writer.print('\t');
1245
+ }
1246
+ String entry = getString(row, col);
1247
+ // just write null entries as blanks, rather than spewing 'null'
1248
+ // all over the spreadsheet file.
1249
+ if (entry != null) {
1250
+ writer.print(entry);
1251
+ }
1252
+ }
1253
+ writer.println();
1254
+ }
1255
+ writer.flush();
1256
+ }
1257
+
1258
+
1259
+ protected void writeCSV(PrintWriter writer) {
1260
+ if (columnTitles != null) {
1261
+ for (int col = 0; col < getColumnCount(); col++) {
1262
+ if (col != 0) {
1263
+ writer.print(',');
1264
+ }
1265
+ try {
1266
+ if (columnTitles[col] != null) { // col < columnTitles.length &&
1267
+ writeEntryCSV(writer, columnTitles[col]);
1268
+ }
1269
+ } catch (ArrayIndexOutOfBoundsException e) {
1270
+ PApplet.printArray(columnTitles);
1271
+ PApplet.printArray(columns);
1272
+ throw e;
1273
+ }
1274
+ }
1275
+ writer.println();
1276
+ }
1277
+ for (int row = 0; row < rowCount; row++) {
1278
+ for (int col = 0; col < getColumnCount(); col++) {
1279
+ if (col != 0) {
1280
+ writer.print(',');
1281
+ }
1282
+ String entry = getString(row, col);
1283
+ // just write null entries as blanks, rather than spewing 'null'
1284
+ // all over the spreadsheet file.
1285
+ if (entry != null) {
1286
+ writeEntryCSV(writer, entry);
1287
+ }
1288
+ }
1289
+ // Prints the newline for the row, even if it's missing
1290
+ writer.println();
1291
+ }
1292
+ writer.flush();
1293
+ }
1294
+
1295
+
1296
+ protected void writeEntryCSV(PrintWriter writer, String entry) {
1297
+ if (entry != null) {
1298
+ if (entry.indexOf('\"') != -1) { // convert quotes to double quotes
1299
+ char[] c = entry.toCharArray();
1300
+ writer.print('\"');
1301
+ for (int i = 0; i < c.length; i++) {
1302
+ if (c[i] == '\"') {
1303
+ writer.print("\"\"");
1304
+ } else {
1305
+ writer.print(c[i]);
1306
+ }
1307
+ }
1308
+ writer.print('\"');
1309
+
1310
+ // add quotes if commas or CR/LF are in the entry
1311
+ } else if (entry.indexOf(',') != -1 ||
1312
+ entry.indexOf('\n') != -1 ||
1313
+ entry.indexOf('\r') != -1) {
1314
+ writer.print('\"');
1315
+ writer.print(entry);
1316
+ writer.print('\"');
1317
+
1318
+
1319
+ // add quotes if leading or trailing space
1320
+ } else if ((entry.length() > 0) &&
1321
+ (entry.charAt(0) == ' ' ||
1322
+ entry.charAt(entry.length() - 1) == ' ')) {
1323
+ writer.print('\"');
1324
+ writer.print(entry);
1325
+ writer.print('\"');
1326
+
1327
+ } else {
1328
+ writer.print(entry);
1329
+ }
1330
+ }
1331
+ }
1332
+
1333
+
1334
+ protected void writeHTML(PrintWriter writer) {
1335
+ writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">");
1336
+ // writer.println("<!DOCTYPE html>");
1337
+ // writer.println("<meta charset=\"utf-8\">");
1338
+
1339
+ writer.println("<html>");
1340
+ writer.println("<head>");
1341
+ writer.println(" <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\" />");
1342
+ writer.println("</head>");
1343
+
1344
+ writer.println("<body>");
1345
+ writer.println(" <table>");
1346
+
1347
+ if (hasColumnTitles()) {
1348
+ writer.println(" <tr>");
1349
+ for (String entry : getColumnTitles()) {
1350
+ writer.print(" <th>");
1351
+ if (entry != null) {
1352
+ writeEntryHTML(writer, entry);
1353
+ }
1354
+ writer.println("</th>");
1355
+ }
1356
+ writer.println(" </tr>");
1357
+ }
1358
+
1359
+ for (int row = 0; row < getRowCount(); row++) {
1360
+ writer.println(" <tr>");
1361
+ for (int col = 0; col < getColumnCount(); col++) {
1362
+ String entry = getString(row, col);
1363
+ writer.print(" <td>");
1364
+ if (entry != null) {
1365
+ // probably not a great idea to mess w/ the export
1366
+ // if (entry.startsWith("<") && entry.endsWith(">")) {
1367
+ // writer.print(entry);
1368
+ // } else {
1369
+ writeEntryHTML(writer, entry);
1370
+ // }
1371
+ }
1372
+ writer.println("</td>");
1373
+ }
1374
+ writer.println(" </tr>");
1375
+ }
1376
+ writer.println(" </table>");
1377
+ writer.println("</body>");
1378
+
1379
+ writer.println("</html>");
1380
+ writer.flush();
1381
+ }
1382
+
1383
+
1384
+ protected void writeEntryHTML(PrintWriter writer, String entry) {
1385
+ //char[] chars = entry.toCharArray();
1386
+ for (char c : entry.toCharArray()) { //chars) {
1387
+ if (c == '<') {
1388
+ writer.print("&lt;");
1389
+ } else if (c == '>') {
1390
+ writer.print("&gt;");
1391
+ } else if (c == '&') {
1392
+ writer.print("&amp;");
1393
+ // } else if (c == '\'') { // only in XML
1394
+ // writer.print("&apos;");
1395
+ } else if (c == '"') {
1396
+ writer.print("&quot;");
1397
+
1398
+ } else if (c < 32 || c > 127) { // keep in ASCII or Tidy complains
1399
+ writer.print("&#");
1400
+ writer.print((int) c);
1401
+ writer.print(';');
1402
+
1403
+ } else {
1404
+ writer.print(c);
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+
1410
+ protected void saveODS(OutputStream os) throws IOException {
1411
+ ZipOutputStream zos = new ZipOutputStream(os);
1412
+
1413
+ final String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
1414
+
1415
+ ZipEntry entry = new ZipEntry("META-INF/manifest.xml");
1416
+ String[] lines = new String[] {
1417
+ xmlHeader,
1418
+ "<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">",
1419
+ " <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.spreadsheet\" manifest:version=\"1.2\" manifest:full-path=\"/\"/>",
1420
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>",
1421
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>",
1422
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>",
1423
+ " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"settings.xml\"/>",
1424
+ "</manifest:manifest>"
1425
+ };
1426
+ zos.putNextEntry(entry);
1427
+ zos.write(PApplet.join(lines, "\n").getBytes());
1428
+ zos.closeEntry();
1429
+
1430
+ /*
1431
+ entry = new ZipEntry("meta.xml");
1432
+ lines = new String[] {
1433
+ xmlHeader,
1434
+ "<office:document-meta office:version=\"1.0\"" +
1435
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1436
+ };
1437
+ zos.putNextEntry(entry);
1438
+ zos.write(PApplet.join(lines, "\n").getBytes());
1439
+ zos.closeEntry();
1440
+
1441
+ entry = new ZipEntry("meta.xml");
1442
+ lines = new String[] {
1443
+ xmlHeader,
1444
+ "<office:document-settings office:version=\"1.0\"" +
1445
+ " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" +
1446
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1447
+ " xmlns:ooo=\"http://openoffice.org/2004/office\"" +
1448
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />"
1449
+ };
1450
+ zos.putNextEntry(entry);
1451
+ zos.write(PApplet.join(lines, "\n").getBytes());
1452
+ zos.closeEntry();
1453
+
1454
+ entry = new ZipEntry("settings.xml");
1455
+ lines = new String[] {
1456
+ xmlHeader,
1457
+ "<office:document-settings office:version=\"1.0\"" +
1458
+ " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" +
1459
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1460
+ " xmlns:ooo=\"http://openoffice.org/2004/office\"" +
1461
+ " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />"
1462
+ };
1463
+ zos.putNextEntry(entry);
1464
+ zos.write(PApplet.join(lines, "\n").getBytes());
1465
+ zos.closeEntry();
1466
+
1467
+ entry = new ZipEntry("styles.xml");
1468
+ lines = new String[] {
1469
+ xmlHeader,
1470
+ "<office:document-styles office:version=\"1.0\"" +
1471
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1472
+ };
1473
+ zos.putNextEntry(entry);
1474
+ zos.write(PApplet.join(lines, "\n").getBytes());
1475
+ zos.closeEntry();
1476
+ */
1477
+
1478
+ final String[] dummyFiles = new String[] {
1479
+ "meta.xml", "settings.xml", "styles.xml"
1480
+ };
1481
+ lines = new String[] {
1482
+ xmlHeader,
1483
+ "<office:document-meta office:version=\"1.0\"" +
1484
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />"
1485
+ };
1486
+ byte[] dummyBytes = PApplet.join(lines, "\n").getBytes();
1487
+ for (String filename : dummyFiles) {
1488
+ entry = new ZipEntry(filename);
1489
+ zos.putNextEntry(entry);
1490
+ zos.write(dummyBytes);
1491
+ zos.closeEntry();
1492
+ }
1493
+
1494
+ //
1495
+
1496
+ entry = new ZipEntry("mimetype");
1497
+ zos.putNextEntry(entry);
1498
+ zos.write("application/vnd.oasis.opendocument.spreadsheet".getBytes());
1499
+ zos.closeEntry();
1500
+
1501
+ //
1502
+
1503
+ entry = new ZipEntry("content.xml");
1504
+ zos.putNextEntry(entry);
1505
+ //lines = new String[] {
1506
+ writeUTF(zos, new String[] {
1507
+ xmlHeader,
1508
+ "<office:document-content" +
1509
+ " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" +
1510
+ " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"" +
1511
+ " xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"" +
1512
+ " office:version=\"1.2\">",
1513
+ " <office:body>",
1514
+ " <office:spreadsheet>",
1515
+ " <table:table table:name=\"Sheet1\" table:print=\"false\">"
1516
+ });
1517
+ //zos.write(PApplet.join(lines, "\n").getBytes());
1518
+
1519
+ byte[] rowStart = " <table:table-row>\n".getBytes();
1520
+ byte[] rowStop = " </table:table-row>\n".getBytes();
1521
+
1522
+ if (hasColumnTitles()) {
1523
+ zos.write(rowStart);
1524
+ for (int i = 0; i < getColumnCount(); i++) {
1525
+ saveStringODS(zos, columnTitles[i]);
1526
+ }
1527
+ zos.write(rowStop);
1528
+ }
1529
+
1530
+ for (TableRow row : rows()) {
1531
+ zos.write(rowStart);
1532
+ for (int i = 0; i < getColumnCount(); i++) {
1533
+ if (columnTypes[i] == STRING || columnTypes[i] == CATEGORY) {
1534
+ saveStringODS(zos, row.getString(i));
1535
+ } else {
1536
+ saveNumberODS(zos, row.getString(i));
1537
+ }
1538
+ }
1539
+ zos.write(rowStop);
1540
+ }
1541
+
1542
+ //lines = new String[] {
1543
+ writeUTF(zos, new String[] {
1544
+ " </table:table>",
1545
+ " </office:spreadsheet>",
1546
+ " </office:body>",
1547
+ "</office:document-content>"
1548
+ });
1549
+ //zos.write(PApplet.join(lines, "\n").getBytes());
1550
+ zos.closeEntry();
1551
+
1552
+ zos.flush();
1553
+ zos.close();
1554
+ }
1555
+
1556
+
1557
+ void saveStringODS(OutputStream output, String text) throws IOException {
1558
+ // At this point, I should have just used the XML library. But this does
1559
+ // save us from having to create the entire document in memory again before
1560
+ // writing to the file. So while it's dorky, the outcome is still useful.
1561
+ StringBuilder sanitized = new StringBuilder();
1562
+ if (text != null) {
1563
+ char[] array = text.toCharArray();
1564
+ for (char c : array) {
1565
+ if (c == '&') {
1566
+ sanitized.append("&amp;");
1567
+ } else if (c == '\'') {
1568
+ sanitized.append("&apos;");
1569
+ } else if (c == '"') {
1570
+ sanitized.append("&quot;");
1571
+ } else if (c == '<') {
1572
+ sanitized.append("&lt;");
1573
+ } else if (c == '>') {
1574
+ sanitized.append("&rt;");
1575
+ } else if (c < 32 || c > 127) {
1576
+ sanitized.append("&#" + ((int) c) + ";");
1577
+ } else {
1578
+ sanitized.append(c);
1579
+ }
1580
+ }
1581
+ }
1582
+
1583
+ writeUTF(output,
1584
+ " <table:table-cell office:value-type=\"string\">",
1585
+ " <text:p>" + sanitized + "</text:p>",
1586
+ " </table:table-cell>");
1587
+ }
1588
+
1589
+
1590
+ void saveNumberODS(OutputStream output, String text) throws IOException {
1591
+ writeUTF(output,
1592
+ " <table:table-cell office:value-type=\"float\" office:value=\"" + text + "\">",
1593
+ " <text:p>" + text + "</text:p>",
1594
+ " </table:table-cell>");
1595
+ }
1596
+
1597
+
1598
+ static Charset utf8;
1599
+
1600
+ static void writeUTF(OutputStream output, String... lines) throws IOException {
1601
+ if (utf8 == null) {
1602
+ utf8 = Charset.forName("UTF-8");
1603
+ }
1604
+ for (String str : lines) {
1605
+ output.write(str.getBytes(utf8));
1606
+ output.write('\n');
1607
+ }
1608
+ }
1609
+
1610
+
1611
+ protected void saveBinary(OutputStream os) throws IOException {
1612
+ DataOutputStream output = new DataOutputStream(new BufferedOutputStream(os));
1613
+ output.writeInt(0x9007AB1E); // version
1614
+ output.writeInt(getRowCount());
1615
+ output.writeInt(getColumnCount());
1616
+ if (columnTitles != null) {
1617
+ output.writeBoolean(true);
1618
+ for (String title : columnTitles) {
1619
+ output.writeUTF(title);
1620
+ }
1621
+ } else {
1622
+ output.writeBoolean(false);
1623
+ }
1624
+ for (int i = 0; i < getColumnCount(); i++) {
1625
+ //System.out.println(i + " is " + columnTypes[i]);
1626
+ output.writeInt(columnTypes[i]);
1627
+ }
1628
+
1629
+ for (int i = 0; i < getColumnCount(); i++) {
1630
+ if (columnTypes[i] == CATEGORY) {
1631
+ columnCategories[i].write(output);
1632
+ }
1633
+ }
1634
+ if (missingString == null) {
1635
+ output.writeBoolean(false);
1636
+ } else {
1637
+ output.writeBoolean(true);
1638
+ output.writeUTF(missingString);
1639
+ }
1640
+ output.writeInt(missingInt);
1641
+ output.writeLong(missingLong);
1642
+ output.writeFloat(missingFloat);
1643
+ output.writeDouble(missingDouble);
1644
+ output.writeInt(missingCategory);
1645
+
1646
+ for (TableRow row : rows()) {
1647
+ for (int col = 0; col < getColumnCount(); col++) {
1648
+ switch (columnTypes[col]) {
1649
+ case STRING:
1650
+ String str = row.getString(col);
1651
+ if (str == null) {
1652
+ output.writeBoolean(false);
1653
+ } else {
1654
+ output.writeBoolean(true);
1655
+ output.writeUTF(str);
1656
+ }
1657
+ break;
1658
+ case INT:
1659
+ output.writeInt(row.getInt(col));
1660
+ break;
1661
+ case LONG:
1662
+ output.writeLong(row.getLong(col));
1663
+ break;
1664
+ case FLOAT:
1665
+ output.writeFloat(row.getFloat(col));
1666
+ break;
1667
+ case DOUBLE:
1668
+ output.writeDouble(row.getDouble(col));
1669
+ break;
1670
+ case CATEGORY:
1671
+ String peace = row.getString(col);
1672
+ if (peace.equals(missingString)) {
1673
+ output.writeInt(missingCategory);
1674
+ } else {
1675
+ output.writeInt(columnCategories[col].index(peace));
1676
+ }
1677
+ break;
1678
+ }
1679
+ }
1680
+ }
1681
+
1682
+ output.flush();
1683
+ output.close();
1684
+ }
1685
+
1686
+
1687
+ protected void loadBinary(InputStream is) throws IOException {
1688
+ DataInputStream input = new DataInputStream(new BufferedInputStream(is));
1689
+
1690
+ int magic = input.readInt();
1691
+ if (magic != 0x9007AB1E) {
1692
+ throw new IOException("Not a compatible binary table (magic was " + PApplet.hex(magic) + ")");
1693
+ }
1694
+ int rowCount = input.readInt();
1695
+ setRowCount(rowCount);
1696
+ int columnCount = input.readInt();
1697
+ setColumnCount(columnCount);
1698
+
1699
+ boolean hasTitles = input.readBoolean();
1700
+ if (hasTitles) {
1701
+ columnTitles = new String[getColumnCount()];
1702
+ for (int i = 0; i < columnCount; i++) {
1703
+ //columnTitles[i] = input.readUTF();
1704
+ setColumnTitle(i, input.readUTF());
1705
+ }
1706
+ }
1707
+ for (int column = 0; column < columnCount; column++) {
1708
+ int newType = input.readInt();
1709
+ columnTypes[column] = newType;
1710
+ switch (newType) {
1711
+ case INT:
1712
+ columns[column] = new int[rowCount];
1713
+ break;
1714
+ case LONG:
1715
+ columns[column] = new long[rowCount];;
1716
+ break;
1717
+ case FLOAT:
1718
+ columns[column] = new float[rowCount];;
1719
+ break;
1720
+ case DOUBLE:
1721
+ columns[column] = new double[rowCount];;
1722
+ break;
1723
+ case STRING:
1724
+ columns[column] = new String[rowCount];;
1725
+ break;
1726
+ case CATEGORY:
1727
+ columns[column] = new int[rowCount];;
1728
+ break;
1729
+ default:
1730
+ throw new IllegalArgumentException(newType + " is not a valid column type.");
1731
+ }
1732
+ }
1733
+
1734
+ for (int i = 0; i < columnCount; i++) {
1735
+ if (columnTypes[i] == CATEGORY) {
1736
+ columnCategories[i] = new HashMapBlows(input);
1737
+ }
1738
+ }
1739
+
1740
+ if (input.readBoolean()) {
1741
+ missingString = input.readUTF();
1742
+ } else {
1743
+ missingString = null;
1744
+ }
1745
+ missingInt = input.readInt();
1746
+ missingLong = input.readLong();
1747
+ missingFloat = input.readFloat();
1748
+ missingDouble = input.readDouble();
1749
+ missingCategory = input.readInt();
1750
+
1751
+ for (int row = 0; row < rowCount; row++) {
1752
+ for (int col = 0; col < columnCount; col++) {
1753
+ switch (columnTypes[col]) {
1754
+ case STRING:
1755
+ String str = null;
1756
+ if (input.readBoolean()) {
1757
+ str = input.readUTF();
1758
+ }
1759
+ setString(row, col, str);
1760
+ break;
1761
+ case INT:
1762
+ setInt(row, col, input.readInt());
1763
+ break;
1764
+ case LONG:
1765
+ setLong(row, col, input.readLong());
1766
+ break;
1767
+ case FLOAT:
1768
+ setFloat(row, col, input.readFloat());
1769
+ break;
1770
+ case DOUBLE:
1771
+ setDouble(row, col, input.readDouble());
1772
+ break;
1773
+ case CATEGORY:
1774
+ int index = input.readInt();
1775
+ //String name = columnCategories[col].key(index);
1776
+ setInt(row, col, index);
1777
+ break;
1778
+ }
1779
+ }
1780
+ }
1781
+
1782
+ input.close();
1783
+ }
1784
+
1785
+
1786
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1787
+
1788
+
1789
+ /**
1790
+ * @webref table:method
1791
+ * @brief Adds a new column to a table
1792
+ * @see Table#removeColumn(String)
1793
+ */
1794
+ public void addColumn() {
1795
+ addColumn(null, STRING);
1796
+ }
1797
+
1798
+
1799
+ /**
1800
+ * @param title the title to be used for the new column
1801
+ */
1802
+ public void addColumn(String title) {
1803
+ addColumn(title, STRING);
1804
+ }
1805
+
1806
+
1807
+ /**
1808
+ * @param type the type to be used for the new column: INT, LONG, FLOAT, DOUBLE, or STRING
1809
+ */
1810
+ public void addColumn(String title, int type) {
1811
+ insertColumn(columns.length, title, type);
1812
+ }
1813
+
1814
+
1815
+ public void insertColumn(int index) {
1816
+ insertColumn(index, null, STRING);
1817
+ }
1818
+
1819
+
1820
+ public void insertColumn(int index, String title) {
1821
+ insertColumn(index, title, STRING);
1822
+ }
1823
+
1824
+
1825
+ public void insertColumn(int index, String title, int type) {
1826
+ if (title != null && columnTitles == null) {
1827
+ columnTitles = new String[columns.length];
1828
+ }
1829
+ if (columnTitles != null) {
1830
+ columnTitles = PApplet.splice(columnTitles, title, index);
1831
+ columnIndices = null;
1832
+ }
1833
+ columnTypes = PApplet.splice(columnTypes, type, index);
1834
+
1835
+ // columnCategories = (HashMapBlows[])
1836
+ // PApplet.splice(columnCategories, new HashMapBlows(), index);
1837
+ HashMapBlows[] catTemp = new HashMapBlows[columns.length + 1];
1838
+ // Faster than arrayCopy for a dozen or so entries
1839
+ for (int i = 0; i < index; i++) {
1840
+ catTemp[i] = columnCategories[i];
1841
+ }
1842
+ catTemp[index] = new HashMapBlows();
1843
+ for (int i = index; i < columns.length; i++) {
1844
+ catTemp[i+1] = columnCategories[i];
1845
+ }
1846
+ columnCategories = catTemp;
1847
+
1848
+ Object[] temp = new Object[columns.length + 1];
1849
+ System.arraycopy(columns, 0, temp, 0, index);
1850
+ System.arraycopy(columns, index, temp, index+1, columns.length - index);
1851
+ columns = temp;
1852
+
1853
+ switch (type) {
1854
+ case INT: columns[index] = new int[rowCount]; break;
1855
+ case LONG: columns[index] = new long[rowCount]; break;
1856
+ case FLOAT: columns[index] = new float[rowCount]; break;
1857
+ case DOUBLE: columns[index] = new double[rowCount]; break;
1858
+ case STRING: columns[index] = new String[rowCount]; break;
1859
+ case CATEGORY: columns[index] = new int[rowCount]; break;
1860
+ }
1861
+ }
1862
+
1863
+ /**
1864
+ * @webref table:method
1865
+ * @brief Removes a column from a table
1866
+ * @param columnName the title of the column to be removed
1867
+ * @see Table#addColumn()
1868
+ */
1869
+ public void removeColumn(String columnName) {
1870
+ removeColumn(getColumnIndex(columnName));
1871
+ }
1872
+
1873
+ /**
1874
+ * @param column the index number of the column to be removed
1875
+ */
1876
+ public void removeColumn(int column) {
1877
+ int newCount = columns.length - 1;
1878
+
1879
+ Object[] columnsTemp = new Object[newCount];
1880
+ HashMapBlows[] catTemp = new HashMapBlows[newCount];
1881
+
1882
+ for (int i = 0; i < column; i++) {
1883
+ columnsTemp[i] = columns[i];
1884
+ catTemp[i] = columnCategories[i];
1885
+ }
1886
+ for (int i = column; i < newCount; i++) {
1887
+ columnsTemp[i] = columns[i+1];
1888
+ catTemp[i] = columnCategories[i+1];
1889
+ }
1890
+
1891
+ columns = columnsTemp;
1892
+ columnCategories = catTemp;
1893
+
1894
+ if (columnTitles != null) {
1895
+ String[] titlesTemp = new String[newCount];
1896
+ for (int i = 0; i < column; i++) {
1897
+ titlesTemp[i] = columnTitles[i];
1898
+ }
1899
+ for (int i = column; i < newCount; i++) {
1900
+ titlesTemp[i] = columnTitles[i+1];
1901
+ }
1902
+ columnTitles = titlesTemp;
1903
+ columnIndices = null;
1904
+ }
1905
+ }
1906
+
1907
+
1908
+ /**
1909
+ * @webref table:method
1910
+ * @brief Gets the number of columns in a table
1911
+ * @see Table#getRowCount()
1912
+ */
1913
+ public int getColumnCount() {
1914
+ return columns.length;
1915
+ }
1916
+
1917
+
1918
+ /**
1919
+ * Change the number of columns in this table. Resizes all rows to ensure
1920
+ * the same number of columns in each row. Entries in the additional (empty)
1921
+ * columns will be set to null.
1922
+ * @param newCount
1923
+ */
1924
+ public void setColumnCount(int newCount) {
1925
+ int oldCount = columns.length;
1926
+ if (oldCount != newCount) {
1927
+ columns = (Object[]) PApplet.expand(columns, newCount);
1928
+ // create new columns, default to String as the data type
1929
+ for (int c = oldCount; c < newCount; c++) {
1930
+ columns[c] = new String[rowCount];
1931
+ }
1932
+
1933
+ if (columnTitles != null) {
1934
+ columnTitles = PApplet.expand(columnTitles, newCount);
1935
+ }
1936
+ columnTypes = PApplet.expand(columnTypes, newCount);
1937
+ columnCategories = (HashMapBlows[])
1938
+ PApplet.expand(columnCategories, newCount);
1939
+ }
1940
+ }
1941
+
1942
+
1943
+ public void setColumnType(String columnName, String columnType) {
1944
+ setColumnType(checkColumnIndex(columnName), columnType);
1945
+ }
1946
+
1947
+
1948
+ static int parseColumnType(String columnType) {
1949
+ columnType = columnType.toLowerCase();
1950
+ int type = -1;
1951
+ if (columnType.equals("string")) {
1952
+ type = STRING;
1953
+ } else if (columnType.equals("int")) {
1954
+ type = INT;
1955
+ } else if (columnType.equals("long")) {
1956
+ type = LONG;
1957
+ } else if (columnType.equals("float")) {
1958
+ type = FLOAT;
1959
+ } else if (columnType.equals("double")) {
1960
+ type = DOUBLE;
1961
+ } else if (columnType.equals("category")) {
1962
+ type = CATEGORY;
1963
+ } else {
1964
+ throw new IllegalArgumentException("'" + columnType + "' is not a valid column type.");
1965
+ }
1966
+ return type;
1967
+ }
1968
+
1969
+
1970
+ /**
1971
+ * Set the data type for a column so that using it is more efficient.
1972
+ * @param column the column to change
1973
+ * @param columnType One of int, long, float, double, string, or category.
1974
+ */
1975
+ public void setColumnType(int column, String columnType) {
1976
+ setColumnType(column, parseColumnType(columnType));
1977
+ }
1978
+
1979
+
1980
+ public void setColumnType(String columnName, int newType) {
1981
+ setColumnType(checkColumnIndex(columnName), newType);
1982
+ }
1983
+
1984
+
1985
+ /**
1986
+ * Sets the column type. If data already exists, then it'll be converted to
1987
+ * the new type.
1988
+ * @param column the column whose type should be changed
1989
+ * @param newType something fresh, maybe try an int or a float for size?
1990
+ */
1991
+ public void setColumnType(int column, int newType) {
1992
+ switch (newType) {
1993
+ case INT: {
1994
+ int[] intData = new int[rowCount];
1995
+ for (int row = 0; row < rowCount; row++) {
1996
+ String s = getString(row, column);
1997
+ intData[row] = (s == null) ? missingInt : PApplet.parseInt(s, missingInt);
1998
+ }
1999
+ columns[column] = intData;
2000
+ break;
2001
+ }
2002
+ case LONG: {
2003
+ long[] longData = new long[rowCount];
2004
+ for (int row = 0; row < rowCount; row++) {
2005
+ String s = getString(row, column);
2006
+ try {
2007
+ longData[row] = (s == null) ? missingLong : Long.parseLong(s);
2008
+ } catch (NumberFormatException nfe) {
2009
+ longData[row] = missingLong;
2010
+ }
2011
+ }
2012
+ columns[column] = longData;
2013
+ break;
2014
+ }
2015
+ case FLOAT: {
2016
+ float[] floatData = new float[rowCount];
2017
+ for (int row = 0; row < rowCount; row++) {
2018
+ String s = getString(row, column);
2019
+ floatData[row] = (s == null) ? missingFloat : PApplet.parseFloat(s, missingFloat);
2020
+ }
2021
+ columns[column] = floatData;
2022
+ break;
2023
+ }
2024
+ case DOUBLE: {
2025
+ double[] doubleData = new double[rowCount];
2026
+ for (int row = 0; row < rowCount; row++) {
2027
+ String s = getString(row, column);
2028
+ try {
2029
+ doubleData[row] = (s == null) ? missingDouble : Double.parseDouble(s);
2030
+ } catch (NumberFormatException nfe) {
2031
+ doubleData[row] = missingDouble;
2032
+ }
2033
+ }
2034
+ columns[column] = doubleData;
2035
+ break;
2036
+ }
2037
+ case STRING: {
2038
+ if (columnTypes[column] != STRING) {
2039
+ String[] stringData = new String[rowCount];
2040
+ for (int row = 0; row < rowCount; row++) {
2041
+ stringData[row] = getString(row, column);
2042
+ }
2043
+ columns[column] = stringData;
2044
+ }
2045
+ break;
2046
+ }
2047
+ case CATEGORY: {
2048
+ int[] indexData = new int[rowCount];
2049
+ HashMapBlows categories = new HashMapBlows();
2050
+ for (int row = 0; row < rowCount; row++) {
2051
+ String s = getString(row, column);
2052
+ indexData[row] = categories.index(s);
2053
+ }
2054
+ columnCategories[column] = categories;
2055
+ columns[column] = indexData;
2056
+ break;
2057
+ }
2058
+ default: {
2059
+ throw new IllegalArgumentException("That's not a valid column type.");
2060
+ }
2061
+ }
2062
+ // System.out.println("new type is " + newType);
2063
+ columnTypes[column] = newType;
2064
+ }
2065
+
2066
+
2067
+ /**
2068
+ * Set the entire table to a specific data type.
2069
+ */
2070
+ public void setTableType(String type) {
2071
+ for (int col = 0; col < getColumnCount(); col++) {
2072
+ setColumnType(col, type);
2073
+ }
2074
+ }
2075
+
2076
+
2077
+ public void setColumnTypes(int[] types) {
2078
+ ensureColumn(types.length - 1);
2079
+ for (int col = 0; col < types.length; col++) {
2080
+ setColumnType(col, types[col]);
2081
+ }
2082
+ }
2083
+
2084
+
2085
+ /**
2086
+ * Set the titles (and if a second column is present) the data types for
2087
+ * this table based on a file loaded separately. This will look for the
2088
+ * title in column 0, and the type in column 1. Better yet, specify a
2089
+ * column named "title" and another named "type" in the dictionary table
2090
+ * to future-proof the code.
2091
+ * @param dictionary
2092
+ */
2093
+ public void setColumnTypes(final Table dictionary) {
2094
+ ensureColumn(dictionary.getRowCount() - 1);
2095
+ int titleCol = 0;
2096
+ int typeCol = 1;
2097
+ if (dictionary.hasColumnTitles()) {
2098
+ titleCol = dictionary.getColumnIndex("title", true);
2099
+ typeCol = dictionary.getColumnIndex("type", true);
2100
+ }
2101
+ setColumnTitles(dictionary.getStringColumn(titleCol));
2102
+ final String[] typeNames = dictionary.getStringColumn(typeCol);
2103
+
2104
+ if (dictionary.getColumnCount() > 1) {
2105
+ if (getRowCount() > 1000) {
2106
+ int proc = Runtime.getRuntime().availableProcessors();
2107
+ ExecutorService pool = Executors.newFixedThreadPool(proc/2);
2108
+ for (int i = 0; i < dictionary.getRowCount(); i++) {
2109
+ final int col = i;
2110
+ pool.execute(new Runnable() {
2111
+ public void run() {
2112
+ setColumnType(col, typeNames[col]);
2113
+ }
2114
+ });
2115
+ }
2116
+ pool.shutdown();
2117
+ while (!pool.isTerminated()) {
2118
+ Thread.yield();
2119
+ }
2120
+
2121
+ } else {
2122
+ for (int col = 0; col < dictionary.getRowCount(); col++) {
2123
+ // setColumnType(i, dictionary.getString(i, typeCol));
2124
+ setColumnType(col, typeNames[col]);
2125
+ }
2126
+ }
2127
+ }
2128
+ }
2129
+
2130
+
2131
+ public int getColumnType(String columnName) {
2132
+ return getColumnType(getColumnIndex(columnName));
2133
+ }
2134
+
2135
+
2136
+ /** Returns one of Table.STRING, Table.INT, etc... */
2137
+ public int getColumnType(int column) {
2138
+ return columnTypes[column];
2139
+ }
2140
+
2141
+
2142
+ public int[] getColumnTypes() {
2143
+ return columnTypes;
2144
+ }
2145
+
2146
+
2147
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2148
+
2149
+
2150
+ /**
2151
+ * Remove the first row from the data set, and use it as the column titles.
2152
+ * Use loadTable("table.csv", "header") instead.
2153
+ */
2154
+ @Deprecated
2155
+ public String[] removeTitleRow() {
2156
+ String[] titles = getStringRow(0);
2157
+ removeRow(0);
2158
+ setColumnTitles(titles);
2159
+ return titles;
2160
+ }
2161
+
2162
+
2163
+ public void setColumnTitles(String[] titles) {
2164
+ if (titles != null) {
2165
+ ensureColumn(titles.length - 1);
2166
+ }
2167
+ columnTitles = titles;
2168
+ columnIndices = null; // remove the cache
2169
+ }
2170
+
2171
+
2172
+ public void setColumnTitle(int column, String title) {
2173
+ ensureColumn(column);
2174
+ if (columnTitles == null) {
2175
+ columnTitles = new String[getColumnCount()];
2176
+ }
2177
+ columnTitles[column] = title;
2178
+ columnIndices = null; // reset these fellas
2179
+ }
2180
+
2181
+
2182
+ public boolean hasColumnTitles() {
2183
+ return columnTitles != null;
2184
+ }
2185
+
2186
+
2187
+ public String[] getColumnTitles() {
2188
+ return columnTitles;
2189
+ }
2190
+
2191
+
2192
+ public String getColumnTitle(int col) {
2193
+ return (columnTitles == null) ? null : columnTitles[col];
2194
+ }
2195
+
2196
+
2197
+ public int getColumnIndex(String columnName) {
2198
+ return getColumnIndex(columnName, true);
2199
+ }
2200
+
2201
+
2202
+ /**
2203
+ * Get the index of a column.
2204
+ * @param name Name of the column.
2205
+ * @param report Whether to throw an exception if the column wasn't found.
2206
+ * @return index of the found column, or -1 if not found.
2207
+ */
2208
+ protected int getColumnIndex(String name, boolean report) {
2209
+ if (columnTitles == null) {
2210
+ if (report) {
2211
+ throw new IllegalArgumentException("This table has no header, so no column titles are set.");
2212
+ }
2213
+ return -1;
2214
+ }
2215
+ // only create this on first get(). subsequent calls to set the title will
2216
+ // also update this array, but only if it exists.
2217
+ if (columnIndices == null) {
2218
+ columnIndices = new HashMap<>();
2219
+ for (int col = 0; col < columns.length; col++) {
2220
+ columnIndices.put(columnTitles[col], col);
2221
+ }
2222
+ }
2223
+ Integer index = columnIndices.get(name);
2224
+ if (index == null) {
2225
+ if (report) {
2226
+ // Throws an exception here because the name is known and therefore most useful.
2227
+ // (Rather than waiting for it to fail inside, say, getInt())
2228
+ throw new IllegalArgumentException("This table has no column named '" + name + "'");
2229
+ }
2230
+ return -1;
2231
+ }
2232
+ return index.intValue();
2233
+ }
2234
+
2235
+
2236
+ /**
2237
+ * Same as getColumnIndex(), but creates the column if it doesn't exist.
2238
+ * Named this way to not conflict with checkColumn(), an internal function
2239
+ * used to ensure that a columns exists, and also to denote that it returns
2240
+ * an int for the column index.
2241
+ * @param title column title
2242
+ * @return index of a new or previously existing column
2243
+ */
2244
+ public int checkColumnIndex(String title) {
2245
+ int index = getColumnIndex(title, false);
2246
+ if (index != -1) {
2247
+ return index;
2248
+ }
2249
+ addColumn(title);
2250
+ return getColumnCount() - 1;
2251
+ }
2252
+
2253
+
2254
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2255
+
2256
+ /**
2257
+ * @webref table:method
2258
+ * @brief Gets the number of rows in a table
2259
+ * @see Table#getColumnCount()
2260
+ */
2261
+ public int getRowCount() {
2262
+ return rowCount;
2263
+ }
2264
+
2265
+
2266
+ public int lastRowIndex() {
2267
+ return getRowCount() - 1;
2268
+ }
2269
+
2270
+
2271
+ /**
2272
+ * @webref table:method
2273
+ * @brief Removes all rows from a table
2274
+ * @see Table#addRow()
2275
+ * @see Table#removeRow(int)
2276
+ */
2277
+ public void clearRows() {
2278
+ setRowCount(0);
2279
+ }
2280
+
2281
+
2282
+ public void setRowCount(int newCount) {
2283
+ if (newCount != rowCount) {
2284
+ if (newCount > 1000000) {
2285
+ System.out.print("Note: setting maximum row count to " + PApplet.nfc(newCount));
2286
+ }
2287
+ long t = System.currentTimeMillis();
2288
+ for (int col = 0; col < columns.length; col++) {
2289
+ switch (columnTypes[col]) {
2290
+ case INT: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
2291
+ case LONG: columns[col] = PApplet.expand((long[]) columns[col], newCount); break;
2292
+ case FLOAT: columns[col] = PApplet.expand((float[]) columns[col], newCount); break;
2293
+ case DOUBLE: columns[col] = PApplet.expand((double[]) columns[col], newCount); break;
2294
+ case STRING: columns[col] = PApplet.expand((String[]) columns[col], newCount); break;
2295
+ case CATEGORY: columns[col] = PApplet.expand((int[]) columns[col], newCount); break;
2296
+ }
2297
+ if (newCount > 1000000) {
2298
+ try {
2299
+ Thread.sleep(10); // gc time!
2300
+ } catch (InterruptedException e) {
2301
+ e.printStackTrace();
2302
+ }
2303
+ }
2304
+ }
2305
+ if (newCount > 1000000) {
2306
+ int ms = (int) (System.currentTimeMillis() - t);
2307
+ System.out.println(" (resize took " + PApplet.nfc(ms) + " ms)");
2308
+ }
2309
+ }
2310
+ rowCount = newCount;
2311
+ }
2312
+
2313
+
2314
+ /**
2315
+ * @webref table:method
2316
+ * @brief Adds a row to a table
2317
+ * @see Table#removeRow(int)
2318
+ * @see Table#clearRows()
2319
+ */
2320
+ public TableRow addRow() {
2321
+ //if (rowIncrement == 0) {
2322
+ setRowCount(rowCount + 1);
2323
+ return new RowPointer(this, rowCount - 1);
2324
+ }
2325
+
2326
+
2327
+ /**
2328
+ * @param source a reference to the original row to be duplicated
2329
+ */
2330
+ public TableRow addRow(TableRow source) {
2331
+ return setRow(rowCount, source);
2332
+ }
2333
+
2334
+
2335
+ public TableRow setRow(int row, TableRow source) {
2336
+ // Make sure there are enough columns to add this data
2337
+ ensureBounds(row, source.getColumnCount() - 1);
2338
+
2339
+ for (int col = 0; col < Math.min(source.getColumnCount(), columns.length); col++) {
2340
+ switch (columnTypes[col]) {
2341
+ case INT:
2342
+ setInt(row, col, source.getInt(col));
2343
+ break;
2344
+ case LONG:
2345
+ setLong(row, col, source.getLong(col));
2346
+ break;
2347
+ case FLOAT:
2348
+ setFloat(row, col, source.getFloat(col));
2349
+ break;
2350
+ case DOUBLE:
2351
+ setDouble(row, col, source.getDouble(col));
2352
+ break;
2353
+ case STRING:
2354
+ setString(row, col, source.getString(col));
2355
+ break;
2356
+ case CATEGORY:
2357
+ int index = source.getInt(col);
2358
+ setInt(row, col, index);
2359
+ if (!columnCategories[col].hasCategory(index)) {
2360
+ columnCategories[col].setCategory(index, source.getString(col));
2361
+ }
2362
+ break;
2363
+
2364
+ default:
2365
+ throw new RuntimeException("no types");
2366
+ }
2367
+ }
2368
+ return new RowPointer(this, row);
2369
+ }
2370
+
2371
+
2372
+ /**
2373
+ * @nowebref
2374
+ */
2375
+ public TableRow addRow(Object[] columnData) {
2376
+ setRow(getRowCount(), columnData);
2377
+ return new RowPointer(this, rowCount - 1);
2378
+ }
2379
+
2380
+
2381
+ public void addRows(Table source) {
2382
+ int index = getRowCount();
2383
+ setRowCount(index + source.getRowCount());
2384
+ for (TableRow row : source.rows()) {
2385
+ setRow(index++, row);
2386
+ }
2387
+ }
2388
+
2389
+
2390
+ public void insertRow(int insert, Object[] columnData) {
2391
+ for (int col = 0; col < columns.length; col++) {
2392
+ switch (columnTypes[col]) {
2393
+ case CATEGORY:
2394
+ case INT: {
2395
+ int[] intTemp = new int[rowCount+1];
2396
+ System.arraycopy(columns[col], 0, intTemp, 0, insert);
2397
+ System.arraycopy(columns[col], insert, intTemp, insert+1, rowCount - insert);
2398
+ columns[col] = intTemp;
2399
+ break;
2400
+ }
2401
+ case LONG: {
2402
+ long[] longTemp = new long[rowCount+1];
2403
+ System.arraycopy(columns[col], 0, longTemp, 0, insert);
2404
+ System.arraycopy(columns[col], insert, longTemp, insert+1, rowCount - insert);
2405
+ columns[col] = longTemp;
2406
+ break;
2407
+ }
2408
+ case FLOAT: {
2409
+ float[] floatTemp = new float[rowCount+1];
2410
+ System.arraycopy(columns[col], 0, floatTemp, 0, insert);
2411
+ System.arraycopy(columns[col], insert, floatTemp, insert+1, rowCount - insert);
2412
+ columns[col] = floatTemp;
2413
+ break;
2414
+ }
2415
+ case DOUBLE: {
2416
+ double[] doubleTemp = new double[rowCount+1];
2417
+ System.arraycopy(columns[col], 0, doubleTemp, 0, insert);
2418
+ System.arraycopy(columns[col], insert, doubleTemp, insert+1, rowCount - insert);
2419
+ columns[col] = doubleTemp;
2420
+ break;
2421
+ }
2422
+ case STRING: {
2423
+ String[] stringTemp = new String[rowCount+1];
2424
+ System.arraycopy(columns[col], 0, stringTemp, 0, insert);
2425
+ System.arraycopy(columns[col], insert, stringTemp, insert+1, rowCount - insert);
2426
+ columns[col] = stringTemp;
2427
+ break;
2428
+ }
2429
+ }
2430
+ }
2431
+ // Need to increment before setRow(), because it calls ensureBounds()
2432
+ // https://github.com/processing/processing/issues/5406
2433
+ ++rowCount;
2434
+ setRow(insert, columnData);
2435
+ }
2436
+
2437
+
2438
+ /**
2439
+ * @webref table:method
2440
+ * @brief Removes a row from a table
2441
+ * @param row ID number of the row to remove
2442
+ * @see Table#addRow()
2443
+ * @see Table#clearRows()
2444
+ */
2445
+ public void removeRow(int row) {
2446
+ for (int col = 0; col < columns.length; col++) {
2447
+ switch (columnTypes[col]) {
2448
+ case CATEGORY:
2449
+ case INT: {
2450
+ int[] intTemp = new int[rowCount-1];
2451
+ // int[] intData = (int[]) columns[col];
2452
+ // System.arraycopy(intData, 0, intTemp, 0, dead);
2453
+ // System.arraycopy(intData, dead+1, intTemp, dead, (rowCount - dead) + 1);
2454
+ System.arraycopy(columns[col], 0, intTemp, 0, row);
2455
+ System.arraycopy(columns[col], row+1, intTemp, row, (rowCount - row) - 1);
2456
+ columns[col] = intTemp;
2457
+ break;
2458
+ }
2459
+ case LONG: {
2460
+ long[] longTemp = new long[rowCount-1];
2461
+ // long[] longData = (long[]) columns[col];
2462
+ // System.arraycopy(longData, 0, longTemp, 0, dead);
2463
+ // System.arraycopy(longData, dead+1, longTemp, dead, (rowCount - dead) + 1);
2464
+ System.arraycopy(columns[col], 0, longTemp, 0, row);
2465
+ System.arraycopy(columns[col], row+1, longTemp, row, (rowCount - row) - 1);
2466
+ columns[col] = longTemp;
2467
+ break;
2468
+ }
2469
+ case FLOAT: {
2470
+ float[] floatTemp = new float[rowCount-1];
2471
+ // float[] floatData = (float[]) columns[col];
2472
+ // System.arraycopy(floatData, 0, floatTemp, 0, dead);
2473
+ // System.arraycopy(floatData, dead+1, floatTemp, dead, (rowCount - dead) + 1);
2474
+ System.arraycopy(columns[col], 0, floatTemp, 0, row);
2475
+ System.arraycopy(columns[col], row+1, floatTemp, row, (rowCount - row) - 1);
2476
+ columns[col] = floatTemp;
2477
+ break;
2478
+ }
2479
+ case DOUBLE: {
2480
+ double[] doubleTemp = new double[rowCount-1];
2481
+ // double[] doubleData = (double[]) columns[col];
2482
+ // System.arraycopy(doubleData, 0, doubleTemp, 0, dead);
2483
+ // System.arraycopy(doubleData, dead+1, doubleTemp, dead, (rowCount - dead) + 1);
2484
+ System.arraycopy(columns[col], 0, doubleTemp, 0, row);
2485
+ System.arraycopy(columns[col], row+1, doubleTemp, row, (rowCount - row) - 1);
2486
+ columns[col] = doubleTemp;
2487
+ break;
2488
+ }
2489
+ case STRING: {
2490
+ String[] stringTemp = new String[rowCount-1];
2491
+ System.arraycopy(columns[col], 0, stringTemp, 0, row);
2492
+ System.arraycopy(columns[col], row+1, stringTemp, row, (rowCount - row) - 1);
2493
+ columns[col] = stringTemp;
2494
+ }
2495
+ }
2496
+ }
2497
+ rowCount--;
2498
+ }
2499
+
2500
+
2501
+ /*
2502
+ public void setRow(int row, String[] pieces) {
2503
+ checkSize(row, pieces.length - 1);
2504
+ // pieces.length may be less than columns.length, so loop over pieces
2505
+ for (int col = 0; col < pieces.length; col++) {
2506
+ setRowCol(row, col, pieces[col]);
2507
+ }
2508
+ }
2509
+
2510
+
2511
+ protected void setRowCol(int row, int col, String piece) {
2512
+ switch (columnTypes[col]) {
2513
+ case STRING:
2514
+ String[] stringData = (String[]) columns[col];
2515
+ stringData[row] = piece;
2516
+ break;
2517
+ case INT:
2518
+ int[] intData = (int[]) columns[col];
2519
+ intData[row] = PApplet.parseInt(piece, missingInt);
2520
+ break;
2521
+ case LONG:
2522
+ long[] longData = (long[]) columns[col];
2523
+ try {
2524
+ longData[row] = Long.parseLong(piece);
2525
+ } catch (NumberFormatException nfe) {
2526
+ longData[row] = missingLong;
2527
+ }
2528
+ break;
2529
+ case FLOAT:
2530
+ float[] floatData = (float[]) columns[col];
2531
+ floatData[row] = PApplet.parseFloat(piece, missingFloat);
2532
+ break;
2533
+ case DOUBLE:
2534
+ double[] doubleData = (double[]) columns[col];
2535
+ try {
2536
+ doubleData[row] = Double.parseDouble(piece);
2537
+ } catch (NumberFormatException nfe) {
2538
+ doubleData[row] = missingDouble;
2539
+ }
2540
+ break;
2541
+ case CATEGORY:
2542
+ int[] indexData = (int[]) columns[col];
2543
+ indexData[row] = columnCategories[col].index(piece);
2544
+ break;
2545
+ default:
2546
+ throw new IllegalArgumentException("That's not a valid column type.");
2547
+ }
2548
+ }
2549
+ */
2550
+
2551
+
2552
+ public void setRow(int row, Object[] pieces) {
2553
+ ensureBounds(row, pieces.length - 1);
2554
+ // pieces.length may be less than columns.length, so loop over pieces
2555
+ for (int col = 0; col < pieces.length; col++) {
2556
+ setRowCol(row, col, pieces[col]);
2557
+ }
2558
+ }
2559
+
2560
+
2561
+ protected void setRowCol(int row, int col, Object piece) {
2562
+ switch (columnTypes[col]) {
2563
+ case STRING:
2564
+ String[] stringData = (String[]) columns[col];
2565
+ if (piece == null) {
2566
+ stringData[row] = null;
2567
+ // } else if (piece instanceof String) {
2568
+ // stringData[row] = (String) piece;
2569
+ } else {
2570
+ // Calls toString() on the object, which is 'return this' for String
2571
+ stringData[row] = String.valueOf(piece);
2572
+ }
2573
+ break;
2574
+ case INT:
2575
+ int[] intData = (int[]) columns[col];
2576
+ //intData[row] = PApplet.parseInt(piece, missingInt);
2577
+ if (piece == null) {
2578
+ intData[row] = missingInt;
2579
+ } else if (piece instanceof Integer) {
2580
+ intData[row] = (Integer) piece;
2581
+ } else {
2582
+ intData[row] = PApplet.parseInt(String.valueOf(piece), missingInt);
2583
+ }
2584
+ break;
2585
+ case LONG:
2586
+ long[] longData = (long[]) columns[col];
2587
+ if (piece == null) {
2588
+ longData[row] = missingLong;
2589
+ } else if (piece instanceof Long) {
2590
+ longData[row] = (Long) piece;
2591
+ } else {
2592
+ try {
2593
+ longData[row] = Long.parseLong(String.valueOf(piece));
2594
+ } catch (NumberFormatException nfe) {
2595
+ longData[row] = missingLong;
2596
+ }
2597
+ }
2598
+ break;
2599
+ case FLOAT:
2600
+ float[] floatData = (float[]) columns[col];
2601
+ if (piece == null) {
2602
+ floatData[row] = missingFloat;
2603
+ } else if (piece instanceof Float) {
2604
+ floatData[row] = (Float) piece;
2605
+ } else {
2606
+ floatData[row] = PApplet.parseFloat(String.valueOf(piece), missingFloat);
2607
+ }
2608
+ break;
2609
+ case DOUBLE:
2610
+ double[] doubleData = (double[]) columns[col];
2611
+ if (piece == null) {
2612
+ doubleData[row] = missingDouble;
2613
+ } else if (piece instanceof Double) {
2614
+ doubleData[row] = (Double) piece;
2615
+ } else {
2616
+ try {
2617
+ doubleData[row] = Double.parseDouble(String.valueOf(piece));
2618
+ } catch (NumberFormatException nfe) {
2619
+ doubleData[row] = missingDouble;
2620
+ }
2621
+ }
2622
+ break;
2623
+ case CATEGORY:
2624
+ int[] indexData = (int[]) columns[col];
2625
+ if (piece == null) {
2626
+ indexData[row] = missingCategory;
2627
+ } else {
2628
+ String peace = String.valueOf(piece);
2629
+ if (peace.equals(missingString)) { // missingString might be null
2630
+ indexData[row] = missingCategory;
2631
+ } else {
2632
+ indexData[row] = columnCategories[col].index(peace);
2633
+ }
2634
+ }
2635
+ break;
2636
+ default:
2637
+ throw new IllegalArgumentException("That's not a valid column type.");
2638
+ }
2639
+ }
2640
+
2641
+
2642
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2643
+
2644
+ /**
2645
+ * @webref table:method
2646
+ * @brief Gets a row from a table
2647
+ * @param row ID number of the row to get
2648
+ * @see Table#rows()
2649
+ * @see Table#findRow(String, int)
2650
+ * @see Table#findRows(String, int)
2651
+ * @see Table#matchRow(String, int)
2652
+ * @see Table#matchRows(String, int)
2653
+ */
2654
+ public TableRow getRow(int row) {
2655
+ return new RowPointer(this, row);
2656
+ }
2657
+
2658
+
2659
+ /**
2660
+ * Note that this one iterator instance is shared by any calls to iterate
2661
+ * the rows of this table. This is very efficient, but not thread-safe.
2662
+ * If you want to iterate in a multi-threaded manner, don't use the iterator.
2663
+ *
2664
+ * @webref table:method
2665
+ * @brief Gets multiple rows from a table
2666
+ * @see Table#getRow(int)
2667
+ * @see Table#findRow(String, int)
2668
+ * @see Table#findRows(String, int)
2669
+ * @see Table#matchRow(String, int)
2670
+ * @see Table#matchRows(String, int)
2671
+ */
2672
+ public Iterable<TableRow> rows() {
2673
+ return new Iterable<TableRow>() {
2674
+ public Iterator<TableRow> iterator() {
2675
+ if (rowIterator == null) {
2676
+ rowIterator = new RowIterator(Table.this);
2677
+ } else {
2678
+ rowIterator.reset();
2679
+ }
2680
+ return rowIterator;
2681
+ }
2682
+ };
2683
+ }
2684
+
2685
+ /**
2686
+ * @nowebref
2687
+ */
2688
+ public Iterable<TableRow> rows(final int[] indices) {
2689
+ return new Iterable<TableRow>() {
2690
+ public Iterator<TableRow> iterator() {
2691
+ return new RowIndexIterator(Table.this, indices);
2692
+ }
2693
+ };
2694
+ }
2695
+
2696
+
2697
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2698
+
2699
+
2700
+ static class RowPointer implements TableRow {
2701
+ Table table;
2702
+ int row;
2703
+
2704
+ public RowPointer(Table table, int row) {
2705
+ this.table = table;
2706
+ this.row = row;
2707
+ }
2708
+
2709
+ public void setRow(int row) {
2710
+ this.row = row;
2711
+ }
2712
+
2713
+ public String getString(int column) {
2714
+ return table.getString(row, column);
2715
+ }
2716
+
2717
+ public String getString(String columnName) {
2718
+ return table.getString(row, columnName);
2719
+ }
2720
+
2721
+ public int getInt(int column) {
2722
+ return table.getInt(row, column);
2723
+ }
2724
+
2725
+ public int getInt(String columnName) {
2726
+ return table.getInt(row, columnName);
2727
+ }
2728
+
2729
+ public long getLong(int column) {
2730
+ return table.getLong(row, column);
2731
+ }
2732
+
2733
+ public long getLong(String columnName) {
2734
+ return table.getLong(row, columnName);
2735
+ }
2736
+
2737
+ public float getFloat(int column) {
2738
+ return table.getFloat(row, column);
2739
+ }
2740
+
2741
+ public float getFloat(String columnName) {
2742
+ return table.getFloat(row, columnName);
2743
+ }
2744
+
2745
+ public double getDouble(int column) {
2746
+ return table.getDouble(row, column);
2747
+ }
2748
+
2749
+ public double getDouble(String columnName) {
2750
+ return table.getDouble(row, columnName);
2751
+ }
2752
+
2753
+ public void setString(int column, String value) {
2754
+ table.setString(row, column, value);
2755
+ }
2756
+
2757
+ public void setString(String columnName, String value) {
2758
+ table.setString(row, columnName, value);
2759
+ }
2760
+
2761
+ public void setInt(int column, int value) {
2762
+ table.setInt(row, column, value);
2763
+ }
2764
+
2765
+ public void setInt(String columnName, int value) {
2766
+ table.setInt(row, columnName, value);
2767
+ }
2768
+
2769
+ public void setLong(int column, long value) {
2770
+ table.setLong(row, column, value);
2771
+ }
2772
+
2773
+ public void setLong(String columnName, long value) {
2774
+ table.setLong(row, columnName, value);
2775
+ }
2776
+
2777
+ public void setFloat(int column, float value) {
2778
+ table.setFloat(row, column, value);
2779
+ }
2780
+
2781
+ public void setFloat(String columnName, float value) {
2782
+ table.setFloat(row, columnName, value);
2783
+ }
2784
+
2785
+ public void setDouble(int column, double value) {
2786
+ table.setDouble(row, column, value);
2787
+ }
2788
+
2789
+ public void setDouble(String columnName, double value) {
2790
+ table.setDouble(row, columnName, value);
2791
+ }
2792
+
2793
+ public int getColumnCount() {
2794
+ return table.getColumnCount();
2795
+ }
2796
+
2797
+ public int getColumnType(String columnName) {
2798
+ return table.getColumnType(columnName);
2799
+ }
2800
+
2801
+ public int getColumnType(int column) {
2802
+ return table.getColumnType(column);
2803
+ }
2804
+
2805
+ public int[] getColumnTypes() {
2806
+ return table.getColumnTypes();
2807
+ }
2808
+
2809
+ public String getColumnTitle(int column) {
2810
+ return table.getColumnTitle(column);
2811
+ }
2812
+
2813
+ public String[] getColumnTitles() {
2814
+ return table.getColumnTitles();
2815
+ }
2816
+
2817
+ public void print() {
2818
+ write(new PrintWriter(System.out));
2819
+ }
2820
+
2821
+ public void write(PrintWriter writer) {
2822
+ for (int i = 0 ; i < getColumnCount(); i++) {
2823
+ if (i != 0) {
2824
+ writer.print('\t');
2825
+ }
2826
+ writer.print(getString(i));
2827
+ }
2828
+ }
2829
+ }
2830
+
2831
+
2832
+ static class RowIterator implements Iterator<TableRow> {
2833
+ Table table;
2834
+ RowPointer rp;
2835
+ int row;
2836
+
2837
+ public RowIterator(Table table) {
2838
+ this.table = table;
2839
+ row = -1;
2840
+ rp = new RowPointer(table, row);
2841
+ }
2842
+
2843
+ public void remove() {
2844
+ table.removeRow(row);
2845
+ }
2846
+
2847
+ public TableRow next() {
2848
+ rp.setRow(++row);
2849
+ return rp;
2850
+ }
2851
+
2852
+ public boolean hasNext() {
2853
+ return row+1 < table.getRowCount();
2854
+ }
2855
+
2856
+ public void reset() {
2857
+ row = -1;
2858
+ }
2859
+ }
2860
+
2861
+
2862
+ static class RowIndexIterator implements Iterator<TableRow> {
2863
+ Table table;
2864
+ RowPointer rp;
2865
+ int[] indices;
2866
+ int index;
2867
+
2868
+ public RowIndexIterator(Table table, int[] indices) {
2869
+ this.table = table;
2870
+ this.indices = indices;
2871
+ index = -1;
2872
+ // just set to something arbitrary
2873
+ rp = new RowPointer(table, -1);
2874
+ }
2875
+
2876
+ public void remove() {
2877
+ table.removeRow(indices[index]);
2878
+ }
2879
+
2880
+ public TableRow next() {
2881
+ rp.setRow(indices[++index]);
2882
+ return rp;
2883
+ }
2884
+
2885
+ public boolean hasNext() {
2886
+ //return row+1 < table.getRowCount();
2887
+ return index + 1 < indices.length;
2888
+ }
2889
+
2890
+ public void reset() {
2891
+ index = -1;
2892
+ }
2893
+ }
2894
+
2895
+
2896
+ /*
2897
+ static public Iterator<TableRow> createIterator(final ResultSet rs) {
2898
+ return new Iterator<TableRow>() {
2899
+ boolean already;
2900
+
2901
+ public boolean hasNext() {
2902
+ already = true;
2903
+ try {
2904
+ return rs.next();
2905
+ } catch (SQLException e) {
2906
+ throw new RuntimeException(e);
2907
+ }
2908
+ }
2909
+
2910
+
2911
+ public TableRow next() {
2912
+ if (!already) {
2913
+ try {
2914
+ rs.next();
2915
+ } catch (SQLException e) {
2916
+ throw new RuntimeException(e);
2917
+ }
2918
+ } else {
2919
+ already = false;
2920
+ }
2921
+
2922
+ return new TableRow() {
2923
+ public double getDouble(int column) {
2924
+ try {
2925
+ return rs.getDouble(column);
2926
+ } catch (SQLException e) {
2927
+ throw new RuntimeException(e);
2928
+ }
2929
+ }
2930
+
2931
+ public double getDouble(String columnName) {
2932
+ try {
2933
+ return rs.getDouble(columnName);
2934
+ } catch (SQLException e) {
2935
+ throw new RuntimeException(e);
2936
+ }
2937
+ }
2938
+
2939
+ public float getFloat(int column) {
2940
+ try {
2941
+ return rs.getFloat(column);
2942
+ } catch (SQLException e) {
2943
+ throw new RuntimeException(e);
2944
+ }
2945
+ }
2946
+
2947
+ public float getFloat(String columnName) {
2948
+ try {
2949
+ return rs.getFloat(columnName);
2950
+ } catch (SQLException e) {
2951
+ throw new RuntimeException(e);
2952
+ }
2953
+ }
2954
+
2955
+ public int getInt(int column) {
2956
+ try {
2957
+ return rs.getInt(column);
2958
+ } catch (SQLException e) {
2959
+ throw new RuntimeException(e);
2960
+ }
2961
+ }
2962
+
2963
+ public int getInt(String columnName) {
2964
+ try {
2965
+ return rs.getInt(columnName);
2966
+ } catch (SQLException e) {
2967
+ throw new RuntimeException(e);
2968
+ }
2969
+ }
2970
+
2971
+ public long getLong(int column) {
2972
+ try {
2973
+ return rs.getLong(column);
2974
+ } catch (SQLException e) {
2975
+ throw new RuntimeException(e);
2976
+ }
2977
+ }
2978
+
2979
+ public long getLong(String columnName) {
2980
+ try {
2981
+ return rs.getLong(columnName);
2982
+ } catch (SQLException e) {
2983
+ throw new RuntimeException(e);
2984
+ }
2985
+ }
2986
+
2987
+ public String getString(int column) {
2988
+ try {
2989
+ return rs.getString(column);
2990
+ } catch (SQLException e) {
2991
+ throw new RuntimeException(e);
2992
+ }
2993
+ }
2994
+
2995
+ public String getString(String columnName) {
2996
+ try {
2997
+ return rs.getString(columnName);
2998
+ } catch (SQLException e) {
2999
+ throw new RuntimeException(e);
3000
+ }
3001
+ }
3002
+
3003
+ public void setString(int column, String value) { immutable(); }
3004
+ public void setString(String columnName, String value) { immutable(); }
3005
+ public void setInt(int column, int value) { immutable(); }
3006
+ public void setInt(String columnName, int value) { immutable(); }
3007
+ public void setLong(int column, long value) { immutable(); }
3008
+ public void setLong(String columnName, long value) { immutable(); }
3009
+ public void setFloat(int column, float value) { immutable(); }
3010
+ public void setFloat(String columnName, float value) { immutable(); }
3011
+ public void setDouble(int column, double value) { immutable(); }
3012
+ public void setDouble(String columnName, double value) { immutable(); }
3013
+
3014
+ private void immutable() {
3015
+ throw new IllegalArgumentException("This TableRow cannot be modified.");
3016
+ }
3017
+
3018
+ public int getColumnCount() {
3019
+ try {
3020
+ return rs.getMetaData().getColumnCount();
3021
+ } catch (SQLException e) {
3022
+ e.printStackTrace();
3023
+ return -1;
3024
+ }
3025
+ }
3026
+
3027
+
3028
+ public int getColumnType(String columnName) {
3029
+ // unimplemented
3030
+ }
3031
+
3032
+
3033
+ public int getColumnType(int column) {
3034
+ // unimplemented
3035
+ }
3036
+
3037
+ };
3038
+ }
3039
+
3040
+ public void remove() {
3041
+ throw new IllegalArgumentException("remove() not supported");
3042
+ }
3043
+ };
3044
+ }
3045
+ */
3046
+
3047
+
3048
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3049
+
3050
+
3051
+ /**
3052
+ * @webref table:method
3053
+ * @brief Get an integer value from the specified row and column
3054
+ * @param row ID number of the row to reference
3055
+ * @param column ID number of the column to reference
3056
+ * @see Table#getFloat(int, int)
3057
+ * @see Table#getString(int, int)
3058
+ * @see Table#getStringColumn(String)
3059
+ * @see Table#setInt(int, int, int)
3060
+ * @see Table#setFloat(int, int, float)
3061
+ * @see Table#setString(int, int, String)
3062
+ */
3063
+ public int getInt(int row, int column) {
3064
+ checkBounds(row, column);
3065
+ if (columnTypes[column] == INT ||
3066
+ columnTypes[column] == CATEGORY) {
3067
+ int[] intData = (int[]) columns[column];
3068
+ return intData[row];
3069
+ }
3070
+ String str = getString(row, column);
3071
+ return (str == null || str.equals(missingString)) ?
3072
+ missingInt : PApplet.parseInt(str, missingInt);
3073
+ }
3074
+
3075
+ /**
3076
+ * @param columnName title of the column to reference
3077
+ */
3078
+ public int getInt(int row, String columnName) {
3079
+ return getInt(row, getColumnIndex(columnName));
3080
+ }
3081
+
3082
+
3083
+ public void setMissingInt(int value) {
3084
+ missingInt = value;
3085
+ }
3086
+
3087
+
3088
+ /**
3089
+ * @webref table:method
3090
+ * @brief Store an integer value in the specified row and column
3091
+ * @param row ID number of the target row
3092
+ * @param column ID number of the target column
3093
+ * @param value value to assign
3094
+ * @see Table#setFloat(int, int, float)
3095
+ * @see Table#setString(int, int, String)
3096
+ * @see Table#getInt(int, int)
3097
+ * @see Table#getFloat(int, int)
3098
+ * @see Table#getString(int, int)
3099
+ * @see Table#getStringColumn(String)
3100
+ */
3101
+ public void setInt(int row, int column, int value) {
3102
+ if (columnTypes[column] == STRING) {
3103
+ setString(row, column, String.valueOf(value));
3104
+
3105
+ } else {
3106
+ ensureBounds(row, column);
3107
+ if (columnTypes[column] != INT &&
3108
+ columnTypes[column] != CATEGORY) {
3109
+ throw new IllegalArgumentException("Column " + column + " is not an int column.");
3110
+ }
3111
+ int[] intData = (int[]) columns[column];
3112
+ intData[row] = value;
3113
+ }
3114
+ }
3115
+
3116
+ /**
3117
+ * @param columnName title of the target column
3118
+ */
3119
+ public void setInt(int row, String columnName, int value) {
3120
+ setInt(row, getColumnIndex(columnName), value);
3121
+ }
3122
+
3123
+
3124
+
3125
+ public int[] getIntColumn(String name) {
3126
+ int col = getColumnIndex(name);
3127
+ return (col == -1) ? null : getIntColumn(col);
3128
+ }
3129
+
3130
+
3131
+ public int[] getIntColumn(int col) {
3132
+ int[] outgoing = new int[rowCount];
3133
+ for (int row = 0; row < rowCount; row++) {
3134
+ outgoing[row] = getInt(row, col);
3135
+ }
3136
+ return outgoing;
3137
+ }
3138
+
3139
+
3140
+ public int[] getIntRow(int row) {
3141
+ int[] outgoing = new int[columns.length];
3142
+ for (int col = 0; col < columns.length; col++) {
3143
+ outgoing[col] = getInt(row, col);
3144
+ }
3145
+ return outgoing;
3146
+ }
3147
+
3148
+
3149
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3150
+
3151
+
3152
+ public long getLong(int row, int column) {
3153
+ checkBounds(row, column);
3154
+ if (columnTypes[column] == LONG) {
3155
+ long[] longData = (long[]) columns[column];
3156
+ return longData[row];
3157
+ }
3158
+ String str = getString(row, column);
3159
+ if (str == null || str.equals(missingString)) {
3160
+ return missingLong;
3161
+ }
3162
+ try {
3163
+ return Long.parseLong(str);
3164
+ } catch (NumberFormatException nfe) {
3165
+ return missingLong;
3166
+ }
3167
+ }
3168
+
3169
+
3170
+ public long getLong(int row, String columnName) {
3171
+ return getLong(row, getColumnIndex(columnName));
3172
+ }
3173
+
3174
+
3175
+ public void setMissingLong(long value) {
3176
+ missingLong = value;
3177
+ }
3178
+
3179
+
3180
+ public void setLong(int row, int column, long value) {
3181
+ if (columnTypes[column] == STRING) {
3182
+ setString(row, column, String.valueOf(value));
3183
+
3184
+ } else {
3185
+ ensureBounds(row, column);
3186
+ if (columnTypes[column] != LONG) {
3187
+ throw new IllegalArgumentException("Column " + column + " is not a 'long' column.");
3188
+ }
3189
+ long[] longData = (long[]) columns[column];
3190
+ longData[row] = value;
3191
+ }
3192
+ }
3193
+
3194
+
3195
+ public void setLong(int row, String columnName, long value) {
3196
+ setLong(row, getColumnIndex(columnName), value);
3197
+ }
3198
+
3199
+
3200
+ public long[] getLongColumn(String name) {
3201
+ int col = getColumnIndex(name);
3202
+ return (col == -1) ? null : getLongColumn(col);
3203
+ }
3204
+
3205
+
3206
+ public long[] getLongColumn(int col) {
3207
+ long[] outgoing = new long[rowCount];
3208
+ for (int row = 0; row < rowCount; row++) {
3209
+ outgoing[row] = getLong(row, col);
3210
+ }
3211
+ return outgoing;
3212
+ }
3213
+
3214
+
3215
+ public long[] getLongRow(int row) {
3216
+ long[] outgoing = new long[columns.length];
3217
+ for (int col = 0; col < columns.length; col++) {
3218
+ outgoing[col] = getLong(row, col);
3219
+ }
3220
+ return outgoing;
3221
+ }
3222
+
3223
+
3224
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3225
+
3226
+
3227
+ /**
3228
+ * Get a float value from the specified row and column. If the value is null
3229
+ * or not parseable as a float, the "missing" value is returned. By default,
3230
+ * this is Float.NaN, but can be controlled with setMissingFloat().
3231
+ *
3232
+ * @webref table:method
3233
+ * @brief Get a float value from the specified row and column
3234
+ * @param row ID number of the row to reference
3235
+ * @param column ID number of the column to reference
3236
+ * @see Table#getInt(int, int)
3237
+ * @see Table#getString(int, int)
3238
+ * @see Table#getStringColumn(String)
3239
+ * @see Table#setInt(int, int, int)
3240
+ * @see Table#setFloat(int, int, float)
3241
+ * @see Table#setString(int, int, String)
3242
+ */
3243
+ public float getFloat(int row, int column) {
3244
+ checkBounds(row, column);
3245
+ if (columnTypes[column] == FLOAT) {
3246
+ float[] floatData = (float[]) columns[column];
3247
+ return floatData[row];
3248
+ }
3249
+ String str = getString(row, column);
3250
+ if (str == null || str.equals(missingString)) {
3251
+ return missingFloat;
3252
+ }
3253
+ return PApplet.parseFloat(str, missingFloat);
3254
+ }
3255
+
3256
+ /**
3257
+ * @param columnName title of the column to reference
3258
+ */
3259
+ public float getFloat(int row, String columnName) {
3260
+ return getFloat(row, getColumnIndex(columnName));
3261
+ }
3262
+
3263
+
3264
+ public void setMissingFloat(float value) {
3265
+ missingFloat = value;
3266
+ }
3267
+
3268
+
3269
+ /**
3270
+ * @webref table:method
3271
+ * @brief Store a float value in the specified row and column
3272
+ * @param row ID number of the target row
3273
+ * @param column ID number of the target column
3274
+ * @param value value to assign
3275
+ * @see Table#setInt(int, int, int)
3276
+ * @see Table#setString(int, int, String)
3277
+ * @see Table#getInt(int, int)
3278
+ * @see Table#getFloat(int, int)
3279
+ * @see Table#getString(int, int)
3280
+ * @see Table#getStringColumn(String)
3281
+ */
3282
+ public void setFloat(int row, int column, float value) {
3283
+ if (columnTypes[column] == STRING) {
3284
+ setString(row, column, String.valueOf(value));
3285
+
3286
+ } else {
3287
+ ensureBounds(row, column);
3288
+ if (columnTypes[column] != FLOAT) {
3289
+ throw new IllegalArgumentException("Column " + column + " is not a float column.");
3290
+ }
3291
+ float[] longData = (float[]) columns[column];
3292
+ longData[row] = value;
3293
+ }
3294
+ }
3295
+
3296
+ /**
3297
+ * @param columnName title of the target column
3298
+ */
3299
+ public void setFloat(int row, String columnName, float value) {
3300
+ setFloat(row, getColumnIndex(columnName), value);
3301
+ }
3302
+
3303
+
3304
+ public float[] getFloatColumn(String name) {
3305
+ int col = getColumnIndex(name);
3306
+ return (col == -1) ? null : getFloatColumn(col);
3307
+ }
3308
+
3309
+
3310
+ public float[] getFloatColumn(int col) {
3311
+ float[] outgoing = new float[rowCount];
3312
+ for (int row = 0; row < rowCount; row++) {
3313
+ outgoing[row] = getFloat(row, col);
3314
+ }
3315
+ return outgoing;
3316
+ }
3317
+
3318
+
3319
+ public float[] getFloatRow(int row) {
3320
+ float[] outgoing = new float[columns.length];
3321
+ for (int col = 0; col < columns.length; col++) {
3322
+ outgoing[col] = getFloat(row, col);
3323
+ }
3324
+ return outgoing;
3325
+ }
3326
+
3327
+
3328
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3329
+
3330
+
3331
+ public double getDouble(int row, int column) {
3332
+ checkBounds(row, column);
3333
+ if (columnTypes[column] == DOUBLE) {
3334
+ double[] doubleData = (double[]) columns[column];
3335
+ return doubleData[row];
3336
+ }
3337
+ String str = getString(row, column);
3338
+ if (str == null || str.equals(missingString)) {
3339
+ return missingDouble;
3340
+ }
3341
+ try {
3342
+ return Double.parseDouble(str);
3343
+ } catch (NumberFormatException nfe) {
3344
+ return missingDouble;
3345
+ }
3346
+ }
3347
+
3348
+
3349
+ public double getDouble(int row, String columnName) {
3350
+ return getDouble(row, getColumnIndex(columnName));
3351
+ }
3352
+
3353
+
3354
+ public void setMissingDouble(double value) {
3355
+ missingDouble = value;
3356
+ }
3357
+
3358
+
3359
+ public void setDouble(int row, int column, double value) {
3360
+ if (columnTypes[column] == STRING) {
3361
+ setString(row, column, String.valueOf(value));
3362
+
3363
+ } else {
3364
+ ensureBounds(row, column);
3365
+ if (columnTypes[column] != DOUBLE) {
3366
+ throw new IllegalArgumentException("Column " + column + " is not a 'double' column.");
3367
+ }
3368
+ double[] doubleData = (double[]) columns[column];
3369
+ doubleData[row] = value;
3370
+ }
3371
+ }
3372
+
3373
+
3374
+ public void setDouble(int row, String columnName, double value) {
3375
+ setDouble(row, getColumnIndex(columnName), value);
3376
+ }
3377
+
3378
+
3379
+ public double[] getDoubleColumn(String name) {
3380
+ int col = getColumnIndex(name);
3381
+ return (col == -1) ? null : getDoubleColumn(col);
3382
+ }
3383
+
3384
+
3385
+ public double[] getDoubleColumn(int col) {
3386
+ double[] outgoing = new double[rowCount];
3387
+ for (int row = 0; row < rowCount; row++) {
3388
+ outgoing[row] = getDouble(row, col);
3389
+ }
3390
+ return outgoing;
3391
+ }
3392
+
3393
+
3394
+ public double[] getDoubleRow(int row) {
3395
+ double[] outgoing = new double[columns.length];
3396
+ for (int col = 0; col < columns.length; col++) {
3397
+ outgoing[col] = getDouble(row, col);
3398
+ }
3399
+ return outgoing;
3400
+ }
3401
+
3402
+
3403
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3404
+
3405
+
3406
+ //public long getTimestamp(String rowName, int column) {
3407
+ //return getTimestamp(getRowIndex(rowName), column);
3408
+ //}
3409
+
3410
+
3411
+ /**
3412
+ * Returns the time in milliseconds by parsing a SQL Timestamp at this cell.
3413
+ */
3414
+ // public long getTimestamp(int row, int column) {
3415
+ // String str = get(row, column);
3416
+ // java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf(str);
3417
+ // return timestamp.getTime();
3418
+ // }
3419
+
3420
+
3421
+ // public long getExcelTimestamp(int row, int column) {
3422
+ // return parseExcelTimestamp(get(row, column));
3423
+ // }
3424
+
3425
+
3426
+ // static protected DateFormat excelDateFormat;
3427
+
3428
+ // static public long parseExcelTimestamp(String timestamp) {
3429
+ // if (excelDateFormat == null) {
3430
+ // excelDateFormat = new SimpleDateFormat("MM/dd/yy HH:mm");
3431
+ // }
3432
+ // try {
3433
+ // return excelDateFormat.parse(timestamp).getTime();
3434
+ // } catch (ParseException e) {
3435
+ // e.printStackTrace();
3436
+ // return -1;
3437
+ // }
3438
+ // }
3439
+
3440
+
3441
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3442
+
3443
+
3444
+ // public void setObject(int row, int column, Object value) {
3445
+ // if (value == null) {
3446
+ // data[row][column] = null;
3447
+ // } else if (value instanceof String) {
3448
+ // set(row, column, (String) value);
3449
+ // } else if (value instanceof Float) {
3450
+ // setFloat(row, column, ((Float) value).floatValue());
3451
+ // } else if (value instanceof Integer) {
3452
+ // setInt(row, column, ((Integer) value).intValue());
3453
+ // } else {
3454
+ // set(row, column, value.toString());
3455
+ // }
3456
+ // }
3457
+
3458
+
3459
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3460
+
3461
+
3462
+ /**
3463
+ * Get a String value from the table. If the row is longer than the table
3464
+ *
3465
+ * @webref table:method
3466
+ * @brief Get an String value from the specified row and column
3467
+ * @param row ID number of the row to reference
3468
+ * @param column ID number of the column to reference
3469
+ * @see Table#getInt(int, int)
3470
+ * @see Table#getFloat(int, int)
3471
+ * @see Table#getStringColumn(String)
3472
+ * @see Table#setInt(int, int, int)
3473
+ * @see Table#setFloat(int, int, float)
3474
+ * @see Table#setString(int, int, String)
3475
+ */
3476
+ public String getString(int row, int column) {
3477
+ checkBounds(row, column);
3478
+ if (columnTypes[column] == STRING) {
3479
+ String[] stringData = (String[]) columns[column];
3480
+ return stringData[row];
3481
+ } else if (columnTypes[column] == CATEGORY) {
3482
+ int cat = getInt(row, column);
3483
+ if (cat == missingCategory) {
3484
+ return missingString;
3485
+ }
3486
+ return columnCategories[column].key(cat);
3487
+ } else if (columnTypes[column] == FLOAT) {
3488
+ if (Float.isNaN(getFloat(row, column))) {
3489
+ return null;
3490
+ }
3491
+ } else if (columnTypes[column] == DOUBLE) {
3492
+ if (Double.isNaN(getFloat(row, column))) {
3493
+ return null;
3494
+ }
3495
+ }
3496
+ return String.valueOf(Array.get(columns[column], row));
3497
+ }
3498
+
3499
+
3500
+ /**
3501
+ * @param columnName title of the column to reference
3502
+ */
3503
+ public String getString(int row, String columnName) {
3504
+ return getString(row, getColumnIndex(columnName));
3505
+ }
3506
+
3507
+
3508
+ /**
3509
+ * Treat entries with this string as "missing". Also used for categorial.
3510
+ */
3511
+ public void setMissingString(String value) {
3512
+ missingString = value;
3513
+ }
3514
+
3515
+
3516
+ /**
3517
+ * @webref table:method
3518
+ * @brief Store a String value in the specified row and column
3519
+ * @param row ID number of the target row
3520
+ * @param column ID number of the target column
3521
+ * @param value value to assign
3522
+ * @see Table#setInt(int, int, int)
3523
+ * @see Table#setFloat(int, int, float)
3524
+ * @see Table#getInt(int, int)
3525
+ * @see Table#getFloat(int, int)
3526
+ * @see Table#getString(int, int)
3527
+ * @see Table#getStringColumn(String)
3528
+ */
3529
+ public void setString(int row, int column, String value) {
3530
+ ensureBounds(row, column);
3531
+ if (columnTypes[column] != STRING) {
3532
+ throw new IllegalArgumentException("Column " + column + " is not a String column.");
3533
+ }
3534
+ String[] stringData = (String[]) columns[column];
3535
+ stringData[row] = value;
3536
+ }
3537
+
3538
+ /**
3539
+ * @param columnName title of the target column
3540
+ */
3541
+ public void setString(int row, String columnName, String value) {
3542
+ int column = checkColumnIndex(columnName);
3543
+ setString(row, column, value);
3544
+ }
3545
+
3546
+ /**
3547
+ * @webref table:method
3548
+ * @brief Gets all values in the specified column
3549
+ * @param columnName title of the column to search
3550
+ * @see Table#getInt(int, int)
3551
+ * @see Table#getFloat(int, int)
3552
+ * @see Table#getString(int, int)
3553
+ * @see Table#setInt(int, int, int)
3554
+ * @see Table#setFloat(int, int, float)
3555
+ * @see Table#setString(int, int, String)
3556
+ */
3557
+ public String[] getStringColumn(String columnName) {
3558
+ int col = getColumnIndex(columnName);
3559
+ return (col == -1) ? null : getStringColumn(col);
3560
+ }
3561
+
3562
+
3563
+ /**
3564
+ * @param column ID number of the column to search
3565
+ */
3566
+ public String[] getStringColumn(int column) {
3567
+ String[] outgoing = new String[rowCount];
3568
+ for (int i = 0; i < rowCount; i++) {
3569
+ outgoing[i] = getString(i, column);
3570
+ }
3571
+ return outgoing;
3572
+ }
3573
+
3574
+
3575
+ public String[] getStringRow(int row) {
3576
+ String[] outgoing = new String[columns.length];
3577
+ for (int col = 0; col < columns.length; col++) {
3578
+ outgoing[col] = getString(row, col);
3579
+ }
3580
+ return outgoing;
3581
+ }
3582
+
3583
+
3584
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3585
+
3586
+
3587
+ /**
3588
+ * Return the row that contains the first String that matches.
3589
+ * @param value the String to match
3590
+ * @param column ID number of the column to search
3591
+ */
3592
+ public int findRowIndex(String value, int column) {
3593
+ checkColumn(column);
3594
+ if (columnTypes[column] == STRING) {
3595
+ String[] stringData = (String[]) columns[column];
3596
+ if (value == null) {
3597
+ for (int row = 0; row < rowCount; row++) {
3598
+ if (stringData[row] == null) return row;
3599
+ }
3600
+ } else {
3601
+ for (int row = 0; row < rowCount; row++) {
3602
+ if (stringData[row] != null && stringData[row].equals(value)) {
3603
+ return row;
3604
+ }
3605
+ }
3606
+ }
3607
+ } else { // less efficient, includes conversion as necessary
3608
+ for (int row = 0; row < rowCount; row++) {
3609
+ String str = getString(row, column);
3610
+ if (str == null) {
3611
+ if (value == null) {
3612
+ return row;
3613
+ }
3614
+ } else if (str.equals(value)) {
3615
+ return row;
3616
+ }
3617
+ }
3618
+ }
3619
+ return -1;
3620
+ }
3621
+
3622
+
3623
+ /**
3624
+ * Return the row that contains the first String that matches.
3625
+ * @param value the String to match
3626
+ * @param columnName title of the column to search
3627
+ */
3628
+ public int findRowIndex(String value, String columnName) {
3629
+ return findRowIndex(value, getColumnIndex(columnName));
3630
+ }
3631
+
3632
+
3633
+ /**
3634
+ * Return a list of rows that contain the String passed in. If there are no
3635
+ * matches, a zero length array will be returned (not a null array).
3636
+ * @param value the String to match
3637
+ * @param column ID number of the column to search
3638
+ */
3639
+ public int[] findRowIndices(String value, int column) {
3640
+ int[] outgoing = new int[rowCount];
3641
+ int count = 0;
3642
+
3643
+ checkColumn(column);
3644
+ if (columnTypes[column] == STRING) {
3645
+ String[] stringData = (String[]) columns[column];
3646
+ if (value == null) {
3647
+ for (int row = 0; row < rowCount; row++) {
3648
+ if (stringData[row] == null) {
3649
+ outgoing[count++] = row;
3650
+ }
3651
+ }
3652
+ } else {
3653
+ for (int row = 0; row < rowCount; row++) {
3654
+ if (stringData[row] != null && stringData[row].equals(value)) {
3655
+ outgoing[count++] = row;
3656
+ }
3657
+ }
3658
+ }
3659
+ } else { // less efficient, includes conversion as necessary
3660
+ for (int row = 0; row < rowCount; row++) {
3661
+ String str = getString(row, column);
3662
+ if (str == null) {
3663
+ if (value == null) {
3664
+ outgoing[count++] = row;
3665
+ }
3666
+ } else if (str.equals(value)) {
3667
+ outgoing[count++] = row;
3668
+ }
3669
+ }
3670
+ }
3671
+ return PApplet.subset(outgoing, 0, count);
3672
+ }
3673
+
3674
+
3675
+ /**
3676
+ * Return a list of rows that contain the String passed in. If there are no
3677
+ * matches, a zero length array will be returned (not a null array).
3678
+ * @param value the String to match
3679
+ * @param columnName title of the column to search
3680
+ */
3681
+ public int[] findRowIndices(String value, String columnName) {
3682
+ return findRowIndices(value, getColumnIndex(columnName));
3683
+ }
3684
+
3685
+
3686
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3687
+
3688
+ /**
3689
+ * @webref table:method
3690
+ * @brief Finds a row that contains the given value
3691
+ * @param value the value to match
3692
+ * @param column ID number of the column to search
3693
+ * @see Table#getRow(int)
3694
+ * @see Table#rows()
3695
+ * @see Table#findRows(String, int)
3696
+ * @see Table#matchRow(String, int)
3697
+ * @see Table#matchRows(String, int)
3698
+ */
3699
+ public TableRow findRow(String value, int column) {
3700
+ int row = findRowIndex(value, column);
3701
+ return (row == -1) ? null : new RowPointer(this, row);
3702
+ }
3703
+
3704
+
3705
+ /**
3706
+ * @param columnName title of the column to search
3707
+ */
3708
+ public TableRow findRow(String value, String columnName) {
3709
+ return findRow(value, getColumnIndex(columnName));
3710
+ }
3711
+
3712
+
3713
+ /**
3714
+ * @webref table:method
3715
+ * @brief Finds multiple rows that contain the given value
3716
+ * @param value the value to match
3717
+ * @param column ID number of the column to search
3718
+ * @see Table#getRow(int)
3719
+ * @see Table#rows()
3720
+ * @see Table#findRow(String, int)
3721
+ * @see Table#matchRow(String, int)
3722
+ * @see Table#matchRows(String, int)
3723
+ */
3724
+ public Iterable<TableRow> findRows(final String value, final int column) {
3725
+ return new Iterable<TableRow>() {
3726
+ public Iterator<TableRow> iterator() {
3727
+ return findRowIterator(value, column);
3728
+ }
3729
+ };
3730
+ }
3731
+
3732
+
3733
+ /**
3734
+ * @param columnName title of the column to search
3735
+ */
3736
+ public Iterable<TableRow> findRows(final String value, final String columnName) {
3737
+ return findRows(value, getColumnIndex(columnName));
3738
+ }
3739
+
3740
+
3741
+ /**
3742
+ * @brief Finds multiple rows that contain the given value
3743
+ * @param value the value to match
3744
+ * @param column ID number of the column to search
3745
+ */
3746
+ public Iterator<TableRow> findRowIterator(String value, int column) {
3747
+ return new RowIndexIterator(this, findRowIndices(value, column));
3748
+ }
3749
+
3750
+
3751
+ /**
3752
+ * @param columnName title of the column to search
3753
+ */
3754
+ public Iterator<TableRow> findRowIterator(String value, String columnName) {
3755
+ return findRowIterator(value, getColumnIndex(columnName));
3756
+ }
3757
+
3758
+
3759
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3760
+
3761
+
3762
+ /**
3763
+ * Return the row that contains the first String that matches.
3764
+ * @param regexp the String to match
3765
+ * @param column ID number of the column to search
3766
+ */
3767
+ public int matchRowIndex(String regexp, int column) {
3768
+ checkColumn(column);
3769
+ if (columnTypes[column] == STRING) {
3770
+ String[] stringData = (String[]) columns[column];
3771
+ for (int row = 0; row < rowCount; row++) {
3772
+ if (stringData[row] != null &&
3773
+ PApplet.match(stringData[row], regexp) != null) {
3774
+ return row;
3775
+ }
3776
+ }
3777
+ } else { // less efficient, includes conversion as necessary
3778
+ for (int row = 0; row < rowCount; row++) {
3779
+ String str = getString(row, column);
3780
+ if (str != null &&
3781
+ PApplet.match(str, regexp) != null) {
3782
+ return row;
3783
+ }
3784
+ }
3785
+ }
3786
+ return -1;
3787
+ }
3788
+
3789
+
3790
+ /**
3791
+ * Return the row that contains the first String that matches.
3792
+ * @param what the String to match
3793
+ * @param columnName title of the column to search
3794
+ */
3795
+ public int matchRowIndex(String what, String columnName) {
3796
+ return matchRowIndex(what, getColumnIndex(columnName));
3797
+ }
3798
+
3799
+
3800
+ /**
3801
+ * Return a list of rows that contain the String passed in. If there are no
3802
+ * matches, a zero length array will be returned (not a null array).
3803
+ * @param regexp the String to match
3804
+ * @param column ID number of the column to search
3805
+ */
3806
+ public int[] matchRowIndices(String regexp, int column) {
3807
+ int[] outgoing = new int[rowCount];
3808
+ int count = 0;
3809
+
3810
+ checkColumn(column);
3811
+ if (columnTypes[column] == STRING) {
3812
+ String[] stringData = (String[]) columns[column];
3813
+ for (int row = 0; row < rowCount; row++) {
3814
+ if (stringData[row] != null &&
3815
+ PApplet.match(stringData[row], regexp) != null) {
3816
+ outgoing[count++] = row;
3817
+ }
3818
+ }
3819
+ } else { // less efficient, includes conversion as necessary
3820
+ for (int row = 0; row < rowCount; row++) {
3821
+ String str = getString(row, column);
3822
+ if (str != null &&
3823
+ PApplet.match(str, regexp) != null) {
3824
+ outgoing[count++] = row;
3825
+ }
3826
+ }
3827
+ }
3828
+ return PApplet.subset(outgoing, 0, count);
3829
+ }
3830
+
3831
+
3832
+ /**
3833
+ * Return a list of rows that match the regex passed in. If there are no
3834
+ * matches, a zero length array will be returned (not a null array).
3835
+ * @param what the String to match
3836
+ * @param columnName title of the column to search
3837
+ */
3838
+ public int[] matchRowIndices(String what, String columnName) {
3839
+ return matchRowIndices(what, getColumnIndex(columnName));
3840
+ }
3841
+
3842
+
3843
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3844
+
3845
+ /**
3846
+ * @webref table:method
3847
+ * @brief Finds a row that matches the given expression
3848
+ * @param regexp the regular expression to match
3849
+ * @param column ID number of the column to search
3850
+ * @see Table#getRow(int)
3851
+ * @see Table#rows()
3852
+ * @see Table#findRow(String, int)
3853
+ * @see Table#findRows(String, int)
3854
+ * @see Table#matchRows(String, int)
3855
+ */
3856
+ public TableRow matchRow(String regexp, int column) {
3857
+ int row = matchRowIndex(regexp, column);
3858
+ return (row == -1) ? null : new RowPointer(this, row);
3859
+ }
3860
+
3861
+
3862
+ /**
3863
+ * @param columnName title of the column to search
3864
+ */
3865
+ public TableRow matchRow(String regexp, String columnName) {
3866
+ return matchRow(regexp, getColumnIndex(columnName));
3867
+ }
3868
+
3869
+
3870
+ /**
3871
+ * @webref table:method
3872
+ * @brief Finds multiple rows that match the given expression
3873
+ * @param regexp the regular expression to match
3874
+ * @param column ID number of the column to search
3875
+ * @see Table#getRow(int)
3876
+ * @see Table#rows()
3877
+ * @see Table#findRow(String, int)
3878
+ * @see Table#findRows(String, int)
3879
+ * @see Table#matchRow(String, int)
3880
+ */
3881
+ public Iterable<TableRow> matchRows(final String regexp, final int column) {
3882
+ return new Iterable<TableRow>() {
3883
+ public Iterator<TableRow> iterator() {
3884
+ return matchRowIterator(regexp, column);
3885
+ }
3886
+ };
3887
+ }
3888
+
3889
+
3890
+ /**
3891
+ * @param columnName title of the column to search
3892
+ */
3893
+ public Iterable<TableRow> matchRows(String regexp, String columnName) {
3894
+ return matchRows(regexp, getColumnIndex(columnName));
3895
+ }
3896
+
3897
+
3898
+ /**
3899
+ * @webref table:method
3900
+ * @brief Finds multiple rows that match the given expression
3901
+ * @param value the regular expression to match
3902
+ * @param column ID number of the column to search
3903
+ */
3904
+ public Iterator<TableRow> matchRowIterator(String value, int column) {
3905
+ return new RowIndexIterator(this, matchRowIndices(value, column));
3906
+ }
3907
+
3908
+
3909
+ /**
3910
+ * @param columnName title of the column to search
3911
+ */
3912
+ public Iterator<TableRow> matchRowIterator(String value, String columnName) {
3913
+ return matchRowIterator(value, getColumnIndex(columnName));
3914
+ }
3915
+
3916
+
3917
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3918
+
3919
+
3920
+ /**
3921
+ * Replace a String with another. Set empty entries null by using
3922
+ * replace("", null) or use replace(null, "") to go the other direction.
3923
+ * If this is a typed table, only String columns will be modified.
3924
+ * @param orig
3925
+ * @param replacement
3926
+ */
3927
+ public void replace(String orig, String replacement) {
3928
+ for (int col = 0; col < columns.length; col++) {
3929
+ replace(orig, replacement, col);
3930
+ }
3931
+ }
3932
+
3933
+
3934
+ public void replace(String orig, String replacement, int col) {
3935
+ if (columnTypes[col] == STRING) {
3936
+ String[] stringData = (String[]) columns[col];
3937
+
3938
+ if (orig != null) {
3939
+ for (int row = 0; row < rowCount; row++) {
3940
+ if (orig.equals(stringData[row])) {
3941
+ stringData[row] = replacement;
3942
+ }
3943
+ }
3944
+ } else { // null is a special case (and faster anyway)
3945
+ for (int row = 0; row < rowCount; row++) {
3946
+ if (stringData[row] == null) {
3947
+ stringData[row] = replacement;
3948
+ }
3949
+ }
3950
+ }
3951
+ }
3952
+ }
3953
+
3954
+
3955
+ public void replace(String orig, String replacement, String colName) {
3956
+ replace(orig, replacement, getColumnIndex(colName));
3957
+ }
3958
+
3959
+
3960
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3961
+
3962
+
3963
+ public void replaceAll(String regex, String replacement) {
3964
+ for (int col = 0; col < columns.length; col++) {
3965
+ replaceAll(regex, replacement, col);
3966
+ }
3967
+ }
3968
+
3969
+
3970
+ public void replaceAll(String regex, String replacement, int column) {
3971
+ checkColumn(column);
3972
+ if (columnTypes[column] == STRING) {
3973
+ String[] stringData = (String[]) columns[column];
3974
+ for (int row = 0; row < rowCount; row++) {
3975
+ if (stringData[row] != null) {
3976
+ stringData[row] = stringData[row].replaceAll(regex, replacement);
3977
+ }
3978
+ }
3979
+ } else {
3980
+ throw new IllegalArgumentException("replaceAll() can only be used on String columns");
3981
+ }
3982
+ }
3983
+
3984
+
3985
+ /**
3986
+ * Run String.replaceAll() on all entries in a column.
3987
+ * Only works with columns that are already String values.
3988
+ * @param regex the String to match
3989
+ * @param columnName title of the column to search
3990
+ */
3991
+ public void replaceAll(String regex, String replacement, String columnName) {
3992
+ replaceAll(regex, replacement, getColumnIndex(columnName));
3993
+ }
3994
+
3995
+
3996
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3997
+
3998
+
3999
+ /**
4000
+ * Remove any of the specified characters from the entire table.
4001
+ *
4002
+ * @webref table:method
4003
+ * @brief Removes characters from the table
4004
+ * @param tokens a list of individual characters to be removed
4005
+ * @see Table#trim()
4006
+ */
4007
+ public void removeTokens(String tokens) {
4008
+ for (int col = 0; col < getColumnCount(); col++) {
4009
+ removeTokens(tokens, col);
4010
+ }
4011
+ }
4012
+
4013
+
4014
+ /**
4015
+ * Removed any of the specified characters from a column. For instance,
4016
+ * the following code removes dollar signs and commas from column 2:
4017
+ * <pre>
4018
+ * table.removeTokens(",$", 2);
4019
+ * </pre>
4020
+ *
4021
+ * @param column ID number of the column to process
4022
+ */
4023
+ public void removeTokens(String tokens, int column) {
4024
+ for (int row = 0; row < rowCount; row++) {
4025
+ String s = getString(row, column);
4026
+ if (s != null) {
4027
+ char[] c = s.toCharArray();
4028
+ int index = 0;
4029
+ for (int j = 0; j < c.length; j++) {
4030
+ if (tokens.indexOf(c[j]) == -1) {
4031
+ if (index != j) {
4032
+ c[index] = c[j];
4033
+ }
4034
+ index++;
4035
+ }
4036
+ }
4037
+ if (index != c.length) {
4038
+ setString(row, column, new String(c, 0, index));
4039
+ }
4040
+ }
4041
+ }
4042
+ }
4043
+
4044
+ /**
4045
+ * @param columnName title of the column to process
4046
+ */
4047
+ public void removeTokens(String tokens, String columnName) {
4048
+ removeTokens(tokens, getColumnIndex(columnName));
4049
+ }
4050
+
4051
+
4052
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4053
+
4054
+
4055
+ /**
4056
+ * @webref table:method
4057
+ * @brief Trims whitespace from values
4058
+ * @see Table#removeTokens(String)
4059
+ */
4060
+ public void trim() {
4061
+ columnTitles = PApplet.trim(columnTitles);
4062
+ for (int col = 0; col < getColumnCount(); col++) {
4063
+ trim(col);
4064
+ }
4065
+ // remove empty columns
4066
+ int lastColumn = getColumnCount() - 1;
4067
+ //while (isEmptyColumn(lastColumn) && lastColumn >= 0) {
4068
+ while (isEmptyArray(getStringColumn(lastColumn)) && lastColumn >= 0) {
4069
+ lastColumn--;
4070
+ }
4071
+ setColumnCount(lastColumn + 1);
4072
+
4073
+ // trim() works from both sides
4074
+ while (getColumnCount() > 0 && isEmptyArray(getStringColumn(0))) {
4075
+ removeColumn(0);
4076
+ }
4077
+
4078
+ // remove empty rows (starting from the end)
4079
+ int lastRow = lastRowIndex();
4080
+ //while (isEmptyRow(lastRow) && lastRow >= 0) {
4081
+ while (isEmptyArray(getStringRow(lastRow)) && lastRow >= 0) {
4082
+ lastRow--;
4083
+ }
4084
+ setRowCount(lastRow + 1);
4085
+
4086
+ while (getRowCount() > 0 && isEmptyArray(getStringRow(0))) {
4087
+ removeRow(0);
4088
+ }
4089
+ }
4090
+
4091
+
4092
+ protected boolean isEmptyArray(String[] contents) {
4093
+ for (String entry : contents) {
4094
+ if (entry != null && entry.length() > 0) {
4095
+ return false;
4096
+ }
4097
+ }
4098
+ return true;
4099
+ }
4100
+
4101
+
4102
+ /*
4103
+ protected boolean isEmptyColumn(int column) {
4104
+ String[] contents = getStringColumn(column);
4105
+ for (String entry : contents) {
4106
+ if (entry != null && entry.length() > 0) {
4107
+ return false;
4108
+ }
4109
+ }
4110
+ return true;
4111
+ }
4112
+
4113
+
4114
+ protected boolean isEmptyRow(int row) {
4115
+ String[] contents = getStringRow(row);
4116
+ for (String entry : contents) {
4117
+ if (entry != null && entry.length() > 0) {
4118
+ return false;
4119
+ }
4120
+ }
4121
+ return true;
4122
+ }
4123
+ */
4124
+
4125
+
4126
+ /**
4127
+ * @param column ID number of the column to trim
4128
+ */
4129
+ public void trim(int column) {
4130
+ if (columnTypes[column] == STRING) {
4131
+ String[] stringData = (String[]) columns[column];
4132
+ for (int row = 0; row < rowCount; row++) {
4133
+ if (stringData[row] != null) {
4134
+ stringData[row] = PApplet.trim(stringData[row]);
4135
+ }
4136
+ }
4137
+ }
4138
+ }
4139
+
4140
+ /**
4141
+ * @param columnName title of the column to trim
4142
+ */
4143
+ public void trim(String columnName) {
4144
+ trim(getColumnIndex(columnName));
4145
+ }
4146
+
4147
+
4148
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4149
+
4150
+
4151
+ /** Make sure this is a legit column, and if not, expand the table. */
4152
+ protected void ensureColumn(int col) {
4153
+ if (col >= columns.length) {
4154
+ setColumnCount(col + 1);
4155
+ }
4156
+ }
4157
+
4158
+
4159
+ /** Make sure this is a legit row, and if not, expand the table. */
4160
+ protected void ensureRow(int row) {
4161
+ if (row >= rowCount) {
4162
+ setRowCount(row + 1);
4163
+ }
4164
+ }
4165
+
4166
+
4167
+ /** Make sure this is a legit row and column. If not, expand the table. */
4168
+ protected void ensureBounds(int row, int col) {
4169
+ ensureRow(row);
4170
+ ensureColumn(col);
4171
+ }
4172
+
4173
+
4174
+ /** Throw an error if this row doesn't exist. */
4175
+ protected void checkRow(int row) {
4176
+ if (row < 0 || row >= rowCount) {
4177
+ throw new ArrayIndexOutOfBoundsException("Row " + row + " does not exist.");
4178
+ }
4179
+ }
4180
+
4181
+
4182
+ /** Throw an error if this column doesn't exist. */
4183
+ protected void checkColumn(int column) {
4184
+ if (column < 0 || column >= columns.length) {
4185
+ throw new ArrayIndexOutOfBoundsException("Column " + column + " does not exist.");
4186
+ }
4187
+ }
4188
+
4189
+
4190
+ /** Throw an error if this entry is out of bounds. */
4191
+ protected void checkBounds(int row, int column) {
4192
+ checkRow(row);
4193
+ checkColumn(column);
4194
+ }
4195
+
4196
+
4197
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4198
+
4199
+
4200
+ static class HashMapBlows {
4201
+ HashMap<String,Integer> dataToIndex = new HashMap<>();
4202
+ ArrayList<String> indexToData = new ArrayList<>();
4203
+
4204
+ HashMapBlows() { }
4205
+
4206
+ HashMapBlows(DataInputStream input) throws IOException {
4207
+ read(input);
4208
+ }
4209
+
4210
+ /** gets the index, and creates one if it doesn't already exist. */
4211
+ int index(String key) {
4212
+ Integer value = dataToIndex.get(key);
4213
+ if (value != null) {
4214
+ return value;
4215
+ }
4216
+
4217
+ int v = dataToIndex.size();
4218
+ dataToIndex.put(key, v);
4219
+ indexToData.add(key);
4220
+ return v;
4221
+ }
4222
+
4223
+ String key(int index) {
4224
+ return indexToData.get(index);
4225
+ }
4226
+
4227
+ boolean hasCategory(int index) {
4228
+ return index < size() && indexToData.get(index) != null;
4229
+ }
4230
+
4231
+ void setCategory(int index, String name) {
4232
+ while (indexToData.size() <= index) {
4233
+ indexToData.add(null);
4234
+ }
4235
+ indexToData.set(index, name);
4236
+ dataToIndex.put(name, index);
4237
+ }
4238
+
4239
+ int size() {
4240
+ return dataToIndex.size();
4241
+ }
4242
+
4243
+ void write(DataOutputStream output) throws IOException {
4244
+ output.writeInt(size());
4245
+ for (String str : indexToData) {
4246
+ output.writeUTF(str);
4247
+ }
4248
+ }
4249
+
4250
+ private void writeln(PrintWriter writer) throws IOException {
4251
+ for (String str : indexToData) {
4252
+ writer.println(str);
4253
+ }
4254
+ writer.flush();
4255
+ writer.close();
4256
+ }
4257
+
4258
+ void read(DataInputStream input) throws IOException {
4259
+ int count = input.readInt();
4260
+ //System.out.println("found " + count + " entries in category map");
4261
+ dataToIndex = new HashMap<>(count);
4262
+ for (int i = 0; i < count; i++) {
4263
+ String str = input.readUTF();
4264
+ //System.out.println(i + " " + str);
4265
+ dataToIndex.put(str, i);
4266
+ indexToData.add(str);
4267
+ }
4268
+ }
4269
+ }
4270
+
4271
+
4272
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4273
+
4274
+
4275
+ // class HashMapSucks extends HashMap<String,Integer> {
4276
+ //
4277
+ // void increment(String what) {
4278
+ // Integer value = get(what);
4279
+ // if (value == null) {
4280
+ // put(what, 1);
4281
+ // } else {
4282
+ // put(what, value + 1);
4283
+ // }
4284
+ // }
4285
+ //
4286
+ // void check(String what) {
4287
+ // if (get(what) == null) {
4288
+ // put(what, 0);
4289
+ // }
4290
+ // }
4291
+ // }
4292
+
4293
+
4294
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4295
+
4296
+ /**
4297
+ * Sorts (orders) a table based on the values in a column.
4298
+ *
4299
+ * @webref table:method
4300
+ * @brief Orders a table based on the values in a column
4301
+ * @param columnName the name of the column to sort
4302
+ * @see Table#trim()
4303
+ */
4304
+ public void sort(String columnName) {
4305
+ sort(getColumnIndex(columnName), false);
4306
+ }
4307
+
4308
+ /**
4309
+ * @param column the column ID, e.g. 0, 1, 2
4310
+ */
4311
+ public void sort(int column) {
4312
+ sort(column, false);
4313
+ }
4314
+
4315
+
4316
+ public void sortReverse(String columnName) {
4317
+ sort(getColumnIndex(columnName), true);
4318
+ }
4319
+
4320
+
4321
+ public void sortReverse(int column) {
4322
+ sort(column, true);
4323
+ }
4324
+
4325
+
4326
+ protected void sort(final int column, final boolean reverse) {
4327
+ final int[] order = IntList.fromRange(getRowCount()).array();
4328
+ Sort s = new Sort() {
4329
+
4330
+ @Override
4331
+ public int size() {
4332
+ return getRowCount();
4333
+ }
4334
+
4335
+ @Override
4336
+ public float compare(int index1, int index2) {
4337
+ int a = reverse ? order[index2] : order[index1];
4338
+ int b = reverse ? order[index1] : order[index2];
4339
+
4340
+ switch (getColumnType(column)) {
4341
+ case INT:
4342
+ return getInt(a, column) - getInt(b, column);
4343
+ case LONG:
4344
+ return getLong(a, column) - getLong(b, column);
4345
+ case FLOAT:
4346
+ return getFloat(a, column) - getFloat(b, column);
4347
+ case DOUBLE:
4348
+ return (float) (getDouble(a, column) - getDouble(b, column));
4349
+ case STRING:
4350
+ return getString(a, column).compareToIgnoreCase(getString(b, column));
4351
+ case CATEGORY:
4352
+ return getInt(a, column) - getInt(b, column);
4353
+ default:
4354
+ throw new IllegalArgumentException("Invalid column type: " + getColumnType(column));
4355
+ }
4356
+ }
4357
+
4358
+ @Override
4359
+ public void swap(int a, int b) {
4360
+ int temp = order[a];
4361
+ order[a] = order[b];
4362
+ order[b] = temp;
4363
+ }
4364
+
4365
+ };
4366
+ s.run();
4367
+
4368
+ //Object[] newColumns = new Object[getColumnCount()];
4369
+ for (int col = 0; col < getColumnCount(); col++) {
4370
+ switch (getColumnType(col)) {
4371
+ case INT:
4372
+ case CATEGORY:
4373
+ int[] oldInt = (int[]) columns[col];
4374
+ int[] newInt = new int[rowCount];
4375
+ for (int row = 0; row < getRowCount(); row++) {
4376
+ newInt[row] = oldInt[order[row]];
4377
+ }
4378
+ columns[col] = newInt;
4379
+ break;
4380
+ case LONG:
4381
+ long[] oldLong = (long[]) columns[col];
4382
+ long[] newLong = new long[rowCount];
4383
+ for (int row = 0; row < getRowCount(); row++) {
4384
+ newLong[row] = oldLong[order[row]];
4385
+ }
4386
+ columns[col] = newLong;
4387
+ break;
4388
+ case FLOAT:
4389
+ float[] oldFloat = (float[]) columns[col];
4390
+ float[] newFloat = new float[rowCount];
4391
+ for (int row = 0; row < getRowCount(); row++) {
4392
+ newFloat[row] = oldFloat[order[row]];
4393
+ }
4394
+ columns[col] = newFloat;
4395
+ break;
4396
+ case DOUBLE:
4397
+ double[] oldDouble = (double[]) columns[col];
4398
+ double[] newDouble = new double[rowCount];
4399
+ for (int row = 0; row < getRowCount(); row++) {
4400
+ newDouble[row] = oldDouble[order[row]];
4401
+ }
4402
+ columns[col] = newDouble;
4403
+ break;
4404
+ case STRING:
4405
+ String[] oldString = (String[]) columns[col];
4406
+ String[] newString = new String[rowCount];
4407
+ for (int row = 0; row < getRowCount(); row++) {
4408
+ newString[row] = oldString[order[row]];
4409
+ }
4410
+ columns[col] = newString;
4411
+ break;
4412
+ }
4413
+ }
4414
+ }
4415
+
4416
+
4417
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4418
+
4419
+
4420
+ public String[] getUnique(String columnName) {
4421
+ return getUnique(getColumnIndex(columnName));
4422
+ }
4423
+
4424
+
4425
+ public String[] getUnique(int column) {
4426
+ StringList list = new StringList(getStringColumn(column));
4427
+ return list.getUnique();
4428
+ }
4429
+
4430
+
4431
+ public IntDict getTally(String columnName) {
4432
+ return getTally(getColumnIndex(columnName));
4433
+ }
4434
+
4435
+
4436
+ public IntDict getTally(int column) {
4437
+ StringList list = new StringList(getStringColumn(column));
4438
+ return list.getTally();
4439
+ }
4440
+
4441
+
4442
+ public IntDict getOrder(String columnName) {
4443
+ return getOrder(getColumnIndex(columnName));
4444
+ }
4445
+
4446
+
4447
+ public IntDict getOrder(int column) {
4448
+ StringList list = new StringList(getStringColumn(column));
4449
+ return list.getOrder();
4450
+ }
4451
+
4452
+
4453
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4454
+
4455
+
4456
+ public IntList getIntList(String columnName) {
4457
+ return new IntList(getIntColumn(columnName));
4458
+ }
4459
+
4460
+
4461
+ public IntList getIntList(int column) {
4462
+ return new IntList(getIntColumn(column));
4463
+ }
4464
+
4465
+
4466
+ public FloatList getFloatList(String columnName) {
4467
+ return new FloatList(getFloatColumn(columnName));
4468
+ }
4469
+
4470
+
4471
+ public FloatList getFloatList(int column) {
4472
+ return new FloatList(getFloatColumn(column));
4473
+ }
4474
+
4475
+
4476
+ public StringList getStringList(String columnName) {
4477
+ return new StringList(getStringColumn(columnName));
4478
+ }
4479
+
4480
+
4481
+ public StringList getStringList(int column) {
4482
+ return new StringList(getStringColumn(column));
4483
+ }
4484
+
4485
+
4486
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4487
+
4488
+
4489
+ public IntDict getIntDict(String keyColumnName, String valueColumnName) {
4490
+ return new IntDict(getStringColumn(keyColumnName),
4491
+ getIntColumn(valueColumnName));
4492
+ }
4493
+
4494
+
4495
+ public IntDict getIntDict(int keyColumn, int valueColumn) {
4496
+ return new IntDict(getStringColumn(keyColumn),
4497
+ getIntColumn(valueColumn));
4498
+ }
4499
+
4500
+
4501
+ public FloatDict getFloatDict(String keyColumnName, String valueColumnName) {
4502
+ return new FloatDict(getStringColumn(keyColumnName),
4503
+ getFloatColumn(valueColumnName));
4504
+ }
4505
+
4506
+
4507
+ public FloatDict getFloatDict(int keyColumn, int valueColumn) {
4508
+ return new FloatDict(getStringColumn(keyColumn),
4509
+ getFloatColumn(valueColumn));
4510
+ }
4511
+
4512
+
4513
+ public StringDict getStringDict(String keyColumnName, String valueColumnName) {
4514
+ return new StringDict(getStringColumn(keyColumnName),
4515
+ getStringColumn(valueColumnName));
4516
+ }
4517
+
4518
+
4519
+ public StringDict getStringDict(int keyColumn, int valueColumn) {
4520
+ return new StringDict(getStringColumn(keyColumn),
4521
+ getStringColumn(valueColumn));
4522
+ }
4523
+
4524
+
4525
+ public Map<String, TableRow> getRowMap(String columnName) {
4526
+ int col = getColumnIndex(columnName);
4527
+ return (col == -1) ? null : getRowMap(col);
4528
+ }
4529
+
4530
+
4531
+ /**
4532
+ * Return a mapping that connects the entry from a column back to the row
4533
+ * from which it came. For instance:
4534
+ * <pre>
4535
+ * Table t = loadTable("country-data.tsv", "header");
4536
+ * // use the contents of the 'country' column to index the table
4537
+ * Map<String, TableRow> lookup = t.getRowMap("country");
4538
+ * // get the row that has "us" in the "country" column:
4539
+ * TableRow usRow = lookup.get("us");
4540
+ * // get an entry from the 'population' column
4541
+ * int population = usRow.getInt("population");
4542
+ * </pre>
4543
+ */
4544
+ public Map<String, TableRow> getRowMap(int column) {
4545
+ Map<String, TableRow> outgoing = new HashMap<>();
4546
+ for (int row = 0; row < getRowCount(); row++) {
4547
+ String id = getString(row, column);
4548
+ outgoing.put(id, new RowPointer(this, row));
4549
+ }
4550
+ // for (TableRow row : rows()) {
4551
+ // String id = row.getString(column);
4552
+ // outgoing.put(id, row);
4553
+ // }
4554
+ return outgoing;
4555
+ }
4556
+
4557
+
4558
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4559
+
4560
+
4561
+ // /**
4562
+ // * Return an object that maps the String values in one column back to the
4563
+ // * row from which they came. For instance, if the "name" of each row is
4564
+ // * found in the first column, getColumnRowLookup(0) would return an object
4565
+ // * that would map each name back to its row.
4566
+ // */
4567
+ // protected HashMap<String,Integer> getRowLookup(int col) {
4568
+ // HashMap<String,Integer> outgoing = new HashMap<String, Integer>();
4569
+ // for (int row = 0; row < getRowCount(); row++) {
4570
+ // outgoing.put(getString(row, col), row);
4571
+ // }
4572
+ // return outgoing;
4573
+ // }
4574
+
4575
+
4576
+ // incomplete, basically this is silly to write all this repetitive code when
4577
+ // it can be implemented in ~3 lines of code...
4578
+ // /**
4579
+ // * Return an object that maps the data from one column to the data of found
4580
+ // * in another column.
4581
+ // */
4582
+ // public HashMap<?,?> getLookup(int col1, int col2) {
4583
+ // HashMap outgoing = null;
4584
+ //
4585
+ // switch (columnTypes[col1]) {
4586
+ // case INT: {
4587
+ // if (columnTypes[col2] == INT) {
4588
+ // outgoing = new HashMap<Integer, Integer>();
4589
+ // for (int row = 0; row < getRowCount(); row++) {
4590
+ // outgoing.put(getInt(row, col1), getInt(row, col2));
4591
+ // }
4592
+ // } else if (columnTypes[col2] == LONG) {
4593
+ // outgoing = new HashMap<Integer, Long>();
4594
+ // for (int row = 0; row < getRowCount(); row++) {
4595
+ // outgoing.put(getInt(row, col1), getLong(row, col2));
4596
+ // }
4597
+ // } else if (columnTypes[col2] == FLOAT) {
4598
+ // outgoing = new HashMap<Integer, Float>();
4599
+ // for (int row = 0; row < getRowCount(); row++) {
4600
+ // outgoing.put(getInt(row, col1), getFloat(row, col2));
4601
+ // }
4602
+ // } else if (columnTypes[col2] == DOUBLE) {
4603
+ // outgoing = new HashMap<Integer, Double>();
4604
+ // for (int row = 0; row < getRowCount(); row++) {
4605
+ // outgoing.put(getInt(row, col1), getDouble(row, col2));
4606
+ // }
4607
+ // } else if (columnTypes[col2] == STRING) {
4608
+ // outgoing = new HashMap<Integer, String>();
4609
+ // for (int row = 0; row < getRowCount(); row++) {
4610
+ // outgoing.put(getInt(row, col1), get(row, col2));
4611
+ // }
4612
+ // }
4613
+ // break;
4614
+ // }
4615
+ // }
4616
+ // return outgoing;
4617
+ // }
4618
+
4619
+
4620
+ // public StringIntPairs getColumnRowLookup(int col) {
4621
+ // StringIntPairs sc = new StringIntPairs();
4622
+ // String[] column = getStringColumn(col);
4623
+ // for (int i = 0; i < column.length; i++) {
4624
+ // sc.set(column[i], i);
4625
+ // }
4626
+ // return sc;
4627
+ // }
4628
+
4629
+
4630
+ // public String[] getUniqueEntries(int column) {
4631
+ //// HashMap indices = new HashMap();
4632
+ //// for (int row = 0; row < rowCount; row++) {
4633
+ //// indices.put(data[row][column], this); // 'this' is a dummy
4634
+ //// }
4635
+ // StringIntPairs sc = getStringCount(column);
4636
+ // return sc.keys();
4637
+ // }
4638
+ //
4639
+ //
4640
+ // public StringIntPairs getStringCount(String columnName) {
4641
+ // return getStringCount(getColumnIndex(columnName));
4642
+ // }
4643
+ //
4644
+ //
4645
+ // public StringIntPairs getStringCount(int column) {
4646
+ // StringIntPairs outgoing = new StringIntPairs();
4647
+ // for (int row = 0; row < rowCount; row++) {
4648
+ // String entry = data[row][column];
4649
+ // if (entry != null) {
4650
+ // outgoing.increment(entry);
4651
+ // }
4652
+ // }
4653
+ // return outgoing;
4654
+ // }
4655
+ //
4656
+ //
4657
+ // /**
4658
+ // * Return an object that maps the String values in one column back to the
4659
+ // * row from which they came. For instance, if the "name" of each row is
4660
+ // * found in the first column, getColumnRowLookup(0) would return an object
4661
+ // * that would map each name back to its row.
4662
+ // */
4663
+ // public StringIntPairs getColumnRowLookup(int col) {
4664
+ // StringIntPairs sc = new StringIntPairs();
4665
+ // String[] column = getStringColumn(col);
4666
+ // for (int i = 0; i < column.length; i++) {
4667
+ // sc.set(column[i], i);
4668
+ // }
4669
+ // return sc;
4670
+ // }
4671
+
4672
+
4673
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4674
+
4675
+
4676
+ // TODO naming/whether to include
4677
+ protected Table createSubset(int[] rowSubset) {
4678
+ Table newbie = new Table();
4679
+ newbie.setColumnTitles(columnTitles); // also sets columns.length
4680
+ newbie.columnTypes = columnTypes;
4681
+ newbie.setRowCount(rowSubset.length);
4682
+
4683
+ for (int i = 0; i < rowSubset.length; i++) {
4684
+ int row = rowSubset[i];
4685
+ for (int col = 0; col < columns.length; col++) {
4686
+ switch (columnTypes[col]) {
4687
+ case STRING: newbie.setString(i, col, getString(row, col)); break;
4688
+ case INT: newbie.setInt(i, col, getInt(row, col)); break;
4689
+ case LONG: newbie.setLong(i, col, getLong(row, col)); break;
4690
+ case FLOAT: newbie.setFloat(i, col, getFloat(row, col)); break;
4691
+ case DOUBLE: newbie.setDouble(i, col, getDouble(row, col)); break;
4692
+ }
4693
+ }
4694
+ }
4695
+ return newbie;
4696
+ }
4697
+
4698
+
4699
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4700
+
4701
+
4702
+ /**
4703
+ * Searches the entire table for float values.
4704
+ * Returns missing float (Float.NaN by default) if no valid numbers found.
4705
+ */
4706
+ protected float getMaxFloat() {
4707
+ boolean found = false;
4708
+ float max = PConstants.MIN_FLOAT;
4709
+ for (int row = 0; row < getRowCount(); row++) {
4710
+ for (int col = 0; col < getColumnCount(); col++) {
4711
+ float value = getFloat(row, col);
4712
+ if (!Float.isNaN(value)) { // TODO no, this should be comparing to the missing value
4713
+ if (!found) {
4714
+ max = value;
4715
+ found = true;
4716
+ } else if (value > max) {
4717
+ max = value;
4718
+ }
4719
+ }
4720
+ }
4721
+ }
4722
+ return found ? max : missingFloat;
4723
+ }
4724
+
4725
+
4726
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4727
+
4728
+
4729
+ // converts a TSV or CSV file to binary.. do not use
4730
+ protected void convertBasic(BufferedReader reader, boolean tsv,
4731
+ File outputFile) throws IOException {
4732
+ FileOutputStream fos = new FileOutputStream(outputFile);
4733
+ BufferedOutputStream bos = new BufferedOutputStream(fos, 16384);
4734
+ DataOutputStream output = new DataOutputStream(bos);
4735
+ output.writeInt(0); // come back for row count
4736
+ output.writeInt(getColumnCount());
4737
+ if (columnTitles != null) {
4738
+ output.writeBoolean(true);
4739
+ for (String title : columnTitles) {
4740
+ output.writeUTF(title);
4741
+ }
4742
+ } else {
4743
+ output.writeBoolean(false);
4744
+ }
4745
+ for (int type : columnTypes) {
4746
+ output.writeInt(type);
4747
+ }
4748
+
4749
+ String line = null;
4750
+ //setRowCount(1);
4751
+ int prev = -1;
4752
+ int row = 0;
4753
+ while ((line = reader.readLine()) != null) {
4754
+ convertRow(output, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader));
4755
+ row++;
4756
+
4757
+ if (row % 10000 == 0) {
4758
+ if (row < rowCount) {
4759
+ int pct = (100 * row) / rowCount;
4760
+ if (pct != prev) {
4761
+ System.out.println(pct + "%");
4762
+ prev = pct;
4763
+ }
4764
+ }
4765
+ // try {
4766
+ // Thread.sleep(5);
4767
+ // } catch (InterruptedException e) {
4768
+ // e.printStackTrace();
4769
+ // }
4770
+ }
4771
+ }
4772
+ // shorten or lengthen based on what's left
4773
+ // if (row != getRowCount()) {
4774
+ // setRowCount(row);
4775
+ // }
4776
+
4777
+ // has to come afterwards, since these tables get built out during the conversion
4778
+ int col = 0;
4779
+ for (HashMapBlows hmb : columnCategories) {
4780
+ if (hmb == null) {
4781
+ output.writeInt(0);
4782
+ } else {
4783
+ hmb.write(output);
4784
+ hmb.writeln(PApplet.createWriter(new File(columnTitles[col] + ".categories")));
4785
+ // output.writeInt(hmb.size());
4786
+ // for (Map.Entry<String,Integer> e : hmb.entrySet()) {
4787
+ // output.writeUTF(e.getKey());
4788
+ // output.writeInt(e.getValue());
4789
+ // }
4790
+ }
4791
+ col++;
4792
+ }
4793
+
4794
+ output.flush();
4795
+ output.close();
4796
+
4797
+ // come back and write the row count
4798
+ RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
4799
+ raf.writeInt(rowCount);
4800
+ raf.close();
4801
+ }
4802
+
4803
+
4804
+ protected void convertRow(DataOutputStream output, String[] pieces) throws IOException {
4805
+ if (pieces.length > getColumnCount()) {
4806
+ throw new IllegalArgumentException("Row with too many columns: " +
4807
+ PApplet.join(pieces, ","));
4808
+ }
4809
+ // pieces.length may be less than columns.length, so loop over pieces
4810
+ for (int col = 0; col < pieces.length; col++) {
4811
+ switch (columnTypes[col]) {
4812
+ case STRING:
4813
+ output.writeUTF(pieces[col]);
4814
+ break;
4815
+ case INT:
4816
+ output.writeInt(PApplet.parseInt(pieces[col], missingInt));
4817
+ break;
4818
+ case LONG:
4819
+ try {
4820
+ output.writeLong(Long.parseLong(pieces[col]));
4821
+ } catch (NumberFormatException nfe) {
4822
+ output.writeLong(missingLong);
4823
+ }
4824
+ break;
4825
+ case FLOAT:
4826
+ output.writeFloat(PApplet.parseFloat(pieces[col], missingFloat));
4827
+ break;
4828
+ case DOUBLE:
4829
+ try {
4830
+ output.writeDouble(Double.parseDouble(pieces[col]));
4831
+ } catch (NumberFormatException nfe) {
4832
+ output.writeDouble(missingDouble);
4833
+ }
4834
+ break;
4835
+ case CATEGORY:
4836
+ String peace = pieces[col];
4837
+ if (peace.equals(missingString)) {
4838
+ output.writeInt(missingCategory);
4839
+ } else {
4840
+ output.writeInt(columnCategories[col].index(peace));
4841
+ }
4842
+ break;
4843
+ }
4844
+ }
4845
+ for (int col = pieces.length; col < getColumnCount(); col++) {
4846
+ switch (columnTypes[col]) {
4847
+ case STRING:
4848
+ output.writeUTF("");
4849
+ break;
4850
+ case INT:
4851
+ output.writeInt(missingInt);
4852
+ break;
4853
+ case LONG:
4854
+ output.writeLong(missingLong);
4855
+ break;
4856
+ case FLOAT:
4857
+ output.writeFloat(missingFloat);
4858
+ break;
4859
+ case DOUBLE:
4860
+ output.writeDouble(missingDouble);
4861
+ break;
4862
+ case CATEGORY:
4863
+ output.writeInt(missingCategory);
4864
+ break;
4865
+
4866
+ }
4867
+ }
4868
+ }
4869
+
4870
+
4871
+ /*
4872
+ private void convertRowCol(DataOutputStream output, int row, int col, String piece) {
4873
+ switch (columnTypes[col]) {
4874
+ case STRING:
4875
+ String[] stringData = (String[]) columns[col];
4876
+ stringData[row] = piece;
4877
+ break;
4878
+ case INT:
4879
+ int[] intData = (int[]) columns[col];
4880
+ intData[row] = PApplet.parseInt(piece, missingInt);
4881
+ break;
4882
+ case LONG:
4883
+ long[] longData = (long[]) columns[col];
4884
+ try {
4885
+ longData[row] = Long.parseLong(piece);
4886
+ } catch (NumberFormatException nfe) {
4887
+ longData[row] = missingLong;
4888
+ }
4889
+ break;
4890
+ case FLOAT:
4891
+ float[] floatData = (float[]) columns[col];
4892
+ floatData[row] = PApplet.parseFloat(piece, missingFloat);
4893
+ break;
4894
+ case DOUBLE:
4895
+ double[] doubleData = (double[]) columns[col];
4896
+ try {
4897
+ doubleData[row] = Double.parseDouble(piece);
4898
+ } catch (NumberFormatException nfe) {
4899
+ doubleData[row] = missingDouble;
4900
+ }
4901
+ break;
4902
+ default:
4903
+ throw new IllegalArgumentException("That's not a valid column type.");
4904
+ }
4905
+ }
4906
+ */
4907
+
4908
+
4909
+ /** Make a copy of the current table */
4910
+ public Table copy() {
4911
+ return new Table(rows());
4912
+ }
4913
+
4914
+
4915
+ public void write(PrintWriter writer) {
4916
+ writeTSV(writer);
4917
+ }
4918
+
4919
+
4920
+ public void print() {
4921
+ writeTSV(new PrintWriter(System.out));
4922
+ }
4923
+ }