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.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 private static final String ERROR_COLOR_NULL = "Cannot format given Color because it's null";
110 private static final String HASH = "#";
111 private static final String ZERO = "0";
112
113 /**
114 * <p>Returns a {@code ColorFormatter} given a color pattern.</p>
115 *
116 * @param pattern the input pattern. Must be one of the 4 supported color patterns.
117 *
118 * @return a {@code ColorPattern} instance
119 *
120 * @throws IllegalArgumentException if the supplied {@code pattern} is not supported
121 */
122 @Nonnull
123 public static ColorFormatter getInstance(@Nonnull String pattern) {
124 return new ColorFormatter(pattern);
125 }
126
127 private final ColorFormatterDelegate delegate;
128
129 protected ColorFormatter(@Nullable String pattern) {
130 if (PATTERN_SHORT.equals(pattern)) {
131 delegate = new ShortColorFormatterDelegate();
132 } else if (PATTERN_SHORT_WITH_ALPHA.equals(pattern)) {
133 delegate = new ShortWithAlphaColorFormatterDelegate();
134 } else if (PATTERN_LONG.equals(pattern)) {
135 delegate = new LongColorFormatterDelegate();
136 } else if (PATTERN_LONG_WITH_ALPHA.equals(pattern)) {
137 delegate = new LongWithAlphaColorFormatterDelegate();
138 } else if (isBlank(pattern)) {
139 delegate = new LongColorFormatterDelegate();
140 } else {
141 throw new IllegalArgumentException("Invalid pattern '" + pattern + "'. Valid patterns are " + Arrays.toString(PATTERNS));
142 }
143 }
144
145 @Override
146 @Nullable
147 public String format(@Nullable Color color) {
148 return color == null ? null : delegate.format(color);
149 }
150
151 @Override
152 @Nullable
153 public Color parse(@Nullable String str) throws ParseException {
154 return isBlank(str) ? null : delegate.parse(str);
155 }
156
157 /**
158 * Returns the pattern used by this {@code ColorFormatter}
159 *
160 * @return the pattern this {@code ColorFormatter} uses for parsing/formatting.
161 */
162 @Nonnull
163 public String getPattern() {
164 return delegate.getPattern();
165 }
166
167 /**
168 * <p>Parses a string into a {@code javafx.scene.paint.Color} instance.</p>
169 * <p>The parsing pattern is chosen given the length of the input string
170 * <ul>
171 * <li>4 - {@code #RGB}</li>
172 * <li>5 - {@code #RGBA}</li>
173 * <li>7 - {@code #RRGGBB}</li>
174 * <li>9 - {@code #RRGGBBAA}</li>
175 * </ul>
176 * </p>
177 * The input string may also be any of the Color constants identified by
178 * {@code javafx.scene.paint.Color}.
179 *
180 * @param str the string representation of a {@code javafx.scene.paint.Color}
181 *
182 * @return a {@code javafx.scene.paint.Color} instance matching the supplied RGBA color components
183 *
184 * @throws ParseException if the string cannot be parsed by the chosen pattern
185 * @see javafx.scene.paint.Color
186 */
187 @Nonnull
188 @SuppressWarnings("ConstantConditions")
189 public static Color parseColor(@Nonnull String str) throws ParseException {
190 if (str.startsWith(HASH)) {
191 switch (str.length()) {
192 case 4:
193 return SHORT.parse(str);
194 case 5:
195 return SHORT_WITH_ALPHA.parse(str);
196 case 7:
197 return LONG.parse(str);
198 case 9:
199 return LONG_WITH_ALPHA.parse(str);
200 default:
201 throw parseError(str, Color.class);
202 }
203 } else {
204 // assume it's a Color constant
205 try {
206 String colorFieldName = str.toUpperCase();
207 Field field = Color.class.getField(colorFieldName);
208 return (Color) field.get(null);
209 } catch (Exception e) {
210 throw parseError(str, Color.class, e);
211 }
212 }
213 }
214
215 private static interface ColorFormatterDelegate {
216 @Nonnull
217 String getPattern();
218
219 @Nonnull
220 String format(@Nonnull Color color);
221
222 @Nonnull
223 Color parse(@Nonnull String str) throws ParseException;
224 }
225
226 private abstract static class AbstractColorFormatterDelegate implements ColorFormatterDelegate {
227 private final String pattern;
228
229 private AbstractColorFormatterDelegate(@Nonnull String pattern) {
230 this.pattern = pattern;
231 }
232
233 @Override
234 @Nonnull
235 public String getPattern() {
236 return pattern;
237 }
238 }
239
240 private static int red(Color color) {
241 return toIntColor(color.getRed());
242 }
243
244 private static int green(Color color) {
245 return toIntColor(color.getGreen());
246 }
247
248 private static int blue(Color color) {
249 return toIntColor(color.getBlue());
250 }
251
252 private static int alpha(Color color) {
253 return toIntColor(color.getOpacity());
254 }
255
256 private static int toIntColor(double c) {
257 return (int) (c * 255);
258 }
259
260 private static class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate {
261 private ShortColorFormatterDelegate() {
262 super(PATTERN_SHORT);
263 }
264
265 @Override
266 @Nonnull
267 public String format(@Nonnull Color color) {
268 requireNonNull(color, ERROR_COLOR_NULL);
269
270 return new StringBuilder(HASH)
271 .append(toHexString(red(color)).charAt(0))
272 .append(toHexString(green(color)).charAt(0))
273 .append(toHexString(blue(color)).charAt(0))
274 .toString();
275 }
276
277 @Override
278 @Nonnull
279 public Color parse(@Nonnull String str) throws ParseException {
280 if (!str.startsWith(HASH) || str.length() != 4) {
281 throw parseError(str, Color.class);
282 }
283
284 int r = parseHexInt(new StringBuilder()
285 .append(str.charAt(1))
286 .append(str.charAt(1))
287 .toString().toUpperCase(), Color.class);
288 int g = parseHexInt(new StringBuilder()
289 .append(str.charAt(2))
290 .append(str.charAt(2))
291 .toString().toUpperCase(), Color.class);
292 int b = parseHexInt(new StringBuilder()
293 .append(str.charAt(3))
294 .append(str.charAt(3))
295 .toString().toUpperCase(), Color.class);
296
297 return Color.rgb(r, g, b);
298 }
299 }
300
301 private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
302 private ShortWithAlphaColorFormatterDelegate() {
303 super(PATTERN_SHORT_WITH_ALPHA);
304 }
305
306 @Override
307 @Nonnull
308 public String format(@Nonnull Color color) {
309 requireNonNull(color, ERROR_COLOR_NULL);
310
311 return new StringBuilder(HASH)
312 .append(toHexString(red(color)).charAt(0))
313 .append(toHexString(green(color)).charAt(0))
314 .append(toHexString(blue(color)).charAt(0))
315 .append(toHexString(alpha(color)).charAt(0))
316 .toString();
317 }
318
319 @Override
320 @Nonnull
321 public Color parse(@Nonnull String str) throws ParseException {
322 if (!str.startsWith(HASH) || str.length() != 5) {
323 throw parseError(str, Color.class);
324 }
325
326 int r = parseHexInt(new StringBuilder()
327 .append(str.charAt(1))
328 .append(str.charAt(1))
329 .toString().toUpperCase(), Color.class);
330 int g = parseHexInt(new StringBuilder()
331 .append(str.charAt(2))
332 .append(str.charAt(2))
333 .toString().toUpperCase(), Color.class);
334 int b = parseHexInt(new StringBuilder()
335 .append(str.charAt(3))
336 .append(str.charAt(3))
337 .toString().toUpperCase(), Color.class);
338 int a = parseHexInt(new StringBuilder()
339 .append(str.charAt(4))
340 .append(str.charAt(4))
341 .toString().toUpperCase(), Color.class);
342
343 return Color.rgb(r, g, b, a / 255d);
344 }
345 }
346
347 private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate {
348 private LongColorFormatterDelegate() {
349 super(PATTERN_LONG);
350 }
351
352 @Override
353 @Nonnull
354 public String format(@Nonnull Color color) {
355 requireNonNull(color, ERROR_COLOR_NULL);
356
357 return new StringBuilder(HASH)
358 .append(padLeft(toHexString(red(color)), ZERO))
359 .append(padLeft(toHexString(green(color)), ZERO))
360 .append(padLeft(toHexString(blue(color)), ZERO))
361 .toString();
362 }
363
364 @Override
365 @Nonnull
366 public Color parse(@Nonnull String str) throws ParseException {
367 if (!str.startsWith(HASH) || str.length() != 7) {
368 throw parseError(str, Color.class);
369 }
370
371 int r = parseHexInt(new StringBuilder()
372 .append(str.charAt(1))
373 .append(str.charAt(2))
374 .toString().toUpperCase(), Color.class);
375 int g = parseHexInt(new StringBuilder()
376 .append(str.charAt(3))
377 .append(str.charAt(4))
378 .toString().toUpperCase(), Color.class);
379 int b = parseHexInt(new StringBuilder()
380 .append(str.charAt(5))
381 .append(str.charAt(6))
382 .toString().toUpperCase(), Color.class);
383
384 return Color.rgb(r, g, b);
385 }
386 }
387
388 private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
389 private LongWithAlphaColorFormatterDelegate() {
390 super(PATTERN_LONG_WITH_ALPHA);
391 }
392
393 @Override
394 @Nonnull
395 public String format(@Nonnull Color color) {
396 requireNonNull(color, ERROR_COLOR_NULL);
397
398 return new StringBuilder(HASH)
399 .append(padLeft(toHexString(red(color)), ZERO))
400 .append(padLeft(toHexString(green(color)), ZERO))
401 .append(padLeft(toHexString(blue(color)), ZERO))
402 .append(padLeft(toHexString(alpha(color)), ZERO))
403 .toString();
404 }
405
406 @Override
407 @Nonnull
408 public Color parse(@Nonnull String str) throws ParseException {
409 if (!str.startsWith(HASH) || str.length() != 9) {
410 throw parseError(str, Color.class);
411 }
412
413 int r = parseHexInt(new StringBuilder()
414 .append(str.charAt(1))
415 .append(str.charAt(2))
416 .toString().toUpperCase(), Color.class);
417 int g = parseHexInt(new StringBuilder()
418 .append(str.charAt(3))
419 .append(str.charAt(4))
420 .toString().toUpperCase(), Color.class);
421 int b = parseHexInt(new StringBuilder()
422 .append(str.charAt(5))
423 .append(str.charAt(6))
424 .toString().toUpperCase(), Color.class);
425 int a = parseHexInt(new StringBuilder()
426 .append(str.charAt(7))
427 .append(str.charAt(8))
428 .toString().toUpperCase(), Color.class);
429
430 return Color.rgb(r, g, b, a / 255d);
431 }
432 }
433
434 private static String padLeft(String self, String padding) {
435 return 2 <= self.length() ? self : padding + self;
436 }
437 }
|