/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.editor.support.yaml.ast;
import static org.springframework.ide.eclipse.editor.support.yaml.ast.NodeUtil.contains;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.ide.eclipse.editor.support.util.Collector;
import org.springframework.ide.eclipse.editor.support.util.IRequestor;
import org.springframework.ide.eclipse.editor.support.util.RememberLast;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.RootRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.SeqRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.TupleKeyRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef.TupleValueRef;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.SequenceNode;
/**
* Represents a parsed yml file.
*
* @author Kris De Volder
*/
public class YamlFileAST {
private static final List<NodeRef<?>> NO_CHILDREN = Collections.emptyList();
private List<Node> nodes;
public YamlFileAST(Iterable<Node> iter) {
nodes = new ArrayList<Node>();
for (Node node : iter) {
nodes.add(node);
}
}
public List<NodeRef<?>> findPath(int offset) {
Collector<NodeRef<?>> path = new Collector<NodeRef<?>>();
findPath(offset, path);
return path.get();
}
/**
* Find 'smallest' ast node that contains offset. The pathRequestor will
* be called as the search progresses down the AST on all nodes on the
* path to the smallest node. If no node in the tree contains the offset
* the requestor will not be called at all.
*/
public void findPath(int offset, IRequestor<NodeRef<?>> pathRequestor) {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
if (contains(node, offset)) {
pathRequestor.accept(new RootRef(this, i) );
findPath(node, offset, pathRequestor);
return;
}
}
}
/**
* Find smallest node that is a child of 'n' that contains 'offset'. Each visited
* node containing the offset, from the down to the found node are
* passed to the pathRequestor.
*/
private void findPath(Node n, int offset, IRequestor<NodeRef<?>> pathRequestor) {
//TODO: avoid lots of garbage production by not using 'getChildren'
// but inling getChildren (i.e a switch-case that visits
// the children without putting them into temporary collections.)
// By doing this it should be possible to avoid creaing lots of temporary
// array lists and NodeRef objects and only create NodeRef objects for
// the nodes we actually care about (i.e. the ones on the path).
List<NodeRef<?>> children = getChildren(n);
for (int i = 0; i < children.size(); i++) {
NodeRef<?> c = children.get(i);
if (contains(c.get(), offset)) {
pathRequestor.accept(c);
findPath(c.get(), offset, pathRequestor);
return;
}
}
}
public static List<NodeRef<?>> getChildren(Node n) {
switch (n.getNodeId()) {
case scalar:
return NO_CHILDREN;
case sequence:
return getChildren((SequenceNode)n);
case mapping:
return getChildren((MappingNode)n);
case anchor:
//TODO: is this right? maybe we should visit down into 'realnode'
// but do we then potentially visit the same node twice?
return NO_CHILDREN;
}
return null;
}
public List<Node> getNodes() {
return nodes;
}
private static List<NodeRef<?>> getChildren(SequenceNode seq) {
int nodes = seq.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<NodeRef<?>>(nodes);
for (int i = 0; i < nodes; i++) {
children.add(new SeqRef(seq, i));
}
return children;
}
private static List<NodeRef<?>> getChildren(MappingNode map) {
int entries = map.getValue().size();
ArrayList<NodeRef<?>> children = new ArrayList<NodeRef<?>>(entries*2);
for (int i = 0; i < entries; i++) {
children.add(new TupleKeyRef(map, i));
children.add(new TupleValueRef(map, i));
}
return children;
}
public NodeRef<?> findNodeRef(int offset) {
RememberLast<NodeRef<?>> lastNode = new RememberLast<NodeRef<?>>();
findPath(offset, lastNode);
return lastNode.get();
}
public Node findNode(int offset) {
NodeRef<?> ref = findNodeRef(offset);
if (ref!=null) {
return ref.get();
}
return null;
}
public Node get(int index) {
return nodes.get(index);
}
public void put(int index, Node value) {
nodes.set(index, value);
}
}