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