@Override public org.jsonschema2pojo.rules.Rule<JPackage, JType> getObjectRule() { final org.jsonschema2pojo.rules.Rule<JPackage, JType> workingRule = super.getObjectRule(); return new org.jsonschema2pojo.rules.Rule<JPackage, JType>() { @Override public JType apply(String nodeName, JsonNode node, JPackage generatableType, Schema currentSchema) { JType objectType = workingRule.apply(nodeName, node, generatableType, currentSchema); if( objectType instanceof JDefinedClass ) { JDefinedClass jclass = (JDefinedClass)objectType; jclass.method(JMod.PUBLIC, jclass.owner().BOOLEAN, "brokenMethod").body(); } return objectType; } }; }
@Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations() && (node.has("minItems") || node.has("maxItems"))) { JAnnotationUse annotation = field.annotate(Size.class); if (node.has("minItems")) { annotation.param("min", node.get("minItems").asInt()); } if (node.has("maxItems")) { annotation.param("max", node.get("maxItems").asInt()); } } return field; }
/** * Applies this schema rule to take the required code generation steps. * <p> * Default values are implemented by assigning an expression to the given * field (so when instances of the generated POJO are created, its fields * will then contain their default values). * <p> * Collections (Lists and Sets) are initialized to an empty collection, even * when no default value is present in the schema (node is null). * * @param nodeName * the name of the property which has (or may have) a default * @param node * the default node (may be null if no default node was present * for this property) * @param field * the Java field that has added to a generated type to represent * this property * @return field, which will have an init expression is appropriate */ @Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { boolean defaultPresent = node != null && isNotEmpty(node.asText()); String fieldType = field.type().fullName(); if (defaultPresent && !field.type().isPrimitive() && node.isNull()) { field.init(JExpr._null()); } else if (fieldType.startsWith(List.class.getName())) { field.init(getDefaultList(field.type(), node)); } else if (fieldType.startsWith(Set.class.getName())) { field.init(getDefaultSet(field.type(), node)); } else if (fieldType.startsWith(String.class.getName()) && node != null ) { field.init(getDefaultValue(field.type(), node)); } else if (defaultPresent) { field.init(getDefaultValue(field.type(), node)); } return field; }
/** * Applies this schema rule to take the not required code generation steps. * <p> * The not required rule adds a Nullable annotation if JSR-305 annotations are desired. * * @param nodeName * the name of the schema node for which this "required" rule has * been added * @param node * the "not required" node, having a value <code>false</code> or * <code>no value</code> * @param generatableType * the class or method which may be marked as "not required" * @return the JavaDoc comment attached to the generatableType, which * <em>may</em> have an added not to mark this construct as * not required. */ @Override public JDocCommentable apply(String nodeName, JsonNode node, JDocCommentable generatableType, Schema schema) { // Since NotRequiredRule is executed for all fields that do not have "required" present, // we need to recognize whether the field is part of the RequiredArrayRule. JsonNode requiredArray = schema.getContent().get("required"); if (requiredArray != null) { for (Iterator<JsonNode> iterator = requiredArray.elements(); iterator.hasNext(); ) { String requiredArrayItem = iterator.next().asText(); if (nodeName.equals(requiredArrayItem)) { return generatableType; } } } if (ruleFactory.getGenerationConfig().isIncludeJsr305Annotations() && generatableType instanceof JFieldVar) { generatableType.javadoc().append(NOT_REQUIRED_COMMENT_TEXT); ((JFieldVar) generatableType).annotate(Nullable.class); } return generatableType; }
private void propertyAnnotations(String nodeName, JsonNode node, Schema schema, JDocCommentable generatedJavaConstruct) { if (node.has("title")) { ruleFactory.getTitleRule().apply(nodeName, node.get("title"), generatedJavaConstruct, schema); } if (node.has("javaName")) { ruleFactory.getJavaNameRule().apply(nodeName, node.get("javaName"), generatedJavaConstruct, schema); } if (node.has("description")) { ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), generatedJavaConstruct, schema); } if (node.has("required")) { ruleFactory.getRequiredRule().apply(nodeName, node.get("required"), generatedJavaConstruct, schema); } else { ruleFactory.getNotRequiredRule().apply(nodeName, node.get("required"), generatedJavaConstruct, schema); } }
/** * Applies this schema rule to take the required code generation steps. * <p> * For each property present within the properties node, this rule will * invoke the 'property' rule provided by the given schema mapper. * * @param nodeName * the name of the node for which properties are being added * @param node * the properties node, containing property names and their * definition * @param jclass * the Java type which will have the given properties added * @return the given jclass */ @Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema schema) { if (node == null) { node = JsonNodeFactory.instance.objectNode(); } for (Iterator<String> properties = node.fieldNames(); properties.hasNext(); ) { String property = properties.next(); ruleFactory.getPropertyRule().apply(property, node.get(property), jclass, schema); } if (ruleFactory.getGenerationConfig().isGenerateBuilders() && !jclass._extends().name().equals("Object")) { addOverrideBuilders(jclass, jclass.owner()._getClass(jclass._extends().fullName())); } ruleFactory.getAnnotator().propertyOrder(jclass, node); return jclass; }
/** * Applies this schema rule to take the required code generation steps. * <p> * The required rule simply adds a note to the JavaDoc comment to mark a * property as required. * * @param nodeName * the name of the schema node for which this "required" rule has * been added * @param node * the "required" node, having a value <code>true</code> or * <code>false</code> * @param generatableType * the class or method which may be marked as "required" * @return the JavaDoc comment attached to the generatableType, which * <em>may</em> have an added not to mark this construct as * required. */ @Override public JDocCommentable apply(String nodeName, JsonNode node, JDocCommentable generatableType, Schema schema) { if (node.asBoolean()) { generatableType.javadoc().append("\n(Required)"); if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations() && generatableType instanceof JFieldVar) { ((JFieldVar) generatableType).annotate(NotNull.class); } if (ruleFactory.getGenerationConfig().isIncludeJsr305Annotations() && generatableType instanceof JFieldVar) { ((JFieldVar) generatableType).annotate(Nonnull.class); } } else { if (ruleFactory.getGenerationConfig().isIncludeJsr305Annotations() && generatableType instanceof JFieldVar) { ((JFieldVar) generatableType).annotate(Nullable.class); } } return generatableType; }
@Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations() && (node.has("minLength") || node.has("maxLength"))) { JAnnotationUse annotation = field.annotate(Size.class); if (node.has("minLength")) { annotation.param("min", node.get("minLength").asInt()); } if (node.has("maxLength")) { annotation.param("max", node.get("maxLength").asInt()); } } return field; }
/** * Applies this schema rule to take the required code generation steps. * <p> * At the root of a schema document this rule should be applied (schema * documents contain a schema), but also in many places within the document. * Each property of type "object" is itself defined by a schema, the items * attribute of an array is a schema, the additionalProperties attribute of * a schema is also a schema. * <p> * Where the schema value is a $ref, the ref URI is assumed to be applicable * as a URL (from which content will be read). Where the ref URI has been * encountered before, the root Java type created by that schema will be * re-used (generation steps won't be repeated). * * @param schema * the schema within which this schema rule is being applied */ @Override public JType apply(String nodeName, JsonNode schemaNode, JClassContainer generatableType, Schema schema) { if (schemaNode.has("$ref")) { schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); schemaNode = schema.getContent(); if (schema.isGenerated()) { return schema.getJavaType(); } return apply(nodeName, schemaNode, generatableType, schema); } JType javaType; if (schemaNode.has("enum")) { javaType = ruleFactory.getEnumRule().apply(nodeName, schemaNode, generatableType, schema); } else { javaType = ruleFactory.getTypeRule().apply(nodeName, schemaNode, generatableType.getPackage(), schema); } schema.setJavaTypeIfEmpty(javaType); return javaType; }
private Schema getSuperSchema(JsonNode node, Schema schema, boolean followRefs) { if (node.has("extends")) { String path; if (schema.getId().getFragment() == null) { path = "#extends"; } else { path = "#" + schema.getId().getFragment() + "/extends"; } Schema superSchema = ruleFactory.getSchemaStore().create(schema, path, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); if (followRefs) { superSchema = resolveSchemaRefsRecursive(superSchema); } return superSchema; } return null; }
@Test public void applyGeneratesDate() { JPackage jpackage = new JCodeModel()._package(getClass().getPackage().getName()); ObjectNode objectNode = new ObjectMapper().createObjectNode(); objectNode.put("type", "string"); TextNode formatNode = TextNode.valueOf("date-time"); objectNode.set("format", formatNode); JType mockDateType = mock(JType.class); FormatRule mockFormatRule = mock(FormatRule.class); when(mockFormatRule.apply(eq("fooBar"), eq(formatNode), Mockito.isA(JType.class), isNull(Schema.class))).thenReturn(mockDateType); when(ruleFactory.getFormatRule()).thenReturn(mockFormatRule); JType result = rule.apply("fooBar", objectNode, jpackage, null); assertThat(result, equalTo(mockDateType)); }
@Test public void arrayWithUniqueItemsProducesSet() { JCodeModel codeModel = new JCodeModel(); JPackage jpackage = codeModel._package(getClass().getPackage().getName()); ObjectMapper mapper = new ObjectMapper(); ObjectNode itemsNode = mapper.createObjectNode(); itemsNode.put("type", "integer"); ObjectNode propertyNode = mapper.createObjectNode(); propertyNode.set("uniqueItems", BooleanNode.TRUE); propertyNode.set("items", itemsNode); JClass propertyType = rule.apply("fooBars", propertyNode, jpackage, mock(Schema.class)); assertThat(propertyType, notNullValue()); assertThat(propertyType.erasure(), is(codeModel.ref(Set.class))); assertThat(propertyType.getTypeParameters().get(0).fullName(), is(Integer.class.getName())); }
@Test public void arrayWithNonUniqueItemsProducesList() { JCodeModel codeModel = new JCodeModel(); JPackage jpackage = codeModel._package(getClass().getPackage().getName()); ObjectMapper mapper = new ObjectMapper(); ObjectNode itemsNode = mapper.createObjectNode(); itemsNode.put("type", "number"); ObjectNode propertyNode = mapper.createObjectNode(); propertyNode.set("uniqueItems", BooleanNode.FALSE); propertyNode.set("items", itemsNode); Schema schema = mock(Schema.class); when(schema.getId()).thenReturn(URI.create("http://example/nonUniqueArray")); when(config.isUseDoubleNumbers()).thenReturn(true); JClass propertyType = rule.apply("fooBars", propertyNode, jpackage, schema); assertThat(propertyType, notNullValue()); assertThat(propertyType.erasure(), is(codeModel.ref(List.class))); assertThat(propertyType.getTypeParameters().get(0).fullName(), is(Double.class.getName())); }
@Test public void arrayOfPrimitivesProducesCollectionOfWrapperTypes() { JCodeModel codeModel = new JCodeModel(); JPackage jpackage = codeModel._package(getClass().getPackage().getName()); ObjectMapper mapper = new ObjectMapper(); ObjectNode itemsNode = mapper.createObjectNode(); itemsNode.put("type", "number"); ObjectNode propertyNode = mapper.createObjectNode(); propertyNode.set("uniqueItems", BooleanNode.FALSE); propertyNode.set("items", itemsNode); Schema schema = mock(Schema.class); when(schema.getId()).thenReturn(URI.create("http://example/nonUniqueArray")); when(config.isUsePrimitives()).thenReturn(true); when(config.isUseDoubleNumbers()).thenReturn(true); JClass propertyType = rule.apply("fooBars", propertyNode, jpackage, schema); assertThat(propertyType, notNullValue()); assertThat(propertyType.erasure(), is(codeModel.ref(List.class))); assertThat(propertyType.getTypeParameters().get(0).fullName(), is(Double.class.getName())); }
@Test public void arrayDefaultsToNonUnique() { JCodeModel codeModel = new JCodeModel(); JPackage jpackage = codeModel._package(getClass().getPackage().getName()); ObjectMapper mapper = new ObjectMapper(); ObjectNode itemsNode = mapper.createObjectNode(); itemsNode.put("type", "boolean"); ObjectNode propertyNode = mapper.createObjectNode(); propertyNode.set("uniqueItems", BooleanNode.FALSE); propertyNode.set("items", itemsNode); Schema schema = mock(Schema.class); when(schema.getId()).thenReturn(URI.create("http://example/defaultArray")); JClass propertyType = rule.apply("fooBars", propertyNode, jpackage, schema); assertThat(propertyType.erasure(), is(codeModel.ref(List.class))); }
@Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations()) { JAnnotationUse annotation = field.annotate(Pattern.class); annotation.param("regexp", node.asText()); } return field; }
@Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations()) { field.annotate(Valid.class); } return field; }
private JsonNode resolveRefs(JsonNode node, Schema parent) { if (node.has("$ref")) { Schema refSchema = ruleFactory.getSchemaStore().create(parent, node.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); JsonNode refNode = refSchema.getContent(); return resolveRefs(refNode, parent); } else { return node; } }
@Override public JDocComment apply(String nodeName, JsonNode node, JDocCommentable generatableType, Schema currentSchema) { JDocComment javaDoc = generatableType.javadoc(); javaDoc.append(String.format("%nCorresponds to the \"%s\" property.", nodeName)); return javaDoc; }
/** * Recursive, walks the schema tree and assembles a list of all properties of this schema's super schemas */ private LinkedHashSet<String> getSuperTypeConstructorPropertiesRecursive(JsonNode node, Schema schema, boolean onlyRequired) { Schema superTypeSchema = getSuperSchema(node, schema, true); if (superTypeSchema == null) { return new LinkedHashSet<String>(); } JsonNode superSchemaNode = superTypeSchema.getContent(); LinkedHashSet<String> rtn = getConstructorProperties(superSchemaNode, onlyRequired); rtn.addAll(getSuperTypeConstructorPropertiesRecursive(superSchemaNode, superTypeSchema, onlyRequired)); return rtn; }
private JType getSuperType(String nodeName, JsonNode node, JPackage jPackage, Schema schema) { if (node.has("extends") && node.has("extendsJavaClass")) { throw new IllegalStateException("'extends' and 'extendsJavaClass' defined simultaneously"); } JType superType = jPackage.owner().ref(Object.class); Schema superTypeSchema = getSuperSchema(node, schema, false); if (superTypeSchema != null) { superType = ruleFactory.getSchemaRule().apply(nodeName + "Parent", node.get("extends"), jPackage, superTypeSchema); } else if (node.has("extendsJavaClass")) { superType = resolveType(jPackage, node.get("extendsJavaClass").asText()); } return superType; }
private Schema resolveSchemaRefsRecursive(Schema schema) { JsonNode schemaNode = schema.getContent(); if (schemaNode.has("$ref")) { schema = ruleFactory.getSchemaStore().create(schema, schemaNode.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); return resolveSchemaRefsRecursive(schema); } return schema; }
@Test public void selfRefWithoutParentFile() throws IOException { JCodeModel codeModel = new JCodeModel(); JsonNode schema = new ObjectMapper().readTree("{\"type\":\"object\", \"properties\":{\"a\":{\"$ref\":\"#/b\"}}, \"b\":\"string\"}"); JPackage p = codeModel._package("com.example"); new RuleFactory().getSchemaRule().apply("Example", schema, p, new Schema(null, schema, schema)); }
@Test public void refToInnerFragmentThatHasRefToOuterFragmentWithoutParentFile() throws IOException, ClassNotFoundException { JCodeModel codeModel = new JCodeModel(); JsonNode schema = new ObjectMapper().readTree("{\n" + " \"type\": \"object\",\n" + " \"definitions\": {\n" + " \"location\": {\n" + " \"type\": \"object\",\n" + " \"properties\": {\n" + " \"cat\": {\n" + " \"$ref\": \"#/definitions/cat\"\n" + " }\n" + " }\n" + " },\n" + " \"cat\": {\n" + " \"type\": \"number\"\n" + " }\n" + " },\n" + " \"properties\": {\n" + " \"location\": {\n" + " \"$ref\": \"#/definitions/location\"\n" + " }\n" + " }\n" + "}"); JPackage p = codeModel._package("com.example"); new RuleFactory().getSchemaRule().apply("Example", schema, p, new Schema(null, schema, schema)); }
@Override public Rule<JType, JType> getFormatRule() { return new FormatRule(this) { @Override public JType apply(String nodeName, JsonNode node, JType baseType, Schema schema) { if (node.asText().equals("date")) { return baseType.owner().ref(LocalDate.class); } return super.apply(nodeName, node, baseType, schema); } }; }
@Override public Rule<JDefinedClass, JDefinedClass> getPropertiesRule() { return new PropertiesRule(this){ @Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema schema) { new APXCustomRule(APXCustomRuleFactory.this).apply(nodeName, node, jclass, schema); return super.apply(nodeName, node, jclass, schema); } }; }
/** * This rule adds dynamic getter, setter and builder methods based on the properties and additional properties * defined in a schema. * <p> * If accessors are being generated, then methods for getting and setting properties by name will be added. These * methods first attempt to call the appropriate getter or setter for the property. If the named property is not defined, * then the additional properties map is used. * <p> * If builders are being generated, then a method for building properties by name will be added. This method first * attempts to call the builder for the property. If no property with the supplied name is defined, then the additional * properties map is used. * <p> * The methods generated by this class throw an IllegalArgumentException, if the name specified for the property is unknown and * additional properties are not enabled. A ClassCastException will be thrown, when the value being set is incompatible with the * type of the named property. * * @param nodeName * the name of the node for which dynamic getters, setters, and builders are being added. * @param node * the properties node, containing property names and their * definition * @param jclass * the Java type which will have the given properties added * @param currentSchema * the schema being implemented * @return the given jclass */ @Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema currentSchema) { if (!ruleFactory.getGenerationConfig().isIncludeDynamicAccessors() || (!ruleFactory.getGenerationConfig().isIncludeDynamicSetters() && !ruleFactory.getGenerationConfig().isIncludeDynamicGetters() && !ruleFactory.getGenerationConfig().isIncludeDynamicBuilders())) { return jclass; } boolean isIncludeAccessors = ruleFactory.getGenerationConfig().isIncludeAccessors(); boolean isIncludeGetters = ruleFactory.getGenerationConfig().isIncludeGetters(); boolean isIncludeSetters = ruleFactory.getGenerationConfig().isIncludeSetters(); boolean isGenerateBuilders = ruleFactory.getGenerationConfig().isGenerateBuilders(); if (isIncludeAccessors || isIncludeGetters || isIncludeSetters || isGenerateBuilders) { if (LanguageFeatures.canUseJava7(ruleFactory.getGenerationConfig())) { if (isIncludeAccessors || isIncludeSetters) { addInternalSetMethodJava7(jclass, node); } if (isIncludeAccessors || isIncludeGetters) { addInternalGetMethodJava7(jclass, node); } } else { if (isIncludeAccessors || isIncludeSetters) { addInternalSetMethodJava6(jclass, node); } if (isIncludeAccessors || isIncludeGetters) { addInternalGetMethodJava6(jclass, node); } } } if (isIncludeAccessors || isIncludeGetters) { addGetMethods(jclass); } if (isIncludeAccessors || isIncludeSetters) { addSetMethods(jclass); } if (isGenerateBuilders) { addWithMethods(jclass); } return jclass; }
/** * Applies this schema rule to take the required code generation steps. * <p> * A Java {@link Enum} is created, with constants for each of the enum * values present in the schema. The enum name is derived from the nodeName, * and the enum type itself is created as an inner class of the owning type. * In the rare case that no owning type exists (the enum is the root of the * schema), then the enum becomes a public class in its own right. * <p> * The actual JSON value for each enum constant is held in a property called * "value" in the generated type. A static factory method * <code>fromValue(String)</code> is added to the generated enum, and the * methods are annotated to allow Jackson to marshal/unmarshal values * correctly. * * @param nodeName * the name of the property which is an "enum" * @param node * the enum node * @param container * the class container (class or package) to which this enum * should be added * @return the newly generated Java type that was created to represent the * given enum */ @Override public JType apply(String nodeName, JsonNode node, JClassContainer container, Schema schema) { JDefinedClass _enum; try { _enum = createEnum(node, nodeName, container); } catch (ClassAlreadyExistsException e) { return e.getExistingClass(); } schema.setJavaTypeIfEmpty(_enum); if (node.has("javaInterfaces")) { addInterfaces(_enum, node.get("javaInterfaces")); } // copy our node; remove the javaType as it will throw off the TypeRule for our case ObjectNode typeNode = (ObjectNode)node.deepCopy(); typeNode.remove("javaType"); // If type is specified on the enum, get a type rule for it. Otherwise, we're a string. // (This is different from the default of Object, which is why we don't do this for every case.) JType backingType = node.has("type") ? ruleFactory.getTypeRule().apply(nodeName, typeNode, container, schema) : container.owner().ref(String.class); JFieldVar valueField = addValueField(_enum, backingType); // override toString only if we have a sensible string to return if(isString(backingType)){ addToString(_enum, valueField); } addValueMethod(_enum, valueField); addEnumConstants(node.path("enum"), _enum, node.path("javaEnumNames"), backingType); addFactoryMethod(_enum, backingType); return _enum; }
@Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema schema) { List<String> requiredFieldMethods = new ArrayList<String>(); JsonNode properties = schema.getContent().get("properties"); for (Iterator<JsonNode> iterator = node.elements(); iterator.hasNext(); ) { String requiredArrayItem = iterator.next().asText(); if (requiredArrayItem.isEmpty()) { continue; } JsonNode propertyNode = null; if (properties != null) { propertyNode = properties.findValue(requiredArrayItem); } String fieldName = ruleFactory.getNameHelper().getPropertyName(requiredArrayItem, propertyNode); JFieldVar field = jclass.fields().get(fieldName); if (field == null) { continue; } addJavaDoc(field); if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations()) { addNotNullAnnotation(field); } if (ruleFactory.getGenerationConfig().isIncludeJsr305Annotations()) { addNonnullAnnotation(field); } requiredFieldMethods.add(getGetterName(fieldName, field.type(), node)); requiredFieldMethods.add(getSetterName(fieldName, node)); } updateGetterSetterJavaDoc(jclass, requiredFieldMethods); return jclass; }
/** * Applies this schema rule to take the required code generation steps. * <p> * This rule adds a property to a given Java class according to the Java * Bean spec. A private field is added to the class, along with accompanying * accessor methods. * <p> * If this rule's schema mapper is configured to include builder methods * (see {@link GenerationConfig#isGenerateBuilders()} ), * then a builder method of the form <code>withFoo(Foo foo);</code> is also * added. * * @param nodeName * the name of the property to be applied * @param node * the node describing the characteristics of this property * @param jclass * the Java class which should have this property added * @return the given jclass */ @Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema schema) { String propertyName = ruleFactory.getNameHelper().getPropertyName(nodeName, node); JType propertyType = ruleFactory.getSchemaRule().apply(nodeName, node, jclass, schema); boolean isIncludeAccessors = ruleFactory.getGenerationConfig().isIncludeAccessors(); boolean isIncludeGetters = ruleFactory.getGenerationConfig().isIncludeGetters(); boolean isIncludeSetters = ruleFactory.getGenerationConfig().isIncludeSetters(); node = resolveRefs(node, schema); int accessModifier = isIncludeAccessors || isIncludeGetters || isIncludeSetters ? JMod.PRIVATE : JMod.PUBLIC; JFieldVar field = jclass.field(accessModifier, propertyType, propertyName); propertyAnnotations(nodeName, node, schema, field); formatAnnotation(field, node); ruleFactory.getAnnotator().propertyField(field, jclass, nodeName, node); if (isIncludeAccessors || isIncludeGetters) { JMethod getter = addGetter(jclass, field, nodeName, node); ruleFactory.getAnnotator().propertyGetter(getter, nodeName); propertyAnnotations(nodeName, node, schema, getter); } if (isIncludeAccessors || isIncludeSetters) { JMethod setter = addSetter(jclass, field, nodeName, node); ruleFactory.getAnnotator().propertySetter(setter, nodeName); propertyAnnotations(nodeName, node, schema, setter); } if (ruleFactory.getGenerationConfig().isGenerateBuilders()) { addBuilder(jclass, field); } if (node.has("pattern")) { ruleFactory.getPatternRule().apply(nodeName, node.get("pattern"), field, schema); } ruleFactory.getDefaultRule().apply(nodeName, node.get("default"), field, schema); ruleFactory.getMinimumMaximumRule().apply(nodeName, node, field, schema); ruleFactory.getMinItemsMaxItemsRule().apply(nodeName, node, field, schema); ruleFactory.getMinLengthMaxLengthRule().apply(nodeName, node, field, schema); if (isObject(node) || isArray(node)) { ruleFactory.getValidRule().apply(nodeName, node, field, schema); } return jclass; }
/** * Applies this schema rule to take the required code generation steps. * <p> * When applied, this rule reads the details of the given node to determine * the appropriate Java type to return. This may be a newly generated type, * it may be a primitive type or other type such as {@link java.lang.String} * or {@link java.lang.Object}. * <p> * JSON schema types and their Java type equivalent: * <ul> * <li>"type":"any" => {@link java.lang.Object} * <li>"type":"array" => Either {@link java.util.Set} or * {@link java.util.List}, see {@link ArrayRule} * <li>"type":"boolean" => <code>boolean</code> * <li>"type":"integer" => <code>int</code> * <li>"type":"null" => {@link java.lang.Object} * <li>"type":"number" => <code>double</code> * <li>"type":"object" => Generated type (see {@link ObjectRule}) * <li>"type":"string" => {@link java.lang.String} (or alternative based * on presence of "format", see {@link FormatRule}) * </ul> * * @param nodeName * the name of the node for which this "type" rule applies * @param node * the node for which this "type" rule applies * @param jClassContainer * the package into which any newly generated type may be placed * @return the Java type which, after reading the details of the given * schema node, most appropriately matches the "type" specified */ @Override public JType apply(String nodeName, JsonNode node, JClassContainer jClassContainer, Schema schema) { String propertyTypeName = getTypeName(node); JType type; if (propertyTypeName.equals("object") || node.has("properties") && node.path("properties").size() > 0) { type = ruleFactory.getObjectRule().apply(nodeName, node, jClassContainer.getPackage(), schema); } else if (node.has("javaType")) { String typeName = node.path("javaType").asText(); if (isPrimitive(typeName, jClassContainer.owner())) { type = primitiveType(typeName, jClassContainer.owner()); } else { type = resolveType(jClassContainer, typeName); } } else if (propertyTypeName.equals("string")) { type = jClassContainer.owner().ref(String.class); } else if (propertyTypeName.equals("number")) { type = getNumberType(jClassContainer.owner(), ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("integer")) { type = getIntegerType(jClassContainer.owner(), node, ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("boolean")) { type = unboxIfNecessary(jClassContainer.owner().ref(Boolean.class), ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("array")) { type = ruleFactory.getArrayRule().apply(nodeName, node, jClassContainer.getPackage(), schema); } else { type = jClassContainer.owner().ref(Object.class); } if (!node.has("javaType") && node.has("format")) { type = ruleFactory.getFormatRule().apply(nodeName, node.get("format"), type, schema); } else if (!node.has("javaType") && propertyTypeName.equals("string") && node.has("media")) { type = ruleFactory.getMediaRule().apply(nodeName, node.get("media"), type, schema); } return type; }
/** * Applies this schema rule to take the required code generation steps. * <p> * If additionalProperties is specified and set to the boolean value * <code>false</code>, this rule does not make any change to the generated * Java type (the type does not allow additional properties). * <p> * If the additionalProperties node is <code>null</code> (not specified in * the schema) or empty, then a new bean property named * "additionalProperties", of type {@link Map}{@literal <String,Object>} is * added to the generated type (with appropriate accessors). The accessors * are annotated to allow unrecognised (additional) properties found in JSON * data to be marshalled/unmarshalled from/to this map. * <p> * If the additionalProperties node is present and specifies a schema, then * an "additionalProperties" map is added to the generated type. This time * the map values will be restricted and must be instances of a newly * generated Java type that will be created based on the * additionalProperties schema provided. If the schema does not specify the * javaType property, the name of the newly generated type will be derived * from the nodeName and the suffix 'Property'. * * @param nodeName * the name of the schema node for which the additionalProperties * node applies * @param node * the additionalProperties node itself, found in the schema (may * be null if not specified in the schema) * @param jclass * the Java type that is being generated to represent this schema * @return the given Java type jclass */ @Override public JDefinedClass apply(String nodeName, JsonNode node, JDefinedClass jclass, Schema schema) { if (node != null && node.isBoolean() && node.asBoolean() == false) { // no additional properties allowed return jclass; } if (!this.ruleFactory.getGenerationConfig().isIncludeAdditionalProperties()) { // no additional properties allowed return jclass; } if (!ruleFactory.getAnnotator().isAdditionalPropertiesSupported()) { // schema allows additional properties, but serializer library can't support them return jclass; } JType propertyType; if (node != null && node.size() != 0) { propertyType = ruleFactory.getSchemaRule().apply(nodeName + "Property", node, jclass, schema); } else { propertyType = jclass.owner().ref(Object.class); } JFieldVar field = addAdditionalPropertiesField(jclass, propertyType); addGetter(jclass, field); addSetter(jclass, propertyType, field); if (ruleFactory.getGenerationConfig().isIncludeJsr303Annotations()) { ruleFactory.getValidRule().apply(nodeName, node, field, schema); } if (ruleFactory.getGenerationConfig().isGenerateBuilders()) { addBuilder(jclass, propertyType, field); } return jclass; }
/** * Applies this schema rule to take the required code generation steps. * <p> * When this rule is applied for schemas of type object, the properties of * the schema are used to generate a new Java class and determine its * characteristics. See other implementers of {@link Rule} for details. */ @Override public JType apply(String nodeName, JsonNode node, JPackage _package, Schema schema) { JType superType = getSuperType(nodeName, node, _package, schema); if (superType.isPrimitive() || isFinal(superType)) { return superType; } JDefinedClass jclass; try { jclass = createClass(nodeName, node, _package); } catch (ClassAlreadyExistsException e) { return e.getExistingClass(); } jclass._extends((JClass) superType); schema.setJavaTypeIfEmpty(jclass); if (node.has("deserializationClassProperty")) { addJsonTypeInfoAnnotation(jclass, node); } if (node.has("title")) { ruleFactory.getTitleRule().apply(nodeName, node.get("title"), jclass, schema); } if (node.has("description")) { ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), jclass, schema); } ruleFactory.getPropertiesRule().apply(nodeName, node.get("properties"), jclass, schema); if (node.has("javaInterfaces")) { addInterfaces(jclass, node.get("javaInterfaces")); } ruleFactory.getAdditionalPropertiesRule().apply(nodeName, node.get("additionalProperties"), jclass, schema); ruleFactory.getDynamicPropertiesRule().apply(nodeName, node.get("properties"), jclass, schema); if (node.has("required")) { ruleFactory.getRequiredArrayRule().apply(nodeName, node.get("required"), jclass, schema); } if (ruleFactory.getGenerationConfig().isIncludeToString()) { addToString(jclass); } if (ruleFactory.getGenerationConfig().isIncludeHashcodeAndEquals()) { addHashCode(jclass, node); addEquals(jclass, node); } if (ruleFactory.getGenerationConfig().isParcelable()) { addParcelSupport(jclass); } if (ruleFactory.getGenerationConfig().isIncludeConstructors()) { addConstructors(jclass, node, schema, ruleFactory.getGenerationConfig().isConstructorsRequiredPropertiesOnly()); } if (ruleFactory.getGenerationConfig().isSerializable()) { SerializableHelper.addSerializableSupport(jclass); } return jclass; }
@Test public void shouldUpdateJavaDoc() throws JClassAlreadyExistsException { JDefinedClass jclass = new JCodeModel()._class(TARGET_CLASS_NAME); jclass.field(JMod.PRIVATE, jclass.owner().ref(String.class), "fooBar"); jclass.field(JMod.PRIVATE, jclass.owner().ref(String.class), "foo"); ObjectMapper mapper = new ObjectMapper(); ArrayNode requiredNode = mapper.createArrayNode().add("fooBar"); rule.apply("Class", requiredNode, jclass, new Schema(null, requiredNode, requiredNode)); JDocComment fooBarJavaDoc = jclass.fields().get("fooBar").javadoc(); JDocComment fooJavaDoc = jclass.fields().get("foo").javadoc(); assertThat(fooBarJavaDoc.size(), is(1)); assertThat((String) fooBarJavaDoc.get(0), is("\n(Required)")); assertThat(fooJavaDoc.size(), is(0)); }
@Test public void shouldUpdateAnnotations() throws JClassAlreadyExistsException { setupRuleFactoryToIncludeJsr303(); JDefinedClass jclass = new JCodeModel()._class(TARGET_CLASS_NAME); jclass.field(JMod.PRIVATE, jclass.owner().ref(String.class), "fooBar"); jclass.field(JMod.PRIVATE, jclass.owner().ref(String.class), "foo"); ObjectMapper mapper = new ObjectMapper(); ArrayNode requiredNode = mapper.createArrayNode().add("foo_bar"); rule.apply("Class", requiredNode, jclass, new Schema(null, requiredNode, requiredNode)); Collection<JAnnotationUse> fooBarAnnotations = jclass.fields().get("fooBar").annotations(); Collection<JAnnotationUse> fooAnnotations = jclass.fields().get("foo").annotations(); assertThat(fooBarAnnotations.size(), is(1)); assertThat(fooBarAnnotations.iterator().next().getAnnotationClass().name(), is(NotNull.class.getSimpleName())); assertThat(fooAnnotations.size(), is(0)); }
@Test public void enumAsRootIsGeneratedCorrectly() throws URISyntaxException, JClassAlreadyExistsException { ObjectNode schemaContent = new ObjectMapper().createObjectNode(); ObjectNode enumNode = schemaContent.objectNode(); enumNode.put("type", "string"); schemaContent.set("enum", enumNode); JDefinedClass jclass = new JCodeModel()._class(TARGET_CLASS_NAME); Schema schema = mock(Schema.class); when(schema.getContent()).thenReturn(schemaContent); schema.setJavaTypeIfEmpty(jclass); EnumRule enumRule = mock(EnumRule.class); when(mockRuleFactory.getEnumRule()).thenReturn(enumRule); when(enumRule.apply(NODE_NAME, enumNode, jclass, schema)).thenReturn(jclass); rule.apply(NODE_NAME, schemaContent, jclass, schema); verify(enumRule).apply(NODE_NAME, schemaContent, jclass, schema); verify(schema, atLeastOnce()).setJavaTypeIfEmpty(jclass); }
@Test public void existingTypeIsUsedWhenTypeIsAlreadyGenerated() throws URISyntaxException { JType previouslyGeneratedType = mock(JType.class); URI schemaUri = getClass().getResource("/schema/address.json").toURI(); SchemaStore schemaStore = new SchemaStore(); Schema schema = schemaStore.create(schemaUri, "#/."); schema.setJavaType(previouslyGeneratedType); final GenerationConfig mockGenerationConfig = mock(GenerationConfig.class); when(mockGenerationConfig.getRefFragmentPathDelimiters()).thenReturn("#/."); when(mockRuleFactory.getSchemaStore()).thenReturn(schemaStore); when(mockRuleFactory.getGenerationConfig()).thenReturn(mockGenerationConfig); ObjectNode schemaNode = new ObjectMapper().createObjectNode(); schemaNode.put("$ref", schemaUri.toString()); JType result = rule.apply(NODE_NAME, schemaNode, null, schema); assertThat(result, is(sameInstance(previouslyGeneratedType))); }
@Override public JDefinedClass apply(String string, JsonNode jn, JDefinedClass t, Schema schema) { JFieldVar idField = t.field(JMod.PRIVATE, int.class, "id"); JMethod getter = t.method(JMod.PUBLIC, idField.type(), "getId"); getter.body()._return(idField); idField.javadoc().add("This is compulsory for both the database and to access models"); JMethod setter = t.method(JMod.PUBLIC, void.class, "setId"); setter.param(idField.type(), idField.name()) .assign(idField); idField.annotate(DatabaseField.class) .param("generatedId", true) .param("columnName", idField.name()); idField.annotate(SerializedName.class) .param("value", "id"); return t; }
/** * Applies this schema rule to take the required code generation steps. * <p> * Default values are implemented by assigning an expression to the given * field (so when instances of the generated POJO are created, its fields * will then contain their default values). * <p> * Collections (Lists and Sets) are initialized to an empty collection, even * when no default value is present in the schema (node is null). * * @param nodeName * the name of the property which has (or may have) a default * @param node * the default node (may be null if no default node was present * for this property) * @param field * the Java field that has added to a generated type to represent * this property * @return field, which will have an init expression is appropriate */ @Override public JFieldVar apply(String nodeName, JsonNode node, JFieldVar field, Schema currentSchema) { boolean defaultPresent = node != null && isNotEmpty(node.asText()); String fieldType = field.type().fullName(); if (defaultPresent && !field.type().isPrimitive() && node.isNull()) { field.init(JExpr._null()); } else if (fieldType.startsWith(List.class.getName())) { field.init(getDefaultList(field.type(), node)); } else if (fieldType.startsWith(Set.class.getName())) { field.init(getDefaultSet(field.type(), node)); } else if (defaultPresent) { field.init(getDefaultValue(field.type(), node)); } return field; }
/** * Applies this schema rule to take the required code generation steps. * <p> * When applied, this rule reads the details of the given node to determine * the appropriate Java type to return. This may be a newly generated type, * it may be a primitive type or other type such as {@link java.lang.String} * or {@link java.lang.Object}. * <p> * JSON schema types and their Java type equivalent: * <ul> * <li>"type":"any" => {@link java.lang.Object} * <li>"type":"array" => Either {@link java.util.Set} or * <li>"type":"boolean" => <code>boolean</code> * <li>"type":"integer" => <code>int</code> * <li>"type":"null" => {@link java.lang.Object} * <li>"type":"number" => <code>double</code> * <li>"type":"object" => Generated type (see {@link org.jsonschema2pojo.rules.ObjectRule}) * {@link java.util.List}, see {@link org.jsonschema2pojo.rules.ArrayRule} * <li>"type":"string" => {@link java.lang.String} (or alternative based on * presence of "format", see {@link org.jsonschema2pojo.rules.FormatRule}) * </ul> * * @param nodeName * the name of the node for which this "type" rule applies * @param node * the node for which this "type" rule applies * @param jClassContainer * the package into which any newly generated type may be placed * @return the Java type which, after reading the details of the given * schema node, most appropriately matches the "type" specified */ @Override public JType apply(String nodeName, JsonNode node, JClassContainer jClassContainer, Schema schema) { String propertyTypeName = getTypeName(node); JType type; if (propertyTypeName.equals("string")) { type = jClassContainer.owner().ref(String.class); } else if (propertyTypeName.equals("number")) { JType typeToUseForNumbers = getNumberType(jClassContainer.owner(), ruleFactory.getGenerationConfig()); type = unboxIfNecessary(typeToUseForNumbers, ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("integer")) { JType typeToUseForIntegers = getIntegerType(jClassContainer.owner(), ruleFactory.getGenerationConfig()); type = unboxIfNecessary(typeToUseForIntegers, ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("short")) { JType typeToUseForShorts = jClassContainer.owner().ref(Short.class); type = unboxIfNecessary(typeToUseForShorts, ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("byte")) { JType typeToUseForBytes = jClassContainer.owner().ref(Byte.class); type = unboxIfNecessary(typeToUseForBytes, ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("boolean")) { type = unboxIfNecessary(jClassContainer.owner().ref(Boolean.class), ruleFactory.getGenerationConfig()); } else if (propertyTypeName.equals("object") || (node.has("properties") && node.path("properties").size() > 0)) { type = ruleFactory.getObjectRule().apply(nodeName, node, jClassContainer.getPackage(), schema); } else if (propertyTypeName.equals("array")) { type = ruleFactory.getArrayRule().apply(nodeName, node, jClassContainer.getPackage(), schema); } else { type = jClassContainer.owner().ref(Object.class); } if (node.has("format")) { type = ruleFactory.getFormatRule().apply(nodeName, node.get("format"), type, schema); } else if(propertyTypeName.equals("string") && node.has("media")) { type = ruleFactory.getMediaRule().apply(nodeName, node.get("media"), type, schema); } return type; }