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.ParseException;
24  
25  import javax.el.ValueExpression;
26  
27  import javax.faces.application.FacesMessage;
28  import javax.faces.component.StateHolder;
29  import javax.faces.component.UIComponent;
30  import javax.faces.context.FacesContext;
31  import javax.faces.convert.Converter;
32  import javax.faces.convert.ConverterException;
33  import javax.faces.el.ValueBinding;
34  
35  import org.apache.myfaces.trinidad.bean.FacesBean;
36  import org.apache.myfaces.trinidad.bean.PropertyKey;
37  import org.apache.myfaces.trinidad.util.ComponentUtils;
38  import org.apache.myfaces.trinidad.util.MessageFactory;
39  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
40  
41  /**
42   * <p>Converters string to Color object and vice versa based on
43   * the patterns and the transparency set.</p>
44   *
45   * Pattern format for colors:
46   *
47   * <p>
48   * <strong>Color Format Syntax:</strong>
49   * <p>
50   * To specify the color format use a <em>color pattern</em> string.
51   * In this pattern, all ASCII letters are reserved as pattern letters,
52   * which are defined as the following:
53   * <blockquote>
54   * <pre>
55   * Symbol   Meaning                 Presentation        Example
56   * ------   -------                 ------------        -------
57   * r        red component           (Number)            242
58   * g        green component         (Number)            242
59   * b        blue component          (Number)            242
60   * a        alpha component         (Number)            255
61   * R        red component           (Hex)               F2
62   * G        green component         (Hex)               F2
63   * B        blue component          (Hex)               F2
64   * A        alpha component         (Hex)               FF
65   * '        escape for text         (Delimiter)
66   * ''       single quote            (Literal)           '
67   * </pre>
68   * </blockquote>
69   * <p>
70   * <strong>Examples:</strong>
71   * <blockquote>
72   * <pre>
73   * Format Pattern                         Result
74   * --------------                         -------
75   * "#RRGGBB"                         ->>  #6609CC
76   * "rrr,ggg,bbb"                     ->>  102,009,204
77   * "t"                               ->>  Transparent (when alpha is zero)
78   * </pre>
79   * </blockquote>
80   * <p>
81   * If patterns is not set then it defaults to patterns  "#RRGGBB", "r,g,b"
82   * The first pattern is special - it is always used for formatting color values.
83   * For default case, getAsString() will use "#RRGGBB" format to represent the color.
84   * Object as String.</p>
85   *
86   * <p>The <code>getAsObject()</code> method parses a String into a {@link java.awt.Color},
87   * according to the following algorithm:</p>
88   * <ul>
89   * <li>If the specified String is null, return
90   *     a <code>null</code>.  Otherwise, trim leading and trailing
91   *     whitespace before proceeding.</li>
92   * <li>If the specified String - after trimming - has a zero length,
93   *     return <code>null</code>.</li>
94   * <li>Parses the trimmed string as mentioned in the documentation,
95   *     If the string does not match the patterns specified  throw
96   *     {@link ConverterException} containing a {@link #CONVERT_MESSAGE_ID} message.
97   *     The detail message of {@link #CONVERT_MESSAGE_ID} can be overridden
98   *     by setting a custom error message, by calling
99   *     method <code>setMessageDetailConvert()</code>. The custom message
100  *     can contain placeholders as specified in {@link #CONVERT_MESSAGE_ID}.
101  *     The placeholders will be replaced by appropriate values as mentioned
102  *     in {@link #CONVERT_MESSAGE_ID}</li>
103  * </ul>
104  *
105  * <p>The <code>getAsString()</code> method expects a value of type
106  * {@link Color} (or a subclass), and creates a formatted
107  * String according to the following algorithm:</p>
108  * <ul>
109  * <li>If the specified value is null, return a zero-length String.</li>
110  * <li>If the specified value is a String, return it unmodified.</li>
111  * <li>return the string representation of color based on the first pattern.</li>
112  * </ul>
113  *
114  *
115  * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/convert/ColorConverter.java#0 $) $Date: 10-nov-2005.19:09:09 $
116  */
117 public class ColorConverter implements Converter, StateHolder
118 {
119 
120   /**
121    * <p>Standard converter id for this converter.</p>
122    */
123   public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.Color";
124 
125  /**
126    * <p>The message identifier of the {@link FacesMessage} to be created when
127    * input value cannot be converterd to color based on the patterns set.
128    * The message format string for this message may optionally include a
129    * <code>{0}</code>, <code>{1}</code>, <code>{4}</code> placeholdes,
130    * which will be replaced  by input value, component label  and the pattern
131    * set in the converter.</p>
132    */
133     public static final String CONVERT_MESSAGE_ID =
134         "org.apache.myfaces.trinidad.convert.ColorConverter.CONVERT";
135 
136  /**
137    * <p>The string identifier for the "Transparent" option 
138    * set in the converter.</p>
139    */
140     private static final String TRANSPARENT =
141         "org.apache.myfaces.trinidad.convert.ColorConverter.TRANSPARENT";
142 
143   // Deprecated array:  arrays should never be public constants,
144   // as they are mutable
145   /**
146    * @deprecated use getDefaultColorFormatPatterns()
147    */
148   @Deprecated
149   public static final String[] DEFAULT_COLOR_FORMAT_PATTERNS = 
150     getDefaultColorFormatPatterns();
151 
152 
153   /**
154    * <p>Returns the default patterns to be used if the pattern if not supplied.
155    * The default patterns is <code>"#RRGGBB", "r,g,b"</code>
156    * The first pattern is special, it is always used for formatting color
157    * values</p>
158    */
159   public static final String[] getDefaultColorFormatPatterns()
160   {
161     return new String[]
162     {
163       "#RRGGBB",
164       "r,g,b"
165     };
166   }
167  
168   /**
169    * <p>Construct a ColorConverter with preconfigured values.</p>
170    * @param patterns The set of R,G,B format patterns that
171    *                 are accepted by this Converter.  The first
172    *                 pattern is special - it is always used for
173    *                 formatting color values.
174    * @param allowsTransparent Indicates whether or not transparent
175    *                 colors are considered valid.
176    */
177   public ColorConverter(
178     String[] patterns,
179     boolean  allowsTransparent
180     )
181   {
182     if (patterns == null)
183     {
184       _facesBean.setProperty(_PATTERNS_KEY, getDefaultColorFormatPatterns());
185     }
186     else
187     {
188       _facesBean.setProperty(_PATTERNS_KEY, patterns);
189     }
190     setTransparentAllowed(allowsTransparent);
191   }
192 
193   /**
194    * <p>Construct a ColorConverter with the default values.
195    * The defualt patterns being "#RRGGBB", "r,g,b" and allowsTransparent is set
196    * to false.</p>
197    */
198   public ColorConverter()
199   {
200     this(null, false);
201   }
202 
203   /**
204    * <p>Convert the specified string value, which is associated with
205    * the specified {@link UIComponent}, into a Color object
206    * based on the patterns set.</p>
207    *
208    * @param context {@link FacesContext} for the request being processed
209    * @param component {@link UIComponent} with which this model object
210    *  value is associated
211    * @param value String value to be converted (may be <code>null</code>)
212    *
213    * @return <code>null</code> if the value to convert is <code>null</code>,
214    *  otherwise return a Color object.
215    *
216    * @exception ConverterException if conversion cannot be successfully
217    *  performed
218    * @exception NullPointerException if <code>context</code> or
219    *  <code>component</code> is <code>null</code>
220    *
221    */
222   public Object getAsObject(
223     FacesContext context,
224     UIComponent component,
225     String value)
226   {
227     if (context == null || component == null)
228       throw new NullPointerException(_LOG.getMessage(
229         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
230 
231     if (value == null)
232       return null;
233 
234     value = value.trim();
235 
236     if (0 == value.length())
237      return null;
238 
239     try
240     {
241       return _parseString(context, value);
242     }
243     catch (ParseException pe)
244     {
245       throw new ConverterException(
246                          _getConvertMessage(context, component, value,
247                                             getPatterns()));
248     }
249   }
250 
251   /**
252    * <p>Return a String representation for the Color object based on the first
253    * pattern in the given patterns.</p>
254    *
255    * @param context {@link FacesContext} for the request being processed
256    * @param component {@link UIComponent} with which this model object
257    *        value is associated.
258    * @param value Model object value to be converted (may be <code>null</code>)
259    *
260    * @return a zero-length String if value is <code>null</code>,
261    *  otherwise String representation for the Color object based on the first
262    * pattern in the specified patterns.
263    *
264    * @exception ConverterException if conversion cannot be successfully
265    *  performed
266    * @exception NullPointerException if <code>context</code> or
267    *  <code>component</code> is <code>null</code>
268    * @exception IllegalArgumentException if the <code>value</code> is not of
269    * type other than {@link java.awt.Color}, {@link java.lang.String} or
270    * <code>value</code> can be null.
271    *
272    */
273   public String getAsString(
274     FacesContext context,
275     UIComponent  component,
276     Object       value)
277   {
278     if (context == null || component == null)
279       throw new NullPointerException(_LOG.getMessage(
280         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
281 
282     if (value == null)
283       return "";
284 
285      // if the incoming value is String then just return it.
286      if (value instanceof String)
287        return (String) value;
288 
289 
290     if (value instanceof Color)
291     {
292       return _formatObject(context, (Color)value);
293     }
294     else throw new IllegalArgumentException("'value' is not of type java.awt.Color'");
295   }
296 
297   /**
298    * <p>Set if localized transparent text should be supported by this
299    * converter.</p>
300    * @param isTransparentAllowed
301    */
302   public void setTransparentAllowed(
303     boolean isTransparentAllowed)
304   {
305     Boolean isAllowed = (isTransparentAllowed ? Boolean.TRUE : Boolean.FALSE);
306     _facesBean.setProperty(_TRANSPARENT_ALLOWED_KEY, isAllowed);
307   }
308 
309   /**
310    * <p>Set the R,G, B patterns, based on the patterns set, Color object is
311    * created during call to getAsObject(FacesContext,UIComponent, String),
312    * while based on the first pattern which is at index 0, the String
313    * representation for the object is determined with call to
314    * getAsString(FacesContext, UIComponent, Object). <code>null</code>
315    * value for patterns result in IllegalArgumentException.</p>
316    * @param patterns
317    * @throws IllegalArgumentException if a value of pattern is null or if an invalid pattern is passed.
318    */
319   public void setPatterns(String[] patterns) throws IllegalArgumentException
320   {
321      if (null == patterns || patterns.length == 0)
322      {
323        throw new IllegalArgumentException(_LOG.getMessage(
324          "PATTERN_MUST_HAVE_VALUE"));
325      }
326      String[] newPatterns = new String[patterns.length];
327      System.arraycopy(patterns, 0, newPatterns, 0, patterns.length);
328      _facesBean.setProperty(_PATTERNS_KEY, newPatterns);
329   }
330 
331   /**
332    * <p>Retrun the patterns set for this converter.</p>
333    * @return patterns
334    */
335   public String[] getPatterns()
336   {
337     return ComponentUtils.resolveStringArray(_facesBean.getProperty(_PATTERNS_KEY));
338   }
339 
340   /**
341    * <p>Return if localized transparent text should be supported by this
342    * converter.</p>
343    */
344   public boolean isTransparentAllowed()
345   {
346     return ComponentUtils.resolveBoolean(_facesBean.getProperty(_TRANSPARENT_ALLOWED_KEY));
347   }
348 
349   /**
350    * <p>Compares this ColorConverter with the specified Object for equality.</p>
351    * @param obj  Object to which this ColorConverter is to be compared.
352    * @return true if and only if the specified Object is a ColorConverter
353    * and if the values patterns, transparentAllowed and transient are equal.
354    */
355   @Override
356   public boolean equals(Object obj)
357   {
358     if ( null == obj)
359       return false;
360 
361     if (this == obj)
362       return true;
363 
364     if (!(obj instanceof ColorConverter))
365       return false;
366 
367     ColorConverter other = (ColorConverter)obj;
368 
369     return ( ( this.isTransient() == other.isTransient() ) &&
370              ( this.isTransparentAllowed() == other.isTransparentAllowed()) &&
371              ( _isEqualPatterns(other.getPatterns())) &&
372              ( ConverterUtils.equals(getMessageDetailConvert(),
373                                      other.getMessageDetailConvert()))
374             );
375   }
376 
377   /**
378    * <p>Returns the hash code for this converter.</p>
379    * @return a hash code value for this object.
380    */
381   @Override
382   public int hashCode()
383   {
384     int result = 17;
385     result = 37 * result + (isTransient() ? 1 : 0);
386     result = 37 * result + (isTransparentAllowed() ? 1 : 0 );
387     String[] patterns = getPatterns();
388     for (int i = 0; i < patterns.length; i++)
389     {
390       result = 37 * result + patterns[i].hashCode();
391     }
392     String convMsgDet = getMessageDetailConvert();
393     result = result * 37 + (convMsgDet == null ? 0 : convMsgDet.hashCode());
394     return result;
395   }
396 
397   public boolean isTransient()
398   {
399     return _isTransient;
400   }
401 
402   public void setTransient(boolean isTransient)
403   {
404     _isTransient = isTransient;
405   }
406 
407   public Object saveState(FacesContext context)
408   {
409     return _facesBean.saveState(context);
410   }
411 
412   public void restoreState(FacesContext context, Object state)
413   {
414     _facesBean.restoreState(context, state);
415   }
416 
417   /**
418    * <p>Set the {@link ValueExpression} used to calculate the value for the
419    * specified attribute if any.</p>
420    *
421    * @param name Name of the attribute for which to set a {@link ValueExpression}
422    * @param expression The {@link ValueExpression} to set, or <code>null</code>
423    *  to remove any currently set {@link ValueExpression}
424    *
425    * @exception NullPointerException if <code>name</code>
426    *  is <code>null</code>
427    * @exception IllegalArgumentException if <code>name</code> is not a valid
428    *            attribute of this converter
429    */
430   public void setValueExpression(String name, ValueExpression expression)
431   {
432     ConverterUtils.setValueExpression(_facesBean, name, expression) ;
433   }
434 
435 
436   /**
437    * <p>Return the {@link ValueExpression} used to calculate the value for the
438    * specified attribute name, if any.</p>
439    *
440    * @param name Name of the attribute or property for which to retrieve a
441    *  {@link ValueExpression}
442    *
443    * @exception NullPointerException if <code>name</code>
444    *  is <code>null</code>
445    * @exception IllegalArgumentException if <code>name</code> is not a valid
446    * attribute of this converter
447    */
448   public ValueExpression getValueExpression(String name)
449   {
450     return ConverterUtils.getValueExpression(_facesBean, name);
451   }
452 
453   /**
454    * <p>Set the {@link ValueBinding} used to calculate the value for the
455    * specified attribute if any.</p>
456    *
457    * @param name Name of the attribute for which to set a {@link ValueBinding}
458    * @param binding The {@link ValueBinding} to set, or <code>null</code>
459    *  to remove any currently set {@link ValueBinding}
460    *
461    * @exception NullPointerException if <code>name</code>
462    *  is <code>null</code>
463    * @exception IllegalArgumentException if <code>name</code> is not a valid
464    *            attribute of this converter
465    * @deprecated
466    */
467   public void setValueBinding(String name, ValueBinding binding)
468   {
469     ConverterUtils.setValueBinding(_facesBean, name, binding) ;
470   }
471 
472   /**
473    * <p>Return the {@link ValueBinding} used to calculate the value for the
474    * specified attribute name, if any.</p>
475    *
476    * @param name Name of the attribute or property for which to retrieve a
477    *  {@link ValueBinding}
478    *
479    * @exception NullPointerException if <code>name</code>
480    *  is <code>null</code>
481    * @exception IllegalArgumentException if <code>name</code> is not a valid
482    * attribute of this converter
483    * @deprecated
484    */
485   public ValueBinding getValueBinding(String name)
486   {
487     return ConverterUtils.getValueBinding(_facesBean, name);
488   }
489 
490   /**
491    * Custom error message to be used, for creating detail part of
492    * the faces message, when <code>value</code> cannot be converted
493    * to {@link java.awt.Color}. Overrides the detail message identified by
494    * {@link #CONVERT_MESSAGE_ID}
495    * @param convertMessageDetail Custom error message.
496    * @see #CONVERT_MESSAGE_ID
497    */
498   public void setMessageDetailConvert(String convertMessageDetail)
499   {
500     _facesBean.setProperty(_CONVERT_MESSAGE_DETAIL_KEY, convertMessageDetail);
501   }
502 
503   /**
504    * Return custom detail error message that was set for creating faces message,
505    * for values that cannot be converted to {@link java.awt.Color}
506    * @return Custom error message.
507    * @see #setMessageDetailConvert(String)
508    */
509   public String getMessageDetailConvert()
510   {
511     return ComponentUtils.resolveString(_facesBean.getProperty(_CONVERT_MESSAGE_DETAIL_KEY));
512   }
513 
514   /**
515    * <p>Custom hint message.</p>
516    * Overrides default hint message
517    * @param hintFormat Custom hint message.
518    */
519   public void setHint(String hintFormat)
520   {
521     _facesBean.setProperty(_HINT_FORMAT_KEY, hintFormat);
522   }
523 
524   /**
525    * <p>Return custom hint message.</p>
526    * @return Custom hint message.
527    * @see  #setHint(String)
528    */
529   public String getHint()
530   {
531     Object obj = _facesBean.getProperty(_HINT_FORMAT_KEY);
532     return ComponentUtils.resolveString(obj);
533   }
534 
535   protected String getTransparentString(FacesContext context)
536   {
537     String msg = MessageFactory.getString(context, TRANSPARENT);
538     return msg;
539   }
540 
541   /**
542    * <p>Returns the value as a Color.</p>
543    */
544   private Object _parseString(
545     FacesContext  context,
546     String        colorString
547     ) throws ParseException
548   {
549     boolean isTrans = isTransparentAllowed();
550     if (isTrans &&
551         colorString != null &&
552         colorString.equals(getTransparentString(context)))
553       return _TRANSPARENT_COLOR;
554 
555     ParseException pe = null;
556 
557     String[] thePatterns =  getPatterns();
558     for (int i=0; i < thePatterns.length; i++)
559     {
560       try
561       {
562         Object  value = _getColorFormat(thePatterns[i]).parse(colorString);
563         return value;
564       }
565       catch (ParseException e)
566       {
567         // ignore, try lenient patterns
568         pe = e;
569       }
570     }
571     // throw the last parse exception
572     if (pe != null)
573       throw pe;
574 
575     return null;
576   }
577 
578   private String _formatObject(
579     FacesContext context,
580     Color color
581     )
582   {
583     if (color != null)
584     {
585        boolean isTrans = isTransparentAllowed();
586       if (isTrans && color.getAlpha() == 0)
587         return getTransparentString(context);
588 
589       return _getFormattingColorFormat().format(color);
590     }
591     else
592     {
593       return null;
594     }
595   }
596 
597   private ColorFormat _getFormattingColorFormat()
598   {
599     // Use the first pattern for formatting
600     return _getColorFormat(_getOutputPattern());
601   }
602 
603   private ColorFormat _getColorFormat(
604     String pattern
605     )
606   {
607     return new RGBColorFormat(pattern);
608   }
609 
610   private String _getOutputPattern()
611   {
612     String[] patterns = getPatterns();
613     return patterns[0];
614   }
615 
616   private Object _getRawConvertMessageDetail()
617   {
618     return _facesBean.getRawProperty(_CONVERT_MESSAGE_DETAIL_KEY);
619   }
620 
621   private boolean _isEqualPatterns(String[] patterns)
622   {
623     String[]  thisPattern = getPatterns();
624     if (null == thisPattern  && null == patterns)
625       return true;
626 
627     if ((thisPattern == null  && patterns != null) ||
628           (patterns == null && thisPattern != null) ||
629             (thisPattern.length != patterns.length))
630       return false;
631 
632     for (int i = 0; i < thisPattern.length; i++)
633     {
634       if (!thisPattern[i].equals(patterns[i]))
635         return false;
636     }
637     return true;
638   }
639 
640   private FacesMessage _getConvertMessage(
641     FacesContext context,
642     UIComponent  component,
643     String       value,
644     String[]       patternsArray
645     )
646   {
647     Object noMatchMsgDet = _getRawConvertMessageDetail();
648 
649     Object label = ConverterUtils.getComponentLabel(component);
650 
651     StringBuffer patterns = new StringBuffer();
652     for (int i = 0; i < patternsArray.length ; i++)
653     {
654       patterns.append(patternsArray[i]);
655       patterns.append(' ');
656     }
657 
658     Object[] params = {label, value, patterns, null, null};
659 
660     FacesMessage msg = MessageFactory.getMessage(context,
661                                                  CONVERT_MESSAGE_ID,
662                                                  noMatchMsgDet,
663                                                  params,
664                                                  component);
665     return  msg;
666   }
667 
668 
669   private static final Color  _TRANSPARENT_COLOR = new Color(0,0,0,0);
670 
671   /**
672    * Identifies if this class is to be persisted
673    */
674   private boolean _isTransient;
675 
676   private static final FacesBean.Type _TYPE = new FacesBean.Type();
677 
678   /**
679    * Should allow color to be transparent
680    */
681   private static final PropertyKey _TRANSPARENT_ALLOWED_KEY
682     = _TYPE.registerKey("transparentAllowed", Boolean.class, Boolean.FALSE);
683 
684   private static final PropertyKey _PATTERNS_KEY
685     = _TYPE.registerKey("patterns", String[].class,
686                         getDefaultColorFormatPatterns());
687 
688   private static final PropertyKey _CONVERT_MESSAGE_DETAIL_KEY
689     = _TYPE.registerKey("messageDetailConvert", String.class);
690 
691   private static final PropertyKey  _HINT_FORMAT_KEY =
692     _TYPE.registerKey("hint", String.class);
693 
694   private FacesBean _facesBean = ConverterUtils.getFacesBean(_TYPE);
695 
696   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
697     ColorConverter.class);
698 }