001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.layout;
017
018 import java.awt.Component;
019 import java.io.IOException;
020 import java.io.StreamTokenizer;
021 import java.io.StringReader;
022 import java.util.ArrayList;
023 import java.util.Collections;
024 import java.util.HashMap;
025 import java.util.HashSet;
026 import java.util.Iterator;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Set;
030
031 import javax.swing.JComponent;
032 import javax.swing.JPanel;
033
034 import org.springframework.richclient.application.Application;
035 import org.springframework.richclient.application.ApplicationServicesLocator;
036 import org.springframework.richclient.factory.ComponentFactory;
037 import org.springframework.richclient.util.CustomizableFocusTraversalPolicy;
038 import org.springframework.util.Assert;
039 import org.springframework.util.StringUtils;
040
041 import com.jgoodies.forms.factories.FormFactory;
042 import com.jgoodies.forms.layout.CellConstraints;
043 import com.jgoodies.forms.layout.ColumnSpec;
044 import com.jgoodies.forms.layout.FormLayout;
045 import com.jgoodies.forms.layout.RowSpec;
046
047 /**
048 * A panel builder that provides the capability to quickly build grid based
049 * forms. The builder allows for layout to be defined in a way that will be
050 * familiar to anyone used to HTML tables and JGoodies Forms. Key features:
051 * <ul>
052 * <li>unlike HTML, cells automatically span all empty columns to the right.
053 * This can be disabled by setting "colSpan=1"</li>
054 * <li>support for gap rows and columns. You don't need to keep track of gap
055 * rows or columns when specifying row or column spans</li>
056 * <li>need only define colSpec and rowSpec when it varies from the default.
057 * Save you having to work out column specs before you start laying out the form
058 * </li>
059 * <li>rows and columns can be aliased with a group ID which save you having to
060 * keep track of row or column indexes for grouping. This also makes grouping
061 * less fragile when the table layout changes</li>
062 * </ul>
063 * <strong>Example: </strong> <br>
064 * <pre>
065 * TableLayoutBuilder table = new TableLayoutBuilder();
066 * table
067 * .row()
068 * .separator("General 1")
069 * .row()
070 * .cell(new JLabel("Company"), "colSpec=right:pref colGrId=labels")
071 * .labelGapCol()
072 * .cell(new JFormattedTextField())
073 * .row()
074 * .cell(new JLabel("Contact"))
075 * .cell(new JFormattedTextField())
076 * .unrelatedGapRow()
077 * .separator("Propeller")
078 * .row()
079 * .cell(new JLabel("PTI [kW]")).cell(new JFormattedTextField())
080 * .unrelatedGapCol()
081 * .cell(new JLabel("Description"), "colSpec=right:pref colGrId=labels")
082 * .labelGapCol()
083 * .cell(new JScrollPane(new JTextArea()), "rowspan=3")
084 * .row()
085 * .cell(new JLabel("R [mm]"))
086 * .cell(new JFormattedTextField())
087 * .cell()
088 * .row()
089 * .cell(new JLabel("D [mm]"))
090 * .cell(new JFormattedTextField())
091 * .cell();
092 * table.getPanel();
093 * </pre>
094 *
095 * @author oliverh
096 */
097 public class TableLayoutBuilder implements LayoutBuilder {
098
099 public static final String DEFAULT_LABEL_ATTRIBUTES = "colGrId=label colSpec=left:pref";
100
101 public static final String ALIGN = "align";
102
103 public static final String VALIGN = "valign";
104
105 public static final String ROWSPEC = "rowSpec";
106
107 public static final String COLSPEC = "colSpec";
108
109 public static final String ROWSPAN = "rowSpan";
110
111 public static final String COLSPAN = "colSpan";
112
113 public static final String ROWGROUPID = "rowGrId";
114
115 public static final String COLGROUPID = "colGrId";
116
117 /** Constant indicating column major focus traversal order. */
118 public static final int COLUMN_MAJOR_FOCUS_ORDER = 1;
119
120 /** Constant indicating row major focus traversal order. */
121 public static final int ROW_MAJOR_FOCUS_ORDER = 2;
122
123 private List rowSpecs = new ArrayList();
124
125 private List rowOccupiers = new ArrayList();
126
127 private List columnSpecs = new ArrayList();
128
129 private Map gapCols = new HashMap();
130
131 private Map gapRows = new HashMap();
132
133 private Map rowGroups = new HashMap();
134
135 private Map colGroups = new HashMap();
136
137 private int[][] adjustedColGroupIndices;
138
139 private int[][] adjustedRowGroupIndices;
140
141 private Cell lastCC = null;
142
143 private int maxColumns = 0;
144
145 private int currentRow = -1;
146
147 private int currentCol = 0;
148
149 private List items = new ArrayList();
150
151 private JPanel panel;
152
153 private List focusOrder = null;
154
155 private ComponentFactory componentFactory;
156
157 /**
158 * Creates a new TableLayoutBuilder.
159 */
160 public TableLayoutBuilder() {
161 // this will cause the panel to be lazily
162 // created in the getPanel method
163 this.panel = null;
164 }
165
166 /**
167 * Creates a new TableLayoutBuilder which will perform it's layout
168 * in the supplied JPanel. Note that any components that are already
169 * contained by the panel will be removed.
170 */
171 public TableLayoutBuilder(JPanel panel) {
172 Assert.notNull(panel, "panel is required");
173 this.panel = panel;
174 panel.removeAll();
175 }
176
177 /**
178 * Returns the {@link ComponentFactory}that this uses to create things like
179 * labels.
180 *
181 * @return if not explicitly set, this uses the {@link Application}'s
182 */
183 public ComponentFactory getComponentFactory() {
184 if (componentFactory == null) {
185 componentFactory = (ComponentFactory)ApplicationServicesLocator.services().getService(ComponentFactory.class);
186 }
187 return componentFactory;
188 }
189
190 /**
191 * Sets the {@link ComponentFactory}that this uses to create things like
192 * labels.
193 */
194 public void setComponentFactory(ComponentFactory componentFactory) {
195 this.componentFactory = componentFactory;
196 }
197
198 /**
199 * Returns the current row (zero-based) that the builder is putting
200 * components in.
201 */
202 public int getCurrentRow() {
203 return currentRow == -1 ? 0 : currentRow;
204 }
205
206 /**
207 * Returns the current column (zero-based) that the builder is putting
208 * components in.
209 */
210 public int getCurrentCol() {
211 return currentCol;
212 }
213
214 /**
215 * Inserts a new row. No gap row is inserted before this row.
216 */
217 public TableLayoutBuilder row() {
218 ++currentRow;
219 lastCC = null;
220 maxColumns = Math.max(maxColumns, currentCol);
221 currentCol = 0;
222 return this;
223 }
224
225 /**
226 * Inserts a new row. A gap row with specified RowSpec will be inserted
227 * before this row.
228 */
229 public TableLayoutBuilder row(String gapRowSpec) {
230 return row(new RowSpec(gapRowSpec));
231 }
232
233 /**
234 * Inserts a new row. A gap row with specified RowSpec will be inserted
235 * before this row.
236 */
237 public TableLayoutBuilder row(RowSpec gapRowSpec) {
238 row();
239 gapRows.put(new Integer(currentRow), gapRowSpec);
240 return this;
241 }
242
243 /**
244 * Inserts a new row. A related component gap row will be inserted before
245 * this row.
246 */
247 public TableLayoutBuilder relatedGapRow() {
248 return row(FormFactory.RELATED_GAP_ROWSPEC);
249 }
250
251 /**
252 * Inserts a new row. An unrelated component gap row will be inserted before
253 * this row.
254 */
255 public TableLayoutBuilder unrelatedGapRow() {
256 return row(FormFactory.UNRELATED_GAP_ROWSPEC);
257 }
258
259 /**
260 * Inserts an empty cell at the current row/column.
261 */
262 public TableLayoutBuilder cell() {
263 return cell("");
264 }
265
266 /**
267 * Inserts an empty cell at the current row/column. Attributes may be zero or
268 * more of rowSpec, columnSpec, colGrId and rowGrId.
269 */
270 public TableLayoutBuilder cell(String attributes) {
271 cellInternal(null, attributes);
272 return this;
273 }
274
275 /**
276 * Inserts a component at the current row/column.
277 */
278 public TableLayoutBuilder cell(JComponent component) {
279 return cell(component, "");
280 }
281
282 /**
283 * Inserts a component at the current row/column. Attributes may be zero or
284 * more of rowSpec, columnSpec, colGrId, rowGrId, align and valign.
285 */
286 public TableLayoutBuilder cell(JComponent component, String attributes) {
287 Cell cc = cellInternal(component, attributes);
288 lastCC = cc;
289 items.add(cc);
290 return this;
291 }
292
293 /**
294 * Inserts a related component gap column.
295 */
296 public TableLayoutBuilder gapCol() {
297 return relatedGapCol();
298 }
299
300 /**
301 * Inserts a gap column with the specified colSpec.
302 */
303 public TableLayoutBuilder gapCol(String colSpec) {
304 return gapCol(new ColumnSpec(colSpec));
305 }
306
307 /**
308 * Inserts a gap column with the specified colSpec.
309 */
310 public TableLayoutBuilder gapCol(ColumnSpec colSpec) {
311 gapCols.put(new Integer(currentCol), colSpec);
312 return this;
313 }
314
315 /**
316 * Inserts a label component gap column.
317 */
318 public TableLayoutBuilder labelGapCol() {
319 return gapCol(FormFactory.LABEL_COMPONENT_GAP_COLSPEC);
320 }
321
322 /**
323 * Inserts a unrelated component gap column.
324 */
325 public TableLayoutBuilder unrelatedGapCol() {
326 return gapCol(FormFactory.UNRELATED_GAP_COLSPEC);
327 }
328
329 /**
330 * Inserts a related component gap column.
331 */
332 public TableLayoutBuilder relatedGapCol() {
333 return gapCol(FormFactory.RELATED_GAP_COLSPEC);
334 }
335
336 /**
337 * Inserts a separator with the given label.
338 *
339 * @deprecated this is a layout builder, creating components should be done elsewhere, use cell() methods instead
340 */
341 public TableLayoutBuilder separator(String labelKey) {
342 return separator(labelKey, "");
343 }
344
345 /**
346 * Inserts a separator with the given label. Attributes my be zero or more of
347 * rowSpec, columnSpec, colGrId, rowGrId, align and valign.
348 *
349 * @deprecated this is a layout builder, creating components should be done elsewhere, use cell() methods instead
350 */
351 public TableLayoutBuilder separator(String labelKey, String attributes) {
352 Cell cc = cellInternal(getComponentFactory().createLabeledSeparator(labelKey), attributes);
353 lastCC = cc;
354 items.add(cc);
355 return this;
356 }
357
358 /**
359 * Return true if there is a gap column to the left of the current cell
360 */
361 public boolean hasGapToLeft() {
362 return currentCol == 0 || gapCols.get(new Integer(currentCol)) != null;
363 }
364
365 /**
366 * Return true if there is a gap row above of the current cell
367 */
368 public boolean hasGapAbove() {
369 return currentRow == 0 || gapRows.get(new Integer(currentRow)) != null;
370 }
371
372 /**
373 * Creates and returns a JPanel with all the given components in it, using
374 * the "hints" that were provided to the builder.
375 *
376 * @return a new JPanel with the components laid-out in it
377 */
378 public JPanel getPanel() {
379 if (panel == null) {
380 panel = getComponentFactory().createPanel();
381 }
382 insertMissingSpecs();
383 fixColSpans();
384 fillInGaps();
385 fillPanel();
386
387 if( focusOrder != null ) {
388 installFocusOrder( focusOrder );
389 }
390
391 return panel;
392 }
393
394 private Cell cellInternal(JComponent component, String attributes) {
395 nextCol();
396 Map attributeMap = getAttributes(attributes);
397 RowSpec rowSpec = getRowSpec(getAttribute(ROWSPEC, attributeMap, ""));
398 if (rowSpec != null) {
399 setRowSpec(currentRow, rowSpec);
400 }
401 ColumnSpec columnSpec = getColumnSpec(getAttribute(COLSPEC, attributeMap, ""));
402 if (columnSpec != null) {
403 setColumnSpec(currentCol, columnSpec);
404 }
405 addRowGroup(getAttribute(ROWGROUPID, attributeMap, null));
406 addColGroup(getAttribute(COLGROUPID, attributeMap, null));
407
408 Cell cc = createCell(component, attributeMap);
409 currentCol = cc.endCol < cc.startCol ? cc.startCol : cc.endCol;
410 markContained(cc);
411 return cc;
412 }
413
414 private void addRowGroup(String groupId) {
415 if (StringUtils.hasText(groupId)) {
416 Set group = (Set)rowGroups.get(groupId);
417 if (group == null) {
418 group = new HashSet();
419 rowGroups.put(groupId, group);
420 }
421 group.add(new Integer(getCurrentRow()));
422 }
423 }
424
425 private void addColGroup(String groupId) {
426 if (StringUtils.hasText(groupId)) {
427 Set group = (Set)colGroups.get(groupId);
428 if (group == null) {
429 group = new HashSet();
430 colGroups.put(groupId, group);
431 }
432 group.add(new Integer(currentCol));
433 }
434 }
435
436 private void setRowSpec(int row, RowSpec rowSpec) {
437 if (row >= rowSpecs.size()) {
438 int missingSpecs = row - rowSpecs.size() + 1;
439 for (int i = 0; i < missingSpecs; i++) {
440 rowSpecs.add(getDefaultRowSpec());
441 }
442 }
443 rowSpecs.set(row, rowSpec);
444 }
445
446 private void setColumnSpec(int col, ColumnSpec columnSpec) {
447 col = col - 1;
448 if (col >= columnSpecs.size()) {
449 int missingSpecs = col - columnSpecs.size() + 1;
450 for (int i = 0; i < missingSpecs; i++) {
451 columnSpecs.add(getDefaultColSpec());
452 }
453 }
454 columnSpecs.set(col, columnSpec);
455 }
456
457 private RowSpec getRowSpec(String rowSpec) {
458 if (StringUtils.hasText(rowSpec))
459 return new RowSpec(rowSpec);
460
461 return null;
462 }
463
464 private ColumnSpec getColumnSpec(String columnSpec) {
465 if (StringUtils.hasText(columnSpec))
466 return new ColumnSpec(columnSpec);
467
468 return null;
469 }
470
471 private void nextCol() {
472 if (lastCC != null && lastCC.endCol < lastCC.startCol) {
473 lastCC.endCol = lastCC.startCol;
474 lastCC = null;
475 }
476 // make sure that the first row has been created
477 if (currentRow == -1) {
478 row();
479 }
480 // now find the first unoccupied column
481 do {
482 ++currentCol;
483 }
484 while (getOccupier(currentRow, currentCol) != null);
485 }
486
487 private Cell getOccupier(int row, int col) {
488 List occupiers = getOccupiers(row);
489 if (col >= occupiers.size()) {
490 return null;
491 }
492 return (Cell)occupiers.get(col);
493 }
494
495 private List getOccupiers(int row) {
496 if (row >= rowOccupiers.size()) {
497 int numMissingRows = (row - rowOccupiers.size()) + 1;
498 for (int i = 0; i < numMissingRows; i++) {
499 rowOccupiers.add(new ArrayList());
500 }
501 }
502 return (List)rowOccupiers.get(row);
503 }
504
505 private void markContained(Cell cc) {
506 setOccupier(cc, cc.startRow, cc.endRow, cc.startCol, cc.endCol < cc.startCol ? cc.startCol : cc.endCol);
507 }
508
509 private void setOccupier(Cell occupier, int startRow, int endRow, int startCol, int endCol) {
510 for (int row = startRow; row <= endRow; row++) {
511 List occupiers = getOccupiers(row);
512 if (endCol >= occupiers.size()) {
513 int numMissingCols = (endCol - occupiers.size()) + 1;
514 for (int i = 0; i < numMissingCols; i++) {
515 occupiers.add(null);
516 }
517 }
518 for (int i = startCol; i <= endCol; i++) {
519 occupiers.set(i, occupier);
520 }
521 }
522
523 }
524
525 private Cell createCell(JComponent component, Map attributes) {
526 String align = getAttribute(ALIGN, attributes, "default");
527 String valign = getAttribute(VALIGN, attributes, "default");
528 int colSpan;
529 try {
530 colSpan = Integer.parseInt(getAttribute(COLSPAN, attributes, "-1"));
531 }
532 catch (NumberFormatException e) {
533 throw new IllegalArgumentException("Attribute 'colspan' must be an integer.");
534 }
535 int rowSpan;
536 try {
537 rowSpan = Integer.parseInt(getAttribute(ROWSPAN, attributes, "1"));
538 }
539 catch (NumberFormatException e) {
540 throw new IllegalArgumentException("Attribute 'rowspan' must be an integer.");
541 }
542
543 return new Cell(component, getCurrentCol(), getCurrentRow(), colSpan, rowSpan, align + "," + valign);
544 }
545
546 private void fixColSpans() {
547 for (Iterator i = items.iterator(); i.hasNext();) {
548 Cell cc = (Cell)i.next();
549 if (cc.endCol < cc.startCol) {
550 int endCol = cc.startCol;
551 while (endCol < maxColumns && getOccupier(cc.startRow, endCol + 1) == null) {
552 ++endCol;
553 }
554 cc.endCol = endCol;
555 }
556 markContained(cc);
557 }
558 }
559
560 private void fillInGaps() {
561 List adjustedCols = new ArrayList();
562 int adjustedCol = 0;
563 for (int col = 0; col < maxColumns; col++, adjustedCol++) {
564 ColumnSpec colSpec = (ColumnSpec)gapCols.get(new Integer(col));
565 if (colSpec != null) {
566 columnSpecs.add(adjustedCol, colSpec);
567 adjustedCol++;
568 }
569 adjustedCols.add(new Integer(adjustedCol + 1));
570 }
571 List adjustedRows = new ArrayList();
572 int adjustedRow = 0;
573 int numRows = rowSpecs.size();
574 for (int row = 0; row < numRows; row++, adjustedRow++) {
575 RowSpec rowSpec = (RowSpec)gapRows.get(new Integer(row));
576 if (rowSpec != null) {
577 rowSpecs.add(adjustedRow, rowSpec);
578 adjustedRow++;
579 }
580 adjustedRows.add(new Integer(adjustedRow));
581 }
582 for (Iterator i = items.iterator(); i.hasNext();) {
583 Cell cc = (Cell)i.next();
584 cc.startCol = ((Integer)adjustedCols.get(cc.startCol - 1)).intValue();
585 cc.endCol = ((Integer)adjustedCols.get(cc.endCol - 1)).intValue();
586 cc.startRow = ((Integer)adjustedRows.get(cc.startRow)).intValue();
587 cc.endRow = ((Integer)adjustedRows.get(cc.endRow)).intValue();
588 }
589 adjustedColGroupIndices = new int[colGroups.size()][];
590 int groupsCount = 0;
591 for (Iterator i = colGroups.values().iterator(); i.hasNext();) {
592 Set group = (Set)i.next();
593 adjustedColGroupIndices[groupsCount] = new int[group.size()];
594 int groupCount = 0;
595 for (Iterator j = group.iterator(); j.hasNext();) {
596 adjustedColGroupIndices[groupsCount][groupCount++] = ((Integer)adjustedCols.get(((Integer)j.next()).intValue() - 1)).intValue();
597 }
598 groupsCount++;
599 }
600
601 adjustedRowGroupIndices = new int[rowGroups.size()][];
602 groupsCount = 0;
603 for (Iterator i = rowGroups.values().iterator(); i.hasNext();) {
604 Set group = (Set)i.next();
605 adjustedRowGroupIndices[groupsCount] = new int[group.size()];
606 int groupCount = 0;
607 for (Iterator j = group.iterator(); j.hasNext();) {
608 adjustedRowGroupIndices[groupsCount][groupCount++] = ((Integer)adjustedRows.get(((Integer)j.next()).intValue() - 1)).intValue();
609 }
610 groupsCount++;
611 }
612 }
613
614 /**
615 * Set the focus traversal order.
616 * @param order forcus traversal order. Must be one of {@link #COLUMN_MAJOR_FOCUS_ORDER}
617 * or {@link #ROW_MAJOR_FOCUS_ORDER}.
618 */
619 public void setFocusTraversalOrder( int traversalOrder ) {
620 Assert.isTrue( traversalOrder == COLUMN_MAJOR_FOCUS_ORDER || traversalOrder == ROW_MAJOR_FOCUS_ORDER,
621 "traversalOrder must be one of COLUMN_MAJOR_FOCUS_ORDER or ROW_MAJOR_FOCUS_ORDER");
622
623 List focusOrder = new ArrayList(items.size());
624
625 if( traversalOrder == ROW_MAJOR_FOCUS_ORDER ) {
626 for( int row=0; row < rowOccupiers.size() - 1; row++ ) {
627 for( int col=0; col < maxColumns; col++ ) {
628 Cell currentCell = getOccupier(row, col);
629 if (currentCell != null && !focusOrder.contains(currentCell.getComponent())) {
630 focusOrder.add(currentCell.getComponent());
631 }
632 }
633 }
634 } else if( traversalOrder == COLUMN_MAJOR_FOCUS_ORDER ) {
635 for( int col = 0; col < maxColumns; col++ ) {
636 for( int row = 0; row < rowOccupiers.size() - 1; row++ ) {
637 Cell currentCell = getOccupier( row, col );
638 if( currentCell != null && !focusOrder.contains( currentCell.getComponent() ) ) {
639 focusOrder.add( currentCell.getComponent() );
640 }
641 }
642 }
643 }
644
645 setCustomFocusTraversalOrder( focusOrder );
646 }
647
648 /**
649 * Set a custom focus traversal order using the provided list of components.
650 * @param focusOrder List of components in the order that focus should follow.
651 */
652 public void setCustomFocusTraversalOrder( List focusOrder ) {
653 this.focusOrder = focusOrder;
654 }
655
656 /**
657 * Install the specified focus order.
658 * @param focusOrder List of components in the order that focus should follow.
659 */
660 protected void installFocusOrder( List focusOrder ) {
661 CustomizableFocusTraversalPolicy.installCustomizableFocusTraversalPolicy();
662 CustomizableFocusTraversalPolicy.customizeFocusTraversalOrder(panel, focusOrder);
663 }
664
665 private void fillPanel() {
666 panel.setLayout(createLayout());
667 for (Iterator i = items.iterator(); i.hasNext();) {
668 Cell cc = (Cell)i.next();
669 panel.add((Component)cc.getComponent(), cc.getCellConstraints());
670 }
671 }
672
673 private FormLayout createLayout() {
674 ColumnSpec[] columnSpecsArray = (ColumnSpec[])columnSpecs.toArray(new ColumnSpec[columnSpecs.size()]);
675 RowSpec[] rowSpecArray = (RowSpec[])rowSpecs.toArray(new RowSpec[rowSpecs.size()]);
676 FormLayout layout = new FormLayout(columnSpecsArray, rowSpecArray);
677 layout.setColumnGroups(adjustedColGroupIndices);
678 layout.setRowGroups(adjustedRowGroupIndices);
679 return layout;
680 }
681
682 private void insertMissingSpecs() {
683 maxColumns = Math.max(maxColumns, currentCol);
684 if (columnSpecs.size() < maxColumns) {
685 setColumnSpec(maxColumns, getDefaultColSpec());
686 }
687
688 if (rowSpecs.size() <= getCurrentRow()) {
689 setRowSpec(getCurrentRow(), getDefaultRowSpec());
690 }
691 }
692
693 private RowSpec getDefaultRowSpec() {
694 return FormFactory.DEFAULT_ROWSPEC;
695 }
696
697 private ColumnSpec getDefaultColSpec() {
698 return new ColumnSpec("default:grow");
699 }
700
701 private static final Set allowedAttributes;
702 static {
703 allowedAttributes = new HashSet();
704 allowedAttributes.add(COLSPAN.toLowerCase());
705 allowedAttributes.add(ROWSPAN.toLowerCase());
706 allowedAttributes.add(COLSPEC.toLowerCase());
707 allowedAttributes.add(ROWSPEC.toLowerCase());
708 allowedAttributes.add(ALIGN.toLowerCase());
709 allowedAttributes.add(VALIGN.toLowerCase());
710 allowedAttributes.add(ROWGROUPID.toLowerCase());
711 allowedAttributes.add(COLGROUPID.toLowerCase());
712 }
713
714 private String getAttribute(String name, Map attributeMap, String defaultValue) {
715 String value = (String)attributeMap.get(name.toLowerCase());
716 if (value == null) {
717 value = defaultValue;
718 }
719 return value;
720 }
721
722 private Map getAttributes(String attributes) {
723 if(!StringUtils.hasText(attributes))
724 return Collections.EMPTY_MAP;
725
726 Map attributeMap = new HashMap();
727 try {
728 StreamTokenizer st = new StreamTokenizer(new StringReader(attributes));
729 st.resetSyntax();
730 st.wordChars(33, 126);
731 st.wordChars(128 + 32, 255);
732 st.whitespaceChars(0, ' ');
733 st.quoteChar('"');
734 st.quoteChar('\'');
735 st.ordinaryChar('=');
736
737 String name = null;
738 boolean needEquals = false;
739
740 while (st.nextToken() != StreamTokenizer.TT_EOF) {
741 if (name == null && st.ttype == StreamTokenizer.TT_WORD) {
742 name = st.sval;
743 if (!allowedAttributes.contains(name.toLowerCase())) {
744 throw new IllegalArgumentException("Attribute name '" + name + "' not recognised.");
745 }
746 needEquals = true;
747 }
748 else if (needEquals && st.ttype == '=') {
749 needEquals = false;
750 }
751 else if (name != null && (st.ttype == StreamTokenizer.TT_WORD || st.ttype == '\'' | st.ttype == '"')) {
752 attributeMap.put(name.toLowerCase(), st.sval);
753 name = null;
754 }
755 else {
756 throw new IllegalArgumentException("Expecting '=' but found '" + st.sval + "'");
757 }
758 }
759 if (needEquals || name != null) {
760 throw new IllegalArgumentException("Premature end of string. Expecting "
761 + (needEquals ? " '='." : " value for attribute '" + name + "'."));
762 }
763 }
764 catch (IOException e) {
765 throw new UnsupportedOperationException("Encountered unexpected IOException. " + e.getMessage());
766 }
767
768 return attributeMap;
769 }
770
771 private static class Cell {
772 private JComponent component;
773
774 private int startCol;
775
776 private int startRow;
777
778 private int endCol;
779
780 private int endRow;
781
782 private String align;
783
784 public Cell(JComponent component, int x, int y, int w, int h, String align) {
785 this.component = component;
786 this.startCol = x;
787 this.startRow = y;
788 this.endCol = x + w - 1;
789 this.endRow = y + h - 1;
790 this.align = align;
791 }
792
793 public Object getComponent() {
794 return component;
795 }
796
797 public CellConstraints getCellConstraints() {
798 return new CellConstraints().xywh(startCol, startRow + 1, endCol - startCol + 1, endRow - startRow + 1,
799 align);
800 }
801 }
802 }