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

Record like property access support #2994

Merged
merged 8 commits into from Nov 22, 2022
Merged

Record like property access support #2994

merged 8 commits into from Nov 22, 2022

Conversation

bbakerman
Copy link
Member

@bbakerman bbakerman commented Oct 25, 2022

This changes the PropertyDataFetcher so that support Java record like naming.

eg object.propertyName() method naming

While the code here is not specific to record classes only - it will work with them and with any class that uses propertyName() method naming

#2994 has been updated

I have decided to change the strategy - we will look for "direct property" methods first and then getters

So given a field issues - then we will look for issues() first and then if it fails to find that it will look for getissues or getIssues (since it decapitalizes)

I think this is better situation on reflection (pun intented)

Originally I wanted to avoid confusion


public behavior() {
   return "not wanted"'
}

public getBehavior()  {
   return "wanted";
}

where if some one previously had declared a record like method (behavior()) and didn't want it to be used. BUT this is crazy to have such confusion and I don't think anyone would do it on purpose AND graphql-java 20 can introduce new behaviors if on balance its worth it. And I think it's worth it.

So this is the new support.

This will also help Kotlin with its special isXXX naming (or lack there of)

given

class KotlinClass(val name : String, val age : Int, val cool: Boolean, var isOpen : Boolean, val issues : String) {
}

data class KotlinDataClass(val name: String, val age: Int, val cool: Boolean, var isOpen : Boolean, val issues : String)

public record JavaRecord(String name, int age, boolean cool, boolean isOpen, String issues) {
}

we get this generated

KotlinDataClass
public final java.lang.String KotlinDataClass.getName()
public boolean KotlinDataClass.equals(java.lang.Object)
public java.lang.String KotlinDataClass.toString()
public int KotlinDataClass.hashCode()
public final boolean KotlinDataClass.isOpen()
public final KotlinDataClass KotlinDataClass.copy(java.lang.String,int,boolean,boolean,java.lang.String)
public static KotlinDataClass KotlinDataClass.copy$default(KotlinDataClass,java.lang.String,int,boolean,boolean,java.lang.String,int,java.lang.Object)
public final int KotlinDataClass.getAge()
public final boolean KotlinDataClass.getCool()
public final void KotlinDataClass.setOpen(boolean)
public final java.lang.String KotlinDataClass.getIssues()
public final java.lang.String KotlinDataClass.component1()
public final int KotlinDataClass.component2()
public final boolean KotlinDataClass.component3()
public final boolean KotlinDataClass.component4()
public final java.lang.String KotlinDataClass.component5()
----
KotlinClass
public final java.lang.String KotlinClass.getName()
public final boolean KotlinClass.isOpen()
public final int KotlinClass.getAge()
public final boolean KotlinClass.getCool()
public final void KotlinClass.setOpen(boolean)
public final java.lang.String KotlinClass.getIssues()
----
JavaRecord
public java.lang.String JavaRecord.name()
public final boolean JavaRecord.equals(java.lang.Object)
public final java.lang.String JavaRecord.toString()
public final int JavaRecord.hashCode()
public boolean JavaRecord.isOpen()
public java.lang.String JavaRecord.issues()
public int JavaRecord.age()
public boolean JavaRecord.cool()
----

So this new code will find directly named methods first and then use the get/is style naming secondary

This will work for Kotlin and Java records a like

@bbakerman bbakerman added this to the 20.0 milestone Oct 25, 2022
putInNegativeCache(cacheKey);
return null;
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unwound the nesting of the exceptions - there was no need for it and I think this is clearer code

Records cannot extend any class - so we need only check the root class for a publicly declared method with the propertyName
*/
private Method findRecordMethod(CacheKey cacheKey, Class<?> rootClass, String methodName) throws NoSuchMethodException {
if (Modifier.isPublic(rootClass.getModifiers())) {
Copy link
Member

@dondonz dondonz Oct 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Records are also final. findRecordMethod should be restricted to records or other final classes.

if (Modifier.isPublic(rootClass.getModifiers()) && Modifier.isFinal(rootClass.getModifiers())) {

* smells like one and that's enough really. Its public, not derived from another
* class and has a public method named after a property
*/
public class RecordLikeClass {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this public final class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have decided we will look up record like methods - not truly final ones like records are.

MethodFinder methodFinder = (rootClass, methodName) -> findRecordMethod(cacheKey, rootClass, methodName);
return getPropertyViaRecordMethod(object, propertyName, methodFinder, singleArgumentValue);
} catch (NoSuchMethodException ignored) {
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unwound the nesting because I think this reads better than try inside try inside try

if (!recordLikeMethods.isEmpty()) {
return recordLikeMethods.get(0);
}
return null;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support in the lambda code to find recordLike() methods


public String recordLike() {
return "recordLike";
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IN theory this is possible - stupid but possible - in the old days the getter would be found so this shows that this still happens. eg record getters are looked up after pojo getters

package graphql.schema.somepackage;

public class RecordLikeTwoClassesDown extends RecordLikeClass {
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hence derivation works with record like methods

Moved record like to the front of the search pattern
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants