View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.layout;
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  import org.apache.myfaces.tobago.layout.LayoutTokens;
25  import org.apache.myfaces.tobago.layout.Orientation;
26  import org.apache.myfaces.tobago.layout.RelativeLayoutToken;
27  
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  
32  public class Grid {
33  
34    private static final Logger LOG = LoggerFactory.getLogger(Grid.class);
35  
36    // TODO: check if it is faster with arrays.
37    /**
38     * The rectangular data as a 1-dim list
39     */
40    private List<Cell> cells;
41  
42    private BankHead[] columnHeads;
43    private BankHead[] rowHeads;
44  
45    private int columnCount;
46    private int rowCount;
47  
48    private int columnCursor;
49    private int rowCursor;
50  
51    private boolean columnOverflow;
52    private boolean rowOverflow;
53  
54    private List<Integer> errorIndexes;
55  
56    public Grid(final LayoutTokens columns, final LayoutTokens rows) {
57      assert columns.getSize() > 0;
58      assert rows.getSize() > 0;
59  
60      this.columnCount = columns.getSize(); 
61      this.rowCount = rows.getSize(); 
62      
63      this.columnHeads = new BankHead[columnCount];
64      for (int i = 0; i < columnCount; i++) {
65        columnHeads[i] = new BankHead(columns.get(i));
66      }
67      this.rowHeads = new BankHead[rowCount];
68      for (int i = 0; i < rowCount; i++) {
69        rowHeads[i] = new BankHead(rows.get(i));
70      }
71      final int size = columnCount * rowCount;
72      this.cells = new ArrayList<Cell>(size);
73      for (int i = 0; i < size; i++) {
74        this.cells.add(null);
75      }
76    }
77  
78    public void add(final OriginCell cell, int columnSpan, final int rowSpan) {
79  
80      assert columnSpan > 0;
81      assert rowSpan > 0;
82  
83      boolean error = false;
84  
85      if (columnSpan + columnCursor > columnCount) {
86        LOG.warn("The columnSpan is to large for the actual position in the grid. Will be fixed. "
87            + "columnSpan=" + columnSpan + " columnCursor=" + columnCursor + " columnCount=" + columnCount);
88        columnSpan = columnCount - columnCursor;
89        error = true;
90      }
91  
92      cell.setColumnSpan(columnSpan);
93      cell.setRowSpan(rowSpan);
94  
95      for (int i = 1; i < columnSpan; i++) {
96        if (getCell(i + columnCursor, rowCursor) != null) {
97          LOG.warn("The columnSpan is to large for the actual position in the grid. Will be fixed. "
98              + "columnSpan=" + columnSpan + " columnCursor=" + columnCursor + " columnCount=" + columnCount);
99          columnSpan = i - 1;
100         error = true;
101       }
102     }
103 
104     for (int j = 0; j < rowSpan; j++) {
105       for (int i = 0; i < columnSpan; i++) {
106         final Cell actualCell;
107         if (i == 0 && j == 0) {
108           actualCell = cell;
109         } else {
110           actualCell = new SpanCell(cell, i == 0, j == 0);
111         }
112         assert getCell(i + columnCursor, j + rowCursor) == null : "Position in the cell must be free.";
113         setCell(i + columnCursor, j + rowCursor, actualCell);
114         if (error) {
115           addError(i + columnCursor, j + rowCursor);
116         }
117       }
118     }
119 
120     findNextFreeCell();
121   }
122 
123   public Cell getCell(final int i, final int j, final Orientation orientation) {
124     return orientation == Orientation.HORIZONTAL ? getCell(i, j) : getCell(j, i);
125   }
126 
127   public Cell getCell(final int column, final int row) {
128     assert column >= 0 && column < columnCount : "column=" + column + " columnCount=" + columnCount;
129     assert row >= 0 : "row=" + row;
130 
131     if (row >= rowCount) {
132       return null;
133     } else {
134       return cells.get(column + row * columnCount);
135     }
136   }
137 
138   public void setCell(final int column, final int row, final Cell cell) {
139     if (row >= rowCount) {
140       enlarge(row - rowCount + 1);
141     }
142     cells.set(column + row * columnCount, cell);
143   }
144 
145   private void findNextFreeCell() {
146     for (; rowCursor < rowCount; rowCursor++) {
147       for (; columnCursor < columnCount; columnCursor++) {
148         if (getCell(columnCursor, rowCursor) == null) {
149           return;
150         }
151       }
152       columnCursor = 0;
153     }
154   }
155 
156   public BankHead[] getBankHeads(final Orientation orientation) {
157     return orientation == Orientation.HORIZONTAL ? columnHeads : rowHeads;
158   }
159   
160   private void enlarge(final int newRows) {
161     
162     // process cells
163     for (int i = 0; i < newRows; i++) {
164       for (int j = 0; j < columnCount; j++) {
165         cells.add(null);
166       }
167     }
168 
169     // process heads
170     final BankHead[] newRowHeads = new BankHead[rowCount + newRows];
171     System.arraycopy(rowHeads, 0, newRowHeads, 0, rowHeads.length);
172     rowHeads = newRowHeads;
173     // todo: shorter in jdk 1.6: rowHeads = Arrays.copyOf(rowHeads, rowHeads.length + newRows);
174     
175     for (int i = rowCount; i < rowCount + newRows; i++) {
176       rowHeads[i] = new BankHead(RelativeLayoutToken.DEFAULT_INSTANCE);
177     }
178 
179     rowCount += newRows;
180   }
181 
182   public boolean isOverflow(final Orientation orientation) {
183     return orientation == Orientation.HORIZONTAL ? columnOverflow : rowOverflow;
184   }
185 
186   public void setOverflow(final boolean overflow, final Orientation orientation) {
187     if (orientation == Orientation.HORIZONTAL) {
188       this.columnOverflow = overflow;
189     } else {
190       this.rowOverflow = overflow;
191     }
192   }
193 
194   public boolean isOverflow() {
195     return columnOverflow;
196   }
197 
198   public void setColumnOverflow(final boolean columnOverflow) {
199     this.columnOverflow = columnOverflow;
200   }
201 
202   public boolean isRowOverflow() {
203     return rowOverflow;
204   }
205 
206   public void setRowOverflow(final boolean rowOverflow) {
207     this.rowOverflow = rowOverflow;
208   }
209 
210   public void addError(final int i, final int j) {
211     if (errorIndexes == null) {
212       errorIndexes = new ArrayList<Integer>();
213     }
214     errorIndexes.add(j * columnCount + i);
215   }
216 
217   public boolean hasError(final int i, final int j) {
218     if (errorIndexes == null) {
219       return false;
220     }
221     return errorIndexes.contains(j * columnCount + i);
222   }
223 
224   public int getColumnCount() {
225     return columnCount;
226   }
227 
228   public int getRowCount() {
229     return rowCount;
230   }
231 
232   /**
233    * Prints the state of the grid as an Unicode shape like this:
234    * ┏━┳━┳━┳━┯━┓
235    * ┃█┃█┃█┃█│➞┃
236    * ┠─╂─╊━╇━┿━┫
237    * ┃⬇┃⬇┃█│➞│➞┃
238    * ┣━╉─╊━╈━╈━┫
239    * ┃█┃⬇┃█┃█┃█┃
240    * ┣━╇━╇━╉─╊━┩
241    * ┃█│➞│➞┃⬇┃◌│
242    * ┡━┿━┿━╉─╂─┤
243    * │◌│◌│◌┃⬇┃◌│
244    * └─┴─┴─┺━┹─┘
245    */
246   public String gridAsString() {
247 
248     final StringBuilder builder = new StringBuilder();
249 
250     // top of grid
251     for (int i = 0; i < columnCount; i++) {
252       if (i == 0) {
253         if (getCell(i, 0) != null) {
254           builder.append("┏");
255         } else {
256           builder.append("┌");
257         }
258       } else {
259         final Cell c = getCell(i - 1, 0);
260         final Cell d = getCell(i, 0);
261         if (c == null && d == null) {
262           builder.append("┬");
263         } else {
264           if (connected(c, d)) {
265             builder.append("┯");
266           } else {
267             if (c == null) {
268               builder.append("┲");
269             } else if (d == null) {
270               builder.append("┱");
271             } else {
272               builder.append("┳");
273             }
274           }
275         }
276       }
277       if (getCell(i, 0) != null) {
278         builder.append("━");
279       } else {
280         builder.append("─");
281       }
282 
283     }
284     if (getCell(columnCount - 1, 0) != null) {
285       builder.append("┓");
286     } else {
287       builder.append("┐");
288     }
289     builder.append("\n");
290 
291     for (int j = 0; j < rowCount; j++) {
292 
293       // between the cells
294       if (j != 0) {
295         for (int i = 0; i < columnCount; i++) {
296           if (i == 0) {
297             final Cell b = getCell(0, j - 1);
298             final Cell d = getCell(0, j);
299             if (b == null && d == null) {
300               builder.append("├");
301             } else {
302               if (connected(b, d)) {
303                 builder.append("┠");
304               } else {
305                 if (b == null) {
306                   builder.append("┢");
307                 } else if (d == null) {
308                   builder.append("┡");
309                 } else {
310                   builder.append("┣");
311                 }
312               }
313             }
314           } else {
315             final Cell a = getCell(i - 1, j - 1);
316             final Cell b = getCell(i, j - 1);
317             final Cell c = getCell(i - 1, j);
318             final Cell d = getCell(i, j);
319 //            a│b
320 //            ─┼─
321 //            c│d
322             if (connected(a, b)) {
323               if (connected(c, d)) {
324                 if (connected(a, c)) {
325                   builder.append("┼");
326                 } else {
327                   builder.append("┿");
328                 }
329               } else {
330                 builder.append("╈");
331               }
332             } else {
333               if (connected(c, d)) {
334                 if (connected(a, c)) {
335                   builder.append("╄");
336                 } else if (connected(b, d)) {
337                   builder.append("╃");
338                 } else {
339                   builder.append("╇");
340                 }
341               } else {
342                 if (connected(a, c)) {
343                   if (connected(b, d)) {
344                     builder.append("╂");
345                   } else {
346                     builder.append("╊");
347                   }
348                 } else {
349                   if (connected(b, d)) {
350                     builder.append("╉");
351                   } else {
352                     builder.append("╋");
353                   }
354                 }
355               }
356             }
357           }
358           final Cell a = getCell(i, j - 1);
359           final Cell c = getCell(i, j);
360           if (connected(a, c)) {
361             builder.append("─");
362           } else {
363             builder.append("━");
364           }
365         }
366         final Cell a = getCell(columnCount - 1, j - 1);
367         final Cell c = getCell(columnCount - 1, j);
368         if (a == null && c == null) {
369           builder.append("┤");
370         } else {
371           if (connected(a, c)) {
372             builder.append("┨");
373           } else {
374             if (a == null) {
375               builder.append("┪");
376             } else if (c == null) {
377               builder.append("┩");
378             } else {
379               builder.append("┫");
380             }
381           }
382         }
383         builder.append("\n");
384       }
385 
386       // cell
387       for (int i = 0; i < columnCount; i++) {
388         if (i == 0) {
389           if (getCell(i, j) != null) {
390             builder.append("┃");
391           } else {
392             builder.append("│");
393           }
394         } else {
395           final Cell c = getCell(i - 1, j);
396           final Cell d = getCell(i, j);
397           if (connected(c, d)) {
398             builder.append("│");
399           } else {
400             builder.append("┃");
401           }
402         }
403         if (hasError(i, j)) {
404           builder.append("✖"); //↯
405         } else {
406           if (getCell(i, j) instanceof OriginCell) {
407             builder.append("█");
408           } else if (getCell(i, j) instanceof SpanCell) {
409             if (j == 0) {
410               builder.append("➞");
411             } else {
412               final Cell a = getCell(i, j - 1);
413               final Cell c = getCell(i, j);
414               if (connected(a, c)) {
415                 builder.append("⬇");
416               } else {
417                 builder.append("➞");
418               }
419             }
420           } else {
421             builder.append("◌");
422           }
423         }
424       }
425       if (getCell(columnCount - 1, j) != null) {
426         builder.append("┃");
427       } else {
428         builder.append("│");
429       }
430       builder.append("\n");
431     }
432 
433     //last bottom
434     for (int i = 0; i < columnCount; i++) {
435       if (i == 0) {
436         if (getCell(0, rowCount - 1) != null) {
437           builder.append("┗");
438         } else {
439           builder.append("└");
440         }
441       } else {
442         final Cell a = getCell(i - 1, rowCount - 1);
443         final Cell b = getCell(i, rowCount - 1);
444         if (a == null && b == null) {
445           builder.append("┴");
446         } else {
447           if (connected(a, b)) {
448             builder.append("┷");
449           } else {
450             if (a == null) {
451               builder.append("┺");
452             } else if (b == null) {
453               builder.append("┹");
454             } else {
455               builder.append("┻");
456             }
457           }
458         }
459       }
460       if (getCell(i, rowCount - 1) != null) {
461         builder.append("━");
462       } else {
463         builder.append("─");
464       }
465     }
466     if (getCell(columnCount - 1, rowCount - 1) != null) {
467       builder.append("┛");
468     } else {
469       builder.append("┘");
470     }
471     builder.append("\n");
472 
473     return builder.toString();
474   }
475 
476   @Override
477   public String toString() {
478     final StringBuilder builder = new StringBuilder();
479     builder.append(gridAsString());
480     builder.append("columnHeads=");
481     builder.append(Arrays.toString(columnHeads));
482     builder.append("\n");
483     builder.append("rowHeads=");
484     builder.append(Arrays.toString(rowHeads));
485     builder.append("\n");
486     return builder.toString();
487   }
488 
489   private boolean connected(final Cell a, final Cell b) {
490     if (a == null && b == null) {
491       return true;
492     }
493     if (a == null || b == null) {
494       return false;
495     }
496     return a.getOrigin().equals(b.getOrigin());
497   }
498 }