Skip to content

Commit

Permalink
feat: added converters for color, dimension, inetaddress, locale, pat…
Browse files Browse the repository at this point in the history
…tern, and point. (#47)
  • Loading branch information
SethFalco authored Feb 4, 2024
1 parent 41500f0 commit a234761
Show file tree
Hide file tree
Showing 13 changed files with 1,188 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@

package org.apache.commons.beanutils2;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.io.File;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
Expand All @@ -40,7 +44,9 @@
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Collection;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Pattern;

import org.apache.commons.beanutils2.converters.ArrayConverter;
import org.apache.commons.beanutils2.converters.BigDecimalConverter;
Expand All @@ -50,23 +56,29 @@
import org.apache.commons.beanutils2.converters.CalendarConverter;
import org.apache.commons.beanutils2.converters.CharacterConverter;
import org.apache.commons.beanutils2.converters.ClassConverter;
import org.apache.commons.beanutils2.converters.ColorConverter;
import org.apache.commons.beanutils2.converters.ConverterFacade;
import org.apache.commons.beanutils2.converters.DateConverter;
import org.apache.commons.beanutils2.converters.DimensionConverter;
import org.apache.commons.beanutils2.converters.DoubleConverter;
import org.apache.commons.beanutils2.converters.DurationConverter;
import org.apache.commons.beanutils2.converters.EnumConverter;
import org.apache.commons.beanutils2.converters.FileConverter;
import org.apache.commons.beanutils2.converters.FloatConverter;
import org.apache.commons.beanutils2.converters.InetAddressConverter;
import org.apache.commons.beanutils2.converters.IntegerConverter;
import org.apache.commons.beanutils2.converters.LocalDateConverter;
import org.apache.commons.beanutils2.converters.LocalDateTimeConverter;
import org.apache.commons.beanutils2.converters.LocalTimeConverter;
import org.apache.commons.beanutils2.converters.LocaleConverter;
import org.apache.commons.beanutils2.converters.LongConverter;
import org.apache.commons.beanutils2.converters.MonthDayConverter;
import org.apache.commons.beanutils2.converters.OffsetDateTimeConverter;
import org.apache.commons.beanutils2.converters.OffsetTimeConverter;
import org.apache.commons.beanutils2.converters.PathConverter;
import org.apache.commons.beanutils2.converters.PatternConverter;
import org.apache.commons.beanutils2.converters.PeriodConverter;
import org.apache.commons.beanutils2.converters.PointConverter;
import org.apache.commons.beanutils2.converters.ShortConverter;
import org.apache.commons.beanutils2.converters.StringConverter;
import org.apache.commons.beanutils2.converters.URIConverter;
Expand Down Expand Up @@ -97,6 +109,8 @@
* <li>byte and java.lang.Byte (default to zero)</li>
* <li>char and java.lang.Character (default to a space)</li>
* <li>java.lang.Class (no default value)</li>
* <li>java.awt.Color (no default value)</li>
* <li>java.awt.Dimension (no default value)</li>
* <li>double and java.lang.Double (default to zero)</li>
* <li>float and java.lang.Float (default to zero)</li>
* <li>int and java.lang.Integer (default to zero)</li>
Expand All @@ -105,6 +119,8 @@
* <li>java.lang.String (default to null)</li>
* <li>java.lang.Enum (default to null)</li>
* <li>java.io.File (no default value)</li>
* <li>java.net.InetAddress (no default value)</li>
* <li>java.util.Locale (no default value)</li>
* <li>java.nio.file.Path (no default value)</li>
* <li>java.net.URL (no default value)</li>
* <li>java.net.URI (no default value)</li>
Expand All @@ -120,7 +136,9 @@
* <li>java.time.ZonedDateTime (no default value)</li>
* <li>java.time.Duration (no default value)</li>
* <li>java.time.MonthDay (no default value)</li>
* <li>java.util.regex.Pattern (no default value)</li>
* <li>java.time.Period (no default value)</li>
* <li>java.awt.Point (no default value)</li>
* <li>java.time.Year (no default value)</li>
* <li>java.time.YearMonth (no default value)</li>
* <li>java.time.ZoneId (no default value)</li>
Expand Down Expand Up @@ -541,10 +559,13 @@ private void registerArrays(final boolean throwException, final int defaultArray

// Other
registerArrayConverter(Class.class, new ClassConverter(), throwException, defaultArraySize);
registerArrayConverter(Color.class, new ColorConverter(), throwException, defaultArraySize);
registerArrayConverter(Enum.class, new EnumConverter(), throwException, defaultArraySize);
registerArrayConverter(java.util.Date.class, new DateConverter(), throwException, defaultArraySize);
registerArrayConverter(Calendar.class, new CalendarConverter(), throwException, defaultArraySize);
registerArrayConverter(Dimension.class, new DimensionConverter(), throwException, defaultArraySize);
registerArrayConverter(File.class, new FileConverter(), throwException, defaultArraySize);
registerArrayConverter(InetAddress.class, new InetAddressConverter(), throwException, defaultArraySize);
registerArrayConverter(Path.class, new PathConverter(), throwException, defaultArraySize);
registerArrayConverter(java.sql.Date.class, new SqlDateConverter(), throwException, defaultArraySize);
registerArrayConverter(java.sql.Time.class, new SqlTimeConverter(), throwException, defaultArraySize);
Expand All @@ -555,12 +576,15 @@ private void registerArrays(final boolean throwException, final int defaultArray
registerArrayConverter(LocalDate.class, new LocalDateConverter(), throwException, defaultArraySize);
registerArrayConverter(LocalDateTime.class, new LocalDateTimeConverter(), throwException, defaultArraySize);
registerArrayConverter(LocalTime.class, new LocalTimeConverter(), throwException, defaultArraySize);
registerArrayConverter(Locale.class, new LocaleConverter(), throwException, defaultArraySize);
registerArrayConverter(OffsetDateTime.class, new OffsetDateTimeConverter(),throwException, defaultArraySize);
registerArrayConverter(OffsetTime.class, new OffsetTimeConverter(), throwException, defaultArraySize);
registerArrayConverter(ZonedDateTime.class, new ZonedDateTimeConverter(), throwException, defaultArraySize);
registerArrayConverter(Duration.class, new DurationConverter(), throwException, defaultArraySize);
registerArrayConverter(MonthDay.class, new MonthDayConverter(), throwException, defaultArraySize);
registerArrayConverter(Pattern.class, new PatternConverter(), throwException, defaultArraySize);
registerArrayConverter(Period.class, new PeriodConverter(), throwException, defaultArraySize);
registerArrayConverter(Point.class, new PointConverter(), throwException, defaultArraySize);
registerArrayConverter(Year.class, new YearConverter(), throwException, defaultArraySize);
registerArrayConverter(YearMonth.class, new YearMonthConverter(), throwException, defaultArraySize);
registerArrayConverter(ZoneId.class, new ZoneIdConverter(), throwException, defaultArraySize);
Expand Down Expand Up @@ -606,10 +630,13 @@ private void registerArrays(final boolean throwException, final int defaultArray
private void registerOther(final boolean throwException) {
// @formatter:off
register(Class.class, throwException ? new ClassConverter<>() : new ClassConverter<>(null));
register(Color.class, throwException ? new ColorConverter() : new ColorConverter(null));
register(Enum.class, throwException ? new EnumConverter() : new EnumConverter(null));
register(java.util.Date.class, throwException ? new DateConverter() : new DateConverter(null));
register(Dimension.class, throwException ? new DimensionConverter() : new DimensionConverter(null));
register(Calendar.class, throwException ? new CalendarConverter() : new CalendarConverter(null));
register(File.class, throwException ? new FileConverter() : new FileConverter(null));
register(InetAddress.class, throwException ? new InetAddressConverter() : new InetAddressConverter(null));
register(Path.class, throwException ? new PathConverter() : new PathConverter(null));
register(java.sql.Date.class, throwException ? new SqlDateConverter() : new SqlDateConverter(null));
register(java.sql.Time.class, throwException ? new SqlTimeConverter() : new SqlTimeConverter(null));
Expand All @@ -620,12 +647,15 @@ private void registerOther(final boolean throwException) {
register(LocalDate.class, throwException ? new LocalDateConverter() : new LocalDateConverter(null));
register(LocalDateTime.class, throwException ? new LocalDateTimeConverter() : new LocalDateTimeConverter(null));
register(LocalTime.class, throwException ? new LocalTimeConverter() : new LocalTimeConverter(null));
register(Locale.class, throwException ? new LocaleConverter() : new LocaleConverter(null));
register(OffsetDateTime.class, throwException ? new OffsetDateTimeConverter() : new OffsetDateTimeConverter(null));
register(OffsetTime.class, throwException ? new OffsetTimeConverter() : new OffsetTimeConverter(null));
register(ZonedDateTime.class, throwException ? new ZonedDateTimeConverter() : new ZonedDateTimeConverter(null));
register(Duration.class, throwException ? new DurationConverter() : new DurationConverter(null));
register(MonthDay.class, throwException ? new MonthDayConverter() : new MonthDayConverter(null));
register(Pattern.class, throwException ? new PatternConverter() : new PatternConverter(null));
register(Period.class, throwException ? new PeriodConverter() : new PeriodConverter(null));
register(Point.class, throwException ? new PointConverter() : new PointConverter(null));
register(Year.class, throwException ? new YearConverter() : new YearConverter(null));
register(YearMonth.class, throwException ? new YearMonthConverter() : new YearMonthConverter(null));
register(ZoneId.class, throwException ? new ZoneIdConverter() : new ZoneIdConverter(null));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.commons.beanutils2.converters;

import java.awt.Color;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

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

/** Regular expression matching the output of {@link Color#toString()}. */
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})\\]?$");

/**
* Construct a <b>{@link Color}</b> <i>Converter</i> that throws a {@code ConversionException} if an error occurs.
*/
public ColorConverter() {
super();
}

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

/**
* Gets the default type this {@code Converter} handles.
*
* @return The default type this {@code Converter} handles.
* @since 2.0.0
*/
@Override
protected Class<Color> getDefaultType() {
return Color.class;
}

/**
* Converts a {@link Color} into a {@link String}.
*
* <p>
* Supports hexadecimal colors like #RGB, #RRGGBB, #RGBA, and #RRGGBBAA, and interprets raw color names based
* on the colors defined in Java, such as:
* </p>
*
* <ul>
* <li>{@link Color#BLACK}</li>
* <li>{@link Color#BLUE}</li>
* <li>{@link Color#CYAN}</li>
* <li>{@link Color#DARK_GRAY}</li>
* <li>{@link Color#GRAY}</li>
* <li>{@link Color#GREEN}</li>
* <li>{@link Color#LIGHT_GRAY}</li>
* <li>{@link Color#MAGENTA}</li>
* <li>{@link Color#ORANGE}</li>
* <li>{@link Color#PINK}</li>
* <li>{@link Color#RED}</li>
* <li>{@link Color#WHITE}</li>
* <li>{@link Color#YELLOW}</li>
* </ul>
*
* @param type Data type to which this value should be converted.
* @param value The String property value to convert.
* @return A {@link Color} which represents the compiled configuration property.
* @throws NullPointerException If the value is null.
* @throws NumberFormatException If an invalid number is provided.
*/
@Override
protected <T> T convertToType(final Class<T> type, final Object value) throws Throwable {
if (Color.class.isAssignableFrom(type)) {
final String stringValue = toString(value);

switch (toLowerCase(stringValue)) {
case "black":
return type.cast(Color.BLACK);
case "blue":
return type.cast(Color.BLUE);
case "cyan":
return type.cast(Color.CYAN);
case "darkgray":
case "darkgrey":
case "dark_gray":
case "dark_grey":
return type.cast(Color.DARK_GRAY);
case "gray":
case "grey":
return type.cast(Color.GRAY);
case "green":
return type.cast(Color.GREEN);
case "lightgray":
case "lightgrey":
case "light_gray":
case "light_grey":
return type.cast(Color.LIGHT_GRAY);
case "magenta":
return type.cast(Color.MAGENTA);
case "orange":
return type.cast(Color.ORANGE);
case "pink":
return type.cast(Color.PINK);
case "red":
return type.cast(Color.RED);
case "white":
return type.cast(Color.WHITE);
case "yellow":
return type.cast(Color.YELLOW);
default:
// Do nothing.
}

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

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

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

throw conversionException(type, value);
}

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

Matcher matcher = JAVA_COLOR_PATTERN.matcher(value);

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

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

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

return new Color(red, green, blue);
}

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

switch (value.length()) {
case 4:
return new Color(
Integer.parseInt(value.substring(1, 2), 16) * 17,
Integer.parseInt(value.substring(2, 3), 16) * 17,
Integer.parseInt(value.substring(3, 4), 16) * 17
);
case 5:
return new Color(
Integer.parseInt(value.substring(1, 2), 16) * 17,
Integer.parseInt(value.substring(2, 3), 16) * 17,
Integer.parseInt(value.substring(3, 4), 16) * 17,
Integer.parseInt(value.substring(4, 5), 16) * 17
);
case 7:
return new Color(
Integer.parseInt(value.substring(1, 3), 16),
Integer.parseInt(value.substring(3, 5), 16),
Integer.parseInt(value.substring(5, 7), 16)
);
case 9:
return new Color(
Integer.parseInt(value.substring(1, 3), 16),
Integer.parseInt(value.substring(3, 5), 16),
Integer.parseInt(value.substring(5, 7), 16),
Integer.parseInt(value.substring(7, 9), 16)
);
default:
throw new IllegalArgumentException("Value is an malformed hexadecimal color, if literal value decoding " +
"is required, prefix with 0x instead of #, otherwise expecting 3, 4, 6, or 8 characters only.");
}
}
}
Loading

0 comments on commit a234761

Please sign in to comment.