package ecologylab.bigsemantics.metametadata; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import ecologylab.generic.StringBuilderBaseUtils; import ecologylab.serialization.annotations.simpl_classes; import ecologylab.serialization.annotations.simpl_map; import ecologylab.serialization.annotations.simpl_scalar; import ecologylab.serialization.types.element.IMappable; /** * A scope (map: String -> Object) with multiple ancestors. * * Objecto reduce creating unnecessary objects, this class will not create a new HashMap until * necessary (i.e. when you put something into the local scope). * * This class ignores null keys. * * @author quyin * * @param <Object> * Objecthe value type. */ public class MmdScope implements Map<String, Object>, IMappable<String> { public static final String NO_ID; private static final HashMap<String, Object> EMPTY_HASH_MAP; public static final MmdScope EMPTY_SCOPE; static { NO_ID = "NO_ID"; EMPTY_HASH_MAP = new HashMap<String, Object>(); EMPTY_SCOPE = new MmdScope("EMPObjectY") { @Override public void addAncestor(MmdScope ancestor) { // no op } @Override public Object put(String key, Object value) { // no op return value; } @Override public void putAll(Map<? extends String, ? extends Object> m) { // no op } }; } @simpl_scalar private String id; @simpl_map("element") @simpl_classes({ MetaMetadata.class, MmdGenericTypeVar.class }) private HashMap<String, Object> local; // FIXME we don't want to serialize this field in the service, but want to do it with tests. // @simpl_collection("ancestor") private List<MmdScope> ancestors; /** * Contains info about resolved generic type vars, so that the client can use them. This field is * for serialization only; the inheritance code does not use it. */ @simpl_map("resolved_generic_type_var") private HashMap<String, MmdGenericTypeVar> resolvedGenericTypeVars; public MmdScope() { this(NO_ID, new MmdScope[] {}); } public MmdScope(String id) { this(id, new MmdScope[] {}); } public MmdScope(MmdScope... ancestors) { this(NO_ID, ancestors); } public MmdScope(String id, MmdScope... ancestors) { super(); this.id = id; addAncestors(ancestors); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String key() { return id; } protected List<MmdScope> ancestors() { if (ancestors == null) { ancestors = new ArrayList<MmdScope>(); } return ancestors; } /** * @return All ancestors using DFS. This is used to prevent infinite loops with ancestors. */ protected List<MmdScope> allAncestors() { List<MmdScope> result = new ArrayList<MmdScope>(); allAncestorsHelper(result, this); return result; } private void allAncestorsHelper(List<MmdScope> result, MmdScope scope) { if (scope.ancestors != null) { for (MmdScope ancestor : scope.ancestors) { if (!result.contains(ancestor)) { result.add(ancestor); allAncestorsHelper(result, ancestor); } } } } /** * @param scope * @return If scope is an (not necessarily immediate) ancestor of this scope. */ public boolean isAncestor(MmdScope scope) { return ancestors == null ? false : allAncestors().contains(scope); } public void addAncestor(MmdScope ancestor) { if (ancestor != null && ancestor != this && !isAncestor(ancestor)) { this.ancestors().add(ancestor); } } public void addAncestors(MmdScope... ancestors) { if (ancestors != null) { for (MmdScope ancestor : ancestors) { this.addAncestor(ancestor); } } } public void removeImmediateAncestor(MmdScope ancestor) { if (ancestors != null) { ancestors.remove(ancestor); } } @Override public int size() { return local == null ? 0 : local.size(); } @Override public boolean isEmpty() { return local == null ? true : local.isEmpty(); } @Override public Set<Entry<String, Object>> entrySet() { return local == null ? EMPTY_HASH_MAP.entrySet() : local.entrySet(); } @Override public Set<String> keySet() { return local == null ? EMPTY_HASH_MAP.keySet() : local.keySet(); } @Override public Collection<Object> values() { return local == null ? EMPTY_HASH_MAP.values() : local.values(); } public <T> Collection<T> valuesOfType(Class<T> clazz) { List<T> result = new ArrayList<T>(); for (Object obj : values()) { if (obj.getClass() == clazz) { result.add((T) obj); } } return result; } /** * This will check BOTH the local scope AND ancestors. * * Ancestors will be looked up in the order of being added. */ @Override public boolean containsKey(Object key) { if (key != null) { if (local != null && local.containsKey(key)) { return true; } if (ancestors != null) { List<MmdScope> allAncestors = allAncestors(); for (MmdScope ancestor : allAncestors) { if (ancestor.containsKeyLocally(key)) { return true; } } } } return false; } /** * This will check BOObjectH the local scope AND ancestors. * * Ancestors will be looked up in the order of being added. */ @Override public boolean containsValue(Object value) { if (local != null && local.containsValue(value)) { return true; } if (ancestors != null) { List<MmdScope> allAncestors = allAncestors(); for (MmdScope ancestor : allAncestors) { if (ancestor.containsValueLocally((Object) value)) { return true; } } } return false; } /** * This will check BOObjectH the local scope AND ancestors. * * Ancestors will be looked up in the order of being added. */ @Override public Object get(Object key) { if (key != null) { if (containsKeyLocally(key)) { return local.get(key); } if (ancestors != null) { List<MmdScope> allAncestors = allAncestors(); for (MmdScope ancestor : allAncestors) { if (ancestor.containsKeyLocally(key)) { return ancestor.getLocally(key); } } } } return null; } /** * Get all values for the key from BOObjectH this scope AND ancestors. * * Ancestors will be looked up in the order of being added. * * @param key * @return */ public List<Object> getAll(Object key) { List<Object> result = new ArrayList<Object>(); if (key != null) { if (local != null && local.containsKey(key)) { result.add(local.get(key)); } if (ancestors != null) { List<MmdScope> allAncestors = allAncestors(); for (MmdScope ancestor : allAncestors) { if (ancestor.containsKeyLocally(key)) { Object value = ancestor.getLocally(key); result.add(value); } } } } return result; } @Override public Object put(String key, Object value) { if (key != null) { if (local == null) { local = new HashMap<String, Object>(); } return local.put(key, value); } return null; } /** * Only put value into the scope when it is not null. * * @param key * @param value */ public Object putIfValueNotNull(String key, Object value) { if (key != null && value != null) { return put(key, value); } return null; } @Override public void putAll(Map<? extends String, ? extends Object> m) { if (local == null) { local = new HashMap<String, Object>(); } local.putAll(m); } @Override public Object remove(Object key) { return local == null ? null : key == null ? null : local.remove(key); } @Override public void clear() { if (local != null) { local.clear(); } } public boolean containsKeyLocally(Object key) { return local == null ? false : key == null ? false : local.containsKey(key); } public boolean containsValueLocally(Object value) { return local == null ? false : local.containsValue(value); } /** * Get the value of the given key only from this scope. * * @param key * @return */ public Object getLocally(Object key) { return local == null ? null : key == null ? null : local.get(key); } @Override public String toString() { StringBuilder sb = StringBuilderBaseUtils.acquire(); Set<Object> visited = new HashSet<Object>(); toStringHelper(sb, "", visited); String result = sb.toString(); StringBuilderBaseUtils.release(sb); return result; } private void toStringHelper(StringBuilder buf, String indent, Set<Object> visited) { buf.append(getClass().getSimpleName()); buf.append(".").append(id == null ? NO_ID : id); buf.append(": [").append(size()).append("]"); buf.append(local == null ? "{}" : local); if (ancestors != null && ancestors.size() > 0) { for (MmdScope ancestor : ancestors) { StringBuilder ancestorStr = StringBuilderBaseUtils.acquire(); ancestorStr.append("\n").append(indent).append(" -> "); if (visited.contains(ancestor)) { ancestorStr.append("(Ref: "); ancestorStr.append(ancestor.getClass().getSimpleName()); ancestorStr.append(".").append(ancestor.getId()).append(")"); } else { visited.add(ancestor); ancestor.toStringHelper(ancestorStr, indent + " ", visited); } buf.append(ancestorStr); StringBuilderBaseUtils.release(ancestorStr); } } } public void reset() { id = null; local = null; ancestors = null; } public void resolveGenericTypeVars() { HashMap<String, MmdGenericTypeVar> result = new HashMap<String, MmdGenericTypeVar>(); if (local != null) { for (MmdGenericTypeVar gtv : valuesOfType(MmdGenericTypeVar.class)) { result.put(gtv.getName(), gtv); } } if (ancestors != null) { for (MmdScope ancestor : ancestors) { for (MmdGenericTypeVar gtv : ancestor.valuesOfType(MmdGenericTypeVar.class)) { result.put(gtv.getName(), gtv); } } } resolvedGenericTypeVars = result; } /** * @param args */ public static void main(String[] args) { // inheritance relation: // s4 -> s2 -----------> s1 // \---> s3(nolocal) -/ MmdScope s1 = new MmdScope("s1"); s1.put("one", 1); s1.put("two", 2); MmdScope s2 = new MmdScope("s2", s1); s2.put("three", 3); MmdScope s3 = new MmdScope("s3", s1); MmdScope s4 = new MmdScope("s4", s2, s3); s4.put("five", 5); System.out.println(s4); System.out.println(s4.get("five")); System.out.println(s4.get("two")); } }