ColorConverter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements. See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership. The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License. You may obtain a copy of the License at
  9.  *
  10.  * http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied. See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.beanutils2.converters;

  20. import java.awt.Color;
  21. import java.util.Objects;
  22. import java.util.regex.Matcher;
  23. import java.util.regex.Pattern;

  24. /**
  25.  * {@link org.apache.commons.beanutils2.Converter} implementation that handles conversion to and from {@link Color}.
  26.  *
  27.  * <p>
  28.  * Will interpret hexadecimal colors similar to CSS engines, for example #RGB is interpreted as #RRGGBB. If using the literal hexadecimal value is desired, the
  29.  * value should be prefixed with {@code 0x} instead of {@link #HEX_COLOR_PREFIX #}.
  30.  * </p>
  31.  *
  32.  * @since 2.0.0
  33.  */
  34. public class ColorConverter extends AbstractConverter<Color> {

  35.     /** Prefix for hexadecimal color notation. */
  36.     private static final String HEX_COLOR_PREFIX = "#";

  37.     /** Regular expression matching the output of {@link Color#toString()}. */
  38.     private static final Pattern JAVA_COLOR_PATTERN = Pattern.compile("^(?:[A-Za-z\\d._]+)??\\[?(?:r=)?(\\d{1,3}),(?:g=)?(\\d{1,3}),(?:b=)?(\\d{1,3})\\]?$");

  39.     /**
  40.      * Construct a <strong>{@link Color}</strong> <em>Converter</em> that throws a {@code ConversionException} if an error occurs.
  41.      */
  42.     public ColorConverter() {
  43.     }

  44.     /**
  45.      * Constructs a {@link org.apache.commons.beanutils2.Converter} that will return the specified default value if a conversion error occurs.
  46.      *
  47.      * @param defaultValue The default value to be returned if the value to be converted is missing or an error occurs converting the value.
  48.      */
  49.     public ColorConverter(final Color defaultValue) {
  50.         super(defaultValue);
  51.     }

  52.     /**
  53.      * Converts a {@link Color} into a {@link String}.
  54.      *
  55.      * <p>
  56.      * Supports hexadecimal colors like #RGB, #RRGGBB, #RGBA, and #RRGGBBAA, and interprets raw color names based on the colors defined in Java, such as:
  57.      * </p>
  58.      *
  59.      * <ul>
  60.      * <li>{@link Color#BLACK}</li>
  61.      * <li>{@link Color#BLUE}</li>
  62.      * <li>{@link Color#CYAN}</li>
  63.      * <li>{@link Color#DARK_GRAY}</li>
  64.      * <li>{@link Color#GRAY}</li>
  65.      * <li>{@link Color#GREEN}</li>
  66.      * <li>{@link Color#LIGHT_GRAY}</li>
  67.      * <li>{@link Color#MAGENTA}</li>
  68.      * <li>{@link Color#ORANGE}</li>
  69.      * <li>{@link Color#PINK}</li>
  70.      * <li>{@link Color#RED}</li>
  71.      * <li>{@link Color#WHITE}</li>
  72.      * <li>{@link Color#YELLOW}</li>
  73.      * </ul>
  74.      *
  75.      * @param type  Data type to which this value should be converted.
  76.      * @param value The String property value to convert.
  77.      * @return A {@link Color} which represents the compiled configuration property.
  78.      * @throws NullPointerException  If the value is null.
  79.      * @throws NumberFormatException If an invalid number is provided.
  80.      */
  81.     @Override
  82.     protected <T> T convertToType(final Class<T> type, final Object value) throws Throwable {
  83.         if (Color.class.isAssignableFrom(type)) {
  84.             final String stringValue = toString(value);

  85.             switch (toLowerCase(stringValue)) {
  86.             case "black":
  87.                 return type.cast(Color.BLACK);
  88.             case "blue":
  89.                 return type.cast(Color.BLUE);
  90.             case "cyan":
  91.                 return type.cast(Color.CYAN);
  92.             case "darkgray":
  93.             case "darkgrey":
  94.             case "dark_gray":
  95.             case "dark_grey":
  96.                 return type.cast(Color.DARK_GRAY);
  97.             case "gray":
  98.             case "grey":
  99.                 return type.cast(Color.GRAY);
  100.             case "green":
  101.                 return type.cast(Color.GREEN);
  102.             case "lightgray":
  103.             case "lightgrey":
  104.             case "light_gray":
  105.             case "light_grey":
  106.                 return type.cast(Color.LIGHT_GRAY);
  107.             case "magenta":
  108.                 return type.cast(Color.MAGENTA);
  109.             case "orange":
  110.                 return type.cast(Color.ORANGE);
  111.             case "pink":
  112.                 return type.cast(Color.PINK);
  113.             case "red":
  114.                 return type.cast(Color.RED);
  115.             case "white":
  116.                 return type.cast(Color.WHITE);
  117.             case "yellow":
  118.                 return type.cast(Color.YELLOW);
  119.             default:
  120.                 // Do nothing.
  121.             }

  122.             if (stringValue.startsWith(HEX_COLOR_PREFIX)) {
  123.                 return type.cast(parseHexadecimalColor(stringValue));
  124.             }

  125.             if (stringValue.contains(",")) {
  126.                 return type.cast(parseToStringColor(stringValue));
  127.             }

  128.             return type.cast(Color.decode(stringValue));
  129.         }

  130.         throw conversionException(type, value);
  131.     }

  132.     /**
  133.      * Gets the default type this {@code Converter} handles.
  134.      *
  135.      * @return The default type this {@code Converter} handles.
  136.      * @since 2.0.0
  137.      */
  138.     @Override
  139.     protected Class<Color> getDefaultType() {
  140.         return Color.class;
  141.     }

  142.     /**
  143.      * Returns a {@link Color} for hexadecimal colors.
  144.      *
  145.      * @param value Hexadecimal representation of a color.
  146.      * @return The converted value.
  147.      * @throws NumberFormatException If the hexadecimal input contains non parsable characters.
  148.      */
  149.     private Color parseHexadecimalColor(final String value) {
  150.         Objects.requireNonNull(value);

  151.         switch (value.length()) {
  152.         case 4:
  153.             return new Color(Integer.parseInt(value.substring(1, 2), 16) * 17, Integer.parseInt(value.substring(2, 3), 16) * 17,
  154.                     Integer.parseInt(value.substring(3, 4), 16) * 17);
  155.         case 5:
  156.             return new Color(Integer.parseInt(value.substring(1, 2), 16) * 17, Integer.parseInt(value.substring(2, 3), 16) * 17,
  157.                     Integer.parseInt(value.substring(3, 4), 16) * 17, Integer.parseInt(value.substring(4, 5), 16) * 17);
  158.         case 7:
  159.             return new Color(Integer.parseInt(value.substring(1, 3), 16), Integer.parseInt(value.substring(3, 5), 16),
  160.                     Integer.parseInt(value.substring(5, 7), 16));
  161.         case 9:
  162.             return new Color(Integer.parseInt(value.substring(1, 3), 16), Integer.parseInt(value.substring(3, 5), 16),
  163.                     Integer.parseInt(value.substring(5, 7), 16), Integer.parseInt(value.substring(7, 9), 16));
  164.         default:
  165.             throw new IllegalArgumentException("Value is an malformed hexadecimal color, if literal value decoding "
  166.                     + "is required, prefix with 0x instead of #, otherwise expecting 3, 4, 6, or 8 characters only.");
  167.         }
  168.     }

  169.     /**
  170.      * Parses the Color based on the result of the {@link Color#toString()} method.
  171.      *
  172.      * Accepts the following values:
  173.      * <ul>
  174.      * <li>{@code java.awt.Color[r=255,g=255,b=255]}</li>
  175.      * <li>{@code [r=255,g=255,b=255]}</li>
  176.      * <li>{@code r=255,g=255,b=255}</li>
  177.      * <li>{@code 255,255,255}</li>
  178.      * </ul>
  179.      *
  180.      * @param value A color as represented by {@link Color#toString()}.
  181.      * @return The Java friendly {@link Color} this color represents.
  182.      * @throws IllegalArgumentException If the input can't be matches by the {@link #JAVA_COLOR_PATTERN} or a {@link Color} component specified is over 255.
  183.      */
  184.     private Color parseToStringColor(final String value) {
  185.         Objects.requireNonNull(value);

  186.         final Matcher matcher = JAVA_COLOR_PATTERN.matcher(value);

  187.         if (!matcher.matches()) {
  188.             throw new IllegalArgumentException("Invalid Color String provided. Could not parse.");
  189.         }

  190.         final int red = Integer.parseInt(matcher.group(1));
  191.         final int green = Integer.parseInt(matcher.group(2));
  192.         final int blue = Integer.parseInt(matcher.group(3));

  193.         if (red > 255 || green > 255 || blue > 255) {
  194.             throw new IllegalArgumentException("Color component integers must be between 0 and 255.");
  195.         }

  196.         return new Color(red, green, blue);
  197.     }
  198. }