View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.convert;
20  
21  import java.awt.Color;
22  
23  import java.text.FieldPosition;
24  import java.text.ParsePosition;
25  
26  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
27  
28  /**
29   * Format for colors.
30   * <p>
31   * <strong>Time Format Syntax:</strong>
32   * <p>
33   * To specify the time format use a <em>color pattern</em> string.
34   * In this pattern, all ASCII letters are reserved as pattern letters,
35   * which are defined as the following:
36   * <blockquote>
37   * <pre>
38   * Symbol   Meaning                 Presentation        Example
39   * ------   -------                 ------------        -------
40   * r        red component           (Number)            242
41   * g        green component         (Number)            242
42   * b        blue component          (Number)            242
43   * a        alpha component         (Number)            255
44   * R        red component           (Hex)               F2
45   * G        green component         (Hex)               F2
46   * B        blue component          (Hex)               F2
47   * A        alpha component         (Hex)               FF
48   * '        escape for text         (Delimiter)
49   * ''       single quote            (Literal)           '
50   * </pre>
51   * </blockquote>
52   * <p>
53   * <strong>Examples:</strong>
54   * <blockquote>
55   * <pre>
56   * Format Pattern                         Result
57   * --------------                         -------
58   * "#RRGGBB"                         ->>  #6609CC
59   * "rrr,ggg,bbb"                     ->>  102,009,204
60   * "t"                               ->>  Transparent (when alpha is zero)
61   * </pre>
62   * </blockquote>
63   *
64   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/convert/RGBColorFormat.java#0 $) $Date: 10-nov-2005.19:09:13 $
65   * @todo This class needs to be part of the public API - need to move it to
66   *        place where our public API exists, once we have figured it out.
67   */
68  class RGBColorFormat extends ColorFormat
69  {
70    /**
71     * Creates a RGBColorFormat with the specified pattern.
72     *
73     * @param pattern  the color format pattern
74     */
75    public RGBColorFormat(
76      String pattern)
77    {
78      if (pattern == null)
79        throw new IllegalArgumentException();
80  
81      _pattern = pattern;
82    }
83  
84    /**
85     * Returns the value as a Color.
86     */
87    @Override
88    public Object parseObject(
89      String        text,
90      ParsePosition pos)
91    {
92      // do not attempt to parse null color string
93      if (text == null)
94        return null;
95  
96      int start = pos.getIndex();
97      int oldStart = start;
98  
99      boolean inQuote = false; // inQuote set true when hits 1st single quote
100     char prevCh = 0;
101     int count = 0;
102     int interQuoteCount = 1; // Number of chars between quotes
103     int[] rgba = new int[_FIELD_COUNT];
104     rgba[_ALPHA_FIELD] = 255; // default alpha to full (opaque)
105 
106     for (int i=0; i < _pattern.length(); i++)
107     {
108       char ch = _pattern.charAt(i);
109 
110       if (inQuote)
111       {
112         if (ch == '\'')
113         {
114           // ends with 2nd single quote
115           inQuote = false;
116           // two consecutive quotes outside a quote means we have
117           // a quote literal we need to match.
118           if (count == 0)
119           {
120             if (start >= text.length() || ch != text.charAt(start))
121             {
122               pos.setIndex(oldStart);
123               pos.setErrorIndex(start);
124               return null;
125             }
126             start++;
127           }
128           count = 0;
129           interQuoteCount = 0;
130         }
131         else
132         {
133           // pattern uses text following from 1st single quote.
134           if (start >= text.length() || ch != text.charAt(start))
135           {
136             // Check for cases like: 'at' in pattern vs "xt"
137             // in time text, where 'a' doesn't match with 'x'.
138             // If fail to match, return null.
139             pos.setIndex(oldStart); // left unchanged
140             pos.setErrorIndex(start);
141             return null;
142           }
143           count++;
144           start++;
145         }
146       }
147       else    // !inQuote
148       {
149         if (ch == '\'')
150         {
151           inQuote = true;
152           if (count > 0) // handle cases like: e'at'
153           {
154             int startOffset = start;
155             start = _subParse(rgba, text, start, prevCh, count);
156             if (start < 0)
157             {
158               pos.setIndex(oldStart);
159               pos.setErrorIndex(startOffset);
160               return null;
161             }
162             count = 0;
163           }
164 
165           if (interQuoteCount == 0)
166           {
167             // This indicates two consecutive quotes inside a quote,
168             // for example, 'o''clock'.  We need to parse this as
169             // representing a single quote within the quote.
170             int startOffset = start;
171             if (start >= text.length() ||  ch != text.charAt(start))
172             {
173               pos.setIndex(oldStart);
174               pos.setErrorIndex(startOffset);
175               return null;
176             }
177             start++;
178             count = 1; // Make it look like we never left
179           }
180         }
181         else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
182         {
183           // ch is a color pattern
184           if (ch != prevCh && count > 0) // e.g., rrr
185           {
186             int startOffset = start;
187             start = _subParse(rgba, text, start, prevCh, count);
188             if (start < 0)
189             {
190               pos.setIndex(oldStart);
191               pos.setErrorIndex(startOffset);
192               return null;
193             }
194             prevCh = ch;
195             count = 1;
196           }
197           else
198           {
199             if (ch != prevCh)
200                 prevCh = ch;
201             count++;
202           }
203         }
204         else if (count > 0)
205         {
206           // handle cases like: "rrr,ggg,bbb", "RR.GG.BB", "r g b", etc
207           // where ch = ',', '.' or ' ', repectively.
208           int startOffset = start;
209           start = _subParse(rgba, text, start, prevCh, count);
210           if ( start < 0 )
211           {
212             pos.setIndex(oldStart);
213             pos.setErrorIndex(startOffset);
214             return null;
215           }
216           if (start >= text.length() || ch != text.charAt(start))
217           {
218             // handle cases like: 'RR g' in pattern vs. "FFx20"
219             // in color text, where ' ' doesn't match with 'x'.
220             pos.setIndex(oldStart);
221             pos.setErrorIndex(start);
222             return null;
223           }
224           start++;
225           count = 0;
226           prevCh = 0;
227         }
228         else // any other unquoted characters
229         {
230           if (start >= text.length() || ch != text.charAt(start))
231           {
232             // handle cases like: 'RR   g' in pattern vs. "FF,,,20"
233             // in color text, where "   " doesn't match with ",,,".
234             pos.setIndex(oldStart);
235             pos.setErrorIndex(start);
236             return null;
237           }
238           start++;
239         }
240 
241         interQuoteCount++;
242       }
243     }
244     // Parse the last item in the pattern
245     if (count > 0)
246     {
247       int startOffset = start;
248       start = _subParse(rgba, text, start, prevCh, count);
249       if (start < 0)
250       {
251         pos.setIndex(oldStart);
252         pos.setErrorIndex(startOffset);
253         return null;
254       }
255     }
256 
257     pos.setIndex(start);
258 
259       return new Color(rgba[_RED_FIELD],
260                        rgba[_GREEN_FIELD],
261                        rgba[_BLUE_FIELD],
262                        rgba[_ALPHA_FIELD]);
263 
264   }
265 
266   @Override
267   public StringBuffer format(
268     Color color,
269     StringBuffer toAppendTo,
270     FieldPosition pos)
271   {
272     // initialize
273     pos.setBeginIndex(0);
274     pos.setEndIndex(0);
275 
276     // do not attempt to format null color
277     if (color == null)
278       return toAppendTo;
279 
280     boolean inQuote = false; // true when between single quotes
281     char prevCh = 0; // previous pattern character
282     int count = 0;  // number of time prevCh repeated
283     for (int i=0; i < _pattern.length(); ++i)
284     {
285       char ch = _pattern.charAt(i);
286       // Use subFormat() to format a repeated pattern character
287       // when a different pattern or non-pattern character is seen
288       if (ch != prevCh && count > 0)
289       {
290         toAppendTo = _subFormat(color, prevCh, count, toAppendTo);
291         count = 0;
292       }
293       if (ch == '\'')
294       {
295         // Consecutive single quotes are a single quote literal,
296         // either outside of quotes or between quotes
297         if ((i+1) < _pattern.length() &&
298             _pattern.charAt(i+1) == '\'')
299         {
300           toAppendTo.append('\'');
301           ++i;
302         }
303         else
304         {
305           inQuote = !inQuote;
306         }
307       }
308       else if (!inQuote &&
309                (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z'))
310       {
311         // ch is a date-time pattern character to be interpreted
312         // by subFormat(); count the number of times it is repeated
313         prevCh = ch;
314         ++count;
315       }
316       else
317       {
318         // Append quoted characters and unquoted non-pattern characters
319         toAppendTo.append(ch);
320       }
321     }
322     // Format the last item in the pattern, if any
323     if (count > 0)
324     {
325       toAppendTo = _subFormat(color, prevCh, count, toAppendTo);
326     }
327 
328     return toAppendTo;
329   }
330 
331   public int length()
332   {
333     int size = 0;
334     boolean inQuote = false; // true when between single quotes
335     char prevCh = 0; // previous pattern character
336     int count = 0;  // number of time prevCh repeated
337     for (int i=0; i < _pattern.length(); ++i)
338     {
339       char ch = _pattern.charAt(i);
340       // Use subFormat() to format a repeated pattern character
341       // when a different pattern or non-pattern character is seen
342       if (ch != prevCh && count > 0)
343       {
344         size += _subLength(prevCh);
345         count = 0;
346       }
347       if (ch == '\'')
348       {
349         // Consecutive single quotes are a single quote literal,
350         // either outside of quotes or between quotes
351         if ((i+1) < _pattern.length() &&
352             _pattern.charAt(i+1) == '\'')
353         {
354           size++;
355           ++i;
356         }
357         else
358         {
359           inQuote = !inQuote;
360         }
361       }
362       else if (!inQuote &&
363                (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z'))
364       {
365         // ch is a date-time pattern character to be interpreted
366         // by subFormat(); count the number of times it is repeated
367         prevCh = ch;
368         ++count;
369       }
370       else
371       {
372         // Append quoted characters and unquoted non-pattern characters
373         size++;
374       }
375     }
376     // Format the last item in the pattern, if any
377     if (count > 0)
378     {
379       size += _subLength(prevCh);
380     }
381 
382     return size;
383   }
384 
385   // Private member function that does the real length calculations.
386   private int _subLength(
387     char ch)
388   {
389     switch (ch)
390     {
391       case 'r':
392       case 'g':
393       case 'b':
394       case 'a':
395         return 3;
396       case 'R':
397       case 'G':
398       case 'B':
399       case 'A':
400         return 2;
401       default:
402         throw new IllegalArgumentException(_LOG.getMessage(
403           "ILLEGAL_PATTERN_CHARACTER", ch));
404     }
405   }
406 
407   // Private member function that does the real color parsing.
408   private int _subParse(
409     int[] rgba,
410     String text,
411     int start,
412     char ch,
413     int count)
414   {
415     switch (ch)
416     {
417       case 'r':
418         return _subParseDecimal(rgba, _RED_FIELD, text, start, count);
419       case 'g':
420         return _subParseDecimal(rgba, _GREEN_FIELD, text, start, count);
421       case 'b':
422         return _subParseDecimal(rgba, _BLUE_FIELD, text, start, count);
423       case 'a':
424         return _subParseDecimal(rgba, _ALPHA_FIELD, text, start, count);
425       case 'R':
426         return _subParseHex(rgba, _RED_FIELD, text, start, count);
427       case 'G':
428         return _subParseHex(rgba, _GREEN_FIELD, text, start, count);
429       case 'B':
430         return _subParseHex(rgba, _BLUE_FIELD, text, start, count);
431       case 'A':
432         return _subParseHex(rgba, _ALPHA_FIELD, text, start, count);
433       default:
434         throw new IllegalArgumentException(_LOG.getMessage(
435           "ILLEGAL_PATTERN_CHARACTER", ch));
436     }
437   }
438 
439   private int _subParseDecimal(
440     int[] rgba,
441     int field,
442     String text,
443     int start,
444     int count)
445   {
446     return _subParseBase(rgba, field, text, start, count, 3, 10);
447   }
448 
449   private int _subParseHex(
450     int[] rgba,
451     int field,
452     String text,
453     int start,
454     int count)
455   {
456     return _subParseBase(rgba, field, text, start, count, 2, 16);
457   }
458 
459   private int _subParseBase(
460     int[] rgba,
461     int field,
462     String text,
463     int start,
464     int minDigits,
465     int maxDigits,
466     int base)
467   {
468     int length = text.length();
469     int atLeast = start + minDigits;
470 
471     // make sure the color string is long enough
472     if (atLeast > length)
473       return -start;
474 
475     int index;
476     int end = Math.min(length, start + maxDigits);
477     int value = 0;
478 
479     for(index=start; index < end; index++)
480     {
481       int digit = Character.digit(text.charAt(index), base);
482 
483       if (digit == -1)
484       {
485         // did not consume sufficient characters in color string
486         if (index < atLeast)
487           return -start;
488         else
489           break;
490       }
491 
492       value *= base;
493       value += digit;
494     }
495 
496     rgba[field] = value;
497 
498     return index;
499   }
500 
501   // Private member function that does the real color formatting.
502   private StringBuffer _subFormat(
503     Color color,
504     char ch,
505     int count,
506     StringBuffer toAppendTo) throws IllegalArgumentException
507   {
508     switch (ch)
509     {
510       case 'r':
511         return _subFormatDecimal(color.getRed(), count, toAppendTo);
512       case 'g':
513         return _subFormatDecimal(color.getGreen(), count, toAppendTo);
514       case 'b':
515         return _subFormatDecimal(color.getBlue(), count, toAppendTo);
516       case 'a':
517         return _subFormatDecimal(color.getAlpha(), count, toAppendTo);
518       case 'R':
519         return _subFormatHex(color.getRed(), count, toAppendTo);
520       case 'G':
521         return _subFormatHex(color.getGreen(), count, toAppendTo);
522       case 'B':
523         return _subFormatHex(color.getBlue(), count, toAppendTo);
524       case 'A':
525         return _subFormatHex(color.getAlpha(), count, toAppendTo);
526       default:
527         throw new IllegalArgumentException(_LOG.getMessage(
528           "ILLEGAL_PATTERN_CHARACTER", ch));
529     }
530   }
531 
532   private StringBuffer _subFormatDecimal(
533     int value,
534     int minDigits,
535     StringBuffer toAppendTo)
536   {
537     _prefixZeros(value, 10, minDigits, toAppendTo);
538     toAppendTo.append(value);
539     return toAppendTo;
540   }
541 
542   private StringBuffer _subFormatHex(
543     int value,
544     int minDigits,
545     StringBuffer toAppendTo)
546   {
547     String hexString = Integer.toHexString(value);
548     int digits = hexString.length();
549 
550     for (int i=0; i < minDigits - digits; i++)
551       toAppendTo.append('0');
552 
553     toAppendTo.append(Integer.toHexString(value).toUpperCase());
554 
555     return toAppendTo;
556   }
557 
558   private StringBuffer _prefixZeros(
559     int value,
560     int base,
561     int minDigits,
562     StringBuffer toAppendTo)
563   {
564     if (value >= 0)
565     {
566       int digits = (value > 0)
567                       ? (int)Math.ceil(Math.log(value) / Math.log(base))
568                       : 1;
569       while (minDigits > 0 && digits < minDigits)
570       {
571         toAppendTo.append('0');
572         minDigits--;
573       }
574     }
575     return toAppendTo;
576   }
577 
578   private String  _pattern;
579 
580   private static final int _RED_FIELD   = 0;
581   private static final int _GREEN_FIELD = 1;
582   private static final int _BLUE_FIELD  = 2;
583   private static final int _ALPHA_FIELD = 3;
584   private static final int _FIELD_COUNT = 4;
585 
586   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(RGBColorFormat.class);
587   private static final long serialVersionUID = 1L;
588 }