package com.chap.memo.memoNodes;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
import com.chap.memo.memoNodes.bus.MemoReadBus;
import com.chap.memo.memoNodes.bus.MemoWriteBus;
import com.chap.memo.memoNodes.model.ArcList;
import com.chap.memo.memoNodes.model.NodeValue;
import com.chap.memo.memoNodes.servlet.JSONTuple;
import com.eaio.uuid.UUID;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* MemoNode is a graph database, designed to run effectively on the Google App Engine datastore.
* It features arbitrary length byte values, full history and a self-referencing pattern-matching
* search facility.
*
* Copyright: Ludo Stellingwerff, Almende B.V.
* License: Apache License Version 2.0
* @see <a href="http://chap.almende.com/">Part of CHAP</a>
*
* @author Ludo Stellingwerff
* @author Almende B.V.
* @version 1.0
*
*/
public class MemoNode implements Comparable<MemoNode> {
public static UUID ROOT = UUID.nilUUID();
// private static int existingNodes = 0;
static final ObjectMapper om = new ObjectMapper();
private MemoReadBus readBus = MemoReadBus.getBus();
private MemoWriteBus writeBus = MemoWriteBus.getBus();
private long lastUpdate= System.currentTimeMillis();
private UUID uuid;
private NodeValue value = null;
private final ArcList parents;
private final ArcList children;
/**
* Makes sure all graph changes are written to the datastore. It is advisable to run this
* at least at the end of each Servlet call.
*/
public static void flushDB(){
MemoWriteBus.getBus().flush();
}
/**
* Dangerous! Empties the entire graph database, irreversible! You will loose data...
*/
public static void emptyDB(){
MemoWriteBus.emptyDB();
}
/**
* Recluster the database for faster access. (Run every couple of minutes)
*/
public static void compactDB(){
MemoReadBus.getBus().compactDB();
}
/**
* Export the entire node database as a byte array
*/
public static void exportDB(OutputStream out){
MemoReadBus.getBus().exportDB(out,false);
}
/**
* Export and remove historical nodes, leaving only current values
*/
public static void purgeHistory(OutputStream out){
MemoReadBus.getBus().exportDB(out, true);
}
/**
* Remove historical data, leaving only current values
*/
public static void dropHistory(){
MemoReadBus.getBus().dropHistory();
}
/**
* import the given byte array as subgraph, HOW?
*/
public static void importDB(InputStream in){
MemoWriteBus.getBus().importDB(in);
}
/**
* Get the node that can serve as a tree root, providing at least one anchor for the database.
* Use sparsely as this node will otherwise get a lot of children, better to use one or more
* intermediate nodes.
*
* Root node has UUID: 00000000-0000-0000-0000-000000000000
*/
public static MemoNode getRootNode() {
MemoReadBus readBus = MemoReadBus.getBus();
MemoNode result = readBus.find(ROOT);
if (result == null) {
result = new MemoNode(ROOT, "root");
}
// System.out.println("Root node("+ROOT.toString()+") has time:"+result.getId().time);
return result;
}
@Override
public int compareTo(MemoNode o) {
if (this.getId().equals(o.getId())) return 0;
return (int) ((this.getTimestamp() - o.getTimestamp())%1);
}
@Override
public int hashCode(){
return this.getId().hashCode();
}
@Override
public boolean equals(Object o){
if (o instanceof MemoNode) {
return this.getId().equals(((MemoNode)o).getId());
} else {
return false;
}
}
/* protected void finalize(){
if (existingNodes > 100000) System.out.println("Quite many nodes found!"+existingNodes);
existingNodes--;
}*/
/**
* Find or create node with specified UUID. This is the recommended way to obtain
* existing nodes of which you know the UUID. If node can't be found, this node will have an
* empty value;
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public MemoNode(UUID uuid){
// existingNodes++;
this.uuid=uuid;
this.parents=new ArcList(uuid,0);
this.children=new ArcList(uuid,1);
}
/**
* Create new node with specified value.
*
*/
public MemoNode(byte[] value){
// existingNodes++;
this.uuid=new UUID();
// if (this.uuid.time < 0) System.out.println("created UUID with time:"+this.uuid.time);
this.value=writeBus.store(this.uuid, value);
this.parents=new ArcList(this.uuid,0);
this.children=new ArcList(this.uuid,1);
}
/**
* Create new node with specified string value.
*
*/
public MemoNode(String value){
// existingNodes++;
this.uuid=new UUID();
// if (this.uuid.time < 0) System.out.println("created UUID with time:"+this.uuid.time);
this.value=writeBus.store(this.uuid, value.getBytes());
this.parents=new ArcList(this.uuid,0);
this.children=new ArcList(this.uuid,1);
}
/**
* Find or create node with specified UUID and value. If node existed it will be updated to the
* provided value.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public MemoNode(UUID uuid,byte[] value){
// existingNodes++;
this.uuid=uuid;
this.parents=new ArcList(this.uuid,0);
this.children=new ArcList(this.uuid,1);
this.value=writeBus.store(uuid, value);
}
/**
* Find or create node with specified UUID and value. If node existed it will be updated to the
* provided string value.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public MemoNode(UUID uuid,String value){
// existingNodes++;
this.uuid=uuid;
this.parents=new ArcList(this.uuid,0);
this.children=new ArcList(this.uuid,1);
this.value=writeBus.store(uuid, value.getBytes());
}
public MemoNode(NodeValue value){
// existingNodes++;
if (value!=null){
this.uuid=value.getId();
this.value=value;
} else {
System.out.println("Null value given, generating new UUID!");
this.uuid=new UUID();
this.value=null;
}
this.parents=new ArcList(this.uuid,0);
this.children=new ArcList(this.uuid,1);
}
/**
* Update node's value to specified value.
*
* @return this node
*/
public MemoNode update(byte[] value){
if (value == null) value = new byte[0];
this.value=writeBus.store(this.getId(), value);
return this;
}
/**
* Update node's value to specified string value.
*
* @return this node
*/
public MemoNode update(String value){
this.value=writeBus.store(this.getId(), value.getBytes());
return this;
}
/**
* Add a new parent arc between a node with specified parent UUID and this node, effectively
* making this node a child of the provided node.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public void addParent(UUID parent){
parents.addNode(parent);
}
/**
* Add a new child arc between a node with specified child UUID and this node, effectively making
* the provided node a child of this node.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public void addChild(UUID child){
children.addNode(child);
}
/**
* Remove the parent arc between the node with specified parent UUID and this node, effectively
* making this node no longer a child of the provided node.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public void delParent(UUID parent){
parents.delNode(parent);
}
/**
* Remove the child arc between this node and the node with the provided child UUID, effectively making
* this node no longer a parent of the provided node.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public void delChild(UUID child){
children.delNode(child);
}
/**
* Add a new parent arc between the specified parent node and this node, effectively
* making this node a child of the provided node.
*
* @return parent
*/
public MemoNode addParent(MemoNode parent){
addParent(parent.getId());
return parent;
}
/**
* Add a new child arc between the specified child node and this node, effectively making
* the provided node a child of this node.
*
* @return child
*/
public MemoNode addChild(MemoNode child){
addChild(child.getId());
return child;
}
/**
* Add a new parent arc between the specified parent node and this node, effectively
* making this node a child of the provided node.
*
* @return this node
*/
public MemoNode setParent(MemoNode parent){
addParent(parent.getId());
return this;
}
/**
* Add a new child arc between the specified child node and this node, effectively making
* the provided node a child of this node.
*
* @return this node
*/
public MemoNode setChild(MemoNode child){
addChild(child.getId());
return this;
}
/**
* Remove the parent arc between the specified parent node and this node, effectively
* making this node no longer a child of the provided node.
*
* @return this node
*/
public MemoNode delParent(MemoNode parent){
delParent(parent.getId());
return this;
}
/**
* Remove the child arc between this node and the provided child node, effectively making
* this node no longer a parent of the provided node.
*
* @return this node
*/
public MemoNode delChild(MemoNode child){
delChild(child.getId());
return this;
}
/**
* Checks if given node is a direct parent of this node.
*
* @return boolean
*/
public boolean isChildOf(MemoNode node){
return this.getParentIds().contains(node.uuid);
}
/**
* Checks if given node is a direct parent of this node.
*
* @return boolean
*/
public boolean isParentOf(MemoNode node){
//TODO: can be done more efficient?
return this.getChildIds().contains(node.uuid);
}
/**
* Get current value of node. If node has been deleted, can't be found or has been
* updated to a null value, this call returns a zero-size byte[].
*
* @return byte[], zero-size if node not found/empty/null
*/
public byte[] getValue(){
if (this.value == null || readBus.valueChanged(lastUpdate)){
this.value=readBus.getValue(this.uuid);
lastUpdate=System.currentTimeMillis();
}
return this.value == null?null:this.value.getValue();
}
/**
* Get current value of node as a string. The returned string is assuming an ASCII charset, for
* special chars use "new String(getValue())" instead.
* If node has been deleted, can't be found or has been updated to a null value,
* this call returns an empty string.
*
* @return String
*/
public String getStringValue(){
byte[] bytes = getValue();
if (bytes == null) return "";
char[] buffer = new char[bytes.length];
for(int i = 0; i < buffer.length; i++) {
buffer[i] = (char)bytes[i];
}
return new String(buffer);
}
public byte[] valueAt(long timestamp){
NodeValue oldValue;
oldValue=readBus.getValue(getId(), timestamp);
return oldValue.getValue();
}
public ArrayList<MemoNode> history(){
ArrayList<MemoNode> result = readBus.findAll(getId());
if (!result.get(result.size()-1).equals(this)){
result.add(this);
}
return result;
}
/**
* Returns the UUID of this node.
*
* @see <a href="http://johannburkard.de/software/uuid/">http://johannburkard.de/software/uuid/</a>
*/
public UUID getId(){
return this.uuid;
}
/**
* Returns the timestamp of the latest update (or creation) to this node as the amount of microseconds since midnight 1 Jan 1970.
*
* @return long, amount of microseconds since 1-1-1970 00:00:00.00;
*/
public long getTimestamp(){
return Math.max(this.value.getTimestamp_long(),Math.max(this.children.getTimestamp_long(),this.parents.getTimestamp_long()));
}
/**
* Returns the list of the uuids of direct parent nodes.
* Use this to count the amount of parents, i.s.o. the getParents.
* (which is way more expensive.)
*
* @return ImmutableList<UUID> parentIds
*/
public ImmutableList<UUID> getParentIds(){
return ImmutableList.copyOf(this.parents.getNodesIds());
}
/**
* Returns the list of direct parent nodes.
*
* @return ImmutableList<MemoNode> parents
*/
public ImmutableList<MemoNode> getParents(){
return this.parents.getNodes();
}
/**
* Returns the list of the uuids of direct child nodes.
* Use this to count the amount of children, i.s.o. the getChildren.
* (which is way more expensive.)
*
* @return ImmutableList<UUID> childIds
*/
public ImmutableList<UUID> getChildIds(){
return ImmutableList.copyOf(this.children.getNodesIds());
}
/**
* Returns the list of direct child nodes.
*
* @return ImmutableList<MemoNode> children
*/
public ImmutableList<MemoNode> getChildren(){
return this.children.getNodes();
}
/**
* Returns all children whose string value equal the given string.
*
* @param value the string value to compare with
* @param topx return a maximum of topx children (Set to 0 for all matching children)
* @return ArrayList<MemoNode> children
*/
public ArrayList<MemoNode> getChildrenByStringValue(String value, int topx){
UUID[] childids = this.children.getNodesIds();
ArrayList<MemoNode> result = new ArrayList<MemoNode>(topx>0?topx:10);
for (UUID childid : childids) {
MemoNode child = new MemoNode(childid);
if (child.getStringValue().equals(value)) {
result.add(child);
if (topx > 0 && result.size() >= topx)
return result;
}
}
return result;
}
/**
* Get a single child whose string value equals the given string. If multiple
* children are found, return the (arbitrary) first one.
*
* @param value the string value to compare with
* @return MemoNode child
*/
public MemoNode getChildByStringValue(String value){
ArrayList<MemoNode> children = getChildrenByStringValue(value,1);
if (children != null && children.size()>0){
return children.get(0);
}
return null;
}
/**
* Returns all children whose string value match the given regular expression.
*
* @see java.util.regex.Pattern
* @param regex the regular expression to match against
* @param topx return a maximum of topx children (Set to 0 for all matching children)
* @return ArrayList<MemoNode> children
*/
public ArrayList<MemoNode> getChildrenByRegEx(Pattern regex, int topx){
UUID[] childids = this.children.getNodesIds();
ArrayList<MemoNode> result = new ArrayList<MemoNode>(topx>0?topx:10);
for (UUID uuid: childids) {
MemoNode child = new MemoNode(uuid);
if (regex.matcher(child.getStringValue()).matches()) {
result.add(child);
if (topx > 0 && result.size() >= topx)
return result;
}
}
return result;
}
/**
* Returns all children whose integer value falls in the give range.
* (Currently interprets the string value as an integer, will probably still change in the future?)
*
* @param lower the lower bound of the range, value is included in the range.
* @param upper the upper bound of the range, value is included in the range.
* @param topx return a maximum of topx children (Set to 0 for all matching children)
* @return ArrayList<MemoNode> children
*/
public ArrayList<MemoNode> getChildrenByRange(int lower, int upper, int topx){
//TODO: store integers differently? Not as String...
UUID[] childids = this.children.getNodesIds();
ArrayList<MemoNode> result = new ArrayList<MemoNode>(topx>0?topx:10);
for (UUID uuid: childids) {
MemoNode child = new MemoNode(uuid);
try {
int value = Integer.parseInt(child.getStringValue());
if (value >= lower && value <= upper) {
result.add(child);
if (topx > 0 && result.size() >= topx)
return result;
}
} catch (NumberFormatException e) {
}
}
return result;
}
/**
* Remove this node.(=setting its value to null and removing all arcs) This method will delete
* entire subgraphs. Removing large subgraphs can be
* an expensive operation.
*
*/
public void delete(){
MemoNode current = this;
ArrayList<UUID> todo = new ArrayList<UUID>(20);
while (current != null){
UUID[] children = current.children.getNodesIds();
UUID[] parents = current.parents.getNodesIds();
current.update((byte[])null);
if (children.length>0){
todo.ensureCapacity(todo.size()+children.length);
todo.addAll(Arrays.asList(children));
current.children.clear();
}
if (parents.length>0){
current.parents.clear();
}
if (todo.size()>0){
current = new MemoNode(todo.remove(0));
} else {
break;
}
}
}
/**
* Convenience method to store a property pattern for this node. This method
* will store the given propValue as a child of an intermediate propName node, which
* will be stored as a child to the current node. (current->propName->propValue)
* Existing propNames for this node will lead to an update of the value.
*
* @see #getPropertyValue(String propName)
* @param propName the name of this property, must be unique for this node.
* @param propValue the (new) value of this property.
*
*/
public MemoNode setPropertyValue(String propName, String propValue){
if (propName == null) return this;
if (propValue == null) propValue="";
ArrayList<MemoNode> properties = getChildrenByStringValue(propName, 1);
switch (properties.size()) {
case 0:
MemoNode value = new MemoNode(propValue.getBytes());
MemoNode property = new MemoNode(propName.getBytes());
property.addChild(value.getId());
this.addChild(property.getId());
break;
case 1:
List<UUID> values = properties.get(0).getChildIds();
if (values.size() == 1) {
MemoNode val=new MemoNode(values.get(0));
if (val.getStringValue().equals(propValue)) break;
val.update(propValue.getBytes());
break;
}
if (values.size() == 0){
properties.get(0).addChild(new MemoNode(propValue.getBytes()));
break;
}
//explicit no-break
default:
System.out
.println("Error, incorrect properties found, repairing! setPropertyValue("
+ propName + "," + propValue + ")!");
for (MemoNode node : properties){
node.delete();
}
return setPropertyValue(propName,propValue);
}
return this;
}
/**
* Convenience method to get a property pattern for this node.
*
* @see #setPropertyValue(String propName,String propValue)
* @param propName the name of the property to retrieve
*
*/
public String getPropertyValue(String propName){
ArrayList<MemoNode> properties = getChildrenByStringValue(propName, 1);
if (properties.size() == 1) {
List<UUID> values = properties.get(0).getChildIds();
if (values.size() > 1)
System.out.println("Warning, property with multiple values: "+this.getId()+":"+propName);
if (values.size() >= 1)
return new MemoNode(values.get(0)).getStringValue();
}
return "";
}
private class StepState{
private boolean matched = false;
public StepState(boolean matched,String reason,MemoQuery query,MemoNode toCompare){
//System.out.println(toCompare.getValue()+"/"+query.value+" -> returning: "+matched+" reason:"+reason);
this.setMatched(matched);
}
public boolean isMatched() {
return matched;
}
public void setMatched(boolean matched) {
this.matched = matched;
}
}
private StepState doStep(boolean preamble, MemoQuery query, MemoNode toCompare,
ArrayList<UUID> results, HashSet<UUID> seenNodes,
ArrayList<MemoNode> patterns, int topX, HashMap<String,String> arguments){
MemoNode step = query.node;
//System.out.println("checking node:" + toCompare.getStringValue() + "/" + query.value + "("+preamble+")");
if (!query.match(toCompare)) return new StepState(false,"Node doesn't match.",query,toCompare);
if (seenNodes.contains(toCompare.getId())) return new StepState(true,"Loop/Multipath detected",query,toCompare);
if (preamble) {
for (MemoNode pattern : patterns){
StepState res = doStep(false,MemoQuery.parseQuery(pattern.getChildren().get(0), arguments),
toCompare,null,new HashSet<UUID>(),null,0,arguments);
if (res.matched){
results.add(toCompare.getId());
return new StepState(true,"Node matches pattern! Added to result, no need to search deeper.",query,toCompare);
}
}
}
seenNodes.add(toCompare.getId());
List<MemoNode> nextPats = step.getChildren();
int toMatchNo = nextPats.size();
if (toMatchNo == 0) return new StepState(true,"End of pattern",query,toCompare);
List<UUID> children = toCompare.getChildIds();
if (!preamble && children.size() < toMatchNo) return new StepState(false,"Too little children for pattern",query,toCompare);
ArrayList<MemoQuery> queries = new ArrayList<MemoQuery>(toMatchNo);
HashSet<MemoQuery> foundQueries = new HashSet<MemoQuery>(toMatchNo);
for (MemoNode nextPat : nextPats) {
queries.add(MemoQuery.parseQuery(nextPat, arguments));
}
MemoQuery[] queryArray = { new MemoQuery() };
queryArray = queries.toArray(queryArray);
Arrays.sort(queryArray);
for (UUID uuid : children) {
MemoNode child = new MemoNode(uuid);
for (MemoQuery iQuery : queryArray) {
if (foundQueries.contains(iQuery)) continue;
StepState res = doStep(preamble,iQuery,child,results,seenNodes,patterns,topX, arguments);
if (topX > 0 && results.size() >= topX) return new StepState(true,"TopX results reached, returning!",query,toCompare);
if (preamble || !res.isMatched()) continue;
//Return on fully matched pattern
foundQueries.add(iQuery);
if (foundQueries.size() == queryArray.length) return new StepState(true,"Pattern fully matched",query,toCompare);
}
}
if (preamble) return new StepState(true,"preamble return.",query,toCompare);
return new StepState(false,"Pattern didn't match entirely.",query,toCompare);
}
/**
* Search for nodes according to the give Preamble(s) and Pattern(s).
* (Full documentation is still on the todo list:) )
*
* @see "MemoTestServlet.java for an example of searching."
*/
public List<MemoNode> search(ArrayList<MemoNode> preambles,
ArrayList<MemoNode> patterns, int topx, HashMap<String,String> arguments) {
ArrayList<UUID> result = new ArrayList<UUID>(topx>0?Math.min(200,topx):200);
HashSet<UUID> seenNodes = new HashSet<UUID>(200);
if (patterns.size() <= 0) {
System.out.println("Warning, empty algorithm used (no patterns).");
return new ArrayList<MemoNode>(0);
}
if (preambles.size() <= 0) {
System.out.println("Warning, empty algorithm used (no preambles).");
return new ArrayList<MemoNode>(0);
}
for (MemoNode preamble : preambles) {
doStep(true,MemoQuery.parseQuery(preamble.getChildren().get(0),arguments),(MemoNode) this,result,seenNodes,patterns,topx,arguments);
}
Builder<MemoNode> resBuilder = ImmutableList.builder();
for (UUID uuid: result){
resBuilder.add(new MemoNode(uuid));
}
return resBuilder.build();
}
/**
* Search for nodes according to the give Preamble(s) and Pattern(s).
* (Full documentation is still on the todo list:) )
*
* @see "MemoTestServlet.java for an example of searching."
*/
public List<MemoNode> search(MemoNode algorithm, int topx, HashMap<String,String> arguments) {
ArrayList<MemoNode> preambles = algorithm.getChildrenByStringValue(
"PreAmble", -1);
ArrayList<MemoNode> patterns = algorithm.getChildrenByStringValue("Pattern",
-1);
return this.search(preambles, patterns, topx, arguments);
}
/**
* Search for nodes according to the give Preamble(s) and Pattern(s).
* (Full documentation is still on the todo list:) )
*
* @see "MemoTestServlet.java for an example of searching."
*/
public List<MemoNode> search(MemoNode preamble, MemoNode pattern,
int topx, HashMap<String,String> arguments) {
ArrayList<MemoNode> preambles = new ArrayList<MemoNode>(1);
preambles.add(preamble);
ArrayList<MemoNode> patterns = new ArrayList<MemoNode>(1);
patterns.add(pattern);
return this.search(preambles, patterns, topx, arguments);
}
/**
* Return the subgraph below this node as a JSON object suitable for the chap network
* viewer.
*
* @see <a href="http://almende.github.com/chap-links-library/network.html">CHAP network viewer</a>
* @param depth maximum depth of the subgraph to retrieve. (set to 0 for unlimited =dangerous)
*/
public String toJSONString(int depth){
//TODO: prevent loops
JSONTuple tuple = new JSONTuple();
this.toJSON(depth,tuple);
ObjectNode node = om.createObjectNode();
node.put("nodes", tuple.getNodes());
node.put("links", tuple.getLinks());
return node.toString();
}
private void toJSON(int depth,JSONTuple result) {
if (result.getSeenNodes().contains(this.uuid)) return;
result.getSeenNodes().add(this.uuid);
ObjectNode node = om.createObjectNode();
node.put("id", this.getId().toString());
node.put("title", this.getStringValue());
List<UUID> children = this.getChildIds();
if (depth-- > 0) {
for (UUID uuid : children) {
MemoNode child = new MemoNode(uuid);
child.toJSON(depth,result);
result.getLinks().add(
om.createObjectNode().
put("from", this.getId().toString()).
put("to",child.getId().toString()));
}
}
if (depth < 0 && children.size()>0){
node.put("group", "more");
}
result.getNodes().add(node);
}
}
class MemoQuery implements Comparable<MemoQuery> {
public enum Type {
Equal, Regex, Range, Any
};
static HashMap<MemoNode, MemoQuery> queryCache = new HashMap<MemoNode, MemoQuery>();
MemoNode node = null;
Type type = Type.Equal;
String value = "";
boolean hasArg=false;
java.util.regex.Pattern regex = null;
int lower = 0;
int upper = 0;
@Override
public int compareTo(MemoQuery arg0) {
return this.type.compareTo(arg0.type);
}
public static MemoQuery parseQuery(MemoNode step,HashMap<String,String> arguments) {
if (queryCache.containsKey(step)) {
MemoQuery cached = queryCache.get(step);
if (!cached.hasArg){
return cached;
};
}
MemoQuery result = new MemoQuery();
result.node = step;
String query = step.getStringValue();
if (query.equals("any")) {
result.type = MemoQuery.Type.Any;
} else if (query.startsWith("equal;")) {
result.type = MemoQuery.Type.Equal;
result.value = query.substring(6);
if (result.value.startsWith("arg(")){
result.value=arguments.get(result.value.substring(4,result.value.length()-1));
//System.out.println("Setting arg value to:"+result.value);
result.hasArg=true;
}
} else if (query.startsWith("regex;")) {
result.type = MemoQuery.Type.Regex;
result.regex = Pattern.compile(query.substring(6));
} else if (query.startsWith("range:")) {
result.type = MemoQuery.Type.Range;
String[] parts = query.substring(6).split(";");
result.lower = Integer.parseInt(parts[0]);
result.upper = Integer.parseInt(parts[0]);
} else {
result.type = MemoQuery.Type.Equal;
result.value = query;
if (result.value.startsWith("arg(")){
result.value=arguments.get(result.value.substring(4,result.value.length()-1));
result.hasArg=true;
}
}
if (!result.hasArg) queryCache.put(step, result);
return result;
}
public boolean match(MemoNode node) {
switch (this.type) {
case Equal:
//System.out.println("Checking "+node.getId()+":"+node.getStringValue()+" against "+this.value);
return node.getStringValue().equals(this.value);
case Regex:
return this.regex.matcher(node.getStringValue()).matches();
case Range:
try {
int value = Integer.parseInt(node.getStringValue());
return value >= this.lower && value <= this.upper;
} catch (Exception e) {
return false;
}
case Any:
return true;
default:
System.out.println("MemoQuery: Unknown enum value???");
return false;
}
}
}