| /* |
| * 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.xml; |
| |
| import java.net.URI; |
| import java.net.URL; |
| import java.net.URISyntaxException; |
| import java.net.MalformedURLException; |
| import java.util.MissingResourceException; |
| import java.util.IllformedLocaleException; |
| import java.util.Locale; |
| import java.util.UUID; |
| import java.nio.charset.Charset; |
| import java.nio.charset.IllegalCharsetNameException; |
| import javax.measure.Unit; |
| import javax.measure.format.ParserException; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.util.Locales; |
| |
| import static org.apache.sis.util.CharSequences.trimWhitespaces; |
| |
| |
| /** |
| * Performs conversions of XML element or attribute values encountered during XML (un)marshalling. |
| * Each method in this class is a converter and can be invoked at (un)marshalling time. |
| * The default implementation is straightforward and documented in the javadoc of each method. |
| * |
| * <p>This class provides a way to handle the errors which may exist in some XML documents. |
| * For example a URL in the document may be malformed, causing a {@link MalformedURLException} |
| * to be thrown. If this error is not handled, it will cause the (un)marshalling of the entire |
| * document to fail. An application may want to change this behavior by replacing URLs that |
| * are known to be erroneous by fixed versions of those URLs. Example:</p> |
| * |
| * {@preformat java |
| * class URLFixer extends ValueConverter { |
| * @Override |
| * public URL toURL(MarshalContext context, URI uri) throws MalformedURLException { |
| * try { |
| * return super.toURL(context, uri); |
| * } catch (MalformedURLException e) { |
| * if (uri.equals(KNOWN_ERRONEOUS_URI) { |
| * return FIXED_URL; |
| * } else { |
| * throw e; |
| * } |
| * } |
| * } |
| * } |
| * } |
| * |
| * See the {@link XML#CONVERTER} javadoc for an example of registering a custom |
| * {@code ValueConverter} to a (un)marshaller. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 0.5 |
| * @since 0.3 |
| * @module |
| */ |
| public class ValueConverter { |
| /** |
| * The default, thread-safe and immutable instance. This instance defines the |
| * converters used during every (un)marshalling if no {@code ValueConverter} |
| * was explicitly set. |
| */ |
| public static final ValueConverter DEFAULT = new ValueConverter(); |
| |
| /** |
| * Creates a default {@code ValueConverter}. This is for subclasses only, |
| * since new instances are useful only if at least one method is overridden. |
| */ |
| protected ValueConverter() { |
| } |
| |
| /** |
| * Invoked when an exception occurred in any {@code toXXX(…)} method. The default implementation |
| * does nothing and return {@code false}, which will cause the (un)marshalling process of the |
| * whole XML document to fail. |
| * |
| * <p>This method provides a single hook that subclasses can override in order to provide their |
| * own error handling for every methods defined in this class, like the example documented in |
| * the {@link XML#CONVERTER} javadoc. Subclasses also have the possibility to override individual |
| * {@code toXXX(…)} methods, like the example provided in this <a href="#skip-navbar_top">class |
| * javadoc</a>.</p> |
| * |
| * @param <T> the compile-time type of the {@code sourceType} argument. |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the value that can't be converted. |
| * @param sourceType the base type of the value to convert. This is determined by the argument type of the method |
| * that caught the exception. For example the source type is always {@code URI.class} |
| * if the exception has been caught by the {@link #toURL(MarshalContext, URI)} method. |
| * @param targetType the expected type of the converted object. |
| * @param exception the exception that occurred during the conversion attempt. |
| * @return {@code true} if the (un)marshalling process should continue despite this error, |
| * or {@code false} (the default) if the exception should be propagated, thus causing |
| * the (un)marshalling to fail. |
| */ |
| protected <T> boolean exceptionOccured(MarshalContext context, T value, |
| Class<T> sourceType, Class<?> targetType, Exception exception) |
| { |
| return false; |
| } |
| |
| /** |
| * Converts the given locale to a language code. For better compliance with ISO standards, the language code |
| * should be a 3-letters ISO 639-2 code (e.g. {@code "jpn"} for {@linkplain Locale#JAPANESE Japanese}). |
| * However those codes may not be available for every locales. |
| * |
| * <p>The default implementation performs the following steps:</p> |
| * <ul> |
| * <li>Try {@link Locale#getISO3Language()}:<ul> |
| * <li>On success, return that value if non-empty, or {@code null} otherwise.</li> |
| * <li>If an exception has been thrown, then:<ul> |
| * <li>If {@link #exceptionOccured exceptionOccured(…)} return {@code true}, then |
| * returns {@code value.getLanguage()} if non-empty or {@code null} otherwise.</li> |
| * <li>If {@code exceptionOccured(…)} returned {@code false} (which is the default |
| * behavior), then let the exception propagate.</li> |
| * </ul></li> |
| * </ul></li> |
| * </ul> |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the locale to convert to a language code, or {@code null}. |
| * @return the language code, or {@code null} if the given value was null or does not contains a language code. |
| * @throws MissingResourceException if no language code can be found for the given locale. |
| * |
| * @see Locale#getISO3Language() |
| * @see Locale#getLanguage() |
| */ |
| public String toLanguageCode(final MarshalContext context, final Locale value) throws MissingResourceException { |
| if (value != null) { |
| String code; |
| try { |
| code = value.getISO3Language(); |
| } catch (MissingResourceException e) { |
| if (!exceptionOccured(context, value, Locale.class, String.class, e)) { |
| throw e; |
| } |
| code = value.getLanguage(); |
| } |
| if (!code.isEmpty()) { |
| return code; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given locale to a country code. For better compliance with ISO standards, the country code |
| * should be a 2-letters ISO 3166 code (e.g. {@code "JP"} for {@linkplain Locale#JAPAN Japan}). |
| * |
| * <p>The default implementation returns {@link Locale#getCountry()} if non-empty, or {@code null} otherwise.</p> |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the locale to convert to a country code, or {@code null}. |
| * @return the country code, or {@code null} if the given value was null or does not contains a country code. |
| * @throws MissingResourceException if no country code can be found for the given locale. |
| * |
| * @see Locale#getISO3Country() |
| * @see Locale#getCountry() |
| */ |
| public String toCountryCode(final MarshalContext context, final Locale value) throws MissingResourceException { |
| if (value != null) { |
| String code = value.getCountry(); |
| if (!code.isEmpty()) { |
| return code; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given character set to a code. |
| * |
| * <p>The default implementation first invokes {@link Charset#name()}. Then if marshalling to legacy ISO 19139:2007, |
| * this method converts the <a href="http://www.iana.org/assignments/character-sets">IANA</a> name to a |
| * ISO 19115:2003 {@code MD_CharacterSetCode} using the following equivalence table:</p> |
| * |
| * <table class="sis"> |
| * <caption>IANA to ISO 19115:2003 character set code</caption> |
| * <tr> |
| * <td><table class="compact"> |
| * <caption>From ISO codes</caption> |
| * <tr><td style="width: 90px"><b>IANA</b></td><td><b>ISO 19115:2003</b></td></tr> |
| * <tr><td>{@code ISO-8859-1}</td> <td>{@code 8859part1}</td></tr> |
| * <tr><td>{@code ISO-8859-2}</td> <td>{@code 8859part2}</td></tr> |
| * <tr><td>{@code ISO-8859-3}</td> <td>{@code 8859part3}</td></tr> |
| * <tr><td>{@code ISO-8859-4}</td> <td>{@code 8859part4}</td></tr> |
| * <tr><td>{@code ISO-8859-5}</td> <td>{@code 8859part5}</td></tr> |
| * <tr><td>{@code ISO-8859-6}</td> <td>{@code 8859part6}</td></tr> |
| * <tr><td>{@code ISO-8859-7}</td> <td>{@code 8859part7}</td></tr> |
| * <tr><td>{@code ISO-8859-8}</td> <td>{@code 8859part8}</td></tr> |
| * <tr><td>{@code ISO-8859-9}</td> <td>{@code 8859part9}</td></tr> |
| * <tr><td>{@code ISO-8859-10}</td> <td>{@code 8859part10}</td></tr> |
| * <tr><td>{@code ISO-8859-11}</td> <td>{@code 8859part11}</td></tr> |
| * <tr><td>{@code ISO-8859-12}</td> <td>{@code 8859part12}</td></tr> |
| * <tr><td>{@code ISO-8859-13}</td> <td>{@code 8859part13}</td></tr> |
| * <tr><td>{@code ISO-8859-14}</td> <td>{@code 8859part14}</td></tr> |
| * <tr><td>{@code ISO-8859-15}</td> <td>{@code 8859part15}</td></tr> |
| * <tr><td>{@code ISO-8859-16}</td> <td>{@code 8859part16}</td></tr> |
| * </table></td> |
| * <td class="sep"><table class="compact"> |
| * <caption>Others</caption> |
| * <tr><td style="width: 90px"><b>IANA</b></td><td><b>ISO 19115:2003</b></td></tr> |
| * <tr><td>{@code UCS-2}</td> <td>{@code ucs2}</td></tr> |
| * <tr><td>{@code UCS-4}</td> <td>{@code ucs4}</td></tr> |
| * <tr><td>{@code UTF-7}</td> <td>{@code utf7}</td></tr> |
| * <tr><td>{@code UTF-8}</td> <td>{@code utf8}</td></tr> |
| * <tr><td>{@code UTF-16}</td> <td>{@code utf16}</td></tr> |
| * <tr><td>{@code JIS_X0201}</td> <td>{@code jis}</td></tr> |
| * <tr><td>{@code Shift_JIS}</td> <td>{@code shiftJIS}</td></tr> |
| * <tr><td>{@code EUC-JP}</td> <td>{@code eucJP}</td></tr> |
| * <tr><td>{@code US-ASCII}</td> <td>{@code usAscii}</td></tr> |
| * <tr><td>{@code EBCDIC}</td> <td>{@code ebcdic}</td></tr> |
| * <tr><td>{@code EUC-KR}</td> <td>{@code eucKR}</td></tr> |
| * <tr><td>{@code Big5}</td> <td>{@code big5}</td></tr> |
| * <tr><td>{@code GB2312}</td> <td>{@code GB2312}</td></tr> |
| * </table></td> |
| * </tr> |
| * </table> |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the locale to convert to a character set code, or {@code null}. |
| * @return the country code, or {@code null} if the given value was null. |
| * |
| * @see Charset#name() |
| * |
| * @since 0.5 |
| */ |
| public String toCharsetCode(final MarshalContext context, final Charset value) { |
| if (value != null) { |
| return LegacyCodes.fromIANA(value.name()); |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a locale. The string is the language code either as the 2 |
| * letters or the 3 letters ISO code. It can optionally be followed by the {@code '_'} |
| * character and the country code (again either as 2 or 3 letters), optionally followed |
| * by {@code '_'} and the variant. |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a locale, or {@code null}. |
| * @return the converted locale, or {@code null} if the given value was null or empty, or |
| * if an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws IllformedLocaleException if the given string can not be converted to a locale. |
| * |
| * @see Locales#parse(String) |
| */ |
| public Locale toLocale(final MarshalContext context, String value) throws IllformedLocaleException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) try { |
| return Locales.parse(value); |
| } catch (IllformedLocaleException e) { |
| if (!exceptionOccured(context, value, String.class, Locale.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a character set. The string can be either a |
| * <a href="http://www.iana.org/assignments/character-sets">IANA</a> identifier, |
| * or one of the ISO 19115:2003 {@code MD_CharacterSetCode} identifier. |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a character set, or {@code null}. |
| * @return the converted character set, or {@code null} if the given value was null or empty, or |
| * if an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws IllegalCharsetNameException if the given string can not be converted to a character set. |
| * |
| * @see Charset#forName(String) |
| * |
| * @since 0.5 |
| */ |
| public Charset toCharset(final MarshalContext context, String value) throws IllegalCharsetNameException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) { |
| value = LegacyCodes.toIANA(value); |
| try { |
| return Charset.forName(value); |
| } catch (IllegalCharsetNameException e) { |
| if (!exceptionOccured(context, value, String.class, Charset.class, e)) { |
| throw e; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a unit. The default implementation is as below, omitting |
| * the check for null value and the call to {@link #exceptionOccured exceptionOccured(…)} |
| * in case of error: |
| * |
| * {@preformat java |
| * return Units.valueOf(value); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a unit, or {@code null}. |
| * @return the converted unit, or {@code null} if the given value was null or empty, or |
| * if an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws IllegalArgumentException if the given string can not be converted to a unit. |
| * |
| * @see Units#valueOf(String) |
| */ |
| public Unit<?> toUnit(final MarshalContext context, String value) throws IllegalArgumentException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) try { |
| return Units.valueOf(value); |
| } catch (ParserException e) { |
| if (!exceptionOccured(context, value, String.class, Unit.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a Universal Unique Identifier. The default implementation |
| * is as below, omitting the check for null value and the call to {@link #exceptionOccured |
| * exceptionOccured(…)} in case of error: |
| * |
| * {@preformat java |
| * return UUID.fromString(value); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a UUID, or {@code null}. |
| * @return the converted UUID, or {@code null} if the given value was null or empty, or |
| * if an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws IllegalArgumentException if the given string can not be converted to a UUID. |
| * |
| * @see UUID#fromString(String) |
| */ |
| public UUID toUUID(final MarshalContext context, String value) throws IllegalArgumentException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) try { |
| return UUID.fromString(value); |
| } catch (IllegalArgumentException e) { |
| if (!exceptionOccured(context, value, String.class, UUID.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a URI. The default performs the following work |
| * (omitting the check for null value and the call to {@link #exceptionOccured |
| * exceptionOccured(…)} in case of error): |
| * |
| * {@preformat java |
| * return new URI(value); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a URI, or {@code null}. |
| * @return the converted URI, or {@code null} if the given value was null or empty, or if |
| * an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws URISyntaxException if the given string can not be converted to a URI. |
| * |
| * @see URI#URI(String) |
| */ |
| public URI toURI(final MarshalContext context, String value) throws URISyntaxException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) try { |
| return new URI(value); |
| } catch (URISyntaxException e) { |
| if (!exceptionOccured(context, value, String.class, URI.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given URL to a URI. The default implementation is as below, omitting |
| * the check for null value and the call to {@link #exceptionOccured exceptionOccured(…)} |
| * in case of error: |
| * |
| * {@preformat java |
| * return value.toURI(); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the URL to convert to a URI, or {@code null}. |
| * @return the converted URI, or {@code null} if the given value was null or if an |
| * exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws URISyntaxException if the given URL can not be converted to a URI. |
| * |
| * @see URL#toURI() |
| */ |
| public URI toURI(final MarshalContext context, final URL value) throws URISyntaxException { |
| if (value != null) try { |
| return value.toURI(); |
| } catch (URISyntaxException e) { |
| if (!exceptionOccured(context, value, URL.class, URI.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given URI to a URL. The default implementation is as below, omitting |
| * the check for null value and the call to {@link #exceptionOccured exceptionOccured(…)} |
| * in case of error: |
| * |
| * {@preformat java |
| * return value.toURL(); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the URI to convert to a URL, or {@code null}. |
| * @return the converted URL, or {@code null} if the given value was null or if an |
| * exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws MalformedURLException if the given URI can not be converted to a URL. |
| * |
| * @see URI#toURL() |
| */ |
| public URL toURL(final MarshalContext context, final URI value) throws MalformedURLException { |
| if (value != null) try { |
| return value.toURL(); |
| } catch (MalformedURLException | IllegalArgumentException e) { |
| if (!exceptionOccured(context, value, URI.class, URL.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the given string to a {@code NilReason}. The default implementation is as below, |
| * omitting the check for null value and the call to {@link #exceptionOccured exceptionOccured(…)} |
| * in case of error: |
| * |
| * {@preformat java |
| * return NilReason.valueOf(value); |
| * } |
| * |
| * @param context context (GML version, locale, <i>etc.</i>) of the (un)marshalling process. |
| * @param value the string to convert to a nil reason, or {@code null}. |
| * @return the converted nil reason, or {@code null} if the given value was null or empty, or |
| * if an exception was thrown and {@code exceptionOccured(…)} returned {@code true}. |
| * @throws URISyntaxException if the given string can not be converted to a nil reason. |
| * |
| * @see NilReason#valueOf(String) |
| */ |
| public NilReason toNilReason(final MarshalContext context, String value) throws URISyntaxException { |
| value = trimWhitespaces(value); |
| if (value != null && !value.isEmpty()) try { |
| return NilReason.valueOf(value); |
| } catch (URISyntaxException e) { |
| if (!exceptionOccured(context, value, String.class, URI.class, e)) { |
| throw e; |
| } |
| } |
| return null; |
| } |
| } |