001: /*
002: #IFNDEF ALT_LICENSE
003: ThinWire(R) RIA Ajax Framework
004: Copyright (C) 2003-2007 Custom Credit Systems
005:
006: This library is free software; you can redistribute it and/or modify it under
007: the terms of the GNU Lesser General Public License as published by the Free
008: Software Foundation; either version 2.1 of the License, or (at your option) any
009: later version.
010:
011: This library is distributed in the hope that it will be useful, but WITHOUT ANY
012: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013: PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014:
015: You should have received a copy of the GNU Lesser General Public License along
016: with this library; if not, write to the Free Software Foundation, Inc., 59
017: Temple Place, Suite 330, Boston, MA 02111-1307 USA
018:
019: Users who would rather have a commercial license, warranty or support should
020: contact the following company who invented, built and supports the technology:
021:
022: Custom Credit Systems, Richardson, TX 75081, USA.
023: email: info@thinwire.com ph: +1 (888) 644-6405
024: http://www.thinwire.com
025: #ENDIF
026: [ v1.2_RC2 ]
027: */
028: package thinwire.util;
029:
030: import java.util.ArrayList;
031: import java.util.Collection;
032: import java.util.List;
033: import java.util.AbstractList;
034:
035: import thinwire.ui.event.ItemChangeEvent.Type;
036:
037: /**
038: * ArrayGrid is an implementation of the Grid interface which provides a disconnected dataset.<p>
039: * The samples below populate an ArrayGrid with values from an
040: * array and then print out the values in the ArrayGrid.<p>
041: * <h3>Notes</h3>
042: * <UL>
043: * <LI>Don't add the same Column to an ArrayGrid twice. The following code illustrates the error.
044: * <pre>
045: * Grid ag = new ArrayGrid();
046: * Grid.Column col = new ArrayGrid.Column();
047: * ag.getColumns().add(col);
048: * ag.getColumns().add(col); //!!! Don't do this.
049: * </pre>
050: * <LI>Don't add the same Row to an ArrayGrid twice.
051: * <pre>
052: * Grid ag = new ArrayGrid();
053: * Grid.Row row = new ArrayGrid.Row();
054: * ag.getRows().add(row);
055: * ag.getRows().add(row); //!!! Don't do this.
056: * </pre>
057: * <LI>If you replace a Row in an ArrayGrid, be careful with the replaced Row.
058: * If after replacing a Row, you retrieve an element from the replaced Row, the element
059: * will still be converted according to the ArrayGrid's policy.
060: * <pre>
061: * Grid ag = new ArrayGrid();
062: * Grid.Row row1 = new ArrayGrid.Row();
063: * ag.getRows().add(row1);
064: * Grid.Row row2 = new ArrayGrid.Row();
065: * Grid.row row3 = ag.getRows().set(0, row2); //Be careful with row3.
066: * Object rowVal = row3.get(0); //Be careful with rowVal.
067: * <pre>
068: * In the code above, row1 is replaced in ag with row2, and row3 is identical to
069: * row1. Despite its removal from ag, row1 (aka row3) still has ag as a parent. If you retrieve
070: * an element from row1 (aka row3), the value you retrieve may not be the exact Object placed in
071: * row1. The value you retrieve - e.g. rowVal in the code above - is a value converted from the
072: * original via ag's policy.
073: *
074: * </UL>
075: * <b>Sample Code:</b><br>
076: * <pre>
077: * int[][] values = new int[][]{ { 0, 1, 2, 3, 4 }, { 10, 11, 12, 13, 14 },
078: * { 20, 21, 22, 23, 24 }, { 30, 31, 32, 33, 34 }, { 40, 41, 42, 43, 44 } };
079: *
080: * ArrayGrid ag = new ArrayGrid();
081: *
082: * for (int i = 0; i < 5; i++) {
083: * ArrayGrid.Column col = new ArrayGrid.Column();
084: * ag.getColumns().add(col);
085: * }
086: *
087: * for (int i = 0; i < 5; i++) {
088: * ArrayGrid.Row row = new ArrayGrid.Row();
089: * ag.getRows().add(row);
090: *
091: * for (int j = 0; j < 5; j++) {
092: * row.set(j, new Integer(values[i][j]));
093: * }
094: * }
095: *
096: * System.out.println("\r\n");
097: *
098: * for (int i = 0; i < 5; i++) {
099: * ArrayGrid.Row row = (ArrayGrid.Row) ag.getRows().get(i);
100: * String line = "";
101: *
102: * for (int j = 0; j < 5; j++) {
103: * line += row.get(j) + " ";
104: * }
105: * System.out.println(line);
106: * }
107: * </pre>
108: * @author Joshua J. Gertzen
109: */
110: public class ArrayGrid<R extends ArrayGrid.Row, C extends ArrayGrid.Column>
111: implements Grid<R, C> {
112: private Class<? extends Row> rowType;
113: private Class<? extends Column> columnType;
114: private List<R> rows;
115: private List<C> columns;
116: private boolean ensuringSymmetry;
117: private Grid<R, C> table;
118:
119: /**
120: * Construct an ArrayGrid.
121: */
122: public ArrayGrid() {
123: this (null);
124: }
125:
126: /**
127: * Constructs an ArrayGrid based on another grid.
128: * @param g a grid that should be copied into this grid.
129: */
130: public ArrayGrid(Grid<R, C> g) {
131: this (null, Row.class, Column.class);
132: if (g != null)
133: columns.addAll(g.getColumns());
134: }
135:
136: /**
137: * Construct an ArrayGrid, specifying an optional inner Grid,
138: * a Row type, and a Column type.
139: *
140: * @param grid the inner Grid to which this ArrayGrid provides access. May be null.
141: * @param rowType the class to which this ArrayGrid's Rows belong
142: * @param columnType the class to which this ArrayGrid's Columns belong
143: * @throws ClassCastException if rowType does not extend Grid.Row or columnType does
144: * not extend Grid.Column.
145: */
146: protected ArrayGrid(Grid<R, C> grid, Class<? extends Row> rowType,
147: Class<? extends Column> columnType) {
148: if (!(Row.class.isAssignableFrom(rowType) && Column.class
149: .isAssignableFrom(columnType)))
150: throw new ClassCastException(
151: "the rowType must be a subclass of Grid.Row and the columnType must be a subclass of Grid.Column");
152:
153: this .rowType = rowType;
154: this .columnType = columnType;
155:
156: if (grid == null)
157: this .table = this ;
158: else
159: this .table = grid;
160:
161: rows = new RowList();
162: columns = new ColumnList();
163: }
164:
165: /**
166: * @see thinwire.util.Grid#getColumns
167: */
168: public List<C> getColumns() {
169: return columns;
170: }
171:
172: /**
173: * @see thinwire.util.Grid#getRows
174: */
175: public List<R> getRows() {
176: return rows;
177: }
178:
179: private C newColumnInstance() {
180: try {
181: return (C) columnType.newInstance();
182: } catch (InstantiationException e) {
183: throw new RuntimeException(e);
184: } catch (IllegalAccessException e) {
185: throw new RuntimeException(e);
186: }
187: }
188:
189: private R newRowInstance() {
190: try {
191: return (R) rowType.newInstance();
192: } catch (InstantiationException e) {
193: throw new RuntimeException(e);
194: } catch (IllegalAccessException e) {
195: throw new RuntimeException(e);
196: }
197: }
198:
199: private void ensureSymmetry(Row r, int addAtIndex) {
200: if (ensuringSymmetry)
201: return;
202: ensuringSymmetry = true;
203:
204: if (r != null) {
205: //NOTE: Only one of the two following loops will actually execute, never both
206: //Grow number of columns to match row size
207: while (columns.size() < r.size())
208: columns.add(newColumnInstance());
209:
210: //Grow row as necessary to match number of columns
211: while (r.size() < columns.size())
212: r.add(null);
213: }
214:
215: for (Grid.Row row : rows) {
216: if (row.size() < columns.size()) {
217: row.set(columns.size() - 1, null); //force the row to expand
218:
219: //Shift row values over
220: for (int i = columns.size() - 1; addAtIndex < i; i--)
221: row.set(i, row.get(i - 1));
222: row.set(addAtIndex, null);
223: }
224: }
225:
226: ensuringSymmetry = false;
227: }
228:
229: protected void fireItemChange(Type type, int rowIndex,
230: int columnIndex, Object oldValue, Object newValue) {
231:
232: }
233:
234: /**
235: * Contains an ArrayList of objects, each object associated with a different column.
236: * @see AbstractList
237: * @see thinwire.util.Grid.Row
238: */
239: public static class Row extends AbstractList<Object> implements
240: Grid.Row {
241: private ArrayGrid<? extends Row, ? extends Column> arrayGrid;
242: private Grid<? extends Grid.Row, ? extends Grid.Column> parent;
243: private List<Object> l;
244: private int rowIndex;
245:
246: /**
247: * Construct a Row.
248: */
249: public Row() {
250: l = new ArrayList<Object>(3);
251: }
252:
253: /**
254: * Construct a Row that contains the values of the specified Collection.
255: * @param c the new Row's values.
256: */
257: public Row(Collection<? extends Object> c) {
258: this ();
259: addAll(c);
260: }
261:
262: /**
263: * Construct a Row that contains the values of the specified Array.
264: * @param a the new Row's values.
265: */
266: public Row(Object... a) {
267: this ();
268:
269: for (Object o : a)
270: add(o);
271: }
272:
273: private void setParent(
274: Grid<? extends Grid.Row, ? extends Grid.Column> parent,
275: ArrayGrid<? extends Row, ? extends Column> arrayGrid) {
276: this .parent = parent;
277: this .arrayGrid = arrayGrid;
278: if (parent == null)
279: rowIndex = -1;
280: }
281:
282: /**
283: * Gets the grid that contains this row.
284: * @see thinwire.util.Grid.Row#getParent
285: */
286: public Grid getParent() {
287: return parent;
288: }
289:
290: private int getColumnIndexByName(String columnName) {
291: if (parent == null)
292: throw new IllegalStateException(
293: "cannot access a row column by name before the row is added to a grid");
294: List<? extends Grid.Column> columns = parent.getColumns();
295:
296: for (int i = columns.size() - 1; i >= 0; i--) {
297: if (((Column) columns.get(i)).getName()
298: .equalsIgnoreCase(columnName)) {
299: return i;
300: }
301: }
302:
303: throw new IllegalArgumentException(
304: "there is no column with the name '" + columnName
305: + "'");
306: }
307:
308: /*
309: * Sets the index of the current row.
310: * @param index The value to set the index to
311: */
312: private void setIndex(int index) {
313: if (parent == null)
314: throw new IllegalStateException(
315: "index cannot be set before the row is added to a grid");
316: if (parent.getRows().get(index) != this )
317: throw new IllegalArgumentException(
318: "this row is not at the specified index");
319: this .rowIndex = index;
320: }
321:
322: /**
323: * Gets the index for this row.
324: * @see thinwire.util.Grid.Row#getIndex()
325: */
326: public int getIndex() {
327: return rowIndex;
328: }
329:
330: /**
331: * @see thinwire.util.Grid.Row#get(java.lang.String)
332: */
333: public Object get(String columnName) {
334: return get(getColumnIndexByName(columnName));
335: }
336:
337: /**
338: * @see java.util.List#get(int)
339: */
340: public Object get(int index) {
341: return l.get(index);
342: }
343:
344: /**
345: * @see java.util.List#set(int, java.lang.Object)
346: */
347: public Object set(int index, Object o) {
348: if (parent != null) {
349: while (l.size() < parent.getColumns().size())
350: l.add(null);
351: }
352:
353: Object ret = l.set(index, o);
354: if (arrayGrid != null && !arrayGrid.ensuringSymmetry)
355: arrayGrid.fireItemChange(Type.SET, rowIndex, index,
356: ret, o);
357: return ret;
358: }
359:
360: public Object set(String columnName, Object o) {
361: return set(getColumnIndexByName(columnName), o);
362: }
363:
364: /**
365: * @see java.util.List#add(int, java.lang.Object)
366: */
367: public void add(int index, Object o) {
368: if (parent != null)
369: throw new UnsupportedOperationException(
370: "you cannot add an item to a row, you must instead add a column and then set the cell's value");
371: l.add(index, o);
372: modCount++;
373: }
374:
375: /**
376: * @see java.util.List#remove(int)
377: */
378: public Object remove(int index) {
379: if (parent != null)
380: throw new UnsupportedOperationException(
381: "you cannot remove an item from a row, you must instead remove a column or set this cell's value to a new value");
382: Object o = l.remove(index);
383: modCount++;
384: return o;
385: }
386:
387: /**
388: * @see java.util.Collection#size()
389: */
390: public int size() {
391: return l.size();
392: }
393: }
394:
395: /**
396: * Contains an ArrayList of objects, each object associated with a different row.
397: * @see java.util.AbstractList
398: * @see thinwire.util.Grid.Column
399: */
400: public static class Column extends AbstractList<Object> implements
401: Grid.Column {
402: private Grid<? extends Grid.Row, ? extends Grid.Column> parent;
403: private List<Object> l;
404: private int columnIndex;
405: private String name = "";
406:
407: /**
408: * Construct a Column.
409: */
410: public Column() {
411: columnIndex = -1;
412: }
413:
414: /**
415: * Construct a Column, specifying a Collection whose values will serve
416: * as the Column's values.
417: * @param c the new Column's values
418: */
419: public Column(Collection<? extends Object> c) {
420: this ();
421: addAll(c);
422: }
423:
424: /**
425: * Construct a Column that contains the values of the specified Array.
426: * @param a the new Column's values
427: */
428: public Column(Object... a) {
429: this ();
430:
431: for (Object o : a)
432: add(o);
433: }
434:
435: /**
436: * @see thinwire.util.Grid.Column#getName()
437: */
438: public String getName() {
439: return name;
440: }
441:
442: /**
443: * @see thinwire.util.Grid.Column#setName(java.lang.String)
444: */
445: public void setName(String name) {
446: this .name = name == null ? "" : name;
447: }
448:
449: /*
450: * Sets the Grid to be associated with this column
451: * @param parent The Grid interface
452: * @param arrayGrid the ArrayGrid object
453: */
454: private void setParent(
455: Grid<? extends Grid.Row, ? extends Grid.Column> parent,
456: ArrayGrid<? extends Row, ? extends Column> arrayGrid) {
457: this .parent = parent;
458: this .l = null;
459: if (parent == null)
460: columnIndex = -1;
461: modCount++;
462: }
463:
464: /**
465: * Gets the grid that contains this column.
466: * @see thinwire.util.Grid.Column#getParent()
467: */
468: public Grid getParent() {
469: return parent;
470: }
471:
472: /*
473: * Sets the index of the column
474: * @param columnIndex The value to set the column index to
475: */
476: private void setIndex(int columnIndex) {
477: if (parent == null)
478: throw new IllegalArgumentException(
479: "index cannot be set before the table is set");
480: if (parent.getColumns().get(columnIndex) != this )
481: throw new IllegalArgumentException(
482: "this column is not at the specified index");
483: this .columnIndex = columnIndex;
484: }
485:
486: /**
487: * @see thinwire.util.Grid.Column#getIndex()
488: */
489: public int getIndex() {
490: return columnIndex;
491: }
492:
493: /**
494: * @see java.util.List#get(int)
495: */
496: public Object get(int index) {
497: if (parent == null) {
498: if (index < 0 || index >= size() || l == null)
499: throw new IndexOutOfBoundsException("Index: "
500: + index + ", Size: " + size());
501: return l.get(index);
502: } else {
503: if (index < 0 || index >= size())
504: throw new IndexOutOfBoundsException("Index: "
505: + index + ", Size: " + size());
506: return ((Row) parent.getRows().get(index))
507: .get(columnIndex);
508: }
509: }
510:
511: /**
512: * @see java.util.List#set(int, java.lang.Object)
513: */
514: public Object set(int index, Object o) {
515: if (parent == null) {
516: if (index < 0 || index >= size() || l == null)
517: throw new IndexOutOfBoundsException("Index: "
518: + index + ", Size: " + size());
519: return l.set(index, o);
520: } else {
521: if (index < 0 || index >= size())
522: throw new IndexOutOfBoundsException("Index: "
523: + index + ", Size: " + size());
524: Row row = (Row) parent.getRows().get(index);
525: Object ret = row.set(columnIndex, o);
526: return ret;
527: }
528: }
529:
530: /**
531: * @see java.util.List#add(int, java.lang.Object)
532: */
533: public void add(int index, Object o) {
534: if (parent != null)
535: throw new UnsupportedOperationException(
536: "you cannot add an item to a column, you must instead add a row and then set the cell's value");
537: if (index < 0 || index > size())
538: throw new IndexOutOfBoundsException("Index: " + index
539: + ", Size: " + size());
540: if (l == null)
541: l = new ArrayList<Object>(3);
542: l.add(index, o);
543: modCount++;
544: }
545:
546: /**
547: * @see java.util.List#remove(int)
548: */
549: public Object remove(int index) {
550: if (parent != null)
551: throw new UnsupportedOperationException(
552: "you cannot remove an item from a column, you must instead remove a row or set this cell's value to a new value");
553: if (index < 0 || index > size() || l == null)
554: throw new IndexOutOfBoundsException("Index: " + index
555: + ", Size: " + size());
556: Object o = l.remove(index);
557: modCount++;
558: return o;
559: }
560:
561: /**
562: * @see java.util.Collection#size()
563: */
564: public int size() {
565: if (parent == null)
566: return l == null ? 0 : l.size();
567: else
568: return parent.getRows().size();
569: }
570: }
571:
572: private class RowList extends AbstractList<R> {
573: private List<R> l;
574:
575: private RowList() {
576: l = new ArrayList<R>();
577: }
578:
579: private R prepareRow(List<Object> o) {
580: R r;
581:
582: if (o instanceof ArrayGrid.Row) {
583: r = (R) o;
584: Grid t = r.getParent();
585:
586: if (t != null && t != table) {
587: R nr = newRowInstance();
588: nr.addAll(r);
589: r = nr;
590: }
591: } else {
592: R nr = newRowInstance();
593: nr.addAll(o);
594: r = nr;
595: }
596:
597: return r;
598: }
599:
600: /*
601: * @see java.util.List#get(int)
602: */
603: public R get(int index) {
604: return l.get(index);
605: }
606:
607: /*
608: * Replace the Row at the specified position.<p>
609: * Note: The returned Row is not an ordinary List. It
610: * Any calls to get(int) on the Row will return an
611: * Object formatted by the ArrayGrid's policy.
612: *
613: * @see java.util.List#set(int, java.lang.Object)
614: */
615: public R set(int index, R o) {
616: if (!(o instanceof List))
617: throw new ClassCastException("unsupported type");
618: R ret = l.get(index);
619: R r = prepareRow(o);
620: ensureSymmetry(r, r.size());
621: l.set(index, r);
622: //If the parent is set to null during a call to Collections.sort,
623: //problems arise. djv 12/08/2004
624: //ret.setParent(null, null);
625: r.setParent(table, ArrayGrid.this );
626: r.setIndex(index);
627: fireItemChange(Type.SET, index, -1, ret, r);
628: return ret;
629: }
630:
631: /*
632: * @see java.util.List#add(int, java.lang.Object)
633: */
634: public void add(int index, R o) {
635: if (!(o instanceof List))
636: throw new ClassCastException("unsupported type");
637: R r = prepareRow(o);
638: ensureSymmetry(r, r.size());
639: l.add(index, r);
640:
641: for (int i = index + 1, cnt = l.size(); i < cnt; i++)
642: ((Row) l.get(i)).setIndex(i);
643:
644: r.setParent(table, ArrayGrid.this );
645: r.setIndex(index);
646: modCount++;
647: fireItemChange(Type.ADD, index, -1, null, r);
648: }
649:
650: /*
651: * @see java.util.List#remove(int)
652: */
653: public R remove(int index) {
654: R r = l.get(index);
655: l.remove(index);
656: r.setParent(null, null);
657:
658: for (int i = index, cnt = l.size(); i < cnt; i++)
659: ((Row) l.get(i)).setIndex(i);
660:
661: modCount++;
662: fireItemChange(Type.REMOVE, index, -1, r, null);
663: return r;
664: }
665:
666: public void clear() {
667: List<R> ol = l;
668: l = new ArrayList<R>();
669: modCount++;
670:
671: for (int i = 0, cnt = ol.size(); i < cnt; i++) {
672: fireItemChange(Type.REMOVE, i, -1, ol.get(i), null);
673: }
674: }
675:
676: /*
677: * @see java.util.Collection#size()
678: */
679: public int size() {
680: return l.size();
681: }
682: }
683:
684: private class ColumnList extends AbstractList<C> {
685: private List<C> l;
686: private List<Object> values;
687:
688: private ColumnList() {
689: l = new ArrayList<C>();
690: }
691:
692: private C prepareColumn(List<Object> o) {
693: C c;
694: values = null;
695:
696: if (o instanceof ArrayGrid.Column) {
697: c = (C) o;
698: Grid t = c.getParent();
699:
700: if (t != null && t != table) {
701: values = c;
702: c = newColumnInstance();
703: c.setName(((Column) values).getName());
704: } else
705: values = new ArrayList<Object>(c);
706: } else {
707: values = o;
708: c = (C) newColumnInstance();
709: }
710:
711: return c;
712: }
713:
714: private void loadValues(int index) {
715: if (values != null) {
716: for (int i = 0, cnt = values.size(); i < cnt; i++) {
717: if (i >= rows.size())
718: rows.add(newRowInstance());
719: ((Row) rows.get(i)).set(index, values.get(i));
720: }
721:
722: values = null;
723: }
724: }
725:
726: private void saveValues(C c, int index) {
727: for (int i = 0, cnt = rows.size(); i < cnt; i++) {
728: c.add(((Row) rows.get(i)).get(index));
729: }
730: }
731:
732: /*
733: * @see java.util.List#get(int)
734: */
735: public C get(int index) {
736: return l.get(index);
737: }
738:
739: /*
740: * @see java.util.List#set(int, java.lang.Object)
741: */
742: public C set(int index, C o) {
743: if (!(o instanceof List))
744: throw new ClassCastException("unsupported type");
745: C ret = l.get(index);
746: C c = prepareColumn(o);
747: l.set(index, c);
748: ret.setParent(null, null);
749: saveValues(ret, index);
750: c.setParent(table, ArrayGrid.this );
751: c.setIndex(index);
752: ensureSymmetry(null, index);
753: loadValues(index);
754: fireItemChange(Type.SET, -1, index, ret, c);
755: return ret;
756: }
757:
758: /*
759: * @see java.util.List#add(int, java.lang.Object)
760: */
761: public void add(int index, C o) {
762: if (!(o instanceof List))
763: throw new ClassCastException("unsupported type");
764: C c = prepareColumn(o);
765: l.add(index, c);
766: c.setParent(table, ArrayGrid.this );
767: c.setIndex(index);
768: ensureSymmetry(null, index);
769:
770: for (int i = index + 1, cnt = l.size(); i < cnt; i++)
771: l.get(i).setIndex(i);
772:
773: loadValues(index);
774: modCount++;
775: fireItemChange(Type.ADD, -1, index, null, c);
776: }
777:
778: /*
779: * @see java.util.List#remove(int)
780: */
781: public C remove(int index) {
782: C c = l.remove(index);
783: c.setParent(null, null);
784: saveValues(c, index);
785:
786: for (int i = index, cnt = l.size(); i < cnt; i++)
787: l.get(i).setIndex(i);
788:
789: modCount++;
790: fireItemChange(Type.REMOVE, -1, index, c, null);
791: return c;
792: }
793:
794: /*
795: * @see java.util.Collection#size()
796: */
797: public int size() {
798: return l.size();
799: }
800: }
801: }
|