org.jsonschema2pojo.rules.AdditionalPropertiesRule.java Source code

Java tutorial

Introduction

Here is the source code for org.jsonschema2pojo.rules.AdditionalPropertiesRule.java

Source

/**
 * Copyright  2010-2017 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 java.util.HashMap;
import java.util.Map;

import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.apache.commons.collections15.CollectionUtils;
import org.jsonschema2pojo.Schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

/**
 * Applies the "additionalProperties" JSON schema rule.
 *
 * @see <a href=
 *      "http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.6">http:/
 *      /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.6</a>
 */
public class AdditionalPropertiesRule implements Rule<JDefinedClass, JDefinedClass> {

    private final RuleFactory ruleFactory;

    protected AdditionalPropertiesRule(RuleFactory ruleFactory) {
        this.ruleFactory = ruleFactory;
    }

    /**
     * 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, JsonNode parent, 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, parent, 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, parent, field, schema);
        }

        if (ruleFactory.getGenerationConfig().isGenerateBuilders()) {
            addBuilder(jclass, propertyType, field);
        }

        return jclass;
    }

    private JFieldVar addAdditionalPropertiesField(JDefinedClass jclass, JType propertyType) {
        JClass propertiesMapType = jclass.owner().ref(Map.class);
        propertiesMapType = propertiesMapType.narrow(jclass.owner().ref(String.class), propertyType.boxify());

        JClass propertiesMapImplType = jclass.owner().ref(HashMap.class);
        propertiesMapImplType = propertiesMapImplType.narrow(jclass.owner().ref(String.class),
                propertyType.boxify());

        JFieldVar field = jclass.field(JMod.PRIVATE, propertiesMapType, "additionalProperties");

        ruleFactory.getAnnotator().additionalPropertiesField(field, jclass, "additionalProperties");

        field.init(JExpr._new(propertiesMapImplType));

        return field;
    }

    private void addSetter(JDefinedClass jclass, JType propertyType, JFieldVar field) {
        JMethod setter = jclass.method(JMod.PUBLIC, void.class, "setAdditionalProperty");

        ruleFactory.getAnnotator().anySetter(setter, jclass);

        JVar nameParam = setter.param(String.class, "name");
        JVar valueParam = setter.param(propertyType, "value");

        JInvocation mapInvocation = setter.body().invoke(JExpr._this().ref(field), "put");
        mapInvocation.arg(nameParam);
        mapInvocation.arg(valueParam);
    }

    private JMethod addGetter(JDefinedClass jclass, JFieldVar field) {
        JMethod getter = jclass.method(JMod.PUBLIC, field.type(), "getAdditionalProperties");

        ruleFactory.getAnnotator().anyGetter(getter, jclass);

        getter.body()._return(JExpr._this().ref(field));
        return getter;
    }

    private JMethod addBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {

        JMethod result = null;
        if (ruleFactory.getGenerationConfig().isUseInnerClassBuilders()) {
            result = addInnerBuilder(jclass, propertyType, field);
        } else {
            result = addLegacyBuilder(jclass, propertyType, field);
        }

        return result;
    }

    private JMethod addLegacyBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
        JMethod builder = jclass.method(JMod.PUBLIC, jclass, "withAdditionalProperty");

        JVar nameParam = builder.param(String.class, "name");
        JVar valueParam = builder.param(propertyType, "value");

        JBlock body = builder.body();
        JInvocation mapInvocation = body.invoke(JExpr._this().ref(field), "put");
        mapInvocation.arg(nameParam);
        mapInvocation.arg(valueParam);
        body._return(JExpr._this());

        return builder;
    }

    private JMethod addInnerBuilder(JDefinedClass jclass, JType propertyType, JFieldVar field) {
        Optional<JDefinedClass> builderClass = StreamSupport
                .stream(Spliterators.spliteratorUnknownSize(jclass.classes(), Spliterator.ORDERED), false)
                .filter(definedClass -> definedClass.name().equals(getBuilderClassName(jclass))).findFirst();

        JMethod builder = builderClass.get().method(JMod.PUBLIC, builderClass.get(), "withAdditionalProperty");

        JVar nameParam = builder.param(String.class, "name");
        JVar valueParam = builder.param(propertyType, "value");

        JBlock body = builder.body();
        JInvocation mapInvocation = body.invoke(JExpr.ref(JExpr.cast(jclass, JExpr._this().ref("instance")), field),
                "put");
        mapInvocation.arg(nameParam);
        mapInvocation.arg(valueParam);
        body._return(JExpr._this());

        return builder;
    }

    private String getBuilderClassName(JDefinedClass c) {
        return ruleFactory.getNameHelper().getBuilderClassName(c);
    }

}