Create PrologMachineCopy to reduce consult costs

Applications that dynamically consult the same code file multiple
times can benefit from performing the consult in an isolated
interpreter, then freezing that database using a PrologMachineCopy,
and later restore it into a new machine.
diff --git a/src/lang/BlockingPrologControl.java b/src/lang/BlockingPrologControl.java
index e788982..33e9219 100644
--- a/src/lang/BlockingPrologControl.java
+++ b/src/lang/BlockingPrologControl.java
@@ -110,9 +110,11 @@
 
   /** Constructs a new <code>BlockingPrologControl</code>. */
   public BlockingPrologControl() {
-  thread = null;
-  result = false;
-  resultReady = false;
+  }
+
+  /** Constructs a new <code>BlockingPrologControl</code>. */
+  public BlockingPrologControl(PrologMachineCopy pmc) {
+    super(pmc);
   }
 
   /**
diff --git a/src/lang/BufferingPrologControl.java b/src/lang/BufferingPrologControl.java
index c66ca00..a71a18f 100644
--- a/src/lang/BufferingPrologControl.java
+++ b/src/lang/BufferingPrologControl.java
@@ -16,6 +16,13 @@
   private boolean resSingle;
   private Term[] resTemplate;
 
+  public BufferingPrologControl() {
+  }
+
+  public BufferingPrologControl(PrologMachineCopy pmc) {
+    super(pmc);
+  }
+
   /**
    * Initialize one or more packages in the interpreter.
    *
diff --git a/src/lang/InternalDatabase.java b/src/lang/InternalDatabase.java
index 0de54be..663383f 100644
--- a/src/lang/InternalDatabase.java
+++ b/src/lang/InternalDatabase.java
@@ -18,7 +18,7 @@
 
     /* For GC */
     /** A list of reusable entry indices. */
-    protected LinkedList<Integer> reusableIndices = new LinkedList<Integer>();
+    protected LinkedList<Integer> reusableIndices;
 
     /** the top index of this <code>InternalDatabase</code>. */
     protected int top;
@@ -32,11 +32,27 @@
     public InternalDatabase(int n) {
 	maxContents = n;
 	buffer = new Term[Math.min(maxContents, DEFAULT_SIZE)];
+	reusableIndices = new LinkedList<Integer>();
 	top = -1;
     }
 
-    /** Discards all entries. */
-    public void init() { eraseAll(); }
+    InternalDatabase(Prolog engine, InternalDatabase src, boolean deepCopy) {
+      maxContents = src.maxContents;
+      buffer = new Term[src.buffer.length];
+      reusableIndices = new LinkedList<Integer>(src.reusableIndices);
+      top = src.top;
+
+      if (deepCopy) {
+        for (int i = 0; i <= top; i++) {
+          Term s = src.buffer[i];
+          if (s != null) {
+            buffer[i] = s.copy(engine);
+          }
+        }
+      } else if (0 <= top) {
+        System.arraycopy(src.buffer, 0, buffer, 0, top + 1);
+      }
+    }
 
     /** Inserts an entry to this <code>InternalDatabase</code>. */
     public int insert(Term t) {
@@ -46,12 +62,11 @@
 		return top;
 	    } else {
 		int i = reusableIndices.remove();
-		//		System.out.println("Reuse " + i);
 		buffer[i] = t;
 		return i;
 	    }
 	} catch (ArrayIndexOutOfBoundsException e) {
-	    if (maxContents == buffer.length)
+	    if (maxContents <= buffer.length)
 	      throw new SystemException("internal database capacity reached");
 	    int len = buffer.length;
 	    Term[] new_buffer = new Term[Math.min(len+10000, maxContents)];
@@ -73,28 +88,15 @@
     public Term erase(int i) {
 	Term t = buffer[i];
 	buffer[i] = null;
-	//	System.out.println("add Reuse index" + i);
 	reusableIndices.add(i);
 	return t;
     }
 
-    /** Discards all entries. */
-    protected void eraseAll() {
-	while (! empty()) {
-	    buffer[top--] = null;
-	}	
-    }
-
     /** Tests if this has no entry. */
-    public boolean empty() {
+    private boolean empty() {
 	return top == -1;
     }
 
-    /** Returns the value of <code>top</code>. 
-     * @see #top
-     */
-    public int top() { return top; }
-
     /** Shows the contents of this <code>InternalDatabase</code>. */
     public void show() {
 	if (empty())
diff --git a/src/lang/Prolog.java b/src/lang/Prolog.java
index d744667..12876f1 100644
--- a/src/lang/Prolog.java
+++ b/src/lang/Prolog.java
@@ -11,6 +11,7 @@
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.Map;
 /**
  * Prolog engine.
  *
@@ -19,6 +20,8 @@
  * @version 1.2
 */
 public final class Prolog {
+    private static final SymbolTerm NONE = SymbolTerm.intern("$none");
+
     /** Prolog thread */
     public PrologControl control;
     /** Argument registers */
@@ -128,14 +131,24 @@
     }
     protected final EnumSet<Feature> features = EnumSet.allOf(Feature.class);
 
-    /** Constructs new Prolog engine. */
-    public Prolog(PrologControl c) { 
-	control    = c;
-	cont       = null;
-	trail      = new Trail();
-	stack      = new ChoicePointStack(trail);
-	hashManager = new HashtableOfTerm();
-	copyHash = new IdentityHashMap<VariableTerm, VariableTerm>();
+    Prolog(PrologControl c) {
+      control = c;
+      trail = new Trail();
+      stack = new ChoicePointStack(trail);
+      copyHash = new IdentityHashMap<VariableTerm, VariableTerm>();
+      hashManager = new HashtableOfTerm();
+    }
+
+    Prolog(PrologControl c, PrologMachineCopy pmc) {
+      control = c;
+      trail = new Trail();
+      stack = new ChoicePointStack(trail);
+      copyHash = new IdentityHashMap<VariableTerm, VariableTerm>();
+
+      // During restore there is no need to copy terms. clause/2 inside of
+      // builtins.pl copies the predicate when it reads from internalDB.
+      hashManager = PrologMachineCopy.copyShallow(pmc.hashManager);
+      internalDB = new InternalDatabase(this, pmc.internalDB, false);
     }
 
     /**
@@ -209,7 +222,6 @@
 	  initOnce();
 	stack.init();
 	trail.init();
-	//	pdl.init();
 	B0 = stack.top();
 	CPFTimeStamp = Long.MIN_VALUE;
 
@@ -229,7 +241,7 @@
 	doubleQuotes    = "codes";
 	printStackTrace = "off";
 
-	exception       = SymbolTerm.intern("$none");
+	exception       = NONE;
 	startRuntime    = System.currentTimeMillis();
 	previousRuntime = 0;
 
diff --git a/src/lang/PrologControl.java b/src/lang/PrologControl.java
index e0d9312..2ee53da 100644
--- a/src/lang/PrologControl.java
+++ b/src/lang/PrologControl.java
@@ -23,8 +23,12 @@
 
     /** Constructs a new <code>PrologControl</code>. */
     public PrologControl() {
-	engine = new Prolog(this);
-	code = null;
+      engine = new Prolog(this);
+    }
+
+    /** Constructs a new <code>PrologControl</code>. */
+    public PrologControl(PrologMachineCopy pmc) {
+      engine = new Prolog(this, pmc);
     }
 
     public boolean isEnabled(Prolog.Feature f) {
@@ -53,7 +57,10 @@
     public void setMaxDatabaseSize(int size) {
       if (engine.aregs != null)
         throw new IllegalStateException("Prolog already initialized");
-      engine.internalDB = new InternalDatabase(size);
+      if (engine.internalDB != null)
+        engine.internalDB.maxContents = size;
+      else
+        engine.internalDB = new InternalDatabase(size);
     }
 
     public PrologClassLoader getPrologClassLoader() {
diff --git a/src/lang/PrologMachineCopy.java b/src/lang/PrologMachineCopy.java
new file mode 100644
index 0000000..4dc3afe
--- /dev/null
+++ b/src/lang/PrologMachineCopy.java
@@ -0,0 +1,92 @@
+package com.googlecode.prolog_cafe.lang;
+
+import java.util.Map;
+
+/**
+ * Backup of a Prolog interpreter that can later create a new interpreter.
+ * <p>
+ * A new interpreter can be bootstrapped by passing this copy object to a
+ * PrologControl constructor. Machine copies are primarily intended to let
+ * callers reuse the state constructed by running {@code consult(+File)} prior
+ * to perform any module initialization or predicate evaluation.
+ * <p>
+ * Only the PrologClassLoader, internal hash manager and database are copied.
+ * These are sections of an interpreter that relate to what code is available.
+ */
+public class PrologMachineCopy {
+  /**
+   * Save the database of a current PrologControl.
+   *
+   * @param ctl control to copy the database of.
+   * @return the copy.
+   */
+  public static PrologMachineCopy save(PrologControl ctl) {
+    return new PrologMachineCopy(ctl.engine);
+  }
+
+  /**
+   * Save the database of a current Prolog interpreter.
+   *
+   * @param engine interpreter to copy the database of.
+   * @return the copy.
+   */
+  public static PrologMachineCopy save(Prolog engine) {
+    return new PrologMachineCopy(engine);
+  }
+
+  protected final PrologClassLoader pcl;
+  protected final HashtableOfTerm hashManager;
+  protected final InternalDatabase internalDB;
+
+  private PrologMachineCopy(Prolog engine) {
+    pcl = engine.pcl;
+
+    // During backup, copy all terms using a single consistent copyHash.
+    // This isolates the copy from the source interpreter, in case it gets
+    // modified again later.
+    //
+    // During restore terms are not copied.
+    try {
+      engine.copyHash.clear();
+
+      hashManager = copyDeep(engine, engine.hashManager);
+      internalDB = new InternalDatabase(engine, engine.internalDB, true);
+    } finally {
+      engine.copyHash.clear();
+    }
+  }
+
+  private static HashtableOfTerm copyDeep(Prolog engine, HashtableOfTerm src) {
+    HashtableOfTerm hm = new HashtableOfTerm();
+    for (Map.Entry<Term, Term> e : src.entrySet()) {
+      Term val = e.getValue().copy(engine);
+
+      if (val.isJavaObject()) {
+        JavaObjectTerm o = (JavaObjectTerm) val;
+        if (o.obj instanceof HashtableOfTerm) {
+          val = new JavaObjectTerm(copyDeep(engine, (HashtableOfTerm) o.obj));
+        }
+      }
+
+      hm.put(e.getKey().copy(engine), val);
+    }
+    return hm;
+  }
+
+  static HashtableOfTerm copyShallow(HashtableOfTerm src) {
+    HashtableOfTerm hm = new HashtableOfTerm();
+    for (Map.Entry<Term, Term> e : src.entrySet()) {
+      Term val = e.getValue();
+
+      if (val.isJavaObject()) {
+        JavaObjectTerm o = (JavaObjectTerm) val;
+        if (o.obj instanceof HashtableOfTerm) {
+          val = new JavaObjectTerm(copyShallow((HashtableOfTerm) o.obj));
+        }
+      }
+
+      hm.put(e.getKey(), val);
+    }
+    return hm;
+  }
+}