1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
144
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
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
568 pe = e;
569 }
570 }
571
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
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 }