org.jsonschema2pojo.rules.OneOfRule.java Source code

Java tutorial

Introduction

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

Source

/**
 * Copyright  2010-2014 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 static java.util.Arrays.asList;
import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.containsOnly;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.join;
import static org.apache.commons.lang3.StringUtils.splitByCharacterTypeCamelCase;
import static org.apache.commons.lang3.StringUtils.upperCase;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Generated;

import org.jsonschema2pojo.Schema;
import org.jsonschema2pojo.SchemaMapper;
import org.jsonschema2pojo.exception.GenerationException;
import org.jsonschema2pojo.util.ParcelableHelper;
import org.jsonschema2pojo.util.SerializableHelper;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCatchBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JClassContainer;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JEnumConstant;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JForEach;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JStatement;
import com.sun.codemodel.JTryBlock;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

/**
 * Applies the generation steps required for schemas of type "oneOf". Java
 * {@link java.lang.reflect.Proxy} won't work while generated classes have same
 * signature of methods. To implement "oneOf", a inner enum {@code ONE_OF} lists
 * all candidates of "oneOf" and capture the signature of all fields. And static
 * factory method {@code fromValue} will convert json string into one of the
 * option instance by matching their required and optional fields.
 * <p>
 * TODO: Parcel and parent schema are not supported.
 * </p>
 * <p>
 * Note: "required" value is not populated while generating @JsonProperty, this
 * need to be fixed to increase the accuracy while choosing which "oneOf" option
 * to use.
 * </p>
 * 
 * @see <a href=
 *      "http://json-schema.org/latest/json-schema-validation.html#anchor88">
 *      http://json-schema.org/latest/json-schema-validation.html#anchor88</a>
 * @author Chengwei.Yan
 */
public class OneOfRule implements Rule<JPackage, JType> {

    private static final String GETTER_VALUE_NAME = "getValue";
    private static final String GETTER_ONE_OF_NAME = "oneOf";
    private static final String FACTORY_METHOD_NAME = "fromValue";
    private static final String ONE_OF = "oneOf";
    private static final String REF = "$ref";
    private static final String ONE_OF_ENUM_NAME = "ONE_OF";
    private static final String ONE_OF_VALUE_FIELD_NAME = "value";
    private static final String ENUM_CONSTANT_NAME = "value";
    private static final String CLASS_FIELD_NAME = "clazz";
    private static final String REQUIRED_FIELD_NAME = "requiredFields";
    private static final String OPTIONAL_FIELD_NAME = "optionalFields";
    /* Indices for anonymous class defined in "oneOf" body. */
    private static final Map<String, AtomicInteger> INDICES = new HashMap<String, AtomicInteger>();
    private final RuleFactory ruleFactory;

    /**
     * Factory constructor.
     */
    protected OneOfRule(RuleFactory ruleFactory, ParcelableHelper parcelableHelper) {
        this.ruleFactory = ruleFactory;
    }

    /**
     * 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.
     * <p>
     * A new Java type will be created when this rule is applied, it is
     * annotated as {@link Generated}, it is given <code>equals</code>,
     * <code>hashCode</code> and <code>toString</code> methods and implements
     * {@link Serializable}.
     */
    @Override
    public JType apply(String nodeName, JsonNode node, JPackage _package, Schema schema) {
        // Public modifier ...
        int modifier = _package.isPackage() ? JMod.PUBLIC : JMod.PUBLIC;
        JDefinedClass oneOf; // oneOf class
        JDefinedClass oneOfOptions; // enum of options
        try {
            oneOf = _package._class(modifier, getClassName(nodeName, node, _package), ClassType.CLASS);
            oneOfOptions = oneOf._class(modifier, ONE_OF_ENUM_NAME, ClassType.ENUM);
        } catch (JClassAlreadyExistsException e) {
            throw new GenerationException("Duplicate oneOf option '" + nodeName + "'.");
        }

        schema.setJavaTypeIfEmpty(oneOf);
        addGeneratedAnnotation(oneOf);

        // Add enum options ...
        addOneOves(nodeName, node, _package, schema, oneOfOptions);

        // Add static creator factory method ...
        addFactoryMethod(oneOf, oneOfOptions);

        // Add constructor, creator and value getters ...
        addMethods(modifier, oneOf, oneOfOptions);

        if (ruleFactory.getGenerationConfig().isSerializable()) {
            SerializableHelper.addSerializableSupport(oneOf);
        }

        // hash and equal
        if (ruleFactory.getGenerationConfig().isIncludeHashcodeAndEquals()) {
            addHashCode(oneOf);
            addEquals(oneOf);
        }

        return oneOf;

    }

    /**
     * Add parameterized constructor and value getter.
     * 
     * @param modifier
     *            The modifier.
     * @param oneOf
     *            The generated oneOf class.
     * @param oneOfOptions
     *            The generated oneOf option enum.
     */
    private void addMethods(int modifier, JDefinedClass oneOf, JDefinedClass oneOfOptions) {
        JClass jsonValue = oneOf.owner().ref(JsonValue.class);
        // Add enum field
        JFieldVar oneOfField = oneOf.field(JMod.PRIVATE | JMod.FINAL, oneOfOptions, ONE_OF);
        // Add value field
        JFieldVar valueField = oneOf.field(JMod.PRIVATE | JMod.FINAL, oneOf.owner().ref(Object.class),
                ONE_OF_VALUE_FIELD_NAME);

        // Constructor
        JMethod constructor = oneOf.constructor(modifier);

        JVar oneOfVar = constructor.param(oneOfOptions, ONE_OF);
        JVar valueVar = constructor.param(Object.class, ONE_OF_VALUE_FIELD_NAME);

        JBlock body = constructor.body();
        body.assign(JExpr._this().ref(oneOfField), oneOfVar);
        body.assign(JExpr._this().ref(valueField), valueVar);

        // Create getter ...
        createGetter(oneOf, jsonValue, valueField);

        // Add getter for oneOf Type ...
        JMethod getter = oneOf.method(JMod.PUBLIC, oneOfOptions, GETTER_ONE_OF_NAME);
        JBlock getterBody = getter.body();
        getterBody._return(JExpr._this().ref(oneOfField));
    }

    /**
     * Create getter method.
     * 
     * @param oneOf
     * @param jsonValue
     * @param valueField
     */
    private void createGetter(JDefinedClass oneOf, JClass jsonValue, JFieldVar valueField) {
        // Add getter ...
        JMethod getter = oneOf.method(JMod.PUBLIC, Object.class, GETTER_VALUE_NAME);
        JBlock getterBody = getter.body();
        getterBody._return(JExpr._this().ref(valueField));
        getter.annotate(jsonValue);
    }

    /**
     * Add "oneOf" option.
     * 
     * @param nodeName
     *            The "oneOf" field name.
     * @param node
     *            The option node.
     * @param _package
     * @param schema
     * @param oneOfOptions
     *            The defined enum.
     */
    private void addOneOves(final String nodeName, JsonNode node, JPackage _package, Schema schema,
            JDefinedClass oneOfOptions) {
        schema.setJavaTypeIfEmpty(oneOfOptions);
        JFieldVar enumFields = addEnumFields(oneOfOptions);
        addToString(oneOfOptions, enumFields);

        // List options ...
        ArrayNode oneOfs = (ArrayNode) node.path(ONE_OF);
        Iterator<JsonNode> it = oneOfs.elements();
        while (it.hasNext()) {
            final JsonNode optionNode = it.next();
            if (optionNode.has(REF)) {
                // Parsing reference ...
                JsonNode n = resolveRefs(optionNode, schema);
                if (isObject(n)) {
                    String referType = optionNode.get(REF).asText();
                    referType = referType.substring(referType.lastIndexOf('/') + 1);
                    JType type = ruleFactory.getObjectRule().apply(referType, n, _package, schema);
                    addOption(referType, type, oneOfOptions);
                } else {
                    throw new GenerationException(
                            "Only object type supported, '" + n + "' cannot be used as an oneOf option.");
                }
            } else {
                // For anonymous object, create index ..
                AtomicInteger index = INDICES.get(nodeName);
                if (index == null) {
                    INDICES.put(nodeName, index = new AtomicInteger(0));
                }

                // Create unique class name ...
                final String className = makeUnique(nodeName + index.incrementAndGet(), _package);
                // Convert ...
                JType type = ruleFactory.getObjectRule().apply(className, optionNode, _package, schema);
                addOption(className, type, oneOfOptions);
            }

        }
    }

    /**
     * Obtains class name.
     * 
     * @param nodeName
     * @param node
     * @param _package
     * @return
     */
    private String getClassName(String nodeName, JsonNode node, JPackage _package) {
        String prefix = ruleFactory.getGenerationConfig().getClassNamePrefix();
        String suffix = ruleFactory.getGenerationConfig().getClassNameSuffix();
        String fieldName = ruleFactory.getNameHelper().getFieldName(nodeName, node);
        String capitalizedFieldName = capitalize(fieldName);
        String fullFieldName = createFullFieldName(capitalizedFieldName, prefix, suffix);

        String className = ruleFactory.getNameHelper().replaceIllegalCharacters(fullFieldName);
        String normalizedName = ruleFactory.getNameHelper().normalizeName(className);
        return makeUnique(normalizedName, _package);
    }

    /**
     * Create a full field name.
     * 
     * @param nodeName
     * @param prefix
     * @param suffix
     * @return
     */
    private String createFullFieldName(String nodeName, String prefix, String suffix) {
        String returnString = nodeName;
        if (prefix != null) {
            returnString = prefix + returnString;
        }

        if (suffix != null) {
            returnString = returnString + suffix;
        }

        return returnString;
    }

    /**
     * Make class name unique.
     * 
     * @param className
     * @param container
     * @return a class name appends "_" if existed already.
     */
    private String makeUnique(String className, JClassContainer container) {
        boolean found = false;
        Iterator<JDefinedClass> classes = container.classes();
        while (classes.hasNext()) {
            JDefinedClass aClass = classes.next();
            if (className.equalsIgnoreCase(aClass.name())) {
                found = true;
                break;
            }
        }
        if (found) {
            className = makeUnique(className + "_", container);
        }
        return className;
    }

    /**
     * Resolve a node reference.
     * 
     * @param node
     * @param parent
     * @return a referred node.
     */
    private JsonNode resolveRefs(JsonNode node, Schema parent) {
        if (node.has(REF)) {
            Schema refSchema = ruleFactory.getSchemaStore().create(parent, node.get(REF).asText());
            JsonNode refNode = refSchema.getContent();
            return resolveRefs(refNode, parent);
        } else {
            return node;
        }
    }

    /**
     * Add fields for nested enum.
     * 
     * @param _enum
     * @return
     */
    private JFieldVar addEnumFields(JDefinedClass _enum) {
        // Enum constant name ...
        JFieldVar nameField = _enum.field(JMod.PRIVATE | JMod.FINAL, String.class, ENUM_CONSTANT_NAME);
        // Option class ...
        JFieldVar classField = _enum.field(JMod.PRIVATE | JMod.FINAL, Class.class, CLASS_FIELD_NAME);
        // Required fields ...
        JFieldVar requiredFields = _enum.field(JMod.PRIVATE | JMod.FINAL, String[].class, REQUIRED_FIELD_NAME);
        // Optional fields ...
        JFieldVar optinalFields = _enum.field(JMod.PRIVATE | JMod.FINAL, String[].class, OPTIONAL_FIELD_NAME);

        // private enum constructor.
        JMethod constructor = _enum.constructor(JMod.PRIVATE);
        JVar valueParam = constructor.param(_enum.owner().ref(String.class), ENUM_CONSTANT_NAME);
        JVar classParam = constructor.param(_enum.owner().ref(Class.class), CLASS_FIELD_NAME);

        JBlock body = constructor.body();
        body.assign(JExpr._this().ref(nameField), valueParam);
        body.assign(JExpr._this().ref(classField), classParam);

        /**
         * Note: "required" annotation field need to be generated to increase
         * the accuracy.
         */
        JVar requires = body.decl(_enum.owner().ref(List.class).narrow(String.class), "required");
        JVar optionals = body.decl(_enum.owner().ref(List.class).narrow(String.class), "optional");
        JClass fieldsType = _enum.owner().ref(ArrayList.class).narrow(_enum.owner().ref(String.class));
        requires.init(JExpr._new(fieldsType));
        optionals.init(JExpr._new(fieldsType));

        // Populate field names ...
        JForEach forEach = body.forEach(_enum.owner().ref(Field.class), "f",
                classParam.invoke("getDeclaredFields"));
        JVar var = forEach.var();

        // Find JsonProperty annotation ...
        JVar jsonProp = forEach.body().decl(_enum.owner().ref(JsonProperty.class), "jsonProp");
        jsonProp.init(var.invoke("getAnnotation").arg(_enum.owner().ref(JsonProperty.class).dotclass()));

        JConditional ifJsonProp = forEach.body()._if(jsonProp.ne(JExpr._null()));

        // Find field name ...
        JVar fn = ifJsonProp._then().decl(_enum.owner().ref(String.class), "fn");
        fn.init(jsonProp.invoke("value"));

        JConditional ifNotNamed = ifJsonProp._then()._if(fn.eq(JExpr._null()));
        ifNotNamed._then().add((JStatement) JExpr.assign(fn, var.invoke("getName")));

        JConditional ifRequired = ifJsonProp._then()._if(jsonProp.invoke("required"));
        ifRequired._then().add(requires.invoke("add").arg(fn));
        ifRequired._else().add(optionals.invoke("add").arg(fn));

        _enum.owner().ref(Collections.class).staticInvoke("sort").arg(requires);
        _enum.owner().ref(Collections.class).staticInvoke("sort").arg(optionals);
        body.assign(JExpr._this().ref(requiredFields),
                requires.invoke("toArray").arg(JExpr._new(_enum.owner().ref(String[].class))));
        body.assign(JExpr._this().ref(optinalFields),
                optionals.invoke("toArray").arg(JExpr._new(_enum.owner().ref(String[].class))));

        return nameField;
    }

    /**
     * Add "toString()" for enum constant.
     * 
     * @param _enum
     * @param valueField
     */
    private void addToString(JDefinedClass _enum, JFieldVar valueField) {
        JMethod toString = _enum.method(JMod.PUBLIC, String.class, "toString");
        JBlock body = toString.body();

        body._return(JExpr._this().ref(valueField));

        ruleFactory.getAnnotator().enumValueMethod(toString);
        toString.annotate(Override.class);
    }

    /**
     * Add an option.
     * 
     * @param name
     * @param type
     * @param oneOf
     */
    private void addOption(String name, JType type, JDefinedClass oneOf) {
        if (type != null) {
            JEnumConstant option = oneOf.enumConstant(getConstantName(name));
            option.arg(JExpr.lit(name));
            option.arg(JExpr.dotclass(type.boxify()));
            ruleFactory.getAnnotator().enumConstant(option, name);
        }
    }

    /**
     * Obtains a constant name.
     * 
     * @param name
     * @return
     */
    protected String getConstantName(String name) {
        List<String> enumNameGroups = new ArrayList<String>(asList(splitByCharacterTypeCamelCase(name)));

        String enumName = "";
        for (Iterator<String> iter = enumNameGroups.iterator(); iter.hasNext();) {
            if (containsOnly(ruleFactory.getNameHelper().replaceIllegalCharacters(iter.next()), "_")) {
                iter.remove();
            }
        }

        enumName = upperCase(join(enumNameGroups, "_"));

        if (isEmpty(enumName)) {
            enumName = "__EMPTY__";
        } else if (Character.isDigit(enumName.charAt(0))) {
            enumName = "_" + enumName;
        }

        return enumName;
    }

    /**
     * Add factory method.
     * 
     * @param parent
     * @param _enum
     */
    private void addFactoryMethod(JDefinedClass parent, JDefinedClass _enum) {
        JClass jsonCreator = parent.owner().ref(JsonCreator.class);
        JFieldVar quickLookupMap = addQuickLookupMap(parent, _enum);

        JMethod fromValue = parent.method(JMod.PUBLIC | JMod.STATIC, parent, FACTORY_METHOD_NAME);
        fromValue._throws(_enum.owner().ref(IOException.class));
        JClass paramType = parent.owner().ref(HashMap.class).narrow(parent.owner().ref(String.class),
                parent.owner().ref(Object.class));
        JVar valueParam = fromValue.param(paramType, "value");
        JBlock body = fromValue.body();

        // Print options found ...
        body.add(parent.owner().ref(System.class).staticRef("out").invoke("println")
                .arg(JExpr.lit("Invoke oneOf factory method with ").plus(quickLookupMap.invoke("size"))
                        .plus(JExpr.lit(" options."))));
        body.add(parent.owner().ref(System.class).staticRef("out").invoke("println").arg(valueParam));

        JClass stringListType = parent.owner().ref(ArrayList.class).narrow(parent.owner().ref(String.class));
        JVar keys = body.decl(parent.owner().ref(List.class).narrow(String.class), "keys");
        keys.init(JExpr._new(stringListType).arg(valueParam.invoke("keySet")));

        JVar mapper = body.decl(parent.owner().ref(ObjectMapper.class), "mapper");
        mapper.init(JExpr._new(parent.owner().ref(ObjectMapper.class)));

        JForEach forEach = body.forEach(_enum, "c", JExpr.direct(_enum.fullName() + ".values()"));
        forEach.body()
                .add(parent.owner().ref(System.class).staticRef("out").invoke("println")
                        .arg(parent.owner().ref(String.class).staticInvoke("format").arg("Required: %s")
                                .arg(parent.owner().ref(Arrays.class).staticInvoke("toString")
                                        .arg(forEach.var().ref(REQUIRED_FIELD_NAME)))));
        forEach.body()
                .add(parent.owner().ref(System.class).staticRef("out").invoke("println")
                        .arg(parent.owner().ref(String.class).staticInvoke("format").arg("Optional: %s")
                                .arg(parent.owner().ref(Arrays.class).staticInvoke("toString")
                                        .arg(forEach.var().ref(OPTIONAL_FIELD_NAME)))));
        // Check fields ...

        JVar requires = forEach.body().decl(parent.owner().ref(List.class).narrow(String.class), "requires");
        requires.init(JExpr._new(stringListType).arg(parent.owner().ref(Arrays.class).staticInvoke("asList")
                .arg(forEach.var().ref(REQUIRED_FIELD_NAME))));
        forEach.body().add(requires.invoke("removeAll").arg(keys));
        JConditional matchRequires = forEach.body()._if(requires.invoke("isEmpty"));
        JVar optionals = matchRequires._then().decl(parent.owner().ref(List.class).narrow(String.class),
                "optionals");
        optionals.init(JExpr._new(stringListType).arg(parent.owner().ref(Arrays.class).staticInvoke("asList")
                .arg(forEach.var().ref(OPTIONAL_FIELD_NAME))));
        JVar fields = matchRequires._then().decl(parent.owner().ref(List.class).narrow(String.class), "fields");
        fields.init(JExpr._new(stringListType).arg(keys));

        matchRequires._then().add(fields.invoke("removeAll").arg(parent.owner().ref(Arrays.class)
                .staticInvoke("asList").arg(forEach.var().ref(REQUIRED_FIELD_NAME))));
        matchRequires._then().add(fields.invoke("removeAll").arg(optionals));
        matchRequires._then().add(parent.owner().ref(System.class).staticRef("out").invoke("println")
                .arg(parent.owner().ref(String.class).staticInvoke("format").arg("Delta: %s").arg(
                        parent.owner().ref(Arrays.class).staticInvoke("toString").arg(fields.invoke("toArray")))));
        matchRequires._else().add(parent.owner().ref(System.class).staticRef("out").invoke("println").arg(parent
                .owner().ref(String.class).staticInvoke("format").arg("Missing required: %s")
                .arg(parent.owner().ref(Arrays.class).staticInvoke("toString").arg(requires.invoke("toArray")))));

        JConditional matchOptionals = matchRequires._then()._if(fields.invoke("isEmpty"));
        JTryBlock tryReturn = matchOptionals._then()._try();
        tryReturn.body()
                ._return(JExpr._new(parent).arg(forEach.var())
                        .arg(mapper.invoke("readValue").arg(mapper.invoke("writeValueAsString").arg(valueParam))
                                .arg(forEach.var().ref(CLASS_FIELD_NAME))));
        JCatchBlock catchBlock = tryReturn._catch(parent.owner().ref(JsonMappingException.class));
        JVar exception = catchBlock.param("e");
        catchBlock.body()
                .add(parent.owner().ref(System.class).staticRef("out").invoke("println")
                        .arg(parent.owner().ref(String.class).staticInvoke("format").arg("Failed convert to %s: %s")
                                .arg(forEach.var().ref(CLASS_FIELD_NAME)).arg(exception.invoke("getMessage"))));
        // JAnnotationUse annotate = fromValue.annotate(jsonCreator);
        // annotate.param("mode", Mode.DELEGATING);

        fromValue.annotate(jsonCreator);
        body._return(JExpr._null());
    }

    /**
     * Add a quick lookup map of option to name.
     * 
     * @param parent
     * @param _enum
     * @return
     */
    private JFieldVar addQuickLookupMap(JDefinedClass parent, JDefinedClass _enum) {

        JClass lookupType = _enum.owner().ref(Map.class).narrow(_enum.owner().ref(String.class), _enum);
        JFieldVar lookupMap = parent.field(JMod.PRIVATE | JMod.STATIC | JMod.FINAL, lookupType, "CONSTANTS");

        JClass lookupImplType = _enum.owner().ref(HashMap.class).narrow(_enum.owner().ref(String.class), _enum);
        lookupMap.init(JExpr._new(lookupImplType));

        JForEach forEach = parent.init().forEach(_enum, "c", JExpr.direct(_enum.fullName() + ".values()"));
        JInvocation put = forEach.body().invoke(lookupMap, "put");
        put.arg(forEach.var().ref("value"));
        put.arg(forEach.var());

        return lookupMap;
    }

    /**
     * Is an object node?
     * 
     * @param node
     * @return
     */
    private boolean isObject(JsonNode node) {
        return node.path("type").asText().equals("object");
    }

    private void addGeneratedAnnotation(JDefinedClass jclass) {
        JAnnotationUse generated = jclass.annotate(Generated.class);
        generated.param("value", SchemaMapper.class.getPackage().getName());
    }

    /**
     * Add hashcode method.
     * 
     * @param jclass
     */
    private void addHashCode(JDefinedClass jclass) {
        Map<String, JFieldVar> fields = jclass.fields();
        if (fields.isEmpty()) {
            return;
        }

        JMethod hashCode = jclass.method(JMod.PUBLIC, int.class, "hashCode");

        Class<?> hashCodeBuilder = ruleFactory.getGenerationConfig().isUseCommonsLang3()
                ? org.apache.commons.lang3.builder.HashCodeBuilder.class
                : org.apache.commons.lang.builder.HashCodeBuilder.class;

        JBlock body = hashCode.body();
        JClass hashCodeBuilderClass = jclass.owner().ref(hashCodeBuilder);
        JInvocation hashCodeBuilderInvocation = JExpr._new(hashCodeBuilderClass);

        if (!jclass._extends().name().equals("Object")) {
            hashCodeBuilderInvocation = hashCodeBuilderInvocation.invoke("appendSuper")
                    .arg(JExpr._super().invoke("hashCode"));
        }

        for (JFieldVar fieldVar : fields.values()) {
            if ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC)
                continue;
            hashCodeBuilderInvocation = hashCodeBuilderInvocation.invoke("append").arg(fieldVar);
        }

        body._return(hashCodeBuilderInvocation.invoke("toHashCode"));

        hashCode.annotate(Override.class);
    }

    /**
     * Add equals method.
     * 
     * @param jclass
     */
    private void addEquals(JDefinedClass jclass) {
        Map<String, JFieldVar> fields = jclass.fields();
        if (fields.isEmpty()) {
            return;
        }

        JMethod equals = jclass.method(JMod.PUBLIC, boolean.class, "equals");
        JVar otherObject = equals.param(Object.class, "other");

        Class<?> equalsBuilder = ruleFactory.getGenerationConfig().isUseCommonsLang3()
                ? org.apache.commons.lang3.builder.EqualsBuilder.class
                : org.apache.commons.lang.builder.EqualsBuilder.class;

        JBlock body = equals.body();

        body._if(otherObject.eq(JExpr._this()))._then()._return(JExpr.TRUE);
        body._if(otherObject._instanceof(jclass).eq(JExpr.FALSE))._then()._return(JExpr.FALSE);

        JVar rhsVar = body.decl(jclass, "rhs").init(JExpr.cast(jclass, otherObject));
        JClass equalsBuilderClass = jclass.owner().ref(equalsBuilder);
        JInvocation equalsBuilderInvocation = JExpr._new(equalsBuilderClass);

        if (!jclass._extends().name().equals("Object")) {
            equalsBuilderInvocation = equalsBuilderInvocation.invoke("appendSuper")
                    .arg(JExpr._super().invoke("equals").arg(otherObject));
        }

        for (JFieldVar fieldVar : fields.values()) {
            if ((fieldVar.mods().getValue() & JMod.STATIC) == JMod.STATIC)
                continue;
            equalsBuilderInvocation = equalsBuilderInvocation.invoke("append").arg(fieldVar)
                    .arg(rhsVar.ref(fieldVar.name()));
        }

        JInvocation reflectionEquals = jclass.owner().ref(equalsBuilder).staticInvoke("reflectionEquals");
        reflectionEquals.arg(JExpr._this());
        reflectionEquals.arg(otherObject);

        body._return(equalsBuilderInvocation.invoke("isEquals"));

        equals.annotate(Override.class);
    }

}