/*******************************************************************************
* Copyright (c) 2015, 2016 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.path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeRef;
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.TupleValueRef;
import org.springframework.ide.eclipse.editor.support.yaml.ast.NodeUtil;
import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPathSegment.YamlPathSegmentType;
/**
* @author Kris De Volder
*/
public class YamlPath {
public static final YamlPath EMPTY = new YamlPath();
private final YamlPathSegment[] segments;
public YamlPath(List<YamlPathSegment> segments) {
this.segments = segments.toArray(new YamlPathSegment[segments.size()]);
}
public YamlPath() {
this.segments = new YamlPathSegment[0];
}
public YamlPath(YamlPathSegment... segments) {
this.segments = segments;
}
public String toPropString() {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (YamlPathSegment s : segments) {
if (first) {
buf.append(s.toPropString());
} else {
buf.append(s.toNavString());
}
first = false;
}
return buf.toString();
}
public String toNavString() {
StringBuilder buf = new StringBuilder();
for (YamlPathSegment s : segments) {
buf.append(s.toNavString());
}
return buf.toString();
}
public YamlPathSegment[] getSegments() {
return segments;
}
/**
* Parse a YamlPath from a dotted property name. The segments are obtained
* by spliting the name at each dot.
*/
public static YamlPath fromProperty(String propName) {
ArrayList<YamlPathSegment> segments = new ArrayList<YamlPathSegment>();
for (String s : propName.split("\\.")) {
segments.add(YamlPathSegment.valueAt(s));
}
return new YamlPath(segments);
}
/**
* Create a YamlPath with a single segment (i.e. like 'fromProperty', but does
* not parse '.' as segment separators.
*/
public static YamlPath fromSimpleProperty(String name) {
return new YamlPath(YamlPathSegment.valueAt(name));
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("YamlPath(");
boolean first = true;
for (YamlPathSegment s : segments) {
if (!first) {
buf.append(", ");
}
buf.append(s);
first = false;
}
buf.append(")");
return buf.toString();
}
public int size() {
return segments.length;
}
public YamlPathSegment getSegment(int segment) {
if (segment>=0 && segment<segments.length) {
return segments[segment];
}
return null;
}
public YamlPath append(YamlPathSegment s) {
YamlPathSegment[] newPath = Arrays.copyOf(segments, segments.length+1);
newPath[segments.length] = s;
return new YamlPath(newPath);
}
public <T extends YamlNavigable<T>> T traverse(T startNode) {
try {
T node = startNode;
for (YamlPathSegment s : segments) {
if (node==null) {
return null;
}
node = node.traverse(s);
}
return node;
} catch (Exception e) {
return null;
}
}
public YamlPath dropFirst(int dropCount) {
if (dropCount>=size()) {
return EMPTY;
}
if (dropCount==0) {
return this;
}
YamlPathSegment[] newPath = new YamlPathSegment[segments.length-dropCount];
for (int i = 0; i < newPath.length; i++) {
newPath[i] = segments[i+dropCount];
}
return new YamlPath(newPath);
}
public YamlPath dropLast() {
return dropLast(1);
}
public YamlPath dropLast(int dropCount) {
if (dropCount>=size()) {
return EMPTY;
}
if (dropCount==0) {
return this;
}
YamlPathSegment[] newPath = new YamlPathSegment[segments.length-dropCount];
for (int i = 0; i < newPath.length; i++) {
newPath[i] = segments[i];
}
return new YamlPath(newPath);
}
public boolean isEmpty() {
return segments.length==0;
}
public YamlPath tail() {
return dropFirst(1);
}
/**
* Attempt to convert a path represented as a list of {@link NodeRef} into YamlPath.
* <p>
* Note that not all AST path can be converted into a YamlPath. Some paths in AST
* do not have a corresponding YamlPath. For such cases this method may return null.
*/
public static YamlPath fromASTPath(List<NodeRef<?>> path) {
List<YamlPathSegment> segments = new ArrayList<YamlPathSegment>(path.size());
for (NodeRef<?> nodeRef : path) {
switch (nodeRef.getKind()) {
case ROOT:
RootRef rref = (RootRef) nodeRef;
segments.add(YamlPathSegment.valueAt(rref.getIndex()));
break;
case KEY: {
String key = NodeUtil.asScalar(nodeRef.get());
if (key==null) {
return null;
} else {
segments.add(YamlPathSegment.keyAt(key));
} }
break;
case VAL: {
TupleValueRef vref = (TupleValueRef) nodeRef;
String key = NodeUtil.asScalar(vref.getTuple().getKeyNode());
if (key==null) {
return null;
} else {
segments.add(YamlPathSegment.valueAt(key));
} }
break;
case SEQ:
SeqRef sref = ((SeqRef)nodeRef);
segments.add(YamlPathSegment.valueAt(sref.getIndex()));
break;
default:
return null;
}
}
return new YamlPath(segments);
}
public YamlPathSegment getLastSegment() {
if (!isEmpty()) {
return segments[segments.length-1];
}
return null;
}
/**
* Attempt to interpret last segment of path as a bean property name.
* @return The name of the property or null if not applicable.
*/
public String getBeanPropertyName() {
if (!isEmpty()) {
YamlPathSegment lastSegment = getLastSegment();
YamlPathSegmentType kind = lastSegment.getType();
if (kind==YamlPathSegmentType.KEY_AT_KEY || kind==YamlPathSegmentType.VAL_AT_KEY) {
return lastSegment.toPropString();
}
}
return null;
}
public boolean pointsAtKey() {
YamlPathSegment s = getLastSegment();
return s!=null && s.getType()==YamlPathSegmentType.KEY_AT_KEY;
}
public boolean pointsAtValue() {
YamlPathSegment s = getLastSegment();
if (s!=null) {
YamlPathSegmentType type = s.getType();
return type==YamlPathSegmentType.VAL_AT_KEY || type==YamlPathSegmentType.VAL_AT_INDEX;
}
return false;
}
public YamlPath commonPrefix(YamlPath other) {
ArrayList<YamlPathSegment> common = new ArrayList<>(this.size());
for (int i = 0; i < this.size(); i++) {
YamlPathSegment s = this.getSegment(i);
if (s.equals(other.getSegment(i))) {
common.add(s);
}
}
return new YamlPath(common);
}
}