001 /*
002 * Copyright 2008-2014 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.javafx.formatters;
017
018 import griffon.core.formatters.AbstractFormatter;
019 import griffon.core.formatters.ParseException;
020 import javafx.scene.paint.Color;
021
022 import javax.annotation.Nonnull;
023 import javax.annotation.Nullable;
024 import java.lang.reflect.Field;
025 import java.util.Arrays;
026
027 import static griffon.util.GriffonNameUtils.isBlank;
028 import static java.lang.Integer.toHexString;
029 import static java.util.Objects.requireNonNull;
030
031 /**
032 * <p>A {@code Formatter} that can parse Strings into {@code javafx.scene.paint.Color} and back
033 * using several patterns</p>
034 * <p/>
035 * <p>
036 * Supported patterns are:
037 * <ul>
038 * <li>{@code #RGB}</li>
039 * <li>{@code #RGBA}</li>
040 * <li>{@code #RRGGBB}</li>
041 * <li>{@code #RRGGBBAA}</li>
042 * </ul>
043 * Where each letter stands for a particular color components in hexadecimal
044 * <ul>
045 * <li>{@code R} - red</li>
046 * <li>{@code G} - green</li>
047 * <li>{@code B} - blue</li>
048 * <li>{@code A} - alpha</li>
049 * </ul>
050 * </p>
051 *
052 * @author Andres Almiray
053 * @see griffon.core.formatters.Formatter
054 * @since 2.0.0
055 */
056 public class ColorFormatter extends AbstractFormatter<Color> {
057 /**
058 * "#RGB"
059 */
060 public static final String PATTERN_SHORT = "#RGB";
061
062 /**
063 * "#RGBA"
064 */
065 public static final String PATTERN_SHORT_WITH_ALPHA = "#RGBA";
066
067 /**
068 * "#RRGGBB"
069 */
070 public static final String PATTERN_LONG = "#RRGGBB";
071
072 /**
073 * "#RRGGBBAA"
074 */
075 public static final String PATTERN_LONG_WITH_ALPHA = "#RRGGBBAA";
076
077 /**
078 * "#RRGGBB"
079 */
080 public static final String DEFAULT_PATTERN = PATTERN_LONG;
081
082 private static final String[] PATTERNS = new String[]{
083 PATTERN_LONG,
084 PATTERN_LONG_WITH_ALPHA,
085 PATTERN_SHORT,
086 PATTERN_SHORT_WITH_ALPHA
087 };
088
089 /**
090 * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT}</b> pattern
091 */
092 public static final ColorFormatter SHORT = new ColorFormatter(PATTERN_SHORT);
093
094 /**
095 * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT_WITH_ALPHA}</b> pattern
096 */
097 public static final ColorFormatter SHORT_WITH_ALPHA = new ColorFormatter(PATTERN_SHORT_WITH_ALPHA);
098
099 /**
100 * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG}</b> pattern
101 */
102 public static final ColorFormatter LONG = new ColorFormatter(PATTERN_LONG);
103
104 /**
105 * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG_WITH_ALPHA}</b> pattern
106 */
107 public static final ColorFormatter LONG_WITH_ALPHA = new ColorFormatter(PATTERN_LONG_WITH_ALPHA);
108
109 /**
110 * <p>Returns a {@code ColorFormatter} given a color pattern.</p>
111 *
112 * @param pattern the input pattern. Must be one of the 4 supported color patterns.
113 * @return a {@code ColorPattern} instance
114 * @throws IllegalArgumentException if the supplied {@code pattern} is not supported
115 */
116 @Nonnull
117 public static ColorFormatter getInstance(@Nonnull String pattern) {
118 return new ColorFormatter(pattern);
119 }
120
121 private final ColorFormatterDelegate delegate;
122
123 protected ColorFormatter(@Nullable String pattern) {
124 if (PATTERN_SHORT.equals(pattern)) {
125 delegate = new ShortColorFormatterDelegate();
126 } else if (PATTERN_SHORT_WITH_ALPHA.equals(pattern)) {
127 delegate = new ShortWithAlphaColorFormatterDelegate();
128 } else if (PATTERN_LONG.equals(pattern)) {
129 delegate = new LongColorFormatterDelegate();
130 } else if (PATTERN_LONG_WITH_ALPHA.equals(pattern)) {
131 delegate = new LongWithAlphaColorFormatterDelegate();
132 } else if (isBlank(pattern)) {
133 delegate = new LongColorFormatterDelegate();
134 } else {
135 throw new IllegalArgumentException("Invalid pattern '" + pattern + "'. Valid patterns are " + Arrays.toString(PATTERNS));
136 }
137 }
138
139 @Nullable
140 public String format(@Nullable Color color) {
141 return color == null ? null : delegate.format(color);
142 }
143
144 @Nullable
145 public Color parse(@Nullable String str) throws ParseException {
146 return isBlank(str) ? null : delegate.parse(str);
147 }
148
149 /**
150 * Returns the pattern used by this {@code ColorFormatter}
151 *
152 * @return the pattern this {@code ColorFormatter} uses for parsing/formatting.
153 */
154 @Nonnull
155 public String getPattern() {
156 return delegate.getPattern();
157 }
158
159 /**
160 * <p>Parses a string into a {@code javafx.scene.paint.Color} instance.</p>
161 * <p>The parsing pattern is chosen given the length of the input string
162 * <ul>
163 * <li>4 - {@code #RGB}</li>
164 * <li>5 - {@code #RGBA}</li>
165 * <li>7 - {@code #RRGGBB}</li>
166 * <li>9 - {@code #RRGGBBAA}</li>
167 * </ul>
168 * </p>
169 * The input string may also be any of the Color constants identified by
170 * {@code javafx.scene.paint.Color}.
171 *
172 * @param str the string representation of a {@code javafx.scene.paint.Color}
173 * @return a {@code javafx.scene.paint.Color} instance matching the supplied RGBA color components
174 * @throws ParseException if the string cannot be parsed by the chosen pattern
175 * @see javafx.scene.paint.Color
176 */
177 @Nonnull
178 @SuppressWarnings("ConstantConditions")
179 public static Color parseColor(@Nonnull String str) throws ParseException {
180 if (str.startsWith("#")) {
181 switch (str.length()) {
182 case 4:
183 return SHORT.parse(str);
184 case 5:
185 return SHORT_WITH_ALPHA.parse(str);
186 case 7:
187 return LONG.parse(str);
188 case 9:
189 return LONG_WITH_ALPHA.parse(str);
190 default:
191 throw parseError(str, Color.class);
192 }
193 } else {
194 // assume it's a Color constant
195 try {
196 String colorFieldName = str.toUpperCase();
197 Field field = Color.class.getField(colorFieldName);
198 return (Color) field.get(null);
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 str) throws 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 int red(Color color) {
230 return toIntColor(color.getRed());
231 }
232
233 private static int green(Color color) {
234 return toIntColor(color.getGreen());
235 }
236
237 private static int blue(Color color) {
238 return toIntColor(color.getBlue());
239 }
240
241 private static int alpha(Color color) {
242 return toIntColor(color.getOpacity());
243 }
244
245 private static int toIntColor(double c) {
246 return new Double(c * 255).intValue();
247 }
248
249 private static class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate {
250 private ShortColorFormatterDelegate() {
251 super(PATTERN_SHORT);
252 }
253
254 @Nonnull
255 public String format(@Nonnull Color color) {
256 requireNonNull(color, "Cannot format given Color because it's null");
257
258 return new StringBuilder("#")
259 .append(toHexString(red(color)).charAt(0))
260 .append(toHexString(green(color)).charAt(0))
261 .append(toHexString(blue(color)).charAt(0))
262 .toString();
263 }
264
265 @Nonnull
266 public Color parse(@Nonnull String str) throws ParseException {
267 if (!str.startsWith("#") || str.length() != 4) {
268 throw parseError(str, Color.class);
269 }
270
271 int r = parseHexInt(new StringBuilder()
272 .append(str.charAt(1))
273 .append(str.charAt(1))
274 .toString().toUpperCase(), Color.class);
275 int g = parseHexInt(new StringBuilder()
276 .append(str.charAt(2))
277 .append(str.charAt(2))
278 .toString().toUpperCase(), Color.class);
279 int b = parseHexInt(new StringBuilder()
280 .append(str.charAt(3))
281 .append(str.charAt(3))
282 .toString().toUpperCase(), Color.class);
283
284 return Color.rgb(r, g, b);
285 }
286 }
287
288 private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
289 private ShortWithAlphaColorFormatterDelegate() {
290 super(PATTERN_SHORT_WITH_ALPHA);
291 }
292
293 @Nonnull
294 public String format(@Nonnull Color color) {
295 requireNonNull(color, "Cannot format given Color because it's null");
296
297 return new StringBuilder("#")
298 .append(toHexString(red(color)).charAt(0))
299 .append(toHexString(green(color)).charAt(0))
300 .append(toHexString(blue(color)).charAt(0))
301 .append(toHexString(alpha(color)).charAt(0))
302 .toString();
303 }
304
305 @Nonnull
306 public Color parse(@Nonnull String str) throws ParseException {
307 if (!str.startsWith("#") || str.length() != 5) {
308 throw parseError(str, Color.class);
309 }
310
311 int r = parseHexInt(new StringBuilder()
312 .append(str.charAt(1))
313 .append(str.charAt(1))
314 .toString().toUpperCase(), Color.class);
315 int g = parseHexInt(new StringBuilder()
316 .append(str.charAt(2))
317 .append(str.charAt(2))
318 .toString().toUpperCase(), Color.class);
319 int b = parseHexInt(new StringBuilder()
320 .append(str.charAt(3))
321 .append(str.charAt(3))
322 .toString().toUpperCase(), Color.class);
323 int a = parseHexInt(new StringBuilder()
324 .append(str.charAt(4))
325 .append(str.charAt(4))
326 .toString().toUpperCase(), Color.class);
327
328 return Color.rgb(r, g, b, a / 255d);
329 }
330 }
331
332 private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate {
333 private LongColorFormatterDelegate() {
334 super(PATTERN_LONG);
335 }
336
337 @Nonnull
338 public String format(@Nonnull Color color) {
339 requireNonNull(color, "Cannot format given Color because it's null");
340
341 return new StringBuilder("#")
342 .append(padLeft(toHexString(red(color)), "0"))
343 .append(padLeft(toHexString(green(color)), "0"))
344 .append(padLeft(toHexString(blue(color)), "0"))
345 .toString();
346 }
347
348 @Nonnull
349 public Color parse(@Nonnull String str) throws ParseException {
350 if (!str.startsWith("#") || str.length() != 7) {
351 throw parseError(str, Color.class);
352 }
353
354 int r = parseHexInt(new StringBuilder()
355 .append(str.charAt(1))
356 .append(str.charAt(2))
357 .toString().toUpperCase(), Color.class);
358 int g = parseHexInt(new StringBuilder()
359 .append(str.charAt(3))
360 .append(str.charAt(4))
361 .toString().toUpperCase(), Color.class);
362 int b = parseHexInt(new StringBuilder()
363 .append(str.charAt(5))
364 .append(str.charAt(6))
365 .toString().toUpperCase(), Color.class);
366
367 return Color.rgb(r, g, b);
368 }
369 }
370
371 private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
372 private LongWithAlphaColorFormatterDelegate() {
373 super(PATTERN_LONG_WITH_ALPHA);
374 }
375
376 @Nonnull
377 public String format(@Nonnull Color color) {
378 requireNonNull(color, "Cannot format given Color because it's null");
379
380 return new StringBuilder("#")
381 .append(padLeft(toHexString(red(color)), "0"))
382 .append(padLeft(toHexString(green(color)), "0"))
383 .append(padLeft(toHexString(blue(color)), "0"))
384 .append(padLeft(toHexString(alpha(color)), "0"))
385 .toString();
386 }
387
388 @Nonnull
389 public Color parse(@Nonnull String str) throws ParseException {
390 if (!str.startsWith("#") || str.length() != 9) {
391 throw parseError(str, Color.class);
392 }
393
394 int r = parseHexInt(new StringBuilder()
395 .append(str.charAt(1))
396 .append(str.charAt(2))
397 .toString().toUpperCase(), Color.class);
398 int g = parseHexInt(new StringBuilder()
399 .append(str.charAt(3))
400 .append(str.charAt(4))
401 .toString().toUpperCase(), Color.class);
402 int b = parseHexInt(new StringBuilder()
403 .append(str.charAt(5))
404 .append(str.charAt(6))
405 .toString().toUpperCase(), Color.class);
406 int a = parseHexInt(new StringBuilder()
407 .append(str.charAt(7))
408 .append(str.charAt(8))
409 .toString().toUpperCase(), Color.class);
410
411 return Color.rgb(r, g, b, a / 255d);
412 }
413 }
414
415 private static String padLeft(String self, String padding) {
416 return 2 <= self.length() ? self : padding + self;
417 }
418 }
|