Author: msahyoun Date: Fri Jan 3 10:57:03 2020 New Revision: 1872281 URL: http://svn.apache.org/viewvc?rev=1872281&view=rev Log: PDFBOX-4723: port equals and hashCode from trunk to 2.0; add SmallMap for COSDictionary; fix PDPageTree
Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/SmallMap.java Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java?rev=1872281&r1=1872280&r2=1872281&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java Fri Jan 3 10:57:03 2020 @@ -18,6 +18,7 @@ package org.apache.pdfbox.cos; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -408,6 +409,49 @@ public class COSArray extends COSBase im } /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + + if (o == this) + { + return true; + } + + if (!(o instanceof COSArray)) + { + return false; + } + + COSArray toBeCompared = (COSArray) o; + + if (toBeCompared.size() != size()) + { + return false; + } + + for (int i=0; i<size(); i++) + { + if (!(get(i).equals(toBeCompared.get(i)))) + { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + Object[] members = {objects, needToBeUpdated}; + return Arrays.hashCode(members); + } + + /** * {@inheritDoc} */ @Override Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java?rev=1872281&r1=1872280&r2=1872281&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java Fri Jan 3 10:57:03 2020 @@ -22,14 +22,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; -import java.util.LinkedHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.util.DateConverter; +import org.apache.pdfbox.util.SmallMap; /** * This class represents a dictionary where name/value pairs reside. @@ -45,7 +47,7 @@ public class COSDictionary extends COSBa /** * The name-value pairs of this dictionary. The pairs are kept in the order they were added to the dictionary. */ - protected Map<COSName, COSBase> items = new LinkedHashMap<COSName, COSBase>(); + protected Map<COSName, COSBase> items = new SmallMap<COSName, COSBase>(); /** * Constructor. @@ -1538,6 +1540,64 @@ public class COSDictionary extends COSBa /** * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (o == this) + { + return true; + } + + if (o == null || !(o.getClass() == COSDictionary.class)) + { + return false; + } + + COSDictionary toBeCompared = (COSDictionary) o; + + if (toBeCompared.size() != size()) + { + return false; + } + + Iterator<Entry<COSName, COSBase>> iter = entrySet().iterator(); + while (iter.hasNext()) + { + Entry<COSName, COSBase> entry = iter.next(); + COSName key = entry.getKey(); + COSBase value = entry.getValue(); + + if (!toBeCompared.containsKey(key)) + { + return false; + } + else if (value == null) + { + if (toBeCompared.getItem(key) != null) + { + return false; + } + } + else if (!value.equals(toBeCompared.getItem(key))) + { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + Object[] members = {items, needToBeUpdated}; + return Arrays.hashCode(members); + } + + /** + * {@inheritDoc} */ @Override public String toString() Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java?rev=1872281&r1=1872280&r2=1872281&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSStream.java Fri Jan 3 10:57:03 2020 @@ -23,7 +23,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.Map.Entry; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.filter.DecodeOptions; @@ -71,6 +75,73 @@ public class COSStream extends COSDictio setInt(COSName.LENGTH, 0); this.scratchFile = scratchFile != null ? scratchFile : ScratchFile.getMainMemoryOnlyInstance(); } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (o == this) + { + return true; + } + + if (!(o instanceof COSStream)) + { + return false; + } + + COSStream toBeCompared = (COSStream) o; + + if (toBeCompared.size() != size()) + { + return false; + } + + // compare dictionary content + Iterator<Entry<COSName, COSBase>> iter = entrySet().iterator(); + while (iter.hasNext()) + { + Entry<COSName, COSBase> entry = iter.next(); + COSName key = entry.getKey(); + COSBase value = entry.getValue(); + + if (!toBeCompared.containsKey(key)) + { + return false; + } + else if (value == null) + { + if (toBeCompared.getItem(key) != null) + { + return false; + } + } + else if (!value.equals(toBeCompared.getItem(key))) + { + return false; + } + } + + // compare stream content + if (!toBeCompared.toTextString().equals(toTextString())) + { + return false; + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + Object[] members = {items, randomAccess, scratchFile, isWriting}; + return Arrays.hashCode(members); + } + + /** * Throws if the random access backing store has been closed. Helpful for catching cases where Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java?rev=1872281&r1=1872280&r2=1872281&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDPageTree.java Fri Jan 3 10:57:03 2020 @@ -381,7 +381,7 @@ public class PDPageTree implements COSOb private void visitPage(COSDictionary current) { index++; - found = searched.equals(current); + found = searched == current; } } Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/SmallMap.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/SmallMap.java?rev=1872281&view=auto ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/SmallMap.java (added) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/util/SmallMap.java Fri Jan 3 10:57:03 2020 @@ -0,0 +1,388 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.pdfbox.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Map implementation with a smallest possible memory usage. + * It should only be used for maps with small number of items + * (e.g. <30) since most operations have an O(n) complexity. + * Thus it should be used in cases with large number of map + * objects, each having only few items. + * + * <p><code>null</code> is not supported for keys or values.</p> + */ +public class SmallMap<K, V> implements Map<K, V> +{ + /** + * stores key-value pair as 2 objects; key first; in case of empty map this might be <code>null</code> + */ + private Object[] mapArr; + + /** Creates empty map. */ + public SmallMap() + { + } + + /** Creates map filled with entries from provided map. */ + public SmallMap(Map<? extends K, ? extends V> initMap) + { + putAll(initMap); + } + + /** + * Returns index of key within map-array or <code>-1</code> + * if key is not found (or key is <code>null</code>). + */ + private int findKey(Object key) + { + if (isEmpty() || (key==null)) + { + return -1; + } + + for ( int aIdx = 0; aIdx < mapArr.length; aIdx+=2 ) + { + if (key.equals(mapArr[aIdx])) + { + return aIdx; + } + } + + return -1; + } + + /** + * Returns index of value within map-array or <code>-1</code> + * if value is not found (or value is <code>null</code>). + */ + private int findValue(Object value) + { + if (isEmpty() || (value==null)) + { + return -1; + } + + for ( int aIdx = 1; aIdx < mapArr.length; aIdx+=2 ) + { + if (value.equals(mapArr[aIdx])) + { + return aIdx; + } + } + + return -1; + } + + @Override + public int size() + { + return mapArr == null ? 0 : mapArr.length >> 1; + } + + @Override + public boolean isEmpty() + { + return (mapArr == null) || (mapArr.length == 0); + } + + @Override + public boolean containsKey(Object key) + { + return findKey(key) >= 0; + } + + @Override + public boolean containsValue(Object value) + { + return findValue(value) >= 0; + } + + @SuppressWarnings("unchecked") + @Override + public V get(Object key) + { + int kIdx = findKey(key); + + return kIdx < 0 ? null : (V) mapArr[kIdx+1]; + } + + @Override + public V put(K key, V value) + { + if ((key == null) || (value == null)) + { + throw new NullPointerException( "Key or value must not be null."); + } + + if (mapArr == null) + { + mapArr = new Object[] { key, value }; + return null; + } + else + { + int kIdx = findKey(key); + + if (kIdx < 0) + { + // key unknown + int oldLen = mapArr.length; + Object[] newMapArr = new Object[oldLen+2]; + System.arraycopy(mapArr, 0, newMapArr, 0, oldLen); + newMapArr[oldLen] = key; + newMapArr[oldLen+1] = value; + mapArr = newMapArr; + return null; + } + else + { + // key exists; replace value + @SuppressWarnings("unchecked") + V oldValue = (V) mapArr[kIdx+1]; + mapArr[kIdx+1] = value; + return oldValue; + } + } + } + + @Override + public V remove(Object key) + { + int kIdx = findKey(key); + + if (kIdx < 0) + { + // not found + return null; + } + + @SuppressWarnings("unchecked") + V oldValue = (V) mapArr[kIdx+1]; + int oldLen = mapArr.length; + + if (oldLen == 2) + { + // was last entry + mapArr = null; + } + else + { + Object[] newMapArr = new Object[oldLen-2]; + System.arraycopy(mapArr, 0, newMapArr, 0, kIdx); + System.arraycopy(mapArr, kIdx+2, newMapArr, kIdx, oldLen - kIdx - 2); + mapArr = newMapArr; + } + + return oldValue; + } + + @Override + public final void putAll(Map<? extends K, ? extends V> otherMap) + { + if ((mapArr == null) || (mapArr.length == 0)) + { + // existing map is empty + mapArr = new Object[otherMap.size() << 1]; + int aIdx = 0; + for (Entry<? extends K, ? extends V> entry : otherMap.entrySet()) + { + if ((entry.getKey() == null) || (entry.getValue() == null)) + { + throw new NullPointerException( "Key or value must not be null."); + } + + mapArr[aIdx++] = entry.getKey(); + mapArr[aIdx++] = entry.getValue(); + } + } + else + { + int oldLen = mapArr.length; + // first increase array size to hold all to put entries as if they have unknown keys + // reduce after adding all to the required size + Object[] newMapArr = new Object[oldLen+(otherMap.size() << 1)]; + System.arraycopy(mapArr, 0, newMapArr, 0, oldLen); + + int newIdx = oldLen; + for (Entry<? extends K, ? extends V> entry : otherMap.entrySet()) + { + if ((entry.getKey() == null) || (entry.getValue() == null)) + { + throw new NullPointerException( "Key or value must not be null."); + } + + int existKeyIdx = findKey(entry.getKey()); + + if (existKeyIdx >= 0) + { + // existing key + newMapArr[existKeyIdx+1] = entry.getValue(); + } + else + { + // new key + newMapArr[newIdx++] = entry.getKey(); + newMapArr[newIdx++] = entry.getValue(); + } + } + + if (newIdx < newMapArr.length) + { + Object[] reducedMapArr = new Object[newIdx]; + System.arraycopy(newMapArr, 0, reducedMapArr, 0, newIdx); + newMapArr = reducedMapArr; + } + + mapArr = newMapArr; + } + } + + @Override + public void clear() + { + mapArr = null; + } + + /** + * Returns a set view of the keys contained in this map. + * + * <p>The current implementation does not allow changes to the + * returned key set (which would have to be reflected in the + * underlying map.</p> + */ + @SuppressWarnings("unchecked") + @Override + public Set<K> keySet() + { + if (isEmpty()) + { + return Collections.emptySet(); + } + + Set<K> keys = new LinkedHashSet<K>(); + for (int kIdx = 0; kIdx < mapArr.length; kIdx+=2) + { + keys.add((K)mapArr[kIdx]); + } + return Collections.unmodifiableSet( keys ); + } + + /** + * Returns a collection of the values contained in this map. + * + * <p>The current implementation does not allow changes to the + * returned collection (which would have to be reflected in the + * underlying map.</p> + */ + @SuppressWarnings("unchecked") + @Override + public Collection<V> values() + { + if (isEmpty()) + { + return Collections.emptySet(); + } + + List<V> values = new ArrayList<V>(mapArr.length >> 1); + for (int vIdx = 1; vIdx < mapArr.length; vIdx+=2) + { + values.add((V)mapArr[vIdx]); + } + return Collections.unmodifiableList( values ); + } + + private class SmallMapEntry implements Entry<K, V> + { + private final int keyIdx; + + SmallMapEntry(int keyInMapIdx) + { + keyIdx = keyInMapIdx; + } + + @SuppressWarnings("unchecked") + @Override + public K getKey() + { + return (K)mapArr[keyIdx]; + } + + @SuppressWarnings("unchecked") + @Override + public V getValue() + { + return (V)mapArr[keyIdx+1]; + } + + @Override + public V setValue(V value) + { + if (value == null) + { + throw new NullPointerException( "Key or value must not be null."); + } + + V oldValue = getValue(); + mapArr[keyIdx+1] = value; + return oldValue; + } + + @Override + public int hashCode() + { + return getKey().hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (!(obj instanceof SmallMap.SmallMapEntry)) + { + return false; + } + @SuppressWarnings("unchecked") + SmallMapEntry other = (SmallMapEntry) obj; + + return getKey().equals(other.getKey()) && getValue().equals(other.getValue()); + } + } + + @Override + public Set<java.util.Map.Entry<K, V>> entrySet() + { + if (isEmpty()) + { + return Collections.emptySet(); + } + + Set<java.util.Map.Entry<K, V>> entries = new LinkedHashSet<Entry<K, V>>(); + for (int kIdx = 0; kIdx < mapArr.length; kIdx+=2) + { + entries.add(new SmallMapEntry(kIdx)); + } + return Collections.unmodifiableSet( entries ); + } + +}