diff src/org/dancres/blitz/entry/ci/CacheIndexerImpl.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children 4b2f23b6feca
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/dancres/blitz/entry/ci/CacheIndexerImpl.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,321 @@
+package org.dancres.blitz.entry.ci;
+
+import java.util.Set;
+
+import java.util.logging.*;
+
+import org.dancres.blitz.entry.EntrySleeve;
+import org.dancres.blitz.entry.TupleLocator;
+
+import org.dancres.blitz.mangler.MangledField;
+import org.dancres.blitz.mangler.MangledEntry;
+
+import org.dancres.blitz.cache.Identifiable;
+
+import org.dancres.blitz.oid.OID;
+
+/**
+ * A skeleton implementation of a cache indexer.
+ * Requires that the underlying index mechanism can support the CacheLines interface.
+ *
+ * @see org.dancres.blitz.entry.ci.CacheLines
+ *
+*/
+public abstract class CacheIndexerImpl extends CacheIndexer {
+
+    /**
+       Because fields are ordered and always present, even if they are null
+       we don't need to use a HashMap here.
+    */
+    private CacheLines[] theCacheLines;
+
+    /**
+       Contains all ids we know about which is required for a wildcard
+       search.  A possibly better solution would iterate across all indexes but
+       then there's the need to do intersection to prevent repeated passes over
+       the same id which means much more complex code and might perform worse
+       than this simple solution.
+    */
+    private Set theAllIds;
+
+    private String theType;
+
+    CacheIndexerImpl(String aType) {
+        theType = aType;
+        theAllIds = newIds();
+    }
+
+    abstract Set newIds();
+
+    public void loaded(Identifiable anIdentifiable) {
+        // System.err.println("CI:Loaded: " + anIdentifiable.getId() + "," + this);
+
+        EntrySleeve mySleeve = (EntrySleeve) anIdentifiable;
+
+        /*
+          No point indexing something that's already deleted - we only want
+          to generate load requests in response to explicit address'ing via
+          UID.
+         */
+        if (mySleeve.isDeleted())
+            return;
+
+        initBarrier(mySleeve);
+
+        insert(mySleeve);
+    }
+
+    public void flushed(Identifiable anIdentifiable) {
+        // System.err.println("CI:Flushed: " + anIdentifiable.getId() + "," + this);
+
+        EntrySleeve mySleeve = (EntrySleeve) anIdentifiable;
+        initBarrier(mySleeve);
+
+        remove(mySleeve);
+    }
+
+    public void dirtied(Identifiable anIdentifiable) {
+        // System.err.println("CI:Dirtied: " + anIdentifiable.getId() + "," + this);
+
+        EntrySleeve mySleeve = (EntrySleeve) anIdentifiable;
+        initBarrier(mySleeve);
+
+        if (mySleeve.isDeleted())
+            remove(mySleeve);
+    }
+
+    public TupleLocator find(MangledEntry anEntry) {
+        try {
+            return findImpl(anEntry);
+        } catch (ArrayIndexOutOfBoundsException anE) {
+            theLogger.log(Level.SEVERE, "Find broke - did you add/remove fields in your Entry?", anE);
+            theLogger.log(Level.SEVERE, "CacheIndexer for: " +
+                          theType);
+            theLogger.log(Level.SEVERE, "Entry looks like");
+            theLogger.log(Level.SEVERE, "Entry type: " +
+                          anEntry.getType());
+            for (int i = 0; i < anEntry.getFields().length; i++) {
+                MangledField myField = anEntry.getField(i);
+
+                theLogger.log(Level.SEVERE, "Name: " +
+                              myField.getName() + ", hashcode:" +
+                              myField.hashCode() + ", offset:" +
+                              i + ", isNull: " +
+                              myField.isNull());
+            }
+            theLogger.log(Level.SEVERE, "Cachelines looks like");
+            theLogger.log(Level.SEVERE, "Cachelines size:" +
+                          theCacheLines.length);
+            for (int i = 0; i < theCacheLines.length; i++) {
+                theLogger.log(Level.SEVERE, "Name: " +
+                              theCacheLines[i].getName() +
+                              " Offset: " + i +
+                              " size: " + 
+                              theCacheLines[i].getSize());
+            }
+
+            throw anE;
+        }
+    }
+
+    private TupleLocator findImpl(MangledEntry anEntry) {
+        /*
+          Unlike the cache listener methods which can only be called with
+          an entry of this indexers type, there's a possibility this method's
+          first entry will be a different type.  If it is, it can't be used
+          to init the barrier - thus, if we fail to init the barrier we should
+          return an empty locator
+        */
+        if (!initBarrier(anEntry))
+            return ArrayLocatorImpl.EMPTY_LOCATOR;
+
+        if ((anEntry == null) || (anEntry.isWildcard())) {
+            // Handle wildcard requests
+            //
+            if (theLogger.isLoggable(Level.FINE))
+                theLogger.log(Level.FINE, "Wildcard match");
+
+            synchronized(theAllIds) {
+                if (theAllIds.size() == 0)
+                    return ArrayLocatorImpl.EMPTY_LOCATOR;
+                else {
+                    OID[] myUids = new OID[theAllIds.size()];
+                    myUids = (OID[]) theAllIds.toArray(myUids);
+
+                    return new ArrayLocatorImpl(myUids);
+                }
+            }
+        } else {
+            // Proper indexing effort
+            //
+            if (theLogger.isLoggable(Level.FINE))
+                theLogger.log(Level.FINE, "Specific match");
+
+            MangledField myChoice = null;
+            int myChoicesSize = 0;
+            int myChoicesOffset = 0;
+
+            // Find the smallest index available
+            for (int i = 0; i < anEntry.getFields().length; i++) {
+                MangledField myField = anEntry.getField(i);
+                int mySize;
+
+                if (theLogger.isLoggable(Level.FINE))
+                    theLogger.log(Level.FINE, "Consider: " + myField.getName());
+
+                // If field is empty, we can't search on it
+                if (myField.isNull())
+                    continue;
+
+                mySize = getSize(myField, i);
+
+                /*
+                  Field is searchable - but, if we get no hits from cache
+                  indexes, that means we have no matches in memory.  This
+                  works because for a match to succeed it must match on 
+                  all fields in the template.  Thus if the template has a
+                  field with a hashcode that produces no hits then whilst
+                  we may have Entry's that match other fields of the template
+                  we know that none of these Entry's will match this field
+                  of the template so we should stop now.
+                 */
+                if (mySize == 0) {
+                    if (theLogger.isLoggable(Level.FINE))
+                        theLogger.log(Level.FINE,
+                                      "One key not indexed - abort");
+                    return ArrayLocatorImpl.EMPTY_LOCATOR;
+                }
+
+                if (theLogger.isLoggable(Level.FINE))
+                    theLogger.log(Level.FINE, "Available size: " + mySize);
+
+                if (myChoice == null) {
+                    myChoice = myField;
+                    myChoicesSize = mySize;
+                    myChoicesOffset = i;
+                } else {
+                    if (mySize < myChoicesSize) {
+                        myChoice = myField;
+                        myChoicesSize = mySize;
+                        myChoicesOffset = i;
+                    }
+                }
+            }
+
+            if (myChoice == null) {
+                return ArrayLocatorImpl.EMPTY_LOCATOR;
+            } else {
+                if (theLogger.isLoggable(Level.FINE))
+                    theLogger.log(Level.FINE, "Chose: " + myChoicesSize +
+                                  myChoice.getName());
+                return getIds(myChoice, myChoicesOffset);
+            }
+        }
+    }
+    
+    private TupleLocator getIds(MangledField aField, int anOffset) {
+        CacheLines myLine = getCacheLines(anOffset);
+        return myLine.getIds(aField.hashCode());
+    }
+
+    private int getSize(MangledField aField, int anOffset) {
+        CacheLines myLine = getCacheLines(anOffset);
+        return myLine.getSize(aField.hashCode());
+    }
+
+    private boolean initBarrier(EntrySleeve aSleeve) {
+        synchronized(this) {
+            if (theCacheLines == null)
+                return initBarrier(aSleeve.getEntry());
+            else
+                return true;
+        }
+    }
+
+    /**
+       @return true if the barrier is already inited or if
+       the barrier was successfully inited, false otherwise.
+    */
+    private boolean initBarrier(MangledEntry anEntry) {
+        synchronized(this) {
+            if (theCacheLines == null) {
+                theLogger.log(Level.FINE,
+                              "Initing cache indexer: " + theType);
+
+                if (!anEntry.getType().equals(theType)) {
+                    theLogger.log(Level.FINE,
+                                  "Can't init indexer with this: " +
+                                  anEntry.getType());
+                    return false;
+                }
+
+                theLogger.log(Level.FINE, "Entry looks like");
+                theLogger.log(Level.FINE, "Entry type: " +
+                              anEntry.getType());
+                for (int i = 0; i < anEntry.getFields().length; i++) {
+                    MangledField myField = anEntry.getField(i);
+
+                    theLogger.log(Level.FINE, "Name: " +
+                                  myField.getName() + ", hashcode:" +
+                                  myField.hashCode() + ", offset:" +
+                                  i + ", isNull: " +
+                                  myField.isNull());
+                }
+
+                MangledField[] myFields = anEntry.getFields();
+                theCacheLines = newLinesArray(myFields.length);
+
+                for (int i = 0; i < myFields.length; i++) {
+                    theCacheLines[i] = newLines(i, myFields[i].getName());
+                }
+            }
+            
+            return true;
+        }
+    }
+
+    abstract CacheLines[] newLinesArray(int aSize);
+
+    abstract CacheLines newLines(int anIndex, String aFieldName);
+
+    private CacheLines getCacheLines(int anOffset) {
+        return theCacheLines[anOffset];
+    }
+
+    /**
+       Add a sleeve to the indexer
+    */
+    private void insert(EntrySleeve aSleeve) {
+        synchronized(theAllIds) {
+
+            if (theAllIds.add(aSleeve.getOID())) {
+                for (int i = 0; i < theCacheLines.length; i++) {
+                    theCacheLines[i].insert(aSleeve);
+                }
+            } else {
+                // System.err.println("Already added");
+            }
+        }
+    }
+    
+    /**
+       Remove a sleeve from the indexer
+    */
+    private void remove(EntrySleeve aSleeve) {
+        synchronized(theAllIds) {
+
+            if (theAllIds.remove(aSleeve.getOID())) {
+                for (int i = 0; i < theCacheLines.length; i++) {
+                    theCacheLines[i].remove(aSleeve);
+                }
+                /*
+                new RuntimeException(
+                    "Removed: " + aSleeve.getOID()).printStackTrace(
+                    System.err);
+                    */
+            } else {
+                // new RuntimeException("Already removed: " + aSleeve.getOID()).printStackTrace(System.err);
+            }
+        }
+    }
+}