From fdb96de13f0c69159e88366e10259914cbfb1787 Mon Sep 17 00:00:00 2001 From: Kakhnovich Raman Date: Thu, 30 Jul 2026 17:54:40 +0300 Subject: [PATCH 1/3] Add isEquivalentTo method to COSBase and COSObject Add support for object comparison from ISO32000-2:2020 Annex J --- src/main/java/org/verapdf/cos/COSArray.java | 45 ++++++++++ src/main/java/org/verapdf/cos/COSBase.java | 6 ++ src/main/java/org/verapdf/cos/COSBoolean.java | 11 +++ .../java/org/verapdf/cos/COSDictionary.java | 64 ++++++++++++++ .../java/org/verapdf/cos/COSIndirect.java | 16 ++++ src/main/java/org/verapdf/cos/COSInteger.java | 23 +++++ src/main/java/org/verapdf/cos/COSName.java | 14 ++++ src/main/java/org/verapdf/cos/COSNull.java | 5 ++ src/main/java/org/verapdf/cos/COSNumber.java | 4 + src/main/java/org/verapdf/cos/COSObject.java | 13 +++ src/main/java/org/verapdf/cos/COSReal.java | 23 +++++ src/main/java/org/verapdf/cos/COSStream.java | 81 ++++++++++++++++++ src/main/java/org/verapdf/cos/COSString.java | 83 +++++++++++++++++++ 13 files changed, 388 insertions(+) diff --git a/src/main/java/org/verapdf/cos/COSArray.java b/src/main/java/org/verapdf/cos/COSArray.java index a9d02597..fc70cbff 100644 --- a/src/main/java/org/verapdf/cos/COSArray.java +++ b/src/main/java/org/verapdf/cos/COSArray.java @@ -189,6 +189,51 @@ private COSObject _at(final int i) { return this.entries.get(i); } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (o == null) return false; + + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get()); + } + if (!(o instanceof COSArray)) return false; + List checkedObjects = new LinkedList<>(); + return isEquivalentTo(o, checkedObjects); + } + + @Override + boolean isEquivalentTo(Object o, List checkedObjects) { + if (this == o) return true; + if (o == null) return false; + + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); + } + if (!(o instanceof COSArray)) return false; + + COSArray that = (COSArray) o; + if (COSBasePair.listContainsPair(checkedObjects, this, that)) { + return true; + } + COSBasePair.addPairToList(checkedObjects, this, that); + + if (this.size() != that.size()) return false; + + for (int i = 0; i < this.size(); ++i) { + COSBase thisElem = this.at(i).getDirectBase(); + COSBase thatElem = that.at(i).getDirectBase(); + + if (thisElem == null && thatElem == null) continue; + if (thisElem == null || thatElem == null) return false; + + if (!thisElem.isEquivalentTo(thatElem, checkedObjects)) { + return false; + } + } + return true; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/org/verapdf/cos/COSBase.java b/src/main/java/org/verapdf/cos/COSBase.java index 4c1ee154..cbe62cf3 100644 --- a/src/main/java/org/verapdf/cos/COSBase.java +++ b/src/main/java/org/verapdf/cos/COSBase.java @@ -140,6 +140,12 @@ public void setObjectKey(COSKey indirectKey) { public abstract void mark(); + public abstract boolean isEquivalentTo(Object obj); + + boolean isEquivalentTo(Object o, List checkedObjects) { + return isEquivalentTo(o); + } + boolean equals(Object obj, List checkedObjects) { return this.equals(obj); } diff --git a/src/main/java/org/verapdf/cos/COSBoolean.java b/src/main/java/org/verapdf/cos/COSBoolean.java index 1cae11d2..8feff328 100644 --- a/src/main/java/org/verapdf/cos/COSBoolean.java +++ b/src/main/java/org/verapdf/cos/COSBoolean.java @@ -71,6 +71,17 @@ public boolean setBoolean(final boolean value) { return true; } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (!(o instanceof COSBoolean)) return false; + + COSBoolean that = (COSBoolean) o; + + return value == that.value; + + } + public boolean get() { return this.value; } diff --git a/src/main/java/org/verapdf/cos/COSDictionary.java b/src/main/java/org/verapdf/cos/COSDictionary.java index 057088c8..231e49be 100644 --- a/src/main/java/org/verapdf/cos/COSDictionary.java +++ b/src/main/java/org/verapdf/cos/COSDictionary.java @@ -299,6 +299,70 @@ public Collection getValues() { return this.entries.values(); } + private Set getNonNullKeySet() { + Set nonNullKeys = new HashSet<>(); + for (ASAtom key : getKeySet()) { + COSBase value = getKey(key).get(); // assume getKey returns a COSBase wrapper + if (value != null) { + nonNullKeys.add(key); + } + } + return nonNullKeys; + } + + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (o == null) return false; + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get()); + } + if (!(o instanceof COSDictionary)) return false; + + List checkedObjects = new LinkedList<>(); + return isEquivalentTo(o, checkedObjects); + } + + /** + * Internal recursive comparison with cycle detection. + */ + boolean isEquivalentTo(Object o, List checkedObjects) { + if (this == o) return true; + if (o == null) return false; + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); + } + if (!(o instanceof COSDictionary)) return false; + + COSDictionary that = (COSDictionary) o; + + if (COSBasePair.listContainsPair(checkedObjects, this, that)) { + return true; + } + COSBasePair.addPairToList(checkedObjects, this, that); + + Set thisKeys = this.getNonNullKeySet(); + Set thatKeys = that.getNonNullKeySet(); + + if (!thisKeys.equals(thatKeys)) { + return false; + } + + for (ASAtom key : thisKeys) { + COSBase thisVal = this.getKey(key).get(); + COSBase thatVal = that.getKey(key).get(); + + if (thisVal == null && thatVal == null) continue; + if (thisVal == null || thatVal == null) return false; + + if (!thisVal.isEquivalentTo(thatVal, checkedObjects)) { + return false; + } + } + + return true; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/org/verapdf/cos/COSIndirect.java b/src/main/java/org/verapdf/cos/COSIndirect.java index 343fa197..abd8c916 100644 --- a/src/main/java/org/verapdf/cos/COSIndirect.java +++ b/src/main/java/org/verapdf/cos/COSIndirect.java @@ -480,6 +480,22 @@ public void mark() { } } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + + if (o instanceof COSIndirect) { + COSIndirect that = (COSIndirect) o; + return this.getDirect().isEquivalentTo(that.getDirect()); + } + + if (o instanceof COSObject) { + return this.getDirect().isEquivalentTo(o); + } + + return false; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/verapdf/cos/COSInteger.java b/src/main/java/org/verapdf/cos/COSInteger.java index f3acdddd..d0a3711c 100644 --- a/src/main/java/org/verapdf/cos/COSInteger.java +++ b/src/main/java/org/verapdf/cos/COSInteger.java @@ -23,6 +23,8 @@ import org.verapdf.cos.visitor.ICOSVisitor; import org.verapdf.cos.visitor.IVisitor; +import java.math.BigDecimal; + /** * @author Timur Kamalov */ @@ -75,6 +77,22 @@ public boolean setReal(final double value) { return true; } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + + if (o instanceof COSInteger) { + return this.value == ((COSInteger) o).value; + } + + if (o instanceof COSReal) { + COSReal thatReal = (COSReal) o; + return BigDecimal.valueOf(this.value).compareTo(thatReal.getDecimalValue()) == 0; + } + + return false; + } + public long get() { return this.value; } @@ -97,4 +115,9 @@ public boolean equals(Object o) { return value == that.value; } + + @Override + public BigDecimal getDecimalValue() { + return BigDecimal.valueOf(value); + } } diff --git a/src/main/java/org/verapdf/cos/COSName.java b/src/main/java/org/verapdf/cos/COSName.java index 41d7ef11..cb784c3f 100644 --- a/src/main/java/org/verapdf/cos/COSName.java +++ b/src/main/java/org/verapdf/cos/COSName.java @@ -25,6 +25,7 @@ import org.verapdf.cos.visitor.IVisitor; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Objects; /** @@ -91,6 +92,19 @@ public boolean setName(final ASAtom value) { return true; } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (!(o instanceof COSName)) return false; + + COSName that = (COSName) o; + + byte[] thisBytes = this.getName().getValue().getBytes(StandardCharsets.ISO_8859_1); + byte[] thatBytes = that.getName().getValue().getBytes(StandardCharsets.ISO_8859_1); + + return Arrays.equals(thisBytes, thatBytes); + } + public ASAtom get() { return value; } diff --git a/src/main/java/org/verapdf/cos/COSNull.java b/src/main/java/org/verapdf/cos/COSNull.java index c8f63e53..e379db66 100644 --- a/src/main/java/org/verapdf/cos/COSNull.java +++ b/src/main/java/org/verapdf/cos/COSNull.java @@ -51,6 +51,11 @@ public Object accept(final ICOSVisitor visitor) { return visitor.visitFromNull(this); } + @Override + public boolean isEquivalentTo(Object o) { + return equals(o); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/verapdf/cos/COSNumber.java b/src/main/java/org/verapdf/cos/COSNumber.java index f970c762..009e64db 100644 --- a/src/main/java/org/verapdf/cos/COSNumber.java +++ b/src/main/java/org/verapdf/cos/COSNumber.java @@ -20,6 +20,8 @@ */ package org.verapdf.cos; +import java.math.BigDecimal; + /** * @author Timur Kamalov */ @@ -29,4 +31,6 @@ public COSNumber() { super(); } + public abstract BigDecimal getDecimalValue(); + } diff --git a/src/main/java/org/verapdf/cos/COSObject.java b/src/main/java/org/verapdf/cos/COSObject.java index f23a4916..1f582c86 100644 --- a/src/main/java/org/verapdf/cos/COSObject.java +++ b/src/main/java/org/verapdf/cos/COSObject.java @@ -494,6 +494,19 @@ public void setIsHeaderFormatComplyPDFA(Boolean isHeaderFormatComplyPDFA) { this.isHeaderFormatComplyPDFA = isHeaderFormatComplyPDFA; } + public boolean isEquivalentTo(Object o) { + if (o == this) return true; + if (o instanceof COSObject) { + COSObject cosObject = (COSObject) o; + return base.isEquivalentTo(cosObject.base); + } + + if (o instanceof COSIndirect) { + return base.isEquivalentTo(((COSIndirect) o).getDirect().base); + } + return false; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/verapdf/cos/COSReal.java b/src/main/java/org/verapdf/cos/COSReal.java index d43d87bf..398f5d49 100644 --- a/src/main/java/org/verapdf/cos/COSReal.java +++ b/src/main/java/org/verapdf/cos/COSReal.java @@ -23,6 +23,7 @@ import org.verapdf.cos.visitor.ICOSVisitor; import org.verapdf.cos.visitor.IVisitor; +import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -45,6 +46,11 @@ public class COSReal extends COSNumber { protected COSReal() { } + @Override + public BigDecimal getDecimalValue() { + return BigDecimal.valueOf(value); + } + protected COSReal(final double value) { this.value = value; } @@ -88,6 +94,23 @@ public boolean setReal(final double value) { return true; } + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + + if (o instanceof COSReal) { + COSReal thatReal = (COSReal) o; + return this.getDecimalValue().compareTo(thatReal.getDecimalValue()) == 0; + } + + if (o instanceof COSInteger) { + COSInteger thatInt = (COSInteger) o; + return this.getDecimalValue().compareTo(thatInt.getDecimalValue()) == 0; + } + + return false; + } + public double get() { return this.value; } diff --git a/src/main/java/org/verapdf/cos/COSStream.java b/src/main/java/org/verapdf/cos/COSStream.java index a6472545..15762ddf 100644 --- a/src/main/java/org/verapdf/cos/COSStream.java +++ b/src/main/java/org/verapdf/cos/COSStream.java @@ -283,6 +283,87 @@ public enum FilterFlags { DECRYPT_AND_DECODE } + private Set getNonNullKeysExcluding(Set exclude) { + Set keys = new HashSet<>(this.getKeySet()); + keys.removeAll(exclude); + keys.removeIf(key -> this.getKey(key).get() == null); + return keys; + } + + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (o == null) return false; + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get()); + } + if (!(o instanceof COSStream)) return false; + List checkedObjects = new LinkedList<>(); + return isEquivalentTo(o, checkedObjects); + } + + @Override + boolean isEquivalentTo(Object o, List checkedObjects) { + if (this == o) return true; + if (o == null) return false; + if (o instanceof COSObject) { + return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); + } + if (!(o instanceof COSStream)) return false; + COSStream that = (COSStream) o; + + if (COSBasePair.listContainsPair(checkedObjects, this, that)) { + return true; + } + COSBasePair.addPairToList(checkedObjects, this, that); + + try { + ASInputStream thisDecoded = this.getData(FilterFlags.DECODE); + ASInputStream thatDecoded = that.getData(FilterFlags.DECODE); + if (!equalsDecodedStreams(thisDecoded, thatDecoded)) { + return false; + } + } catch (IOException e) { + LOGGER.log(Level.FINE, "Exception during comparing decoded streams", e); + return false; + } + + Set excluded = new HashSet<>(Arrays.asList(ASAtom.LENGTH, ASAtom.FILTER, ASAtom.DECODE_PARMS)); + Set thisKeys = this.getNonNullKeysExcluding(excluded); + Set thatKeys = that.getNonNullKeysExcluding(excluded); + + if (!thisKeys.equals(thatKeys)) { + return false; + } + for (ASAtom key : thisKeys) { + COSBase thisVal = this.getKey(key).get(); + COSBase thatVal = that.getKey(key).get(); + if (thisVal == null && thatVal == null) continue; + if (thisVal == null || thatVal == null) return false; + if (!thisVal.isEquivalentTo(thatVal, checkedObjects)) { + return false; + } + } + + return true; + } + + private static boolean equalsDecodedStreams(ASInputStream first, ASInputStream second) throws IOException { + byte[] buf1 = new byte[8192]; + byte[] buf2 = new byte[8192]; + int read1, read2; + while (true) { + read1 = first.read(buf1); + read2 = second.read(buf2); + if (read1 != read2) return false; + if (read1 == -1) break; + for (int i = 0; i < read1; i++) { + if (buf1[i] != buf2[i]) return false; + } + } + return true; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/org/verapdf/cos/COSString.java b/src/main/java/org/verapdf/cos/COSString.java index db633e67..0128fc00 100644 --- a/src/main/java/org/verapdf/cos/COSString.java +++ b/src/main/java/org/verapdf/cos/COSString.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; +import java.io.ByteArrayOutputStream; /** * @author Timur Kamalov @@ -364,6 +365,88 @@ public void setHexCount(long hexCount) { this.hexCount = hexCount; } + private byte[] getCanonicalBytes() { + if (isHex) { + return Arrays.copyOf(value, value.length); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int i = 0; + while (i < value.length) { + byte b = value[i]; + if (b == '\\') { + if (i + 1 >= value.length) { + break; + } + byte next = value[i + 1]; + + if (next >= '0' && next <= '7') { + int octal = 0; + int count = 0; + while (i + 1 < value.length && count < 3) { + byte c = value[i + 1]; + if (c >= '0' && c <= '7') { + octal = octal * 8 + (c - '0'); + i++; + count++; + } else { + break; + } + } + out.write(octal); + continue; + } + + if (next == '\r') { + i += 2; + if (i < value.length && value[i] == '\n') { + i++; + } + continue; + } else if (next == '\n') { + i += 2; + continue; + } + + i++; + if (i >= value.length) break; + byte esc = value[i]; + byte replacement; + switch (esc) { + case 'n': replacement = '\n'; break; + case 'r': replacement = '\r'; break; + case 't': replacement = '\t'; break; + case 'b': replacement = '\b'; break; + case 'f': replacement = '\f'; break; + case '(': replacement = '('; break; + case ')': replacement = ')'; break; + case '\\': replacement = '\\'; break; + default: + replacement = esc; + break; + } + out.write(replacement); + } else { + out.write(b); + } + i++; + } + return out.toByteArray(); + } + + @Override + public boolean isEquivalentTo(Object o) { + if (this == o) return true; + if (!(o instanceof COSString)) return false; + + COSString that = (COSString) o; + + byte[] thisBytes = this.getCanonicalBytes(); + byte[] thatBytes = that.getCanonicalBytes(); + + return Arrays.equals(thisBytes, thatBytes); + } + @Override public boolean equals(Object o) { if (this == o) return true; From 41cb5af5b8d36fcacdc8a94d5928349938bd1db7 Mon Sep 17 00:00:00 2001 From: Kakhnovich Raman Date: Tue, 4 Aug 2026 14:24:05 +0300 Subject: [PATCH 2/3] Update isEquivalentTo logic --- src/main/java/org/verapdf/cos/COSArray.java | 14 ++-- src/main/java/org/verapdf/cos/COSBase.java | 4 +- src/main/java/org/verapdf/cos/COSBoolean.java | 7 +- .../java/org/verapdf/cos/COSDictionary.java | 23 +++--- .../java/org/verapdf/cos/COSIndirect.java | 15 ++-- src/main/java/org/verapdf/cos/COSInteger.java | 6 +- src/main/java/org/verapdf/cos/COSName.java | 12 +-- src/main/java/org/verapdf/cos/COSNull.java | 2 +- src/main/java/org/verapdf/cos/COSObject.java | 19 +++-- src/main/java/org/verapdf/cos/COSReal.java | 6 +- src/main/java/org/verapdf/cos/COSStream.java | 27 ++----- src/main/java/org/verapdf/cos/COSString.java | 80 ++----------------- 12 files changed, 75 insertions(+), 140 deletions(-) diff --git a/src/main/java/org/verapdf/cos/COSArray.java b/src/main/java/org/verapdf/cos/COSArray.java index fc70cbff..3e8cf1e2 100644 --- a/src/main/java/org/verapdf/cos/COSArray.java +++ b/src/main/java/org/verapdf/cos/COSArray.java @@ -190,26 +190,24 @@ private COSObject _at(final int i) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get()); + if (o.isIndirect()) { + return this.isEquivalentTo(o.getDirectBase()); } + if (!(o instanceof COSArray)) return false; List checkedObjects = new LinkedList<>(); return isEquivalentTo(o, checkedObjects); } @Override - boolean isEquivalentTo(Object o, List checkedObjects) { + boolean isEquivalentTo(COSBase o, List checkedObjects) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); - } if (!(o instanceof COSArray)) return false; COSArray that = (COSArray) o; @@ -218,7 +216,7 @@ boolean isEquivalentTo(Object o, List checkedObjects) { } COSBasePair.addPairToList(checkedObjects, this, that); - if (this.size() != that.size()) return false; + if (!Objects.equals(this.size(), that.size())) return false; for (int i = 0; i < this.size(); ++i) { COSBase thisElem = this.at(i).getDirectBase(); diff --git a/src/main/java/org/verapdf/cos/COSBase.java b/src/main/java/org/verapdf/cos/COSBase.java index cbe62cf3..7378f1dd 100644 --- a/src/main/java/org/verapdf/cos/COSBase.java +++ b/src/main/java/org/verapdf/cos/COSBase.java @@ -140,9 +140,9 @@ public void setObjectKey(COSKey indirectKey) { public abstract void mark(); - public abstract boolean isEquivalentTo(Object obj); + public abstract boolean isEquivalentTo(COSBase obj); - boolean isEquivalentTo(Object o, List checkedObjects) { + boolean isEquivalentTo(COSBase o, List checkedObjects) { return isEquivalentTo(o); } diff --git a/src/main/java/org/verapdf/cos/COSBoolean.java b/src/main/java/org/verapdf/cos/COSBoolean.java index 8feff328..0fe6194a 100644 --- a/src/main/java/org/verapdf/cos/COSBoolean.java +++ b/src/main/java/org/verapdf/cos/COSBoolean.java @@ -72,8 +72,13 @@ public boolean setBoolean(final boolean value) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); + } + if (!(o instanceof COSBoolean)) return false; COSBoolean that = (COSBoolean) o; diff --git a/src/main/java/org/verapdf/cos/COSDictionary.java b/src/main/java/org/verapdf/cos/COSDictionary.java index 231e49be..aa7d8108 100644 --- a/src/main/java/org/verapdf/cos/COSDictionary.java +++ b/src/main/java/org/verapdf/cos/COSDictionary.java @@ -302,7 +302,7 @@ public Collection getValues() { private Set getNonNullKeySet() { Set nonNullKeys = new HashSet<>(); for (ASAtom key : getKeySet()) { - COSBase value = getKey(key).get(); // assume getKey returns a COSBase wrapper + COSBase value = getKey(key).getDirectBase(); if (value != null) { nonNullKeys.add(key); } @@ -311,12 +311,14 @@ private Set getNonNullKeySet() { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get()); + + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); } + if (!(o instanceof COSDictionary)) return false; List checkedObjects = new LinkedList<>(); @@ -326,12 +328,9 @@ public boolean isEquivalentTo(Object o) { /** * Internal recursive comparison with cycle detection. */ - boolean isEquivalentTo(Object o, List checkedObjects) { + boolean isEquivalentTo(COSBase o, List checkedObjects) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); - } if (!(o instanceof COSDictionary)) return false; COSDictionary that = (COSDictionary) o; @@ -348,9 +347,13 @@ boolean isEquivalentTo(Object o, List checkedObjects) { return false; } + return isEquivalentKeys(that, thisKeys, checkedObjects); + } + + protected boolean isEquivalentKeys(COSDictionary that, Set thisKeys, List checkedObjects) { for (ASAtom key : thisKeys) { - COSBase thisVal = this.getKey(key).get(); - COSBase thatVal = that.getKey(key).get(); + COSBase thisVal = this.getKey(key).getDirectBase(); + COSBase thatVal = that.getKey(key).getDirectBase(); if (thisVal == null && thatVal == null) continue; if (thisVal == null || thatVal == null) return false; diff --git a/src/main/java/org/verapdf/cos/COSIndirect.java b/src/main/java/org/verapdf/cos/COSIndirect.java index abd8c916..9b3f67a4 100644 --- a/src/main/java/org/verapdf/cos/COSIndirect.java +++ b/src/main/java/org/verapdf/cos/COSIndirect.java @@ -481,19 +481,14 @@ public void mark() { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; - if (o instanceof COSIndirect) { - COSIndirect that = (COSIndirect) o; - return this.getDirect().isEquivalentTo(that.getDirect()); - } - - if (o instanceof COSObject) { - return this.getDirect().isEquivalentTo(o); - } + COSBase thisBase = this.getDirectBase(); + if (thisBase == null && o == null) return true; + if (thisBase == null || o == null) return false; - return false; + return thisBase.isEquivalentTo(o.getDirectBase()); } @Override diff --git a/src/main/java/org/verapdf/cos/COSInteger.java b/src/main/java/org/verapdf/cos/COSInteger.java index d0a3711c..d773661b 100644 --- a/src/main/java/org/verapdf/cos/COSInteger.java +++ b/src/main/java/org/verapdf/cos/COSInteger.java @@ -78,9 +78,13 @@ public boolean setReal(final double value) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); + } + if (o instanceof COSInteger) { return this.value == ((COSInteger) o).value; } diff --git a/src/main/java/org/verapdf/cos/COSName.java b/src/main/java/org/verapdf/cos/COSName.java index cb784c3f..1b2940ba 100644 --- a/src/main/java/org/verapdf/cos/COSName.java +++ b/src/main/java/org/verapdf/cos/COSName.java @@ -93,16 +93,18 @@ public boolean setName(final ASAtom value) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); + } + if (!(o instanceof COSName)) return false; COSName that = (COSName) o; - byte[] thisBytes = this.getName().getValue().getBytes(StandardCharsets.ISO_8859_1); - byte[] thatBytes = that.getName().getValue().getBytes(StandardCharsets.ISO_8859_1); - - return Arrays.equals(thisBytes, thatBytes); + return Objects.equals(value, that.value); } public ASAtom get() { diff --git a/src/main/java/org/verapdf/cos/COSNull.java b/src/main/java/org/verapdf/cos/COSNull.java index e379db66..e159a280 100644 --- a/src/main/java/org/verapdf/cos/COSNull.java +++ b/src/main/java/org/verapdf/cos/COSNull.java @@ -52,7 +52,7 @@ public Object accept(final ICOSVisitor visitor) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { return equals(o); } diff --git a/src/main/java/org/verapdf/cos/COSObject.java b/src/main/java/org/verapdf/cos/COSObject.java index 1f582c86..fad8c777 100644 --- a/src/main/java/org/verapdf/cos/COSObject.java +++ b/src/main/java/org/verapdf/cos/COSObject.java @@ -494,17 +494,16 @@ public void setIsHeaderFormatComplyPDFA(Boolean isHeaderFormatComplyPDFA) { this.isHeaderFormatComplyPDFA = isHeaderFormatComplyPDFA; } - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSObject o) { if (o == this) return true; - if (o instanceof COSObject) { - COSObject cosObject = (COSObject) o; - return base.isEquivalentTo(cosObject.base); - } - - if (o instanceof COSIndirect) { - return base.isEquivalentTo(((COSIndirect) o).getDirect().base); - } - return false; + if (o == null) return false; + + COSBase thisBase = this.getDirectBase(); + COSBase otherBase = o.getDirectBase(); + if (thisBase == null && otherBase == null) return true; + if (thisBase == null || otherBase == null) return false; + + return thisBase.isEquivalentTo(otherBase); } @Override diff --git a/src/main/java/org/verapdf/cos/COSReal.java b/src/main/java/org/verapdf/cos/COSReal.java index 398f5d49..244b20fb 100644 --- a/src/main/java/org/verapdf/cos/COSReal.java +++ b/src/main/java/org/verapdf/cos/COSReal.java @@ -95,9 +95,13 @@ public boolean setReal(final double value) { } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); + } + if (o instanceof COSReal) { COSReal thatReal = (COSReal) o; return this.getDecimalValue().compareTo(thatReal.getDecimalValue()) == 0; diff --git a/src/main/java/org/verapdf/cos/COSStream.java b/src/main/java/org/verapdf/cos/COSStream.java index 15762ddf..8fc4976e 100644 --- a/src/main/java/org/verapdf/cos/COSStream.java +++ b/src/main/java/org/verapdf/cos/COSStream.java @@ -286,16 +286,16 @@ public enum FilterFlags { private Set getNonNullKeysExcluding(Set exclude) { Set keys = new HashSet<>(this.getKeySet()); keys.removeAll(exclude); - keys.removeIf(key -> this.getKey(key).get() == null); + keys.removeIf(key -> this.getKey(key).getDirectBase() == null); return keys; } @Override - public boolean isEquivalentTo(Object o) { + public boolean isEquivalentTo(COSBase o) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get()); + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); } if (!(o instanceof COSStream)) return false; List checkedObjects = new LinkedList<>(); @@ -303,12 +303,10 @@ public boolean isEquivalentTo(Object o) { } @Override - boolean isEquivalentTo(Object o, List checkedObjects) { + boolean isEquivalentTo(COSBase o, List checkedObjects) { if (this == o) return true; if (o == null) return false; - if (o instanceof COSObject) { - return this.isEquivalentTo(((COSObject) o).get(), checkedObjects); - } + if (!(o instanceof COSStream)) return false; COSStream that = (COSStream) o; @@ -324,7 +322,7 @@ boolean isEquivalentTo(Object o, List checkedObjects) { return false; } } catch (IOException e) { - LOGGER.log(Level.FINE, "Exception during comparing decoded streams", e); + LOGGER.log(Level.FINE, "Exception during comparing decoded streams"); return false; } @@ -335,17 +333,8 @@ boolean isEquivalentTo(Object o, List checkedObjects) { if (!thisKeys.equals(thatKeys)) { return false; } - for (ASAtom key : thisKeys) { - COSBase thisVal = this.getKey(key).get(); - COSBase thatVal = that.getKey(key).get(); - if (thisVal == null && thatVal == null) continue; - if (thisVal == null || thatVal == null) return false; - if (!thisVal.isEquivalentTo(thatVal, checkedObjects)) { - return false; - } - } - return true; + return isEquivalentKeys(that, thisKeys, checkedObjects); } private static boolean equalsDecodedStreams(ASInputStream first, ASInputStream second) throws IOException { diff --git a/src/main/java/org/verapdf/cos/COSString.java b/src/main/java/org/verapdf/cos/COSString.java index 0128fc00..54437751 100644 --- a/src/main/java/org/verapdf/cos/COSString.java +++ b/src/main/java/org/verapdf/cos/COSString.java @@ -365,86 +365,22 @@ public void setHexCount(long hexCount) { this.hexCount = hexCount; } - private byte[] getCanonicalBytes() { - if (isHex) { - return Arrays.copyOf(value, value.length); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int i = 0; - while (i < value.length) { - byte b = value[i]; - if (b == '\\') { - if (i + 1 >= value.length) { - break; - } - byte next = value[i + 1]; - - if (next >= '0' && next <= '7') { - int octal = 0; - int count = 0; - while (i + 1 < value.length && count < 3) { - byte c = value[i + 1]; - if (c >= '0' && c <= '7') { - octal = octal * 8 + (c - '0'); - i++; - count++; - } else { - break; - } - } - out.write(octal); - continue; - } - - if (next == '\r') { - i += 2; - if (i < value.length && value[i] == '\n') { - i++; - } - continue; - } else if (next == '\n') { - i += 2; - continue; - } + @Override + public boolean isEquivalentTo(COSBase o) { + if (this == o) return true; - i++; - if (i >= value.length) break; - byte esc = value[i]; - byte replacement; - switch (esc) { - case 'n': replacement = '\n'; break; - case 'r': replacement = '\r'; break; - case 't': replacement = '\t'; break; - case 'b': replacement = '\b'; break; - case 'f': replacement = '\f'; break; - case '(': replacement = '('; break; - case ')': replacement = ')'; break; - case '\\': replacement = '\\'; break; - default: - replacement = esc; - break; - } - out.write(replacement); - } else { - out.write(b); - } - i++; + if (o.isIndirect()) { + return isEquivalentTo(o.getDirectBase()); } - return out.toByteArray(); - } - @Override - public boolean isEquivalentTo(Object o) { - if (this == o) return true; if (!(o instanceof COSString)) return false; COSString that = (COSString) o; - byte[] thisBytes = this.getCanonicalBytes(); - byte[] thatBytes = that.getCanonicalBytes(); + String thisString = this.getString(); + String thatString = that.getString(); - return Arrays.equals(thisBytes, thatBytes); + return thisString.equals(thatString); } @Override From 3d16ada11a8c2e6d1bdf0e10110681a04f994b0c Mon Sep 17 00:00:00 2001 From: Kakhnovich Raman Date: Tue, 4 Aug 2026 15:40:21 +0300 Subject: [PATCH 3/3] Add check for null --- src/main/java/org/verapdf/cos/COSBoolean.java | 1 + src/main/java/org/verapdf/cos/COSInteger.java | 1 + src/main/java/org/verapdf/cos/COSName.java | 1 + src/main/java/org/verapdf/cos/COSString.java | 1 + 4 files changed, 4 insertions(+) diff --git a/src/main/java/org/verapdf/cos/COSBoolean.java b/src/main/java/org/verapdf/cos/COSBoolean.java index 0fe6194a..3ba44c4d 100644 --- a/src/main/java/org/verapdf/cos/COSBoolean.java +++ b/src/main/java/org/verapdf/cos/COSBoolean.java @@ -74,6 +74,7 @@ public boolean setBoolean(final boolean value) { @Override public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o == null) return false; if (o.isIndirect()) { return isEquivalentTo(o.getDirectBase()); diff --git a/src/main/java/org/verapdf/cos/COSInteger.java b/src/main/java/org/verapdf/cos/COSInteger.java index d773661b..899e13e3 100644 --- a/src/main/java/org/verapdf/cos/COSInteger.java +++ b/src/main/java/org/verapdf/cos/COSInteger.java @@ -80,6 +80,7 @@ public boolean setReal(final double value) { @Override public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o == null) return false; if (o.isIndirect()) { return isEquivalentTo(o.getDirectBase()); diff --git a/src/main/java/org/verapdf/cos/COSName.java b/src/main/java/org/verapdf/cos/COSName.java index 1b2940ba..fc2b8c93 100644 --- a/src/main/java/org/verapdf/cos/COSName.java +++ b/src/main/java/org/verapdf/cos/COSName.java @@ -95,6 +95,7 @@ public boolean setName(final ASAtom value) { @Override public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o == null) return false; if (o.isIndirect()) { return isEquivalentTo(o.getDirectBase()); diff --git a/src/main/java/org/verapdf/cos/COSString.java b/src/main/java/org/verapdf/cos/COSString.java index 54437751..94940cf7 100644 --- a/src/main/java/org/verapdf/cos/COSString.java +++ b/src/main/java/org/verapdf/cos/COSString.java @@ -368,6 +368,7 @@ public void setHexCount(long hexCount) { @Override public boolean isEquivalentTo(COSBase o) { if (this == o) return true; + if (o == null) return false; if (o.isIndirect()) { return isEquivalentTo(o.getDirectBase());