[#1837] Add support for @java.beans.ConstructorProperties when

fetching into immutable POJOs
This commit is contained in:
Lukas Eder 2012-09-22 13:44:48 +02:00
parent 95a0f0e6b5
commit b1ff568f41
10 changed files with 527 additions and 17 deletions

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
import java.beans.ConstructorProperties;
import java.util.Date;
/**
* @author Lukas Eder
*/
public class ImmutableAuthorWithConstructorProperties {
private final int id;
private final String firstName;
private final String lastName;
private final Date dateOfBirth;
// Check if setAccessible is called correctly
@ConstructorProperties({ "firstName", "lastName", "id", "dateOfBirth" })
ImmutableAuthorWithConstructorProperties(String firstName, String lastName, int id, Date dateOfBirth) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.dateOfBirth = dateOfBirth;
}
// This should never be called
@SuppressWarnings("unused")
ImmutableAuthorWithConstructorProperties(int ID, String firstName, String lastName) {
throw new RuntimeException();
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
}

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
import java.beans.ConstructorProperties;
import java.util.Date;
import javax.persistence.Column;
/**
* @author Lukas Eder
*/
public class ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations {
private final String f1;
private final String f2;
private final int f3;
private final Date f4;
// Check if setAccessible is called correctly
@ConstructorProperties({ "f1", "f2", "f3", "f4" })
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations(String firstName, String lastName, int id,
Date dateOfBirth) {
this.f1 = firstName;
this.f2 = lastName;
this.f3 = id;
this.f4 = dateOfBirth;
}
// This should never be called
@SuppressWarnings("unused")
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations(int ID, String firstName, String lastName) {
throw new RuntimeException();
}
@Column(name = "FIRST_NAME")
public String getF1() {
return f1;
}
@Column(name = "LAST_NAME")
public String getF2() {
return f2;
}
@Column(name = "ID")
public int getF3() {
return f3;
}
@Column(name = "DATE_OF_BIRTH")
public Date getF4() {
return f4;
}
}

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
import java.beans.ConstructorProperties;
import java.util.Date;
import javax.persistence.Column;
/**
* @author Lukas Eder
*/
public class ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields {
@Column(name = "FIRST_NAME")
public final String f1;
@Column(name = "LAST_NAME")
public final String f2;
@Column(name = "ID")
public final int f3;
@Column(name = "DATE_OF_BIRTH")
public final Date f4;
// Check if setAccessible is called correctly
@ConstructorProperties({ "f1", "f2", "f3", "f4" })
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields(String firstName, String lastName, int id,
Date dateOfBirth) {
this.f1 = firstName;
this.f2 = lastName;
this.f3 = id;
this.f4 = dateOfBirth;
}
// This should never be called
@SuppressWarnings("unused")
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields(int ID, String firstName, String lastName) {
throw new RuntimeException();
}
}

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com
* All rights reserved.
*
* This software is licensed to you under the Apache License, Version 2.0
* (the "License"); You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* . Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* . Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* . Neither the name "jOOQ" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.jooq.test._;
import java.beans.ConstructorProperties;
import java.util.Date;
/**
* @author Lukas Eder
*/
public class ImmutableAuthorWithConstructorPropertiesAndPublicFields {
public final String firstName;
public final String lastName;
public final int id;
public final Date dateOfBirth;
// Check if setAccessible is called correctly
@ConstructorProperties({ "firstName", "lastName", "id", "dateOfBirth" })
ImmutableAuthorWithConstructorPropertiesAndPublicFields(String firstName, String lastName, int id, Date dateOfBirth) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
this.dateOfBirth = dateOfBirth;
}
// This should never be called
@SuppressWarnings("unused")
ImmutableAuthorWithConstructorPropertiesAndPublicFields(int ID, String firstName, String lastName) {
throw new RuntimeException();
}
}

View File

@ -91,6 +91,10 @@ import org.jooq.test._.FinalWithoutAnnotations;
import org.jooq.test._.IBookWithAnnotations;
import org.jooq.test._.IBookWithoutAnnotations;
import org.jooq.test._.ImmutableAuthor;
import org.jooq.test._.ImmutableAuthorWithConstructorProperties;
import org.jooq.test._.ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations;
import org.jooq.test._.ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields;
import org.jooq.test._.ImmutableAuthorWithConstructorPropertiesAndPublicFields;
import org.jooq.test._.StaticWithAnnotations;
import org.jooq.test._.StaticWithoutAnnotations;
@ -981,6 +985,59 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
catch (MappingException expected) {}
}
@Test
public void testReflectionWithImmutablesAndConstructorProperties() throws Exception {
// TODO [#791] Fix test data and have all upper case columns everywhere
switch (getDialect()) {
case ASE:
case CUBRID:
case INGRES:
case POSTGRES:
log.info("SKIPPING", "fetchInto() tests");
return;
}
Record author =
create().select(
TAuthor_LAST_NAME(),
TAuthor_FIRST_NAME(),
TAuthor_ID(),
TAuthor_YEAR_OF_BIRTH())
.from(TAuthor())
.where(TAuthor_ID().equal(1))
.fetchOne();
ImmutableAuthorWithConstructorProperties into1 =
author.into(ImmutableAuthorWithConstructorProperties.class);
assertNull(into1.getDateOfBirth());
assertEquals(1, into1.getId());
assertEquals(AUTHOR_FIRST_NAMES.get(0), into1.getFirstName());
assertEquals(AUTHOR_LAST_NAMES.get(0), into1.getLastName());
ImmutableAuthorWithConstructorPropertiesAndPublicFields into2 =
author.into(ImmutableAuthorWithConstructorPropertiesAndPublicFields.class);
assertNull(into2.dateOfBirth);
assertEquals(1, into2.id);
assertEquals(AUTHOR_FIRST_NAMES.get(0), into2.firstName);
assertEquals(AUTHOR_LAST_NAMES.get(0), into2.lastName);
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations into3 =
author.into(ImmutableAuthorWithConstructorPropertiesAndJPAAnnotations.class);
assertNull(into3.getF4());
assertEquals(1, into3.getF3());
assertEquals(AUTHOR_FIRST_NAMES.get(0), into3.getF1());
assertEquals(AUTHOR_LAST_NAMES.get(0), into3.getF2());
ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields into4 =
author.into(ImmutableAuthorWithConstructorPropertiesAndJPAAnnotationsAndPublicFields.class);
assertNull(into4.f4);
assertEquals(1, into4.f3);
assertEquals(AUTHOR_FIRST_NAMES.get(0), into4.f1);
assertEquals(AUTHOR_LAST_NAMES.get(0), into4.f2);
}
@Test
public void testFetchIntoTableRecords() throws Exception {
jOOQAbstractTest.reset = false;

View File

@ -1119,6 +1119,11 @@ public abstract class jOOQAbstractTest<
new FetchTests(this).testReflectionWithImmutables();
}
@Test
public void testReflectionWithImmutablesAndConstructorProperties() throws Exception {
new FetchTests(this).testReflectionWithImmutablesAndConstructorProperties();
}
@Test
public void testFetchIntoTableRecords() throws Exception {
new FetchTests(this).testFetchIntoTableRecords();

View File

@ -4447,7 +4447,8 @@ notExists(create.selectOne().from(BOOK)
(DATE '2010-01-01', DATE '2010-01-03') OVERLAPS (DATE '2010-01-02' DATE '2010-01-04')
-- INTERVAL data types are also supported. This is equivalent to the above
(DATE '2010-01-01', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND)) OVERLAPS (DATE '2010-01-02', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND))]]></sql>
(DATE '2010-01-01', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND)) OVERLAPS
(DATE '2010-01-02', CAST('+2 00:00:00' AS INTERVAL DAY TO SECOND))]]></sql>
<h3>The OVERLAPS predicate in jOOQ</h3>
<p>
@ -5494,9 +5495,18 @@ public class MyBook1 {
// The various "into()" methods allow for fetching records into your custom POJOs:
MyBook1 myBook = create.select().from(BOOK).fetchAny().into(MyBook1.class);
List<MyBook1> myBooks = create.select().from(BOOK).fetch().into(MyBook1.class);
List<MyBook1> myBooks = create.select().from(BOOK).fetchInto(MyBook1.class);
List<MyBook1> myBooks = create.select().from(BOOK).fetchInto(MyBook1.class);]]></java>
<p>
Please refer to the <reference class="org.jooq.Record" anchor="#into(java.lang.Class)" title="Record.into()"/> Javadoc for more details.
</p>
// An "immutable" POJO class
<h3>Using "immutable" POJOs</h3>
<p>
If jOOQ does not find any default constructor, columns are mapped to the "best-matching" constructor. This allows for using "immutable" POJOs with jOOQ. An example illustrates this:
</p>
<java><![CDATA[// An "immutable" POJO class
public class MyBook2 {
public final int id;
public final String title;
@ -5510,7 +5520,26 @@ public class MyBook2 {
// With "immutable" POJO classes, there must be an exact match between projected fields and available constructors:
MyBook2 myBook = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchAny().into(MyBook2.class);
List<MyBook2> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetch().into(MyBook2.class);
List<MyBook2> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchInto(MyBook2.class);]]></java>
List<MyBook2> myBooks = create.select(BOOK.ID, BOOK.TITLE).from(BOOK).fetchInto(MyBook2.class);
// An "immutable" POJO class with a java.beans.ConstructorProperties annotation
public class MyBook3 {
public final String title;
public final int id;
@ConstructorProperties({ "title", "id"})
public MyBook2(String title, int id) {
this.title = title;
this.id = id;
}
}
// With annotated "immutable" POJO classes, there doesn't need to be an exact match between fields and constructor arguments.
// In the below cases, only BOOK.ID is really set onto the POJO, BOOK.TITLE remains null and BOOK.AUTHOR_ID is ignored
MyBook3 myBook = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetchAny().into(MyBook3.class);
List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetch().into(MyBook3.class);
List<MyBook3> myBooks = create.select(BOOK.ID, BOOK.AUTHOR_ID).from(BOOK).fetchInto(MyBook3.class);
]]></java>
<p>
Please refer to the <reference class="org.jooq.Record" anchor="#into(java.lang.Class)" title="Record.into()"/> Javadoc for more details.

View File

@ -36,6 +36,7 @@
package org.jooq;
import java.beans.ConstructorProperties;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
@ -965,7 +966,8 @@ public interface Record extends FieldProvider, Store<Object> {
* that might have occurred
* @see Convert#convert(Object, Converter)
*/
<T, U> U getValue(Field<T> field, Converter<? super T, U> converter) throws IllegalArgumentException, DataTypeException;
<T, U> U getValue(Field<T> field, Converter<? super T, U> converter) throws IllegalArgumentException,
DataTypeException;
/**
* Get a converted value from this record, providing a field.
@ -983,8 +985,8 @@ public interface Record extends FieldProvider, Store<Object> {
* that might have occurred
* @see Convert#convert(Object, Converter)
*/
<T, U> U getValue(Field<T> field, Converter<? super T, U> converter, U defaultValue) throws IllegalArgumentException,
DataTypeException;
<T, U> U getValue(Field<T> field, Converter<? super T, U> converter, U defaultValue)
throws IllegalArgumentException, DataTypeException;
/**
* Get a converted value from this Record, providing a field name.
@ -1175,6 +1177,23 @@ public interface Record extends FieldProvider, Store<Object> {
* <li>Public non-final instance member field <code>MY_field</code></li>
* <li>Public non-final instance member field <code>myField</code></li>
* </ul>
* <h3>If no default constructor is available, but at least one constructor
* annotated with <code>ConstructorProperties</code> is available, that one
* is used</h3>
* <ul>
* <li>The standard JavaBeans {@link ConstructorProperties} annotation is
* used to match constructor arguments against POJO members or getters.</li>
* <li>If those POJO members or getters have JPA annotations, those will be
* used according to the aforementioned rules, in order to map
* <code>Record</code> values onto constructor arguments.</li>
* <li>If those POJO members or getters don't have JPA annotations, the
* aforementioned naming conventions will be used, in order to map
* <code>Record</code> values onto constructor arguments.</li>
* <li>When several annotated constructors are found, the first one is
* chosen (as reported by {@link Class#getDeclaredConstructors()}</li>
* <li>When invoking the annotated constructor, values are converted onto
* constructor argument types</li>
* </ul>
* <h3>If no default constructor is available, but at least one "matching"
* constructor is available, that one is used</h3>
* <ul>
@ -1182,7 +1201,8 @@ public interface Record extends FieldProvider, Store<Object> {
* this record holds fields</li>
* <li>When several "matching" constructors are found, the first one is
* chosen (as reported by {@link Class#getDeclaredConstructors()}</li>
* <li>When invoking the "matching"
* <li>When invoking the "matching" constructor, values are converted onto
* constructor argument types</li>
* </ul>
* <h3>If the supplied type is an interface or an abstract class</h3>
* Abstract types are instanciated using Java reflection {@link Proxy}
@ -1293,12 +1313,11 @@ public interface Record extends FieldProvider, Store<Object> {
* <ul>
* <li>primitive types are supported.</li>
* </ul>
* <h3>General notes</h3>
* The resulting record will have its internal "changed" flags set to true
* for all values. This means that {@link UpdatableRecord#store()} will
* perform an <code>INSERT</code> statement. If you wish to store the record
* using an <code>UPDATE</code> statement, use
* {@link Factory#executeUpdate(UpdatableRecord)} instead.
* <h3>General notes</h3> The resulting record will have its internal
* "changed" flags set to true for all values. This means that
* {@link UpdatableRecord#store()} will perform an <code>INSERT</code>
* statement. If you wish to store the record using an <code>UPDATE</code>
* statement, use {@link Factory#executeUpdate(UpdatableRecord)} instead.
*
* @param source The source object to copy data from
* @throws MappingException wrapping any reflection exception that might

View File

@ -42,8 +42,11 @@ import static org.jooq.impl.Util.getAnnotatedSetters;
import static org.jooq.impl.Util.getMatchingGetter;
import static org.jooq.impl.Util.getMatchingMembers;
import static org.jooq.impl.Util.getMatchingSetters;
import static org.jooq.impl.Util.getPropertyName;
import static org.jooq.impl.Util.hasColumnAnnotations;
import static org.jooq.tools.reflect.Reflect.accessible;
import java.beans.ConstructorProperties;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@ -54,6 +57,7 @@ import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@ -715,7 +719,7 @@ abstract class AbstractRecord extends AbstractStore<Object> implements Record {
// [#1340] Allow for using non-public default constructors
else {
result = Reflect.accessible(type.getDeclaredConstructor()).newInstance();
result = accessible(type.getDeclaredConstructor()).newInstance();
}
return intoMutablePOJO(type, result);
@ -734,19 +738,83 @@ abstract class AbstractRecord extends AbstractStore<Object> implements Record {
*/
@SuppressWarnings("unchecked")
private final <E> E intoImmutablePOJO(Class<? extends E> type) throws Exception {
for (Constructor<E> constructor : (Constructor<E>[]) type.getDeclaredConstructors()) {
Constructor<E>[] constructors = (Constructor<E>[]) type.getDeclaredConstructors();
// [#1837] If any java.beans.ConstructorProperties annotations are
// present use those rather than matching constructors by the number of
// arguments
for (Constructor<E> constructor : constructors) {
ConstructorProperties properties = constructor.getAnnotation(ConstructorProperties.class);
if (properties != null) {
return intoImmutablePOJO(type, constructor, properties);
}
}
// Without ConstructorProperties, match constructors by matching
// argument length
for (Constructor<E> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
// Match the first constructor by parameter length
if (parameterTypes.length == getFields().size()) {
Object[] converted = Util.convert(parameterTypes, intoArray());
return Reflect.accessible(constructor).newInstance(converted);
return accessible(constructor).newInstance(converted);
}
}
throw new MappingException("No matching constructor found on type " + type + " for record " + this);
}
/**
* Create an immutable POJO given a constructor and its associated JavaBeans
* {@link ConstructorProperties}
*/
private final <E> E intoImmutablePOJO(Class<? extends E> type, Constructor<E> constructor, ConstructorProperties properties) throws Exception {
boolean useAnnotations = hasColumnAnnotations(type);
List<String> propertyNames = Arrays.asList(properties.value());
Class<?>[] parameterTypes = constructor.getParameterTypes();
Object[] parameterValues = new Object[parameterTypes.length];
for (Field<?> field : getFields()) {
List<java.lang.reflect.Field> members;
Method method;
// Annotations are available and present
if (useAnnotations) {
members = getAnnotatedMembers(type, field.getName());
method = getAnnotatedGetter(type, field.getName());
}
// No annotations are present
else {
members = getMatchingMembers(type, field.getName());
method = getMatchingGetter(type, field.getName());
}
for (java.lang.reflect.Field member : members) {
int index = propertyNames.indexOf(member.getName());
if (index >= 0) {
parameterValues[index] = getValue(field);
}
}
if (method != null) {
String name = getPropertyName(method.getName());
int index = propertyNames.indexOf(name);
if (index >= 0) {
parameterValues[index] = getValue(field);
}
}
}
Object[] converted = Util.convert(parameterTypes, parameterValues);
return accessible(constructor).newInstance(converted);
}
/**
* Convert this record into a "mutable" POJO (non-final fields or setters
* available)

View File

@ -988,6 +988,25 @@ final class Util {
return result;
}
/**
* Get a property name associated with a getter/setter method name.
*/
static String getPropertyName(String methodName) {
String name = methodName;
if (name.startsWith("is") && name.length() > 2) {
name = name.substring(2, 3).toLowerCase() + name.substring(3);
}
else if (name.startsWith("get") && name.length() > 3) {
name = name.substring(3, 4).toLowerCase() + name.substring(4);
}
else if (name.startsWith("set") && name.length() > 3) {
name = name.substring(3, 4).toLowerCase() + name.substring(4);
}
return name;
}
/**
* Type-safely copy a value from one record to another
*/