001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.util.LinkedHashMap;
005import java.util.Map;
006import java.util.Optional;
007import java.util.function.Function;
008
009/**
010 * Utility class to parse various types from other values.
011 *
012 * @param <U> the type of the objects to parse
013 * @since 16184
014 */
015public class GenericParser<U> {
016
017    protected final Map<Class<?>, Function<U, ?>> parsers;
018
019    /**
020     * Creates an empty {@code GenericParser}
021     */
022    public GenericParser() {
023        this(new LinkedHashMap<>());
024    }
025
026    /**
027     * Creates a new {@code GenericParser} by deeply copying {@code parser}
028     *
029     * @param parser the parser to copy
030     */
031    public GenericParser(GenericParser<U> parser) {
032        this(new LinkedHashMap<>(parser.parsers));
033    }
034
035    /**
036     * Creates a new {@code GenericParser} with the same parsers as the specified map
037     *
038     * @param parsers the parsers
039     */
040    protected GenericParser(Map<Class<?>, Function<U, ?>> parsers) {
041        this.parsers = parsers;
042    }
043
044    public <T> GenericParser<U> registerParser(Class<T> type, Function<U, T> value) {
045        parsers.put(type, value);
046        return this;
047    }
048
049    /**
050     * Determines whether {@code type} can be {@linkplain #parse parsed}
051     *
052     * @param type the type
053     * @return true if {@code type} can be parsed
054     */
055    public boolean supports(Class<?> type) {
056        return parsers.containsKey(type);
057    }
058
059    /**
060     * Parses the given {@code value} as {@code type} and returns the result
061     *
062     * @param type  the type class
063     * @param value the value to parse
064     * @param <T>   the type
065     * @return the parsed value for {@code string} as type {@code type}
066     * @throws UnsupportedOperationException if {@code type} is not {@linkplain #supports supported}
067     * @throws UncheckedParseException       when the parsing fails
068     */
069    @SuppressWarnings("unchecked")
070    public <T> T parse(Class<T> type, U value) {
071        final Function<U, ?> parser = parsers.get(type);
072        if (parser == null) {
073            throw new UnsupportedOperationException(type + " is not supported");
074        }
075        try {
076            return (T) parser.apply(value);
077        } catch (RuntimeException ex) {
078            throw new UncheckedParseException("Failed to parse [" + value + "] as " + type, ex);
079        }
080    }
081
082    /**
083     * Tries to parse the given {@code value} as {@code type} and returns the result.
084     *
085     * @param type  the type class
086     * @param value the value to parse
087     * @param <T>   the type
088     * @return the parsed value for {@code value} as type {@code type},
089     * or {@code Optional.empty()} (if parsing fails, or the type is not {@linkplain #supports supported})
090     */
091    public <T> Optional<T> tryParse(Class<T> type, U value) {
092        try {
093            return Optional.ofNullable(parse(type, value));
094        } catch (RuntimeException ex) {
095            Logging.trace(ex);
096            return Optional.empty();
097        }
098    }
099}