]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.impl/src/org/simantics/db/impl/query/CacheEntryBase.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.db.impl / src / org / simantics / db / impl / query / CacheEntryBase.java
index 953f915b9dbc6b4c413c334b48d4feb7df94d6a0..98e7f0d00554f1c3f351339d8aa4b891028966ae 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.db.impl.query;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Iterator;\r
-\r
-import org.simantics.db.common.utils.Logger;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.impl.DebugPolicy;\r
-import org.simantics.db.impl.graph.ReadGraphImpl;\r
-import org.simantics.db.impl.procedure.InternalProcedure;\r
-\r
-abstract public class CacheEntryBase extends CacheEntry {\r
-\r
-       // Default level is something that is not quite a prospect but still allows for ordering within CacheCollectionResult \r
-       public static final short UNDEFINED_LEVEL = 5;\r
-       \r
-       public short level = UNDEFINED_LEVEL;\r
-       public short age = 0;\r
-       public int GCStatus = 0;\r
-       \r
-    final public static CacheEntryBase[] NONE = new CacheEntryBase[0];\r
-\r
-       static private Object NO_RESULT = new Object();\r
-       static protected Object INVALID_RESULT = new Object();\r
-       \r
-       // Just created\r
-    static protected Object FRESH = new Object() { public String toString() { return "CREATED"; }};\r
-    // Result is computed - no exception\r
-    static protected Object READY = new Object() { public String toString() { return "READY"; }};\r
-    // Computation is under way\r
-    static protected Object PENDING = new Object() { public String toString() { return "PENDING"; }};\r
-    // Entry is discarded and is waiting for garbage collect\r
-    static protected Object DISCARDED = new Object() { public String toString() { return "DISCARDED"; }};\r
-    // The result has been invalidated\r
-    static protected Object REFUTED = new Object() { public String toString() { return "REFUTED"; }};\r
-    // The computation has excepted - the exception is in the result\r
-    static protected Object EXCEPTED = new Object() { public String toString() { return "EXCEPTED"; }};\r
-\r
-    // This indicates the status of the entry\r
-    public Object statusOrException = FRESH;\r
-    \r
-    private CacheEntry p1 = null;\r
-    private Object p2OrParents = null;\r
-    \r
-    private int hash = 0;\r
-    \r
-    @Override\r
-    final public int hashCode() {\r
-       if(hash == 0) hash = makeHash();\r
-       return hash;\r
-    }\r
-    \r
-    abstract int makeHash();\r
-    \r
-    // This can be tested to see if the result is finished\r
-    private Object result = NO_RESULT;\r
-    \r
-    final public boolean isFresh() {\r
-       return FRESH == statusOrException;\r
-    }\r
-\r
-    public void setReady() {\r
-       statusOrException = READY;\r
-    }\r
-\r
-    @Deprecated\r
-    final public boolean isReady() {\r
-       return READY == statusOrException || EXCEPTED == statusOrException;\r
-    }\r
-    \r
-    @Override\r
-    public void discard() {\r
-       if(DebugPolicy.QUERY_STATE) System.out.println("[QUERY STATE]: discarded " + this);\r
-       statusOrException = DISCARDED;\r
-    }\r
-    \r
-    @Override\r
-    final public boolean isDiscarded() {\r
-        return DISCARDED == statusOrException;\r
-    }\r
-    \r
-    @Override\r
-    final public void refute() {\r
-       if(DebugPolicy.QUERY_STATE) System.out.println("[QUERY STATE]: refuted " + this);\r
-       statusOrException = REFUTED;\r
-    }\r
-    \r
-    @Override\r
-    final public boolean isRefuted() {\r
-        return REFUTED == statusOrException;\r
-    }\r
-\r
-    @Override\r
-    final public void except(Throwable t) {\r
-       if(DebugPolicy.QUERY_STATE) System.out.println("[QUERY STATE]: excepted " + this);\r
-       if(statusOrException != DISCARDED) {\r
-               statusOrException = EXCEPTED;\r
-               result = t;\r
-       } else {\r
-               Logger.defaultLogError("Cache entry got excepted status after being discarded: " + getClass().getSimpleName(), t);\r
-               result = t;\r
-       }\r
-    }\r
-    \r
-    final public void checkAndThrow() throws DatabaseException {\r
-       if(isExcepted()) {\r
-           Throwable throwable = (Throwable)result;\r
-           if(throwable instanceof DatabaseException) throw (DatabaseException)throwable;\r
-           else throw new DatabaseException(throwable);\r
-       }\r
-    }\r
-    \r
-    @Override\r
-    final public boolean isExcepted() {\r
-        return EXCEPTED == statusOrException;\r
-    }\r
-\r
-    @Override\r
-    final public void setPending() {\r
-       statusOrException = PENDING;\r
-    }\r
-    \r
-    @Override\r
-    final public boolean isPending() {\r
-        return PENDING == statusOrException;\r
-    }\r
-    \r
-    final public boolean assertPending() {\r
-       boolean result = isPending();\r
-       if(!result) {\r
-               System.err.println("Assertion failed, expected pending, got " + statusOrException);\r
-       }\r
-       return result;\r
-    }\r
-\r
-    final public boolean assertNotPending() {\r
-       boolean result = !isPending();\r
-       if(!result) {\r
-               new Exception(this +  ": Assertion failed, expected not pending, got " + statusOrException).printStackTrace();\r
-       }\r
-       return result;\r
-    }\r
-\r
-    final public boolean assertNotDiscarded() {\r
-       boolean result = !isDiscarded();\r
-       if(!result) {\r
-               new Exception(this +  ": Assertion failed, expected not discarded, got " + statusOrException).printStackTrace();\r
-       }\r
-       return result;\r
-    }\r
-\r
-    @Override\r
-    public void setResult(Object result) {\r
-       this.result = result;\r
-    }\r
-    \r
-    @Override\r
-    final public <T> T getResult() {\r
-        assert(statusOrException != DISCARDED);\r
-        return (T)result;\r
-    }\r
-    \r
-    @Override\r
-    public void clearResult(QuerySupport support) {\r
-       setResult(NO_RESULT);\r
-    }\r
-    \r
-    @Override\r
-    final public void addParent(CacheEntry entry) {\r
-        \r
-        assert(entry != null);\r
-        \r
-        if(p1 == entry) {\r
-               return;\r
-        }\r
-        if(p2OrParents == entry) {\r
-               return;\r
-        }\r
-        if(p1 == null) {\r
-               p1 = entry;\r
-        } else if(p2OrParents == null) {\r
-               p2OrParents = entry;\r
-        } else if(p2OrParents instanceof QueryIdentityHashSet) {\r
-            ((QueryIdentityHashSet)p2OrParents).add(entry);\r
-            ((QueryIdentityHashSet)p2OrParents).purge();\r
-        } else {\r
-            CacheEntry tmp = (CacheEntry)p2OrParents;\r
-            p2OrParents = new QueryIdentityHashSet(2);\r
-            ((QueryIdentityHashSet)p2OrParents).add(tmp);\r
-            ((QueryIdentityHashSet)p2OrParents).add(entry);\r
-        }\r
-        \r
-    }\r
-\r
-    @Override\r
-    CacheEntry pruneFirstParents() {\r
-\r
-       if(p1 == null) {\r
-               // No parents\r
-               return null;\r
-       }\r
-       \r
-       if(!p1.isDiscarded()) {\r
-               \r
-               // First parent is still active\r
-               return p1;\r
-               \r
-       } else {\r
-               \r
-               // Clear p1\r
-               p1 = null;\r
-               \r
-               // First parent is discarded => look for more parents\r
-               if(p2OrParents instanceof QueryIdentityHashSet) {\r
-\r
-                       QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;\r
-                       CacheEntry entry = set.removeDiscarded();\r
-                       if(entry == null) p2OrParents = null;\r
-                       p1 = entry;\r
-                       return p1;\r
-\r
-               } else if(p2OrParents instanceof CacheEntry) {\r
-                       \r
-                       CacheEntry entry = (CacheEntry)p2OrParents;\r
-                       if(entry.isDiscarded()) {\r
-                               // Second entry is also discarded => all empty\r
-                               p2OrParents = null;\r
-                               return null;\r
-                       } else {\r
-                               p1 = entry;\r
-                               p2OrParents = null;\r
-                               return p1;\r
-                       }\r
-                       \r
-               } else {\r
-               \r
-                       // Nothing left\r
-                       return null;\r
-                       \r
-               }\r
-               \r
-       }\r
-        \r
-    }\r
-    \r
-    @Override\r
-    final public void removeParent(CacheEntry entry) {\r
-       \r
-        if(p1 == null) {\r
-            if(p2OrParents != null) throw new Error("CacheEntryBase.removeParent: corrupted parents (p1 == null, while p2OrParents != null).");\r
-            else throw new Error("CacheEntryBase.removeParent: no parents.");\r
-        }\r
-        if(p1 == entry) {\r
-            if(p2OrParents == null) {\r
-                p1 = null;\r
-            } else if(p2OrParents instanceof QueryIdentityHashSet) {\r
-                QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;\r
-                int size = set.size();\r
-                if(size == 0) {\r
-                    p1 = null;\r
-                    p2OrParents = null;\r
-                } else if (size == 1) {\r
-                    CacheEntry next = set.iterator().next();\r
-                    p1 = next;\r
-                    set = null;\r
-                } else if(set.size() == 2) {\r
-                    Iterator<CacheEntry> iterator = set.iterator();\r
-                    p1 = iterator.next();\r
-                    p2OrParents = iterator.next();\r
-                } else {\r
-                    p1 = set.iterator().next();\r
-                    set.remove(p1);\r
-                }\r
-            } else {\r
-                p1 = (CacheEntry)p2OrParents;\r
-                p2OrParents = null;\r
-            }\r
-            \r
-        } else if(p2OrParents.getClass() == QueryIdentityHashSet.class) {\r
-            \r
-            QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;\r
-            boolean success = set.remove(entry);\r
-            if(!success) {\r
-               throw new Error("CacheEntryBase.removeParent: parent was not found.");\r
-            }\r
-            assert(set.size() >= 1);\r
-            if(set.size() == 1) {\r
-                p2OrParents = set.iterator().next();\r
-            }\r
-            \r
-        } else {\r
-            if(p2OrParents == entry) {\r
-                p2OrParents = null;\r
-            } else {\r
-                throw new Error("CacheEntryBase.removeParent: had 2 parents but neither was removed.");\r
-            }\r
-        }\r
-    }\r
-\r
-    @Override\r
-    final public boolean hasParents() {\r
-        assert(statusOrException != DISCARDED);\r
-        return p1 != null;\r
-    }\r
-    \r
-    @Override\r
-       final public Iterable<CacheEntry> getParents(QueryProcessor processor) {\r
-\r
-               ArrayList<CacheEntry> result = new ArrayList<CacheEntry>();\r
-               if(p1 != null) result.add(p1);\r
-               if(p2OrParents != null) {\r
-               if(p2OrParents instanceof QueryIdentityHashSet) {\r
-                       for(CacheEntry entry : (QueryIdentityHashSet)p2OrParents) {\r
-                               result.add(entry);\r
-                       }\r
-               } else {\r
-                       result.add((CacheEntry)p2OrParents);\r
-               }\r
-               }\r
-               fillImpliedParents(processor, result);\r
-               return result;\r
-               \r
-       }\r
-    \r
-    @Override\r
-    CacheEntry getFirstParent(QueryProcessor processor) {\r
-       return p1;\r
-    }\r
-    \r
-    @Override\r
-    boolean moreThanOneParent(QueryProcessor processor) {\r
-       return p2OrParents != null;\r
-    }\r
-    \r
-    @Override\r
-    int parentCount(QueryProcessor processor) {\r
-       if(p2OrParents != null) {\r
-               if(p2OrParents instanceof QueryIdentityHashSet) {\r
-                       return ((QueryIdentityHashSet)p2OrParents).size()+1;\r
-               } else {\r
-                       return 2;\r
-               }\r
-       } else {\r
-               return p1 != null ? 1 : 0;\r
-       }\r
-       \r
-    }\r
-    \r
-    protected void fillImpliedParents(QueryProcessor processor, ArrayList<CacheEntry> result) {\r
-       \r
-    }\r
-    \r
-    protected String internalError() {\r
-       return toString() + " " + statusOrException + " " + result;\r
-    }\r
-\r
-    \r
-    protected boolean handleException(ReadGraphImpl graph, IntProcedure procedure) {\r
-       if(isExcepted()) {\r
-               procedure.exception(graph, (Throwable)getResult());\r
-               return true;\r
-       } else {\r
-               return false;\r
-       }\r
-    }\r
-    \r
-    protected boolean handleException(ReadGraphImpl graph, TripleIntProcedure procedure) {\r
-       if(isExcepted()) {\r
-               procedure.exception(graph, (Throwable)getResult());\r
-               return true;\r
-       } else {\r
-               return false;\r
-       }\r
-    }\r
-\r
-    protected <T> boolean handleException(ReadGraphImpl graph, InternalProcedure<T> procedure) {\r
-       if(isExcepted()) {\r
-               procedure.exception(graph, (Throwable)getResult());\r
-               return true;\r
-       } else {\r
-               return false;\r
-       }\r
-    }\r
-    \r
-    @Override\r
-    boolean isImmutable(ReadGraphImpl graph) throws DatabaseException {\r
-       return false;\r
-    }\r
-    \r
-    @Override\r
-    boolean shouldBeCollected() {\r
-       return true;\r
-    }\r
-    \r
-    @Override\r
-    short getLevel() {\r
-       return level;\r
-    }\r
-    \r
-    @Override\r
-    short setLevel(short level) {\r
-       short existing = this.level;\r
-       this.level = level;\r
-       return existing;\r
-    }\r
-    \r
-    @Override\r
-    void prepareRecompute(QuerySupport querySupport) {\r
-               setPending();\r
-               clearResult(querySupport);\r
-    }\r
-    \r
-    /*\r
-     * \r
-     * \r
-     */\r
-    @Override\r
-    int getGCStatus() {\r
-       return GCStatus;\r
-    }\r
-    \r
-    @Override\r
-    int setGCStatus(int status) {\r
-       GCStatus = status;\r
-       return GCStatus;\r
-    }\r
-    \r
-    @Override\r
-    void setGCStatusFlag(int flag, boolean value) {\r
-       if(value) {\r
-               GCStatus |= flag;\r
-       } else {\r
-               GCStatus &= ~flag;\r
-       }\r
-    }\r
-    \r
-    @Override\r
-    public Object getOriginalRequest() {\r
-       // This is the original request for all built-in queries\r
-       return getQuery();\r
-    }\r
-    \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.db.impl.query;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.simantics.databoard.Bindings;
+import org.simantics.db.DevelopmentKeys;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.impl.graph.ReadGraphImpl;
+import org.simantics.db.impl.procedure.InternalProcedure;
+import org.simantics.utils.Development;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class CacheEntryBase<Procedure> extends CacheEntry<Procedure> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CacheEntryBase.class);
+    
+       // Default level is something that is not quite a prospect but still allows for ordering within CacheCollectionResult 
+       public static final short UNDEFINED_LEVEL = 5;
+       
+       public short level = UNDEFINED_LEVEL;
+       public short age = 0;
+       public int GCStatus = 0;
+       
+    final public static CacheEntryBase[] NONE = new CacheEntryBase[0];
+
+       static Object NO_RESULT = new Object() { public String toString() { return "NO_RESULT"; }};
+       static protected Object INVALID_RESULT = new Object() { public String toString() { return "INVALID_RESULT"; }};
+       
+//     // Just created
+//    static protected Object FRESH = new Object() { public String toString() { return "CREATED"; }};
+    // Result is computed - no exception
+    static protected Object READY = new Object() { public String toString() { return "READY"; }};
+    // Computation is under way
+    static protected Object PENDING = new Object() { public String toString() { return "PENDING"; }};
+    // Entry is discarded and is waiting for garbage collect
+    static protected Object DISCARDED = new Object() { public String toString() { return "DISCARDED"; }};
+    // The result has been invalidated
+    static protected Object REQUIRES_COMPUTATION = new Object() { public String toString() { return "REFUTED"; }};
+    // The computation has excepted - the exception is in the result
+    static protected Object EXCEPTED = new Object() { public String toString() { return "EXCEPTED"; }};
+
+    // This indicates the status of the entry
+    public Object statusOrException = REQUIRES_COMPUTATION;
+    
+    private CacheEntry p1 = null;
+    private Object p2OrParents = null;
+    
+    private int hash = 0;
+    
+    @Override
+    final public int hashCode() {
+       if(hash == 0) hash = makeHash();
+       return hash;
+    }
+    
+    abstract int makeHash();
+    
+    // This can be tested to see if the result is finished
+    Object result = NO_RESULT;
+    
+    final public boolean isFresh() {
+       return REQUIRES_COMPUTATION == statusOrException;
+    }
+
+    public void setReady() {
+        assert(result != NO_RESULT);
+       statusOrException = READY;
+    }
+
+    @Deprecated
+    final public boolean isReady() {
+       return READY == statusOrException || EXCEPTED == statusOrException;
+    }
+    
+    @Override
+    public void discard() {
+               if (Development.DEVELOPMENT) {
+                       if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
+                               System.err.println("[QUERY STATE]: discarded " + this);
+                       }
+               }
+       statusOrException = DISCARDED;
+    }
+    
+    @Override
+    final public boolean isDiscarded() {
+        return DISCARDED == statusOrException;
+    }
+    
+    @Override
+    final public void refute() {
+               if (Development.DEVELOPMENT) {
+                       if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
+                               System.err.println("[QUERY STATE]: refuted " + this);
+                       }
+               }
+       statusOrException = REQUIRES_COMPUTATION;
+    }
+    
+    @Override
+    final public boolean isRefuted() {
+        return REQUIRES_COMPUTATION == statusOrException;
+    }
+
+    @Override
+    public void except(Throwable throwable) {
+               if (Development.DEVELOPMENT) {
+                       if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
+                               System.err.println("[QUERY STATE]: excepted " + this);
+                       }
+               }
+       if(statusOrException != DISCARDED) {
+               statusOrException = EXCEPTED;
+               result = throwable;
+       } else {
+               LOGGER.warn("Cache entry got excepted status after being discarded: " + getClass().getSimpleName(), throwable);
+               result = throwable;
+       }
+    }
+    
+    final public void checkAndThrow() throws DatabaseException {
+       if(isExcepted()) {
+           Throwable throwable = (Throwable)result;
+           if(throwable instanceof DatabaseException) throw (DatabaseException)throwable;
+           else throw new DatabaseException(throwable);
+       }
+    }
+    
+    @Override
+    final public boolean isExcepted() {
+        return EXCEPTED == statusOrException;
+    }
+
+    @Override
+    public void setPending(QuerySupport querySupport) {
+        statusOrException = PENDING;
+        clearResult(querySupport);
+    }
+    
+    @Override
+    final public boolean isPending() {
+        return PENDING == statusOrException;
+    }
+    
+    final public boolean requiresComputation() {
+       return REQUIRES_COMPUTATION == statusOrException;
+    }
+    
+    final public boolean assertPending() {
+       boolean result = isPending();
+       if(!result) {
+               LOGGER.warn("Assertion failed, expected pending, got " + statusOrException);
+       }
+       return result;
+    }
+
+    final public boolean assertNotPending() {
+       boolean result = !isPending();
+       if(!result) {
+               new Exception(this +  ": Assertion failed, expected not pending, got " + statusOrException).printStackTrace();
+       }
+       return result;
+    }
+
+    final public boolean assertNotDiscarded() {
+       boolean result = !isDiscarded();
+       if(!result) {
+               new Exception(this +  ": Assertion failed, expected not discarded, got " + statusOrException).printStackTrace();
+       }
+       return result;
+    }
+
+    @Override
+    public void setResult(Object result) {
+       this.result = result;
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    final public <T> T getResult() {
+        assert(statusOrException != DISCARDED);
+        return (T)result;
+    }
+    
+    @Override
+    public void clearResult(QuerySupport support) {
+       setResult(NO_RESULT);
+    }
+    
+    @Override
+    final public void addParent(CacheEntry entry) {
+        
+        assert(entry != null);
+        
+        if(p1 == entry) {
+               return;
+        }
+        if(p2OrParents == entry) {
+               return;
+        }
+        if(p1 == null) {
+               p1 = entry;
+        } else if(p2OrParents == null) {
+               p2OrParents = entry;
+        } else if(p2OrParents instanceof QueryIdentityHashSet) {
+            ((QueryIdentityHashSet)p2OrParents).add(entry);
+            ((QueryIdentityHashSet)p2OrParents).purge();
+        } else {
+            CacheEntry tmp = (CacheEntry)p2OrParents;
+            p2OrParents = new QueryIdentityHashSet(2);
+            ((QueryIdentityHashSet)p2OrParents).add(tmp);
+            ((QueryIdentityHashSet)p2OrParents).add(entry);
+        }
+        
+    }
+
+    @Override
+    CacheEntry pruneFirstParents() {
+
+       if(p1 == null) {
+               // No parents
+               return null;
+       }
+       
+       if(!p1.isDiscarded()) {
+               
+               // First parent is still active
+               return p1;
+               
+       } else {
+               
+               // Clear p1
+               p1 = null;
+               
+               // First parent is discarded => look for more parents
+               if(p2OrParents instanceof QueryIdentityHashSet) {
+
+                       QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
+                       CacheEntry entry = set.removeDiscarded();
+                       if(entry == null) p2OrParents = null;
+                       p1 = entry;
+                       return p1;
+
+               } else if(p2OrParents instanceof CacheEntry) {
+                       
+                       CacheEntry entry = (CacheEntry)p2OrParents;
+                       if(entry.isDiscarded()) {
+                               // Second entry is also discarded => all empty
+                               p2OrParents = null;
+                               return null;
+                       } else {
+                               p1 = entry;
+                               p2OrParents = null;
+                               return p1;
+                       }
+                       
+               } else {
+               
+                       // Nothing left
+                       return null;
+                       
+               }
+               
+       }
+        
+    }
+
+    @Override
+    void pruneParentSet() {
+        // First parent is discarded => look for more parents
+        if(p2OrParents instanceof QueryIdentityHashSet) {
+
+            QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
+            set.removeDiscardedReally();
+            if(set.isEmpty()) p2OrParents = null;
+
+        } else if(p2OrParents instanceof CacheEntry) {
+
+            CacheEntry entry = (CacheEntry)p2OrParents;
+            if(entry.isDiscarded()) {
+                // Second entry is also discarded => all empty
+                p2OrParents = null;
+            }
+
+        } else {
+
+            // Nothing left
+
+        }
+    }
+
+    @Override
+    final public void removeParent(CacheEntry entry) {
+       
+        if(p1 == null) {
+            if(p2OrParents != null) throw new Error("CacheEntryBase.removeParent: corrupted parents (p1 == null, while p2OrParents != null).");
+            else throw new Error("CacheEntryBase.removeParent: no parents.");
+        }
+        if(p1 == entry) {
+            if(p2OrParents == null) {
+                p1 = null;
+            } else if(p2OrParents instanceof QueryIdentityHashSet) {
+                QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
+                int size = set.size();
+                if(size == 0) {
+                    p1 = null;
+                    p2OrParents = null;
+                } else if (size == 1) {
+                    CacheEntry next = set.iterator().next();
+                    p1 = next;
+                    set = null;
+                } else if(set.size() == 2) {
+                    Iterator<CacheEntry> iterator = set.iterator();
+                    p1 = iterator.next();
+                    p2OrParents = iterator.next();
+                } else {
+                    p1 = set.iterator().next();
+                    set.remove(p1);
+                }
+            } else {
+                p1 = (CacheEntry)p2OrParents;
+                p2OrParents = null;
+            }
+            
+        } else if(p2OrParents.getClass() == QueryIdentityHashSet.class) {
+            
+            QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
+            boolean success = set.remove(entry);
+            if(!success) {
+               throw new Error("CacheEntryBase.removeParent: parent was not found.");
+            }
+            assert(set.size() >= 1);
+            if(set.size() == 1) {
+                p2OrParents = set.iterator().next();
+            }
+            
+        } else {
+            if(p2OrParents == entry) {
+                p2OrParents = null;
+            } else {
+                throw new Error("CacheEntryBase.removeParent: had 2 parents but neither was removed.");
+            }
+        }
+    }
+
+    @Override
+    final public boolean hasParents() {
+        assert(statusOrException != DISCARDED);
+        return p1 != null;
+    }
+    
+    @Override
+       final public Collection<CacheEntry<?>> getParents(QueryProcessor processor) {
+
+               ArrayList<CacheEntry<?>> result = new ArrayList<CacheEntry<?>>();
+               if(p1 != null) result.add(p1);
+               if(p2OrParents != null) {
+               if(p2OrParents instanceof QueryIdentityHashSet) {
+                       for(CacheEntry entry : (QueryIdentityHashSet)p2OrParents) {
+                               result.add(entry);
+                       }
+               } else {
+                       result.add((CacheEntry)p2OrParents);
+               }
+               }
+               fillImpliedParents(processor, result);
+               return result;
+               
+       }
+    
+    @Override
+    CacheEntry getFirstParent(QueryProcessor processor) {
+       return p1;
+    }
+    
+    @Override
+    boolean moreThanOneParent(QueryProcessor processor) {
+       return p2OrParents != null;
+    }
+    
+    @Override
+    int parentCount(QueryProcessor processor) {
+       if(p2OrParents != null) {
+               if(p2OrParents instanceof QueryIdentityHashSet) {
+                       return ((QueryIdentityHashSet)p2OrParents).size()+1;
+               } else {
+                       return 2;
+               }
+       } else {
+               return p1 != null ? 1 : 0;
+       }
+       
+    }
+    
+    protected void fillImpliedParents(QueryProcessor processor, ArrayList<CacheEntry<?>> result) {
+    }
+    
+    protected String internalError() {
+       return toString() + " " + statusOrException + " " + result;
+    }
+
+    
+    protected boolean handleException(ReadGraphImpl graph, IntProcedure procedure) throws DatabaseException {
+       if(isExcepted()) {
+               procedure.exception(graph, (Throwable)getResult());
+               return true;
+       } else {
+               return false;
+       }
+    }
+    
+    protected boolean handleException(ReadGraphImpl graph, TripleIntProcedure procedure) throws DatabaseException {
+       if(isExcepted()) {
+               procedure.exception(graph, (Throwable)getResult());
+               return true;
+       } else {
+               return false;
+       }
+    }
+
+    protected <T> boolean handleException(ReadGraphImpl graph, InternalProcedure<T> procedure) throws DatabaseException {
+       if(isExcepted()) {
+               procedure.exception(graph, (Throwable)getResult());
+               return true;
+       } else {
+               return false;
+       }
+    }
+    
+    @Override
+    boolean isImmutable(ReadGraphImpl graph) throws DatabaseException {
+       return false;
+    }
+    
+    @Override
+    boolean shouldBeCollected() {
+       return true;
+    }
+    
+    @Override
+    short getLevel() {
+       return level;
+    }
+    
+    @Override
+    short setLevel(short level) {
+       short existing = this.level;
+       this.level = level;
+       return existing;
+    }
+    
+    @Override
+    void prepareRecompute(QuerySupport querySupport) {
+        setPending(querySupport);
+    }
+    
+    @Override
+    int getGCStatus() {
+       return GCStatus;
+    }
+    
+    @Override
+    int setGCStatus(int status) {
+       GCStatus = status;
+       return GCStatus;
+    }
+    
+    @Override
+    void setGCStatusFlag(int flag, boolean value) {
+       if(value) {
+               GCStatus |= flag;
+       } else {
+               GCStatus &= ~flag;
+       }
+    }
+    
+    @Override
+    public Object getOriginalRequest() {
+       // This is the original request for all built-in queries
+       return getQuery();
+    }
+
+    public CacheEntryBase() {
+    }
+    
+    public String classId() {
+        return getClass().getName();
+    }
+
+    public void serializeKey(QuerySerializer serializer) {
+        throw new IllegalStateException("Cannot serialize query key for " + this);
+    }
+    
+    public void serializeValue(QuerySerializer serializer) {
+        throw new IllegalStateException("Cannot serialize query value for " + this);
+    }
+    
+    public void serializeParents(QuerySerializer serializer) {
+        Collection<CacheEntry<?>> ps = getParents(serializer.getQueryProcessor());
+        int sizePos = serializer.writeUnknownSize();
+        int actual = 0;
+        for(CacheEntry<?> entry : ps) {
+            CacheEntryBase b = (CacheEntryBase)entry;
+            String cid = b.classId();
+            if(cid == null) 
+                continue;
+            serializer.serializeId(b.classId());
+            b.serializeKey(serializer);
+            actual++;
+        }
+        serializer.setUnknownSize(sizePos, actual);
+    }
+
+    public long cluster(QueryProcessor processor) {
+        throw new IllegalStateException("Cannot compute query cluster for " + this);
+    }
+
+    public void serialize(QuerySerializer serializer) {
+        serializer.serializeId(classId());
+        serializeKey(serializer);
+        serializeValue(serializer);
+        serializeParents(serializer);
+    }
+
+}