ColorFormatter.java
001 /*
002  * Copyright 2008-2015 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *     http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package griffon.pivot.formatters;
017 
018 import griffon.core.formatters.AbstractFormatter;
019 import griffon.core.formatters.ParseException;
020 import griffon.pivot.support.Colors;
021 
022 import javax.annotation.Nonnull;
023 import javax.annotation.Nullable;
024 import java.awt.Color;
025 import java.util.Arrays;
026 
027 import static griffon.util.GriffonNameUtils.isBlank;
028 import static griffon.util.GriffonNameUtils.requireNonBlank;
029 import static java.lang.Integer.toHexString;
030 import static java.util.Objects.requireNonNull;
031 
032 /**
033  <p>A {@code Formatter} that can parse Strings into {@code java.awt.Color} and back
034  * using several patterns</p>
035  <p/>
036  <p>
037  * Supported patterns are:
038  <ul>
039  <li>{@code #RGB}</li>
040  <li>{@code #RGBA}</li>
041  <li>{@code #RRGGBB}</li>
042  <li>{@code #RRGGBBAA}</li>
043  </ul>
044  * Where each letter stands for a particular color components in hexadecimal
045  <ul>
046  <li>{@code R} - red</li>
047  <li>{@code G} - green</li>
048  <li>{@code B} - blue</li>
049  <li>{@code A} - alpha</li>
050  </ul>
051  </p>
052  *
053  @author Andres Almiray
054  @see griffon.core.formatters.Formatter
055  @since 2.0.0
056  */
057 public class ColorFormatter extends AbstractFormatter<Color> {
058     /**
059      * "#RGB"
060      */
061     public static final String PATTERN_SHORT = "#RGB";
062 
063     /**
064      * "#RGBA"
065      */
066     public static final String PATTERN_SHORT_WITH_ALPHA = "#RGBA";
067 
068     /**
069      * "#RRGGBB"
070      */
071     public static final String PATTERN_LONG = "#RRGGBB";
072 
073     /**
074      * "#RRGGBBAA"
075      */
076     public static final String PATTERN_LONG_WITH_ALPHA = "#RRGGBBAA";
077 
078     /**
079      * "#RRGGBB"
080      */
081     public static final String DEFAULT_PATTERN = PATTERN_LONG;
082 
083     private static final String[] PATTERNS = new String[]{
084         PATTERN_LONG,
085         PATTERN_LONG_WITH_ALPHA,
086         PATTERN_SHORT,
087         PATTERN_SHORT_WITH_ALPHA
088     };
089 
090     /**
091      * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT}</b> pattern
092      */
093     public static final ColorFormatter SHORT = new ColorFormatter(PATTERN_SHORT);
094 
095     /**
096      * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT_WITH_ALPHA}</b> pattern
097      */
098     public static final ColorFormatter SHORT_WITH_ALPHA = new ColorFormatter(PATTERN_SHORT_WITH_ALPHA);
099 
100     /**
101      * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG}</b> pattern
102      */
103     public static final ColorFormatter LONG = new ColorFormatter(PATTERN_LONG);
104 
105     /**
106      * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG_WITH_ALPHA}</b> pattern
107      */
108     public static final ColorFormatter LONG_WITH_ALPHA = new ColorFormatter(PATTERN_LONG_WITH_ALPHA);
109 
110     /**
111      <p>Returns a {@code ColorFormatter} given a color pattern.</p>
112      *
113      @param pattern the input pattern. Must be one of the 4 supported color patterns.
114      @return a {@code ColorPattern} instance
115      @throws IllegalArgumentException if the supplied {@code pattern} is not supported
116      */
117     @Nonnull
118     public static ColorFormatter getInstance(@Nullable String pattern) {
119         return new ColorFormatter(pattern);
120     }
121 
122     private final ColorFormatterDelegate delegate;
123 
124     protected ColorFormatter(@Nullable String pattern) {
125         if (PATTERN_SHORT.equals(pattern)) {
126             delegate = new ShortColorFormatterDelegate();
127         else if (PATTERN_SHORT_WITH_ALPHA.equals(pattern)) {
128             delegate = new ShortWithAlphaColorFormatterDelegate();
129         else if (PATTERN_LONG.equals(pattern)) {
130             delegate = new LongColorFormatterDelegate();
131         else if (PATTERN_LONG_WITH_ALPHA.equals(pattern)) {
132             delegate = new LongWithAlphaColorFormatterDelegate();
133         else if (isBlank(pattern)) {
134             delegate = new LongColorFormatterDelegate();
135         else {
136             throw new IllegalArgumentException("Invalid pattern '" + pattern + "'. Valid patterns are " + Arrays.toString(PATTERNS));
137         }
138     }
139 
140     @Nullable
141     public String format(@Nullable Color color) {
142         return color == null null : delegate.format(color);
143     }
144 
145     @Nullable
146     public Color parse(@Nullable String strthrows ParseException {
147         return isBlank(strnull : delegate.parse(str);
148     }
149 
150     /**
151      * Returns the pattern used by this {@code ColorFormatter}
152      *
153      @return the pattern this {@code ColorFormatter} uses for parsing/formatting.
154      */
155     @Nonnull
156     public String getPattern() {
157         return delegate.getPattern();
158     }
159 
160     /**
161      <p>Parses a string into a {@code java.awt.Color} instance.</p>
162      <p>The parsing pattern is chosen given the length of the input string
163      <ul>
164      <li>4 - {@code #RGB}</li>
165      <li>5 - {@code #RGBA}</li>
166      <li>7 - {@code #RRGGBB}</li>
167      <li>9 - {@code #RRGGBBAA}</li>
168      </ul>
169      </p>
170      * The input string may also be any of the Color constants identified by
171      * {@code griffon.pivot.support.Colors}.
172      *
173      @param str the string representation of a {@code java.awt.Color}
174      @return a {@code java.awt.Color} instance matching the supplied RGBA color components
175      @throws ParseException if the string cannot be parsed by the chosen pattern
176      @see griffon.pivot.support.Colors
177      */
178     @Nonnull
179     @SuppressWarnings("ConstantConditions")
180     public static Color parseColor(@Nonnull String strthrows ParseException {
181         requireNonBlank(str, "Argument must not be blank");
182         if (str.startsWith("#")) {
183             switch (str.length()) {
184                 case 4:
185                     return SHORT.parse(str);
186                 case 5:
187                     return SHORT_WITH_ALPHA.parse(str);
188                 case 7:
189                     return LONG.parse(str);
190                 case 9:
191                     return LONG_WITH_ALPHA.parse(str);
192                 default:
193                     throw parseError(str, Color.class);
194             }
195         else {
196             // assume it's a Color constant
197             try {
198                 return Colors.valueOf(str.toUpperCase()).getColor();
199             catch (Exception e) {
200                 throw parseError(str, Color.class, e);
201             }
202         }
203     }
204 
205     private static interface ColorFormatterDelegate {
206         @Nonnull
207         String getPattern();
208 
209         @Nonnull
210         String format(@Nonnull Color color);
211 
212         @Nonnull
213         Color parse(@Nonnull String strthrows ParseException;
214     }
215 
216     private static abstract class AbstractColorFormatterDelegate implements ColorFormatterDelegate {
217         private final String pattern;
218 
219         private AbstractColorFormatterDelegate(@Nonnull String pattern) {
220             this.pattern = pattern;
221         }
222 
223         @Nonnull
224         public String getPattern() {
225             return pattern;
226         }
227     }
228 
229     private static class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate {
230         private ShortColorFormatterDelegate() {
231             super(PATTERN_SHORT);
232         }
233 
234         @Nonnull
235         public String format(@Nonnull Color color) {
236             requireNonNull(color, "Cannot format given Color because it's null");
237 
238             return new StringBuilder("#")
239                 .append(toHexString(color.getRed()).charAt(0))
240                 .append(toHexString(color.getGreen()).charAt(0))
241                 .append(toHexString(color.getBlue()).charAt(0))
242                 .toString();
243         }
244 
245         @Nonnull
246         public Color parse(@Nonnull String strthrows ParseException {
247             if (!str.startsWith("#"|| str.length() != 4) {
248                 throw parseError(str, Color.class);
249             }
250 
251             int r = parseHexInt(new StringBuilder()
252                 .append(str.charAt(1))
253                 .append(str.charAt(1))
254                 .toString().toUpperCase(), Color.class);
255             int g = parseHexInt(new StringBuilder()
256                 .append(str.charAt(2))
257                 .append(str.charAt(2))
258                 .toString().toUpperCase(), Color.class);
259             int b = parseHexInt(new StringBuilder()
260                 .append(str.charAt(3))
261                 .append(str.charAt(3))
262                 .toString().toUpperCase(), Color.class);
263 
264             return new Color(r, g, b);
265         }
266     }
267 
268     private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
269         private ShortWithAlphaColorFormatterDelegate() {
270             super(PATTERN_SHORT_WITH_ALPHA);
271         }
272 
273         @Nonnull
274         public String format(@Nonnull Color color) {
275             requireNonNull(color, "Cannot format given Color because it's null");
276 
277             return new StringBuilder("#")
278                 .append(toHexString(color.getRed()).charAt(0))
279                 .append(toHexString(color.getGreen()).charAt(0))
280                 .append(toHexString(color.getBlue()).charAt(0))
281                 .append(toHexString(color.getAlpha()).charAt(0))
282                 .toString();
283         }
284 
285         @Nonnull
286         public Color parse(@Nonnull String strthrows ParseException {
287             if (!str.startsWith("#"|| str.length() != 5) {
288                 throw parseError(str, Color.class);
289             }
290 
291             int r = parseHexInt(new StringBuilder()
292                 .append(str.charAt(1))
293                 .append(str.charAt(1))
294                 .toString().toUpperCase(), Color.class);
295             int g = parseHexInt(new StringBuilder()
296                 .append(str.charAt(2))
297                 .append(str.charAt(2))
298                 .toString().toUpperCase(), Color.class);
299             int b = parseHexInt(new StringBuilder()
300                 .append(str.charAt(3))
301                 .append(str.charAt(3))
302                 .toString().toUpperCase(), Color.class);
303             int a = parseHexInt(new StringBuilder()
304                 .append(str.charAt(4))
305                 .append(str.charAt(4))
306                 .toString().toUpperCase(), Color.class);
307 
308             return new Color(r, g, b, a);
309         }
310     }
311 
312     private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate {
313         private LongColorFormatterDelegate() {
314             super(PATTERN_LONG);
315         }
316 
317         @Nonnull
318         public String format(@Nonnull Color color) {
319             requireNonNull(color, "Cannot format given Color because it's null");
320             return new StringBuilder("#")
321                 .append(padLeft(toHexString(color.getRed())"0"))
322                 .append(padLeft(toHexString(color.getGreen())"0"))
323                 .append(padLeft(toHexString(color.getBlue())"0"))
324                 .toString();
325         }
326 
327         @Nonnull
328         public Color parse(@Nonnull String strthrows ParseException {
329             if (!str.startsWith("#"|| str.length() != 7) {
330                 throw parseError(str, Color.class);
331             }
332 
333             int r = parseHexInt(new StringBuilder()
334                 .append(str.charAt(1))
335                 .append(str.charAt(2))
336                 .toString().toUpperCase(), Color.class);
337             int g = parseHexInt(new StringBuilder()
338                 .append(str.charAt(3))
339                 .append(str.charAt(4))
340                 .toString().toUpperCase(), Color.class);
341             int b = parseHexInt(new StringBuilder()
342                 .append(str.charAt(5))
343                 .append(str.charAt(6))
344                 .toString().toUpperCase(), Color.class);
345 
346             return new Color(r, g, b);
347         }
348     }
349 
350     private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
351         private LongWithAlphaColorFormatterDelegate() {
352             super(PATTERN_LONG_WITH_ALPHA);
353         }
354 
355         @Nonnull
356         public String format(@Nonnull Color color) {
357             requireNonNull(color, "Cannot format given Color because it's null");
358 
359             return new StringBuilder("#")
360                 .append(padLeft(toHexString(color.getRed())"0"))
361                 .append(padLeft(toHexString(color.getGreen())"0"))
362                 .append(padLeft(toHexString(color.getBlue())"0"))
363                 .append(padLeft(toHexString(color.getAlpha())"0"))
364                 .toString();
365         }
366 
367         @Nonnull
368         public Color parse(@Nonnull String strthrows ParseException {
369             if (!str.startsWith("#"|| str.length() != 9) {
370                 throw parseError(str, Color.class);
371             }
372 
373             int r = parseHexInt(new StringBuilder()
374                 .append(str.charAt(1))
375                 .append(str.charAt(2))
376                 .toString().toUpperCase(), Color.class);
377             int g = parseHexInt(new StringBuilder()
378                 .append(str.charAt(3))
379                 .append(str.charAt(4))
380                 .toString().toUpperCase(), Color.class);
381             int b = parseHexInt(new StringBuilder()
382                 .append(str.charAt(5))
383                 .append(str.charAt(6))
384                 .toString().toUpperCase(), Color.class);
385             int a = parseHexInt(new StringBuilder()
386                 .append(str.charAt(7))
387                 .append(str.charAt(8))
388                 .toString().toUpperCase(), Color.class);
389 
390             return new Color(r, g, b, a);
391         }
392     }
393 
394     private static String padLeft(String self, String padding) {
395         return <= self.length() ? self : padding + self;
396     }
397 }