001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jorphan.collections;
020:
021: import java.io.Serializable;
022: import java.sql.ResultSet;
023: import java.sql.ResultSetMetaData;
024: import java.sql.SQLException;
025: import java.util.ArrayList;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030:
031: import org.apache.jorphan.logging.LoggingManager;
032: import org.apache.jorphan.util.JOrphanUtils;
033: import org.apache.log.Logger;
034:
035: /**
036: * Use this class to store database-like data. This class uses rows and columns
037: * to organize its data. It has some convenience methods that allow fast loading
038: * and retrieval of the data into and out of string arrays. It is also handy for
039: * reading CSV files.
040: *
041: * @author Michael Stover (mstover1 at apache.org)
042: * @version $Revision: 582659 $
043: */
044: public class Data implements Serializable {
045: private static Logger log = LoggingManager.getLoggerForClass();
046:
047: Map data;
048:
049: // Map iterators = new HashMap();
050: // Hashtable dataLine;
051: ArrayList header;
052:
053: // saves current position in data Vector
054: int currentPos, size;
055:
056: /**
057: * Constructor - takes no arguments.
058: */
059: public Data() {
060: header = new ArrayList();
061: data = new HashMap();
062: currentPos = -1;
063: size = currentPos + 1;
064: }
065:
066: /**
067: * Replaces the given header name with a new header name.
068: *
069: * @param oldHeader
070: * Old header name.
071: * @param newHeader
072: * New header name.
073: */
074: public void replaceHeader(String oldHeader, String newHeader) {
075: List tempList;
076: int index = header.indexOf(oldHeader);
077: header.set(index, newHeader);
078: tempList = (List) data.remove(oldHeader);
079: data.put(newHeader, tempList);
080: }
081:
082: /**
083: * Adds the rows of the given Data object to this Data object.
084: *
085: * @param d
086: * data object to be appended to this one
087: */
088: public void append(Data d) {
089: boolean valid = true;
090: String[] headers = getHeaders();
091: String[] dHeaders = d.getHeaders();
092: if (headers.length != dHeaders.length) {
093: valid = false;
094: } else {
095: for (int count = 0; count < dHeaders.length; count++) {
096: if (!header.contains(dHeaders[count])) {
097: valid = false;
098: }
099: }
100: }
101:
102: if (valid) {
103: currentPos = size;
104: d.reset();
105: while (d.next()) {
106: for (int count = 0; count < headers.length; count++) {
107: addColumnValue(headers[count], d
108: .getColumnValue(headers[count]));
109: }
110: }
111: }
112: }
113:
114: /**
115: * Get the number of the current row.
116: *
117: * @return integer representing the current row
118: */
119: public int getCurrentPos() {
120: return currentPos;
121: }
122:
123: /**
124: * Removes the current row.
125: */
126: public void removeRow() {
127: List tempList;
128: Iterator it = data.keySet().iterator();
129: log.debug("removing row, size = " + size);
130: if (currentPos > -1 && currentPos < size) {
131: log.debug("got to here");
132: while (it.hasNext()) {
133: tempList = (List) data.get(it.next());
134: tempList.remove(currentPos);
135: }
136: if (currentPos > 0) {
137: currentPos--;
138: }
139: size--;
140: }
141: }
142:
143: public void removeRow(int index) {
144: log.debug("Removing row: " + index);
145: if (index < size) {
146: setCurrentPos(index);
147: log.debug("Setting currentpos to " + index);
148: removeRow();
149: }
150: }
151:
152: public void addRow() {
153: String[] headers = getHeaders();
154: List tempList = new ArrayList();
155: for (int i = 0; i < headers.length; i++) {
156: if ((tempList = (ArrayList) data.get(header.get(i))) == null) {
157: tempList = new ArrayList();
158: data.put(headers[i], tempList);
159: }
160: tempList.add("");
161: }
162: size = tempList.size();
163: setCurrentPos(size - 1);
164: }
165:
166: /**
167: * Sets the current pos. If value sent to method is not a valid number, the
168: * current position is set to one higher than the maximum.
169: *
170: * @param r
171: * position to set to.
172: */
173: public void setCurrentPos(int r) {
174: currentPos = r;
175: }
176:
177: /**
178: * Sorts the data using a given row as the sorting criteria. A boolean value
179: * indicates whether to sort ascending or descending.
180: *
181: * @param column
182: * name of column to use as sorting criteria.
183: * @param asc
184: * boolean value indicating whether to sort ascending or
185: * descending. True for asc, false for desc. Currently this
186: * feature is not enabled and all sorts are asc.
187: */
188: public void sort(String column, boolean asc) {
189: sortData(column, 0, size);
190: }
191:
192: private void swapRows(int row1, int row2) {
193: List temp;
194: Object o;
195: Iterator it = data.keySet().iterator();
196: while (it.hasNext()) {
197: temp = (List) data.get(it.next());
198: o = temp.get(row1);
199: temp.set(row1, temp.get(row2));
200: temp.set(row2, o);
201: }
202: }
203:
204: /**
205: * Private method that implements the quicksort algorithm to sort the rows
206: * of the Data object.
207: *
208: * @param column
209: * name of column to use as sorting criteria.
210: * @param start
211: * starting index (for quicksort algorithm).
212: * @param end
213: * ending index (for quicksort algorithm).
214: */
215: private void sortData(String column, int start, int end) {
216: int x = start, y = end - 1;
217: String basis = ((List) data.get(column)).get((x + y) / 2)
218: .toString();
219: if (x == y) {
220: return;
221: }
222:
223: while (x <= y) {
224: while (x < end
225: && ((List) data.get(column)).get(x).toString()
226: .compareTo(basis) < 0) {
227: x++;
228: }
229:
230: while (y >= (start - 1)
231: && ((List) data.get(column)).get(y).toString()
232: .compareTo(basis) > 0) {
233: y--;
234: }
235:
236: if (x <= y) {
237: swapRows(x, y);
238: x++;
239: y--;
240: }
241: }
242:
243: if (x == y) {
244: x++;
245: }
246:
247: y = end - x;
248:
249: if (x > 0) {
250: sortData(column, start, x);
251: }
252:
253: if (y > 0) {
254: sortData(column, x, end);
255: }
256: }
257:
258: /**
259: * Gets the number of rows in the Data object.
260: *
261: * @return number of rows in Data object.
262: */
263: public int size() {
264: return size;
265: } // end method
266:
267: /**
268: * Adds a value into the Data set at the current row, using a column name to
269: * find the column in which to insert the new value.
270: *
271: * @param column
272: * the name of the column to set.
273: * @param value
274: * value to set into column.
275: */
276: public void addColumnValue(String column, Object value) {
277: ArrayList tempList;
278: if ((tempList = (ArrayList) data.get(column)) == null) {
279: tempList = new ArrayList();
280: data.put(column, tempList);
281: }
282: int s = tempList.size();
283: if (currentPos == -1) {
284: currentPos = size;
285: }
286:
287: if (currentPos >= size) {
288: size = currentPos + 1;
289: }
290:
291: while (currentPos > s) {
292: s++;
293: tempList.add(null);
294: }
295:
296: if (currentPos == s) {
297: tempList.add(value);
298: } else {
299: tempList.set(currentPos, value);
300: }
301: }
302:
303: /**
304: * Returns the row number where a certain value is.
305: *
306: * @param column
307: * column to be searched for value.
308: * @param value
309: * object in Search of.
310: * @return row # where value exists.
311: */
312: public int findValue(String column, Object value) {
313: List list = (List) data.get(column);
314: int ret = -1;
315: ret = list.indexOf(value);
316: return ret;
317: }
318:
319: /**
320: * Sets the value in the Data set at the current row, using a column name to
321: * find the column in which to insert the new value.
322: *
323: * @param column
324: * the name of the column to set.
325: * @param value
326: * value to set into column.
327: */
328: public void setColumnValue(String column, Object value) {
329: List tempList;
330: if ((tempList = (List) data.get(column)) == null) {
331: tempList = new ArrayList();
332: data.put(column, tempList);
333: }
334:
335: if (currentPos == -1) {
336: currentPos = 0;
337: }
338:
339: if (currentPos >= size) {
340: size++;
341: tempList.add(value);
342: } else if (currentPos >= tempList.size()) {
343: tempList.add(value);
344: } else {
345: tempList.set(currentPos, value);
346: }
347: }
348:
349: /**
350: * Checks to see if a column exists in the Data object.
351: *
352: * @param column
353: * Name of column header to check for.
354: * @return True or False depending on whether the column exists.
355: */
356: public boolean hasHeader(String column) {
357: return data.containsKey(column);
358: }
359:
360: /**
361: * Sets the current position of the Data set to the next row.
362: *
363: * @return True if there is another row. False if there are no more rows.
364: */
365: public boolean next() {
366: return (++currentPos < size);
367: }
368:
369: /**
370: * Gets a Data object from a ResultSet.
371: *
372: * @param rs
373: * ResultSet passed in from a database query
374: * @return a Data object
375: * @throws java.sql.SQLException
376: */
377: public static Data getDataFromResultSet(ResultSet rs)
378: throws SQLException {
379: ResultSetMetaData meta = rs.getMetaData();
380: Data data = new Data();
381:
382: int numColumns = meta.getColumnCount();
383: String[] dbCols = new String[numColumns];
384: for (int i = 0; i < numColumns; i++) {
385: dbCols[i] = meta.getColumnName(i + 1);
386: data.addHeader(dbCols[i]);
387: }
388:
389: while (rs.next()) {
390: data.next();
391: for (int i = 0; i < numColumns; i++) {
392: Object o = rs.getObject(i + 1);
393: if (o instanceof byte[]) {
394: o = new String((byte[]) o);
395: }
396: data.addColumnValue(dbCols[i], o);
397: }
398: }
399: return data;
400: }
401:
402: /**
403: * Sets the current position of the Data set to the previous row.
404: *
405: * @return True if there is another row. False if there are no more rows.
406: */
407: public boolean previous() {
408: return (--currentPos >= 0);
409: }
410:
411: /**
412: * Resets the current position of the data set to just before the first
413: * element.
414: */
415: public void reset() {
416: currentPos = -1;
417: }
418:
419: /**
420: * Gets the value in the current row of the given column.
421: *
422: * @param column
423: * name of the column.
424: * @return an Object which holds the value of the column.
425: */
426: public Object getColumnValue(String column) {
427: try {
428: if (currentPos < size) {
429: return ((List) data.get(column)).get(currentPos);
430: } else {
431: return null;
432: }
433: } catch (Exception e) {
434: return null;
435: }
436: }
437:
438: /**
439: * Gets the value in the current row of the given column.
440: *
441: * @param column
442: * index of the column (starts at 0).
443: * @return an Object which holds the value of the column.
444: */
445: public Object getColumnValue(int column) {
446: String columnName = (String) header.get(column);
447: try {
448: if (currentPos < size) {
449: return ((List) data.get(columnName)).get(currentPos);
450: } else {
451: return null;
452: }
453: } catch (Exception e) {
454: return null;
455: }
456: }
457:
458: public Object getColumnValue(int column, int row) {
459: setCurrentPos(row);
460: return getColumnValue(column);
461: }
462:
463: public void removeColumn(int col) {
464: String columnName = (String) header.get(col);
465: data.remove(columnName);
466: header.remove(columnName);
467: }
468:
469: /**
470: * Sets the headers for the data set. Each header represents a column of
471: * data. Each row's data can be gotten with the column header name, which
472: * will always be a string.
473: *
474: * @param h
475: * array of strings representing the column headers.
476: */
477: public void setHeaders(String[] h) {
478: int x = 0;
479: header = new ArrayList(h.length);
480: for (x = 0; x < h.length; x++) {
481: header.add(h[x]);
482: data.put(h[x], new ArrayList());
483: }
484: }
485:
486: /**
487: * Returns a String array of the column headers.
488: *
489: * @return array of strings of the column headers.
490: */
491: public String[] getHeaders() {
492: String[] r = new String[header.size()];
493: if (r.length > 0) {
494: r = (String[]) header.toArray(r);
495: }
496: return r;
497: }
498:
499: public int getHeaderCount() {
500: return header.size();
501: }
502:
503: /**
504: * This method will retrieve every entry in a certain column. It returns an
505: * array of Objects from the column.
506: *
507: * @param columnName
508: * name of the column.
509: * @return array of Objects representing the data.
510: */
511: public List getColumnAsObjectArray(String columnName) {
512: return (List) data.get(columnName);
513: }
514:
515: /**
516: * This method will retrieve every entry in a certain column. It returns an
517: * array of strings from the column. Even if the data are not strings, they
518: * will be returned as strings in this method.
519: *
520: * @param columnName
521: * name of the column.
522: * @return array of Strings representing the data.
523: */
524: public String[] getColumn(String columnName) {
525: String[] returnValue;
526: Object o;
527: List temp = (List) data.get(columnName);
528: if (temp != null) {
529: returnValue = new String[temp.size()];
530: Iterator it = temp.iterator();
531: int index = 0;
532: while (it.hasNext()) {
533: o = it.next();
534: if (o != null) {
535: if (o instanceof String) {
536: returnValue[index++] = (String) o;
537: } else {
538: returnValue[index++] = o.toString();
539: }
540: }
541: }
542: } else {
543: returnValue = new String[0];
544: }
545: return returnValue;
546: }
547:
548: /**
549: * Use this method to set the entire data set. It takes an array of strings.
550: * It uses the first row as the headers, and the next rows as the data
551: * elements. Delimiter represents the delimiting character(s) that separate
552: * each item in a data row.
553: *
554: * @param contents
555: * array of strings, the first element is a list of the column
556: * headers, the next elements each represent a single row of
557: * data.
558: * @param delimiter
559: * the delimiter character that separates columns within the
560: * string array.
561: */
562: public void setData(String[] contents, String delimiter) {
563: setHeaders(JOrphanUtils.split(contents[0], delimiter));
564: int x = 1;
565: while (x < contents.length) {
566: setLine(JOrphanUtils.split(contents[x++], delimiter));
567: }
568: }
569:
570: /*
571: * Deletes a header from the Data object. Takes the column name as input. It
572: * will delete the entire column.
573: *
574: * public void deleteHeader(String s) {
575: * }
576: */
577:
578: /**
579: * Sets the data for every row in the column.
580: */
581: public void setColumnData(String colName, Object value) {
582: List list = this .getColumnAsObjectArray(colName);
583: while (list.size() < size()) {
584: list.add(value);
585: }
586: }
587:
588: public void setColumnData(int col, List data) {
589: reset();
590: Iterator iter = data.iterator();
591: String columnName = (String) header.get(col);
592: while (iter.hasNext()) {
593: next();
594: setColumnValue(columnName, iter.next());
595: }
596: }
597:
598: /**
599: * Adds a header name to the Data object.
600: *
601: * @param s
602: * name of header.
603: */
604: public void addHeader(String s) {
605: header.add(s);
606: data.put(s, new ArrayList(Math.max(size(), 100)));
607: }
608:
609: /**
610: * Sets a row of data using an array of strings as input. Each value in the
611: * array represents a column's value in that row. Assumes the order will be
612: * the same order in which the headers were added to the data set.
613: *
614: * @param line
615: * array of strings representing column values.
616: */
617: public void setLine(String[] line) {
618: List tempList;
619: String[] h = getHeaders();
620: for (int count = 0; count < h.length; count++) {
621: tempList = (List) data.get(h[count]);
622: if (count < line.length && line[count].length() > 0) {
623: tempList.add(line[count]);
624: } else {
625: tempList.add("N/A");
626: }
627: }
628: size++;
629: }
630:
631: /**
632: * Sets a row of data using an array of strings as input. Each value in the
633: * array represents a column's value in that row. Assumes the order will be
634: * the same order in which the headers were added to the data set.
635: *
636: * @param line
637: * array of strings representing column values.
638: * @param deflt
639: * default value to be placed in data if line is not as long as
640: * headers.
641: */
642: public void setLine(String[] line, String deflt) {
643: List tempList;
644: String[] h = getHeaders();
645: for (int count = 0; count < h.length; count++) {
646: tempList = (List) data.get(h[count]);
647: if (count < line.length && line[count].length() > 0) {
648: tempList.add(line[count]);
649: } else {
650: tempList.add(deflt);
651: }
652: }
653: size++;
654: }
655:
656: /**
657: * Returns all the data in the Data set as an array of strings. Each array
658: * gives a row of data, each column separated by tabs.
659: *
660: * @return array of strings.
661: */
662: public String[] getDataAsText() {
663: StringBuffer temp = new StringBuffer("");
664: String[] line = new String[size + 1];
665: String[] elements = getHeaders();
666: for (int count = 0; count < elements.length; count++) {
667: temp.append(elements[count]);
668: if (count + 1 < elements.length) {
669: temp.append("\t");
670: }
671: }
672: line[0] = temp.toString();
673: reset();
674: int index = 1;
675: while (next()) {
676: temp = new StringBuffer("");
677: for (int count = 0; count < elements.length; count++) {
678: temp.append(getColumnValue(count));
679: if (count + 1 < elements.length) {
680: temp.append("\t");
681: }
682: }
683: line[index++] = temp.toString();
684: }
685: return line;
686: }
687:
688: public String toString() {
689: String[] contents = getDataAsText();
690: StringBuffer sb = new StringBuffer();
691: boolean first = true;
692: for (int x = 0; x < contents.length; x++) {
693: if (!first) {
694: sb.append("\n");
695: } else {
696: first = false;
697: }
698: sb.append(contents[x]);
699: }
700: return sb.toString();
701: }
702: }
|