org.ggp.base.util.gdl.grammar.GdlPool Maven / Gradle / Ivy
Show all versions of alloy-ggp-base Show documentation
package org.ggp.base.util.gdl.grammar;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
/**
* The GdlPool manages the creation of {@link Gdl} objects. It is the only way Gdl
* objects may be created. It ensures that for each possible sentence, rule, or
* fragment of GDL, only one corresponding Gdl object is created. This means Gdl
* objects may be checked for equality with an instance-equality check (==) rather
* than a more expensive recursive equality check.
*
* A long-lived game player may accumulate lots of objects in its pool. To remove
* them, it may call {@link #drainPool()} in between games. Note that if this
* method is called while references to Gdl objects other than keyword constants
* are held elsewhere, bad things will happen.
*/
public final class GdlPool
{
private static final ConcurrentMap> distinctPool = new ConcurrentHashMap>();
private static final ConcurrentMap, GdlFunction>> functionPool = new ConcurrentHashMap, GdlFunction>>();
private static final ConcurrentMap notPool = new ConcurrentHashMap();
private static final ConcurrentMap, GdlOr> orPool = new ConcurrentHashMap, GdlOr>();
private static final ConcurrentMap propositionPool = new ConcurrentHashMap();
private static final ConcurrentMap, GdlRelation>> relationPool = new ConcurrentHashMap, GdlRelation>>();
private static final ConcurrentMap, GdlRule>> rulePool = new ConcurrentHashMap, GdlRule>>();
private static final ConcurrentMap variablePool = new ConcurrentHashMap();
private static final ConcurrentMap constantPool = new ConcurrentHashMap();
//Access to constantCases and variableCases should be synchronized using their monitor locks.
private static final Map constantCases = new TreeMap(String.CASE_INSENSITIVE_ORDER);
private static final Map variableCases = new TreeMap(String.CASE_INSENSITIVE_ORDER);
// Controls whether we normalize the case of incoming constants and variables.
public static volatile boolean caseSensitive = true;
// Special keyword constants. These are never drained between games and are always
// represented as lower-case so that they can easily be referred to internally and
// so that all of the "true" objects are equal to each other in the Java == sense.
// For example, attempting to create a GdlConstant "TRUE" will return the same constant
// as if one had attempted to create the GdlConstant "true", regardless of whether the
// game-specific constants are case-sensitive or not. These special keywords are never
// sent over the network in PLAY requests and responses, so this should be safe.
public static final ImmutableSet KEYWORDS = ImmutableSet.of(
"init","true","next","role","does","goal","legal","terminal","base","input","_");
public static final GdlConstant BASE = getConstant("base");
public static final GdlConstant DOES = getConstant("does");
public static final GdlConstant GOAL = getConstant("goal");
public static final GdlConstant INIT = getConstant("init");
public static final GdlConstant INPUT = getConstant("input");
public static final GdlConstant LEGAL = getConstant("legal");
public static final GdlConstant NEXT = getConstant("next");
public static final GdlConstant ROLE = getConstant("role");
public static final GdlConstant TERMINAL = getConstant("terminal");
public static final GdlConstant TRUE = getConstant("true");
/**
* Represents a single underscore ("_"). The underscore is not a GDL keyword, but
* it's used by SentenceForms and is generally convenient for utility methods.
*/
public static final GdlConstant UNDERSCORE = getConstant("_");
private GdlPool() {
// Not instantiable
}
/**
* Drains the contents of the GdlPool. Useful to control memory usage
* once you have finished playing a large game.
*
* WARNING: Should only be called *between games*, when there are no
* references to Gdl objects (other than keyword constants) outside the
* pool.
*/
public static void drainPool() {
distinctPool.clear();
functionPool.clear();
notPool.clear();
orPool.clear();
propositionPool.clear();
relationPool.clear();
rulePool.clear();
variablePool.clear();
synchronized (variableCases) {
variableCases.clear();
}
// // When draining the pool between matches, we still need to preserve the keywords
// // since there are global references to them. For example, the Prover state machine
// // has a reference to the GdlConstant "true", and that reference must still point
// // to the authoritative GdlConstant "true" after the pool is drained and another
// // game has begun. As such, when draining the constant pool, these special keywords
// // are set aside and returned to the pool after all of the other constants (which
// // were game-specific) have been drained.
// Map keywordConstants = new HashMap();
// for (String keyword : KEYWORDS) {
// keywordConstants.put(keyword, GdlPool.getConstant(keyword));
// }
// synchronized (constantCases) {
// constantPool.clear();
// constantCases.clear();
// for (Map.Entry keywordEntry : keywordConstants.entrySet()) {
// constantCases.put(keywordEntry.getKey(), keywordEntry.getKey());
// constantPool.put(keywordEntry.getKey(), keywordEntry.getValue());
// }
// }
}
/**
* If the pool does not have a mapping for the given key, adds a mapping from key to value
* to the pool.
*
* Note that even if you've checked to make sure that the pool doesn't contain the key,
* you still shouldn't assume that this method actually inserts the given value, since
* this class is accessed by multiple threads simultaneously.
*
* @return the value mapped to by key in the pool
*/
private static V addToPool(K key, V value, ConcurrentMap pool) {
V prevValue = pool.putIfAbsent(key, value);
if (prevValue == null) {
return value;
} else {
return prevValue;
}
}
public static GdlConstant getConstant(String value)
{
if (KEYWORDS.contains(value.toLowerCase())) {
value = value.toLowerCase();
}
if (!caseSensitive) {
synchronized (constantCases) {
if (constantCases.containsKey(value)) {
value = constantCases.get(value);
} else {
constantCases.put(value, value);
}
}
}
GdlConstant ret = constantPool.get(value);
if(ret == null)
ret = addToPool(value, new GdlConstant(value), constantPool);
return ret;
}
public static GdlVariable getVariable(String name)
{
if (!caseSensitive) {
synchronized (variableCases) {
if (variableCases.containsKey(name)) {
name = variableCases.get(name);
} else {
variableCases.put(name, name);
}
}
}
GdlVariable ret = variablePool.get(name);
if(ret == null)
ret = addToPool(name, new GdlVariable(name), variablePool);
return ret;
}
public static GdlDistinct getDistinct(GdlTerm arg1, GdlTerm arg2)
{
ConcurrentMap bucket = distinctPool.get(arg1);
if(bucket == null)
bucket = addToPool(arg1, new ConcurrentHashMap(), distinctPool);
GdlDistinct ret = bucket.get(arg2);
if(ret == null)
ret = addToPool(arg2, new GdlDistinct(arg1, arg2), bucket);
return ret;
}
public static GdlFunction getFunction(GdlConstant name)
{
List empty = Collections.emptyList();
return getFunction(name, empty);
}
public static GdlFunction getFunction(GdlConstant name, GdlTerm[] body)
{
return getFunction(name, Arrays.asList(body));
}
public static GdlFunction getFunction(GdlConstant name, List body)
{
ConcurrentMap, GdlFunction> bucket = functionPool.get(name);
if(bucket == null) {
ConcurrentMap, GdlFunction> newMap = new MapMaker().weakValues().makeMap();
bucket = addToPool(name, newMap, functionPool);
}
GdlFunction ret = bucket.get(body);
if(ret == null) {
ImmutableList immutableBody = ImmutableList.copyOf(body);
ret = addToPool(immutableBody, new GdlFunction(name, immutableBody), bucket);
}
return ret;
}
public static GdlNot getNot(GdlLiteral body)
{
GdlNot ret = notPool.get(body);
if(ret == null)
ret = addToPool(body, new GdlNot(body), notPool);
return ret;
}
public static GdlOr getOr(GdlLiteral[] disjuncts)
{
return getOr(Arrays.asList(disjuncts));
}
public static GdlOr getOr(List disjuncts)
{
GdlOr ret = orPool.get(disjuncts);
if(ret == null) {
ImmutableList immutableDisjuncts = ImmutableList.copyOf(disjuncts);
ret = addToPool(immutableDisjuncts, new GdlOr(immutableDisjuncts), orPool);
}
return ret;
}
public static GdlProposition getProposition(GdlConstant name)
{
GdlProposition ret = propositionPool.get(name);
if(ret == null)
ret = addToPool(name, new GdlProposition(name), propositionPool);
return ret;
}
public static GdlRelation getRelation(GdlConstant name)
{
List empty = Collections.emptyList();
return getRelation(name, empty);
}
public static GdlRelation getRelation(GdlConstant name, GdlTerm[] body)
{
return getRelation(name, Arrays.asList(body));
}
public static GdlRelation getRelation(GdlConstant name, List extends GdlTerm> body)
{
ConcurrentMap, GdlRelation> bucket = relationPool.get(name);
if(bucket == null) {
ConcurrentMap, GdlRelation> newMap = new MapMaker().weakValues().makeMap();
bucket = addToPool(name, newMap, relationPool);
}
GdlRelation ret = bucket.get(body);
if(ret == null) {
ImmutableList immutableBody = ImmutableList.copyOf(body);
ret = addToPool(immutableBody, new GdlRelation(name, immutableBody), bucket);
}
return ret;
}
public static GdlRule getRule(GdlSentence head)
{
List empty = Collections.emptyList();
return getRule(head, empty);
}
public static GdlRule getRule(GdlSentence head, GdlLiteral[] body)
{
return getRule(head, Arrays.asList(body));
}
public static GdlRule getRule(GdlSentence head, List body)
{
ConcurrentMap, GdlRule> bucket = rulePool.get(head);
if (bucket == null) {
ConcurrentMap, GdlRule> newMap = new MapMaker().weakValues().makeMap();
bucket = addToPool(head, newMap, rulePool);
}
GdlRule ret = bucket.get(body);
if(ret == null) {
ImmutableList immutableBody = ImmutableList.copyOf(body);
ret = addToPool(immutableBody, new GdlRule(head, immutableBody), bucket);
}
return ret;
}
/**
* This method should only rarely be used. It takes a foreign GDL object
* (one that wasn't constructed through the GdlPool) and returns a version
* that lives in the GdlPool. Various parts of the prover infrastructure
* expect that all GDL objects live in the GdlPool, and so it's important
* that any foreign GDL objects created outside the GdlPool be immersed
* before being used. Since every GDL object should be created through the
* GdlPool, immerse should only need to be called on GDL that appears from
* outside sources: for example, being deserialized from a file.
*/
@SuppressWarnings("unchecked") //Cast to T for convenience; it works here
public static T immerse(T foreignGdl) {
if(foreignGdl instanceof GdlDistinct) {
return (T) GdlPool.getDistinct(immerse(((GdlDistinct) foreignGdl).getArg1()), immerse(((GdlDistinct) foreignGdl).getArg2()));
} else if(foreignGdl instanceof GdlNot) {
return (T) GdlPool.getNot(immerse(((GdlNot) foreignGdl).getBody()));
} else if(foreignGdl instanceof GdlOr) {
GdlOr or = (GdlOr)foreignGdl;
List rval = new ArrayList();
for(int i=0; i rval = new ArrayList();
for(int i=0; i rval = new ArrayList();
for(int i=0; i rval = new ArrayList();
for(int i=0; i