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 record #183

Open
mkarg opened this issue Apr 17, 2021 · 9 comments
Open

Support for record #183

mkarg opened this issue Apr 17, 2021 · 9 comments
Labels
enhancement New feature or request

Comments

@mkarg
Copy link

mkarg commented Apr 17, 2021

With the release of Java 16 in March 2021 the broad use of the record keyword is expected to grow.

I'd like to propose that the JAXB API clearly defines if and how the record keyword is supported by JAXB:

  • Can records be used with JAXB?
  • How to annotate records, as most methods and fields no exists unless overwritten?
  • Are there benefits / drawbacks of using records over custom classes in direct relation to JAXB?
  • Is support for records mandatory for implementations of the JAXB API (hence, is it safe to be used by vendor-agnositic applications)?
@winne42
Copy link

winne42 commented Nov 19, 2021

The Java 17 LTS release further pushes people towards records. Have you ever heard anything about this topic from anyone, @mkarg ?

@mkarg
Copy link
Author

mkarg commented Nov 20, 2021

@winne42 I am not aware of any information besides what you can read on this page.

@winne42
Copy link

winne42 commented Nov 23, 2021

Thanks @mkarg , sad...

@lukasj
Copy link
Contributor

lukasj commented Nov 23, 2021

@winne42 feel free to propose a solution you want to see

@lukasj lukasj added the enhancement New feature or request label May 19, 2022
@stbischof
Copy link

stbischof commented May 29, 2022

some news on this?

I expected this to work.

@XmlRootElement(namespace = "example.books")
public record Bookstore(
		String name, 
		String location,
		@XmlElementWrapper(name = "bookList")
		@XmlElement(name = "book") List<Book> 
		bookList
		) {
}
@XmlRootElement(name = "book")
@XmlType(propOrder = { "author", "name", "publisher", "isbn" })
public record Book(
		@XmlElement(name = "title") 
		String name, 
		String author,
		String publisher,
		String isbn) {
}


Here are some more of my thoughts on this topic:

Instantiating Classes vs Records

The main difference to the current implementations is the processing order. Classes/beans could be creates using the default no args Constructor and then setting the values using fields or methods. Records must be creates using the Constructor that describes every single attribute. So you need to hold all needed elements for an record in cache until you can construct the record with these elements.

Java 16++

Records are a Feature of Java 16 you have to find a way to handle language features.
Jaxb-api and implementations will not depend on Java 17. Options would be Multi-Release or Reflections.

This POC shows a way to read and instantiate Records with Java 11

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;

public class Main {

	public static void main(String[] args) throws JAXBException {
		Map<String, Object> m = new HashMap<>();
		m.put("name", "theName");
		m.put("author", "theAuther");
		m.put("publisher", "thePublisher");
		m.put("isbn", "theIsbn");

		double javaVersion = RecordUtil.javaVersion();
		System.out.println("javaVersion: " + javaVersion);

		boolean isRecord = RecordUtil.isRecord(Book_minimal.class);
		System.out.println("isRecord: " + isRecord);

		Object book = RecordUtil.instanceOfMap(Book_minimal.class, m);
		System.out.println(book);

		Method[] methodsOfRecord = RecordUtil.methodsOfRecord(Book_minimal.class);
		System.out.println("methodsOfRecord:");
		for (Method method : methodsOfRecord) {
			System.out.println("- " + method);
			try {
				Object returnVal = method.invoke(book);
				System.out.println("= " + returnVal);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		JAXBContext jc = JAXBContext.newInstance(Book_minimal.class);

		Marshaller marshaller = jc.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		marshaller.marshal(book, System.out);

	}

	// Handles actions on Record with Java 11
	static class RecordUtil {
		private static Method class_getRecordComponentsMethod = null;
		private static Method recordComponent_getAccessorMethod = null;

		static double javaVersion() {
			return Double.parseDouble(System.getProperty("java.class.version"));
		}

		static boolean isRecord(Class<?> cls) {

			if (javaVersion() < 60) {
				// TODO: 61 for java17
				return false;
			}

			try {
				Method m = Class.class.getMethod("isRecord");
				if (m == null) {
					return false;
				}

				Boolean b = (Boolean) m.invoke(cls);
				return b.booleanValue();
			} catch (Exception e) {
				return false;
			}
		}

		static Method[] methodsOfRecord(Class<?> recordClass) {
			if (recordComponent_getAccessorMethod == null) {

				try {
					final Class<?> recordComponentClass = Class.forName("java.lang.reflect.RecordComponent");
					recordComponent_getAccessorMethod = recordComponentClass.getMethod("getAccessor");
				} catch (Exception ex) {
					throw new RuntimeException(ex);
				}
			}
			Object[] recordComponents = recordComponents(recordClass);

			Method[] methods = Stream.of(recordComponents).map(rc -> {
				try {
					return recordComponent_getAccessorMethod.invoke(rc);
				} catch (Exception e) {
					throw new IllegalArgumentException("Could not get Method-Accessor of the RecordComponent: " + rc,
							e);
				}
			}).toArray(Method[]::new);
			return methods;
		}

		static Object[] recordComponents(Class<?> recordClass) {
			try {
				if (class_getRecordComponentsMethod == null) {
					class_getRecordComponentsMethod = Class.class.getMethod("getRecordComponents");
				}
				return (Object[]) class_getRecordComponentsMethod.invoke(recordClass);
			} catch (Exception e) {
				throw new IllegalArgumentException("", e);
			}
		}

		static Object instanceOfMap(Class<Book_minimal> targetClass, Map<String, Object> m) {
			Constructor<?> constr = Book_minimal.class.getDeclaredConstructors()[0];
			int count = constr.getParameterCount();
			Object[] objects = new Object[count];
			Parameter[] params = constr.getParameters();

			for (int i = 0; i < count; i++) {
				Parameter param = params[i];
				System.out.println(param);
				Object o = null;

				Class<?> type = param.getType();

				String name = param.getName();

				if (m.containsKey(name)) {
					o = m.get(name);
				} else {
					o = m.entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(name)).findFirst()
							.map(Entry::getValue).orElse(null);
					// spec multiple ignorecase take first;
				}
				// trypecheck
				objects[i] = o;
			}
			try {
				return constr.newInstance(objects);

			} catch (Exception exception) {
				throw new RuntimeException("Could not create the record: " + targetClass, exception);
			}
		}

	}
}

Jaxb Features

@xmlelement - name, defaultValue, required, nillable, type

The @XmlElement features must work like with class

@XmlRootElement(name = "book")
public record Book_element(
		@XmlElement(name = "title") 
		String name, //must work like class
		
		@XmlElement(defaultValue = "me") 
		String author,//must work like class
		
		@XmlElement(required =  true, nillable = false) 
		String publisher,//must work like class

		@XmlElement(type = String.class) 
		Object isbn //must work like class
		
		) {
}

@xmlelement - factoryMethod, factoryClass

factoryClass and factoryMethod could not be used with Records because there is no option to set values after creating an Record and no way to provide arguments into the factory method because it must be a no-arg factory method.

@XmlType(factoryClass = Book4.class, factoryMethod = "newInstance")
public record Book4(String title, String author, String publisher, String isbn) {

	public static Book4 newInstance() {
		return new Book4("", "", "", "");
	}
}

@XmlElementWrapper and @XmlJavaTypeAdapter

there are no implications that avoid the handling of@XmlElementWrapper or @XmlJavaTypeAdapter

@XmlRootElement(namespace = "example.books")
public record Bookstore_wrapper(
		String name, 
		String location,
		@XmlElementWrapper(name = "bookList")
		@XmlElement(name = "book") List<Book_minimal> 
		bookList
		) {
}

@XmlAccessorType

Record do not have an any setters and fields that are setable. Just arguments of an constructor and method to get access to the value of the RecordComponent.
More documentation is needed.

@stbischof
Copy link

what are the steps that must be done to bring this forward?

@mkarg
Copy link
Author

mkarg commented Aug 28, 2022

what are the steps that must be done to bring this forward?

Basing on the great research results already posted above, identify the items to actually get changed in the API, JavaDoc, and TCK. Provide a PR implementing your propsal. Discuss the PR with the committers. If a majority is convinced to implement the needed changes in their products, the PR will pass. :-)

Note that the EF actually likes to have a working product first before fixing the API, so it might sense to get one of the vendors into your boat, e. g. by adding a PR to a JAXB implementation, proving that your ideas will work in an actual product. :-)

@stbischof
Copy link

FYI:
I did a POC that could marshal and unmarshal a Record to see how much work it would be in eclipse-ee4j/jaxb-ri

I am pretty sure someone else from the jaxb-ri team could do it much better!
But maybe this helps others with the analysis.

afranken added a commit to adobe/S3Mock that referenced this issue Jul 3, 2023
Classes using JAX-B annotations can't be refactored right now.
jakartaee/jaxb-api#183
afranken added a commit to adobe/S3Mock that referenced this issue Jul 5, 2023
Classes using JAX-B annotations can't be refactored right now.
jakartaee/jaxb-api#183
@cjdxhjj
Copy link

cjdxhjj commented Dec 4, 2023

any progrss?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants