| 
001 /*002  * Copyright 2008-2015 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.swing.formatters;
 017
 018 import griffon.core.formatters.AbstractFormatter;
 019 import griffon.core.formatters.ParseException;
 020 import griffon.swing.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     public String format(@Nullable Color color) {
 142         return color == null ? null : delegate.format(color);
 143     }
 144
 145     @Nullable
 146     public Color parse(@Nullable String str) throws ParseException {
 147         return isBlank(str) ? null : delegate.parse(str);
 148     }
 149
 150     /**
 151      * Returns the pattern used by this {@code ColorFormatter}
 152      *
 153      * @return the pattern this {@code ColorFormatter} uses for parsing/formatting.
 154      */
 155     @Nonnull
 156     public String getPattern() {
 157         return delegate.getPattern();
 158     }
 159
 160     /**
 161      * <p>Parses a string into a {@code java.awt.Color} instance.</p>
 162      * <p>The parsing pattern is chosen given the length of the input string
 163      * <ul>
 164      * <li>4 - {@code #RGB}</li>
 165      * <li>5 - {@code #RGBA}</li>
 166      * <li>7 - {@code #RRGGBB}</li>
 167      * <li>9 - {@code #RRGGBBAA}</li>
 168      * </ul>
 169      * </p>
 170      * The input string may also be any of the Color constants identified by
 171      * {@code griffon.swing.support.Colors}.
 172      *
 173      * @param str the string representation of a {@code java.awt.Color}
 174      * @return a {@code java.awt.Color} instance matching the supplied RGBA color components
 175      * @throws ParseException if the string cannot be parsed by the chosen pattern
 176      * @see griffon.swing.support.Colors
 177      */
 178     @Nonnull
 179     @SuppressWarnings("ConstantConditions")
 180     public static Color parseColor(@Nonnull String str) throws ParseException {
 181         requireNonBlank(str, "Argument must not be blank");
 182         if (str.startsWith("#")) {
 183             switch (str.length()) {
 184                 case 4:
 185                     return SHORT.parse(str);
 186                 case 5:
 187                     return SHORT_WITH_ALPHA.parse(str);
 188                 case 7:
 189                     return LONG.parse(str);
 190                 case 9:
 191                     return LONG_WITH_ALPHA.parse(str);
 192                 default:
 193                     throw parseError(str, Color.class);
 194             }
 195         } else {
 196             // assume it's a Color constant
 197             try {
 198                 return Colors.valueOf(str.toUpperCase()).getColor();
 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 class ShortColorFormatterDelegate extends AbstractColorFormatterDelegate {
 230         private ShortColorFormatterDelegate() {
 231             super(PATTERN_SHORT);
 232         }
 233
 234         @Nonnull
 235         public String format(@Nonnull Color color) {
 236             requireNonNull(color, "Cannot format given Color because it's null");
 237
 238             return new StringBuilder("#")
 239                 .append(toHexString(color.getRed()).charAt(0))
 240                 .append(toHexString(color.getGreen()).charAt(0))
 241                 .append(toHexString(color.getBlue()).charAt(0))
 242                 .toString();
 243         }
 244
 245         @Nonnull
 246         public Color parse(@Nonnull String str) throws ParseException {
 247             if (!str.startsWith("#") || str.length() != 4) {
 248                 throw parseError(str, Color.class);
 249             }
 250
 251             int r = parseHexInt(new StringBuilder()
 252                 .append(str.charAt(1))
 253                 .append(str.charAt(1))
 254                 .toString().toUpperCase(), Color.class);
 255             int g = parseHexInt(new StringBuilder()
 256                 .append(str.charAt(2))
 257                 .append(str.charAt(2))
 258                 .toString().toUpperCase(), Color.class);
 259             int b = parseHexInt(new StringBuilder()
 260                 .append(str.charAt(3))
 261                 .append(str.charAt(3))
 262                 .toString().toUpperCase(), Color.class);
 263
 264             return new Color(r, g, b);
 265         }
 266     }
 267
 268     private static class ShortWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
 269         private ShortWithAlphaColorFormatterDelegate() {
 270             super(PATTERN_SHORT_WITH_ALPHA);
 271         }
 272
 273         @Nonnull
 274         public String format(@Nonnull Color color) {
 275             requireNonNull(color, "Cannot format given Color because it's null");
 276
 277             return new StringBuilder("#")
 278                 .append(toHexString(color.getRed()).charAt(0))
 279                 .append(toHexString(color.getGreen()).charAt(0))
 280                 .append(toHexString(color.getBlue()).charAt(0))
 281                 .append(toHexString(color.getAlpha()).charAt(0))
 282                 .toString();
 283         }
 284
 285         @Nonnull
 286         public Color parse(@Nonnull String str) throws ParseException {
 287             if (!str.startsWith("#") || str.length() != 5) {
 288                 throw parseError(str, Color.class);
 289             }
 290
 291             int r = parseHexInt(new StringBuilder()
 292                 .append(str.charAt(1))
 293                 .append(str.charAt(1))
 294                 .toString().toUpperCase(), Color.class);
 295             int g = parseHexInt(new StringBuilder()
 296                 .append(str.charAt(2))
 297                 .append(str.charAt(2))
 298                 .toString().toUpperCase(), Color.class);
 299             int b = parseHexInt(new StringBuilder()
 300                 .append(str.charAt(3))
 301                 .append(str.charAt(3))
 302                 .toString().toUpperCase(), Color.class);
 303             int a = parseHexInt(new StringBuilder()
 304                 .append(str.charAt(4))
 305                 .append(str.charAt(4))
 306                 .toString().toUpperCase(), Color.class);
 307
 308             return new Color(r, g, b, a);
 309         }
 310     }
 311
 312     private static class LongColorFormatterDelegate extends AbstractColorFormatterDelegate {
 313         private LongColorFormatterDelegate() {
 314             super(PATTERN_LONG);
 315         }
 316
 317         @Nonnull
 318         public String format(@Nonnull Color color) {
 319             requireNonNull(color, "Cannot format given Color because it's null");
 320             return new StringBuilder("#")
 321                 .append(padLeft(toHexString(color.getRed()), "0"))
 322                 .append(padLeft(toHexString(color.getGreen()), "0"))
 323                 .append(padLeft(toHexString(color.getBlue()), "0"))
 324                 .toString();
 325         }
 326
 327         @Nonnull
 328         public Color parse(@Nonnull String str) throws ParseException {
 329             if (!str.startsWith("#") || str.length() != 7) {
 330                 throw parseError(str, Color.class);
 331             }
 332
 333             int r = parseHexInt(new StringBuilder()
 334                 .append(str.charAt(1))
 335                 .append(str.charAt(2))
 336                 .toString().toUpperCase(), Color.class);
 337             int g = parseHexInt(new StringBuilder()
 338                 .append(str.charAt(3))
 339                 .append(str.charAt(4))
 340                 .toString().toUpperCase(), Color.class);
 341             int b = parseHexInt(new StringBuilder()
 342                 .append(str.charAt(5))
 343                 .append(str.charAt(6))
 344                 .toString().toUpperCase(), Color.class);
 345
 346             return new Color(r, g, b);
 347         }
 348     }
 349
 350     private static class LongWithAlphaColorFormatterDelegate extends AbstractColorFormatterDelegate {
 351         private LongWithAlphaColorFormatterDelegate() {
 352             super(PATTERN_LONG_WITH_ALPHA);
 353         }
 354
 355         @Nonnull
 356         public String format(@Nonnull Color color) {
 357             requireNonNull(color, "Cannot format given Color because it's null");
 358
 359             return new StringBuilder("#")
 360                 .append(padLeft(toHexString(color.getRed()), "0"))
 361                 .append(padLeft(toHexString(color.getGreen()), "0"))
 362                 .append(padLeft(toHexString(color.getBlue()), "0"))
 363                 .append(padLeft(toHexString(color.getAlpha()), "0"))
 364                 .toString();
 365         }
 366
 367         @Nonnull
 368         public Color parse(@Nonnull String str) throws ParseException {
 369             if (!str.startsWith("#") || str.length() != 9) {
 370                 throw parseError(str, Color.class);
 371             }
 372
 373             int r = parseHexInt(new StringBuilder()
 374                 .append(str.charAt(1))
 375                 .append(str.charAt(2))
 376                 .toString().toUpperCase(), Color.class);
 377             int g = parseHexInt(new StringBuilder()
 378                 .append(str.charAt(3))
 379                 .append(str.charAt(4))
 380                 .toString().toUpperCase(), Color.class);
 381             int b = parseHexInt(new StringBuilder()
 382                 .append(str.charAt(5))
 383                 .append(str.charAt(6))
 384                 .toString().toUpperCase(), Color.class);
 385             int a = parseHexInt(new StringBuilder()
 386                 .append(str.charAt(7))
 387                 .append(str.charAt(8))
 388                 .toString().toUpperCase(), Color.class);
 389
 390             return new Color(r, g, b, a);
 391         }
 392     }
 393
 394     private static String padLeft(String self, String padding) {
 395         return 2 <= self.length() ? self : padding + self;
 396     }
 397 }
 |