001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with the
009: * License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
013: * the specific language governing rights and limitations under the License.
014: *
015: * Alternatively, the contents of this file may be used under the terms of
016: * either the GNU General Public License Version 2 or later (the "GPL"), or the
017: * GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
018: * case the provisions of the GPL or the LGPL are applicable instead of those
019: * above. If you wish to allow use of your version of this file only under the
020: * terms of either the GPL or the LGPL, and not to allow others to use your
021: * version of this file under the terms of the MPL, indicate your decision by
022: * deleting the provisions above and replace them with the notice and other
023: * provisions required by the GPL or the LGPL. If you do not delete the
024: * provisions above, a recipient may use your version of this file under the
025: * terms of any one of the MPL, the GPL or the LGPL.
026: */
027:
028: package nextapp.echo2.webcontainer.syncpeer;
029:
030: import java.util.ArrayList;
031: import java.util.BitSet;
032: import java.util.List;
033:
034: import nextapp.echo2.app.Component;
035: import nextapp.echo2.app.Extent;
036: import nextapp.echo2.app.Grid;
037: import nextapp.echo2.app.LayoutData;
038: import nextapp.echo2.app.layout.GridLayoutData;
039:
040: /**
041: * Provides analysis of a <code>Grid</code> for rendering purposes.
042: * <p>
043: * This object defines the <code>Grid</code> in terms of two axes, "x" and
044: * "y". The axes are transposed based on whether the <code>origin</code>
045: * property of the <code>Grid</code> is horizontal or vertical. For
046: * horizontally-oriented <code>Grid</code>s, the x-axis represents columns
047: * and the y-axis represents rows. For vertically oriented <code>Grid</code>s,
048: * the x-axis represents rows and the y-axis represents columns.
049: * <p>
050: * Once a <code>GridProcessor</code> has been instantiated, the rendering
051: * <code>GridPeer</code> can make inquiries to it to determine how the
052: * HTML table representing the Grid should be rendered.
053: * <p>
054: * Upon instantiation, the dimensions of the grid are calculated, and the
055: * content of each cell within those dimensions is determined. By specifying
056: * an "x" and "y" coordinate to various getXXX() methods, the renderer can
057: * determine what <code>Component</code> exists at a particular coordinate,
058: * how many rows and columns that <code>Component</code> spans, and the index
059: * of the <code>Component</code> within its parent <code>Grid</code>'s
060: * children.
061: * <p>
062: * This class should not be extended or used by classes outside of the Echo
063: * framework.
064: */
065: public class GridProcessor {
066:
067: private static final Integer DEFAULT_SIZE = new Integer(
068: Grid.DEFAULT_SIZE);
069:
070: /**
071: * Internal representation of a rendered cell at a specific coordinate.
072: */
073: private class Cell {
074:
075: /**
076: * Creates a new <code>Cell</code>.
077: *
078: * @param component the represented <code>Component</code>
079: * @param index the index of <code>component</code> within the
080: * parent <code>Grid</code>
081: * @param xSpan the current calculated x-span of the cell
082: * @param ySpan the current calculated y-span of the cell
083: */
084: private Cell(Component component, int index, int xSpan,
085: int ySpan) {
086: super ();
087: this .component = component;
088: this .index = index;
089: this .xSpan = xSpan;
090: this .ySpan = ySpan;
091: }
092:
093: /** the current calculated x-span of the cell */
094: private int xSpan;
095:
096: /** the current calculated y-span of the cell */
097: private int ySpan;
098:
099: /** the represented <code>Component</code> */
100: private Component component;
101:
102: /**
103: * the index of <code>component</code> within the parent
104: * <code>Grid</code>
105: */
106: private int index;
107: }
108:
109: private Grid grid;
110:
111: /**
112: * A <code>List</code> containing arrays of <code>Cell</code>s.
113: * Each contained array of <code>Cell</code>s represents a single point on
114: * the y-axis. The cell indices represent points on the x-axis.
115: * Individual y-axis entries should be obtained via the
116: * <code>getCellArray()</code> method.
117: *
118: * @see #getCellArray(int, boolean)
119: */
120: private List cellArrays;
121:
122: /** The current calculated size of the x-axis. */
123: private int gridXSize;
124:
125: /** The current calculated size of the y-axis. */
126: private int gridYSize;
127:
128: /**
129: * The calculated dimensions of each array of cells on the x-axis.
130: * These values may change as a result of x-axis reductions.
131: */
132: private List xExtents;
133:
134: /**
135: * The calculated dimensions of each array of cells on the y-axis.
136: * These values may change as a result of y-axis reductions.
137: */
138: private List yExtents;
139:
140: /**
141: * Flag indicating whether grid has a horizontal (or vertical) orientation.
142: */
143: private boolean horizontalOrientation;
144:
145: /**
146: * Creates a new <code>GridProcessor</code> for the specified
147: * <code>Grid</code>. Creating a new <code>GridProcessor</code> will
148: * cause immediately analyze the <code>Grid</code> which will immediately
149: * consume processor and memory resources. Such operations should be done at
150: * most once per rendering.
151: *
152: * @param grid the <code>Grid</code>
153: */
154: public GridProcessor(Grid grid) {
155: super ();
156: this .grid = grid;
157: cellArrays = new ArrayList();
158:
159: Integer orientationValue = (Integer) grid
160: .getRenderProperty(Grid.PROPERTY_ORIENTATION);
161: int orientation = orientationValue == null ? Grid.ORIENTATION_HORIZONTAL
162: : orientationValue.intValue();
163: horizontalOrientation = orientation != Grid.ORIENTATION_VERTICAL;
164:
165: Cell[] cells = createCells();
166: if (cells == null) {
167: // Special case: empty Grid.
168: gridXSize = 0;
169: gridYSize = 0;
170: return;
171: }
172: renderCellMatrix(cells);
173:
174: calculateExtents();
175:
176: reduceY();
177: reduceX();
178: trimX();
179: }
180:
181: private void calculateExtents() {
182: String xProperty = horizontalOrientation ? Grid.PROPERTY_COLUMN_WIDTH
183: : Grid.PROPERTY_ROW_HEIGHT;
184: String yProperty = horizontalOrientation ? Grid.PROPERTY_ROW_HEIGHT
185: : Grid.PROPERTY_COLUMN_WIDTH;
186:
187: xExtents = new ArrayList();
188: for (int i = 0; i < gridXSize; ++i) {
189: xExtents.add(grid.getRenderIndexedProperty(xProperty, i));
190: }
191:
192: yExtents = new ArrayList();
193: for (int i = 0; i < gridYSize; ++i) {
194: yExtents.add(grid.getRenderIndexedProperty(yProperty, i));
195: }
196: }
197:
198: /**
199: * Creates a one-dimensional array of <code>Cell</code> instances
200: * representing all visible components contained within the
201: * <code>Grid</code>.
202: *
203: * @return the array of <code>Cell</code>s
204: */
205: private Cell[] createCells() {
206: Component[] children = grid.getVisibleComponents();
207:
208: if (children.length == 0) {
209: // Abort if Grid is empty.
210: return null;
211: }
212:
213: Cell[] cells = new Cell[children.length];
214:
215: for (int i = 0; i < children.length; ++i) {
216: LayoutData layoutData = (LayoutData) children[i]
217: .getRenderProperty(Grid.PROPERTY_LAYOUT_DATA);
218: if (layoutData instanceof GridLayoutData) {
219: GridLayoutData gcLayoutData = (GridLayoutData) layoutData;
220: int xSpan = horizontalOrientation ? gcLayoutData
221: .getColumnSpan() : gcLayoutData.getRowSpan();
222: int ySpan = horizontalOrientation ? gcLayoutData
223: .getRowSpan() : gcLayoutData.getColumnSpan();
224: cells[i] = new Cell(children[i], i, xSpan, ySpan);
225: } else {
226: cells[i] = new Cell(children[i], i, 1, 1);
227: }
228: }
229: return cells;
230: }
231:
232: /**
233: * Retrieves the array of cells representing a particular coordinate on
234: * the y-axis of the rendered representation.
235: *
236: * @param y the y-axis index
237: * @param expand a flag indicating whether the <code>CellArray</code>
238: * should be expanded in the event the given y-axis does not
239: * exist.
240: * @see #cellArrays
241: */
242: private Cell[] getCellArray(int y, boolean expand) {
243: while (expand && y >= cellArrays.size()) {
244: cellArrays.add(new Cell[gridXSize]);
245: }
246: return (Cell[]) cellArrays.get(y);
247: }
248:
249: /**
250: * Returns the <code>Component</code> that should be rendered at the
251: * specified position.
252: *
253: * @param column the column index
254: * @param row the row index
255: * @return the <code>Component</code> (may be null)
256: */
257: public Component getContent(int column, int row) {
258: if (horizontalOrientation) {
259: Cell cell = getCellArray(row, false)[column];
260: return cell == null ? null : cell.component;
261: } else {
262: Cell cell = getCellArray(column, false)[row];
263: return cell == null ? null : cell.component;
264: }
265: }
266:
267: /**
268: * Returns the index of the <code>Component</code> that should be rendered
269: * at the specified position within its parent <code>Grid</code>
270: * container.
271: *
272: * @param column the column index
273: * @param row the row index
274: * @return the index of the <code>Component</code> within its
275: * container.
276: */
277: public int getComponentIndex(int column, int row) {
278: if (horizontalOrientation) {
279: Cell cell = getCellArray(row, false)[column];
280: return cell == null ? -1 : cell.index;
281: } else {
282: Cell cell = getCellArray(column, false)[row];
283: return cell == null ? -1 : cell.index;
284: }
285: }
286:
287: /**
288: * Returns the number of columns that should be rendered.
289: *
290: * @return the number of rendered columns
291: */
292: public int getColumnCount() {
293: return horizontalOrientation ? gridXSize : gridYSize;
294: }
295:
296: /**
297: * Returns the number of rows that should be rendered.
298: *
299: * @return the number of rendered rows
300: */
301: public int getRowCount() {
302: return horizontalOrientation ? gridYSize : gridXSize;
303: }
304:
305: /**
306: * Returns the width of the specified column index
307: *
308: * @param column the column index
309: * @return the width
310: */
311: public Extent getColumnWidth(int column) {
312: return (Extent) (horizontalOrientation ? xExtents : yExtents)
313: .get(column);
314: }
315:
316: /**
317: * Returns the height of the specified row index
318: *
319: * @param row the row index
320: * @return the height
321: */
322: public Extent getRowHeight(int row) {
323: return (Extent) (horizontalOrientation ? yExtents : xExtents)
324: .get(row);
325: }
326:
327: /**
328: * Returns the column span of the cell at the specified rendered index.
329: *
330: * @param column the column index
331: * @param row the row index
332: * @return the column span (-1 will be returned in the event that no
333: * cell exists at the specified index)
334: */
335: public int getColumnSpan(int column, int row) {
336: if (horizontalOrientation) {
337: Cell cell = getCellArray(row, false)[column];
338: return cell == null ? -1 : cell.xSpan;
339: } else {
340: Cell cell = getCellArray(column, false)[row];
341: return cell == null ? -1 : cell.ySpan;
342: }
343: }
344:
345: /**
346: * Returns the row span of the cell at the specified rendered index.
347: *
348: * @param column the column index
349: * @param row the row index
350: * @return the row span (-1 will be returned in the event that no
351: * cell exists at the specified index)
352: */
353: public int getRowSpan(int column, int row) {
354: if (horizontalOrientation) {
355: Cell cell = getCellArray(row, false)[column];
356: return cell == null ? -1 : cell.ySpan;
357: } else {
358: Cell cell = getCellArray(column, false)[row];
359: return cell == null ? -1 : cell.xSpan;
360: }
361: }
362:
363: /**
364: * Remove duplicates from the x-axis where all cells simply
365: * "span over" a given x-axis coordinate.
366: */
367: private void reduceX() {
368: // Determine duplicate cell sets on x-axis.
369: BitSet xRemoves = new BitSet();
370: int x = 1;
371: int length = getCellArray(0, false).length;
372: while (x < length) {
373: int y = 0;
374: boolean identical = true;
375: while (y < cellArrays.size()) {
376: if (getCellArray(y, false)[x] != getCellArray(y, false)[x - 1]) {
377: identical = false;
378: break;
379: }
380: ++y;
381: }
382: if (identical) {
383: xRemoves.set(x, true);
384: }
385: ++x;
386: }
387:
388: // If no reductions are necessary on the x-axis, do nothing.
389: if (xRemoves.nextSetBit(0) == -1) {
390: return;
391: }
392:
393: for (int removedX = gridXSize - 1; removedX >= 0; --removedX) {
394: if (!xRemoves.get(removedX)) {
395: continue;
396: }
397:
398: for (int y = 0; y < gridYSize; ++y) {
399: if (y == 0
400: || getCellArray(y, false)[removedX - 1] != getCellArray(
401: y - 1, false)[removedX - 1]) {
402: // Reduce x-span, taking care not to reduce it multiple times if cell has a y-span.
403: Cell[] cellArray = getCellArray(y, false);
404: if (cellArray[removedX - 1] != null) {
405: --getCellArray(y, false)[removedX - 1].xSpan;
406: }
407: }
408: for (x = removedX; x < gridXSize - 1; ++x) {
409: getCellArray(y, false)[x] = getCellArray(y, false)[x + 1];
410: }
411: }
412:
413: // Calculate Extent-size of merged indices.
414: Extent retainedExtent = (Extent) xExtents.get(removedX - 1);
415: Extent removedExtent = (Extent) xExtents.get(removedX);
416: xExtents.remove(removedX);
417: if (removedExtent != null) {
418: xExtents.set(removedX - 1, Extent.add(removedExtent,
419: retainedExtent));
420: }
421:
422: --gridXSize;
423: }
424: }
425:
426: /**
427: * Remove duplicates from the y-axis where all cells simply
428: * "span over" a given y-axis coordinate.
429: */
430: private void reduceY() {
431: // Determine duplicate cell sets on y-axis.
432: BitSet yRemoves = new BitSet();
433: int y = 1;
434:
435: int size = cellArrays.size();
436: Cell[] previousCellArray;
437: Cell[] currentCellArray = getCellArray(0, false);
438:
439: while (y < size) {
440: previousCellArray = currentCellArray;
441: currentCellArray = getCellArray(y, false);
442:
443: int x = 0;
444: boolean identical = true;
445:
446: while (x < currentCellArray.length) {
447: if (currentCellArray[x] != previousCellArray[x]) {
448: identical = false;
449: break;
450: }
451: ++x;
452: }
453: if (identical) {
454: yRemoves.set(y, true);
455: }
456:
457: ++y;
458: }
459:
460: // If no reductions are necessary on the y-axis, do nothing.
461: if (yRemoves.nextSetBit(0) == -1) {
462: return;
463: }
464:
465: for (int removedY = gridYSize - 1; removedY >= 0; --removedY) {
466: if (!yRemoves.get(removedY)) {
467: continue;
468: }
469:
470: // Shorten the y-spans of the cell array that will be retained to
471: // reflect the fact that a cell array is being removed.
472: Cell[] retainedCellArray = getCellArray(removedY - 1, false);
473: for (int x = 0; x < gridXSize; ++x) {
474: if (x == 0
475: || retainedCellArray[x] != retainedCellArray[x - 1]) {
476: // Reduce y-span, taking care not to reduce it multiple times if cell has a x-span.
477: if (retainedCellArray[x] != null) {
478: --retainedCellArray[x].ySpan;
479: }
480: }
481: }
482:
483: // Remove the duplicate cell array.
484: cellArrays.remove(removedY);
485:
486: // Calculate Extent-size of merged indices.
487: Extent retainedExtent = (Extent) yExtents.get(removedY - 1);
488: Extent removedExtent = (Extent) yExtents.get(removedY);
489: yExtents.remove(removedY);
490: if (removedExtent != null) {
491: yExtents.set(removedY - 1, Extent.add(removedExtent,
492: retainedExtent));
493: }
494:
495: // Decrement the grid size to reflect cell array removal.
496: --gridYSize;
497: }
498: }
499:
500: private void renderCellMatrix(Cell[] cells) {
501: gridXSize = ((Integer) grid.getRenderProperty(
502: Grid.PROPERTY_SIZE, DEFAULT_SIZE)).intValue();
503:
504: int x = 0, y = 0;
505: Cell[] yCells = getCellArray(y, true);
506: for (int componentIndex = 0; componentIndex < cells.length; ++componentIndex) {
507:
508: // Set x-span to fill remaining size in the even SPAN_FILL has been specified or if the cell would
509: // otherwise extend past the specified size.
510: if (cells[componentIndex].xSpan == GridLayoutData.SPAN_FILL
511: || cells[componentIndex].xSpan > gridXSize - x) {
512: cells[componentIndex].xSpan = gridXSize - x;
513: }
514:
515: // Set x-span of any cell INCORRECTLY set to negative value to 1 (note that SPAN_FILL has already been handled).
516: if (cells[componentIndex].xSpan < 1) {
517: cells[componentIndex].xSpan = 1;
518: }
519: // Set y-span of any cell INCORRECTLY set to negative value (or more likely SPAN_FILL) to 1.
520: if (cells[componentIndex].ySpan < 1) {
521: cells[componentIndex].ySpan = 1;
522: }
523:
524: if (cells[componentIndex].xSpan != 1
525: || cells[componentIndex].ySpan != 1) {
526: // Scan to ensure no y-spans are blocking this x-span.
527: // If a y-span is blocking, shorten the x-span to not
528: // interfere.
529: for (int xIndex = 1; xIndex < cells[componentIndex].xSpan; ++xIndex) {
530: if (yCells[x + xIndex] != null) {
531: // Blocking component found.
532: cells[componentIndex].xSpan = xIndex;
533: break;
534: }
535: }
536: for (int yIndex = 0; yIndex < cells[componentIndex].ySpan; ++yIndex) {
537: Cell[] yIndexCells = getCellArray(y + yIndex, true);
538: for (int xIndex = 0; xIndex < cells[componentIndex].xSpan; ++xIndex) {
539: yIndexCells[x + xIndex] = cells[componentIndex];
540: }
541: }
542: }
543: yCells[x] = cells[componentIndex];
544:
545: if (componentIndex < cells.length - 1) {
546: // Move rendering cursor.
547: boolean nextRenderPointFound = false;
548: while (!nextRenderPointFound) {
549: if (x < gridXSize - 1) {
550: ++x;
551: } else {
552: // Move cursor to next line.
553: x = 0;
554: ++y;
555: yCells = getCellArray(y, true);
556:
557: }
558: nextRenderPointFound = yCells[x] == null;
559: }
560: }
561: }
562:
563: // Store actual 'y' dimension.
564: gridYSize = cellArrays.size();
565: }
566:
567: /**
568: * Special case: Trim excess null cells from Grid x-size if the
569: * Grid y-size is 1.
570: */
571: private void trimX() {
572: if (gridYSize == 1) {
573: Cell[] cellArray = getCellArray(0, false);
574: for (int i = 0; i < gridXSize; ++i) {
575: if (cellArray[i] == null) {
576: gridXSize = i;
577: }
578: }
579: }
580: }
581: }
|