/******************************************************************************* * 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 extends CacheEntry { 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.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.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.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 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 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> getParents(QueryProcessor processor) { ArrayList> result = new ArrayList>(); 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> 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 boolean handleException(ReadGraphImpl graph, InternalProcedure 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> 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); } }