You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2019/08/26 12:50:56 UTC

[cayenne] 02/05: CAY-2527 API to map Object[] result to POJO - simple Object[] to Pojo mapper

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 <st...@gmail.com>
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