Универсальный прозрачный конвертер?

Появилась потребность в создании прозрачного конвертера сложных объектов однотипных по структуре, но различающихся по namespace'у. Изначально имеется два XSD с описанием разных типов. JAXB их отлично обрабатывает и создаёт стабы. Далее по бизнес-логике нужно производить преобразование объекта из одного namespace'а в другой. Когда типов объектов не много, то не сильно утомительно сделать индивидуальный конвертер, но по мере разрастания структуры данных, такой подход уже не эффективен. Поэтому возникла идея написать один универсальный конвертер с прозрачным переносом данных.


Хочется сделать так, чтобы вызов конвертера в программном коде выглядел как нибудь так:

MyObjectA _a = new MyObjectA();
...
MyObjectB _b = Transparent.convert(_a);



За десять минут накидал скелет метода:

public class Transparent {

    public static <A, B> A convert(B x) {
        A res = null;
        for (Method m : x.getClass().getMethods()) {    // Читаем все методы полученного объекта из входного параметра
            String _methodName = m.getName();           // Запоминаем название метода
            if (_methodName.startsWith("get")) {        // Если это getter, то проходим дальше
                String _fieldName = _methodName.substring(3);            // Отрезаем 'get' и запоминаем название поля
                Class[] _paramTypes = new Class[]{m.getReturnType()};    // Запоминаем тип возвращаемого объекта этого метода
                try {
                    Method _methodName2 = res.getClass().getMethod("set".concat(_fieldName ), _paramTypes);
         // Пытаемся найти setter-метод с требуемой сигнатурой в требуемом возвращаемом типе
         // Дальше логика следующая - если setter-метод найден, то с помощью рефлексии получаем значение параметра из входного объекта, и присваиваем это значение возвращаемому объекту "res".
                } catch (NoSuchMethodException ex) {
                    Logger.getLogger(Transparent.class.getName()).log(Level.SEVERE, null, ex);
                    continue;
                } catch (SecurityException ex) {
                    Logger.getLogger(Transparent.class.getName()).log(Level.SEVERE, null, ex);
                    continue;
                }
            }
        }
        return res;
    }
}



Всё бы хорошо, но споткнулся на строке "_methodName2 = res.getClass().getMethod(«s...», объект «res» не инициирован, и это логично, ибо «A res = null;».


Варианты «new A()», «A.class», «A.getClass().newInstance()» не рабочие.


Дополнительно пробовал вот такой вариант:
private class _init<A> {

        private Class<A> type;

        public <A> A get(A c) {
            A ret = null;
            try {
                ret = (A) type.newInstance();
            } catch (InstantiationException ex) {
                Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
            }
            return ret;
        }
    }



Инициализацию соответственно провести как «A res = (new _init()).newInstance()», но данный вариант тоже не проходит, так как не допустим в static-контексте.


Собственно вопрос к гуру Java — как можно инициировать объект res в таком статическом методе?

Обновление от 04.09.2013


Проанализировал весь материал, и, похоже никак не получится в runtime'е узнать return-тип, и как следствие накидал следующий код. Код еще пока не окончательный, планируется обработать рекурсивную обработку сложных типов.
public class Converter {

    public static <A, B> A run(B x, Class<A> type) {
        A res = null;
        try {
            res = type.newInstance();
            for (Method m : x.getClass().getMethods()) {
                String _methodName = m.getName();
                if (m.getDeclaringClass() == x.getClass() && _methodName.startsWith("get")) {
                    String _fieldName = _methodName.substring(3);
                    Class[] _paramTypes = new Class[]{m.getReturnType()};
                    Method _methodName2 = null;
                    try {
                        _methodName2 = res.getClass().getMethod("set".concat(_fieldName), _paramTypes);
                        Object val = m.invoke(x);
                        if (val.getClass().equals(JAXBElement.class)) {
                            val = ((JAXBElement<?>) val).getValue();
                            String _ns = type.getDeclaredField(_fieldName.toLowerCase()).getAnnotation(javax.xml.bind.annotation.XmlElementRef.class).namespace();
                            QName _qname = new QName(_ns, _fieldName);
                            val = new JAXBElement(_qname, val.getClass(), type, val);
                        }
                        _methodName2.invoke(res, val);
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null);
                    } catch (NoSuchMethodException ex) {
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
                        continue;
                    } catch (SecurityException ex) {
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
                        continue;
                    } catch (IllegalArgumentException ex) {
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
                        continue;
                    } catch (InvocationTargetException ex) {
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
                        continue;
                    } catch (NoSuchFieldException ex) {
                        Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
                        continue;
                    }
                }
            }
        } catch (InstantiationException ex) {
            Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
        }
        return res;
    }
}
  • Вопрос задан
  • 3350 просмотров
Пригласить эксперта
Ответы на вопрос 5
@mayorovp
Я же писал вам в прошлый раз — принимайте Class в качестве аргумента метода convert. Судя по всему, это единственный способ.
Ответ написан
Комментировать
@zvorygin
В java «шаблоны» или как правильно говорить generic-и, существуют только на момент компиляции. В рантайме их нет, и когда в рантайме вызывается метод convert, про A уже абсолютно ничего неизсвестно — так что единственный возможный и правильный вариант — принимать Class в качестве аргумента метода. Можно принимать класс типа Class — так типизация сохранится.
Ответ написан
Комментировать
serso
@serso
Ничего не понимаю — в классе _init поле type никак не инициализируется. Должен быть конструктор с параметром — классом (_init(Class type)), в котором происходит выставление поля. Далее всё должно сработать.
НО, создать generic-объект без передачи класса (иди другой информации о классе (имя, имя пакета и т.д.)) — нельзя. Т.е., помимо передачи класса в конструктор _init, нужно ещё его передавать в статичный метод.

PS Писать в таком стиле, я считаю, это неуважение к читателю кода (вы читали java code conventions?)
PPS Инициализирован, а не инициирован
Ответ написан
Комментировать
barker
@barker
В дополнение к предыдущему ответу, да, как-то так должно работать:
    private class _init<A> {

        private Class<A> type;

        public A get(Class<A> type) {
            this.type = type;
            A ret = null;
            try {
                ret = type.newInstance();
            } catch (InstantiationException ex) {
                //...
            } catch (IllegalAccessException ex) {
            	//...
            }
            return ret;
        }
    }

Не знаю готовый ли это для вас пример, но должно работать по крайней мере, от этого надо плясать.
Ответ написан
tonyvelichko
@tonyvelichko
Немного не верное утверждение, что генерики существуют только на момент компиляции, точнее не во всех случаях. Например при наследовании от класса с генериками или имплементации генерик интерфейса, у нас есть возможность в рантайме получить полную информацию о самих генериках. Тоесть наш класс конвертера будет иметь примерно такой вид:
public class Converter<A, B> {

    final Class<? extends A> retClass;

    protected Converter() {
        retClass = findType(getClass());
        if (retClass == null) {
            throw new RuntimeException("Error while determine generic types");
        }
    }

    public A convert(B from) {
        A res = null;
        try {
            res = retClass.newInstance();
            Map<String, Field> bFields = getDeclaredAndInheritedFields(from.getClass(), false);
            Map<String, Field> aFields = getDeclaredAndInheritedFields(retClass, false);

            for(Field field : bFields.values()) {
                if (aFields.containsKey(field.getName())) {
                    Field aField = aFields.get(field.getName());

                    if (aField.getType().isAssignableFrom(field.getType())) {
                        field.setAccessible(true);
                        aField.setAccessible(true);

                        aField.set(res, field.get(from));
                    } else {
                        // типы не приводятся
                    }
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            //Logger.getLogger(Transparent.class.getName()).log(Level.SEVERE, null, ex);
        }
        return res;
    }

    @SuppressWarnings("unchecked")
    private Class<A> findType(Class<? extends Converter> clazz) {
        ParameterizedType genericSuperclass = (ParameterizedType) clazz.getGenericSuperclass();

        Type type = genericSuperclass.getActualTypeArguments()[0];

        if (type instanceof Class<?>) {
            return (Class<A>) type;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type rawType = parameterizedType.getRawType();
            if (rawType instanceof Class<?>) {
                return (Class<A>) rawType;
            }
        }

        return null;
    }

    public static Map<String, Field> getDeclaredAndInheritedFields(Class<?> type) {
        Map<String, Field> allFields = new HashMap<>();
        allFields.putAll(getValidFields(type.getDeclaredFields()));
        Class parent = type.getSuperclass();
        while (parent != null && parent != Object.class) {
            allFields.putAll(getValidFields(parent.getDeclaredFields()));
            parent = parent.getSuperclass();
        }
        return allFields;
    }

    public static Map<String, Field> getValidFields(Field[] fields) {
        Map<String, Field> allFields = new HashMap<>();
        for (Field field : fields) {
            if (!Modifier.isStatic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers())) {
                allFields.put(field.getName(), field);
            }
        }
        return allFields;
    }
}


Для конвертации из объекта класса BC в объект класса AC можно воспользоваться вот таким способом:

    public static class AC {
        private String one;

        private Date   two;

        public String getOne() {
            return one;
        }

        public void setOne(String one) {
            this.one = one;
        }

        public Date getTwo() {
            return two;
        }

        public void setTwo(Date two) {
            this.two = two;
        }
    }

    public static class BC {
        private String one;

        private Date   two;

        public BC(String one, Date two) {
            this.one = one;
            this.two = two;
        }

        public String getOne() {
            return one;
        }

        public void setOne(String one) {
            this.one = one;
        }

        public Date getTwo() {
            return two;
        }

        public void setTwo(Date two) {
            this.two = two;
        }
    }

    public static void main(String[] args) {
        BC bObj = new BC("Test", new Date());

        AC obj = new Converter<AC, BC>(){}.convert(bObj);

        boolean same = obj.getOne().equals(bObj.getOne()) && obj.getTwo().equals(bObj.getTwo());
        if (same) {
            System.out.print("Object the same");
        }
    }

НО: на каждую такую запись new Converter<AC, BC>(){} в коде будет создан новый анонимный класс, который унаследуется от Converter<AC, BC> .

Я бы такой код не использовал, лучше сделать метод который будет принимать либо два объекта один пустой другой полный, либо передвать полную информацию о классах в метод.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы