1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.db.impl.query;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Iterator;
18 import org.simantics.databoard.Bindings;
19 import org.simantics.db.DevelopmentKeys;
20 import org.simantics.db.exception.DatabaseException;
21 import org.simantics.db.impl.graph.ReadGraphImpl;
22 import org.simantics.db.impl.procedure.InternalProcedure;
23 import org.simantics.utils.Development;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
27 public abstract class CacheEntryBase<Procedure> extends CacheEntry<Procedure> {
29 private static final Logger LOGGER = LoggerFactory.getLogger(CacheEntryBase.class);
31 // Default level is something that is not quite a prospect but still allows for ordering within CacheCollectionResult
32 public static final short UNDEFINED_LEVEL = 5;
34 public short level = UNDEFINED_LEVEL;
36 public int GCStatus = 0;
38 final public static CacheEntryBase[] NONE = new CacheEntryBase[0];
40 static Object NO_RESULT = new Object() { public String toString() { return "NO_RESULT"; }};
41 static protected Object INVALID_RESULT = new Object() { public String toString() { return "INVALID_RESULT"; }};
44 // static protected Object FRESH = new Object() { public String toString() { return "CREATED"; }};
45 // Result is computed - no exception
46 static protected Object READY = new Object() { public String toString() { return "READY"; }};
47 // Computation is under way
48 static protected Object PENDING = new Object() { public String toString() { return "PENDING"; }};
49 // Entry is discarded and is waiting for garbage collect
50 static protected Object DISCARDED = new Object() { public String toString() { return "DISCARDED"; }};
51 // The result has been invalidated
52 static protected Object REQUIRES_COMPUTATION = new Object() { public String toString() { return "REFUTED"; }};
53 // The computation has excepted - the exception is in the result
54 static protected Object EXCEPTED = new Object() { public String toString() { return "EXCEPTED"; }};
56 // This indicates the status of the entry
57 public Object statusOrException = REQUIRES_COMPUTATION;
59 private CacheEntry p1 = null;
60 private Object p2OrParents = null;
65 final public int hashCode() {
66 if(hash == 0) hash = makeHash();
70 abstract int makeHash();
72 // This can be tested to see if the result is finished
73 Object result = NO_RESULT;
75 final public boolean isFresh() {
76 return REQUIRES_COMPUTATION == statusOrException;
79 public void setReady() {
80 assert(result != NO_RESULT);
81 statusOrException = READY;
85 final public boolean isReady() {
86 return READY == statusOrException || EXCEPTED == statusOrException;
90 public void discard() {
91 if (Development.DEVELOPMENT) {
92 if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
93 System.err.println("[QUERY STATE]: discarded " + this);
96 statusOrException = DISCARDED;
100 final public boolean isDiscarded() {
101 return DISCARDED == statusOrException;
105 final public void refute() {
106 if (Development.DEVELOPMENT) {
107 if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
108 System.err.println("[QUERY STATE]: refuted " + this);
111 statusOrException = REQUIRES_COMPUTATION;
115 final public boolean isRefuted() {
116 return REQUIRES_COMPUTATION == statusOrException;
120 public void except(Throwable throwable) {
121 if (Development.DEVELOPMENT) {
122 if(Development.<Boolean>getProperty(DevelopmentKeys.CACHE_ENTRY_STATE, Bindings.BOOLEAN)) {
123 System.err.println("[QUERY STATE]: excepted " + this);
126 if(statusOrException != DISCARDED) {
127 statusOrException = EXCEPTED;
130 LOGGER.warn("Cache entry got excepted status after being discarded: " + getClass().getSimpleName(), throwable);
135 final public void checkAndThrow() throws DatabaseException {
137 Throwable throwable = (Throwable)result;
138 if(throwable instanceof DatabaseException) throw (DatabaseException)throwable;
139 else throw new DatabaseException(throwable);
144 final public boolean isExcepted() {
145 return EXCEPTED == statusOrException;
149 public void setPending(QuerySupport querySupport) {
150 statusOrException = PENDING;
151 clearResult(querySupport);
155 final public boolean isPending() {
156 return PENDING == statusOrException;
159 final public boolean requiresComputation() {
160 return REQUIRES_COMPUTATION == statusOrException;
163 final public boolean assertPending() {
164 boolean result = isPending();
166 LOGGER.warn("Assertion failed, expected pending, got " + statusOrException);
171 final public boolean assertNotPending() {
172 boolean result = !isPending();
174 new Exception(this + ": Assertion failed, expected not pending, got " + statusOrException).printStackTrace();
179 final public boolean assertNotDiscarded() {
180 boolean result = !isDiscarded();
182 new Exception(this + ": Assertion failed, expected not discarded, got " + statusOrException).printStackTrace();
188 public void setResult(Object result) {
189 this.result = result;
192 @SuppressWarnings("unchecked")
194 final public <T> T getResult() {
195 assert(statusOrException != DISCARDED);
200 public void clearResult(QuerySupport support) {
201 setResult(NO_RESULT);
205 final public void addParent(CacheEntry entry) {
207 assert(entry != null);
212 if(p2OrParents == entry) {
217 } else if(p2OrParents == null) {
219 } else if(p2OrParents instanceof QueryIdentityHashSet) {
220 ((QueryIdentityHashSet)p2OrParents).add(entry);
221 ((QueryIdentityHashSet)p2OrParents).purge();
223 CacheEntry tmp = (CacheEntry)p2OrParents;
224 p2OrParents = new QueryIdentityHashSet(2);
225 ((QueryIdentityHashSet)p2OrParents).add(tmp);
226 ((QueryIdentityHashSet)p2OrParents).add(entry);
232 CacheEntry pruneFirstParents() {
239 if(!p1.isDiscarded()) {
241 // First parent is still active
249 // First parent is discarded => look for more parents
250 if(p2OrParents instanceof QueryIdentityHashSet) {
252 QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
253 CacheEntry entry = set.removeDiscarded();
254 if(entry == null) p2OrParents = null;
258 } else if(p2OrParents instanceof CacheEntry) {
260 CacheEntry entry = (CacheEntry)p2OrParents;
261 if(entry.isDiscarded()) {
262 // Second entry is also discarded => all empty
283 void pruneParentSet() {
284 // First parent is discarded => look for more parents
285 if(p2OrParents instanceof QueryIdentityHashSet) {
287 QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
288 set.removeDiscardedReally();
289 if(set.isEmpty()) p2OrParents = null;
291 } else if(p2OrParents instanceof CacheEntry) {
293 CacheEntry entry = (CacheEntry)p2OrParents;
294 if(entry.isDiscarded()) {
295 // Second entry is also discarded => all empty
307 final public void removeParent(CacheEntry entry) {
310 if(p2OrParents != null) throw new Error("CacheEntryBase.removeParent: corrupted parents (p1 == null, while p2OrParents != null).");
311 else throw new Error("CacheEntryBase.removeParent: no parents.");
314 if(p2OrParents == null) {
316 } else if(p2OrParents instanceof QueryIdentityHashSet) {
317 QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
318 int size = set.size();
322 } else if (size == 1) {
323 CacheEntry next = set.iterator().next();
326 } else if(set.size() == 2) {
327 Iterator<CacheEntry> iterator = set.iterator();
328 p1 = iterator.next();
329 p2OrParents = iterator.next();
331 p1 = set.iterator().next();
335 p1 = (CacheEntry)p2OrParents;
339 } else if(p2OrParents.getClass() == QueryIdentityHashSet.class) {
341 QueryIdentityHashSet set = (QueryIdentityHashSet)p2OrParents;
342 boolean success = set.remove(entry);
344 throw new Error("CacheEntryBase.removeParent: parent was not found.");
346 assert(set.size() >= 1);
347 if(set.size() == 1) {
348 p2OrParents = set.iterator().next();
352 if(p2OrParents == entry) {
355 throw new Error("CacheEntryBase.removeParent: had 2 parents but neither was removed.");
361 final public boolean hasParents() {
362 assert(statusOrException != DISCARDED);
367 final public Collection<CacheEntry<?>> getParents(QueryProcessor processor) {
369 ArrayList<CacheEntry<?>> result = new ArrayList<CacheEntry<?>>();
370 if(p1 != null) result.add(p1);
371 if(p2OrParents != null) {
372 if(p2OrParents instanceof QueryIdentityHashSet) {
373 for(CacheEntry entry : (QueryIdentityHashSet)p2OrParents) {
377 result.add((CacheEntry)p2OrParents);
380 fillImpliedParents(processor, result);
386 CacheEntry getFirstParent(QueryProcessor processor) {
391 boolean moreThanOneParent(QueryProcessor processor) {
392 return p2OrParents != null;
396 int parentCount(QueryProcessor processor) {
397 if(p2OrParents != null) {
398 if(p2OrParents instanceof QueryIdentityHashSet) {
399 return ((QueryIdentityHashSet)p2OrParents).size()+1;
404 return p1 != null ? 1 : 0;
409 protected void fillImpliedParents(QueryProcessor processor, ArrayList<CacheEntry<?>> result) {
412 protected String internalError() {
413 return toString() + " " + statusOrException + " " + result;
417 protected boolean handleException(ReadGraphImpl graph, IntProcedure procedure) throws DatabaseException {
419 procedure.exception(graph, (Throwable)getResult());
426 protected boolean handleException(ReadGraphImpl graph, TripleIntProcedure procedure) throws DatabaseException {
428 procedure.exception(graph, (Throwable)getResult());
435 protected <T> boolean handleException(ReadGraphImpl graph, InternalProcedure<T> procedure) throws DatabaseException {
437 procedure.exception(graph, (Throwable)getResult());
445 boolean isImmutable(ReadGraphImpl graph) throws DatabaseException {
450 boolean shouldBeCollected() {
460 short setLevel(short level) {
461 short existing = this.level;
467 void prepareRecompute(QuerySupport querySupport) {
468 setPending(querySupport);
477 int setGCStatus(int status) {
483 void setGCStatusFlag(int flag, boolean value) {
492 public Object getOriginalRequest() {
493 // This is the original request for all built-in queries
497 public CacheEntryBase() {
500 public String classId() {
501 return getClass().getName();
504 public void serializeKey(QuerySerializer serializer) {
505 throw new IllegalStateException("Cannot serialize query key for " + this);
508 public void serializeValue(QuerySerializer serializer) {
509 throw new IllegalStateException("Cannot serialize query value for " + this);
512 public void serializeParents(QuerySerializer serializer) {
513 Collection<CacheEntry<?>> ps = getParents(serializer.getQueryProcessor());
514 int sizePos = serializer.writeUnknownSize();
516 for(CacheEntry<?> entry : ps) {
517 CacheEntryBase b = (CacheEntryBase)entry;
518 String cid = b.classId();
521 serializer.serializeId(b.classId());
522 b.serializeKey(serializer);
525 serializer.setUnknownSize(sizePos, actual);
528 public long cluster(QueryProcessor processor) {
529 throw new IllegalStateException("Cannot compute query cluster for " + this);
532 public void serialize(QuerySerializer serializer) {
533 serializer.serializeId(classId());
534 serializeKey(serializer);
535 serializeValue(serializer);
536 serializeParents(serializer);