blob: 0b069c4392d4b3b5d7d602a2868eb5284159be52 [file] [log] [blame]
/*
* 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.sis.internal.converter;
import java.util.Set;
import java.util.EnumSet;
import java.util.IllformedLocaleException;
import java.nio.charset.UnsupportedCharsetException;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.nio.file.InvalidPathException;
import javax.measure.format.ParserException;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.Locales;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.iso.SimpleInternationalString;
import org.apache.sis.internal.util.CodeLists;
import org.apache.sis.measure.Units;
/**
* Handles conversions between {@link String} and various kinds of objects.
* Each inner class in this {@code StringConverter} class defines both the
* forward and the inverse converters.
*
* <p>Most converters are pretty close to bijective functions, but not exactly.
* For example conversions from {@code String} to {@link java.io.File} is not
* completely bijective because various path separators ({@code '/'} and {@code '\'})
* produce the same {@code File} object.</p>
*
* <div class="section">Special cases</div>
* Conversion table from {@link String} to {@link java.lang.Boolean}:
*
* <table class="sis">
* <caption>Conversion table from strings</caption>
* <tr><th>source</th> <th>target</th></tr>
* <tr><td>{@code "true"} </td><td>{@link java.lang.Boolean#TRUE} </td></tr>
* <tr><td>{@code "false"} </td><td>{@link java.lang.Boolean#FALSE} </td></tr>
* <tr><td>{@code "yes"} </td><td>{@link java.lang.Boolean#TRUE} </td></tr>
* <tr><td>{@code "no"} </td><td>{@link java.lang.Boolean#FALSE} </td></tr>
* <tr><td>{@code "on"} </td><td>{@link java.lang.Boolean#TRUE} </td></tr>
* <tr><td>{@code "off"} </td><td>{@link java.lang.Boolean#FALSE} </td></tr>
* <tr><td>{@code "1"} </td><td>{@link java.lang.Boolean#TRUE} </td></tr>
* <tr><td>{@code "0"} </td><td>{@link java.lang.Boolean#FALSE} </td></tr>
* </table>
*
* <div class="section">Immutability and thread safety</div>
* This base class and all inner classes are immutable, and thus inherently thread-safe.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.5
* @since 0.3
* @module
*/
abstract class StringConverter<T> extends SystemConverter<String, T> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -7181934724345598473L;
/**
* The inverse converter from the target to the source class.
*/
private final ObjectConverter<T, String> inverse;
/**
* Creates a new converter for the given target class.
*
* @param targetClass the {@linkplain #getTargetClass() target class}.
*/
@SuppressWarnings("OverridableMethodCallDuringObjectConstruction")
StringConverter(final Class<T> targetClass) {
super(String.class, targetClass);
inverse = createInverse();
}
/**
* Invoked by the constructor for creating the inverse converter.
* To be overridden by classes which need a specialized instance.
*/
ObjectConverter<T, String> createInverse() {
return new ObjectToString<>(targetClass, this);
}
/**
* While this is not a general rule for surjective functions,
* all converters defined in this class are invertibles.
*/
@Override
public Set<FunctionProperty> properties() {
return EnumSet.of(FunctionProperty.SURJECTIVE, FunctionProperty.INVERTIBLE);
}
/**
* Returns the inverse converter.
*/
@Override
public final ObjectConverter<T, String> inverse() {
return inverse;
}
/**
* Converts the given string to the target type of this converter.
* This method verifies that the given string is non-null and non-empty,
* then delegates to {@link #doConvert(String)}.
*
* @param source the string to convert, or {@code null}.
* @return the converted value, or {@code null} if the given string was null or empty.
* @throws UnconvertibleObjectException if an error occurred during the conversion.
*/
@Override
public final T apply(String source) throws UnconvertibleObjectException {
source = CharSequences.trimWhitespaces(source);
if (source == null || source.isEmpty()) {
return null;
}
try {
return doConvert(source);
} catch (UnconvertibleObjectException e) {
throw e;
} catch (Exception e) {
throw new UnconvertibleObjectException(formatErrorMessage(source), e);
}
}
/**
* Invoked by {@link #apply(String)} for converting the given string to the target
* type of this converter.
*
* @param source the string to convert, guaranteed to be non-null and non-empty.
* @return the converted value.
* @throws Exception if an error occurred during the conversion.
*/
abstract T doConvert(String source) throws Exception;
/**
* Converter from {@link String} to various kinds of {@link java.lang.Number}.
*/
public static final class Number extends StringConverter<java.lang.Number> {
private static final long serialVersionUID = 8356246549731207392L;
public Number() {super(java.lang.Number.class);} // Instantiated by ServiceLoader.
@Override java.lang.Number doConvert(String source) throws NumberFormatException {
return Numbers.narrowestNumber(source);
}
}
public static final class Double extends StringConverter<java.lang.Double> {
private static final long serialVersionUID = -8576620873911755486L;
public Double() {super(java.lang.Double.class);} // Instantiated by ServiceLoader.
@Override java.lang.Double doConvert(String source) throws NumberFormatException {
return java.lang.Double.parseDouble(source);
}
}
public static final class Float extends StringConverter<java.lang.Float> {
private static final long serialVersionUID = 2301101264860653956L;
public Float() {super(java.lang.Float.class);} // Instantiated by ServiceLoader.
@Override java.lang.Float doConvert(String source) throws NumberFormatException {
return java.lang.Float.parseFloat(source);
}
}
public static final class Long extends StringConverter<java.lang.Long> {
private static final long serialVersionUID = 4711495660311641145L;
public Long() {super(java.lang.Long.class);} // Instantiated by ServiceLoader.
@Override java.lang.Long doConvert(String source) throws NumberFormatException {
return java.lang.Long.parseLong(source);
}
}
public static final class Integer extends StringConverter<java.lang.Integer> {
private static final long serialVersionUID = -5024227987148221073L;
public Integer() {super(java.lang.Integer.class);} // Instantiated by ServiceLoader.
@Override java.lang.Integer doConvert(String source) throws NumberFormatException {
return java.lang.Integer.parseInt(source);
}
}
public static final class Short extends StringConverter<java.lang.Short> {
private static final long serialVersionUID = 4067703596268901375L;
public Short() {super(java.lang.Short.class);} // Instantiated by ServiceLoader.
@Override java.lang.Short doConvert(String source) throws NumberFormatException {
return java.lang.Short.parseShort(source);
}
}
public static final class Byte extends StringConverter<java.lang.Byte> {
private static final long serialVersionUID = 6934470534898203474L;
public Byte() {super(java.lang.Byte.class);} // Instantiated by ServiceLoader.
@Override java.lang.Byte doConvert(String source) throws NumberFormatException {
return java.lang.Byte.parseByte(source);
}
}
public static final class BigDecimal extends StringConverter<java.math.BigDecimal> {
private static final long serialVersionUID = -5949128086478498785L;
public BigDecimal() {super(java.math.BigDecimal.class);} // Instantiated by ServiceLoader.
@Override java.math.BigDecimal doConvert(String source) throws NumberFormatException {
return new java.math.BigDecimal(source);
}
}
public static final class BigInteger extends StringConverter<java.math.BigInteger> {
private static final long serialVersionUID = 6387019773702794255L;
public BigInteger() {super(java.math.BigInteger.class);} // Instantiated by ServiceLoader.
@Override java.math.BigInteger doConvert(String source) throws NumberFormatException {
return new java.math.BigInteger(source);
}
}
public static final class Boolean extends StringConverter<java.lang.Boolean> {
private static final long serialVersionUID = 4689076223535035309L;
public Boolean() {super(java.lang.Boolean.class);} // Instantiated by ServiceLoader.
/** See {@link StringConverter} for the conversion table. */
@Override java.lang.Boolean doConvert(final String source) throws UnconvertibleObjectException {
switch (source.toLowerCase(java.util.Locale.ROOT)) {
case "true": case "yes": case "on": case "1": return java.lang.Boolean.TRUE;
case "false": case "no": case "off": case "0": return java.lang.Boolean.FALSE;
}
throw new UnconvertibleObjectException(formatErrorMessage(source));
}
}
public static final class Locale extends StringConverter<java.util.Locale> {
private static final long serialVersionUID = -794933131690043494L;
public Locale() {super(java.util.Locale.class);} // Instantiated by ServiceLoader.
@Override java.util.Locale doConvert(String source) throws IllformedLocaleException {
return Locales.parse(source);
}
}
public static final class Charset extends StringConverter<java.nio.charset.Charset> {
private static final long serialVersionUID = 4375157214436581095L;
public Charset() {super(java.nio.charset.Charset.class);} // Instantiated by ServiceLoader.
@Override java.nio.charset.Charset doConvert(String source) throws UnsupportedCharsetException {
return java.nio.charset.Charset.forName(source);
}
}
public static final class File extends StringConverter<java.io.File> {
private static final long serialVersionUID = -2157537605361631529L;
public File() {super(java.io.File.class);} // Instantiated by ServiceLoader.
@Override java.io.File doConvert(String source) {
return new java.io.File(source);
}
}
public static final class Path extends StringConverter<java.nio.file.Path> {
private static final long serialVersionUID = -1737315635965906042L;
public Path() {super(java.nio.file.Path.class);} // Instantiated by ServiceLoader.
@Override java.nio.file.Path doConvert(String source) throws InvalidPathException {
return java.nio.file.Paths.get(source);
}
}
public static final class URI extends StringConverter<java.net.URI> {
private static final long serialVersionUID = 7266486748714603336L;
public URI() {super(java.net.URI.class);} // Instantiated by ServiceLoader.
@Override java.net.URI doConvert(String source) throws URISyntaxException {
return new java.net.URI(source);
}
}
public static final class URL extends StringConverter<java.net.URL> {
private static final long serialVersionUID = -6518011235037500143L;
public URL() {super(java.net.URL.class);} // Instantiated by ServiceLoader.
@Override java.net.URL doConvert(String source) throws MalformedURLException {
return new java.net.URL(source);
}
}
public static final class Unit extends StringConverter<javax.measure.Unit<?>> {
private static final long serialVersionUID = -1809497218136016210L;
@SuppressWarnings("unchecked")
public Unit() {super((Class) javax.measure.Unit.class);} // Instantiated by ServiceLoader.
@Override javax.measure.Unit<?> doConvert(String source) throws ParserException {
return Units.valueOf(source);
}
}
public static final class Angle extends StringConverter<org.apache.sis.measure.Angle> {
private static final long serialVersionUID = -6937967772504961327L;
public Angle() {super(org.apache.sis.measure.Angle.class);} // Instantiated by ServiceLoader.
@Override org.apache.sis.measure.Angle doConvert(String source) throws NumberFormatException {
return new org.apache.sis.measure.Angle(source);
}
}
public static final class InternationalString extends StringConverter<org.opengis.util.InternationalString> {
private static final long serialVersionUID = -7386778914761469877L;
public InternationalString() {super(org.opengis.util.InternationalString.class);} // Instantiated by ServiceLoader.
@Override org.opengis.util.InternationalString doConvert(String source) {
return new SimpleInternationalString(source);
}
/** Declares that the converter is bijective. */
@Override public Set<FunctionProperty> properties() {
return bijective();
}
}
/**
* Converter from {@link String} to {@link org.opengis.util.CodeList}.
* This converter is particular in that it requires the target class in argument
* to the constructor.
*
* <p>Instances of this class are created by
* {@link SystemRegistry#createConverter(Class, Class)}.</p>
*/
static final class CodeList<T extends org.opengis.util.CodeList<T>> extends StringConverter<T> {
/** For cross-version compatibility on serialization. */
private static final long serialVersionUID = -6351669842222010105L;
/** Creates a new converter for the given code list. */
CodeList(final Class<T> targetClass) {
super(targetClass);
}
/** Converts the given string to the target type of this converter. */
@Override T doConvert(final String source) {
final T code = CodeLists.forName(targetClass, source, false);
if (code == null) {
throw new UnconvertibleObjectException(formatErrorMessage(source));
}
return code;
}
/** Invoked by the constructor for creating the inverse converter. */
@Override ObjectConverter<T, String> createInverse() {
return new ObjectToString.CodeList<>(targetClass, this);
}
}
/**
* Converter from {@link String} to {@link java.lang.Enum}.
* This converter is particular in that it requires the target class in argument
* to the constructor.
*
* <p>Instances of this class are created by
* {@link SystemRegistry#createConverter(Class, Class)}.</p>
*/
static final class Enum<T extends java.lang.Enum<T>> extends StringConverter<T> {
/** For cross-version compatibility on serialization. */
private static final long serialVersionUID = -4124617013044304640L;
/** Creates a new converter for the given enumeration. */
Enum(final Class<T> targetClass) {
super(targetClass);
}
/** Converts the given string to the target type of this converter. */
@Override T doConvert(final String source) {
final T code = CodeLists.forName(targetClass, source);
if (code == null) {
throw new UnconvertibleObjectException(formatErrorMessage(source));
}
return code;
}
/** Invoked by the constructor for creating the inverse converter. */
@Override ObjectConverter<T, String> createInverse() {
return new ObjectToString.Enum<>(targetClass, this);
}
}
}