JAL-3032 adds Java 8 functionality (1/2)
[jalview.git] / src / net / miginfocom / layout / ConstraintParser.java
1 package net.miginfocom.layout;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.Iterator;
6 import java.util.Map;
7 /*
8  * License (BSD):
9  * ==============
10  *
11  * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
12  * All rights reserved.
13  *
14  * Redistribution and use in source and binary forms, with or without modification,
15  * are permitted provided that the following conditions are met:
16  * Redistributions of source code must retain the above copyright notice, this list
17  * of conditions and the following disclaimer.
18  * Redistributions in binary form must reproduce the above copyright notice, this
19  * list of conditions and the following disclaimer in the documentation and/or other
20  * materials provided with the distribution.
21  * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
22  * used to endorse or promote products derived from this software without specific
23  * prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
31  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
34  * OF SUCH DAMAGE.
35  *
36  * @version 1.0
37  * @author Mikael Grev, MiG InfoCom AB
38  *         Date: 2006-sep-08
39  */
40
41 /** Parses string constraints.
42  */
43 public final class ConstraintParser
44 {
45         private ConstraintParser()
46         {
47         }
48
49         /** Parses the layout constraints and stores the parsed values in the transient (cache) member variables.
50          * @param s The String to parse. Should not be <code>null</code> and <b>must be lower case and trimmed</b>.
51          * @throws RuntimeException if the constraint was not valid.
52          * @return The parsed constraint. Never <code>null</code>.
53          */
54         public static LC parseLayoutConstraint(String s)
55         {
56                 LC lc = new LC();
57                 if (s.isEmpty())
58                         return lc;
59
60                 String[] parts = toTrimmedTokens(s, ',');
61
62                 // First check for "ltr" or "rtl" since that will affect the interpretation of the other constraints.
63                 for (int i = 0; i < parts.length; i++) {
64                         String part = parts[i];
65                         if (part == null)
66                                 continue;
67
68                         int len = part.length();
69                         if (len == 3 || len == 11) {   // Optimization
70                                 if (part.equals("ltr") || part.equals("rtl") || part.equals("lefttoright") || part.equals("righttoleft")) {
71                                         lc.setLeftToRight(part.charAt(0) == 'l' ? Boolean.TRUE : Boolean.FALSE);
72                                         parts[i] = null;    // So we will not try to interpret it again
73                                 }
74
75                                 if (part.equals("ttb") || part.equals("btt") || part.equals("toptobottom") || part.equals("bottomtotop")) {
76                                         lc.setTopToBottom(part.charAt(0) == 't');
77                                         parts[i] = null;    // So we will not try to interpret it again
78                                 }
79                         }
80                 }
81
82                 for (String part : parts) {
83                         if (part == null || part.length() == 0)
84                                 continue;
85
86                         try {
87                                 int ix = -1;
88                                 char c = part.charAt(0);
89
90                                 if (c == 'w' || c == 'h') {
91
92                                         ix = startsWithLenient(part, "wrap", -1, true);
93                                         if (ix > -1) {
94                                                 String num = part.substring(ix).trim();
95                                                 lc.setWrapAfter(num.length() != 0 ? Integer.parseInt(num) : 0);
96                                                 continue;
97                                         }
98
99                                         boolean isHor = c == 'w';
100                                         if (isHor && (part.startsWith("w ") || part.startsWith("width "))) {
101                                                 String sz = part.substring(part.charAt(1) == ' ' ? 2 : 6).trim();
102                                                 lc.setWidth(parseBoundSize(sz, false, true));
103                                                 continue;
104                                         }
105
106                                         if (!isHor && (part.startsWith("h ") || part.startsWith("height "))) {
107                                                 String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 7).trim();
108                                                 lc.setHeight(parseBoundSize(uvStr, false, false));
109                                                 continue;
110                                         }
111
112                                         if (part.length() > 5) {
113                                                 String sz = part.substring(5).trim();
114                                                 if (part.startsWith("wmin ")) {
115                                                         lc.minWidth(sz);
116                                                         continue;
117                                                 } else if (part.startsWith("wmax ")) {
118                                                         lc.maxWidth(sz);
119                                                         continue;
120                                                 } else if (part.startsWith("hmin ")) {
121                                                         lc.minHeight(sz);
122                                                         continue;
123                                                 } else if (part.startsWith("hmax ")) {
124                                                         lc.maxHeight(sz);
125                                                         continue;
126                                                 }
127                                         }
128
129                                         if (part.startsWith("hidemode ")) {
130                                                 lc.setHideMode(Integer.parseInt(part.substring(9)));
131                                                 continue;
132                                         }
133                                 }
134
135                                 if (c == 'g') {
136                                         if (part.startsWith("gapx ")) {
137                                                 lc.setGridGapX(parseBoundSize(part.substring(5).trim(), true, true));
138                                                 continue;
139                                         }
140
141                                         if (part.startsWith("gapy ")) {
142                                                 lc.setGridGapY(parseBoundSize(part.substring(5).trim(), true, false));
143                                                 continue;
144                                         }
145
146                                         if (part.startsWith("gap ")) {
147                                                 String[] gaps = toTrimmedTokens(part.substring(4).trim(), ' ');
148                                                 lc.setGridGapX(parseBoundSize(gaps[0], true, true));
149                                                 lc.setGridGapY(gaps.length > 1 ? parseBoundSize(gaps[1], true, false) : lc.getGridGapX());
150                                                 continue;
151                                         }
152                                 }
153
154                                 if (c == 'd') {
155                                         ix = startsWithLenient(part, "debug", 5, true);
156                                         if (ix > -1) {
157                                                 String millis = part.substring(ix).trim();
158                                                 lc.setDebugMillis(millis.length() > 0 ? Integer.parseInt(millis) : 1000);
159                                                 continue;
160                                         }
161                                 }
162
163                                 if (c == 'n') {
164                                         if (part.equals("nogrid")) {
165                                                 lc.setNoGrid(true);
166                                                 continue;
167                                         }
168
169                                         if (part.equals("nocache")) {
170                                                 lc.setNoCache(true);
171                                                 continue;
172                                         }
173
174                                         if (part.equals("novisualpadding")) {
175                                                 lc.setVisualPadding(false);
176                                                 continue;
177                                         }
178                                 }
179
180                                 if (c == 'f') {
181                                         if (part.equals("fill") || part.equals("fillx") || part.equals("filly")) {
182                                                 lc.setFillX(part.length() == 4 || part.charAt(4) == 'x');
183                                                 lc.setFillY(part.length() == 4 || part.charAt(4) == 'y');
184                                                 continue;
185                                         }
186
187                                         if (part.equals("flowy")) {
188                                                 lc.setFlowX(false);
189                                                 continue;
190                                         }
191
192                                         if (part.equals("flowx")) {
193                                                 lc.setFlowX(true); // This is the default but added for consistency
194                                                 continue;
195                                         }
196                                 }
197
198                                 if (c == 'i') {
199                                         ix = startsWithLenient(part, "insets", 3, true);
200                                         if (ix > -1) {
201                                                 String insStr = part.substring(ix).trim();
202                                                 UnitValue[] ins = parseInsets(insStr, true);
203                                                 LayoutUtil.putCCString(ins, insStr);
204                                                 lc.setInsets(ins);
205                                                 continue;
206                                         }
207                                 }
208
209                                 if (c == 'a') {
210                                         ix = startsWithLenient(part, new String[]{"aligny", "ay"}, new int[]{6, 2}, true);
211                                         if (ix > -1) {
212                                                 UnitValue align = parseUnitValueOrAlign(part.substring(ix).trim(), false, null);
213                                                 if (align == UnitValue.BASELINE_IDENTITY)
214                                                         throw new IllegalArgumentException("'baseline' can not be used to align the whole component group.");
215                                                 lc.setAlignY(align);
216                                                 continue;
217                                         }
218
219                                         ix = startsWithLenient(part, new String[]{"alignx", "ax"}, new int[]{6, 2}, true);
220                                         if (ix > -1) {
221                                                 lc.setAlignX(parseUnitValueOrAlign(part.substring(ix).trim(), true, null));
222                                                 continue;
223                                         }
224
225                                         ix = startsWithLenient(part, "align", 2, true);
226                                         if (ix > -1) {
227                                                 String[] gaps = toTrimmedTokens(part.substring(ix).trim(), ' ');
228                                                 lc.setAlignX(parseUnitValueOrAlign(gaps[0], true, null));
229                                                 if (gaps.length > 1) {
230                                                         UnitValue align = parseUnitValueOrAlign(gaps[1], false, null);
231                                                         if (align == UnitValue.BASELINE_IDENTITY)
232                                                                 throw new IllegalArgumentException("'baseline' can not be used to align the whole component group.");
233                                                         lc.setAlignY(align);
234                                                 }
235                                                 continue;
236                                         }
237                                 }
238
239                                 if (c == 'p') {
240                                         if (part.startsWith("packalign ")) {
241                                                 String[] packs = toTrimmedTokens(part.substring(10).trim(), ' ');
242                                                 lc.setPackWidthAlign(packs[0].length() > 0 ? Float.parseFloat(packs[0]) : 0.5f);
243                                                 if (packs.length > 1)
244                                                         lc.setPackHeightAlign(Float.parseFloat(packs[1]));
245                                                 continue;
246                                         }
247
248                                         if (part.startsWith("pack ") || part.equals("pack")) {
249                                                 String ps = part.substring(4).trim();
250                                                 String[] packs = toTrimmedTokens(ps.length() > 0 ? ps : "pref pref", ' ');
251                                                 lc.setPackWidth(parseBoundSize(packs[0], false, true));
252                                                 if (packs.length > 1)
253                                                         lc.setPackHeight(parseBoundSize(packs[1], false, false));
254
255                                                 continue;
256                                         }
257                                 }
258
259                                 if (lc.getAlignX() == null) {
260                                         UnitValue alignX = parseAlignKeywords(part, true);
261                                         if (alignX != null) {
262                                                 lc.setAlignX(alignX);
263                                                 continue;
264                                         }
265                                 }
266
267                                 UnitValue alignY = parseAlignKeywords(part, false);
268                                 if (alignY != null) {
269                                         lc.setAlignY(alignY);
270                                         continue;
271                                 }
272
273                                 throw new IllegalArgumentException("Unknown Constraint: '" + part + "'\n");
274
275                         } catch (Exception ex) {
276                                 throw new IllegalArgumentException("Illegal Constraint: '" + part + "'\n" + ex.getMessage());
277                         }
278                 }
279
280 //              lc = (LC) serializeTest(lc);
281
282                 return lc;
283         }
284
285         /** Parses the column or rows constraints. They normally looks something like <code>"[min:pref]rel[10px][]"</code>.
286          * @param s The string to parse. Not <code>null</code>.
287          * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed.
288          * @throws RuntimeException if the constraint was not valid.
289          */
290         public static AC parseRowConstraints(String s)
291         {
292                 return parseAxisConstraint(s, false);
293         }
294
295         /** Parses the column or rows constraints. They normally looks something like <code>"[min:pref]rel[10px][]"</code>.
296          * @param s The string to parse. Not <code>null</code>.
297          * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed.
298          * @throws RuntimeException if the constraint was not valid.
299          */
300         public static AC parseColumnConstraints(String s)
301         {
302                 return parseAxisConstraint(s, true);
303         }
304
305         /** Parses the column or rows constraints. They normally looks something like <code>"[min:pref]rel[10px][]"</code>.
306          * @param s The string to parse. Not <code>null</code>.
307          * @param isCols If this for columns rather than rows.
308          * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed.
309          * @throws RuntimeException if the constraint was not valid.
310          */
311         private static AC parseAxisConstraint(String s, boolean isCols)
312         {
313                 s = s.trim();
314
315                 if (s.length() == 0)
316                         return new AC();    // Short circuit for performance.
317
318                 s = s.toLowerCase();
319
320                 ArrayList<String> parts = getRowColAndGapsTrimmed(s);
321
322                 BoundSize[] gaps = new BoundSize[(parts.size() >> 1) + 1];
323                 for (int i = 0, iSz = parts.size(), gIx = 0; i < iSz; i += 2, gIx++)
324                         gaps[gIx] = parseBoundSize(parts.get(i), true, isCols);
325
326                 DimConstraint[] colSpecs = new DimConstraint[parts.size() >> 1];
327                 for (int i = 0, gIx = 0; i < colSpecs.length; i++, gIx++) {
328                         if (gIx >= gaps.length - 1)
329                                 gIx = gaps.length - 2;
330
331                         colSpecs[i] = parseDimConstraint(parts.get((i << 1) + 1), gaps[gIx], gaps[gIx + 1], isCols);
332                 }
333
334                 AC ac = new AC();
335                 ac.setConstaints(colSpecs);
336
337 //              ac = (AC) serializeTest(ac);
338
339                 return ac;
340         }
341
342         /** Parses a single column or row constraint.
343          * @param s The single constraint to parse. May look something like <code>"min:pref,fill,grow"</code>. Should not be <code>null</code> and <b>must
344          * be lower case and trimmed</b>.
345          * @param gapBefore The default gap "before" the column/row constraint. Can be overridden with a <code>"gap"</code> section within <code>s</code>.
346          * @param gapAfter The default gap "after" the column/row constraint. Can be overridden with a <code>"gap"</code> section within <code>s</code>.
347          * @param isCols If the constraints are column constraints rather than row constraints.
348          * @return A single constraint. Never <code>null</code>.
349          * @throws RuntimeException if the constraint was not valid.
350          */
351         private static DimConstraint parseDimConstraint(String s, BoundSize gapBefore, BoundSize gapAfter, boolean isCols)
352         {
353                 DimConstraint dimConstraint = new DimConstraint();
354
355                 // Default values.
356                 dimConstraint.setGapBefore(gapBefore);
357                 dimConstraint.setGapAfter(gapAfter);
358
359                 String[] parts = toTrimmedTokens(s, ',');
360                 for (int i = 0; i < parts.length; i++) {
361                         String part = parts[i];
362                         try {
363                                 if (part.length() == 0)
364                                         continue;
365
366                                 if (part.equals("fill")) {
367                                         dimConstraint.setFill(true);
368 //                                       dimConstraint.setAlign(null);   // Can not have both fill and alignment (changed for 3.5 since it can have "growy 0")
369                                         continue;
370                                 }
371
372                                 if (part.equals("nogrid")) {
373                                         dimConstraint.setNoGrid(true);
374                                         continue;
375                                 }
376
377                                 int ix = -1;
378                                 char c = part.charAt(0);
379
380                                 if (c == 's') {
381                                         ix = startsWithLenient(part, new String[] {"sizegroup", "sg"}, new int[] {5, 2}, true);
382                                         if (ix > -1) {
383                                                 dimConstraint.setSizeGroup(part.substring(ix).trim());
384                                                 continue;
385                                         }
386
387
388                                         ix = startsWithLenient(part, new String[] {"shrinkprio", "shp"}, new int[] {10, 3}, true);
389                                         if (ix > -1) {
390                                                 dimConstraint.setShrinkPriority(Integer.parseInt(part.substring(ix).trim()));
391                                                 continue;
392                                         }
393
394                                         ix = startsWithLenient(part, "shrink", 6, true);
395                                         if (ix > -1) {
396                                                 dimConstraint.setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
397                                                 continue;
398                                         }
399                                 }
400
401                                 if (c == 'g') {
402                                         ix = startsWithLenient(part, new String[] {"growpriority", "gp"}, new int[] {5, 2}, true);
403                                         if (ix > -1) {
404                                                 dimConstraint.setGrowPriority(Integer.parseInt(part.substring(ix).trim()));
405                                                 continue;
406                                         }
407
408                                         ix = startsWithLenient(part, "grow", 4, true);
409                                         if (ix > -1) {
410                                                 dimConstraint.setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
411                                                 continue;
412                                         }
413                                 }
414
415                                 if (c == 'a') {
416                                         ix = startsWithLenient(part, "align", 2, true);
417                                         if (ix > -1) {
418 //                                              if (dimConstraint.isFill() == false)    // Swallow, but ignore if fill is set. (changed for 3.5 since it can have "growy 0")
419                                                         dimConstraint.setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), isCols, null));
420                                                 continue;
421                                         }
422                                 }
423
424                                 UnitValue align = parseAlignKeywords(part, isCols);
425                                 if (align != null) {
426 //                                      if (dimConstraint.isFill() == false)    // Swallow, but ignore if fill is set. (changed for 3.5 since it can have "growy 0")
427                                                 dimConstraint.setAlign(align);
428                                         continue;
429                                 }
430
431                                  // Only min:pref:max still left that is ok
432                                 dimConstraint.setSize(parseBoundSize(part, false, isCols));
433
434                         } catch (Exception ex) {
435                                 throw new IllegalArgumentException("Illegal constraint: '" + part + "'\n" + ex.getMessage());
436                         }
437                 }
438                 return dimConstraint;
439         }
440
441         /** Parses all component constraints and stores the parsed values in the transient (cache) member variables.
442          * @param constrMap The constraints as <code>String</code>s. Strings <b>must be lower case and trimmed</b>
443          * @return The parsed constraints. Never <code>null</code>.
444          */
445         public static Map<ComponentWrapper, CC> parseComponentConstraints(Map<ComponentWrapper, String> constrMap)
446         {
447                 HashMap<ComponentWrapper, CC> flowConstrMap = new HashMap<ComponentWrapper, CC>();
448
449                 for (Iterator<Map.Entry<ComponentWrapper, String>> it = constrMap.entrySet().iterator(); it.hasNext();) {
450                         Map.Entry<ComponentWrapper, String> entry = it.next();
451                         flowConstrMap.put(entry.getKey(), parseComponentConstraint(entry.getValue()));
452                 }
453
454                 return flowConstrMap;
455         }
456
457         /** Parses one component constraint and returns the parsed value.
458          * @param s The string to parse. <b>Must be lower case and trimmed</b>.
459          * @throws RuntimeException if the constraint was not valid.
460          * @return The parsed constraint. Never <code>null</code>.
461          */
462         public static CC parseComponentConstraint(String s)
463         {
464                 CC cc = new CC();
465
466                 if (s == null || s.isEmpty())
467                         return cc;
468
469                 String[] parts = toTrimmedTokens(s, ',');
470
471                 for (String part : parts) {
472                         try {
473                                 if (part.length() == 0)
474                                         continue;
475
476                                 int ix = -1;
477                                 char c = part.charAt(0);
478
479                                 if (c == 'n') {
480                                         if (part.equals("north")) {
481                                                 cc.setDockSide(0);
482                                                 continue;
483                                         }
484
485                                         if (part.equals("newline")) {
486                                                 cc.setNewline(true);
487                                                 continue;
488                                         }
489
490                                         if (part.startsWith("newline ")) {
491                                                 String gapSz = part.substring(7).trim();
492                                                 cc.setNewlineGapSize(parseBoundSize(gapSz, true, true));
493                                                 continue;
494                                         }
495                                 }
496
497                                 if (c == 'f' && (part.equals("flowy") || part.equals("flowx"))) {
498                                         cc.setFlowX(part.charAt(4) == 'x' ? Boolean.TRUE : Boolean.FALSE);
499                                         continue;
500                                 }
501
502                                 if (c == 's') {
503                                         ix = startsWithLenient(part, "skip", 4, true);
504                                         if (ix > -1) {
505                                                 String num = part.substring(ix).trim();
506                                                 cc.setSkip(num.length() != 0 ? Integer.parseInt(num) : 1);
507                                                 continue;
508                                         }
509
510                                         ix = startsWithLenient(part, "split", 5, true);
511                                         if (ix > -1) {
512                                                 String split = part.substring(ix).trim();
513                                                 cc.setSplit(split.length() > 0 ? Integer.parseInt(split) : LayoutUtil.INF);
514                                                 continue;
515                                         }
516
517                                         if (part.equals("south")) {
518                                                 cc.setDockSide(2);
519                                                 continue;
520                                         }
521
522                                         ix = startsWithLenient(part, new String[]{"spany", "sy"}, new int[]{5, 2}, true);
523                                         if (ix > -1) {
524                                                 cc.setSpanY(parseSpan(part.substring(ix).trim()));
525                                                 continue;
526                                         }
527
528                                         ix = startsWithLenient(part, new String[]{"spanx", "sx"}, new int[]{5, 2}, true);
529                                         if (ix > -1) {
530                                                 cc.setSpanX(parseSpan(part.substring(ix).trim()));
531                                                 continue;
532                                         }
533
534                                         ix = startsWithLenient(part, "span", 4, true);
535                                         if (ix > -1) {
536                                                 String[] spans = toTrimmedTokens(part.substring(ix).trim(), ' ');
537                                                 cc.setSpanX(spans[0].length() > 0 ? Integer.parseInt(spans[0]) : LayoutUtil.INF);
538                                                 cc.setSpanY(spans.length > 1 ? Integer.parseInt(spans[1]) : 1);
539                                                 continue;
540                                         }
541
542                                         ix = startsWithLenient(part, "shrinkx", 7, true);
543                                         if (ix > -1) {
544                                                 cc.getHorizontal().setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
545                                                 continue;
546                                         }
547
548                                         ix = startsWithLenient(part, "shrinky", 7, true);
549                                         if (ix > -1) {
550                                                 cc.getVertical().setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
551                                                 continue;
552                                         }
553
554                                         ix = startsWithLenient(part, "shrink", 6, false);
555                                         if (ix > -1) {
556                                                 String[] shrinks = toTrimmedTokens(part.substring(ix).trim(), ' ');
557                                                 cc.getHorizontal().setShrink(parseFloat(shrinks[0], ResizeConstraint.WEIGHT_100));
558                                                 if (shrinks.length > 1)
559                                                         cc.getVertical().setShrink(parseFloat(shrinks[1], ResizeConstraint.WEIGHT_100));
560                                                 continue;
561                                         }
562
563                                         ix = startsWithLenient(part, new String[]{"shrinkprio", "shp"}, new int[]{10, 3}, true);
564                                         if (ix > -1) {
565                                                 String sp = part.substring(ix).trim();
566                                                 if (sp.startsWith("x") || sp.startsWith("y")) { // To handle "gpx", "gpy", "shrinkpriorityx", shrinkpriorityy"
567                                                         (sp.startsWith("x") ? cc.getHorizontal() : cc.getVertical()).setShrinkPriority(Integer.parseInt(sp.substring(2)));
568                                                 } else {
569                                                         String[] shrinks = toTrimmedTokens(sp, ' ');
570                                                         cc.getHorizontal().setShrinkPriority(Integer.parseInt(shrinks[0]));
571                                                         if (shrinks.length > 1)
572                                                                 cc.getVertical().setShrinkPriority(Integer.parseInt(shrinks[1]));
573                                                 }
574                                                 continue;
575                                         }
576
577                                         ix = startsWithLenient(part, new String[]{"sizegroupx", "sizegroupy", "sgx", "sgy"}, new int[]{9, 9, 2, 2}, true);
578                                         if (ix > -1) {
579                                                 String sg = part.substring(ix).trim();
580                                                 char lc = part.charAt(ix - 1);
581                                                 if (lc != 'y')
582                                                         cc.getHorizontal().setSizeGroup(sg);
583                                                 if (lc != 'x')
584                                                         cc.getVertical().setSizeGroup(sg);
585                                                 continue;
586                                         }
587                                 }
588
589                                 if (c == 'g') {
590                                         ix = startsWithLenient(part, "growx", 5, true);
591                                         if (ix > -1) {
592                                                 cc.getHorizontal().setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
593                                                 continue;
594                                         }
595
596                                         ix = startsWithLenient(part, "growy", 5, true);
597                                         if (ix > -1) {
598                                                 cc.getVertical().setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
599                                                 continue;
600                                         }
601
602                                         ix = startsWithLenient(part, "grow", 4, false);
603                                         if (ix > -1) {
604                                                 String[] grows = toTrimmedTokens(part.substring(ix).trim(), ' ');
605                                                 cc.getHorizontal().setGrow(parseFloat(grows[0], ResizeConstraint.WEIGHT_100));
606                                                 cc.getVertical().setGrow(parseFloat(grows.length > 1 ? grows[1] : "", ResizeConstraint.WEIGHT_100));
607                                                 continue;
608                                         }
609
610                                         ix = startsWithLenient(part, new String[]{"growprio", "gp"}, new int[]{8, 2}, true);
611                                         if (ix > -1) {
612                                                 String gp = part.substring(ix).trim();
613                                                 char c0 = gp.length() > 0 ? gp.charAt(0) : ' ';
614                                                 if (c0 == 'x' || c0 == 'y') { // To handle "gpx", "gpy", "growpriorityx", growpriorityy"
615                                                         (c0 == 'x' ? cc.getHorizontal() : cc.getVertical()).setGrowPriority(Integer.parseInt(gp.substring(2)));
616                                                 } else {
617                                                         String[] grows = toTrimmedTokens(gp, ' ');
618                                                         cc.getHorizontal().setGrowPriority(Integer.parseInt(grows[0]));
619                                                         if (grows.length > 1)
620                                                                 cc.getVertical().setGrowPriority(Integer.parseInt(grows[1]));
621                                                 }
622                                                 continue;
623                                         }
624
625                                         if (part.startsWith("gap")) {
626                                                 BoundSize[] gaps = parseGaps(part); // Changes order!!
627                                                 if (gaps[0] != null)
628                                                         cc.getVertical().setGapBefore(gaps[0]);
629                                                 if (gaps[1] != null)
630                                                         cc.getHorizontal().setGapBefore(gaps[1]);
631                                                 if (gaps[2] != null)
632                                                         cc.getVertical().setGapAfter(gaps[2]);
633                                                 if (gaps[3] != null)
634                                                         cc.getHorizontal().setGapAfter(gaps[3]);
635                                                 continue;
636                                         }
637                                 }
638
639                                 if (c == 'a') {
640                                         ix = startsWithLenient(part, new String[]{"aligny", "ay"}, new int[]{6, 2}, true);
641                                         if (ix > -1) {
642                                                 cc.getVertical().setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), false, null));
643                                                 continue;
644                                         }
645
646                                         ix = startsWithLenient(part, new String[]{"alignx", "ax"}, new int[]{6, 2}, true);
647                                         if (ix > -1) {
648                                                 cc.getHorizontal().setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), true, null));
649                                                 continue;
650                                         }
651
652                                         ix = startsWithLenient(part, "align", 2, true);
653                                         if (ix > -1) {
654                                                 String[] gaps = toTrimmedTokens(part.substring(ix).trim(), ' ');
655                                                 cc.getHorizontal().setAlign(parseUnitValueOrAlign(gaps[0], true, null));
656                                                 if (gaps.length > 1)
657                                                         cc.getVertical().setAlign(parseUnitValueOrAlign(gaps[1], false, null));
658                                                 continue;
659                                         }
660                                 }
661
662                                 if ((c == 'x' || c == 'y') && part.length() > 2) {
663                                         char c2 = part.charAt(1);
664                                         if (c2 == ' ' || (c2 == '2' && part.charAt(2) == ' ')) {
665                                                 if (cc.getPos() == null) {
666                                                         cc.setPos(new UnitValue[4]);
667                                                 } else if (cc.isBoundsInGrid() == false) {
668                                                         throw new IllegalArgumentException("Cannot combine 'position' with 'x/y/x2/y2' keywords.");
669                                                 }
670
671                                                 int edge = (c == 'x' ? 0 : 1) + (c2 == '2' ? 2 : 0);
672                                                 UnitValue[] pos = cc.getPos();
673                                                 pos[edge] = parseUnitValue(part.substring(2).trim(), null, c == 'x');
674                                                 cc.setPos(pos);
675                                                 cc.setBoundsInGrid(true);
676                                                 continue;
677                                         }
678                                 }
679
680                                 if (c == 'c') {
681                                         ix = startsWithLenient(part, "cell", 4, true);
682                                         if (ix > -1) {
683                                                 String[] grs = toTrimmedTokens(part.substring(ix).trim(), ' ');
684                                                 if (grs.length < 2)
685                                                         throw new IllegalArgumentException("At least two integers must follow " + part);
686                                                 cc.setCellX(Integer.parseInt(grs[0]));
687                                                 cc.setCellY(Integer.parseInt(grs[1]));
688                                                 if (grs.length > 2)
689                                                         cc.setSpanX(Integer.parseInt(grs[2]));
690                                                 if (grs.length > 3)
691                                                         cc.setSpanY(Integer.parseInt(grs[3]));
692                                                 continue;
693                                         }
694                                 }
695
696                                 if (c == 'p') {
697                                         ix = startsWithLenient(part, "pos", 3, true);
698                                         if (ix > -1) {
699                                                 if (cc.getPos() != null && cc.isBoundsInGrid())
700                                                         throw new IllegalArgumentException("Can not combine 'pos' with 'x/y/x2/y2' keywords.");
701
702                                                 String[] pos = toTrimmedTokens(part.substring(ix).trim(), ' ');
703                                                 UnitValue[] bounds = new UnitValue[4];
704                                                 for (int j = 0; j < pos.length; j++)
705                                                         bounds[j] = parseUnitValue(pos[j], null, j % 2 == 0);
706
707                                                 if (bounds[0] == null && bounds[2] == null || bounds[1] == null && bounds[3] == null)
708                                                         throw new IllegalArgumentException("Both x and x2 or y and y2 can not be null!");
709
710                                                 cc.setPos(bounds);
711                                                 cc.setBoundsInGrid(false);
712                                                 continue;
713                                         }
714
715                                         ix = startsWithLenient(part, "pad", 3, true);
716                                         if (ix > -1) {
717                                                 UnitValue[] p = parseInsets(part.substring(ix).trim(), false);
718                                                 cc.setPadding(new UnitValue[]{
719                                                         p[0],
720                                                         p.length > 1 ? p[1] : null,
721                                                         p.length > 2 ? p[2] : null,
722                                                         p.length > 3 ? p[3] : null});
723                                                 continue;
724                                         }
725
726                                         ix = startsWithLenient(part, "pushx", 5, true);
727                                         if (ix > -1) {
728                                                 cc.setPushX(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
729                                                 continue;
730                                         }
731
732                                         ix = startsWithLenient(part, "pushy", 5, true);
733                                         if (ix > -1) {
734                                                 cc.setPushY(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100));
735                                                 continue;
736                                         }
737
738                                         ix = startsWithLenient(part, "push", 4, false);
739                                         if (ix > -1) {
740                                                 String[] pushs = toTrimmedTokens(part.substring(ix).trim(), ' ');
741                                                 cc.setPushX(parseFloat(pushs[0], ResizeConstraint.WEIGHT_100));
742                                                 cc.setPushY(parseFloat(pushs.length > 1 ? pushs[1] : "", ResizeConstraint.WEIGHT_100));
743                                                 continue;
744                                         }
745                                 }
746
747                                 if (c == 't') {
748                                         ix = startsWithLenient(part, "tag", 3, true);
749                                         if (ix > -1) {
750                                                 cc.setTag(part.substring(ix).trim());
751                                                 continue;
752                                         }
753                                 }
754
755                                 if (c == 'w' || c == 'h') {
756                                         if (part.equals("wrap")) {
757                                                 cc.setWrap(true);
758                                                 continue;
759                                         }
760
761                                         if (part.startsWith("wrap ")) {
762                                                 String gapSz = part.substring(5).trim();
763                                                 cc.setWrapGapSize(parseBoundSize(gapSz, true, true));
764                                                 continue;
765                                         }
766
767                                         boolean isHor = c == 'w';
768                                         if (isHor && (part.startsWith("w ") || part.startsWith("width "))) {
769                                                 String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 6).trim();
770                                                 cc.getHorizontal().setSize(parseBoundSize(uvStr, false, true));
771                                                 continue;
772                                         }
773
774                                         if (!isHor && (part.startsWith("h ") || part.startsWith("height "))) {
775                                                 String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 7).trim();
776                                                 cc.getVertical().setSize(parseBoundSize(uvStr, false, false));
777                                                 continue;
778                                         }
779
780                                         if (part.startsWith("wmin ") || part.startsWith("wmax ") || part.startsWith("hmin ") || part.startsWith("hmax ")) {
781                                                 String uvStr = part.substring(5).trim();
782                                                 if (uvStr.length() > 0) {
783                                                         UnitValue uv = parseUnitValue(uvStr, null, isHor);
784                                                         boolean isMin = part.charAt(3) == 'n';
785                                                         DimConstraint dc = isHor ? cc.getHorizontal() : cc.getVertical();
786                                                         dc.setSize(new BoundSize(
787                                                                 isMin ? uv : dc.getSize().getMin(),
788                                                                 dc.getSize().getPreferred(),
789                                                                 isMin ? (dc.getSize().getMax()) : uv,
790                                                                 uvStr
791                                                         ));
792                                                         continue;
793                                                 }
794                                         }
795
796                                         if (part.equals("west")) {
797                                                 cc.setDockSide(1);
798                                                 continue;
799                                         }
800
801                                         if (part.startsWith("hidemode ")) {
802                                                 cc.setHideMode(Integer.parseInt(part.substring(9)));
803                                                 continue;
804                                         }
805                                 }
806
807                                 if (c == 'i' && part.startsWith("id ")) {
808                                         cc.setId(part.substring(3).trim());
809                                         int dIx = cc.getId().indexOf('.');
810                                         if (dIx == 0 || dIx == cc.getId().length() - 1)
811                                                 throw new IllegalArgumentException("Dot must not be first or last!");
812
813                                         continue;
814                                 }
815
816                                 if (c == 'e') {
817                                         if (part.equals("east")) {
818                                                 cc.setDockSide(3);
819                                                 continue;
820                                         }
821
822                                         if (part.equals("external")) {
823                                                 cc.setExternal(true);
824                                                 continue;
825                                         }
826
827                                         ix = startsWithLenient(part, new String[]{"endgroupx", "endgroupy", "egx", "egy"}, new int[]{-1, -1, -1, -1}, true);
828                                         if (ix > -1) {
829                                                 String sg = part.substring(ix).trim();
830                                                 char lc = part.charAt(ix - 1);
831                                                 DimConstraint dc = (lc == 'x' ? cc.getHorizontal() : cc.getVertical());
832                                                 dc.setEndGroup(sg);
833                                                 continue;
834                                         }
835                                 }
836
837                                 if (c == 'd') {
838                                         if (part.equals("dock north")) {
839                                                 cc.setDockSide(0);
840                                                 continue;
841                                         }
842                                         if (part.equals("dock west")) {
843                                                 cc.setDockSide(1);
844                                                 continue;
845                                         }
846                                         if (part.equals("dock south")) {
847                                                 cc.setDockSide(2);
848                                                 continue;
849                                         }
850                                         if (part.equals("dock east")) {
851                                                 cc.setDockSide(3);
852                                                 continue;
853                                         }
854
855                                         if (part.equals("dock center")) {
856                                                 cc.getHorizontal().setGrow(100f);
857                                                 cc.getVertical().setGrow(100f);
858                                                 cc.setPushX(100f);
859                                                 cc.setPushY(100f);
860                                                 continue;
861                                         }
862                                 }
863
864                                 if (c == 'v') {
865                                         ix = startsWithLenient(part, new String[] {"visualpadding", "vp"}, new int[] {3, 2}, true);
866                                         if (ix > -1) {
867                                                 UnitValue[] p = parseInsets(part.substring(ix).trim(), false);
868                                                 cc.setVisualPadding(new UnitValue[] {
869                                                         p[0],
870                                                         p.length > 1 ? p[1] : null,
871                                                         p.length > 2 ? p[2] : null,
872                                                         p.length > 3 ? p[3] : null});
873                                                 continue;
874                                         }
875                                 }
876
877                                 UnitValue horAlign = parseAlignKeywords(part, true);
878                                 if (horAlign != null) {
879                                         cc.getHorizontal().setAlign(horAlign);
880                                         continue;
881                                 }
882
883                                 UnitValue verAlign = parseAlignKeywords(part, false);
884                                 if (verAlign != null) {
885                                         cc.getVertical().setAlign(verAlign);
886                                         continue;
887                                 }
888
889                                 throw new IllegalArgumentException("Unknown keyword.");
890
891                         } catch (Exception ex) {
892                                 throw new IllegalArgumentException("Error parsing Constraint: '" + part + "'", ex);
893                         }
894                 }
895
896 //              cc = (CC) serializeTest(cc);
897
898                 return cc;
899         }
900
901         /** Parses insets which consists of 1-4 <code>UnitValue</code>s.
902          * @param s The string to parse. E.g. "10 10 10 10" or "20". If less than 4 groups the last will be used for the missing.
903          * @param acceptPanel If "panel" and "dialog" should be accepted. They are used to access platform defaults.
904          * @return An array of length 4 with the parsed insets.
905          * @throws IllegalArgumentException if the parsing could not be done.
906          */
907         public static UnitValue[] parseInsets(String s, boolean acceptPanel)
908         {
909                 if (s.length() == 0 || s.equals("dialog") || s.equals("panel")) {
910                         if (acceptPanel == false)
911                                 throw new IllegalArgumentException("Insets now allowed: " + s + "\n");
912
913                         boolean isPanel = s.startsWith("p");
914                         UnitValue[] ins = new UnitValue[4];
915                         for (int j = 0; j < 4; j++)
916                                 ins[j] = isPanel ? PlatformDefaults.getPanelInsets(j) : PlatformDefaults.getDialogInsets(j);
917
918                         return ins;
919                 } else {
920                         String[] insS = toTrimmedTokens(s, ' ');
921                         UnitValue[] ins = new UnitValue[4];
922                         for (int j = 0; j < 4; j++) {
923                                 UnitValue insSz = parseUnitValue(insS[j < insS.length ? j : insS.length - 1], UnitValue.ZERO, j % 2 == 1);
924                                 ins[j] = insSz != null ? insSz : PlatformDefaults.getPanelInsets(j);
925                         }
926                         return ins;
927                 }
928         }
929
930         /** Parses gaps.
931          * @param s The string that contains gap information. Should start with "gap".
932          * @return The gaps as specified in <code>s</code>. Indexed: <code>[top,left,bottom,right][min,pref,max]</code> or
933          * [before,after][min,pref,max] if <code>oneDim</code> is true.
934          */
935         private static BoundSize[] parseGaps(String s)
936         {
937                 BoundSize[] ret = new BoundSize[4];
938
939                 int ix = startsWithLenient(s, "gaptop", -1, true);
940                 if (ix > -1) {
941                         s = s.substring(ix).trim();
942                         ret[0] = parseBoundSize(s, true, false);
943                         return ret;
944                 }
945
946                 ix = startsWithLenient(s, "gapleft", -1, true);
947                 if (ix > -1) {
948                         s = s.substring(ix).trim();
949                         ret[1] = parseBoundSize(s, true, true);
950                         return ret;
951                 }
952
953                 ix = startsWithLenient(s, "gapbottom", -1, true);
954                 if (ix > -1) {
955                         s = s.substring(ix).trim();
956                         ret[2] = parseBoundSize(s, true, false);
957                         return ret;
958                 }
959
960                 ix = startsWithLenient(s, "gapright", -1, true);
961                 if (ix > -1) {
962                         s = s.substring(ix).trim();
963                         ret[3] = parseBoundSize(s, true, true);
964                         return ret;
965                 }
966
967                 ix = startsWithLenient(s, "gapbefore", -1, true);
968                 if (ix > -1) {
969                         s = s.substring(ix).trim();
970                         ret[1] = parseBoundSize(s, true, true);
971                         return ret;
972                 }
973
974                 ix = startsWithLenient(s, "gapafter", -1, true);
975                 if (ix > -1) {
976                         s = s.substring(ix).trim();
977                         ret[3] = parseBoundSize(s, true, true);
978                         return ret;
979                 }
980
981                 ix = startsWithLenient(s, new String[] {"gapx", "gapy"}, null, true);
982                 if (ix > -1) {
983                         boolean x = s.charAt(3) == 'x';
984                         String[] gaps = toTrimmedTokens(s.substring(ix).trim(), ' ');
985                         ret[x ? 1 : 0] = parseBoundSize(gaps[0], true, x);
986                         if (gaps.length > 1)
987                                 ret[x ? 3 : 2] = parseBoundSize(gaps[1], true, !x);
988                         return ret;
989                 }
990
991                 ix = startsWithLenient(s, "gap ", 1, true);
992                 if (ix > -1) {
993                         String[] gaps = toTrimmedTokens(s.substring(ix).trim(), ' ');
994
995                         ret[1] = parseBoundSize(gaps[0], true, true);   // left
996                         if (gaps.length > 1) {
997                                 ret[3] = parseBoundSize(gaps[1], true, false);   // right
998                                 if (gaps.length > 2) {
999                                         ret[0] = parseBoundSize(gaps[2], true, true);   // top
1000                                         if (gaps.length > 3)
1001                                                 ret[2] = parseBoundSize(gaps[3], true, false);   // bottom
1002                                 }
1003                         }
1004                         return ret;
1005                 }
1006
1007                 throw new IllegalArgumentException("Unknown Gap part: '" + s + "'");
1008         }
1009
1010         private static int parseSpan(String s)
1011         {
1012                 return s.length() > 0 ? Integer.parseInt(s) : LayoutUtil.INF;
1013         }
1014
1015         private static Float parseFloat(String s, Float nullVal)
1016         {
1017                 return s.length() > 0 ? new Float(Float.parseFloat(s)) : nullVal;
1018         }
1019
1020         /** Parses a single "min:pref:max" value. May look something like <code>"10px:20lp:30%"</code> or <code>"pref!"</code>.
1021          * @param s The string to parse. Not <code>null</code>.
1022          * @param isGap If this bound size is a gap (different empty string handling).
1023          * @param isHor If the size is for the horizontal dimension.
1024          * @return A bound size that may be <code>null</code> if the string was "null", "n" or <code>null</code>.
1025          */
1026         public static BoundSize parseBoundSize(String s, boolean isGap, boolean isHor)
1027         {
1028                 if (s.length() == 0 || s.equals("null") || s.equals("n"))
1029                         return null;
1030
1031                 String cs = s;
1032                 boolean push = false;
1033                 if (s.endsWith("push")) {
1034                         push = true;
1035                         int l = s.length();
1036                         s = s.substring(0, l - (s.endsWith(":push") ? 5 : 4));
1037                         if (s.length() == 0)
1038                                 return new BoundSize(null, null, null, true, cs);
1039                 }
1040
1041                 String[] sizes = toTrimmedTokens(s, ':');
1042                 String s0 = sizes[0];
1043
1044                 if (sizes.length == 1) {
1045                         boolean hasEM = s0.endsWith("!");
1046                         if (hasEM)
1047                                 s0 = s0.substring(0, s0.length() - 1);
1048                         UnitValue uv = parseUnitValue(s0, null, isHor);
1049                         return new BoundSize(((isGap || hasEM) ? uv : null), uv, (hasEM ? uv : null), push, cs);
1050
1051                 } else if (sizes.length == 2) {
1052                         return new BoundSize(parseUnitValue(s0, null, isHor), parseUnitValue(sizes[1], null, isHor), null, push, cs);
1053                 } else if (sizes.length == 3) {
1054                         return new BoundSize(parseUnitValue(s0, null, isHor), parseUnitValue(sizes[1], null, isHor), parseUnitValue(sizes[2], null, isHor), push, cs);
1055                 } else {
1056                         throw new IllegalArgumentException("Min:Preferred:Max size section must contain 0, 1 or 2 colons. '" + cs + "'");
1057                 }
1058         }
1059
1060         /** Parses a single unit value that may also be an alignment as parsed by {@link #parseAlignKeywords(String, boolean)}.
1061          * @param s The string to parse. Not <code>null</code>. May look something like <code>"10px"</code> or <code>"5dlu"</code>.
1062          * @param isHor If the value is for the horizontal dimension.
1063          * @param emptyReplacement A replacement if <code>s</code> is empty. May be <code>null</code>.
1064          * @return The parsed unit value. May be <code>null</code>.
1065          */
1066         public static UnitValue parseUnitValueOrAlign(String s, boolean isHor, UnitValue emptyReplacement)
1067         {
1068                 if (s.length() == 0)
1069                         return emptyReplacement;
1070
1071                 UnitValue align = parseAlignKeywords(s, isHor);
1072                 if (align != null)
1073                         return align;
1074
1075                 return parseUnitValue(s, emptyReplacement, isHor);
1076         }
1077
1078         /** Parses a single unit value. E.g. "10px" or "5in"
1079          * @param s The string to parse. Not <code>null</code>. May look something like <code>"10px"</code> or <code>"5dlu"</code>.
1080          * @param isHor If the value is for the horizontal dimension.
1081          * @return The parsed unit value. <code>null</code> is empty string,
1082          */
1083         public static UnitValue parseUnitValue(String s, boolean isHor)
1084         {
1085                 return parseUnitValue(s, null, isHor);
1086         }
1087
1088         /** Parses a single unit value.
1089          * @param s The string to parse. May be <code>null</code>. May look something like <code>"10px"</code> or <code>"5dlu"</code>.
1090          * @param emptyReplacement A replacement <code>s</code> is empty or <code>null</code>. May be <code>null</code>.
1091          * @param isHor If the value is for the horizontal dimension.
1092          * @return The parsed unit value. May be <code>null</code>.
1093          */
1094         private static UnitValue parseUnitValue(String s, UnitValue emptyReplacement, boolean isHor)
1095         {
1096                 if (s == null || s.length() == 0)
1097                         return emptyReplacement;
1098
1099                 String cs = s; // Save creation string.
1100                 char c0 = s.charAt(0);
1101
1102                 // Remove start and end parentheses, if there.
1103                 if (c0 == '(' && s.charAt(s.length() - 1) == ')')
1104                         s = s.substring(1, s.length() - 1);
1105
1106                 if (c0 == 'n' && (s.equals("null") || s.equals("n")))
1107                         return null;
1108
1109                 if (c0 == 'i' && s.equals("inf"))
1110                         return UnitValue.INF;
1111
1112                 int oper = getOper(s);
1113                 boolean inline = oper == UnitValue.ADD || oper == UnitValue.SUB || oper == UnitValue.MUL || oper == UnitValue.DIV;
1114
1115                 if (oper != UnitValue.STATIC) {  // It is a multi-value
1116
1117                         String[] uvs;
1118                         if (inline == false) {   // If the format is of type "opr(xxx,yyy)" (compared to in-line "10%+15px")
1119                                 String sub = s.substring(4, s.length() - 1).trim();
1120                                 uvs = toTrimmedTokens(sub, ',');
1121                                 if (uvs.length == 1)
1122                                         return parseUnitValue(sub, null, isHor);
1123                         } else {
1124                                 char delim;
1125                                 if (oper == UnitValue.ADD) {
1126                                         delim = '+';
1127                                 } else if (oper == UnitValue.SUB) {
1128                                         delim = '-';
1129                                 } else if (oper == UnitValue.MUL) {
1130                                         delim = '*';
1131                                 } else {    // div left
1132                                         delim = '/';
1133                                 }
1134                                 uvs = toTrimmedTokens(s, delim);
1135                                 if (uvs.length > 2) {   // More than one +-*/.
1136                                         String last = uvs[uvs.length - 1];
1137                                         String first = s.substring(0, s.length() - last.length() - 1);
1138                                         uvs = new String[] {first, last};
1139                                 }
1140                         }
1141
1142                         if (uvs.length != 2)
1143                                 throw new IllegalArgumentException("Malformed UnitValue: '" + s + "'");
1144
1145                         UnitValue sub1 = parseUnitValue(uvs[0], null, isHor);
1146                         UnitValue sub2 = parseUnitValue(uvs[1], null, isHor);
1147
1148                         if (sub1 == null || sub2 == null)
1149                                 throw new IllegalArgumentException("Malformed UnitValue. Must be two sub-values: '" + s + "'");
1150
1151                         return new UnitValue(isHor, oper, sub1, sub2, cs);
1152                 } else {
1153                         try {
1154                                 String[] numParts = getNumTextParts(s);
1155                                 float value = numParts[0].length() > 0 ? Float.parseFloat(numParts[0]) : 1;     // e.g. "related" has no number part..
1156
1157                                 return new UnitValue(value, numParts[1], isHor, oper, cs);
1158
1159                         } catch(Exception e) {
1160                                 throw new IllegalArgumentException("Malformed UnitValue: '" + s + "'", e);
1161                         }
1162                 }
1163         }
1164
1165         /** Parses alignment keywords and returns the appropriate <code>UnitValue</code>.
1166          * @param s The string to parse. Not <code>null</code>.
1167          * @param isHor If alignments for horizontal is checked. <code>false</code> means vertical.
1168          * @return The unit value or <code>null</code> if not recognized (no exception).
1169          */
1170         static UnitValue parseAlignKeywords(String s, boolean isHor)
1171         {
1172                 if (startsWithLenient(s, "center", 1, false) != -1)
1173                         return UnitValue.CENTER;
1174
1175                 if (isHor) {
1176                         if (startsWithLenient(s, "left", 1, false) != -1)
1177                                 return UnitValue.LEFT;
1178
1179                         if (startsWithLenient(s, "right", 1, false) != -1)
1180                                 return UnitValue.RIGHT;
1181
1182                         if (startsWithLenient(s, "leading", 4, false) != -1)
1183                                 return UnitValue.LEADING;
1184
1185                         if (startsWithLenient(s, "trailing", 5, false) != -1)
1186                                 return UnitValue.TRAILING;
1187
1188                         if (startsWithLenient(s, "label", 5, false) != -1)
1189                                 return UnitValue.LABEL;
1190
1191                 } else {
1192
1193                         if (startsWithLenient(s, "baseline", 4, false) != -1)
1194                                 return UnitValue.BASELINE_IDENTITY;
1195
1196                         if (startsWithLenient(s, "top", 1, false) != -1)
1197                                 return UnitValue.TOP;
1198
1199                         if (startsWithLenient(s, "bottom", 1, false) != -1)
1200                                 return UnitValue.BOTTOM;
1201                 }
1202
1203                 return null;
1204         }
1205
1206         /** Splits a text-number combination such as "hello 10.0" into <code>{"hello", "10.0"}</code>.
1207          * @param s The string to split. Not <code>null</code>. Needs be be reasonably formatted since the method
1208          * only finds the first 0-9 or . and cuts the string in half there.
1209          * @return Always length 2 and no <code>null</code> elements. Elements are "" if no part found.
1210          */
1211         private static String[] getNumTextParts(String s)
1212         {
1213                 for (int i = 0, iSz = s.length(); i < iSz; i++) {
1214                         char c = s.charAt(i);
1215                         if (c == ' ')
1216                                 throw new IllegalArgumentException("Space in UnitValue: '" + s + "'");
1217
1218                         if ((c < '0' || c > '9') && c != '.' && c != '-')
1219                                 return new String[] {s.substring(0, i).trim(), s.substring(i).trim()};
1220                 }
1221                 return new String[] {s, ""};
1222         }
1223
1224         /** Returns the operation depending on the start character.
1225          * @param s The string to check. Not <code>null</code>.
1226          * @return E.g. UnitValue.ADD, UnitValue.SUB or UnitValue.STATIC. Returns negative value for in-line operations.
1227          */
1228         private static int getOper(String s)
1229         {
1230                 int len = s.length();
1231                 if (len < 3)
1232                         return UnitValue.STATIC;
1233
1234                 if (len > 5 && s.charAt(3) == '(' && s.charAt(len - 1) == ')') {
1235                         if (s.startsWith("min("))
1236                                 return UnitValue.MIN;
1237
1238                         if (s.startsWith("max("))
1239                                 return UnitValue.MAX;
1240
1241                         if (s.startsWith("mid("))
1242                                 return UnitValue.MID;
1243                 }
1244
1245                 // Try in-line add/sub. E.g. "pref+10px".
1246                 for (int j = 0; j < 2; j++) {   // First +-   then */   (precedence)
1247                         for (int i = len - 1, p = 0; i > 0; i--) {
1248                                 char c = s.charAt(i);
1249                                 if (c == ')') {
1250                                         p++;
1251                                 } else if (c == '(') {
1252                                         p--;
1253                                 } else if (p == 0) {
1254                                         if (j == 0) {
1255                                                 if (c == '+')
1256                                                         return UnitValue.ADD;
1257                                                 if (c == '-')
1258                                                         return UnitValue.SUB;
1259                                         } else {
1260                                                 if (c == '*')
1261                                                         return UnitValue.MUL;
1262                                                 if (c == '/')
1263                                                         return UnitValue.DIV;
1264                                         }
1265                                 }
1266                         }
1267                 }
1268                 return UnitValue.STATIC;
1269         }
1270
1271         /** Returns if a string shares at least a specified numbers starting characters with a number of matches.
1272          * <p>
1273          * This method just exercise {@link #startsWithLenient(String, String, int, boolean)} with every one of
1274          * <code>matches</code> and <code>minChars</code>.
1275          * @param s The string to check. Not <code>null</code>.
1276          * @param matches A number of possible starts for <code>s</code>.
1277          * @param minChars The minimum number of characters to match for every element in <code>matches</code>. Needs
1278          * to be of same length as <code>matches</code>. Can be <code>null</code>.
1279          * @param acceptTrailing If after the required number of characters are matched on recognized characters that are not
1280          * in one of the the <code>matches</code> string should be accepted. For instance if "abczz" should be matched with
1281          * "abcdef" and min chars 3.
1282          * @return The index of the first unmatched character if <code>minChars</code> was reached or <code>-1</code> if a match was not
1283          * found.
1284          */
1285         private static int startsWithLenient(String s, String[] matches, int[] minChars, boolean acceptTrailing)
1286         {
1287                 for (int i = 0; i < matches.length; i++) {
1288                         int minChar = minChars != null ? minChars[i] : -1;
1289                         int ix = startsWithLenient(s, matches[i], minChar, acceptTrailing);
1290                         if (ix > -1)
1291                                 return ix;
1292                 }
1293                 return -1;
1294         }
1295
1296         /** Returns if a string shares at least a specified numbers starting characters with a match.
1297          * @param s The string to check. Not <code>null</code> and must be trimmed.
1298          * @param match The possible start for <code>s</code>. Not <code>null</code> and must be trimmed.
1299          * @param minChars The mimimum number of characters to match to <code>s</code> for it this to be considered a match. -1 means
1300          * the full length of <code>match</code>.
1301          * @param acceptTrailing If after the required number of charecters are matched unrecognized characters that are not
1302          * in one of the the <code>matches</code> string should be accepted. For instance if "abczz" should be matched with
1303          * "abcdef" and min chars 3.
1304          * @return The index of the first unmatched character if <code>minChars</code> was reached or <code>-1</code> if a match was not
1305          * found.
1306          */
1307         private static int startsWithLenient(String s, String match, int minChars, boolean acceptTrailing)
1308         {
1309                 if (s.charAt(0) != match.charAt(0)) // Fast sanity check.
1310                         return -1;
1311
1312                 if (minChars == -1)
1313                         minChars = match.length();
1314
1315                 int sSz = s.length();
1316                 if (sSz < minChars)
1317                         return -1;
1318
1319                 int mSz = match.length();
1320                 int sIx = 0;
1321                 for (int mIx = 0; mIx < mSz; sIx++, mIx++) {
1322                         while (sIx < sSz && (s.charAt(sIx) == ' ' || s.charAt(sIx) == '_'))    // Disregard spaces and _
1323                                 sIx++;
1324
1325                         if (sIx >= sSz || s.charAt(sIx) != match.charAt(mIx))
1326                                 return mIx >= minChars && (acceptTrailing || sIx >= sSz) && (sIx >= sSz || s.charAt(sIx - 1) == ' ') ? sIx : -1;
1327                 }
1328                 return sIx >= sSz || acceptTrailing ||s.charAt(sIx) == ' ' ? sIx : -1;
1329         }
1330
1331         /** Parses a string and returns it in those parts of the string that are separated with a <code>sep</code> character.
1332          * <p>
1333          * separator characters within parentheses will not be counted or handled in any way, whatever the depth.
1334          * <p>
1335          * A space separator will be a hit to one or more spaces and thus not return empty strings.
1336          * @param s The string to parse. If it starts and/or ends with a <code>sep</code> the first and/or last element returned will be "". If
1337          * two <code>sep</code> are next to each other and empty element will be "between" the periods. The <code>sep</code> themselves will never be returned.
1338          * @param sep The separator char.
1339          * @return Those parts of the string that are separated with <code>sep</code>. Never null and at least of size 1
1340          * @since 6.7.2 Changed so more than one space in a row works as one space.
1341          */
1342         private static String[] toTrimmedTokens(String s, char sep)
1343         {
1344                 int toks = 0, sSize = s.length();
1345                 boolean disregardDoubles = sep == ' ';
1346
1347                 // Count the sep:s
1348                 int p = 0;
1349                 for(int i = 0; i < sSize; i++) {
1350                         char c = s.charAt(i);
1351                         if (c == '(') {
1352                                 p++;
1353                         } else if (c == ')') {
1354                                 p--;
1355                         } else if (p == 0 && c == sep) {
1356                                 toks++;
1357                                 while (disregardDoubles && i < sSize - 1 && s.charAt(i + 1) == ' ')
1358                                         i++;
1359                         }
1360                         if (p < 0)
1361                                 throw new IllegalArgumentException("Unbalanced parentheses: '" + s + "'");
1362                 }
1363                 if (p != 0)
1364                         throw new IllegalArgumentException("Unbalanced parentheses: '" + s + "'");
1365
1366                 if (toks == 0)
1367                         return new String [] {s.trim()};
1368
1369                 String[] retArr = new String[toks + 1];
1370
1371                 int st = 0, pNr = 0;
1372                 p = 0;
1373                 for (int i = 0; i < sSize; i++) {
1374
1375                         char c = s.charAt(i);
1376                         if (c == '(') {
1377                                 p++;
1378                         } else if (c == ')') {
1379                                 p--;
1380                         } else if (p == 0 && c == sep) {
1381                                 retArr[pNr++] = s.substring(st, i).trim();
1382                                 st = i + 1;
1383                                 while (disregardDoubles && i < sSize - 1 && s.charAt(i + 1) == ' ')
1384                                         i++;
1385                         }
1386                 }
1387
1388                 retArr[pNr++] = s.substring(st, sSize).trim();
1389                 return retArr;
1390         }
1391
1392         /** Parses "AAA[BBB]CCC[DDD]EEE" into {"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}. Handles empty parts. Will always start and end outside
1393          * a [] block so that the number of returned elemets will always be uneven and at least of length 3.
1394          * <p>
1395          * "|" is interpreted as "][".
1396          * @param s The string. Might be "" but not null. Should be trimmed.
1397          * @return The string divided into elements. Never <code>null</code> and at least of length 3.
1398          * @throws IllegalArgumentException If a [] mismatch of some kind. (If not same [ as ] count or if the interleave.)
1399          */
1400         private static ArrayList<String> getRowColAndGapsTrimmed(String s)
1401         {
1402                 if (s.indexOf('|') != -1)
1403                         s = s.replaceAll("\\|", "][");
1404
1405                 ArrayList<String> retList = new ArrayList<String>(Math.max(s.length() >> 2 + 1, 3)); // Approx return length.
1406                 int s0 = 0, s1 = 0; // '[' and ']' count.
1407                 int st = 0; // Start of "next token to add".
1408                 for (int i = 0, iSz = s.length(); i < iSz; i++) {
1409                         char c = s.charAt(i);
1410                         if (c == '[') {
1411                                 s0++;
1412                         } else if (c == ']') {
1413                                 s1++;
1414                         } else {
1415                                 continue;
1416                         }
1417
1418                         if (s0 != s1 && (s0 - 1) != s1)
1419                                 break;  // Wrong [ or ] found. Break for throw.
1420
1421                         retList.add(s.substring(st, i).trim());
1422                         st = i + 1;
1423                 }
1424                 if (s0 != s1)
1425                         throw new IllegalArgumentException("'[' and ']' mismatch in row/column format string: " + s);
1426
1427                 if (s0 == 0) {
1428                         retList.add("");
1429                         retList.add(s);
1430                         retList.add("");
1431                 } else if (retList.size() % 2 == 0) {
1432                         retList.add(s.substring(st, s.length()));
1433                 }
1434
1435                 return retList;
1436         }
1437
1438         /** Makes <code>null</code> "", trims and converts to lower case.
1439          * @param s The string
1440          * @return Not null.
1441          */
1442         public static String prepare(String s)
1443         {
1444                 return s != null ? s.trim().toLowerCase() : "";
1445         }
1446
1447 //      /** Tests to serialize and deserialize the object with both XMLEncoder/Decoder and through Serializable
1448 //       * @param o The object to serialize
1449 //       * @return The same object after a tri through the process.
1450 //       */
1451 //      public static final Object serializeTest(Object o)
1452 //      {
1453 //              try {
1454 //                      ByteArrayOutputStream barr = new ByteArrayOutputStream();
1455 //                      XMLEncoder enc = new XMLEncoder(barr);
1456 //                      enc.writeObject(o);
1457 //                      enc.close();
1458 //
1459 //                      XMLDecoder dec = new XMLDecoder(new ByteArrayInputStream(barr.toByteArray()));
1460 //                      o = dec.readObject();
1461 //                      dec.close();
1462 //              } catch (Exception e) {
1463 //                      e.printStackTrace();
1464 //              }
1465 //
1466 //              try {
1467 //                      ByteArrayOutputStream barr = new ByteArrayOutputStream();
1468 //                      ObjectOutputStream oos = new ObjectOutputStream(barr);
1469 //                      oos.writeObject(o);
1470 //                      oos.close();
1471 //
1472 //                      ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
1473 //                      o = ois.readObject();
1474 //                      ois.close();
1475 //              } catch (Exception e) {
1476 //                      e.printStackTrace();
1477 //              }
1478 //
1479 //              return o;
1480 //      }
1481 }