ColorFormatter.java
001 /*
002  * Copyright 2008-2017 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.swing.formatters;
017 
018 import griffon.core.formatters.AbstractFormatter;
019 import griffon.core.formatters.ParseException;
020 import griffon.swing.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     @Override
142     public String format(@Nullable Color color) {
143         return color == null null : delegate.format(color);
144     }
145 
146     @Nullable
147     @Override
148     public Color parse(@Nullable String strthrows ParseException {
149         return isBlank(strnull : delegate.parse(str);
150     }
151 
152     /**
153      * Returns the pattern used by this {@code ColorFormatter}
154      *
155      @return the pattern this {@code ColorFormatter} uses for parsing/formatting.
156      */
157     @Nonnull
158     public String getPattern() {
159         return delegate.getPattern();
160     }
161 
162     /**
163      <p>Parses a string into a {@code java.awt.Color} instance.</p>
164      <p>The parsing pattern is chosen given the length of the input string
165      <ul>
166      <li>4 - {@code #RGB}</li>
167      <li>5 - {@code #RGBA}</li>
168      <li>7 - {@code #RRGGBB}</li>
169      <li>9 - {@code #RRGGBBAA}</li>
170      </ul>
171      </p>
172      * The input string may also be any of the Color constants identified by
173      * {@code griffon.swing.support.Colors}.
174      *
175      @param str the string representation of a {@code java.awt.Color}
176      @return a {@code java.awt.Color} instance matching the supplied RGBA color components
177      @throws ParseException if the string cannot be parsed by the chosen pattern
178      @see griffon.swing.support.Colors
179      */
180     @Nonnull
181     @SuppressWarnings("ConstantConditions")
182     public static Color parseColor(@Nonnull String strthrows ParseException {
183         requireNonBlank(str, "Argument must not be blank");
184         if (str.startsWith("#")) {
185             switch (str.length()) {
186                 case 4:
187                     return SHORT.parse(str);
188                 case 5:
189                     return SHORT_WITH_ALPHA.parse(str);
190                 case 7:
191                     return LONG.parse(str);
192                 case 9:
193                     return LONG_WITH_ALPHA.parse(str);
194                 default:
195                     throw parseError(str, Color.class);
196             }
197         else {
198             // assume it's a Color constant
199             try {
200                 return Colors.valueOf(str.toUpperCase()).getColor();
201             catch (Exception e) {
202                 throw parseError(str, Color.class, e);
203             }
204         }
205     }
206 
207     private static interface ColorFormatterDelegate {
208         @Nonnull
209         String getPattern();
210 
211         @Nonnull
212         String format(@Nonnull Color color);
213 
214         @Nonnull
215         Color parse(@Nonnull String strthrows ParseException;
216     }
217 
218     private abstract static class AbstractColorFormatterDelegate implements ColorFormatterDelegate {
219         private final String pattern;
220 
221         private AbstractColorFormatterDelegate(@Nonnull String pattern) {
222             this.pattern = pattern;
223         }
224 
225         @Nonnull
226         @Override
227         public String getPattern() {
228             return pattern;
229         }
230     }
231 
232     private static class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate {
233         private ShortColorFormatterDelegate() {
234             super(PATTERN_SHORT);
235         }
236 
237         @Nonnull
238         @Override
239         public String format(@Nonnull Color color) {
240             requireNonNull(color, "Cannot format given Color because it's null");
241 
242             return new StringBuilder("#")
243                 .append(toHexString(color.getRed()).charAt(0))
244                 .append(toHexString(color.getGreen()).charAt(0))
245                 .append(toHexString(color.getBlue()).charAt(0))
246                 .toString();
247         }
248 
249         @Nonnull
250         @Override
251         public Color parse(@Nonnull String strthrows ParseException {
252             if (!str.startsWith("#"|| str.length() != 4) {
253                 throw parseError(str, Color.class);
254             }
255 
256             int r = parseHexInt(new StringBuilder()
257                 .append(str.charAt(1))
258                 .append(str.charAt(1))
259                 .toString().toUpperCase(), Color.class);
260             int g = parseHexInt(new StringBuilder()
261                 .append(str.charAt(2))
262                 .append(str.charAt(2))
263                 .toString().toUpperCase(), Color.class);
264             int b = parseHexInt(new StringBuilder()
265                 .append(str.charAt(3))
266                 .append(str.charAt(3))
267                 .toString().toUpperCase(), Color.class);
268 
269             return new Color(r, g, b);
270         }
271     }
272 
273     private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
274         private ShortWithAlphaColorFormatterDelegate() {
275             super(PATTERN_SHORT_WITH_ALPHA);
276         }
277 
278         @Nonnull
279         @Override
280         public String format(@Nonnull Color color) {
281             requireNonNull(color, "Cannot format given Color because it's null");
282 
283             return new StringBuilder("#")
284                 .append(toHexString(color.getRed()).charAt(0))
285                 .append(toHexString(color.getGreen()).charAt(0))
286                 .append(toHexString(color.getBlue()).charAt(0))
287                 .append(toHexString(color.getAlpha()).charAt(0))
288                 .toString();
289         }
290 
291         @Nonnull
292         @Override
293         public Color parse(@Nonnull String strthrows ParseException {
294             if (!str.startsWith("#"|| str.length() != 5) {
295                 throw parseError(str, Color.class);
296             }
297 
298             int r = parseHexInt(new StringBuilder()
299                 .append(str.charAt(1))
300                 .append(str.charAt(1))
301                 .toString().toUpperCase(), Color.class);
302             int g = parseHexInt(new StringBuilder()
303                 .append(str.charAt(2))
304                 .append(str.charAt(2))
305                 .toString().toUpperCase(), Color.class);
306             int b = parseHexInt(new StringBuilder()
307                 .append(str.charAt(3))
308                 .append(str.charAt(3))
309                 .toString().toUpperCase(), Color.class);
310             int a = parseHexInt(new StringBuilder()
311                 .append(str.charAt(4))
312                 .append(str.charAt(4))
313                 .toString().toUpperCase(), Color.class);
314 
315             return new Color(r, g, b, a);
316         }
317     }
318 
319     private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate {
320         private LongColorFormatterDelegate() {
321             super(PATTERN_LONG);
322         }
323 
324         @Nonnull
325         @Override
326         public String format(@Nonnull Color color) {
327             requireNonNull(color, "Cannot format given Color because it's null");
328             return new StringBuilder("#")
329                 .append(padLeft(toHexString(color.getRed())"0"))
330                 .append(padLeft(toHexString(color.getGreen())"0"))
331                 .append(padLeft(toHexString(color.getBlue())"0"))
332                 .toString();
333         }
334 
335         @Nonnull
336         @Override
337         public Color parse(@Nonnull String strthrows ParseException {
338             if (!str.startsWith("#"|| str.length() != 7) {
339                 throw parseError(str, Color.class);
340             }
341 
342             int r = parseHexInt(new StringBuilder()
343                 .append(str.charAt(1))
344                 .append(str.charAt(2))
345                 .toString().toUpperCase(), Color.class);
346             int g = parseHexInt(new StringBuilder()
347                 .append(str.charAt(3))
348                 .append(str.charAt(4))
349                 .toString().toUpperCase(), Color.class);
350             int b = parseHexInt(new StringBuilder()
351                 .append(str.charAt(5))
352                 .append(str.charAt(6))
353                 .toString().toUpperCase(), Color.class);
354 
355             return new Color(r, g, b);
356         }
357     }
358 
359     private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
360         private LongWithAlphaColorFormatterDelegate() {
361             super(PATTERN_LONG_WITH_ALPHA);
362         }
363 
364         @Nonnull
365         @Override
366         public String format(@Nonnull Color color) {
367             requireNonNull(color, "Cannot format given Color because it's null");
368 
369             return new StringBuilder("#")
370                 .append(padLeft(toHexString(color.getRed())"0"))
371                 .append(padLeft(toHexString(color.getGreen())"0"))
372                 .append(padLeft(toHexString(color.getBlue())"0"))
373                 .append(padLeft(toHexString(color.getAlpha())"0"))
374                 .toString();
375         }
376 
377         @Nonnull
378         @Override
379         public Color parse(@Nonnull String strthrows ParseException {
380             if (!str.startsWith("#"|| str.length() != 9) {
381                 throw parseError(str, Color.class);
382             }
383 
384             int r = parseHexInt(new StringBuilder()
385                 .append(str.charAt(1))
386                 .append(str.charAt(2))
387                 .toString().toUpperCase(), Color.class);
388             int g = parseHexInt(new StringBuilder()
389                 .append(str.charAt(3))
390                 .append(str.charAt(4))
391                 .toString().toUpperCase(), Color.class);
392             int b = parseHexInt(new StringBuilder()
393                 .append(str.charAt(5))
394                 .append(str.charAt(6))
395                 .toString().toUpperCase(), Color.class);
396             int a = parseHexInt(new StringBuilder()
397                 .append(str.charAt(7))
398                 .append(str.charAt(8))
399                 .toString().toUpperCase(), Color.class);
400 
401             return new Color(r, g, b, a);
402         }
403     }
404 
405     private static String padLeft(String self, String padding) {
406         return <= self.length() ? self : padding + self;
407     }
408 }