/* * Copyright 2008-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package griffon.pivot.formatters; import griffon.core.formatters.AbstractFormatter; import griffon.core.formatters.ParseException; import griffon.pivot.support.Colors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.awt.Color; import java.util.Arrays; import static griffon.util.GriffonNameUtils.isBlank; import static griffon.util.GriffonNameUtils.requireNonBlank; import static java.lang.Integer.toHexString; import static java.util.Objects.requireNonNull; /** * <p>A {@code Formatter} that can parse Strings into {@code java.awt.Color} and back * using several patterns</p> * <p/> * <p> * Supported patterns are: * <ul> * <li>{@code #RGB}</li> * <li>{@code #RGBA}</li> * <li>{@code #RRGGBB}</li> * <li>{@code #RRGGBBAA}</li> * </ul> * Where each letter stands for a particular color components in hexadecimal * <ul> * <li>{@code R} - red</li> * <li>{@code G} - green</li> * <li>{@code B} - blue</li> * <li>{@code A} - alpha</li> * </ul> * </p> * * @author Andres Almiray * @see griffon.core.formatters.Formatter * @since 2.0.0 */ public class ColorFormatter extends AbstractFormatter<Color> { /** * "#RGB" */ public static final String PATTERN_SHORT = "#RGB"; /** * "#RGBA" */ public static final String PATTERN_SHORT_WITH_ALPHA = "#RGBA"; /** * "#RRGGBB" */ public static final String PATTERN_LONG = "#RRGGBB"; /** * "#RRGGBBAA" */ public static final String PATTERN_LONG_WITH_ALPHA = "#RRGGBBAA"; /** * "#RRGGBB" */ public static final String DEFAULT_PATTERN = PATTERN_LONG; private static final String[] PATTERNS = new String[]{ PATTERN_LONG, PATTERN_LONG_WITH_ALPHA, PATTERN_SHORT, PATTERN_SHORT_WITH_ALPHA }; /** * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT}</b> pattern */ public static final ColorFormatter SHORT = new ColorFormatter(PATTERN_SHORT); /** * {@code ColorFormatter} that uses the <b>{@code PATTERN_SHORT_WITH_ALPHA}</b> pattern */ public static final ColorFormatter SHORT_WITH_ALPHA = new ColorFormatter(PATTERN_SHORT_WITH_ALPHA); /** * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG}</b> pattern */ public static final ColorFormatter LONG = new ColorFormatter(PATTERN_LONG); /** * {@code ColorFormatter} that uses the <b>{@code PATTERN_LONG_WITH_ALPHA}</b> pattern */ public static final ColorFormatter LONG_WITH_ALPHA = new ColorFormatter(PATTERN_LONG_WITH_ALPHA); /** * <p>Returns a {@code ColorFormatter} given a color pattern.</p> * * @param pattern the input pattern. Must be one of the 4 supported color patterns. * @return a {@code ColorPattern} instance * @throws IllegalArgumentException if the supplied {@code pattern} is not supported */ @Nonnull public static ColorFormatter getInstance(@Nullable String pattern) { return new ColorFormatter(pattern); } private final ColorFormatterDelegate delegate; protected ColorFormatter(@Nullable String pattern) { if (PATTERN_SHORT.equals(pattern)) { delegate = new ShortColorFormatterDelegate(); } else if (PATTERN_SHORT_WITH_ALPHA.equals(pattern)) { delegate = new ShortWithAlphaColorFormatterDelegate(); } else if (PATTERN_LONG.equals(pattern)) { delegate = new LongColorFormatterDelegate(); } else if (PATTERN_LONG_WITH_ALPHA.equals(pattern)) { delegate = new LongWithAlphaColorFormatterDelegate(); } else if (isBlank(pattern)) { delegate = new LongColorFormatterDelegate(); } else { throw new IllegalArgumentException("Invalid pattern '" + pattern + "'. Valid patterns are " + Arrays.toString(PATTERNS)); } } @Nullable public String format(@Nullable Color color) { return color == null ? null : delegate.format(color); } @Nullable public Color parse(@Nullable String str) throws ParseException { return isBlank(str) ? null : delegate.parse(str); } /** * Returns the pattern used by this {@code ColorFormatter} * * @return the pattern this {@code ColorFormatter} uses for parsing/formatting. */ @Nonnull public String getPattern() { return delegate.getPattern(); } /** * <p>Parses a string into a {@code java.awt.Color} instance.</p> * <p>The parsing pattern is chosen given the length of the input string * <ul> * <li>4 - {@code #RGB}</li> * <li>5 - {@code #RGBA}</li> * <li>7 - {@code #RRGGBB}</li> * <li>9 - {@code #RRGGBBAA}</li> * </ul> * </p> * The input string may also be any of the Color constants identified by * {@code griffon.pivot.support.Colors}. * * @param str the string representation of a {@code java.awt.Color} * @return a {@code java.awt.Color} instance matching the supplied RGBA color components * @throws ParseException if the string cannot be parsed by the chosen pattern * @see griffon.pivot.support.Colors */ @Nonnull @SuppressWarnings("ConstantConditions") public static Color parseColor(@Nonnull String str) throws ParseException { requireNonBlank(str, "Argument must not be blank"); if (str.startsWith("#")) { switch (str.length()) { case 4: return SHORT.parse(str); case 5: return SHORT_WITH_ALPHA.parse(str); case 7: return LONG.parse(str); case 9: return LONG_WITH_ALPHA.parse(str); default: throw parseError(str, Color.class); } } else { // assume it's a Color constant try { return Colors.valueOf(str.toUpperCase()).getColor(); } catch (Exception e) { throw parseError(str, Color.class, e); } } } private static interface ColorFormatterDelegate { @Nonnull String getPattern(); @Nonnull String format(@Nonnull Color color); @Nonnull Color parse(@Nonnull String str) throws ParseException; } private static abstract class AbstractColorFormatterDelegate implements ColorFormatterDelegate { private final String pattern; private AbstractColorFormatterDelegate(@Nonnull String pattern) { this.pattern = pattern; } @Nonnull public String getPattern() { return pattern; } } private static class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate { private ShortColorFormatterDelegate() { super(PATTERN_SHORT); } @Nonnull public String format(@Nonnull Color color) { requireNonNull(color, "Cannot format given Color because it's null"); return new StringBuilder("#") .append(toHexString(color.getRed()).charAt(0)) .append(toHexString(color.getGreen()).charAt(0)) .append(toHexString(color.getBlue()).charAt(0)) .toString(); } @Nonnull public Color parse(@Nonnull String str) throws ParseException { if (!str.startsWith("#") || str.length() != 4) { throw parseError(str, Color.class); } int r = parseHexInt(new StringBuilder() .append(str.charAt(1)) .append(str.charAt(1)) .toString().toUpperCase(), Color.class); int g = parseHexInt(new StringBuilder() .append(str.charAt(2)) .append(str.charAt(2)) .toString().toUpperCase(), Color.class); int b = parseHexInt(new StringBuilder() .append(str.charAt(3)) .append(str.charAt(3)) .toString().toUpperCase(), Color.class); return new Color(r, g, b); } } private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate { private ShortWithAlphaColorFormatterDelegate() { super(PATTERN_SHORT_WITH_ALPHA); } @Nonnull public String format(@Nonnull Color color) { requireNonNull(color, "Cannot format given Color because it's null"); return new StringBuilder("#") .append(toHexString(color.getRed()).charAt(0)) .append(toHexString(color.getGreen()).charAt(0)) .append(toHexString(color.getBlue()).charAt(0)) .append(toHexString(color.getAlpha()).charAt(0)) .toString(); } @Nonnull public Color parse(@Nonnull String str) throws ParseException { if (!str.startsWith("#") || str.length() != 5) { throw parseError(str, Color.class); } int r = parseHexInt(new StringBuilder() .append(str.charAt(1)) .append(str.charAt(1)) .toString().toUpperCase(), Color.class); int g = parseHexInt(new StringBuilder() .append(str.charAt(2)) .append(str.charAt(2)) .toString().toUpperCase(), Color.class); int b = parseHexInt(new StringBuilder() .append(str.charAt(3)) .append(str.charAt(3)) .toString().toUpperCase(), Color.class); int a = parseHexInt(new StringBuilder() .append(str.charAt(4)) .append(str.charAt(4)) .toString().toUpperCase(), Color.class); return new Color(r, g, b, a); } } private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate { private LongColorFormatterDelegate() { super(PATTERN_LONG); } @Nonnull public String format(@Nonnull Color color) { requireNonNull(color, "Cannot format given Color because it's null"); return new StringBuilder("#") .append(padLeft(toHexString(color.getRed()), "0")) .append(padLeft(toHexString(color.getGreen()), "0")) .append(padLeft(toHexString(color.getBlue()), "0")) .toString(); } @Nonnull public Color parse(@Nonnull String str) throws ParseException { if (!str.startsWith("#") || str.length() != 7) { throw parseError(str, Color.class); } int r = parseHexInt(new StringBuilder() .append(str.charAt(1)) .append(str.charAt(2)) .toString().toUpperCase(), Color.class); int g = parseHexInt(new StringBuilder() .append(str.charAt(3)) .append(str.charAt(4)) .toString().toUpperCase(), Color.class); int b = parseHexInt(new StringBuilder() .append(str.charAt(5)) .append(str.charAt(6)) .toString().toUpperCase(), Color.class); return new Color(r, g, b); } } private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate { private LongWithAlphaColorFormatterDelegate() { super(PATTERN_LONG_WITH_ALPHA); } @Nonnull public String format(@Nonnull Color color) { requireNonNull(color, "Cannot format given Color because it's null"); return new StringBuilder("#") .append(padLeft(toHexString(color.getRed()), "0")) .append(padLeft(toHexString(color.getGreen()), "0")) .append(padLeft(toHexString(color.getBlue()), "0")) .append(padLeft(toHexString(color.getAlpha()), "0")) .toString(); } @Nonnull public Color parse(@Nonnull String str) throws ParseException { if (!str.startsWith("#") || str.length() != 9) { throw parseError(str, Color.class); } int r = parseHexInt(new StringBuilder() .append(str.charAt(1)) .append(str.charAt(2)) .toString().toUpperCase(), Color.class); int g = parseHexInt(new StringBuilder() .append(str.charAt(3)) .append(str.charAt(4)) .toString().toUpperCase(), Color.class); int b = parseHexInt(new StringBuilder() .append(str.charAt(5)) .append(str.charAt(6)) .toString().toUpperCase(), Color.class); int a = parseHexInt(new StringBuilder() .append(str.charAt(7)) .append(str.charAt(8)) .toString().toUpperCase(), Color.class); return new Color(r, g, b, a); } } private static String padLeft(String self, String padding) { return 2 <= self.length() ? self : padding + self; } }