Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '11'
java-version: '17'
distribution: 'temurin'
cache: maven
- uses: s4u/maven-settings-action@v2.6.0
Expand Down
2 changes: 1 addition & 1 deletion .run/All Tests.run.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="JUnit" factoryName="JUnit">
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
<option name="ALTERNATIVE_JRE_PATH" value="17" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="pattern" />
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Here is an overview of the module dependencies:

lis-commons-beans (0 deps)

lis-commons-beans-record (1 deps)
+- lis-commons-beans

lis-commons-lang-criteria (3 deps)
+- lis-commons-lang
+- lis-commons-util
Expand Down Expand Up @@ -80,6 +83,23 @@ will output
button model changed.
button model armed: true

# [lis-commons-beans-records](lis-commons-beans-records/README.md)

Beans support for Java records.

public record PersonRecord(String firstname, String lastname) {}


BeansFactory beansFactory = BeansFactory.getInstance("record");
Bean<Person> bean = beansFactory.createBean(new PersonRecord("René", "Link"));
PropertyList properties = bean.getProperties();

assertEquals("René", properties.getByName("firstname").getValue());
assertEquals("Link", properties.getByName("lastname").getValue());

Details at [lis-commons-beans-records](lis-commons-beans-records/README.md).


# lis-commons-jdbc

Provides a convenient api to access the meta-data of a jdbc connection
Expand Down
90 changes: 90 additions & 0 deletions lis-commons-beans-records/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# lis-commons-beans-records

An extension library for the lis-commons-beans lib to handle Java records like beans.

## Usage

Let's assume you have a PersonRecord like this:

public record PersonRecord(String firstname, String lastname) {

PersonRecord() {
this("unknown", "unknown");
}
}

You can then get an instance of the record BeansFactory by using the name "record":

BeansFactory beansFactory = BeansFactory.getInstance("record");

This factory will create BeanClass instances that can handle Java records:

BeanClass<PersonRecord> personRecordBeanClass = beansFactory.createBeanClass(PersonRecord.class);

PersonRecord personRecord = new PersonRecord("René", "Link");
Bean<PersonRecord> personRecordBean = personRecordBeanClass.getBeanFromInstance(personRecord);

PropertyList properties = personRecordBean.getProperties();

Property firstnameProperty = properties.getByName("firstname");
assertEquals("René", firstnameProperty.getValue());

Property lastnameProperty = properties.getByName("lastname");
assertEquals("Link", lastnameProperty.getValue());


public record PersonRecord(String firstname, String lastname) {}


BeansFactory beansFactory = BeansFactory.getInstance("record");
Bean<Person> bean = beansFactory.createBean(new PersonRecord("René", "Link"));
PropertyList properties = bean.getProperties();

assertEquals("René", properties.getByName("firstname").getValue());
assertEquals("Link", properties.getByName("lastname").getValue());

You can also copy a record's values to another bean.

Let's assume you hava a Java record named `PersonRecord`

public record PersonRecord(String firstname, String lastname) {}

and a Java bean names `PersonBean`

public class PersonBean {

private String firstname;
private String lastname;

public String getFirstname() {
return firstname;
}

public void setFirstname(String firstname) {
this.firstname = firstname;
}

public String getLastname() {
return lastname;
}

public void setLastname(String lastname) {
this.lastname = lastname;
}
}

you can then copy the "properties" (record values) from the `PersonRecord` to the `PersonBean` by using different BeanFactory instances.

BeansFactory recordBeansFactory = BeansFactory.getInstance("record");
PersonRecord personRecord = new PersonRecord("René", "Link");
Bean<Person> recordBean = recordBeansFactory.createBean(personRecord);

BeansFactory javaBeansFactory = BeansFactory.getInstance("java"); // the default if no name is provided
PersonBean personJavaBean = new PersonBean();
Bean<PersonBean> javaBean = javaBeansFactory.createBean(personJavaBean);

recordBean.getProperties().copy(javaBean.getProperties());

assertEquals("René", personJavaBean.getFirstname());
assertEquals("Link", personJavaBean.getLastname());

28 changes: 28 additions & 0 deletions lis-commons-beans-records/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.link-intersystems.commons</groupId>
<artifactId>lis-commons</artifactId>
<version>1.9.7-SNAPSHOT</version>
</parent>

<artifactId>lis-commons-beans-records</artifactId>
<name>LIS Commons Beans Records</name>
<description>A lis-commons-beans provider that allows you to access Java records through the bean api.</description>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.link-intersystems.commons</groupId>
<artifactId>lis-commons-beans</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.link_intersystems.beans.java.record;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;

public class Introspector {

public static BeanInfo getBeanInfo(Class<?> recordType) throws IntrospectionException {
return new RecordBeanInfo(recordType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.link_intersystems.beans.java.record;

import com.link_intersystems.beans.java.JavaBean;
import com.link_intersystems.beans.java.JavaBeanClass;

public class RecordBean<T> extends JavaBean<T> {

protected RecordBean(JavaBeanClass<T> beanClass, T bean) {
super(beanClass, bean);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.link_intersystems.beans.java.record;

import com.link_intersystems.beans.BeanInstanceFactory;
import com.link_intersystems.beans.java.JavaBeanClass;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;

public class RecordBeanClass<T> extends JavaBeanClass<T> {

protected RecordBeanClass(Class<T> beanClass) throws IntrospectionException {
this(Introspector.getBeanInfo(beanClass));
}

protected RecordBeanClass(BeanInfo recordBeanInfo) {
super(recordBeanInfo);
}

@Override
public BeanInstanceFactory<T> getBeanInstanceFactory() {
return new RecordBeanInstanceFactory<>(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.link_intersystems.beans.java.record;

import java.beans.BeanDescriptor;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.lang.reflect.RecordComponent;

class RecordBeanInfo extends SimpleBeanInfo {

private final BeanDescriptor beanDescriptor;
private PropertyDescriptor[] propertyDescriptors;

public RecordBeanInfo(Class<?> recordClass) throws IntrospectionException {
if (!recordClass.isRecord()) {
throw new IntrospectionException(recordClass + " is not a Java record.");
}

beanDescriptor = new BeanDescriptor(recordClass);
propertyDescriptors = createPropertyDescriptors(recordClass.getRecordComponents());
}

private PropertyDescriptor[] createPropertyDescriptors(RecordComponent[] recordComponents) throws IntrospectionException {
PropertyDescriptor[] descriptors = new PropertyDescriptor[recordComponents.length];

for (int i = 0; i < recordComponents.length; i++) {
RecordComponent recordComponent = recordComponents[i];
descriptors[i] = new PropertyDescriptor(recordComponent.getName(), recordComponent.getAccessor(), null);
}

return descriptors;
}

@Override
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
}

@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return propertyDescriptors.clone();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.link_intersystems.beans.java.record;

import com.link_intersystems.beans.*;
import com.link_intersystems.beans.java.JavaBeanClass;

import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;
import java.util.concurrent.Callable;

import static java.util.Objects.requireNonNull;

class RecordBeanInstanceFactory<T> implements BeanInstanceFactory<T> {

private JavaBeanClass<T> beanClass;

RecordBeanInstanceFactory(JavaBeanClass<T> beanClass) {
this.beanClass = requireNonNull(beanClass);
}

@Override
public Bean<T> newBeanInstance(ArgumentResolver argumentResolver) {

try {
Callable<T> newInstanceCallable = resolveNewInstanceCallable(argumentResolver);
if (newInstanceCallable == null) {
throw new BeanInstantiationException("No constructor resolvable for " + beanClass);
}

try {
T bean = newInstanceCallable.call();
return fromExistingInstance(bean);
} catch (Exception e) {
throw new BeanInstantiationException("Unable to create a record bean.", e);
}
} catch (ArgumentResolveException e) {
throw new BeanInstantiationException("No constructor resolvable for " + beanClass, e);
}
}

protected Callable<T> resolveNewInstanceCallable(ArgumentResolver argumentResolver) throws ArgumentResolveException {
Class<T> type = beanClass.getType();

try {
Constructor<T> defaultConstructor = type.getDeclaredConstructor();
return () -> defaultConstructor.newInstance();
} catch (NoSuchMethodException e) {
Constructor<?>[] declaredConstructors = type.getDeclaredConstructors();

for (Constructor<?> constructor : declaredConstructors) {
Parameter[] parameters = constructor.getParameters();
if (argumentResolver.canResolveArguments(parameters)) {
Object[] arguments = argumentResolver.resolveArguments(parameters);
if (arguments != null && arguments.length == parameters.length) {
return () -> (T) constructor.newInstance(arguments);
}
}
}
}

return null;
}

@Override
public Bean<T> fromExistingInstance(T beanObject) {
return new RecordBean<>(beanClass, beanObject);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.link_intersystems.beans.java.record;

import com.link_intersystems.beans.BeanClass;
import com.link_intersystems.beans.BeanClassException;
import com.link_intersystems.beans.BeansFactory;

import java.beans.IntrospectionException;

public class RecordBeansFactory extends BeansFactory {
@Override
public String getTypeName() {
return "record";
}

@Override
public <T> BeanClass<T> createBeanClass(Class<T> beanClass, Class<?> stopClass) throws BeanClassException {
/**
* Ignoring stopClass, because records can not create an inheritance hierarchy.
* See Java 17 JLS 8.10 https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.10
*/
try {
return new RecordBeanClass<>(beanClass);
} catch (IntrospectionException e) {
throw new BeanClassException("Unable to create record bean class", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.link_intersystems.beans.java.record.RecordBeansFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.link_intersystems.beans.java.record;

import org.junit.jupiter.api.Test;

import javax.swing.*;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;

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

class IntrospectorTest {

@Test
void getBeanInfo() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(PersonRecord.class);

assertEquals("PersonRecord", beanInfo.getBeanDescriptor().getName());
assertEquals("firstname", beanInfo.getPropertyDescriptors()[0].getName());
assertEquals("lastname", beanInfo.getPropertyDescriptors()[1].getName());
}

@Test
void getBeanInfoNotRecord() {
assertThrows(IntrospectionException.class, () -> Introspector.getBeanInfo(DefaultListModel.class));
}
}
Loading