Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for JDK 16 record type #2477

Merged
merged 10 commits into from
Mar 23, 2022
42 changes: 42 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,16 @@
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<configuration>
<ignores>
<!-- https://github.com/mojohaus/animal-sniffer/issues/67 -->
<ignore>java.lang.invoke.MethodHandle</ignore>
</ignores>
</configuration>
</plugin>
</plugins>

<resources>
Expand Down Expand Up @@ -415,6 +425,38 @@
<excludedGroups />
</properties>
</profile>
<profile>
<id>pre16</id>
<activation>
<jdk>(,16)</jdk>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<testExcludes>
<testExclude>**/record_type/*.java</testExclude>
</testExcludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
<profile>
<id>16</id>
<activation>
<jdk>[16,)</jdk>
</activation>
<properties>
<maven.compiler.testTarget>16</maven.compiler.testTarget>
<maven.compiler.testSource>16</maven.compiler.testSource>
<excludedGroups>TestcontainersTests,RequireIllegalAccess</excludedGroups>
</properties>
</profile>
</profiles>

</project>
42 changes: 38 additions & 4 deletions src/main/java/org/apache/ibatis/reflection/Reflector.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2021 the original author or authors.
* Copyright 2009-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,9 @@
*/
package org.apache.ibatis.reflection;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
Expand Down Expand Up @@ -50,6 +53,7 @@
*/
public class Reflector {

private static final MethodHandle isRecordMethodHandle = getIsRecordMethodHandle();
private final Class<?> type;
private final String[] readablePropertyNames;
private final String[] writablePropertyNames;
Expand All @@ -65,9 +69,13 @@ public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
Method[] classMethods = getClassMethods(clazz);
addGetMethods(classMethods);
addSetMethods(classMethods);
addFields(clazz);
if (isRecord(type)) {
addRecordGetMethods(classMethods);
} else {
addGetMethods(classMethods);
addSetMethods(classMethods);
addFields(clazz);
}
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
Expand All @@ -78,6 +86,11 @@ public Reflector(Class<?> clazz) {
}
}

private void addRecordGetMethods(Method[] methods) {
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0)
.forEach(m -> addGetMethod(m.getName(), m, false));
}

private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
Expand Down Expand Up @@ -445,4 +458,25 @@ public boolean hasGetter(String propertyName) {
public String findPropertyName(String name) {
return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
}

/**
* Class.isRecord() alternative for Java 15 and older.
*/
private static boolean isRecord(Class<?> clazz) {
try {
return isRecordMethodHandle != null && (boolean)isRecordMethodHandle.invokeExact(clazz);
} catch (Throwable e) {
throw new ReflectionException("Failed to invoke 'Class.isRecord()'.", e);
}
}

private static MethodHandle getIsRecordMethodHandle() {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(boolean.class);
try {
return lookup.findVirtual(Class.class, "isRecord", mt);
} catch (NoSuchMethodException | IllegalAccessException e) {
return null;
}
}
}
32 changes: 32 additions & 0 deletions src/test/java/org/apache/ibatis/submitted/record_type/CreateDB.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--
-- Copyright 2009-2022 the original author or authors.
--
-- Licensed 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.
--

drop table prop if exists;

create table prop (
id int,
val varchar(20),
url varchar(32)
);

insert into prop (id, val, url) values (1, 'Val1', 'https://www.google.com');

create table item (
id int,
prop_id int
);

insert into item (id, prop_id) values (100, 1);
20 changes: 20 additions & 0 deletions src/test/java/org/apache/ibatis/submitted/record_type/Item.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright 2009-2022 the original author or authors.
*
* Licensed 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.ibatis.submitted.record_type;

public record Item(Integer id, Property property) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed 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.ibatis.submitted.record_type;

public record Property(int id, String value, String URL) {
public String value() {
// Differentiate between method call and field access
return value + "!";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed 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.ibatis.submitted.record_type;

import org.apache.ibatis.annotations.Arg;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

public interface RecordTypeMapper {

@Select("select id, val, url from prop where id = #{id}")
Property selectPropertyAutomapping(int id);

@Results(id = "propertyRM")
@Arg(column = "id", javaType = int.class)
@Arg(column = "val", javaType = String.class)
@Arg(column = "url", javaType = String.class)
@Select("select val, id, url from prop where id = #{id}")
Property selectProperty(int id);

@Insert("insert into prop (id, val, url) values (#{id}, #{value}, #{URL})")
int insertProperty(Property property);

@Arg(id = true, column = "id", javaType = Integer.class)
@Arg(javaType = Property.class, resultMap = "propertyRM", columnPrefix = "p_")
@Select({
"select i.id, p.id p_id, p.val p_val, p.url p_url",
"from item i left join prop p on p.id = i.prop_id",
"where i.id = #{id}" })
Item selectItem(Integer id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed 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.ibatis.submitted.record_type;

import static org.junit.jupiter.api.Assertions.*;

import java.io.Reader;

import org.apache.ibatis.BaseDataTest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class RecordTypeTest {

private static SqlSessionFactory sqlSessionFactory;

@BeforeAll
static void setUp() throws Exception {
// create a SqlSessionFactory
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/record_type/mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/submitted/record_type/CreateDB.sql");
}

@Test
void testSelectRecord() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
Property prop = mapper.selectProperty(1);
assertEquals("Val1!", prop.value());
assertEquals("https://www.google.com", prop.URL());
}
}

@Test
void testSelectRecordAutomapping() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
Property prop = mapper.selectPropertyAutomapping(1);
assertEquals("Val1!", prop.value());
assertEquals("https://www.google.com", prop.URL());
}
}

@Test
void testInsertRecord() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
assertEquals(1, mapper.insertProperty(new Property(2, "Val2", "https://mybatis.org")));
sqlSession.commit();
}
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
Property prop = mapper.selectProperty(2);
assertEquals("Val2!!", prop.value());
assertEquals("https://mybatis.org", prop.URL());
}
}

@Test
void testSelectNestedRecord() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
RecordTypeMapper mapper = sqlSession.getMapper(RecordTypeMapper.class);
Item item = mapper.selectItem(100);
assertEquals(Integer.valueOf(100), item.id());
assertEquals(new Property(1, "Val1", "https://www.google.com"), item.property());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--

Copyright 2009-2022 the original author or authors.

Licensed 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.

-->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value="" />
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url"
value="jdbc:hsqldb:mem:record_type" />
<property name="username" value="sa" />
</dataSource>
</environment>
</environments>

<mappers>
<mapper
class="org.apache.ibatis.submitted.record_type.RecordTypeMapper" />
</mappers>

</configuration>