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; } } }