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.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 @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 str) throws ParseException {
149 return isBlank(str) ? null : 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.pivot.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.pivot.support.Colors
179 */
180 @Nonnull
181 @SuppressWarnings("ConstantConditions")
182 public static Color parseColor(@Nonnull String str) throws 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 str) throws 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 str) throws 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 str) throws 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 str) throws 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 str) throws 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 2 <= self.length() ? self : padding + self;
407 }
408 }
|