M
M
Mar4elo2013-09-03 09:22:03
Java
Mar4elo, 2013-09-03 09:22:03

Universal transparent converter?

There was a need to create a transparent converter for complex objects of the same type in structure, but different in namespace. Initially, there are two XSDs with descriptions of different types. JAXB handles them perfectly and creates stubs. Further, according to the business logic, it is necessary to convert the object from one namespace to another. When there are not many types of objects, it is not very tedious to make an individual converter, but as the data structure grows, this approach is no longer effective. Therefore, the idea arose to write one universal converter with transparent data transfer.
I want to make the converter call in the program code look something like this:

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

In ten minutes, I threw in the skeleton of the method:
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;
    }
}

Everything would be fine, but I stumbled on the line "_methodName2 = res.getClass().getMethod("s...", the object "res" is not initiated, and this is logical, because "A res = null;".
Options "new A ()", "A.class", "A.getClass().newInstance()" are not working.
Additionally, I tried this option:
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;
        }
    }

Initialization, respectively, should be carried out as “A res = (new _init()). newInstance()”, but this option also does not work, since it is not allowed in a static context.
Actually, the question is for the Java guru - how can you initiate a res object in such a static method?
Update as of 09/04/2013
I analyzed all the material, and it seems that it will not be possible to find out the return type at runtime, and as a result I threw in the following code. The code is not yet final, it is planned to handle recursive processing of complex types.
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;
    }
}

Answer the question

In order to leave comments, you need to log in

5 answer(s)
M
mayorovp, 2013-09-03
@mayorovp

I wrote to you last time - take Class as an argument to the convert method. Apparently, this is the only way.

Z
zvorygin, 2013-09-03
@zvorygin

In java, "templates" or how to properly say generics, exist only at the time of compilation. There are none in runtime, and when the convert method is called in runtime, absolutely nothing is known about A - so the only possible and correct option is to take Class as the method argument. You can accept a class of type Class - so the typing will be preserved.

S
serso, 2013-09-03
@serso

I don’t understand anything - in the _init class, the type field is not initialized in any way. There must be a constructor with a class parameter (_init(Class type)), in which the field is set. Then everything should work.
BUT, it is impossible to create a generic object without passing the class (or other information about the class (name, package name, etc.)) - it is impossible. That is, in addition to passing the class to the _init constructor, you also need to pass it to the static method.
PS To write in this style, I think it's disrespectful to the reader of the code (did you read java code conventions?)
PPS Initialized, not initiated

B
barker, 2013-09-03
@barker

In addition to the previous answer, yes, something like this should work:

    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;
        }
    }

I don’t know if this is an example ready for you, but it should work at least, you have to dance from it.

T
tonyvelichko, 2013-09-04
@tonyvelichko

A slightly incorrect statement that generics exist only at the time of compilation, more precisely, not in all cases. For example, when inheriting from a class with generics or implementing a generic interface, we have the opportunity to get complete information about the generics themselves at runtime. That is, our converter class will look something like this:

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;
    }
}

To convert from an object of class BC to an object of class AC, you can use this method:

    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");
        }
    }

BUT: for each such new Converter<AC, BC>(){} entry in the code, a new anonymous class will be created that will inherit from Converter<AC, BC> .
I would not use such code, it is better to make a method that will either accept two objects, one empty and the other full, or transfer full information about classes to the method.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question