diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstRule.java new file mode 100644 index 000000000..d639efb8d --- /dev/null +++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ConstRule.java @@ -0,0 +1,152 @@ +/** + * 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.rules; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.util.StdDateFormat; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JExpression; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JType; +import org.joda.time.DateTime; +import org.joda.time.LocalDate; +import org.joda.time.LocalTime; +import org.jsonschema2pojo.Schema; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.text.ParseException; +import java.util.Date; +import java.util.Objects; + +import static org.apache.commons.lang3.StringUtils.isNotEmpty; +import static org.apache.commons.lang3.StringUtils.joinWith; +import static org.apache.commons.lang3.StringUtils.splitByCharacterTypeCamelCase; + +public class ConstRule implements Rule { + + private final RuleFactory ruleFactory; + private final JDefinedClass jclass; + + public ConstRule(RuleFactory ruleFactory, JDefinedClass jclass) { + this.ruleFactory = ruleFactory; + this.jclass = jclass; + } + + @Override + public JFieldVar apply(String nodeName, JsonNode node, JsonNode parent, JFieldVar field, Schema currentSchema) { + boolean constPresent = node != null && isNotEmpty(node.asText()); + JType propertyType = field.type(); + + if (!constPresent) { + return field; + } + + String constName = joinWith("_", splitByCharacterTypeCamelCase(nodeName)).toUpperCase(); + JFieldVar myConstant = jclass.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, + propertyType, + constName, + getValue(propertyType, node)); + + return field; + } + + static JExpression getValue(JType type, JsonNode node) { + Objects.requireNonNull(node); + String fieldType = type.fullName(); + if (fieldType.startsWith(String.class.getName())) { + return getConstValue(type, node); + } else { + return getConstValue(type, node); + } + } + + static JExpression getConstValue(JType fieldType, JsonNode node) { + return getConstValue(fieldType, node.asText()); + } + + static JExpression getConstValue(JType fieldType, String value) { + + fieldType = fieldType.unboxify(); + + if (fieldType.fullName().equals(String.class.getName())) { + return JExpr.lit(value); + + } else if (fieldType.fullName().equals(int.class.getName())) { + return JExpr.lit(Integer.parseInt(value)); + + } else if (fieldType.fullName().equals(BigInteger.class.getName())) { + return JExpr._new(fieldType).arg(JExpr.lit(value)); + + } else if (fieldType.fullName().equals(double.class.getName())) { + return JExpr.lit(Double.parseDouble(value)); + + } else if (fieldType.fullName().equals(BigDecimal.class.getName())) { + return JExpr._new(fieldType).arg(JExpr.lit(value)); + + } else if (fieldType.fullName().equals(boolean.class.getName())) { + return JExpr.lit(Boolean.parseBoolean(value)); + + } else if (fieldType.fullName().equals(DateTime.class.getName()) || fieldType.fullName().equals(Date.class.getName())) { + long millisecs = parseDateToMillisecs(value); + + JInvocation newDateTime = JExpr._new(fieldType); + newDateTime.arg(JExpr.lit(millisecs)); + + return newDateTime; + + } else if (fieldType.fullName().equals(LocalDate.class.getName()) || fieldType.fullName().equals(LocalTime.class.getName())) { + + JInvocation stringParseableTypeInstance = JExpr._new(fieldType); + stringParseableTypeInstance.arg(JExpr.lit(value)); + return stringParseableTypeInstance; + + } else if (fieldType.fullName().equals(long.class.getName())) { + return JExpr.lit(Long.parseLong(value)); + + } else if (fieldType.fullName().equals(float.class.getName())) { + return JExpr.lit(Float.parseFloat(value)); + + } else if (fieldType.fullName().equals(URI.class.getName())) { + JInvocation invokeCreate = fieldType.owner().ref(URI.class).staticInvoke("create"); + return invokeCreate.arg(JExpr.lit(value)); + } else { + return JExpr._null(); + + } + + } + + private static long parseDateToMillisecs(String valueAsText) { + + try { + return Long.parseLong(valueAsText); + } catch (NumberFormatException nfe) { + try { + return new StdDateFormat().parse(valueAsText).getTime(); + } catch (ParseException pe) { + throw new IllegalArgumentException("Unable to parse this string as a date: " + valueAsText); + } + } + + } + +} diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java index 9bb0ecb72..7cce228c3 100644 --- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java +++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/PropertyRule.java @@ -119,6 +119,8 @@ public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDef ruleFactory.getPatternRule().apply(nodeName, node.get("pattern"), node, field, schema); } + ruleFactory.getConstRule(jclass).apply(nodeName, node.get("const"), node, field, schema); + ruleFactory.getDefaultRule().apply(nodeName, node.get("default"), node, field, schema); ruleFactory.getMinimumMaximumRule().apply(nodeName, node, parent, field, schema); diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java index 5b1c41c4c..b2c05d16d 100644 --- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java +++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/RuleFactory.java @@ -252,6 +252,11 @@ public Rule getDefaultRule() { return new DefaultRule(this); } + + public Rule getConstRule(JDefinedClass jclass) { + return new ConstRule(this, jclass); + } + /** * Provides a rule instance that should be applied when a property * declaration is found in the schema, to assign any minimum/maximum diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/DefaultIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/DefaultIT.java index f0ac4341d..1ca5edbc7 100644 --- a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/DefaultIT.java +++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/DefaultIT.java @@ -20,8 +20,10 @@ import static org.jsonschema2pojo.integration.util.CodeGenerationHelper.*; import static org.junit.Assert.*; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; @@ -37,7 +39,7 @@ import org.junit.Test; public class DefaultIT { - + @ClassRule public static Jsonschema2PojoRule classSchemaRule = new Jsonschema2PojoRule(); @Rule public Jsonschema2PojoRule schemaRule = new Jsonschema2PojoRule(); @@ -74,6 +76,162 @@ public void stringPropertyHasCorrectDefaultValue() throws NoSuchMethodException, } + @Test + public void emptyConst() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { + + Object instance = classWithDefaults.newInstance(); + + assertThrows(NoSuchFieldException.class, () -> classWithDefaults.getField("EMPTY_WITH_CONST")); + Method getter = classWithDefaults.getMethod("getEmptyWithConst"); + assertNull(getter.invoke(instance)); + } + + @Test + public void stringConst() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Object instance = classWithDefaults.newInstance(); + + Field field = classWithDefaults.getField("STRING_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getStringWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + assertEquals("abc", value); + assertNull(getter.invoke(instance)); + } + + @Test + public void booleanConst() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Object instance = classWithDefaults.newInstance(); + + Field field = classWithDefaults.getField("BOOLEAN_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getBooleanWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + assertEquals(true, value); + assertNull(getter.invoke(instance)); + } + + @Test + public void integerConst() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Object instance = classWithDefaults.newInstance(); + + Field field = classWithDefaults.getField("INTEGER_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getIntegerWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + assertEquals(1, value); + assertNull(getter.invoke(instance)); + } + + @Test + public void numberConst() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Object instance = classWithDefaults.newInstance(); + + Field field = classWithDefaults.getField("NUMBER_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getNumberWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + assertEquals(1.1, value); + assertNull(getter.invoke(instance)); + } + + @Test + public void numberConstBigDecimal() throws Exception { + ClassLoader resultsClassLoader = schemaRule.generateAndCompile("/schema/default/default.json", "com.example", config("useBigDecimals", true)); + Class c = resultsClassLoader.loadClass("com.example.Default"); + + Field field = c.getField("NUMBER_WITH_CONST"); + + assertPublicStaticFinal(field); + Object value = field.get(null); + + assertThat((BigDecimal) value, is(equalTo(new BigDecimal("1.1")))); + } + + @Test + public void dateTimeAsMillisecPropertyHasCorrectConstValue() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Field field = classWithDefaults.getField("DATE_TIME_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getDateTimeWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + + assertThat((Date) value, is(equalTo(new Date(123456789)))); + } + + private static void assertPublicStaticFinal(Field field) { + int modifiers = field.getModifiers(); + assertTrue(Modifier.isPublic(modifiers)); + assertTrue(Modifier.isStatic(modifiers)); + assertTrue(Modifier.isFinal(modifiers)); + } + + @Test + public void dateTimeAsStringPropertyHasCorrectConstValue() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Field field = classWithDefaults.getField("DATE_TIME_AS_STRING_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getDateTimeAsStringWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + + assertThat((Date) value, is(equalTo(new Date(1298539523112L)))); + } + + @Test + public void utcmillisecPropertyHasCorrectConstValue() + throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, + NoSuchFieldException { + + Field field = classWithDefaults.getField("UTCMILLISEC_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getUtcmillisecWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + + assertThat((Long) value, is(equalTo(123456789L))); + } + + @Test + public void uriPropertyHasCorrectConstValue() throws Exception { + Field field = classWithDefaults.getField("URI_WITH_CONST"); + Method getter = classWithDefaults.getMethod("getUriWithConst"); + + assertPublicStaticFinal(field); + + Object value = field.get(null); + + assertThat((URI) value, is(URI.create("http://example.com"))); + } + @Test public void integerPropertyHasCorrectDefaultValue() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { @@ -270,7 +428,7 @@ public void simplePropertyCanHaveNullDefaultValue() throws NoSuchMethodException assertThat(getter.invoke(instance), is(nullValue())); } - + @Test public void arrayPropertyCanHaveNullDefaultValue() throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/default/default.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/default/default.json index 5d7102c5f..c8a3bc122 100644 --- a/jsonschema2pojo-integration-tests/src/test/resources/schema/default/default.json +++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/default/default.json @@ -1,6 +1,56 @@ { "type" : "object", "properties" : { + "emptyWithConst" : { + "type" : "string", + "const": "" + }, + "stringWithConst" : { + "type" : "string", + "const": "abc" + }, + "booleanWithConst" : { + "type" : "boolean", + "const": true + }, + "integerWithConst" : { + "type" : "integer", + "const": 1 + }, + "numberWithConst" : { + "type" : "number", + "const": 1.1 + }, + "dateTimeWithConst" : { + "type" : "string", + "format" : "date-time", + "const" : 123456789 + }, + "dateTimeAsStringWithConst" : { + "type" : "string", + "format" : "date-time", + "const" : "2011-02-24T09:25:23.112+0000" + }, + "dateAsStringWithConst" : { + "type" : "string", + "format" : "date", + "const" : "2015-03-04" + }, + "timeAsStringWithConst" : { + "type" : "string", + "format" : "time", + "const" : "16:15:00" + }, + "utcmillisecWithConst" : { + "type" : "integer", + "format" : "utc-millisec", + "const" : 123456789 + }, + "uriWithConst" : { + "type" : "string", + "format": "uri", + "const": "http://example.com" + }, "emptyStringWithDefault" : { "type" : "string", "default" : "" @@ -130,4 +180,4 @@ } } } -} \ No newline at end of file +}