View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.layout;
17  
18  import java.io.Serializable;
19  import java.util.HashMap;
20  import java.util.Map;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.commons.lang.mutable.MutableObject;
24  
25  /**
26   * <p>
27   * A class for describing column and row constraints for the
28   * {@link PercentLayout} layout manager.
29   * </p>
30   * <p>
31   * Objects of this class define properties for the single columns and rows that
32   * comprise the layout maintained by {@link PercentLayout}. Supported properties
33   * are:
34   * </p>
35   * <p>
36   * <ul>
37   * <li>The alignment of the represented cell. This can be one of the constants
38   * defined by the {@link CellAlignment} enumeration class.</li>
39   * <li>An initial size. This property defines how the column's or row's size
40   * depends on the sizes of the contained components. The possible values are
41   * defined by the enumeration class {@link CellSize}.</li>
42   * <li>A minimum size for this cell. This is a numeric value specifying the
43   * minimum width for columns and minimum height for rows. To determine the size
44   * of a column or row the layout first calculates the initial size. If this is
45   * less than the minimum size, the minimum size is used. It is possible to use
46   * different units for specifying this property.</li>
47   * <li>A weight factor. This factor determines how this cell should behave if
48   * additional space becomes available when the container's size changes (e.g. if
49   * the user resizes the window). If this factor is 0, the cell won't change its
50   * size; it will always keep its initial size. For other values than 0 the
51   * remaining space is given to the affected cells with regard to their factors.
52   * It is recommended to use percentage values for these factors, which sum up to
53   * 100 percent (hence the name of the {@code PercentLayout} layout manager).
54   * Then it is easy to understand that each cell with a weight factor greater
55   * than 0 is granted as much percent of the remaining space.</li>
56   * </ul>
57   * </p>
58   * <p>
59   * Instances of this class are not created directly, but the inner {@code
60   * Builder} class is used for this purpose (an application of the
61   * <em>builder</em> pattern). A typical invocation sequence could look as
62   * follows:
63   *
64   * <pre>
65   * CellConstraints.Builder ccb = new CellConstraints.Builder();
66   * CellConstraints cc = ccb.withCellSize(CellSize.Preferred).withMinimumSize(
67   *         new NumberWithUnit(20, Unit.PIXEL)).withWeight(25).withCellAlignment(
68   *         CellAlignment.FULL).create();
69   * </pre>
70   *
71   * </p>
72   * <p>
73   * For instances of this class a string representation is defined. Strings
74   * conforming to the format explained below can also be passed to the builder
75   * for creating instances:
76   * </p>
77   * <p>
78   *
79   * <pre>
80   * CONSTRAINT   ::= [ALIGN&quot;/&quot;]SIZE[&quot;/&quot;WEIGHT]
81   * SIZE         ::= INITSIZE | MINSIZE | BOTHSIZES
82   * BOTHSIZES    ::= INITSIZE &quot;(&quot; MINSIZE &quot;)&quot;
83   * INITSIZE     ::= &quot;PREFERRED&quot; | &quot;MINIMUM&quot; | &quot;NONE&quot;
84   * MINSIZE      ::= &lt;Positive number&gt; [UNIT]
85   * UNIT         ::= &quot;px&quot; | &quot;cm&quot; | &quot;in&quot; | &quot;dlu&quot;
86   * ALIGN        ::= &quot;START&quot; | &quot;CENTER&quot; | &quot;END&quot; | &quot;FULL&quot;
87   * WEIGHT       ::= &lt;Positive number&gt;
88   * </pre>
89   *
90   * Here are some examples:
91   * <ul>
92   * <li>{@code PREFERRED}: only the cell size is set, the other properties are
93   * set to default values.</li>
94   * <li>{@code CENTER/MINIMUM(20px)/33}: A {@code CellConstraints} object is
95   * created with the {@link CellSize} {@code MINIMUM} and the alignment {@code
96   * CENTER}. The minimum size is set to 20 pixels, and the weight factor is set
97   * to 33.</li>
98   * <li>{@code (20)}: Here only the minimum size of the cell is set. The
99   * {@link CellSize} is automatically set to {@code NONE}.</li>
100  * </ul>
101  * </p>
102  * <p>
103  * Instances of this class are immutable and thus can be shared between multiple
104  * threads. The {@code Builder} class makes use of this feature and caches the
105  * instances that have been created. So if another constraint with already used
106  * properties is requested, a shared instance can be returned.
107  * </p>
108  *
109  * @author Oliver Heger
110  * @version $Id: CellConstraints.java 205 2012-01-29 18:29:57Z oheger $
111  */
112 public final class CellConstraints implements Serializable
113 {
114     /**
115      * The serial version UID.
116      */
117     private static final long serialVersionUID = 20090730L;
118 
119     /** Constant for the minimum size start delimiter. */
120     private static final char MINSIZE_START = '(';
121 
122     /** Constant for the minimum size end delimiter. */
123     private static final char MINSIZE_END = ')';
124 
125     /** Constant for the separator used for cell constraints in string form. */
126     private static final String DELIMITER = "/";
127 
128     /** Constant for the default string buffer size. */
129     private static final int BUF_SIZE = 32;
130 
131     /** Constant for the maximum length of a parsed constraints specification. */
132     private static final int LEN_MAX = 3;
133 
134     /** Constant for the middle length of a parsed constraints specification. */
135     private static final int LEN_MEDIUM = 2;
136 
137     /** Constant for the minimum length of a parsed constraints specification. */
138     private static final int LEN_MIN = 1;
139 
140     /** Stores the alignment. */
141     private final CellAlignment alignment;
142 
143     /** Stores the cell size. */
144     private final CellSize cellSize;
145 
146     /** Stores the cell's minimum size. */
147     private final NumberWithUnit minSize;
148 
149     /** Stores the weight factor. */
150     private final int weight;
151 
152     /**
153      * Creates a new instance of {@code CellConstraints} and initializes it.
154      * Client code uses the inner {@code Builder} class for creating instances.
155      *
156      * @param al the alignment
157      * @param sz the size
158      * @param minsz the minimum size
159      * @param w the weight factor
160      */
161     private CellConstraints(CellAlignment al, CellSize sz,
162             NumberWithUnit minsz, int w)
163     {
164         alignment = al;
165         minSize = NumberWithUnit.nonNull(minsz);
166         weight = w;
167         cellSize = sz;
168     }
169 
170     /**
171      * Returns the alignment string.
172      *
173      * @return the alignment string
174      */
175     public CellAlignment getAlignment()
176     {
177         return alignment;
178     }
179 
180     /**
181      * Returns the size of the cell.
182      *
183      * @return the cell size
184      */
185     public CellSize getCellSize()
186     {
187         return cellSize;
188     }
189 
190     /**
191      * Returns the minimum size.
192      *
193      * @return the minimum size
194      */
195     public NumberWithUnit getMinSize()
196     {
197         return minSize;
198     }
199 
200     /**
201      * Returns the weight factor.
202      *
203      * @return the weight factor
204      */
205     public int getWeight()
206     {
207         return weight;
208     }
209 
210     /**
211      * Helper method for creating a specification string for the specified
212      * constraint data.
213      *
214      * @param align the alignment string
215      * @param size the cell size
216      * @param minsz the minimum size
217      * @param w the weight factor
218      * @return a string specification for the passed in data
219      */
220     private static String toString(CellAlignment align, CellSize size,
221             NumberWithUnit minsz, int w)
222     {
223         StringBuilder buf = new StringBuilder(BUF_SIZE);
224         buf.append(align.name()).append(DELIMITER);
225         buf.append(size.name());
226         buf.append(MINSIZE_START);
227         minsz.buildUnitString(buf);
228         buf.append(MINSIZE_END);
229         buf.append(DELIMITER).append(w);
230         return buf.toString();
231     }
232 
233     /**
234      * Returns a string specification for this {@code CellConstraints} object.
235      * This string conforms to the definition given in the class comment. It can
236      * be passed to a {@code Builder} instance to obtain a corresponding {@code
237      * CellConstraints} instance. The string returned by this method contains
238      * the values of all properties of this object, even if some have been set
239      * to default values.
240      *
241      * @return a specification string for this instance
242      */
243     public String toSpecificationString()
244     {
245         return toString(getAlignment(), getCellSize(), getMinSize(),
246                 getWeight());
247     }
248 
249     /**
250      * Returns a string representation of this constraints object. This string
251      * contains the specification string produced by
252      * {@link #toSpecificationString()}.
253      *
254      * @return a string representation for this object
255      */
256     @Override
257     public String toString()
258     {
259         StringBuilder buf = new StringBuilder();
260         buf.append("CellConstraints [ ");
261         buf.append(toSpecificationString());
262         buf.append(" ]");
263         return buf.toString();
264     }
265 
266     /**
267      * Compares this object with another one. Two instances of {@code
268      * CellConstraints} are equal if all of their properties are equal.
269      *
270      * @param obj the object to compare to
271      * @return a flag whether these objects are equal
272      */
273     @Override
274     public boolean equals(Object obj)
275     {
276         if (this == obj)
277         {
278             return true;
279         }
280         if (!(obj instanceof CellConstraints))
281         {
282             return false;
283         }
284 
285         CellConstraints c = (CellConstraints) obj;
286         return getAlignment() == c.getAlignment()
287                 && getCellSize() == c.getCellSize()
288                 && getMinSize().equals(c.getMinSize())
289                 && getWeight() == c.getWeight();
290     }
291 
292     /**
293      * Returns a hash code for this object.
294      *
295      * @return a hash code for this object
296      */
297     @Override
298     public int hashCode()
299     {
300         final int factor = 31;
301         final int seed = 17;
302 
303         int result = seed;
304         result = factor * result + getAlignment().hashCode();
305         result = factor * result + getCellSize().hashCode();
306         result = factor * result + getMinSize().hashCode();
307         result = factor * result + getWeight();
308 
309         return result;
310     }
311 
312     /**
313      * Helper method for parsing the minimum size. This method tests whether the
314      * passed in string contains a valid size definition. If this is the case,
315      * the corresponding values are returned in the values of the passed in
316      * mutable objects.
317      *
318      * @param sizeDef the string to be parsed
319      * @param size takes the value of the size
320      * @param minSize takes the value of the minimum size
321      * @return a flag if the size could be successfully parsed
322      */
323     private static boolean parseSize(String sizeDef, MutableObject size,
324             MutableObject minSize)
325     {
326         String s = sizeDef.trim();
327         int pos = s.indexOf(MINSIZE_START);
328         if (pos > 0)
329         {
330             // both initial size and minimum size
331             if (!s.endsWith(String.valueOf(MINSIZE_END)))
332             {
333                 return false;
334             }
335 
336             try
337             {
338                 size.setValue(valueOf(CellSize.class, s.substring(0, pos)));
339                 minSize.setValue(new NumberWithUnit(s.substring(pos + 1, s
340                         .length() - 1)));
341             }
342             catch (IllegalArgumentException iex)
343             {
344                 // not a valid literal or number with unit
345                 return false;
346             }
347         }
348 
349         else
350         {
351             try
352             {
353                 // initial size defined?
354                 size.setValue(valueOf(CellSize.class, s));
355                 minSize.setValue(NumberWithUnit.ZERO);
356             }
357             catch (IllegalArgumentException iex)
358             {
359                 // no, a minimum size?
360                 try
361                 {
362                     minSize.setValue(new NumberWithUnit(s));
363                     size.setValue(CellSize.NONE);
364                 }
365                 catch (IllegalArgumentException iex2)
366                 {
367                     return false;
368                 }
369             }
370         }
371 
372         return true;
373     }
374 
375     /**
376      * Helper method for parsing and validating a size definition. This method
377      * works like {@link #parseSize(String, MutableObject, MutableObject)}, but
378      * throws an exception if parsing fails.
379      *
380      * @param s the string to be parsed
381      * @param size takes the value of the size
382      * @param minSize takes the value of the minimum size
383      */
384     private static void parseSizeEx(String s, MutableObject size,
385             MutableObject minSize)
386     {
387         if (!parseSize(s, size, minSize))
388         {
389             throw new IllegalArgumentException("Invalid size declaration: " + s);
390         }
391     }
392 
393     /**
394      * Parses the given alignment specification.
395      *
396      * @param comps the components of the complete specification
397      * @param index the index of the component to be parsed
398      * @param defAlign the default alignment
399      * @return the alignment
400      * @throws IllegalArgumentException if the specification is invalid
401      */
402     private static CellAlignment parseAlignment(String[] comps, int index,
403             CellAlignment defAlign)
404     {
405         if (index < 0)
406         {
407             return defAlign;
408         }
409         else
410         {
411             return valueOf(CellAlignment.class, comps[index]);
412         }
413     }
414 
415     /**
416      * Parses the given specification of a weight factor.
417      *
418      * @param comps the components of the complete specification
419      * @param index the index of the component to be parsed
420      * @return the weight factor
421      * @throws IllegalArgumentException if the specification is invalid
422      */
423     private static int parseWeight(String[] comps, int index)
424     {
425         if (index < 0)
426         {
427             return 0;
428         }
429 
430         try
431         {
432             int weight = Integer.parseInt(comps[index].trim());
433             if (weight < 0)
434             {
435                 throw new IllegalArgumentException(
436                         "Weight factor must be positive: " + comps[index]);
437             }
438             return weight;
439         }
440         catch (NumberFormatException nfex)
441         {
442             throw new IllegalArgumentException(
443                     "Invalid weight factor specification: " + comps[index]);
444         }
445     }
446 
447     /**
448      * Helper method for obtaining an enumeration literal for a given case. The
449      * string is transformed to upper case before the literal is checked, so
450      * this method is case insensitive.
451      *
452      * @param <T> the type of the enum class
453      * @param enumType the type of the enumeration
454      * @param s the string
455      * @return the corresponding enum value
456      * @throws IllegalArgumentException if the string is not a valid enumeration
457      *         literal
458      */
459     private static <T extends Enum<T>> T valueOf(Class<T> enumType, String s)
460     {
461         return Enum.valueOf(enumType, s.trim().toUpperCase());
462     }
463 
464     /**
465      * Parses the given specification string for a {@code CellConstraints}
466      * object and creates a corresponding instance.
467      *
468      * @param spec the string to be parsed
469      * @param defAlign the default alignment
470      * @return the corresponding instance
471      */
472     private static CellConstraints parse(String spec, CellAlignment defAlign)
473     {
474         if (StringUtils.isEmpty(spec))
475         {
476             throw new IllegalArgumentException(
477                     "Undefined specification string!");
478         }
479 
480         MutableObject size = new MutableObject();
481         MutableObject minSize = new MutableObject();
482         int indexAlign = -1;
483         int indexWeight = -1;
484         String[] comps = spec.split(DELIMITER);
485 
486         switch (comps.length)
487         {
488         case LEN_MAX:
489             indexAlign = 0;
490             indexWeight = 2;
491             parseSizeEx(comps[1], size, minSize);
492             break;
493 
494         case LEN_MEDIUM:
495             if (parseSize(comps[0], size, minSize))
496             {
497                 indexWeight = 1;
498             }
499             else
500             {
501                 parseSizeEx(comps[1], size, minSize);
502                 indexAlign = 0;
503             }
504             break;
505 
506         case LEN_MIN:
507             parseSizeEx(comps[0], size, minSize);
508             break;
509 
510         default:
511             throw new IllegalArgumentException(
512                     "Invalid number of components in specification string: "
513                             + spec);
514         }
515 
516         NumberWithUnit nMinSize = (NumberWithUnit) minSize.getValue();
517         if (nMinSize.getValue() < 0)
518         {
519             throw new IllegalArgumentException(
520                     "Minimum size must be non-negative: " + nMinSize);
521         }
522 
523         return new CellConstraints(parseAlignment(comps, indexAlign, defAlign),
524                 (CellSize) size.getValue(), nMinSize, parseWeight(comps,
525                         indexWeight));
526     }
527 
528     /**
529      * <p>
530      * A <em>builder</em> class for creating instances of {@code
531      * CellConstraints}.
532      * </p>
533      * <p>
534      * With the different {@code withXXXX()} methods the properties of the new
535      * {@code CellConstraints} object are defined. Then, with the {@code
536      * create()} method, the instance is actually created. Alternatively, with
537      * the {@code parse()} method a {@code CellConstraints} instance for a
538      * string representation can be requested.
539      * </p>
540      * <p>
541      * This class is not thread-safe. It maintains an internal (unsynchronized)
542      * cache of the {@code CellConstraints} instances that have already been
543      * created. If instances with equal properties are requested, the same
544      * instance is returned.
545      * </p>
546      * <p>
547      * More information including a usage example of this class can be found in
548      * the documentation for {@link CellConstraints}.
549      * </p>
550      *
551      * @author Oliver Heger
552      * @version $Id: CellConstraints.java 205 2012-01-29 18:29:57Z oheger $
553      */
554     public static final class Builder
555     {
556         /** The cache of instances created so far. */
557         private final Map<String, CellConstraints> cache;
558 
559         /** The current cell size. */
560         private CellSize cellSize;
561 
562         /** The current alignment. */
563         private CellAlignment alignment;
564 
565         /** The default alignment. */
566         private CellAlignment defaultAlignment;
567 
568         /** The current minimum size. */
569         private NumberWithUnit minSize;
570 
571         /** The current weight factor. */
572         private int weight;
573 
574         /**
575          * Creates a new instance of {@code Builder}.
576          */
577         public Builder()
578         {
579             cache = new HashMap<String, CellConstraints>();
580             defaultAlignment = CellAlignment.FULL;
581             reset();
582         }
583 
584         /**
585          * Returns the default {@code CellAlignment} used by this builder.
586          *
587          * @return the default {@code CellAlignment}
588          */
589         public CellAlignment getDefaultAlignment()
590         {
591             return defaultAlignment;
592         }
593 
594         /**
595          * Sets the default {@code CellAlignment} used by this builder. This
596          * alignment is set by the {@code reset()} method. Note: after calling
597          * this method {@code reset()} must be called to apply the new default
598          * alignment.
599          *
600          * @param defaultAlignment the new default alignment (must not be
601          *        <b>null</b>)
602          * @throws IllegalArgumentException if the parameter is <b>null</b>
603          */
604         public void setDefaultAlignment(CellAlignment defaultAlignment)
605         {
606             if (defaultAlignment == null)
607             {
608                 throw new IllegalArgumentException(
609                         "Default alignment must not be null!");
610             }
611 
612             this.defaultAlignment = defaultAlignment;
613         }
614 
615         /**
616          * Sets the {@code size} property of the {@link CellConstraints}
617          * instance to be created.
618          *
619          * @param sz the size of the cell (must not be <b>null</b>)
620          * @return a reference to this builder for method chaining
621          * @throws IllegalArgumentException if the {@code CellSize} object is
622          *         <b>null</b>
623          */
624         public Builder withCellSize(CellSize sz)
625         {
626             if (sz == null)
627             {
628                 throw new IllegalArgumentException(
629                         "Cell size must not be null!");
630             }
631             cellSize = sz;
632             return this;
633         }
634 
635         /**
636          * Sets the {@code minimumSize} property of the {@link CellConstraints}
637          * instance to be created.
638          *
639          * @param minSize the minimum size of the cell (can be <b>null</b>, then
640          *        a minimum size of 0 pixels is assumed)
641          * @return a reference to this builder for method chaining
642          * @throws IllegalArgumentException if the value of the minimum size is
643          *         negative
644          */
645         public Builder withMinimumSize(NumberWithUnit minSize)
646         {
647             this.minSize = NumberWithUnit.nonNull(minSize);
648             if (this.minSize.getValue() < 0)
649             {
650                 throw new IllegalArgumentException(
651                         "Minimum size must not be negative: " + this.minSize);
652             }
653             return this;
654         }
655 
656         /**
657          * Sets the {@code alignment} property of the {@link CellConstraints}
658          * instance to be created.
659          *
660          * @param align the alignment of the cell (must not be <b>null</b>)
661          * @return a reference to this builder for method chaining
662          * @throws IllegalArgumentException if the alignment is <b>null</b>
663          */
664         public Builder withCellAlignment(CellAlignment align)
665         {
666             if (align == null)
667             {
668                 throw new IllegalArgumentException(
669                         "Cell alignment must not be null!");
670             }
671             alignment = align;
672             return this;
673         }
674 
675         /**
676          * Sets the {@code weight} property of the {@link CellConstraints}
677          * instance to be created. This must be a positive number.
678          *
679          * @param factor the weight factor
680          * @return a reference to this builder for method chaining
681          * @throws IllegalArgumentException if the number is less than 0
682          */
683         public Builder withWeight(int factor)
684         {
685             if (factor < 0)
686             {
687                 throw new IllegalArgumentException(
688                         "Weight factor must not be negative: " + factor);
689             }
690             weight = factor;
691             return this;
692         }
693 
694         /**
695          * Initializes the properties of the {@link CellConstraints} instance to
696          * be created with default values for a column. This means:
697          * <ul>
698          * <li>the alignment is set to {@code FULL}</li>
699          * <li>the size is set to {@code PREFERRED}</li>
700          * <li>no minimum size is set</li>
701          * <li>the weight factor is set to 0</li>
702          * </ul>
703          * This method can be called first for initializing default values.
704          * Then, with other {@code withXXXX()} methods specific values can be
705          * set.
706          *
707          * @return a reference to this builder for method chaining
708          */
709         public Builder defaultColumn()
710         {
711             alignment = CellAlignment.FULL;
712             cellSize = CellSize.PREFERRED;
713             minSize = NumberWithUnit.ZERO;
714             weight = 0;
715             return this;
716         }
717 
718         /**
719          * Initializes the properties of the {@link CellConstraints} instance to
720          * be created with default values for a row. This means:
721          * <ul>
722          * <li>the alignment is set to {@code CENTER}</li>
723          * <li>the size is set to {@code PREFERRED}</li>
724          * <li>no minimum size is set</li>
725          * <li>the weight factor is set to 0</li>
726          * </ul>
727          * This method can be called first for initializing default values.
728          * Then, with other {@code withXXXX()} methods specific values can be
729          * set.
730          *
731          * @return a reference to this builder for method chaining
732          */
733         public Builder defaultRow()
734         {
735             alignment = CellAlignment.CENTER;
736             cellSize = CellSize.PREFERRED;
737             minSize = NumberWithUnit.ZERO;
738             weight = 0;
739             return this;
740         }
741 
742         /**
743          * Returns a {@link CellConstraints} instance for the properties defined
744          * so far. After that all properties are reset, so that properties for a
745          * new instance can be specified.
746          *
747          * @return a {@code CellConstraints} instance corresponding to the
748          *         properties set so far
749          * @throws IllegalStateException if required properties are missing
750          */
751         public CellConstraints create()
752         {
753             if (cellSize == null && minSize == null)
754             {
755                 throw new IllegalStateException("Size of cell is unspecified!"
756                         + " Set a cell size or a minimum size.");
757             }
758             if (cellSize == null)
759             {
760                 cellSize = CellSize.NONE;
761             }
762             if (minSize == null)
763             {
764                 minSize = NumberWithUnit.ZERO;
765             }
766 
767             String spec = CellConstraints.toString(alignment, cellSize,
768                     minSize, weight);
769             CellConstraints cc = cache.get(spec);
770             if (cc == null)
771             {
772                 cc = new CellConstraints(alignment, cellSize, NumberWithUnit
773                         .nonNull(minSize), weight);
774                 cache.put(spec, cc);
775             }
776 
777             reset();
778             return cc;
779         }
780 
781         /**
782          * Resets all properties set so far. This reverts all invocations of
783          * {@code withXXXX()} methods since the last instance was created.
784          */
785         public void reset()
786         {
787             alignment = getDefaultAlignment();
788             cellSize = null;
789             minSize = null;
790             weight = 0;
791         }
792 
793         /**
794          * Parses a string with the specification of a {@code CellConstraints}
795          * object and returns a corresponding instance.
796          *
797          * @param spec the specification of the {@code CellConstraints} instance
798          * @return the corresponding {@code CellConstraints} instance
799          * @throws IllegalArgumentException if the string cannot be parsed
800          */
801         public CellConstraints fromString(String spec)
802         {
803             CellConstraints cc = cache.get(spec);
804             if (cc != null)
805             {
806                 return cc;
807             }
808 
809             cc = CellConstraints.parse(spec, getDefaultAlignment());
810             cache.put(spec, cc);
811             String canonicalSpec = cc.toSpecificationString();
812             if (!canonicalSpec.equals(spec))
813             {
814                 cache.put(canonicalSpec, cc);
815             }
816 
817             return cc;
818         }
819     }
820 }