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