Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static org.apache.commons.lang3.StringUtils.*;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
Expand Down Expand Up @@ -157,6 +158,10 @@ static JExpression getDefaultValue(JType fieldType, String value) {

return getDefaultEnum(fieldType, value);

} else if (fieldType instanceof JClass && isClasspathEnum(fieldType)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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);


return getDefaultEnumFromClasspath((JClass) fieldType, value);

} else {
return JExpr._null();

Expand Down Expand Up @@ -255,6 +260,50 @@ private static JExpression getDefaultEnum(JType fieldType, String value) {
return invokeFromValue;
}

/**
* Checks whether the given type is an enum that already exists on the
* classpath (i.e. it is not a {@link JDefinedClass} generated during this
* run, but a reference to a pre-compiled class).
*
* @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a>
*/
private static boolean isClasspathEnum(JType fieldType) {
Copy link
Copy Markdown
Collaborator

@unkish unkish Feb 13, 2026

Choose a reason for hiding this comment

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

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 ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@unkish gentle bump on this thread 😄

if (fieldType instanceof JDefinedClass) {
return false;
}
try {
return Thread.currentThread().getContextClassLoader()
.loadClass(fieldType.fullName()).isEnum();
} catch (ClassNotFoundException e) {
return false;
}
}

/**
* Creates a default enum expression for an enum type that already exists on
* the classpath. Uses reflection to discover the {@code fromValue} method's
* parameter type so the correct literal can be generated.
*
* @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a>
*/
private static JExpression getDefaultEnumFromClasspath(JClass fieldType, String value) {
try {
Class<?> enumClass = Thread.currentThread().getContextClassLoader()
.loadClass(fieldType.fullName());
for (Method m : enumClass.getMethods()) {
if ("fromValue".equals(m.getName()) && m.getParameterCount() == 1) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should:

  • return type also be checked ?
  • static modifier be checked ?

JType backingType = fieldType.owner().ref(m.getParameterTypes()[0]).unboxify();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unboxify is redundant as

  • it will be done downstream in getDefaultValue
  • .ref would throw an IllegalArgumentException for primitive type

JInvocation invoke = fieldType.staticInvoke("fromValue");
invoke.arg(getDefaultValue(backingType, value));
return invoke;
}
}
} catch (ClassNotFoundException e) {
// enum not found on classpath — fall through to null
}
return JExpr._null();
}

private static long parseDateToMillisecs(String valueAsText) {

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import java.util.Set;

import org.jsonschema2pojo.integration.classpath.ExistingClasspathEnum;
import org.jsonschema2pojo.integration.util.Jsonschema2PojoRule;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -400,4 +401,52 @@ public void uniqueArrayPropertyWithoutDefaultIsEmptySet() throws NoSuchMethodExc

}

/**
* Verifies that enum default values are correctly applied when the enum
* type already exists on the classpath (issue #926).
*
* @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a>
*/
@Test
public void classpathEnumPropertyHasCorrectDefaultValue() throws Exception {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.


ClassLoader resultsClassLoader = schemaRule.generateAndCompile(
"/schema/default/defaultWithClasspathEnum.json", "com.example");

Class<?> generatedClass = resultsClassLoader.loadClass("com.example.DefaultWithClasspathEnum");
Object instance = generatedClass.getDeclaredConstructor().newInstance();

Method getter = generatedClass.getMethod("getClasspathEnumWithDefault");
Object defaultValue = getter.invoke(instance);

assertThat(defaultValue, is(notNullValue()));
assertThat(defaultValue, is(instanceOf(ExistingClasspathEnum.class)));
assertThat(defaultValue, is(equalTo(ExistingClasspathEnum.BETA)));

}

/**
* Verifies that enum default values are correctly applied when using
* {@code existingJavaType} to reference an enum on the classpath (issue #926).
*
* @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a>
*/
@Test
public void existingJavaTypeEnumPropertyHasCorrectDefaultValue() throws Exception {

ClassLoader resultsClassLoader = schemaRule.generateAndCompile(
"/schema/default/defaultWithExistingClasspathEnum.json", "com.example");

Class<?> generatedClass = resultsClassLoader.loadClass("com.example.DefaultWithExistingClasspathEnum");
Object instance = generatedClass.getDeclaredConstructor().newInstance();

Method getter = generatedClass.getMethod("getExistingEnumWithDefault");
Object defaultValue = getter.invoke(instance);

assertThat(defaultValue, is(notNullValue()));
assertThat(defaultValue, is(instanceOf(ExistingClasspathEnum.class)));
assertThat(defaultValue, is(equalTo(ExistingClasspathEnum.BETA)));

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright © 2010-2020 Nokia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jsonschema2pojo.integration.classpath;

import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

/**
* A pre-compiled enum that simulates the pattern generated by jsonschema2pojo.
* Used by integration tests to verify that default values are correctly applied
* when an enum type already exists on the classpath.
*
* @see <a href="https://github.com/joelittlejohn/jsonschema2pojo/issues/926">issue #926</a>
*/
public enum ExistingClasspathEnum {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would rather move that file away from org.jsonschema2pojo.integration, maybe into com.example.classpath keeping former package name for integration tests.


ALPHA("alpha"),
BETA("beta"),
GAMMA("gamma");

private final String value;
private static final Map<String, ExistingClasspathEnum> CONSTANTS = new HashMap<>();

static {
for (ExistingClasspathEnum c : values()) {
CONSTANTS.put(c.value, c);
}
}

ExistingClasspathEnum(String value) {
this.value = value;
}

@JsonValue
public String value() {
return this.value;
}

@JsonCreator
public static ExistingClasspathEnum fromValue(String value) {
ExistingClasspathEnum constant = CONSTANTS.get(value);
if (constant == null) {
throw new IllegalArgumentException(value);
}
return constant;
}

@Override
public String toString() {
return this.value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type" : "object",
"properties" : {
"classpathEnumWithDefault" : {
"javaType" : "org.jsonschema2pojo.integration.classpath.ExistingClasspathEnum",
"type" : "string",
"enum" : ["alpha", "beta", "gamma"],
"default" : "beta"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type" : "object",
"properties" : {
"existingEnumWithDefault" : {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

"existingJavaType" : "org.jsonschema2pojo.integration.classpath.ExistingClasspathEnum",
"type" : "string",
"enum" : ["alpha", "beta", "gamma"],
"default" : "beta"
}
}
}