import java.io.*;
import java.util.*;

// reproducer for https://bugs.openjdk.org/browse/JDK-4957674
public class HashSetElementSelfRef {
	
	public static void main(final String[] args) throws Exception {
		final int id = 2;
		final A a = new A(2);
		a.addSelf();
		// basic sanity test
		final Set<A> c1 = a.getCollection();
		if (c1.size() != 1) {
			throw new RuntimeException("Unexpected size of collection before deserialization");
		}
		for (A item : c1) {
			if (!c1.contains(item)) {
				throw new RuntimeException("Missing item before serialization");
			}
		}

		// now serializable
		final byte[] serialized = serialize(a);
		System.out.println("Serialized instance of A with id " + a.id);
		// now deserializable
		final A deserialized = deserialize(serialized);
		System.out.println("Deserialized instance of A with id " + deserialized.id);


		// now check the contents of the set
		final Set<A> coll = deserialized.getCollection();
		if (coll.size() != 1) {
			throw new RuntimeException("Unexpected size of collection after deserialization");
		}
		for (A item : coll) {
			if (!coll.contains(item)) {
				throw new RuntimeException("Missing item after serialization");
			}
		}

	}

	private static byte[] serialize(final A a) throws Exception {
		final ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (final ObjectOutputStream oos = new ObjectOutputStream(bos)) {
	        oos.writeObject(a);
	        oos.flush();
	    }
	    return bos.toByteArray();
	}

	private static A deserialize(final byte[] bytes) throws Exception {
		final ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
        final ObjectInputStream ois = new ObjectInputStream(bin);
        return (A) ois.readObject();
	}

	private static final class A implements Serializable {

		private final int id;
		private final Set<A> someSet = new HashSet<>();

		private A(int id) {
			this.id = id;
		}

		private void addSelf() {
			this.someSet.add(this);
		}

		private Set<A> getCollection() {
			return this.someSet;
		}

		@Override
		public int hashCode() {
			return Integer.hashCode(this.id);
		}

		@Override
		public boolean equals(final Object other) {
			if (this == other) {
				return true;
			}
			if (!(other instanceof A o)) {
				return false;
			}
            return this.id == o.id;
		}

	}
}