среда, 30 октября 2013 г.

Разбор дат и других нетривиальных классов в Simple

Базовое использование Simple мы рассмотрели, но есть одна особенность, которую хочется разобрать отдельно.

Иногда просто аннотаций бывает недостаточно. Бывает, что в объектной модели есть поля таких типов, к которым мы либо не можем написать маппинги, либо там нужна более сложная логика, чем просто отображение узлов и атрибутов. Обычный пример — любые классы для работы с датами: что стандартный Calendar, что более приятный DateTime из библиотеки joda-time. Дата в XML может представляться миллисекундами, секундами, отформатированной строкой и чем угодно ещё. Задать правила разбора не всегда предоставляется возможным.

В Simple подобные задачи предусмотрены, и для них существует концепция Matcher-ов.

Для примера возьмем обычный rss с какими-нибудь новостями. В новости есть поле pubDate, в котором обычно строка вида Tue, 29 Oct 2013 14:52:57 +0400. Нам хорошо бы получить из неё поле типа Calendar.

Порядок действий следующий:

Transform

Для начала определяем наследника класса Transform<T>, который отвечает за преобразование типа из строки и в строку:

public class CalendarTransform implements Transform<Calendar> {
    final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyy HH:mm:ss zzz", Locale.ENGLISH);

    @Override
    public Calendar read(String s) throws Exception {
        Calendar c = Calendar.getInstance();
        c.setTime(dateFormat.parse(s));
        return c;
    }
    @Override
    public String write(Calendar dateTime) throws Exception {
        return dateFormat.format(dateTime.getTime());
    }
}

Как несложно догадаться, write будет использоваться для сериализации объекта, а read — для десериализации.

Matcher

Matcher нужен для того, чтобы было, где описать, какому классу соответствует какая трансформация. У нас особый случай всего один, так что код будет такой:

Matcher matcher = new Matcher() {
    @Override
    public Transform match(Class aClass) throws Exception {
        if (aClass == Calendar.class) {
            return new CalendarTransform();
        }
        return null;
    }
};

Вообще-то идея сопоставлять только по классу мне не очень нравится, потому что полей одного типа с разными форматами может быть несколько. В том же RSS для подкастов есть элемент Duration, который хорошо бы разбирать, как число и хранить там длительность в минутах. Но прогонять все другие возможные целочисленные поля в Matcher-е тип Integer кажется не очень хорошей идеей. Но других возможностей у нас нет.

Serializer

Осталось подключить всю эту логику сериализатору:

Serializer serializer = new Persister(matcher);

Вот и всё. Теперь, когда сериализатор будет встречать поле класса Calendar, оно будет разбираться именно в таком формате.

Код примера

А вот же он

Комментариев нет: