package com.mobilesorcery.sdk.html5.debug.hotreplace; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import org.eclipse.wst.jsdt.core.dom.ASTNode; import com.mobilesorcery.sdk.core.IFilter; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.html5.debug.IRedefinable; import com.mobilesorcery.sdk.html5.debug.IRedefiner; import com.mobilesorcery.sdk.html5.debug.ReloadVirtualMachine; import com.mobilesorcery.sdk.html5.debug.rewrite.ISourceSupport; public abstract class AbstractRedefinable implements IRedefinable { private IRedefinable parent; private List<IRedefinable> children = new ArrayList<IRedefinable>(); protected ISourceSupport source; private HashMap<String, IRedefinable> childrenByKey; protected AbstractRedefinable(IRedefinable parent, ISourceSupport source) { this.parent = parent; this.source = source; if (parent != null) { parent.addChild(this); } } protected void setSource(ISourceSupport source) { this.source = source; } /** * Returns the <b>instrumented</b> source of this node. * @param node * @return */ protected String getInstrumentedSource(IFilter<String> features, ASTNode node) { return source.getInstrumentedSource(features, node); } protected String getSourceRange(int start, int end) { return source.getSource().substring(start, end); } protected String getSource() { return source.getSource(); } public void addChild(IRedefinable child) { children.add(child); // Just to trigger reindexing. childrenByKey = null; } /** * Replaces a child with another. This method * uses the key of the replacement to find * the child to replace. * If no such child exists, the replacement * is added. * @param replacement */ public void replaceChild(IRedefinable replacement) { if (replacement == null) { return; } boolean wasReplaced = false; for (int i = 0; i < children.size(); i++) { IRedefinable child = children.get(i); if (replacement.key().equals(child.key())) { children.set(i, replacement); wasReplaced = true; } } if (!wasReplaced) { addChild(replacement); } childrenByKey = null; } public IRedefinable getChild(String key) { if (childrenByKey == null) { childrenByKey = new HashMap<String, IRedefinable>(); for (IRedefinable child : children) { childrenByKey.put(child.key(), child); } } return childrenByKey.get(key); } /** * The default implementation redefines all children as well, * delegating to the {@link #redefineAdded(IRedefinable, IRedefiner)} * method if the replacement has a child not present in this * {@link IRedefinable}. */ @Override public void redefine(IRedefinable replacement, IRedefiner redefiner) { if (replacement != null && !Util.equals(replacement.key(), key())) { throw new IllegalArgumentException("Internal error: key mismatch"); } if (redefiner != null) { redefiner.changed(this, replacement); } HashSet<String> redefined = new HashSet(); for (IRedefinable originalChild : getChildren()) { String key = originalChild.key(); IRedefinable replacementChild = replacement.getChild(key); if (replacementChild != null) { replacementChild.redefine(originalChild, redefiner); redefined.add(key); } else { redefiner.deleted(originalChild); } } for (IRedefinable replaceChild : replacement.getChildren()) { if (!redefined.contains(replaceChild.key())) { redefiner.added(replaceChild); } } } @Override public List<IRedefinable> getChildren() { return children; } @Override public IRedefinable getParent() { return parent; } public <T extends IRedefinable> T getParent(Class<T> parentType) { IRedefinable parent = this.parent; while (parent != null) { if (parentType.isAssignableFrom(parent.getClass())) { return (T) parent; } parent = parent.getParent(); } return null; } /** * Returns the ancestors of this {@link IRedefinable}, * as a list. Lower index = closer ancestor. * @return */ protected List<IRedefinable> getAncestors() { ArrayList<IRedefinable> result = new ArrayList<IRedefinable>(); IRedefinable parent = this.parent; while (parent != null) { result.add(parent); parent = parent.getParent(); } return result; } public String constructKey(String subkey) { String parentKey = parent == null ? "" : parent.key(); return parentKey + "/" + subkey; } }