Создание парсера Class name + String value для типизированного значения - PullRequest
1 голос
/ 07 апреля 2020

Я пытаюсь написать метод, который может принимать имя класса String и значение String и возвращать значение, представленное в виде этой String.

Пример ввода:

parse("java.lang.String", "abc") -> String "ABC"
parse("java.lang.Boolean", "FALSE") -> Boolean FALSE
parse("java.lang.Integer", "123") -> Integer 123
parse("com.me.Color", "RED") -> enum Color.RED

Я обнаружил, что если я использую блок if, содержащий вызовы assignableFrom, я могу этого добиться. Но я бы предпочел написать что-то более расширяемое, поэтому завтра не так сложно добавить новый парсер.

Вот что у меня сейчас:

    String stringClassName = //stringified full class name
    String value = //value to parse
    Class<?> fieldType = Class.forName(stringClassName)
    if (fieldType.isAssignableFrom(String.class)) {
      return value;
    } else if (fieldType.isAssignableFrom(Boolean.class)) {
      return Util.toBoolean(value);
    } else if (fieldType.isEnum()) {
      return Util.toEnum(fieldType, value);
    } else {
      // throw exception
    }

1 Ответ

2 голосов
/ 07 апреля 2020

Есть несколько способов сделать это. Например:

У вас может быть интерфейс с именем Parser

package example;

public interface Parser {

    boolean canParse(String fullQualifiedClassName);
    Object parse(String fullQualifiedClassName, String value) throws ParseException;

    class ParseException extends Exception {

        public ParseException(String msg) {
            super(msg);
        }

        public ParseException(Exception cause) {
            super(cause);
        }
    }
}

И все ваши стандартные реализации в Enum или статически определенные другим способом:

package example;

public enum DefaultParser implements Parser {

    STRING {
        @Override
        public boolean canParse(String fullQualifiedClassName) {
            return isClassAssignableFromClassName(fullQualifiedClassName, String.class);
        }

        @Override
        public Object parse(String fullQualifiedClassName, String value) throws ParseException {
            return value;
        }
    },
    ENUM {
        @Override
        public boolean canParse(String fullQualifiedClassName) {
            return isClassAssignableFromClassName(fullQualifiedClassName, Enum.class);
        }

        @Override
        public Object parse(String fullQualifiedClassName, String value) throws ParseException {
            final Class<? extends Enum> clazz;
            try {
                clazz = (Class<? extends Enum>) Class.forName(fullQualifiedClassName);
            } catch (ClassNotFoundException e) {
                throw new ParseException(e);
            }

            return Enum.valueOf(clazz, value);
        }
    },
    BOOLEAN {
        @Override
        public boolean canParse(String fullQualifiedClassName) {
            return isClassAssignableFromClassName(fullQualifiedClassName, Boolean.class);
        }

        @Override
        public Object parse(String fullQualifiedClassName, String value) throws ParseException {
            return value.toLowerCase().equals("true");
        }
    };

    private static boolean isClassAssignableFromClassName(String fullQualifiedClassName, Class<?> clazz) {
        try {
            return clazz.isAssignableFrom(Class.forName(fullQualifiedClassName));
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}

И ParentParser Реализация, которая объединяет несколько парсеров в один:

package example;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class ParentParser implements Parser {

    private final List<Parser> parsers;

    public ParentParser() {
        this.parsers = new ArrayList<>();
        this.parsers.addAll(Arrays.asList(DefaultParser.values()));
    }

    public void register(Parser parser) {
        this.parsers.add(parser);
    }

    @Override
    public boolean canParse(String fullQualifiedClassName) {
        return findParser(fullQualifiedClassName).isPresent();
    }

    @Override
    public Object parse(String fullQualifiedClassName, String value) throws ParseException {
        return findParser(fullQualifiedClassName)
              .orElseThrow(() -> new ParseException("no registered parser found for class=" + fullQualifiedClassName))
              .parse(fullQualifiedClassName, value);
    }

    private Optional<Parser> findParser(String fullQualifiedClassName) {
        return this.parsers.stream().filter(parser -> parser.canParse(fullQualifiedClassName)).findAny();
    }
}

, который вы затем можете использовать так:

package example;

import example.Parser.ParseException;

public class Example {

    public static void main(String[] args) throws ParseException {
        final ParentParser parser = new ParentParser();

        System.out.println(parser.parse("java.lang.String", "hello world"));
        System.out.println(parser.parse("java.lang.Boolean", "true"));
        System.out.println(parser.parse("java.time.DayOfWeek", "TUESDAY"));
    }
}

И вы можете добавить, например, больше парсеров парсер, использующий Джексона (JSON):

package example;

import com.fasterxml.jackson.databind.ObjectMapper;
import example.Parser.ParseException;

import java.io.IOException;

public class Example {

    public static void main(String[] args) throws ParseException {
        final ParentParser parser = new ParentParser();

        System.out.println(parser.parse("java.lang.String", "hello world"));
        System.out.println(parser.parse("java.lang.Boolean", "true"));
        System.out.println(parser.parse("java.time.DayOfWeek", "TUESDAY"));

        parser.register(new JacksonParser());

        System.out.println(parser.parse("java.util.Map", "{\"key\":\"value\"}"));
    }

    private static class JacksonParser implements Parser {

        private static final ObjectMapper MAPPER = new ObjectMapper();

        @Override
        public boolean canParse(String fullQualifiedClassName) {
            final Class<?> clazz;
            try {
                clazz = Class.forName(fullQualifiedClassName);
            } catch (ClassNotFoundException e) {
                return false;
            }

            return MAPPER.canDeserialize(MAPPER.constructType(clazz));
        }

        @Override
        public Object parse(String fullQualifiedClassName, String value) throws ParseException {
            try {
                return MAPPER.readValue(value, Class.forName(fullQualifiedClassName));
            } catch (ClassNotFoundException | IOException e) {
                throw new ParseException(e);
            }
        }
    }
}

Обратите внимание, что это, конечно, можно оптимизировать в зависимости от ваших потребностей. Если ваши реализации синтаксического анализатора могут анализировать только статический список типов c и имеется только одна реализация синтаксического анализатора на класс, вы должны изменить List<Parser> на Map<Class<?>, Parser> и изменить метод регистра на register(Class<?> clazz, Parser parser), например.

...