This is an automated email from the ASF dual-hosted git repository. ntimofeev pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit a8c2c79a01a72fcb56dafbbbd7f4844df4185480 Author: Nikita Timofeev <[email protected]> AuthorDate: Mon Aug 5 17:34:21 2019 +0300 CAY-2527 API to map Object[] result to POJO - simple Object[] to Pojo mapper --- .../org/apache/cayenne/reflect/PojoMapper.java | 93 ++++++++++++++++++++++ .../org/apache/cayenne/reflect/PojoMapperTest.java | 88 ++++++++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/PojoMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PojoMapper.java new file mode 100644 index 0000000..c0abf6e --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/PojoMapper.java @@ -0,0 +1,93 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.reflect; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.util.function.Function; + +import org.apache.cayenne.CayenneRuntimeException; + +/** + * Simple mapper of Object[] to POJO class. This class relies on field order, so use with caution. + * @param <T> type of object to produce + * @since 4.2 + */ +public class PojoMapper<T> implements Function<Object[], T> { + + private static MethodHandles.Lookup lookup = MethodHandles.lookup(); + + private final Class<T> type; + private final MethodHandle constructor; + private final MethodHandle[] setters; + + public PojoMapper(Class<T> type) { + this.type = type; + try { + this.constructor = lookup.unreflectConstructor(type.getConstructor()); + } catch (NoSuchMethodException | IllegalAccessException ex) { + throw new CayenneRuntimeException("No default constructor found for class '%s'.", type.getName()); + } + + Field[] declaredFields = type.getDeclaredFields(); + this.setters = new MethodHandle[declaredFields.length]; + int i = 0; + for(Field field : declaredFields) { + field.setAccessible(true); + try { + setters[i++] = lookup.unreflectSetter(field); + } catch (IllegalAccessException e) { + throw new CayenneRuntimeException("Field '%s'.'%s' is inaccessible.", e, type.getName(), field.getName()); + } + } + } + + private T newObject() { + try { + @SuppressWarnings("unchecked") + T object = (T)constructor.invoke(); + return object; + } catch (Throwable ex) { + throw new CayenneRuntimeException("Unable to instantiate %s.", ex, type.getName()); + } + } + + public T apply(Object[] data) { + if(data.length > setters.length) { + throw new CayenneRuntimeException("Unable to create '%s'. Values length (%d) > fields count (%d)" + , type.getName(), data.length, setters.length); + } + + T object = newObject(); + + for (int i = 0; i < data.length; i++) { + if (data[i] != null) { + try { + setters[i].invoke(object, data[i]); + } catch (Throwable ex) { + throw new CayenneRuntimeException("Unable to set field of %s.", ex, type.getName()); + } + } + } + + return object; + } +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/reflect/PojoMapperTest.java b/cayenne-server/src/test/java/org/apache/cayenne/reflect/PojoMapperTest.java new file mode 100644 index 0000000..a593a42 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/reflect/PojoMapperTest.java @@ -0,0 +1,88 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.reflect; + +import org.apache.cayenne.CayenneRuntimeException; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @since 4.2 + */ +public class PojoMapperTest { + + @Test + public void testObjectCreation() { + PojoMapper<C1> descriptor = new PojoMapper<>(C1.class); + + Object o = new Object(); + Object[] data = {"123", o, 42}; + C1 object = descriptor.apply(data); + assertEquals("123", object.a); + assertSame(o, object.b); + assertEquals(42, object.c); + } + + @Test(expected = CayenneRuntimeException.class) + public void testNonPublicClass() { + new PojoMapper<>(C2.class); + } + + @Test(expected = CayenneRuntimeException.class) + public void testNonPublicConstructor() { + new PojoMapper<>(C3.class); + } + + @Test(expected = CayenneRuntimeException.class) + public void testNonDefaultConstructor() { + new PojoMapper<>(C4.class); + } + + @Test(expected = CayenneRuntimeException.class) + public void testWrongArgumentCount() { + PojoMapper<C1> descriptor = new PojoMapper<>(C1.class); + + Object[] data = {"123", new Object(), 42, 32}; + descriptor.apply(data); + } + + public static class C1 { + String a; + Object b; + int c; + } + + private static class C2 { + int a; + } + + public static class C3 { + int a; + private C3() { + } + } + + public static class C4 { + int a; + public C4(int a) { + } + } +} \ No newline at end of file
