/* * ome.util.ContextFilter * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * modified (hierarchical) visitor pattern. See * http://c2.com/cgi/wiki?HierarchicalVisitorPattern for more information. (A * better name may be "contextual visitor pattern" for graph traversing.) * * The modifications to Visitor make use of a model graph (here: the ome * generated model classes) implementing Filterable. As documented in * Filterable, model objects are responsible for calling * <code>filter.filter(someField)</code> for all fields <b>and setting the * value of that field to the return value of the method call</b>. * * The Filter itself is responsible for returning a compatible object and * (optionally) stepping into objects and keeping up with context. * * Note: This class is not thread-safe. * * template method to filter domain objects. The standard idiom is: <code> * if (m != null && hasntSeen(m)){ * enter(m); // Provides context * addSeen(m); // Prevents looping * m.acceptFilter(this); // Visits all fields * exit(m); // Remove from context * } * * </code> * * * Implementation notes: - nulls are already "seen" */ public class ContextFilter implements Filter { private static Logger log = LoggerFactory.getLogger(ContextFilter.class); private Object dummy; protected Map _cache = new IdentityHashMap(); protected LinkedList _context = new LinkedList(); protected void beforeFilter(String fieldId, Object o) { enter(o); addSeen(o); } protected void doFilter(String fieldId, Object o) { // nothing } protected void doFilter(String fieldId, Filterable f) { f.acceptFilter(this); } protected void doFilter(String fieldId, Collection c) { List copy = new ArrayList(); for (Iterator iter = c.iterator(); iter.hasNext();) { Object item = iter.next(); Object result = filter(fieldId, item); copy.add(result); } try { c.clear(); c.addAll(copy); } catch (UnsupportedOperationException uoe) { // This should only happen for the primitive // array types like Namespace.keywords. // Do nothing. } } protected void afterFilter(String fieldId, Object o) { exit(o); } public Filterable filter(String fieldId, Filterable f) { if (f != null && hasntSeen(f)) { beforeFilter(fieldId, f); doFilter(fieldId, f); afterFilter(fieldId, f); } return f;// null; } /** * iterates over the contents of the collection and filters each. Adds * itself to the context. This is somewhat dangerous. */ public Collection filter(String fieldId, Collection c) { if (c != null && hasntSeen(c)) { beforeFilter(fieldId, c); doFilter(fieldId, c); afterFilter(fieldId, c); } return c; } /** * filters both the key and value sets of the map. Adds itself to the * context. Somewhat dangerous. */ public Map filter(String fieldId, Map m) { if (m != null && hasntSeen(m)) { beforeFilter(fieldId, m); Map add = new HashMap(); for (Iterator iter = m.entrySet().iterator(); iter.hasNext();) { Map.Entry _entry = (Map.Entry) iter.next(); Entry entry = new Entry(_entry.getKey(), _entry.getValue()); Entry result = filter(fieldId, entry); if (null == result) { iter.remove(); } else if (result.key != entry.key || result.value != entry.value) { iter.remove(); add.put(result.key, result.value); } } m.putAll(add); afterFilter(fieldId, m); } return m; } /** used when type is unknown. this is possibly omittable with generics */ public Object filter(String fieldId, Object o) { Object result; if (o instanceof Enum) { result = o; } else if (o instanceof ome.model.internal.Primitive) { result = o; } else if (o instanceof Filterable) { result = filter(fieldId, (Filterable) o); } else if (o instanceof Collection) { result = filter(fieldId, (Collection) o); } else if (o instanceof Map) { result = filter(fieldId, (Map) o); } else { beforeFilter(fieldId, o); doFilter(fieldId, o); afterFilter(fieldId, o); result = o; } return result; } /** doesn't return a new entry. only changes key and value */ protected Entry filter(String fieldId, Entry entry) { if (entry != null && hasntSeen(entry)) { addSeen(entry); enter(entry); Object key = filter(fieldId, entry.key); Object value = filter(fieldId, entry.value); exit(entry); entry.key = key; entry.value = value; } return entry; } // ~ CONTEXT METHODS // ========================================================================= public boolean enter(Object o) { push(o); return true; } public boolean exit(Object o) { pop(o); return true; } public Object currentContext() { return _context.size() > 0 ? _context.getLast() : null; } public Object previousContext(int index) { if (index < 0 || index >= _context.size()) { return null; } return _context.get(_context.size() - index - 1); } protected void push(Object o) { _context.addLast(o); } // beware: context is being changed during filtering. Eek! FIXME protected void pop(Object o) { Object last = _context.removeLast(); if (o != last) { throw new IllegalStateException( "Context is invalid. Trying to remove Object " + o + " and removed Object " + last); } } protected void addSeen(Object o) { _cache.put(o, dummy); } protected boolean hasntSeen(Object o) { return !_cache.containsKey(o); } /* * simple Entry type for dealing with maps. */ class Entry { Object key; Object value; @Override public String toString() { return "(" + key + ":" + value + ")"; } Entry(Object key, Object value) { this.key = key; this.value = value; } } }