You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ms...@apache.org on 2021/10/03 13:24:54 UTC

svn commit: r1893854 - in /pdfbox/trunk/pdfbox/src: main/java/org/apache/pdfbox/cos/ main/java/org/apache/pdfbox/cos/observer/ main/java/org/apache/pdfbox/pdfwriter/ main/java/org/apache/pdfbox/pdmodel/ main/java/org/apache/pdfbox/pdmodel/fdf/ test/jav...

Author: msahyoun
Date: Sun Oct  3 13:24:54 2021
New Revision: 1893854

URL: http://svn.apache.org/viewvc?rev=1893854&view=rev
Log:
PDFBOX-5263: introduce state based incremental handling as suggested by Christian Appl

Added:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocumentState.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSIncrement.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java
    pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSIncrement.java
      - copied, changed from r1893853, pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfoObserver.java
Removed:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfoObserver.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/observer/
    pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfoObserver.java
Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSBase.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObject.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfo.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java
    pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSArray.java Sun Oct  3 13:24:54 2021
@@ -24,9 +24,6 @@ import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
-import org.apache.pdfbox.cos.observer.event.COSAddEvent;
-import org.apache.pdfbox.cos.observer.event.COSReplaceEvent;
-import org.apache.pdfbox.cos.observer.event.COSRemoveEvent;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 
 /**
@@ -37,13 +34,14 @@ import org.apache.pdfbox.pdmodel.common.
 public class COSArray extends COSBase implements Iterable<COSBase>, COSUpdateInfo
 {
     private final List<COSBase> objects = new ArrayList<>();
+    private final COSUpdateState updateState;
 
     /**
      * Constructor.
      */
     public COSArray()
     {
-        //default constructor
+        updateState = new COSUpdateState(this);
     }
 
     /**
@@ -57,9 +55,9 @@ public class COSArray extends COSBase im
         {
             throw new IllegalArgumentException("List of COSObjectables cannot be null");
         }
+        updateState = new COSUpdateState(this);
         cosObjectables.forEach(cosObjectable ->
             objects.add(cosObjectable != null ? cosObjectable.getCOSObject() : null));
-        reportUpdate(new COSAddEvent<>(this, objects));
     }
 
     /**
@@ -70,7 +68,7 @@ public class COSArray extends COSBase im
     public void add( COSBase object )
     {
         objects.add( object );
-        reportUpdate(new COSAddEvent<>(this, object));
+        getUpdateState().update(object);
     }
 
     /**
@@ -81,7 +79,7 @@ public class COSArray extends COSBase im
     public void add( COSObjectable object )
     {
         objects.add( object.getCOSObject() );
-        reportUpdate(new COSAddEvent<>(this, object.getCOSObject()));
+        getUpdateState().update(object.getCOSObject());
     }
 
     /**
@@ -94,7 +92,7 @@ public class COSArray extends COSBase im
     public void add( int i, COSBase object)
     {
         objects.add( i, object );
-        reportUpdate(new COSAddEvent<>(this, object));
+        getUpdateState().update(object);
     }
 
     /**
@@ -102,9 +100,8 @@ public class COSArray extends COSBase im
      */
     public void clear()
     {
-        List<COSBase> removed = new ArrayList<>(objects);
         objects.clear();
-        reportUpdate(new COSRemoveEvent<>(this, removed));
+        getUpdateState().update();
     }
 
     /**
@@ -115,7 +112,7 @@ public class COSArray extends COSBase im
     public void removeAll( Collection<COSBase> objectsList )
     {
         objects.removeAll( objectsList );
-        reportUpdate(new COSRemoveEvent<>(this, objectsList));
+        getUpdateState().update();
     }
 
     /**
@@ -125,10 +122,8 @@ public class COSArray extends COSBase im
      */
     public void retainAll( Collection<COSBase> objectsList )
     {
-        List<COSBase> removed = new ArrayList<>(this.objects);
-        removed.removeAll(objectsList);
         objects.retainAll( objectsList );
-        reportUpdate(new COSRemoveEvent<>(this, removed));
+        getUpdateState().update();
     }
 
     /**
@@ -139,7 +134,7 @@ public class COSArray extends COSBase im
     public void addAll( Collection<COSBase> objectsList )
     {
         objects.addAll( objectsList );
-        reportUpdate(new COSAddEvent<>(this, objectsList));
+        getUpdateState().update(objectsList);
     }
 
     /**
@@ -152,7 +147,7 @@ public class COSArray extends COSBase im
         if( objectList != null )
         {
             objects.addAll( objectList.objects );
-            reportUpdate(new COSAddEvent<>(this, objectList));
+            getUpdateState().update(objectList);
         }
     }
 
@@ -166,7 +161,7 @@ public class COSArray extends COSBase im
     public void addAll( int i, Collection<COSBase> objectList )
     {
         objects.addAll( i, objectList );
-        reportUpdate(new COSAddEvent<>(this, objectList));
+        getUpdateState().update(objectList);
     }
 
     /**
@@ -177,13 +172,8 @@ public class COSArray extends COSBase im
      */
     public void set( int index, COSBase object )
     {
-        COSBase replacedEntry = null;
-        if(index >= 0 && index < size())
-        {
-            replacedEntry = get(index);
-        }
         objects.set( index, object );
-        reportUpdate(new COSReplaceEvent<>(this, replacedEntry, object));
+        getUpdateState().update(object);
     }
 
     /**
@@ -194,14 +184,8 @@ public class COSArray extends COSBase im
      */
     public void set( int index, int intVal )
     {
-        COSBase replacedEntry = null;
-        if(index >= 0 && index < size())
-        {
-            replacedEntry = get(index);
-        }
-        COSBase replacingEntry = COSInteger.get(intVal);
-        objects.set( index, replacingEntry );
-        reportUpdate(new COSReplaceEvent<>(this, replacedEntry, replacingEntry));
+        objects.set( index, COSInteger.get(intVal));
+        getUpdateState().update();
     }
 
     /**
@@ -212,18 +196,13 @@ public class COSArray extends COSBase im
      */
     public void set( int index, COSObjectable object )
     {
-        COSBase replacedEntry = null;
-        if(index >= 0 && index < size())
-        {
-            replacedEntry = get(index);
-        }
         COSBase base = null;
         if( object != null )
         {
             base = object.getCOSObject();
         }
         objects.set( index, base );
-        reportUpdate(new COSReplaceEvent<>(this, replacedEntry, base));
+        getUpdateState().update(base);
     }
 
     /**
@@ -414,7 +393,7 @@ public class COSArray extends COSBase im
     public COSBase remove( int i )
     {
         COSBase removedEntry = objects.remove( i );
-        reportUpdate(new COSRemoveEvent<>(this, removedEntry));
+        getUpdateState().update();
         return removedEntry;
     }
 
@@ -429,7 +408,7 @@ public class COSArray extends COSBase im
     public boolean remove( COSBase o )
     {
         boolean removed = objects.remove( o );
-        reportUpdate(new COSRemoveEvent<>(this, o));
+        getUpdateState().update();
         return removed;
     }
 
@@ -558,13 +537,12 @@ public class COSArray extends COSBase im
      */
     public void growToSize( int size, COSBase object )
     {
-        List<COSBase> addedObjects = new ArrayList<>();
         while( size() < size )
         {
             add( object );
-            addedObjects.add(object);
+            getUpdateState().update(object);
         }
-        reportUpdate(new COSAddEvent<>(this, addedObjects));
+        getUpdateState().update();
     }
 
     /**
@@ -732,5 +710,17 @@ public class COSArray extends COSBase im
         strings.forEach(s -> retval.add(new COSString(s)));
         return retval;
     }
-
+    
+    /**
+     * Returns the current {@link COSUpdateState} of this {@link COSArray}.
+     *
+     * @return The current {@link COSUpdateState} of this {@link COSArray}.
+     * @see COSUpdateState
+     */
+    @Override
+    public COSUpdateState getUpdateState()
+    {
+        return updateState;
+    }
+    
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSBase.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSBase.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSBase.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSBase.java Sun Oct  3 13:24:54 2021
@@ -16,14 +16,9 @@
  */
 package org.apache.pdfbox.cos;
 
-import org.apache.pdfbox.cos.observer.event.COSDirectObjectEvent;
-import org.apache.pdfbox.cos.observer.event.COSEvent;
-import org.apache.pdfbox.cos.observer.COSObserver;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 
 import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * The base object that all objects in the PDF document will extend.
@@ -32,8 +27,7 @@ import java.util.concurrent.CopyOnWriteA
  */
 public abstract class COSBase implements COSObjectable
 {
-
-    private final List<COSObserver> cosChangeObservers = new CopyOnWriteArrayList<>();
+    
     private boolean direct;
     private COSObjectKey key;
 
@@ -83,9 +77,6 @@ public abstract class COSBase implements
     public void setDirect(boolean direct)
     {
         this.direct = direct;
-        if(direct){
-            reportUpdate(new COSDirectObjectEvent<>(this));
-        }
     }
 
     /**
@@ -108,50 +99,4 @@ public abstract class COSBase implements
         this.key = key;
     }
 
-    /**
-     * Register the given {@link COSObserver} to this {@link COSBase}.
-     *
-     * @param observer The {@link COSObserver} to register.
-     * @see #reportUpdate(COSEvent)
-     */
-    public void registerObserver(COSObserver observer)
-    {
-        this.cosChangeObservers.add(observer);
-    }
-
-    /**
-     * Unregister the given {@link COSObserver} from this {@link COSBase}.
-     *
-     * @param observer The {@link COSObserver} to unregister.
-     * @see #reportUpdate(COSEvent)
-     */
-    public void unregisterObserver(COSObserver observer)
-    {
-        this.cosChangeObservers.remove(observer);
-    }
-
-    /**
-     * Returns the {@link COSObserver}s registered to this {@link COSBase}.
-     *
-     * @return A list of all {@link COSObserver}s currently registered to this {@link COSBase}.
-     */
-    public List<COSObserver> getRegisteredObservers()
-    {
-        return this.cosChangeObservers;
-    }
-
-    /**
-     * Report a change to this {@link COSBase} as a {@link COSEvent} to all registered {@link COSObserver}s.
-     *
-     * @param event      The {@link COSEvent} to report.
-     * @param <COS_TYPE> The explicit {@link COSBase} type of the reporting object.
-     * @see #registerObserver(COSObserver)
-     */
-    public <COS_TYPE extends COSBase> void reportUpdate(COSEvent<COS_TYPE> event)
-    {
-        for (COSObserver observer : this.cosChangeObservers) {
-            observer.reportUpdate(event);
-        }
-    }
-
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDictionary.java Sun Oct  3 13:24:54 2021
@@ -30,9 +30,6 @@ import java.util.function.BiConsumer;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.apache.pdfbox.cos.observer.event.COSAddEvent;
-import org.apache.pdfbox.cos.observer.event.COSReplaceEvent;
-import org.apache.pdfbox.cos.observer.event.COSRemoveEvent;
 import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.common.COSObjectable;
 import org.apache.pdfbox.util.DateConverter;
@@ -58,13 +55,14 @@ 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 SmallMap<>();
+    private final COSUpdateState updateState;
 
     /**
      * Constructor.
      */
     public COSDictionary()
     {
-        // default constructor
+        updateState = new COSUpdateState(this);
     }
 
     /**
@@ -74,8 +72,8 @@ public class COSDictionary extends COSBa
      */
     public COSDictionary(COSDictionary dict)
     {
+        updateState = new COSUpdateState(this);
         items.putAll(dict.items);
-        reportUpdate(new COSAddEvent<>(this, items.values()));
     }
 
     /**
@@ -131,9 +129,8 @@ public class COSDictionary extends COSBa
      */
     public void clear()
     {
-        List<COSBase> removed = new ArrayList<>(items.values());
         items.clear();
-        reportUpdate(new COSRemoveEvent<>(this, removed));
+        getUpdateState().update();
     }
 
     /**
@@ -206,20 +203,8 @@ public class COSDictionary extends COSBa
         }
         else
         {
-            COSBase replacedEntry = null;
-            if (items.containsKey(key))
-            {
-                replacedEntry = items.get(key);
-            }
             items.put(key, value);
-            if(replacedEntry != null)
-            {
-                reportUpdate(new COSReplaceEvent<>(this, replacedEntry, value));
-            }
-            else
-            {
-                reportUpdate(new COSAddEvent<>(this, value));
-            }
+            getUpdateState().update(value);
         }
     }
 
@@ -1168,9 +1153,8 @@ public class COSDictionary extends COSBa
      */
     public void removeItem(COSName key)
     {
-        COSBase removed = getItem(key);
         items.remove(key);
-        reportUpdate(new COSRemoveEvent<>(this, removed));
+        getUpdateState().update();
     }
 
     /**
@@ -1416,4 +1400,17 @@ public class COSDictionary extends COSBa
         }
         return base.toString();
     }
+    
+    /**
+     * Returns the current {@link COSUpdateState} of this {@link COSDictionary}.
+     *
+     * @return The current {@link COSUpdateState} of this {@link COSDictionary}.
+     * @see COSUpdateState
+     */
+    @Override
+    public COSUpdateState getUpdateState()
+    {
+        return updateState;
+    }
+    
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocument.java Sun Oct  3 13:24:54 2021
@@ -46,8 +46,7 @@ public class COSDocument extends COSBase
      * Log instance.
      */
     private static final Log LOG = LogFactory.getLog(COSDocument.class);
-
-    private final COSUpdateInfoObserver updateObserver;
+    
     private float version = 1.4f;
 
     /**
@@ -64,7 +63,7 @@ public class COSDocument extends COSBase
         new HashMap<>();
 
     /**
-     * List containing all streams which are created when creating a new pdf. 
+     * List containing all streams which are created when creating a new pdf.
      */
     private final List<COSStream> streams = new ArrayList<>();
     
@@ -73,8 +72,8 @@ public class COSDocument extends COSBase
      */
     private COSDictionary trailer;
     
-    /** 
-     * Signal that document is already decrypted. 
+    /**
+     * Signal that document is already decrypted.
      */
     private boolean isDecrypted = false;
     
@@ -92,6 +91,8 @@ public class COSDocument extends COSBase
     private long highestXRefObjectNumber;
 
     private final ICOSParser parser;
+    
+    private final COSDocumentState documentState = new COSDocumentState();
 
     /**
      * Constructor. Uses main memory to buffer PDF streams.
@@ -131,7 +132,6 @@ public class COSDocument extends COSBase
      */
     public COSDocument(MemoryUsageSetting memUsageSetting, ICOSParser parser)
     {
-        this.updateObserver = new COSUpdateInfoObserver(this);
         try
         {
             if (memUsageSetting != null)
@@ -365,7 +365,7 @@ public class COSDocument extends COSBase
     public void setTrailer(COSDictionary newTrailer)
     {
         trailer = newTrailer;
-        updateObserver.monitor(newTrailer);
+        trailer.getUpdateState().setOriginDocumentState(documentState);
     }
 
     /**
@@ -542,15 +542,16 @@ public class COSDocument extends COSBase
     {
         isXRefStream = isXRefStreamValue;
     }
-
+    
     /**
-     * Returns the {@link COSUpdateInfoObserver}, that is keeping track of changes to this {@link COSDocument}.
+     * Returns the {@link COSDocumentState} of this {@link COSDocument}.
      *
-     * @return The {@link COSUpdateInfoObserver}, that is keeping track of changes to this {@link COSDocument}.
+     * @return The {@link COSDocumentState} of this {@link COSDocument}.
+     * @see COSDocumentState
      */
-    public COSUpdateInfoObserver getUpdateObserver()
+    public COSDocumentState getDocumentState()
     {
-        return updateObserver;
+        return documentState;
     }
-
+    
 }

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocumentState.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocumentState.java?rev=1893854&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocumentState.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSDocumentState.java Sun Oct  3 13:24:54 2021
@@ -0,0 +1,58 @@
+/*
+ * 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.cos;
+
+/**
+ * An instance of {@link COSDocumentState} collects all known states a {@link COSDocument} may have and shall allow
+ * their evaluation.
+ *
+ * @author Christian Appl
+ * @see COSDocument
+ */
+public class COSDocumentState
+{
+    
+    /**
+     * The parsing state of the document.
+     * <ul>
+     * <li>{@code true}, if the document is currently being parsed. (initial state)</li>
+     * <li>{@code false}, if the document's parsing completed and it may be edited and updated.</li>
+     * </ul>
+     */
+    private boolean parsing = true;
+    
+    /**
+     * Sets the {@link #parsing} state of the document.
+     *
+     * @param parsing The {@link #parsing} state to set.
+     */
+    public void setParsing(boolean parsing)
+    {
+        this.parsing = parsing;
+    }
+    
+    /**
+     * Returns {@code true}, if the document´s {@link #parsing} is completed and it may be updated.
+     *
+     * @return {@code true}, if the document´s {@link #parsing} is completed and it may be updated.
+     */
+    public boolean isAcceptingUpdates()
+    {
+        return !parsing;
+    }
+    
+}

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSIncrement.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSIncrement.java?rev=1893854&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSIncrement.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSIncrement.java Sun Oct  3 13:24:54 2021
@@ -0,0 +1,358 @@
+/*
+ * 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.cos;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A {@link COSIncrement} starts at a given {@link COSUpdateInfo} to collect updates, that have been made to a
+ * {@link COSDocument} and therefore should be added to it´s next increment.
+ *
+ * @author Christian Appl
+ * @see COSUpdateState
+ * @see COSUpdateInfo
+ */
+public class COSIncrement implements Iterable<COSBase>
+{
+    
+    /**
+     * Contains the {@link COSBase}s, that shall be added to the increment at top level.
+     */
+    private final Set<COSBase> objects = new LinkedHashSet<>();
+    /**
+     * Contains the direct {@link COSBase}s, that are either contained written directly by structures contained in
+     * {@link #objects} or that must be excluded from being written as indirect {@link COSObject}s for other reasons.
+     */
+    private final Set<COSBase> excluded = new HashSet<>();
+    /**
+     * Contains all {@link COSObject}s, that have already been processed by this {@link COSIncrement} and shall not be
+     * processed again.
+     */
+    private final Set<COSObject> processedObjects = new HashSet<>();
+    /**
+     * Contains the {@link COSUpdateInfo} that this {@link COSIncrement} creates an increment for.
+     */
+    private final COSUpdateInfo incrementOrigin;
+    /**
+     * Whether this {@link COSIncrement} has already been determined, or must still be evaluated.
+     */
+    private boolean initialized = false;
+    
+    /**
+     * Creates a new {@link COSIncrement} for the given {@link COSUpdateInfo}, the increment will use it´s
+     * {@link COSDocumentState} as it´s own origin and shall conllect all updates contained in the given
+     * {@link COSUpdateInfo}.<br>
+     * Should the given object be {@code null}, the resulting increment shall be empty.
+     *
+     * @param incrementOrigin The {@link COSUpdateInfo} serving as an update source for this {@link COSIncrement}.
+     */
+    public COSIncrement(COSUpdateInfo incrementOrigin)
+    {
+        this.incrementOrigin = incrementOrigin;
+    }
+    
+    /**
+     * Collect all updates made to the given {@link COSBase} and it's contained structures.<br>
+     * This shall forward all {@link COSUpdateInfo} objects to the proper specialized collection methods.
+     *
+     * @param base The {@link COSBase} updates shall be collected for.
+     * @return Returns {@code true}, if the {@link COSBase} represents a direct child structure, that would require it´s
+     * parent to be updated instead.
+     * @see #collect(COSDictionary)
+     * @see #collect(COSArray)
+     * @see #collect(COSObject)
+     */
+    private boolean collect(COSBase base)
+    {
+        if(contains(base))
+        {
+            return false;
+        }
+        // handle updatable objects:
+        if(base instanceof COSDictionary)
+        {
+            return collect((COSDictionary) base);
+        }
+        else if(base instanceof COSObject)
+        {
+            return collect((COSObject) base);
+        }
+        else if(base instanceof COSArray)
+        {
+            return collect((COSArray) base);
+        }
+        return false;
+    }
+    
+    /**
+     * Collect all updates made to the given {@link COSDictionary} and it's contained structures.
+     *
+     * @param dictionary The {@link COSDictionary} updates shall be collected for.
+     * @return Returns {@code true}, if the {@link COSDictionary} represents a direct child structure, that would
+     * require it´s parent to be updated instead.
+     */
+    private boolean collect(COSDictionary dictionary)
+    {
+        COSUpdateState updateState = dictionary.getUpdateState();
+        // Is definitely part of the increment?
+        if(!isExcluded(dictionary) && !contains(dictionary) && updateState.isUpdated())
+        {
+            add(dictionary);
+        }
+        boolean childDemandsParentUpdate = false;
+        // Collect children:
+        for(COSBase entry : dictionary.getValues())
+        {
+            // Primitives can not be part of an increment. (on top level)
+            if(!(entry instanceof COSUpdateInfo) || contains(entry))
+            {
+                continue;
+            }
+            COSUpdateInfo updatableEntry = (COSUpdateInfo) entry;
+            COSUpdateState entryUpdateState = ((COSUpdateInfo) entry).getUpdateState();
+            // Entries with different document origin must be part of the increment!
+            updateDifferentOrigin(entryUpdateState);
+            // Always attempt to write COSArrays as direct objects.
+            if(updatableEntry.isNeedToBeUpdated() &&
+                ((!(entry instanceof COSObject) && entry.isDirect()) || entry instanceof COSArray))
+            {
+                // Exclude direct entries from the increment!
+                exclude(entry);
+                childDemandsParentUpdate = true;
+            }
+            // Collect descendants:
+            childDemandsParentUpdate = collect(entry) || childDemandsParentUpdate;
+        }
+        
+        if(isExcluded(dictionary))
+        {
+            return childDemandsParentUpdate;
+        }
+        else
+        {
+            if(childDemandsParentUpdate && !contains(dictionary))
+            {
+                add(dictionary);
+            }
+            return false;
+        }
+    }
+    
+    /**
+     * Collect all updates made to the given {@link COSArray} and it's contained structures.
+     *
+     * @param array The {@link COSDictionary} updates shall be collected for.
+     * @return Returns {@code true}, if the {@link COSArray}´s elements changed. A {@link COSArray} shall always be
+     * treated as a direct structure, that would require it´s parent to be updated instead.
+     */
+    private boolean collect(COSArray array)
+    {
+        COSUpdateState updateState = array.getUpdateState();
+        boolean childDemandsParentUpdate = updateState.isUpdated();
+        for(COSBase entry : array)
+        {
+            // Primitives can not be part of an increment. (on top level)
+            if(!(entry instanceof COSUpdateInfo) || contains(entry))
+            {
+                continue;
+            }
+            COSUpdateState entryUpdateState = ((COSUpdateInfo) entry).getUpdateState();
+            // Entries with different document origin must be part of the increment!
+            updateDifferentOrigin(entryUpdateState);
+            // Collect descendants:
+            childDemandsParentUpdate = collect(entry) || childDemandsParentUpdate;
+        }
+        return childDemandsParentUpdate;
+    }
+    
+    /**
+     * Collect all updates made to the given {@link COSObject} and it's contained structures.
+     *
+     * @param object The {@link COSObject} updates shall be collected for.
+     * @return Always returns {@code false}. {@link COSObject}s by definition are indirect and shall never cause a
+     * parent structure to be updated.
+     */
+    private boolean collect(COSObject object)
+    {
+        if(contains(object))
+        {
+            return false;
+        }
+        addProcessedObject(object);
+        COSUpdateState updateState = object.getUpdateState();
+        // Objects with different document origin must be part of the increment!
+        updateDifferentOrigin(updateState);
+        // determine actual, if necessary or possible without dereferencing:
+        COSUpdateInfo actual = null;
+        if(updateState.isUpdated() || object.isDereferenced())
+        {
+            COSBase base = object.getObject();
+            if(base instanceof COSUpdateInfo)
+            {
+                actual = (COSUpdateInfo) base;
+            }
+        }
+        // Skip?
+        if(actual == null || contains(actual.getCOSObject()))
+        {
+            return false;
+        }
+        boolean childDemandsParentUpdate = false;
+        COSUpdateState actualUpdateState = actual.getUpdateState();
+        if(actualUpdateState.isUpdated())
+        {
+            childDemandsParentUpdate = true;
+        }
+        exclude(actual.getCOSObject());
+        childDemandsParentUpdate = collect(actual.getCOSObject()) || childDemandsParentUpdate;
+        if(updateState.isUpdated() || childDemandsParentUpdate)
+        {
+            add(actual.getCOSObject());
+        }
+        return false;
+    }
+    
+    /**
+     * Returns {@code true}, if the given {@link COSBase} is already known to and has been processed by this
+     * {@link COSIncrement}.
+     *
+     * @param base The {@link COSBase} to check.
+     * @return {@code true}, if the given {@link COSBase} is already known to and has been processed by this
+     * {@link COSIncrement}.
+     * @see #objects
+     * @see #processedObjects
+     */
+    public boolean contains(COSBase base)
+    {
+        return objects.contains(base) || (base instanceof COSObject && processedObjects.contains((COSObject) base));
+    }
+    
+    /**
+     * Check whether the given {@link COSUpdateState}´s {@link COSDocumentState} differs from the {@link COSIncrement}´s
+     * known {@link #incrementOrigin}.<br>
+     * Should that be the case, the {@link COSUpdateState} originates from another {@link COSDocument} and must be added
+     * to the {@link COSIncrement}, hence call {@link COSUpdateState#update()}.
+     *
+     * @param updateState The {@link COSUpdateState} that shall be updated, if it's originating from another
+     *                    {@link COSDocument}.
+     * @see #incrementOrigin
+     */
+    private void updateDifferentOrigin(COSUpdateState updateState)
+    {
+        if(incrementOrigin != null && updateState != null &&
+            incrementOrigin.getUpdateState().getOriginDocumentState() != updateState.getOriginDocumentState())
+        {
+            updateState.update();
+        }
+    }
+    
+    /**
+     * The given object and actual {COSBase}s shall be part of the increment and must be added to {@link #objects},
+     * if possible.<br>
+     * {@code null} values shall be skipped.
+     *
+     * @param object The {@link COSBase} to add to {@link #objects}.
+     * @see #objects
+     */
+    private void add(COSBase object)
+    {
+        if(object != null)
+        {
+            objects.add(object);
+        }
+    }
+    
+    /**
+     * The given {@link COSObject} has been processed, or is being processed. It shall be added to
+     * {@link #processedObjects} to skip it, should it be encountered again.<br>
+     * {@code null} values shall be ignored.
+     *
+     * @param base The {@link COSObject} to add to {@link #processedObjects}.
+     * @see #processedObjects
+     */
+    private void addProcessedObject(COSObject base)
+    {
+        if(base != null)
+        {
+            processedObjects.add(base);
+        }
+    }
+    
+    /**
+     * The given {@link COSBase}s are not fit for inclusion in an increment and shall be added to {@link #excluded}.<br>
+     * {@code null} values shall be ignored.
+     *
+     * @param base The {@link COSBase}s to add to {@link #excluded}.
+     * @return The {@link COSIncrement} itself, to allow method chaining.
+     * @see #excluded
+     */
+    public COSIncrement exclude(COSBase... base)
+    {
+        if(base != null)
+        {
+            excluded.addAll(Arrays.asList(base));
+        }
+        return this;
+    }
+    
+    /**
+     * Returns {@code true}, if the given {@link COSBase} has been excluded from the increment, and hence is contained
+     * in {@link #excluded}.
+     *
+     * @param base The {@link COSBase} to check for exclusion.
+     * @return {@code true}, if the given {@link COSBase} has been excluded from the increment, and hence is contained
+     * in {@link #excluded}.
+     * @see #excluded
+     */
+    private boolean isExcluded(COSBase base)
+    {
+        return excluded.contains(base);
+    }
+    
+    /**
+     * Returns all indirect {@link COSBase}s, that shall be written to an increment as top level {@link COSObject}s.<br>
+     * Calling this method will cause the increment to be initialized.
+     *
+     * @return All indirect {@link COSBase}s, that shall be written to an increment as top level {@link COSObject}s.
+     * @see #objects
+     */
+    public Set<COSBase> getObjects()
+    {
+        if(!initialized && incrementOrigin != null)
+        {
+            collect(incrementOrigin.getCOSObject());
+            initialized = true;
+        }
+        return objects;
+    }
+    
+    /**
+     * Return an iterator for the determined {@link #objects} contained in this {@link COSIncrement}.
+     *
+     * @return An iterator for the determined {@link #objects} contained in this {@link COSIncrement}.
+     */
+    @Override
+    public Iterator<COSBase> iterator()
+    {
+        return getObjects().iterator();
+    }
+    
+}

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObject.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObject.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObject.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObject.java Sun Oct  3 13:24:54 2021
@@ -20,8 +20,6 @@ import java.io.IOException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.pdfbox.cos.observer.event.COSDereferenceEvent;
-import org.apache.pdfbox.cos.observer.event.COSRemoveEvent;
 
 /**
  * This class represents a PDF object.
@@ -36,7 +34,8 @@ public class COSObject extends COSBase i
     private int generationNumber;
     private ICOSParser parser;
     private boolean isDereferenced = false;
-
+    private final COSUpdateState updateState;
+    
     private static final Log LOG = LogFactory.getLog(COSObject.class);
 
     /**
@@ -47,9 +46,9 @@ public class COSObject extends COSBase i
      */
     public COSObject(COSBase object)
     {
+        updateState = new COSUpdateState(this);
         baseObject = object;
         isDereferenced = true;
-        reportUpdate(new COSDereferenceEvent<>(this, baseObject));
     }
 
     /**
@@ -63,7 +62,6 @@ public class COSObject extends COSBase i
         this(objectKey, null);
         baseObject = object;
         isDereferenced = true;
-        reportUpdate(new COSDereferenceEvent<>(this, baseObject));
     }
 
     /**
@@ -75,11 +73,9 @@ public class COSObject extends COSBase i
      */
     public COSObject(COSBase object, ICOSParser parser)
     {
+        updateState = new COSUpdateState(this);
         baseObject = object;
         isDereferenced = object != null;
-        if (isDereferenced) {
-            reportUpdate(new COSDereferenceEvent<>(this, baseObject));
-        }
         this.parser = parser;
     }
 
@@ -92,6 +88,7 @@ public class COSObject extends COSBase i
      */
     public COSObject(COSObjectKey key, ICOSParser parser)
     {
+        updateState = new COSUpdateState(this);
         this.parser = parser;
         objectNumber = key.getNumber();
         generationNumber = key.getGeneration();
@@ -122,7 +119,7 @@ public class COSObject extends COSBase i
                 // mark as dereferenced to avoid endless recursions
                 isDereferenced = true;
                 baseObject = parser.dereferenceCOSObject(this);
-                reportUpdate(new COSDereferenceEvent<>(this, baseObject));
+                getUpdateState().dereferenceChild(baseObject);
             }
             catch (IOException e)
             {
@@ -143,7 +140,7 @@ public class COSObject extends COSBase i
     {
         if(baseObject != null)
         {
-            reportUpdate(new COSRemoveEvent<>(this, baseObject));
+            getUpdateState().update();
         }
         baseObject = COSNull.NULL;
         parser = null;
@@ -199,5 +196,17 @@ public class COSObject extends COSBase i
     {
         return isDereferenced;
     }
-
+    
+    /**
+     * Returns the current {@link COSUpdateState} of this {@link COSObject}.
+     *
+     * @return The current {@link COSUpdateState} of this {@link COSObject}.
+     * @see COSUpdateState
+     */
+    @Override
+    public COSUpdateState getUpdateState()
+    {
+        return updateState;
+    }
+    
 }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfo.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfo.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfo.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateInfo.java Sun Oct  3 13:24:54 2021
@@ -16,11 +16,9 @@
  */
 package org.apache.pdfbox.cos;
 
-import org.apache.pdfbox.cos.observer.COSIncrementObserver;
-import org.apache.pdfbox.cos.observer.event.COSDirectUpdateEvent;
-import org.apache.pdfbox.cos.observer.COSObserver;
+import org.apache.pdfbox.pdmodel.common.COSObjectable;
 
-public interface COSUpdateInfo
+public interface COSUpdateInfo extends COSObjectable
 {
 
     /**
@@ -31,21 +29,7 @@ public interface COSUpdateInfo
      */
     default boolean isNeedToBeUpdated()
     {
-        COSBase instance = getCOSObject();
-        if (instance != null)
-        {
-            for (COSObserver observer : instance.getRegisteredObservers())
-            {
-                if (observer instanceof COSIncrementObserver)
-                {
-                    if (((COSIncrementObserver) observer).isNeedToBeUpdated(instance))
-                    {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
+        return getUpdateState().isUpdated();
     }
 
     /**
@@ -56,19 +40,26 @@ public interface COSUpdateInfo
      */
     default void setNeedToBeUpdated(boolean flag)
     {
-        COSBase instance = getCOSObject();
-        if (instance != null)
-        {
-            instance.reportUpdate(new COSDirectUpdateEvent<>(instance, flag));
-        }
+        getUpdateState().update(flag);
     }
-
+    
     /**
-     * Convert this standard java object to a COS object.
+     * Uses this {@link COSUpdateInfo} as the base object of a new {@link COSIncrement}.
      *
-     * @return The cos object that matches this Java object.
-     * @see COSBase#getCOSObject()
+     * @return A {@link COSIncrement} based on this {@link COSUpdateInfo}.
+     * @see COSIncrement
      */
-    COSBase getCOSObject();
-
+    default COSIncrement toIncrement()
+    {
+        return getUpdateState().toIncrement();
+    }
+    
+    /**
+     * Returns the current {@link COSUpdateState} of this {@link COSUpdateInfo}.
+     *
+     * @return The current {@link COSUpdateState} of this {@link COSUpdateInfo}.
+     * @see COSUpdateState
+     */
+    COSUpdateState getUpdateState();
+    
 }
\ No newline at end of file

Added: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java?rev=1893854&view=auto
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java (added)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSUpdateState.java Sun Oct  3 13:24:54 2021
@@ -0,0 +1,344 @@
+/*
+ * 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.cos;
+
+/**
+ * A {@link COSUpdateState} instance manages update states for a {@link COSUpdateInfo}. Such states are used to create
+ * a {@link COSIncrement} for the incremental saving of a {@link COSDocument}.
+ *
+ * @author Christian Appl
+ * @see COSDocumentState
+ * @see COSUpdateInfo
+ * @see COSIncrement
+ */
+public class COSUpdateState
+{
+    
+    /**
+     * The {@link COSUpdateInfo} the {@link COSUpdateState} does manage update states for.
+     */
+    private final COSUpdateInfo updateInfo;
+    /**
+     * The {@link COSDocumentState} the {@link #updateInfo} is linked to.
+     */
+    private COSDocumentState originDocumentState = null;
+    /**
+     * The actual update state of {@link #updateInfo}.
+     * <ul>
+     * <li>{@code true}, if {@link #updateInfo} has been updated after the document completed parsing.</li>
+     * <li>{@code false}, if {@link #updateInfo} has remained unaltered since the document completed parsing.</li>
+     * </ul>
+     */
+    private boolean updated = false;
+    
+    /**
+     * Creates a new {@link COSUpdateState} for the given {@link COSUpdateInfo}.
+     *
+     * @param updateInfo The {@link COSUpdateInfo}, that shall be managed by this {@link COSUpdateState}.
+     */
+    public COSUpdateState(COSUpdateInfo updateInfo)
+    {
+        this.updateInfo = updateInfo;
+    }
+    
+    /**
+     * <p>
+     * Links the given {@link COSDocumentState} to the {@link #updated} state of the managed {@link #updateInfo}.<br>
+     * </p>
+     * <p>
+     * This shall also initialize {@link #updated} accordingly and will also set the same {@link COSDocumentState} for
+     * all possibly contained substructures.
+     * </p>
+     * <p>
+     * Should {@link #originDocumentState} already have been set, by a prior call to this method, this shall deny to
+     * overwrite it.
+     * </p>
+     * <p>
+     * {@link COSDocumentState#isAcceptingUpdates()} shall determine, whether updates to {@link #updateInfo} are
+     * allowed.
+     * </p>
+     * <p>
+     * As long as no {@link COSDocumentState} is linked to this {@link COSUpdateState}, it shall not accept updates.
+     * </p>
+     *
+     * @param originDocumentState The {@link COSDocumentState} that shall be linked to this {@link COSUpdateState}.
+     * @see #originDocumentState
+     * @see #updated
+     */
+    public void setOriginDocumentState(COSDocumentState originDocumentState)
+    {
+        setOriginDocumentState(originDocumentState, false);
+    }
+    
+    /**
+     * <p>
+     * Links the given {@link COSDocumentState} to the {@link #updated} state of the managed {@link #updateInfo}.<br>
+     * </p>
+     * <p>
+     * This shall also initialize {@link #updated} accordingly and will also set the same {@link COSDocumentState} for
+     * all possibly contained substructures.
+     * </p>
+     * <p>
+     * Should {@link #originDocumentState} already have been set, by a prior call to this method, this shall deny to
+     * overwrite it.
+     * </p>
+     * <p>
+     * {@link COSDocumentState#isAcceptingUpdates()} shall determine, whether updates to {@link #updateInfo} are
+     * allowed.
+     * </p>
+     * <p>
+     * As long as no {@link COSDocumentState} is linked to this {@link COSUpdateState}, it shall not accept updates.
+     * </p>
+     * <p>
+     * Additionally to {@link #setOriginDocumentState(COSDocumentState)}, this shall also deny changing
+     * {@link #updated}, should the flag {@code dereferencing} indicate, that this is caused by dereferencing a
+     * {@link COSObject}.
+     * </p>
+     *
+     * @param originDocumentState The {@link COSDocumentState} that shall be linked to this {@link COSUpdateState}.
+     * @param dereferencing       {@code true}, if this update of the {@link COSDocumentState} is caused by
+     *                            dereferencing a {@link COSObject}.
+     * @see #originDocumentState
+     * @see #updated
+     */
+    private void setOriginDocumentState(COSDocumentState originDocumentState, boolean dereferencing)
+    {
+        if(this.originDocumentState != null || originDocumentState == null)
+        {
+            return;
+        }
+        this.originDocumentState = originDocumentState;
+        if(!dereferencing)
+        {
+            update();
+        }
+        
+        if(updateInfo instanceof COSDictionary)
+        {
+            COSDictionary dictionary = (COSDictionary) updateInfo;
+            for(COSBase entry : dictionary.getValues())
+            {
+                if(!(entry instanceof COSUpdateInfo))
+                {
+                    continue;
+                }
+                ((COSUpdateInfo) entry).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
+            }
+        }
+        else if(updateInfo instanceof COSArray)
+        {
+            COSArray array = (COSArray) updateInfo;
+            for(COSBase entry : array)
+            {
+                if(!(entry instanceof COSUpdateInfo))
+                {
+                    continue;
+                }
+                ((COSUpdateInfo) entry).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
+            }
+        }
+        else if(updateInfo instanceof COSObject)
+        {
+            COSObject object = (COSObject) updateInfo;
+            COSBase reference;
+            if(object.isDereferenced() && (reference = object.getObject()) instanceof COSUpdateInfo)
+            {
+                ((COSUpdateInfo) reference).getUpdateState().setOriginDocumentState(originDocumentState, dereferencing);
+            }
+        }
+    }
+    
+    /**
+     * <p>
+     * Returns the {@link #originDocumentState}, that is linked to the managed {@link #updateInfo}.
+     * </p>
+     * <p>
+     * {@link COSDocumentState#isAcceptingUpdates()} shall determine, whether updates to {@link #updateInfo} are
+     * allowed.
+     * </p>
+     * <p>
+     * As long as no {@link COSDocumentState} is linked to this {@link COSUpdateState}, it shall not accept updates.
+     * </p>
+     *
+     * @return The {@link COSDocumentState} linked to this {@link COSUpdateState}.
+     * @see #setOriginDocumentState(COSDocumentState)
+     */
+    public COSDocumentState getOriginDocumentState()
+    {
+        return originDocumentState;
+    }
+    
+    /**
+     * Returns {@code true}, if the linked {@link #originDocumentState} {@link COSDocumentState#isAcceptingUpdates()}
+     * and such a {@link COSDocumentState} has been linked to this {@link COSUpdateState}.
+     *
+     * @return {@code true}, if the linked {@link #originDocumentState} {@link COSDocumentState#isAcceptingUpdates()}
+     * and such a {@link COSDocumentState} has been linked to this {@link COSUpdateState}.
+     * @see #originDocumentState
+     * @see COSDocumentState#isAcceptingUpdates()
+     */
+    boolean isAcceptingUpdates()
+    {
+        return originDocumentState != null && originDocumentState.isAcceptingUpdates();
+    }
+    
+    /**
+     * Returns the actual {@link #updated} state of the managed {@link #updateInfo}.
+     *
+     * @return The actual {@link #updated} state of the managed {@link #updateInfo}
+     * @see #updated
+     */
+    public boolean isUpdated()
+    {
+        return updated;
+    }
+    
+    /**
+     * Calls {@link #update(boolean)} with {@code true} as the new update state.<br>
+     * This shall only then have an effect, if {@link #isAcceptingUpdates()} returns {@code true}.
+     *
+     * @see #update(boolean)
+     * @see #updated
+     * @see #isAcceptingUpdates()
+     */
+    void update()
+    {
+        update(true);
+    }
+    
+    /**
+     * Sets the {@link #updated} state of the managed {@link #updateInfo} to the given state.<br>
+     * This shall only then have an effect, if {@link #isAcceptingUpdates()} returns {@code true}.
+     *
+     * @param updated The state to set for {@link #updated}.
+     * @see #update(boolean)
+     * @see #updated
+     * @see #isAcceptingUpdates()
+     */
+    void update(boolean updated)
+    {
+        if(isAcceptingUpdates())
+        {
+            this.updated = updated;
+        }
+    }
+    
+    /**
+     * <p>
+     * Shall call {@link #update()} for this {@link COSUpdateState} and shall
+     * {@link #setOriginDocumentState(COSDocumentState)} for the given child, initializing it´s {@link #updated} state
+     * and {@link #originDocumentState}.
+     * </p>
+     * <p>
+     * This shall have no effect for a child, that is not an instance of {@link COSUpdateInfo}.
+     * </p>
+     *
+     * @param child The child that shall also be updated.
+     * @see #update()
+     * @see #setOriginDocumentState(COSDocumentState)
+     */
+    void update(COSBase child)
+    {
+        update();
+        if(child instanceof COSUpdateInfo)
+        {
+            ((COSUpdateInfo) child).getUpdateState().setOriginDocumentState(originDocumentState);
+        }
+    }
+    
+    /**
+     * <p>
+     * Shall call {@link #update()} for this {@link COSUpdateState} and shall
+     * {@link #setOriginDocumentState(COSDocumentState)} for the given children, initializing their {@link #updated}
+     * state and {@link #originDocumentState}.
+     * </p>
+     * <p>
+     * This shall have no effect for a child, that is not an instance of {@link COSUpdateInfo}.
+     * </p>
+     *
+     * @param children The children that shall also be updated.
+     * @see #update()
+     * @see #setOriginDocumentState(COSDocumentState)
+     */
+    void update(COSArray children)
+    {
+        update((Iterable<COSBase>) children);
+    }
+    
+    /**
+     * <p>
+     * Shall call {@link #update()} for this {@link COSUpdateState} and shall
+     * {@link #setOriginDocumentState(COSDocumentState)} for the given children, initializing their {@link #updated}
+     * state and {@link #originDocumentState}.
+     * </p>
+     * <p>
+     * This shall have no effect for a child, that is not an instance of {@link COSUpdateInfo}.
+     * </p>
+     *
+     * @param children The children that shall also be updated.
+     * @see #update()
+     * @see #setOriginDocumentState(COSDocumentState)
+     */
+    void update(Iterable<COSBase> children)
+    {
+        update();
+        if(children == null)
+        {
+            return;
+        }
+        for(COSBase child : children)
+        {
+            if(child instanceof COSUpdateInfo)
+            {
+                ((COSUpdateInfo) child).getUpdateState().setOriginDocumentState(originDocumentState);
+            }
+        }
+    }
+    
+    /**
+     * This shall {@link #setOriginDocumentState(COSDocumentState, boolean)}  for the dereferenced child,
+     * initializing it´s {@link #originDocumentState}.
+     * </p>
+     * <p>
+     * This shall have no effect for a child, that is not an instance of {@link COSUpdateInfo} and will never change
+     * the child´s {@link #updated} state.
+     * </p>
+     *
+     * @param child The child, that has been dereferenced.
+     * @see #setOriginDocumentState(COSDocumentState, boolean)
+     */
+    void dereferenceChild(COSBase child)
+    {
+        if(child instanceof COSUpdateInfo)
+        {
+            ((COSUpdateInfo) child).getUpdateState().setOriginDocumentState(originDocumentState, true);
+        }
+    }
+    
+    /**
+     * Uses the managed {@link #updateInfo} as the base object of a new {@link COSIncrement}.
+     *
+     * @return A {@link COSIncrement} based on the managed {@link #updateInfo}.
+     * @see COSUpdateInfo
+     * @see COSIncrement
+     */
+    COSIncrement toIncrement()
+    {
+        return new COSIncrement(updateInfo);
+    }
+    
+}

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdfwriter/COSWriter.java Sun Oct  3 13:24:54 2021
@@ -41,7 +41,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSBase;
 import org.apache.pdfbox.cos.COSBoolean;
@@ -1469,14 +1468,10 @@ public class COSWriter implements ICOSVi
     public void write(PDDocument doc, SignatureInterface signInterface) throws IOException
     {
         pdDocument = doc;
-        // Whatever the writer wishes to change, the writer itself shall keep track of it.
-        pdDocument.getDocument().getUpdateObserver().stopTrackingChanges();
-        if (incrementalUpdate)
-        {
-            for(COSUpdateInfo updatedObject : pdDocument.getDocument().getUpdateObserver())
-            {
-                addObjectToWrite(updatedObject.getCOSObject());
-            }
+        COSDocument cosDoc = pdDocument.getDocument();
+        COSDictionary trailer = cosDoc.getTrailer();
+        if(incrementalUpdate){
+            trailer.toIncrement().exclude(trailer).forEach(objectsToWrite::add);
         }
         signatureInterface = signInterface;
         number = pdDocument.getDocument().getHighestXRefObjectNumber();
@@ -1486,8 +1481,6 @@ public class COSWriter implements ICOSVi
         }
         Long idTime = pdDocument.getDocumentId() == null ? System.currentTimeMillis()
                 : pdDocument.getDocumentId();
-        COSDocument cosDoc = pdDocument.getDocument();
-        COSDictionary trailer = cosDoc.getTrailer();
 
         // if the document says we should remove encryption, then we shouldn't encrypt
         if (doc.isAllSecurityToBeRemoved())
@@ -1582,17 +1575,12 @@ public class COSWriter implements ICOSVi
     public void write(FDFDocument doc) throws IOException
     {
         fdfDocument = doc;
-        // Whatever the writer wishes to change, the writer itself shall keep track of it.
-        fdfDocument.getDocument().getUpdateObserver().stopTrackingChanges();
-        if (incrementalUpdate)
-        {
-            for(COSUpdateInfo updatedObject : pdDocument.getDocument().getUpdateObserver())
-            {
-                addObjectToWrite(updatedObject.getCOSObject());
-            }
+        COSDocument cosDoc = fdfDocument.getDocument();
+        COSDictionary trailer = cosDoc.getTrailer();
+        if(incrementalUpdate){
+            trailer.toIncrement().exclude(trailer).forEach(objectsToWrite::add);
         }
         willEncrypt = false;
-        COSDocument cosDoc = fdfDocument.getDocument();
         cosDoc.accept(this);
     }
     /**

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/PDDocument.java Sun Oct  3 13:24:54 2021
@@ -168,7 +168,7 @@ public class PDDocument implements Close
     public PDDocument(MemoryUsageSetting memUsageSetting)
     {
         document = new COSDocument(memUsageSetting);
-        document.getUpdateObserver().startTrackingChanges();
+        document.getDocumentState().setParsing(false);
         pdfSource = null;
 
         // First we need a trailer
@@ -222,7 +222,7 @@ public class PDDocument implements Close
     public PDDocument(COSDocument doc, RandomAccessRead source, AccessPermission permission)
     {
         document = doc;
-        document.getUpdateObserver().startTrackingChanges();
+        document.getDocumentState().setParsing(false);
         pdfSource = source;
         accessPermission = permission;
     }

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/fdf/FDFDocument.java Sun Oct  3 13:24:54 2021
@@ -50,6 +50,7 @@ public class FDFDocument implements Clos
     public FDFDocument()
     {
         document = new COSDocument();
+        document.getDocumentState().setParsing(false);
         document.setVersion(1.2f);
 
         // First we need a trailer
@@ -68,6 +69,7 @@ public class FDFDocument implements Clos
     public FDFDocument(COSDocument doc)
     {
         document = doc;
+        document.getDocumentState().setParsing(false);
     }
 
     /**

Copied: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSIncrement.java (from r1893853, pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfoObserver.java)
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSIncrement.java?p2=pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSIncrement.java&p1=pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfoObserver.java&r1=1893853&r2=1893854&rev=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfoObserver.java (original)
+++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSIncrement.java Sun Oct  3 13:24:54 2021
@@ -45,7 +45,7 @@ import java.io.IOException;
 import java.net.URL;
 import java.util.ConcurrentModificationException;
 
-class TestCOSUpdateInfoObserver
+class TestCOSIncrement
 {
 
     // TODO Very basic and primitive test - add in depth testing for all this.
@@ -120,7 +120,7 @@ class TestCOSUpdateInfoObserver
             assertNull(document.getPage(1).getResources(), "Page 2 should not have contained resources");
             try (PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(0)))
             {
-                URL imageResource = TestCOSUpdateInfoObserver.class.getResource("simple.png");
+                URL imageResource = TestCOSIncrement.class.getResource("simple.png");
                 assertNotNull(imageResource, "Image resource not found.");
                 File image = assertDoesNotThrow(() -> new File(imageResource.toURI()),
                     "Image file could not be loaded");

Modified: pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java?rev=1893854&r1=1893853&r2=1893854&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java (original)
+++ pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSUpdateInfo.java Sun Oct  3 13:24:54 2021
@@ -34,12 +34,13 @@ class TestCOSUpdateInfo
     @Test
     void testIsSetNeedToBeUpdate()
     {
-        COSUpdateInfoObserver observer = new COSUpdateInfoObserver(null);
-        observer.startTrackingChanges();
-
+        COSDocumentState origin = new COSDocumentState();
+        origin.setParsing(false);
         // COSDictionary
         COSUpdateInfo testCOSDictionary = new COSDictionary();
-        observer.monitor(testCOSDictionary.getCOSObject());
+        testCOSDictionary.setNeedToBeUpdated(true);
+        assertFalse(testCOSDictionary.isNeedToBeUpdated());
+        testCOSDictionary.getUpdateState().setOriginDocumentState(origin);
         testCOSDictionary.setNeedToBeUpdated(true);
         assertTrue(testCOSDictionary.isNeedToBeUpdated());
         testCOSDictionary.setNeedToBeUpdated(false);
@@ -48,7 +49,9 @@ class TestCOSUpdateInfo
         // COSObject
         COSUpdateInfo testCOSObject;
         testCOSObject = new COSObject(null);
-        observer.monitor(testCOSObject.getCOSObject());
+        testCOSObject.setNeedToBeUpdated(true);
+        assertFalse(testCOSObject.isNeedToBeUpdated());
+        testCOSObject.getUpdateState().setOriginDocumentState(origin);
         testCOSObject.setNeedToBeUpdated(true);
         assertTrue(testCOSObject.isNeedToBeUpdated());
         testCOSObject.setNeedToBeUpdated(false);