From 8b8806da6b1cd001b06faac337c10f4f044bd265 Mon Sep 17 00:00:00 2001 From: Wojciech Matuszewski Date: Tue, 12 Sep 2017 09:45:08 +0200 Subject: [PATCH] Allow fields to be excluded from hashCode() and equals() generation (#530) --- .../org/jsonschema2pojo/rules/ObjectRule.java | 90 +++++++--- .../ExcludedFromEqualsAndHashCodeIT.java | 168 ++++++++++++++++++ .../excludedFromEqualsAndHashCode.json | 20 +++ 3 files changed, 250 insertions(+), 28 deletions(-) create mode 100644 jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/ExcludedFromEqualsAndHashCodeIT.java create mode 100644 jsonschema2pojo-integration-tests/src/test/resources/schema/excludedFromEqualsAndHashCode/excludedFromEqualsAndHashCode.json diff --git a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java index 2dbcef904..7db3b85bf 100644 --- a/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java +++ b/jsonschema2pojo-core/src/main/java/org/jsonschema2pojo/rules/ObjectRule.java @@ -16,27 +16,7 @@ package org.jsonschema2pojo.rules; -import static org.apache.commons.lang3.StringUtils.*; -import static org.jsonschema2pojo.rules.PrimitiveTypes.*; -import static org.jsonschema2pojo.util.TypeUtil.*; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jsonschema2pojo.AnnotationStyle; -import org.jsonschema2pojo.Schema; -import org.jsonschema2pojo.exception.ClassAlreadyExistsException; -import org.jsonschema2pojo.util.NameHelper; -import org.jsonschema2pojo.util.ParcelableHelper; -import org.jsonschema2pojo.util.SerializableHelper; +import android.os.Parcelable; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.JsonNode; @@ -55,7 +35,30 @@ import com.sun.codemodel.JType; import com.sun.codemodel.JVar; -import android.os.Parcelable; +import org.jsonschema2pojo.AnnotationStyle; +import org.jsonschema2pojo.Schema; +import org.jsonschema2pojo.exception.ClassAlreadyExistsException; +import org.jsonschema2pojo.util.NameHelper; +import org.jsonschema2pojo.util.ParcelableHelper; +import org.jsonschema2pojo.util.SerializableHelper; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.capitalize; +import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.apache.commons.lang3.StringUtils.substringBefore; +import static org.jsonschema2pojo.rules.PrimitiveTypes.isPrimitive; +import static org.jsonschema2pojo.rules.PrimitiveTypes.primitiveType; +import static org.jsonschema2pojo.util.TypeUtil.resolveType; /** * Applies the generation steps required for schemas of type "object". @@ -132,8 +135,8 @@ public JType apply(String nodeName, JsonNode node, JPackage _package, Schema sch } if (ruleFactory.getGenerationConfig().isIncludeHashcodeAndEquals()) { - addHashCode(jclass); - addEquals(jclass); + addHashCode(jclass, node); + addEquals(jclass, node); } if (ruleFactory.getGenerationConfig().isParcelable()) { @@ -403,8 +406,8 @@ private void addToString(JDefinedClass jclass) { toString.annotate(Override.class); } - private void addHashCode(JDefinedClass jclass) { - Map fields = jclass.fields(); + private void addHashCode(JDefinedClass jclass, JsonNode node) { + Map fields = removeFieldsExcludedFromEqualsAndHashCode(jclass.fields(), node); JMethod hashCode = jclass.method(JMod.PUBLIC, int.class, "hashCode"); @@ -430,6 +433,37 @@ private void addHashCode(JDefinedClass jclass) { hashCode.annotate(Override.class); } + private Map removeFieldsExcludedFromEqualsAndHashCode(Map fields, JsonNode node) { + Map filteredFields = new HashMap(fields); + + JsonNode properties = node.get("properties"); + + if (properties != null) { + if (node.has("excludedFromEqualsAndHashCode")) { + JsonNode excludedArray = node.get("excludedFromEqualsAndHashCode"); + + for (Iterator iterator = excludedArray.elements(); iterator.hasNext(); ) { + String excludedPropertyName = iterator.next().asText(); + JsonNode excludedPropertyNode = properties.get(excludedPropertyName); + filteredFields.remove(ruleFactory.getNameHelper().getPropertyName(excludedPropertyName, excludedPropertyNode)); + } + } + + for (Iterator> iterator = properties.fields(); iterator.hasNext(); ) { + Map.Entry entry = iterator.next(); + String propertyName = entry.getKey(); + JsonNode propertyNode = entry.getValue(); + + if (propertyNode.has("excludedFromEqualsAndHashCode") && + propertyNode.get("excludedFromEqualsAndHashCode").asBoolean()) { + filteredFields.remove(ruleFactory.getNameHelper().getPropertyName(propertyName, propertyNode)); + } + } + } + + return filteredFields; + } + private void addConstructors(JDefinedClass jclass, JsonNode node, Schema schema, boolean onlyRequired) { LinkedHashSet classProperties = getConstructorProperties(node, onlyRequired); @@ -522,8 +556,8 @@ private JFieldVar searchClassAndSuperClassesForField(String property, JDefinedCl return field; } - private void addEquals(JDefinedClass jclass) { - Map fields = jclass.fields(); + private void addEquals(JDefinedClass jclass, JsonNode node) { + Map fields = removeFieldsExcludedFromEqualsAndHashCode(jclass.fields(), node); JMethod equals = jclass.method(JMod.PUBLIC, boolean.class, "equals"); JVar otherObject = equals.param(Object.class, "other"); diff --git a/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/ExcludedFromEqualsAndHashCodeIT.java b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/ExcludedFromEqualsAndHashCodeIT.java new file mode 100644 index 000000000..d3460e4a0 --- /dev/null +++ b/jsonschema2pojo-integration-tests/src/test/java/org/jsonschema2pojo/integration/ExcludedFromEqualsAndHashCodeIT.java @@ -0,0 +1,168 @@ +/** + * 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.integration; + +import org.jsonschema2pojo.integration.util.Jsonschema2PojoRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; + +public class ExcludedFromEqualsAndHashCodeIT { + @ClassRule public static Jsonschema2PojoRule classSchemaRule = new Jsonschema2PojoRule(); + + private static Class clazz; + + @BeforeClass + public static void generateAndCompileEnum() throws ClassNotFoundException, IllegalAccessException, InstantiationException { + + ClassLoader resultsClassLoader = classSchemaRule.generateAndCompile("/schema/excludedFromEqualsAndHashCode/excludedFromEqualsAndHashCode.json", "com.example"); + + clazz = resultsClassLoader.loadClass("com.example.ExcludedFromEqualsAndHashCode"); + } + + @Test + public void hashCodeTest() throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { + + Object instance = clazz.newInstance(); + + setProperty(instance, "excludedByProperty", "one"); + setProperty(instance, "excludedByArray", "two"); + setProperty(instance, "notExcluded", "three"); + setProperty(instance, "notExcludedByProperty", "four"); + + int hashCodeBefore; + int hashCodeAfter; + + hashCodeBefore = instance.hashCode(); + setProperty(instance, "excludedByProperty", "five"); + hashCodeAfter = instance.hashCode(); + + assertThat(hashCodeBefore, is(equalTo(hashCodeAfter))); + + hashCodeBefore = hashCodeAfter; + setProperty(instance, "excludedByArray", "six"); + hashCodeAfter = instance.hashCode(); + + assertThat(hashCodeBefore, is(equalTo(hashCodeAfter))); + + hashCodeBefore = hashCodeAfter; + setProperty(instance, "notExcluded", "seven"); + hashCodeAfter = instance.hashCode(); + + assertThat(hashCodeBefore, is(not(equalTo(hashCodeAfter)))); + + hashCodeBefore = hashCodeAfter; + setProperty(instance, "notExcludedByProperty", "eight"); + hashCodeAfter = instance.hashCode(); + + assertThat(hashCodeBefore, is(not(equalTo(hashCodeAfter)))); + } + + @Test + public void equalsSelf() throws IllegalAccessException, InstantiationException { + Object instance = clazz.newInstance(); + + assertThat(instance.equals(instance), is(true)); + } + + @Test + public void exludedByPropertyTest() throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { + Object instanceOne = clazz.newInstance(); + Object instanceTwo = clazz.newInstance(); + + setProperty(instanceOne, "excludedByProperty", "one"); + setProperty(instanceOne, "excludedByArray", "two"); + setProperty(instanceOne, "notExcluded", "three"); + setProperty(instanceOne, "notExcludedByProperty", "four"); + + setProperty(instanceTwo, "excludedByProperty", "differentValue"); + setProperty(instanceTwo, "excludedByArray", "two"); + setProperty(instanceTwo, "notExcluded", "three"); + setProperty(instanceTwo, "notExcludedByProperty", "four"); + + assertThat(instanceOne.equals(instanceTwo), is(true)); + } + + @Test + public void exludedByArrayTest() throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { + Object instanceOne = clazz.newInstance(); + Object instanceTwo = clazz.newInstance(); + + setProperty(instanceOne, "excludedByProperty", "one"); + setProperty(instanceOne, "excludedByArray", "two"); + setProperty(instanceOne, "notExcluded", "three"); + setProperty(instanceOne, "notExcludedByProperty", "four"); + + setProperty(instanceTwo, "excludedByProperty", "one"); + setProperty(instanceTwo, "excludedByArray", "differentValue"); + setProperty(instanceTwo, "notExcluded", "three"); + setProperty(instanceTwo, "notExcludedByProperty", "four"); + + assertThat(instanceOne.equals(instanceTwo), is(true)); + } + + @Test + public void notExcludedTest() throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { + Object instanceOne = clazz.newInstance(); + Object instanceTwo = clazz.newInstance(); + + setProperty(instanceOne, "excludedByProperty", "one"); + setProperty(instanceOne, "excludedByArray", "two"); + setProperty(instanceOne, "notExcluded", "three"); + setProperty(instanceOne, "notExcludedByProperty", "four"); + + setProperty(instanceTwo, "excludedByProperty", "one"); + setProperty(instanceTwo, "excludedByArray", "two"); + setProperty(instanceTwo, "notExcluded", "differentValue"); + setProperty(instanceTwo, "notExcludedByProperty", "four"); + + assertThat(instanceOne.equals(instanceTwo), is(false)); + } + + @Test + public void notExludedByPropertyTest() throws IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { + Object instanceOne = clazz.newInstance(); + Object instanceTwo = clazz.newInstance(); + + setProperty(instanceOne, "excludedByProperty", "one"); + setProperty(instanceOne, "excludedByArray", "two"); + setProperty(instanceOne, "notExcluded", "three"); + setProperty(instanceOne, "notExcludedByProperty", "four"); + + setProperty(instanceTwo, "excludedByProperty", "one"); + setProperty(instanceTwo, "excludedByArray", "two"); + setProperty(instanceTwo, "notExcluded", "three"); + setProperty(instanceTwo, "notExcludedByProperty", "differentValue"); + + assertThat(instanceOne.equals(instanceTwo), is(false)); + } + + private static void setProperty(Object instance, String property, String value) throws IllegalAccessException, InvocationTargetException, IntrospectionException { + new PropertyDescriptor(property, clazz).getWriteMethod().invoke(instance, value); + } + +} diff --git a/jsonschema2pojo-integration-tests/src/test/resources/schema/excludedFromEqualsAndHashCode/excludedFromEqualsAndHashCode.json b/jsonschema2pojo-integration-tests/src/test/resources/schema/excludedFromEqualsAndHashCode/excludedFromEqualsAndHashCode.json new file mode 100644 index 000000000..942af1f5e --- /dev/null +++ b/jsonschema2pojo-integration-tests/src/test/resources/schema/excludedFromEqualsAndHashCode/excludedFromEqualsAndHashCode.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "excludedFromEqualsAndHashCode" : [ "excludedByArray" ], + "properties": { + "notExcluded" : { + "type" : "string" + }, + "excludedByProperty" : { + "type" : "string", + "excludedFromEqualsAndHashCode" : true + }, + "notExcludedByProperty" : { + "type" : "string", + "excludedFromEqualsAndHashCode" : false + }, + "excludedByArray" : { + "type" : "string" + } + } +} \ No newline at end of file