Skip to content

Commit

Permalink
Issue #302: DRAFT: support ReferenceSchema#equals with cyclic schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobias Marstaller committed Jul 24, 2020
1 parent fe1a51c commit 13438c9
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 16 deletions.
12 changes: 9 additions & 3 deletions core/src/main/java/org/everit/json/schema/ReferenceSchema.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.everit.json.schema;

import static java.util.Objects.requireNonNull;
import org.everit.json.schema.internal.EqualsCycleBreaker;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

/**
* This class is used by {@link org.everit.json.schema.loader.SchemaLoader} to resolve JSON pointers
* during the construction of the schema. This class has been made mutable to permit the loading of
Expand Down Expand Up @@ -147,13 +149,17 @@ public boolean equals(Object o) {
Objects.equals(unprocessedProperties, that.unprocessedProperties) &&
Objects.equals(title, that.title) &&
Objects.equals(description, that.description) &&
Objects.equals(referredSchema, that.referredSchema) &&
super.equals(that);
super.equals(that) &&
EqualsCycleBreaker.equalsWithoutCycle(this, that, true, ReferenceSchema::equalsPossiblyCyclic);
} else {
return false;
}
}

private boolean equalsPossiblyCyclic(ReferenceSchema that) {
return Objects.equals(referredSchema, that.referredSchema);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), refValue, unprocessedProperties, title, description);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.everit.json.schema.internal;

import java.util.HashSet;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.BiFunction;

public final class EqualsCycleBreaker
{
private EqualsCycleBreaker()
{
throw new UnsupportedOperationException();
}

/**
* For each invocation of <code>a.equals(b)</code> that uses this class,
*/
private static final ThreadLocal<WeakHashMap<Identity, Set>> ongoingEqualityChecks = ThreadLocal.withInitial(WeakHashMap::new);

public static <T> boolean equalsWithoutCycle(T self, T other, boolean equalsOnCycle, BiFunction<T, T, Boolean> equalityFunction) {
Set<T> localOngoingEqualityChecks = ongoingEqualityChecks.get()
.computeIfAbsent(new Identity<>(self), (_k) -> new HashSet<>());
if (localOngoingEqualityChecks.add(other)) {
try {
return equalityFunction.apply(self, other);
}
finally {
localOngoingEqualityChecks.remove(other);
if (localOngoingEqualityChecks.isEmpty()) {
ongoingEqualityChecks.remove();
}
}
} else {
return equalsOnCycle;
}
}

private static class Identity<E> {
private final E e;

public Identity(E e)
{
this.e = e;
}

public int hashCode() {
return System.identityHashCode(e);
}

public boolean equals(Object o) {
if (!(o instanceof Identity)) {
return false;
}
return ((Identity<?>) o).e == e;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.io.IOException;
import java.io.InputStream;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;

public class HashCodeRecursionTest
{
Expand All @@ -21,9 +21,10 @@ public void hashCodeShouldNotProduceStackoverflowOnCyclicSchema() throws IOExcep
@Test
public void equalsShouldNotProduceStackoverflowOnCyclicSchema() throws IOException
{
Schema cyclic = loadSelfCyclic();
Schema cyclicCopy = loadSelfCyclic();
cyclic.equals(cyclicCopy);
CombinedSchema cyclic = (CombinedSchema) loadSelfCyclic();
CombinedSchema cyclicCopy = (CombinedSchema) loadSelfCyclic();

assertEquals(cyclic, cyclicCopy);
}

private Schema loadSelfCyclic() throws IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,19 @@
*/
package org.everit.json.schema;

import static java.util.Collections.emptyMap;
import static org.everit.json.schema.TestSupport.buildWithLocation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import com.google.common.collect.ImmutableMap;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import org.everit.json.schema.ReferenceSchema.Builder;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;

import com.google.common.collect.ImmutableMap;

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import static java.util.Collections.emptyMap;
import static org.everit.json.schema.TestSupport.buildWithLocation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class ReferenceSchemaTest {

Expand Down

0 comments on commit 13438c9

Please sign in to comment.