Skip to content

Commit 948e217

Browse files
committed
Add PojoEncoder
This class creates and fills a new object instance with stream data and sends the result to the given object receiver.
1 parent 036176f commit 948e217

File tree

2 files changed

+591
-0
lines changed

2 files changed

+591
-0
lines changed
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
package org.culturegraph.mf.stream.converter;
2+
3+
import java.beans.Introspector;
4+
import java.beans.PropertyEditor;
5+
import java.beans.PropertyEditorManager;
6+
import java.lang.reflect.Field;
7+
import java.lang.reflect.InvocationTargetException;
8+
import java.lang.reflect.Method;
9+
import java.lang.reflect.Modifier;
10+
import java.lang.reflect.ParameterizedType;
11+
import java.lang.reflect.Type;
12+
import java.util.ArrayDeque;
13+
import java.util.ArrayList;
14+
import java.util.Deque;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
19+
import org.culturegraph.mf.exceptions.MetafactureException;
20+
import org.culturegraph.mf.framework.DefaultStreamPipe;
21+
import org.culturegraph.mf.framework.ObjectReceiver;
22+
import org.culturegraph.mf.framework.StreamReceiver;
23+
import org.culturegraph.mf.framework.annotations.Description;
24+
import org.culturegraph.mf.framework.annotations.In;
25+
import org.culturegraph.mf.framework.annotations.Out;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
/**
30+
* This class creates and fills a new object instance with stream data and sends
31+
* the result to the given object receiver.
32+
*
33+
* @author Thomas Seidel
34+
*
35+
* @param <T>
36+
* The type of the object to create.
37+
*
38+
*/
39+
@Description("Creates a pojo (Plain Old Java Object) based on a record containing the member values")
40+
@In(StreamReceiver.class)
41+
@Out(Object.class)
42+
public class PojoEncoder<T> extends DefaultStreamPipe<ObjectReceiver<T>> {
43+
44+
private static final Logger LOG = LoggerFactory
45+
.getLogger(PojoEncoder.class);
46+
47+
private static class ValueType {
48+
private final Class<?> rawClass;
49+
private Class<?> elementClass;
50+
51+
public ValueType(final Class<?> clazz) {
52+
rawClass = clazz;
53+
}
54+
55+
public ValueType(final Class<?> clazz, final Type type) {
56+
rawClass = clazz;
57+
if (type instanceof ParameterizedType) {
58+
elementClass = (Class<?>) (((ParameterizedType) type)
59+
.getActualTypeArguments()[0]);
60+
}
61+
}
62+
63+
public Class<?> getRawClass() {
64+
return rawClass;
65+
}
66+
67+
public Class<?> getElementClass() {
68+
return elementClass;
69+
}
70+
71+
}
72+
73+
/**
74+
* A ValueSetter sets a pojos's member, via setter method or field access.
75+
* Used by {@link ComplexTypeEncoder} only
76+
*
77+
* @author Thomas Seidel
78+
*
79+
*/
80+
private interface ValueSetter {
81+
82+
void setValue(final Object object, final Object value);
83+
84+
String getName();
85+
86+
ValueType getValueType();
87+
88+
}
89+
90+
private static class MethodValueSetter implements ValueSetter {
91+
92+
private static final String METHOD_PREFIX = "set";
93+
94+
private final String name;
95+
private final Method method;
96+
97+
public static boolean supportsMethod(final Method m) {
98+
return Modifier.isPublic(m.getModifiers())
99+
&& m.getName().length() > METHOD_PREFIX.length()
100+
&& m.getName().startsWith(METHOD_PREFIX)
101+
&& m.getParameterTypes().length == 1;
102+
}
103+
104+
public MethodValueSetter(final Method method) {
105+
assert supportsMethod(method);
106+
this.method = method;
107+
// remove prefix then lower case first character
108+
name = Introspector.decapitalize(method.getName().substring(
109+
METHOD_PREFIX.length()));
110+
}
111+
112+
@Override
113+
public void setValue(final Object object, final Object value) {
114+
try {
115+
method.invoke(object, value);
116+
} catch (final IllegalArgumentException e) {
117+
throw new MetafactureException(
118+
"The given object don't have a method named "
119+
+ method.getName(), e);
120+
} catch (final IllegalAccessException e) {
121+
throw new MetafactureException("Can't access the method named "
122+
+ method.getName(), e);
123+
} catch (final InvocationTargetException e) {
124+
throw new MetafactureException("Invoking the method named "
125+
+ method.getName() + " throws an excpetion", e);
126+
}
127+
}
128+
129+
@Override
130+
public String getName() {
131+
return name;
132+
}
133+
134+
@Override
135+
public ValueType getValueType() {
136+
return new ValueType(method.getParameterTypes()[0],
137+
method.getGenericParameterTypes()[0]);
138+
}
139+
140+
}
141+
142+
private static class FieldValueSetter implements ValueSetter {
143+
144+
final Field field;
145+
146+
public static boolean supportsField(final Field f) {
147+
return Modifier.isPublic(f.getModifiers());
148+
}
149+
150+
public FieldValueSetter(final Field field) {
151+
assert supportsField(field);
152+
this.field = field;
153+
}
154+
155+
@Override
156+
public void setValue(final Object object, final Object value) {
157+
try {
158+
field.set(object, value);
159+
} catch (final IllegalArgumentException e) {
160+
throw new MetafactureException(
161+
"The given object don't have a field named "
162+
+ field.getName(), e);
163+
} catch (final IllegalAccessException e) {
164+
throw new MetafactureException("Can't access the field named "
165+
+ field.getName(), e);
166+
}
167+
}
168+
169+
@Override
170+
public String getName() {
171+
return field.getName();
172+
}
173+
174+
@Override
175+
public ValueType getValueType() {
176+
return new ValueType(field.getType(), field.getGenericType());
177+
}
178+
179+
}
180+
181+
/**
182+
* A TypeEncoder encodes a metafacture stream to a new object
183+
*
184+
* @author Thomas Seidel
185+
*
186+
*/
187+
private interface TypeEncoder {
188+
189+
void setValue(String name, Object value);
190+
191+
ValueType getValueType(String name);
192+
193+
Object getInstance();
194+
195+
}
196+
197+
private final TypeEncoderFactory typeEncoderFactory = new TypeEncoderFactory();
198+
199+
private static class TypeEncoderFactory {
200+
private final Map<Class<?>, TypeEncoder> typeEncoders = new HashMap<Class<?>, TypeEncoder>();
201+
202+
private TypeEncoder create(final ValueType valueType) {
203+
final TypeEncoder typeEncoder;
204+
// if (typeEncoders.containsKey(clazz)) {
205+
// return typeEncoders.get(clazz);
206+
// }
207+
final Class<?> rawClass = valueType.getRawClass();
208+
if (ListTypeEncoder.supportsType(rawClass)) {
209+
typeEncoder = new ListTypeEncoder(valueType);
210+
} else if (ComplexTypeEncoder.supportsType(rawClass)) {
211+
typeEncoder = new ComplexTypeEncoder(rawClass);
212+
} else {
213+
throw new MetafactureException("Can't encode type " + rawClass);
214+
}
215+
typeEncoders.put(rawClass, typeEncoder);
216+
LOG.debug("typeEncoders: {})", typeEncoders);
217+
return typeEncoder;
218+
}
219+
}
220+
221+
private static class ComplexTypeEncoder implements TypeEncoder {
222+
223+
private final Object instance;
224+
private final Map<String, ValueSetter> valueSetters;
225+
226+
public ComplexTypeEncoder(final Class<?> clazz) {
227+
assert supportsType(clazz);
228+
instance = createInstance(clazz);
229+
valueSetters = new HashMap<String, ValueSetter>();
230+
// get all public fields of this class and all super classes
231+
final Field[] fields = clazz.getDeclaredFields();
232+
for (final Field field : fields) {
233+
if (FieldValueSetter.supportsField(field)) {
234+
final FieldValueSetter fieldValueSetter = new FieldValueSetter(
235+
field);
236+
valueSetters.put(fieldValueSetter.getName(),
237+
fieldValueSetter);
238+
}
239+
}
240+
// get all valid public methods of this class and all super classes
241+
final Method[] methods = clazz.getDeclaredMethods();
242+
for (final Method method : methods) {
243+
if (MethodValueSetter.supportsMethod(method)) {
244+
final MethodValueSetter methodValueSetter = new MethodValueSetter(
245+
method);
246+
valueSetters.put(methodValueSetter.getName(),
247+
methodValueSetter);
248+
}
249+
}
250+
}
251+
252+
public static boolean supportsType(final Class<?> clazz) {
253+
return !clazz.isPrimitive() && !clazz.equals(String.class)
254+
&& !ListTypeEncoder.supportsType(clazz);
255+
}
256+
257+
@Override
258+
public void setValue(final String name, final Object value) {
259+
final ValueSetter valueSetter = valueSetters.get(name);
260+
valueSetter.setValue(instance, value);
261+
}
262+
263+
@Override
264+
public ValueType getValueType(final String name) {
265+
final ValueSetter valueSetter = valueSetters.get(name);
266+
return valueSetter.getValueType();
267+
}
268+
269+
@Override
270+
public Object getInstance() {
271+
return instance;
272+
}
273+
274+
}
275+
276+
private static class ListTypeEncoder implements TypeEncoder {
277+
278+
private final ValueType valueType;
279+
private final List<Object> objects;
280+
281+
public ListTypeEncoder(final ValueType valueType) {
282+
this.valueType = valueType;
283+
objects = new ArrayList<Object>();
284+
}
285+
286+
public static boolean supportsType(final Class<?> clazz) {
287+
return List.class.isAssignableFrom(clazz);
288+
}
289+
290+
@Override
291+
public void setValue(final String name, final Object value) {
292+
objects.add(value);
293+
}
294+
295+
@Override
296+
public ValueType getValueType(final String name) {
297+
return new ValueType(valueType.getElementClass());
298+
}
299+
300+
@Override
301+
public Object getInstance() {
302+
return objects;
303+
}
304+
305+
}
306+
307+
private static Object createObjectFromString(final String value,
308+
final Class<?> targetType) {
309+
final PropertyEditor propertyEditor = PropertyEditorManager
310+
.findEditor(targetType);
311+
propertyEditor.setAsText(value);
312+
return propertyEditor.getValue();
313+
}
314+
315+
static {
316+
// Initialize the property manager to map the primitive data types to
317+
// the corresponding object based types, e.g. int to Integer
318+
PropertyEditorManager.registerEditor(Boolean.class,
319+
PropertyEditorManager.findEditor(boolean.class).getClass());
320+
PropertyEditorManager.registerEditor(Integer.class,
321+
PropertyEditorManager.findEditor(int.class).getClass());
322+
PropertyEditorManager.registerEditor(Long.class, PropertyEditorManager
323+
.findEditor(long.class).getClass());
324+
}
325+
326+
private static Object createInstance(final Class<?> clazz) {
327+
Object object;
328+
try {
329+
object = clazz.newInstance();
330+
} catch (final Exception e) {
331+
throw new MetafactureException(
332+
"Can't instantiate object of class: " + clazz, e);
333+
}
334+
return object;
335+
}
336+
337+
private final Class<T> pojoClass;
338+
private final Deque<TypeEncoder> typeEncoderStack;
339+
340+
public PojoEncoder(final Class<T> pojoClass) {
341+
this.pojoClass = pojoClass;
342+
typeEncoderStack = new ArrayDeque<TypeEncoder>();
343+
}
344+
345+
@Override
346+
public void startRecord(final String identifier) {
347+
typeEncoderStack.clear();
348+
typeEncoderStack.push(new ComplexTypeEncoder(pojoClass));
349+
}
350+
351+
@Override
352+
public void startEntity(final String name) {
353+
final TypeEncoder currentTypeEncoder = typeEncoderStack.peek();
354+
final ValueType newType = currentTypeEncoder.getValueType(name);
355+
final TypeEncoder newTypeEncoder = typeEncoderFactory.create(newType);
356+
currentTypeEncoder.setValue(name, newTypeEncoder.getInstance());
357+
typeEncoderStack.push(newTypeEncoder);
358+
}
359+
360+
@Override
361+
public void literal(final String name, final String value) {
362+
final TypeEncoder currentTypeEncoder = typeEncoderStack.peek();
363+
final Class<?> targetType = currentTypeEncoder.getValueType(name)
364+
.getRawClass();
365+
currentTypeEncoder.setValue(name,
366+
createObjectFromString(value, targetType));
367+
}
368+
369+
@Override
370+
public void endEntity() {
371+
typeEncoderStack.pop();
372+
}
373+
374+
@SuppressWarnings("unchecked")
375+
@Override
376+
public void endRecord() {
377+
assert typeEncoderStack.size() == 1;
378+
final ObjectReceiver<T> objectReceiver = getReceiver();
379+
objectReceiver.process((T) typeEncoderStack.peek().getInstance());
380+
typeEncoderStack.clear();
381+
}
382+
383+
@Override
384+
public void onCloseStream() {
385+
typeEncoderStack.clear();
386+
}
387+
388+
@Override
389+
public void onResetStream() {
390+
typeEncoderStack.clear();
391+
}
392+
393+
}

0 commit comments

Comments
 (0)