Fix #926: Apply enum defaults for types already on the classpath#1779
Fix #926: Apply enum defaults for types already on the classpath#1779manerow wants to merge 2 commits intojoelittlejohn:masterfrom
Conversation
|
I haven't checked yet, but I'm slightly confused on this one. You should be using 'existingJavaType' to refer to something from the classpath. 'javaType' is used when you are generating a new type, but you want to specify the fully-qualified type name. No? |
|
Thanks for the quick look @joelittlejohn! You're right that This matters in multi-module projects where the same schemas are processed in different build contexts. The upstream module generates everything from scratch ( This PR just completes what For the test, I used |
|
Fair enough. Hyrum's Law in action I suppose! I think this is a hangover from before we separated javaType and existingJavaType. I think we should fix this for existingJavaType because it gives you a way to refer to an existing enum and choose a default value, without having to use the javaType trick. |
|
Good point @joelittlejohn. The fix already covers I've added an integration test ( |
| * | ||
| * @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a> | ||
| */ | ||
| private static boolean isClasspathEnum(JType fieldType) { |
There was a problem hiding this comment.
Question: why argument type is JType if invocation is done only for JClass (fieldType instanceof JClass) ?
If there's already logic to check for JDefinedClass inside, would it make sense to move fieldType instanceof JClass check aslo there ?
There was a problem hiding this comment.
The instanceof JClass check at the call site (line 161) serves a dual purpose: it gates the isClasspathEnum call and ensures the cast to JClass on line 163 is safe for getDefaultEnumFromClasspath.
isClasspathEnum takes JType to be self-contained. It needs its own JDefinedClass guard because JDefinedClass extends JClass, so a JDefinedClass would pass the instanceof JClass check at the call site. In practice this branch is already handled by line 157, but it keeps the method safe to call independently.
That said, I could narrow the parameter to JClass since the JDefinedClass guard still works the same way. Happy to change it if you prefer.
…lasspath When an enum type already exists on the plugin classpath (e.g. from a dependency JAR), EnumRule returns a JDirectClass/JReferencedClass instead of a JDefinedClass. DefaultRule.getDefaultValue() only checked for JDefinedClass when applying enum defaults, causing the default to be silently replaced with null. Add a fallback in getDefaultValue() that detects classpath enum types via reflection and generates the correct fromValue(...) initialiser.
af9e22a to
3e179df
Compare
| * | ||
| * @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a> | ||
| */ | ||
| public enum ExistingClasspathEnum { |
There was a problem hiding this comment.
I would rather move that file away from org.jsonschema2pojo.integration, maybe into com.example.classpath keeping former package name for integration tests.
| * @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a> | ||
| */ | ||
| @Test | ||
| public void classpathEnumPropertyHasCorrectDefaultValue() throws Exception { |
There was a problem hiding this comment.
I'm not quite sure as to what to think of this test - this will just cause EnumRule to return reference to existing class which should be equivalent to test that has existingJavaType.
It does no harm here, but should it be a part of DefaultIT or EnumIT, maybe someone else has also an opinion on that.
|
|
||
| return getDefaultEnum(fieldType, value); | ||
|
|
||
| } else if (fieldType instanceof JClass && isClasspathEnum(fieldType)) { |
There was a problem hiding this comment.
Nit: instanceOf operator could be used here, eliminating the need for downstream casting
} else if (fieldType instanceof JClass jClass && isClasspathEnum(jClass)) {
return getDefaultEnumFromClasspath(jClass, value);| .loadClass(fieldType.fullName()); | ||
| for (Method m : enumClass.getMethods()) { | ||
| if ("fromValue".equals(m.getName()) && m.getParameterCount() == 1) { | ||
| JType backingType = fieldType.owner().ref(m.getParameterTypes()[0]).unboxify(); |
There was a problem hiding this comment.
unboxify is redundant as
- it will be done downstream in
getDefaultValue .refwould throw anIllegalArgumentExceptionfor primitive type
| Class<?> enumClass = Thread.currentThread().getContextClassLoader() | ||
| .loadClass(fieldType.fullName()); | ||
| for (Method m : enumClass.getMethods()) { | ||
| if ("fromValue".equals(m.getName()) && m.getParameterCount() == 1) { |
There was a problem hiding this comment.
Nit: to me both the method name and the PR/issue title are a bit misleading as although sounding generalized in reality there seems to be an assumption that "enum" is an enum generated by js2p.
| Class<?> enumClass = Thread.currentThread().getContextClassLoader() | ||
| .loadClass(fieldType.fullName()); | ||
| for (Method m : enumClass.getMethods()) { | ||
| if ("fromValue".equals(m.getName()) && m.getParameterCount() == 1) { |
There was a problem hiding this comment.
Should:
- return type also be checked ?
- static modifier be checked ?
| { | ||
| "type" : "object", | ||
| "properties" : { | ||
| "existingEnumWithDefault" : { |
There was a problem hiding this comment.
I wonder if it'd be possible to have a test with a "non-standard" enum value type eg. one that is backed by double or bigdecimal
Problem
When an enum type already exists on the plugin classpath (e.g. from a dependency JAR),
DefaultRule.getDefaultValue()fails to generate thefromValue(...)initialiser.The root cause is that
EnumRulereturns aJDirectClass(a reference to the pre-existing classpath class) instead of aJDefinedClass(a freshly generated class).DefaultRuleonly checksfieldType instanceof JDefinedClasswhen deciding whether to apply enum defaults, so classpath enum types fall through toreturn JExpr._null().This means any project that depends on a JAR containing pre-compiled enum classes while regenerating POJOs from the same schemas will silently lose all enum default values.
Example
Given a schema with:
Expected (and what you get when the enum is generated from scratch):
Actual (when
ProviderType.classalready exists on the classpath):Fix
Add a fallback branch in
DefaultRule.getDefaultValue()that detects classpath enum types using reflection:isClasspathEnum()— checks if a non-JDefinedClasstype is an enum on the classpath viaClass.isEnum()getDefaultEnumFromClasspath()— finds thefromValuemethod via reflection, determines the backing type from its parameter, and generates the correctfromValue(...)expressionThis handles string-backed, integer-backed, and number-backed enums correctly by inspecting the
fromValuemethod's parameter type.Test
Added an integration test (
DefaultIT.classpathEnumPropertyHasCorrectDefaultValue) that:ExistingClasspathEnum) in the test sources — this class is on the classpath during code generation, triggering theClassAlreadyExistsExceptionpath inEnumRule"javaType"and declares"default": "beta"ExistingClasspathEnum.BETAinstead ofnullAll 596 tests pass. The only pre-existing failure (
DynamicPropertiesIT.shouldSetStringFieldJava7) targets Java 7 on JDK 17 and is unrelated.Fixes #926