current_user/1: Refactor to perform caching in Java

Instead of implementing all of the caching logic in Prolog, do
it in Java. Its slightly shorter this way, and faster at runtime.

Also support user(anonymous).

Change-Id: Icfb588329cc61b6470deb3128f8a7f86621a4099
diff --git a/gerrit-server/src/main/java/gerrit/PRED_$current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_$current_user_2.java
deleted file mode 100644
index 2b999a5..0000000
--- a/gerrit-server/src/main/java/gerrit/PRED_$current_user_2.java
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package gerrit;
-
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.rules.PrologEnvironment;
-import com.google.gerrit.rules.StoredValues;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Provider;
-
-import com.googlecode.prolog_cafe.lang.IllegalTypeException;
-import com.googlecode.prolog_cafe.lang.IntegerTerm;
-import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
-import com.googlecode.prolog_cafe.lang.Operation;
-import com.googlecode.prolog_cafe.lang.PInstantiationException;
-import com.googlecode.prolog_cafe.lang.Predicate;
-import com.googlecode.prolog_cafe.lang.Prolog;
-import com.googlecode.prolog_cafe.lang.PrologException;
-import com.googlecode.prolog_cafe.lang.Term;
-
-/**
- * Loads a CurrentUser object for a user identity.
- *
- * <pre>
- *   '$current_user'(+AccountId, -CurrentUser).
- * </pre>
- */
-class PRED_$current_user_2 extends Predicate.P2 {
-  private static final long serialVersionUID = 1L;
-
-  PRED_$current_user_2(Term a1, Term a2, Operation n) {
-    arg1 = a1;
-    arg2 = a2;
-    cont = n;
-  }
-
-  @Override
-  public Operation exec(Prolog engine) throws PrologException {
-    engine.setB0();
-    Term a1 = arg1.dereference();
-    Term a2 = arg2.dereference();
-
-    if (a1.isVariable()) {
-      throw new PInstantiationException(this, 1);
-    }
-    if (!a1.isInteger()) {
-      throw new IllegalTypeException(this, 1, "int", a1);
-    }
-    Account.Id accountId = new Account.Id(((IntegerTerm) a1).intValue());
-
-    final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
-    IdentifiedUser.GenericFactory userFactory = userFactory(engine);
-    CurrentUser user;
-    if (db != null) {
-      user = userFactory.create(new Provider<ReviewDb>() {
-        public ReviewDb get() {
-          return db;
-        }
-      }, accountId);
-    } else {
-      user = userFactory.create(accountId);
-    }
-
-    if (!a2.unify(new JavaObjectTerm(user), engine.trail)) {
-      return engine.fail();
-    }
-
-    return cont;
-  }
-
-  private IdentifiedUser.GenericFactory userFactory(Prolog engine) {
-    PrologEnvironment env = (PrologEnvironment) engine.control;
-    return env.getInjector().getInstance(IdentifiedUser.GenericFactory.class);
-  }
-}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
new file mode 100644
index 0000000..fe74f5d
--- /dev/null
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -0,0 +1,155 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gerrit;
+
+import static com.googlecode.prolog_cafe.lang.SymbolTerm.intern;
+
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.inject.Provider;
+
+import com.googlecode.prolog_cafe.lang.HashtableOfTerm;
+import com.googlecode.prolog_cafe.lang.IllegalTypeException;
+import com.googlecode.prolog_cafe.lang.IntegerTerm;
+import com.googlecode.prolog_cafe.lang.InternalException;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.Operation;
+import com.googlecode.prolog_cafe.lang.PInstantiationException;
+import com.googlecode.prolog_cafe.lang.Predicate;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.StructureTerm;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+
+/**
+ * Loads a CurrentUser object for a user identity.
+ * <p>
+ * Values are cached in the hash {@code current_user}, avoiding recreation
+ * during a single evaluation.
+ *
+ * <pre>
+ *   current_user(user(+AccountId), -CurrentUser).
+ * </pre>
+ */
+class PRED_current_user_2 extends Predicate.P2 {
+  private static final long serialVersionUID = 1L;
+  private static final SymbolTerm user = intern("user", 1);
+  private static final SymbolTerm anonymous = intern("anonymous");
+  private static final SymbolTerm current_user = intern("current_user");
+
+  PRED_current_user_2(Term a1, Term a2, Operation n) {
+    arg1 = a1;
+    arg2 = a2;
+    cont = n;
+  }
+
+  @Override
+  public Operation exec(Prolog engine) throws PrologException {
+    engine.setB0();
+    Term a1 = arg1.dereference();
+    Term a2 = arg2.dereference();
+
+    if (a1.isVariable()) {
+      throw new PInstantiationException(this, 1);
+    }
+
+    HashtableOfTerm userHash = userHash(engine);
+    Term userTerm = userHash.get(a1);
+    if (userTerm != null && userTerm.isJavaObject()) {
+      if (!(((JavaObjectTerm) userTerm).object() instanceof CurrentUser)) {
+        userTerm = createUser(engine, a1, userHash);
+      }
+    } else {
+      userTerm = createUser(engine, a1, userHash);
+    }
+
+    if (!a2.unify(userTerm, engine.trail)) {
+      return engine.fail();
+    }
+
+    return cont;
+  }
+
+  public Term createUser(Prolog engine, Term key, HashtableOfTerm userHash) {
+    if (!key.isStructure()
+        || key.arity() != 1
+        || !((StructureTerm) key).functor().equals(user)) {
+      throw new IllegalTypeException(this, 1, "user(int)", key);
+    }
+
+    Term idTerm = key.arg(0);
+    CurrentUser user;
+    if (idTerm.isInteger()) {
+      Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
+
+      final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
+      IdentifiedUser.GenericFactory userFactory = userFactory(engine);
+      if (db != null) {
+        user = userFactory.create(new Provider<ReviewDb>() {
+          public ReviewDb get() {
+            return db;
+          }
+        }, accountId);
+      } else {
+        user = userFactory.create(accountId);
+      }
+
+
+    } else if (idTerm.equals(anonymous)) {
+      user = anonymousUser(engine);
+
+    } else {
+      throw new IllegalTypeException(this, 1, "user(int)", key);
+    }
+
+    Term userTerm = new JavaObjectTerm(user);
+    userHash.put(key, userTerm);
+    return userTerm;
+  }
+
+  private static HashtableOfTerm userHash(Prolog engine) {
+    Term userHash = engine.getHashManager().get(current_user);
+    if (userHash == null) {
+      HashtableOfTerm users = new HashtableOfTerm();
+      engine.getHashManager().put(current_user, new JavaObjectTerm(userHash));
+      return users;
+    }
+
+    if (userHash.isJavaObject()) {
+      Object obj = ((JavaObjectTerm) userHash).object();
+      if (obj instanceof HashtableOfTerm) {
+        return (HashtableOfTerm) obj;
+      }
+    }
+
+    throw new InternalException(current_user + " is not HashtableOfTerm");
+  }
+
+  private static AnonymousUser anonymousUser(Prolog engine) {
+    PrologEnvironment env = (PrologEnvironment) engine.control;
+    return env.getInjector().getInstance(AnonymousUser.class);
+  }
+
+  private static IdentifiedUser.GenericFactory userFactory(Prolog engine) {
+    PrologEnvironment env = (PrologEnvironment) engine.control;
+    return env.getInjector().getInstance(IdentifiedUser.GenericFactory.class);
+  }
+}
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index c89dc97..18015dc 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -93,36 +93,18 @@
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
-%% current_user/2:
-%%
-%%   Locate the CurrentUser object caching group memberships, account data.
-%%
-current_user(user(AccountId), User) :-
-  hash_get(current_user, AccountId, User),
-  User \== [],
-  !
-  .
-current_user(user(AccountId), User) :-
-  integer(AccountId),
-  '$current_user'(AccountId, User),
-  hash_put(current_user, AccountId, User).
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%
 %% user_label_range/4:
 %%
 %%   Lookup the range allowed to be used.
 %%
-user_label_range(Label, test_user(Name), Min, Max) :-
-  % TODO Replace this hack clause when RefControl is rewritten.
-  !,
-  clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
-  .
 user_label_range(Label, Who, Min, Max) :-
+  Who = user(_), !,
   atom(Label),
   current_user(Who, User),
   '$user_label_range'(Label, User, Min, Max).
+user_label_range(Label, test_user(Name), Min, Max) :-
+  clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
+  .
 
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%