phax / jcodemodel

A heavily extended fork of the com.sun.codemodel (from 2013/09)
Other
93 stars 34 forks source link

java 16 record ? #98

Open glelouet opened 3 years ago

glelouet commented 3 years ago

are they already present ? If not, are they to be implemented ? if yes, are you working on it or can I work on it ?

phax commented 3 years ago

No plans on my side - feel free :)

glelouet commented 3 years ago

ok so.

Use Case :

// simple call that should be used most of the time
var record = cm.class("RecordTest").record("Person").withParam(String.class, "name").withParam(String.class, "address");
//more complex as we add a new constructor
var cons2 = record.construct();
var namep = cons2.param(String.class, "name");
cons2.body().add(JExpr.invoke("this").arg(namep).arg(Jexpr.null));

should produce

class RecordTest {
  public record Person(String name, String address){
    public Person(String name) {
      this(name, null);
    }
  }
}
glelouet commented 3 years ago

There also is the base code to be added to the constructor, example

public Person {
  Object.requireNonNull(name);
}

I think this should be a JBlock accessible with _init() in the Jrecord ?

glelouet commented 3 years ago

okay so I think I was wrong, I can create a record as a class so maybe no need to rewrite the whole thing.

The goal is therefore to add the type to the existing EClassType, and then… I guess the following code is a good start ?


import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public record Person(String name, String address) {

    public Person {
        Objects.requireNonNull(name);
    }

    public Person(String name) {
        this(name, null);
    }

    public String familyName() {
        return Stream.of(name.split(" ")).skip(1).collect(Collectors.joining(" "));
    }

    public String firstName() {
        return name.split(" ")[0];
    }

    // singleton

    private static Person nobody = new Person("nobody");

    public static Person nobody() {
        return nobody;
    }

}

It compiles on my computer so being able to describe it programatically seems a good start ?

phax commented 3 years ago

I must admit I haven't look at the official specs yet, and just adding to EClassType will not suffice I fear. Are the record types the final specification or are they still in "draft" mode?

glelouet commented 3 years ago

They are final for java 16. preview in java 15 which I used. I also thought that adding an EclassType would not be enough, because the fields are generated from the "method call" declaration . But at the same time, it allows static field so maybe the easiest way to do so is to not recreate a new class (especially since it uses code from abstractJcontainer and JDefinedClass)

glelouet commented 3 years ago

https://openjdk.java.net/jeps/395

glelouet commented 3 years ago

ok so I think these are the required steps :

  1. get all the links needed, and use case to evaluate the functionality and its importance. Is that really useful ? we assume it is.
  2. create one or more class to represent what we want to achieve. The "Person" I provided before is an example (just a start).
  3. Define the code we want to add to generate those class. Typically the class JRecord, its methods, etc.
  4. create a test that generates the classes of 2. and implement the code.
  5. define another case to manage when the projected JDK version is <14 OR <16 AND no --enable-preview . In that case, the records are created as final classes with their final fields and a cannonic constructor if not present already, also getters if not present already.
glelouet commented 3 years ago

The example test should provide an executable class, either with a main() or by implementing Runnable, so that it can actually be ran with the in-memory compiler and return a value that would be tested with Assert.assertEqual.

This test should also provide the same result when called as a java12 and therefore the test class should make both calls. (with record as a feature and with record as final immutable)

IMO the enableRecord flag should be present in the JCodemodel, default false untill JCM uses java 16 (in maven)

glelouet commented 3 years ago

https://github.com/glelouet/JCM-test-records/blob/main/src/main/java/com/helger/jcodemodel/target/record/Person.java showcasing the features of records. Not all, eg missing sealed etc.

glelouet commented 3 years ago

Usage of records : records manipulation in JCM would allow to automatically generate immutable class.

In my case, it would be useful as the key type of a cached remote server resource modeling. Example if the remote server proposes a path clients_by_region_id_and_id/42/25 (the client of region 42 and id 25) then the built API should have a cache for the path parameters (region, id) to the compiled resource type eg private final HashMap<KEYTYPE, WeakReference<Holder<RESOURCETYPE>>> cache_clients_by_region_and_id therefore a record would alleviate the creation of such a KEYTYPE class. Even the RESOURCETYPE would gain from the change, allowing to never have the data modified by user (the client API is used by several users). The KEYTYPE is only managed by the API access , typically behind a corresponding method call that creates the KEYTYPE transparently.

https://github.com/guiguilechat/JCELechat/blob/master/model/esi/JCESI-compiled/src/generated/java/fr/guiguilechat/jcelechat/model/jcesi/compiler/compiled/connected/Characters.java#L200 Here is an example where I have a cache that creates its own key . In that case, both the K_0_int_Integer class (key) and R_get_characters_character_id_calendar (resource representation) would benefit from being created as records.

Later, because java16 is not here yet :P

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.