package org.freeplane.plugin.script.proxy; import groovy.lang.Closure; import java.net.URI; import java.util.AbstractList; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.freeplane.features.filter.condition.ICondition; import org.freeplane.features.format.FormatController; import org.freeplane.features.format.FormattedDate; import org.freeplane.features.format.FormattedNumber; import org.freeplane.features.format.IFormattedObject; import org.freeplane.features.map.NodeModel; import org.freeplane.features.text.TextController; import org.freeplane.features.text.mindmapmode.MTextController; import org.freeplane.plugin.script.ScriptContext; import org.freeplane.plugin.script.proxy.Proxy.Node; public class ProxyUtils { static List<Node> createNodeList(final List<NodeModel> list, final ScriptContext scriptContext) { return new AbstractList<Node>() { final private List<NodeModel> nodeModels = list; @Override public Node get(final int index) { final NodeModel nodeModel = nodeModels.get(index); return new NodeProxy(nodeModel, scriptContext); } @Override public int size() { return nodeModels.size(); } }; } static List<Node> find(final ICondition condition, final NodeModel node, final ScriptContext scriptContext) { return ProxyUtils.createNodeList(ProxyUtils.findImpl(condition, node, true), scriptContext); } static List<Node> findAll(final NodeModel node, final ScriptContext scriptContext, boolean breadthFirst) { return ProxyUtils.createNodeList(ProxyUtils.findImpl(null, node, breadthFirst), scriptContext); } static List<Node> find(final Closure<Boolean> closure, final NodeModel node, final ScriptContext scriptContext) { return ProxyUtils.find(createCondition(closure, scriptContext), node, scriptContext); } static ICondition createCondition(final Closure<Boolean> closure, final ScriptContext scriptContext) { final ICondition condition = new ICondition() { public boolean checkNode(final NodeModel node) { try { final Boolean result = closure .call(new Object[] { new NodeProxy(node, scriptContext) }); if (result == null) { throw new RuntimeException("find(): closure returned null instead of boolean/Boolean"); } return result; } catch (final ClassCastException e) { throw new RuntimeException("find(): closure returned " + e.getMessage() + " instead of boolean/Boolean"); } } }; return condition; } /** finds from any node downwards. * @param condition if null every node will match. */ @SuppressWarnings("unchecked") private static List<NodeModel> findImpl(final ICondition condition, final NodeModel node, boolean breadthFirst) { final boolean nodeMatches = condition == null || condition.checkNode(node); // a shortcut for non-matching leaves if (node.isLeaf() && !nodeMatches) { return Collections.EMPTY_LIST; } final List<NodeModel> matches = new ArrayList<NodeModel>(); if (nodeMatches && breadthFirst) { matches.add(node); } final Enumeration<NodeModel> children = node.children(); while (children.hasMoreElements()) { final NodeModel child = children.nextElement(); matches.addAll(ProxyUtils.findImpl(condition, child, breadthFirst)); } if (nodeMatches && !breadthFirst) { matches.add(node); } return matches; } public static List<Proxy.Node> createListOfChildren(final NodeModel nodeModel, final ScriptContext scriptContext) { return new ArrayList<Proxy.Node>(new AbstractList<Proxy.Node>() { @Override public Proxy.Node get(final int index) { final NodeModel child = (NodeModel) nodeModel.getChildAt(index); return new NodeProxy(child, scriptContext); } @Override public int size() { return nodeModel.getChildCount(); } }); } /** this method is null-safe, i.e. value may be null and the result is not null. */ public static Convertible attributeValueToConvertible(final NodeModel nodeModel, final ScriptContext scriptContext, Object value) { if (value instanceof IFormattedObject) value = ((IFormattedObject) value).getObject(); if (value instanceof Number) return new ConvertibleNumber((Number) value); else if (value instanceof Date) return new ConvertibleDate((Date) value); return new ConvertibleText(nodeModel, scriptContext, value == null ? null : value.toString()); } public static Convertible nodeModelToConvertible(final NodeModel nodeModel, final ScriptContext scriptContext) { Object value = nodeModel.getUserObject(); if (value instanceof IFormattedObject) value = ((IFormattedObject) value).getObject(); if (value instanceof Number) return new ConvertibleNumber((Number) value); else if (value instanceof Date) return new ConvertibleDate((Date) value); return new ConvertibleNodeText(nodeModel, scriptContext); } public static <T> List<T> createList(final Collection<T> collection) { return new AbstractList<T>() { private int lastIndex; private Iterator<T> iterator; @Override public T get(int index) { if(index >= size()) throw new NoSuchElementException(); if(index == 0) return collection.iterator().next(); if(iterator == null || index <= lastIndex){ lastIndex = -1; iterator = collection.iterator(); } try{ T object; for(object = null; lastIndex < index; lastIndex++) object = iterator.next(); return object; } catch (ConcurrentModificationException e) { iterator = null; return get(index); } } @Override public int indexOf(Object o) { final Iterator<T> it = iterator(); int i = -1; while(it.hasNext()){ i++; final T next = it.next(); if(o ==next || o != null && o.equals(next)) return i; } return -1; } @Override public int lastIndexOf(Object o) { final Iterator<T> it = iterator(); int i = -1; int result = -1; while(it.hasNext()){ i++; final T next = it.next(); if(o ==next || o != null && o.equals(next)) result = i; } return result; } @Override public Iterator<T> iterator() { return collection.iterator(); } @Override public int size() { return collection.size(); } }; } /** used for node core texts and for attribute values. Note that it would lead to an error on reopening of a map * if we would allow to assign GStrings here. So all unknown stuff is cast to String. */ static Object transformObject(Object objectToTransform, String pattern) { final Object object = createFormattedObjectIfPossible(objectToTransform, pattern); if (object instanceof IFormattedObject) return object; else if (object instanceof Number) return new FormattedNumber((Number) object); else if (object instanceof Date) return createDefaultFormattedDate((Date) object); else if (object instanceof Calendar) return createDefaultFormattedDate(((Calendar) object).getTime()); else if (object instanceof URI) return object; else return Convertible.toString(object); } private static Object createFormattedObjectIfPossible(Object object, String pattern) { if (object instanceof String) object = ((MTextController) TextController.getController()).guessObjectOrURI(object, pattern); else if (pattern != null) object = FormatController.format(object, pattern); return object; } static FormattedDate createDefaultFormattedDate(final Date date) { return FormattedDate.createDefaultFormattedDate(date.getTime(), IFormattedObject.TYPE_DATE); } static FormattedDate createDefaultFormattedDateTime(final Date date) { return FormattedDate.createDefaultFormattedDate(date.getTime(), IFormattedObject.TYPE_DATETIME); } }