-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix #926: Apply enum defaults for types already on the classpath #1779
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -157,6 +158,10 @@ static JExpression getDefaultValue(JType fieldType, String value) { | |
|
|
||
| return getDefaultEnum(fieldType, value); | ||
|
|
||
| } else if (fieldType instanceof JClass && isClasspathEnum(fieldType)) { | ||
|
|
||
| return getDefaultEnumFromClasspath((JClass) fieldType, value); | ||
|
|
||
| } else { | ||
| return JExpr._null(); | ||
|
|
||
|
|
@@ -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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: why argument type is
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
That said, I could narrow the parameter to
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should:
|
||
| JType backingType = fieldType.owner().ref(m.getParameterTypes()[0]).unboxify(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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 { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| 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 { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather move that file away from |
||
|
|
||
| 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" : { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
instanceOfoperator could be used here, eliminating the need for downstream casting