diff --git a/vscode-extensions/commons/language-server-commons/pom.xml b/vscode-extensions/commons/language-server-commons/pom.xml
index 70623cdea..6fe6e7d2a 100644
--- a/vscode-extensions/commons/language-server-commons/pom.xml
+++ b/vscode-extensions/commons/language-server-commons/pom.xml
@@ -31,6 +31,11 @@
+
+ org.springframework.ide.vscode
+ util-commons
+ ${project.version}
+
io.typefox.lsapi
@@ -74,6 +79,4 @@
${jackson-2-version}
-
-
\ No newline at end of file
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IDocument.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IDocument.java
new file mode 100644
index 000000000..eadcc2578
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IDocument.java
@@ -0,0 +1,7 @@
+package org.springframework.ide.vscode.commons.reconcile;
+
+public interface IDocument {
+
+ String get();
+
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IProblemCollector.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IProblemCollector.java
new file mode 100644
index 000000000..773f72b6d
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IProblemCollector.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2014-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.vscode.commons.reconcile;
+
+public interface IProblemCollector {
+
+ void beginCollecting();
+ void endCollecting();
+ void accept(ReconcileProblem problem);
+
+ /**
+ * Problem collector that simply ignores/discards anything passed to it.
+ */
+ IProblemCollector NULL = new IProblemCollector() {
+ public void beginCollecting() {
+ }
+ public void endCollecting() {
+ }
+ public void accept(ReconcileProblem problem) {
+ }
+ };
+}
\ No newline at end of file
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IReconcileEngine.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IReconcileEngine.java
new file mode 100644
index 000000000..0d57ffabf
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/IReconcileEngine.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * 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.vscode.commons.reconcile;
+
+public interface IReconcileEngine {
+ public void reconcile(IDocument doc, IProblemCollector problemCollector);
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemSeverity.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemSeverity.java
new file mode 100644
index 000000000..d215237eb
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemSeverity.java
@@ -0,0 +1,12 @@
+package org.springframework.ide.vscode.commons.reconcile;
+
+/**
+ * @author Kris De Volder
+ */
+public enum ProblemSeverity {
+
+ IGNORE,
+ WARNING,
+ ERROR;
+
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemType.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemType.java
new file mode 100644
index 000000000..45a6fdbe8
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ProblemType.java
@@ -0,0 +1,18 @@
+package org.springframework.ide.vscode.commons.reconcile;
+
+/**
+ * Besides the methods below, the only hard requirement for a 'problem type' is
+ * that it is a unique object that is not 'equals' to any other object.
+ *
+ * It is probably nice if you implement a good toString however.
+ *
+ * A good way to implement a discrete set of problemType objects is as an enum
+ * that implements this interace.
+ *
+ * @author Kris De Volder
+ */
+public interface ProblemType {
+ ProblemSeverity getDefaultSeverity();
+ String toString();
+ String getCode();
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblem.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblem.java
new file mode 100644
index 000000000..ee3d61b6e
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblem.java
@@ -0,0 +1,15 @@
+package org.springframework.ide.vscode.commons.reconcile;
+
+/**
+ * Minamal interface that objects representing a reconciler problem must
+ * implement.
+ *
+ * @author Kris De Volder
+ */
+public interface ReconcileProblem {
+ ProblemType getType();
+ String getMessage();
+ int getOffset();
+ int getLength();
+ String getCode();
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblemImpl.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblemImpl.java
new file mode 100644
index 000000000..f83d4468b
--- /dev/null
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/commons/reconcile/ReconcileProblemImpl.java
@@ -0,0 +1,48 @@
+package org.springframework.ide.vscode.commons.reconcile;
+
+/**
+ * An implementation of {@link ReconcileProblem} that is just a simple data object.
+ *
+ * @author Kris De Volder
+ */
+public class ReconcileProblemImpl implements ReconcileProblem {
+
+ final private ProblemType type;
+ final private String msg;
+ final private int offset;
+ final private int len;
+
+ public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) {
+ super();
+ this.type = type;
+ this.msg = msg;
+ this.offset = offset;
+ this.len = len;
+ }
+
+ @Override
+ public ProblemType getType() {
+ return type;
+ }
+
+ @Override
+ public String getMessage() {
+ return msg;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+
+ @Override
+ public int getLength() {
+ return len;
+ }
+
+ @Override
+ public String getCode() {
+ return getType().getCode();
+ }
+
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/SimpleTextDocumentService.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/SimpleTextDocumentService.java
index bb47c3946..ec50435b7 100644
--- a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/SimpleTextDocumentService.java
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/SimpleTextDocumentService.java
@@ -260,7 +260,7 @@ public class SimpleTextDocumentService implements TextDocumentService {
}
public void publishDiagnostics(TextDocument doc, List diagnostics) {
- if (diagnostics!=null && !diagnostics.isEmpty()) {
+ if (diagnostics!=null) {
PublishDiagnosticsParamsImpl params = new PublishDiagnosticsParamsImpl();
params.setUri(doc.getUri());
params.setDiagnostics(diagnostics);
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/TextDocument.java b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/TextDocument.java
index 3fbf5bfa7..50f931d1e 100644
--- a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/TextDocument.java
+++ b/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/TextDocument.java
@@ -1,9 +1,22 @@
package org.springframework.ide.vscode.util;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.ide.vscode.commons.reconcile.IDocument;
+
+import io.typefox.lsapi.Position;
+import io.typefox.lsapi.PositionImpl;
import io.typefox.lsapi.Range;
+import io.typefox.lsapi.RangeImpl;
import io.typefox.lsapi.TextDocumentContentChangeEvent;
-public class TextDocument {
+public class TextDocument implements IDocument {
+
+ Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r");
+ private int[] _lineStarts;
private final String uri;
private String text = "";
@@ -16,23 +29,96 @@ public class TextDocument {
return uri;
}
- public String getText() {
+ public String get() {
+ return getText();
+ }
+
+ public synchronized String getText() {
return text;
}
- public void setText(String text) {
+ public synchronized void setText(String text) {
this.text = text;
+ this._lineStarts = null;
}
public void apply(TextDocumentContentChangeEvent change) {
Range rng = change.getRange();
if (rng==null) {
//full sync mode
- this.text = change.getText();
+ setText(change.getText());
} else {
//incremental sync mode
throw new IllegalStateException("Incremental sync not yet implemented");
}
}
+ /**
+ * Convert a simple offset+length pair into a vscode range. This is a method on
+ * TextDocument because it requires splitting document into lines to determine
+ * line numbers from offsets.
+ */
+ public RangeImpl toRange(int offset, int length) {
+ int end = offset + length;
+ RangeImpl range = new RangeImpl();
+ range.setStart(toPosition(offset));
+ range.setEnd(toPosition(end));
+ return range;
+ }
+
+ /**
+ * Determine the line-number a given offset (i.e. what line is the offset inside of?)
+ */
+ private int lineNumber(int offset) {
+ int[] lineStarts = lineStarts();
+ // TODO Should really use binary search here for speed
+ int lineNumber = 0;
+ for (int i = 0; i < lineStarts.length; i++) {
+ if (lineStarts[i]<=offset) {
+ lineNumber = i;
+ } else {
+ return lineNumber;
+ }
+ }
+ return lineNumber;
+ }
+
+
+ public PositionImpl toPosition(int offset) {
+ int line = lineNumber(offset);
+ int startOfLine = startOfLine(line);
+ int column = offset - startOfLine;
+ PositionImpl pos = new PositionImpl();
+ pos.setCharacter(column);
+ pos.setLine(line);
+ return pos;
+ }
+
+ private int startOfLine(int line) {
+ return lineStarts()[line];
+ }
+
+ private synchronized int[] lineStarts() {
+ if (_lineStarts==null) {
+ _lineStarts = parseLines();
+ }
+ return _lineStarts;
+ }
+
+ private int[] parseLines() {
+ List lineStarts = new ArrayList<>();
+ lineStarts.add(0);
+ Matcher matcher = NEWLINE.matcher(getText());
+ int pos = 0;
+ while (matcher.find(pos)) {
+ lineStarts.add(pos = matcher.end());
+ }
+ int[] array = new int[lineStarts.size()];
+ for (int i = 0; i < array.length; i++) {
+ array[i] = lineStarts.get(i);
+ }
+ return array;
+ }
+
+
}
diff --git a/vscode-extensions/commons/language-server-test-harness/.classpath b/vscode-extensions/commons/language-server-test-harness/.classpath
new file mode 100644
index 000000000..fae1a2b37
--- /dev/null
+++ b/vscode-extensions/commons/language-server-test-harness/.classpath
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vscode-extensions/commons/language-server-test-harness/.project b/vscode-extensions/commons/language-server-test-harness/.project
new file mode 100644
index 000000000..a03ca7c45
--- /dev/null
+++ b/vscode-extensions/commons/language-server-test-harness/.project
@@ -0,0 +1,23 @@
+
+
+ language-server-test-harness
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..714351aec
--- /dev/null
+++ b/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 000000000..f897a7f1c
--- /dev/null
+++ b/vscode-extensions/commons/language-server-test-harness/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/vscode-extensions/commons/language-server-test-harness/pom.xml b/vscode-extensions/commons/language-server-test-harness/pom.xml
new file mode 100644
index 000000000..d10989b19
--- /dev/null
+++ b/vscode-extensions/commons/language-server-test-harness/pom.xml
@@ -0,0 +1,33 @@
+
+ 4.0.0
+ language-server-test-harness
+ language-server-test-harness
+ Test harness for testing language server functionaltity implemented in Java
+
+
+ org.springframework.ide.vscode
+ commons-parent
+ 0.0.1-SNAPSHOT
+ ../pom.xml
+
+
+
+
+ org.springframework.ide.vscode
+ language-server-commons
+ ${project.version}
+
+
+ junit
+ junit
+ ${junit-version}
+
+
+ org.assertj
+ assertj-core
+ ${assertj-version}
+
+
+
+
\ No newline at end of file
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/Editor.java
similarity index 92%
rename from vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java
rename to vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/Editor.java
index 46a3a6209..ad341bfae 100644
--- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java
+++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/Editor.java
@@ -128,4 +128,16 @@ public class Editor {
return Collections.emptyList();
}
+ public void assertCompletions(String... specs) {
+ throw new UnsupportedOperationException("Not implemented yet!");
+ }
+
+ public void assertIsHoverRegion(String string) {
+ throw new UnsupportedOperationException("Not implemented yet!");
+ }
+
+ public void assertHoverContains(String string, String string2) {
+ throw new UnsupportedOperationException("Not implemented yet!");
+ }
+
}
diff --git a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
similarity index 98%
rename from vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
rename to vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
index d7e81a611..db3838c41 100644
--- a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
+++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
@@ -34,8 +34,7 @@ import io.typefox.lsapi.services.LanguageServer;
public class LanguageServerHarness {
- //Warning this 'harness' is not very good yet. It just implements bare minimum to
- // be able to test the MyLanguageServer example.
+ //Warning this 'harness' is incomplete. Growing it as needed.
private Random random = new Random();
diff --git a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java
similarity index 100%
rename from vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java
rename to vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java
diff --git a/vscode-extensions/commons/pom.xml b/vscode-extensions/commons/pom.xml
index 01c573a06..9dff83f23 100644
--- a/vscode-extensions/commons/pom.xml
+++ b/vscode-extensions/commons/pom.xml
@@ -7,12 +7,22 @@
commons-parent
pom
0.0.1-SNAPSHOT
- Parent pom for headless services written in Java
+ commons-parent
language-server-commons
+ language-server-test-harness
+ yaml-commons
+ util-commons
+
+ 4.11
+ 3.5.2
+ 1.7.21
+ 19.0
+
+
@@ -29,16 +39,21 @@
+
+ org.slf4j
+ slf4j-api
+ ${slf4j-version}
+
junit
junit
- 4.11
+ ${junit-version}
test
org.assertj
assertj-core
- 3.5.2
+ ${assertj-version}
test
diff --git a/vscode-extensions/commons/util-commons/.classpath b/vscode-extensions/commons/util-commons/.classpath
new file mode 100644
index 000000000..fae1a2b37
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/.classpath
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vscode-extensions/commons/util-commons/.project b/vscode-extensions/commons/util-commons/.project
new file mode 100644
index 000000000..d33777c9c
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/.project
@@ -0,0 +1,23 @@
+
+
+ util-commons
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/vscode-extensions/commons/util-commons/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/commons/util-commons/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..714351aec
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/vscode-extensions/commons/util-commons/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/commons/util-commons/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 000000000..f897a7f1c
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/vscode-extensions/commons/util-commons/pom.xml b/vscode-extensions/commons/util-commons/pom.xml
new file mode 100644
index 000000000..377e63200
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/pom.xml
@@ -0,0 +1,13 @@
+
+ 4.0.0
+ util-commons
+ util-commons
+
+
+ org.springframework.ide.vscode
+ commons-parent
+ 0.0.1-SNAPSHOT
+ ../pom.xml
+
+
+
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java
similarity index 66%
rename from vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java
rename to vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java
index 6d0ae7ffd..f9fd476c8 100644
--- a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Assert.java
@@ -7,5 +7,11 @@ public class Assert {
throw new IllegalStateException(msg);
}
}
+
+ public static void isLegal(boolean b) {
+ if (!b) {
+ throw new IllegalStateException();
+ }
+ }
}
diff --git a/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Collector.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Collector.java
new file mode 100644
index 000000000..c961c509f
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Collector.java
@@ -0,0 +1,29 @@
+package org.springframework.ide.vscode.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link IRequestor} that simplies stores all items received into
+ * a List
+ *
+ * @author Kris De Volder
+ */
+public class Collector implements IRequestor {
+
+ @SuppressWarnings("unchecked")
+ private List nodes = Collections.EMPTY_LIST;
+
+ @Override
+ public void accept(T node) {
+ if (nodes==Collections.EMPTY_LIST) {
+ nodes = new ArrayList();
+ }
+ nodes.add(node);
+ }
+
+ public List get() {
+ return nodes;
+ }
+}
diff --git a/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/ExceptionUtil.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/ExceptionUtil.java
new file mode 100644
index 000000000..3922208bf
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/ExceptionUtil.java
@@ -0,0 +1,68 @@
+package org.springframework.ide.vscode.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.CancellationException;
+
+/**
+ * Utility methods to convert exceptions into other types of exceptions, status
+ * objects etc.
+ *
+ * @author Kris De Volder
+ */
+public class ExceptionUtil {
+
+ public static Throwable getDeepestCause(Throwable e) {
+ Throwable cause = e;
+ Throwable parent = e.getCause();
+ while (parent != null && parent != e) {
+ cause = parent;
+ parent = cause.getCause();
+ }
+ return cause;
+ }
+
+ public static String getMessage(Throwable e) {
+ // The message of nested exception is usually more interesting than the
+ // one on top.
+ Throwable cause = getDeepestCause(e);
+ String msg = cause.getClass().getSimpleName() + ": " + cause.getMessage();
+ return msg;
+ }
+
+ public static IllegalStateException notImplemented(String string) {
+ return new IllegalStateException("Not implemented: " + string);
+ }
+
+ public static boolean isCancelation(Throwable e) {
+ return (
+// e instanceof OperationCanceledException ||
+ e instanceof InterruptedException ||
+ e instanceof CancellationException
+// (
+// e instanceof CoreException &&
+// ((CoreException)e).getStatus().getSeverity()==IStatus.CANCEL
+// )
+ );
+ }
+
+ public static RuntimeException unchecked(Exception e) {
+ return new RuntimeException(e);
+ }
+
+ public static String stacktrace() {
+ return stacktrace(new Exception("Stacktrace"));
+ }
+
+ public static String stacktrace(Exception exception) {
+ ByteArrayOutputStream dump = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(dump);
+ try {
+ exception.printStackTrace(out);
+ } finally {
+ out.close();
+ }
+ return dump.toString();
+ }
+
+}
diff --git a/vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/Futures.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Futures.java
similarity index 100%
rename from vscode-extensions/commons/language-server-commons/src/main/java/org/springframework/ide/vscode/util/Futures.java
rename to vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/Futures.java
diff --git a/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/IRequestor.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/IRequestor.java
new file mode 100644
index 000000000..4e7bb38a5
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/IRequestor.java
@@ -0,0 +1,5 @@
+package org.springframework.ide.vscode.util;
+
+public interface IRequestor {
+ void accept(T node);
+}
diff --git a/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/RememberLast.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/RememberLast.java
new file mode 100644
index 000000000..746697625
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/RememberLast.java
@@ -0,0 +1,24 @@
+package org.springframework.ide.vscode.util;
+
+/**
+ * Requestor that remembers only the last item received.
+ *
+ * @author Kris De Volder
+ */
+public class RememberLast implements IRequestor {
+
+ private T last = null;
+
+ @Override
+ public void accept(T node) {
+ this.last = node;
+ }
+
+ /**
+ * @return the last received item, may return null if no items where received.
+ */
+ public T get() {
+ return last;
+ }
+
+}
diff --git a/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/StringUtil.java b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/StringUtil.java
new file mode 100644
index 000000000..52de63069
--- /dev/null
+++ b/vscode-extensions/commons/util-commons/src/main/java/org/springframework/ide/vscode/util/StringUtil.java
@@ -0,0 +1,20 @@
+package org.springframework.ide.vscode.util;
+
+public class StringUtil {
+ public static boolean hasText(String name) {
+ return name!=null && !name.trim().equals("");
+ }
+
+ public static String collectionToDelimitedString(Iterable strings, String delim) {
+ StringBuilder b = new StringBuilder();
+ boolean first = true;
+ for (String s : strings) {
+ if (!first) {
+ b.append(delim);
+ }
+ b.append(s);
+ first = false;
+ }
+ return b.toString();
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/.classpath b/vscode-extensions/commons/yaml-commons/.classpath
new file mode 100644
index 000000000..fae1a2b37
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/.classpath
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vscode-extensions/commons/yaml-commons/.project b/vscode-extensions/commons/yaml-commons/.project
new file mode 100644
index 000000000..8a125b66e
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/.project
@@ -0,0 +1,23 @@
+
+
+ yaml-commons
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..714351aec
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 000000000..f897a7f1c
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/vscode-extensions/commons/yaml-commons/pom.xml b/vscode-extensions/commons/yaml-commons/pom.xml
new file mode 100644
index 000000000..384cfcdd5
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/pom.xml
@@ -0,0 +1,49 @@
+
+ 4.0.0
+ yaml-commons
+ yaml-commons
+ Shared utilities for working with yaml
+
+
+ org.springframework.ide.vscode
+ commons-parent
+ 0.0.1-SNAPSHOT
+ ../pom.xml
+
+
+
+
+ org.springframework.ide.vscode
+ util-commons
+ ${project.version}
+
+
+ org.springframework.ide.vscode
+ language-server-commons
+ ${project.version}
+
+
+ org.yaml
+ snakeyaml
+ 1.17
+
+
+ org.yaml
+ snakeyaml
+ 1.17
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+ com.google.guava
+ guava
+ ${guava-version}
+
+
+
+
+
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeRef.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeRef.java
new file mode 100644
index 000000000..ac66c83cf
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeRef.java
@@ -0,0 +1,190 @@
+package org.springframework.ide.vscode.yaml.ast;
+
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.SequenceNode;
+
+/**
+ * A node reference represents a 'pointer' to a location where a Node is stored.
+ * The concept is useful because when looking for a 'path' inside an Yaml AST a
+ * NodeRef makes it explicit where the node is with respect to some 'container'.
+ * For example it allows distinguishing between a reference to Node which is
+ * obtained from map key versus a map value.
+ *
+ * @author Kris De Volder
+ */
+public abstract class NodeRef {
+
+ public static enum Kind {
+ ROOT, SEQ, KEY, VAL
+ }
+
+ private Parent parent;
+
+ public NodeRef(Parent parent) {
+ this.parent = parent;
+ }
+
+ public Parent getParent() {
+ return parent;
+ }
+
+ public abstract Node get();
+ public abstract void put(Node value);
+
+ public abstract String toString();
+
+ public abstract Kind getKind();
+
+ /**
+ * Represents a reference to a root node, contained directly
+ * inside a {@link YamlFileAST}
+ */
+ public static class RootRef extends NodeRef {
+ private int index;
+
+ public RootRef(YamlFileAST file, int index) {
+ super(file);
+ this.index = index;
+ }
+ @Override
+ public Node get() {
+ return getParent().get(index);
+ }
+
+ @Override
+ public void put(Node value) {
+ getParent().put(index, value);
+ }
+
+ @Override
+ public String toString() {
+ return "ROOT["+index+"]";
+ }
+ @Override
+ public Kind getKind() {
+ return Kind.ROOT;
+ }
+ public int getIndex() {
+ return index;
+ }
+ }
+
+ public static class SeqRef extends NodeRef {
+ private int index;
+ public SeqRef(SequenceNode seq, int index) {
+ super(seq);
+ this.index = index;
+ }
+ @Override
+ public Node get() {
+ return getParent().getValue().get(index);
+ }
+ @Override
+ public void put(Node value) {
+ getParent().getValue().set(index, value);
+ }
+ @Override
+ public String toString() {
+ return "["+index+"]";
+ }
+ @Override
+ public Kind getKind() {
+ return Kind.SEQ;
+ }
+ public int getIndex() {
+ return index;
+ }
+ }
+
+ /**
+ * Abstract, represent reference to either a key or
+ * value inside a map tuple. Concrete subclasses define whether
+ * key or value is being accessed.
+ */
+ public static abstract class TupleRef extends NodeRef {
+ protected int index;
+ public TupleRef(MappingNode map, int index) {
+ super(map);
+ this.index = index;
+ }
+
+ public NodeTuple getTuple() {
+ return getParent().getValue().get(index);
+ }
+
+ public void putTuple(NodeTuple value) {
+ getParent().getValue().set(index, value);
+ }
+ }
+
+ /**
+ * References a key of a map entry
+ */
+ public static class TupleKeyRef extends TupleRef {
+ public TupleKeyRef(MappingNode parent, int index) {
+ super(parent, index);
+ }
+
+ @Override
+ public Node get() {
+ return getTuple().getKeyNode();
+ }
+
+ @Override
+ public void put(Node newKey) {
+ NodeTuple tuple = getTuple();
+ putTuple(new NodeTuple(newKey, tuple.getValueNode()));
+ }
+
+ @Override
+ public String toString() {
+ return "@key["+index+"]";
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.KEY;
+ }
+ }
+
+ /**
+ * References a value of a map entry
+ */
+ public static class TupleValueRef extends TupleRef {
+ public TupleValueRef(MappingNode parent, int index) {
+ super(parent, index);
+ }
+
+ @Override
+ public Node get() {
+ return getTuple().getValueNode();
+ }
+
+ @Override
+ public void put(Node newValue) {
+ NodeTuple t = getTuple();
+ putTuple(new NodeTuple(t.getKeyNode(), newValue));
+ }
+
+ @Override
+ public String toString() {
+ String keyString = NodeUtil.asScalar(getTuple().getKeyNode());
+ if (keyString!=null) {
+ //more readable to use the key value than the index of the tuple
+ return "@val['"+keyString+"']";
+ }
+ return "@val["+index+"]";
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.VAL;
+ }
+
+ public Node getKey() {
+ return getTuple().getKeyNode();
+ }
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeUtil.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeUtil.java
new file mode 100644
index 000000000..31451de43
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/ast/NodeUtil.java
@@ -0,0 +1,50 @@
+package org.springframework.ide.vscode.yaml.ast;
+
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeId;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+
+/**
+ * @author Kris De Volder
+ */
+public class NodeUtil {
+
+ /**
+ * Determines whether a node contains the given offset.
+ * The range a node covers between its begin and end mark is treated
+ * as a half open interval. The start offset is treated as included
+ * in the range but the end offset is excluded.
+ *
+ * This is to avoid ambiguity as node ranges tend to 'join'
+ * together so that the end region of one node coincides with
+ * the start region of the next node. By treating node ranges
+ * as 'half open' intervals every offset is typically only
+ * part of two different nodes if those nodes effectively
+ * have overlapping ranges (i.e. only if one node contains
+ * the other). Thus, an operation like finding the smallest
+ * node that contains an offset is unambgious.
+ */
+ public static boolean contains(Node node, int offset) {
+ return getStart(node)<=offset && offset> NO_CHILDREN = Collections.emptyList();
+ private List nodes;
+
+ public YamlFileAST(Iterable iter) {
+ nodes = new ArrayList();
+ for (Node node : iter) {
+ nodes.add(node);
+ }
+ }
+
+ public List> findPath(int offset) {
+ Collector> path = new Collector>();
+ 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> 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> 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> 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> 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 getNodes() {
+ return nodes;
+ }
+
+ private static List> getChildren(SequenceNode seq) {
+ int nodes = seq.getValue().size();
+ ArrayList> children = new ArrayList>(nodes);
+ for (int i = 0; i < nodes; i++) {
+ children.add(new SeqRef(seq, i));
+ }
+ return children;
+ }
+
+ private static List> getChildren(MappingNode map) {
+ int entries = map.getValue().size();
+ ArrayList> children = new ArrayList>(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> lastNode = new RememberLast>();
+ 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);
+ }
+
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/SchemaBasedYamlASTReconciler.java
new file mode 100644
index 000000000..7ea5f5ad0
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/SchemaBasedYamlASTReconciler.java
@@ -0,0 +1,167 @@
+package org.springframework.ide.vscode.yaml.reconcile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
+import org.springframework.ide.vscode.util.ExceptionUtil;
+import org.springframework.ide.vscode.util.StringUtil;
+import org.springframework.ide.vscode.yaml.ast.NodeUtil;
+import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
+import org.springframework.ide.vscode.yaml.schema.YType;
+import org.springframework.ide.vscode.yaml.schema.YTypeUtil;
+import org.springframework.ide.vscode.yaml.schema.YTypedProperty;
+import org.springframework.ide.vscode.yaml.schema.YamlSchema;
+import org.springframework.ide.vscode.yaml.util.ValueParser;
+import org.yaml.snakeyaml.nodes.MappingNode;
+import org.yaml.snakeyaml.nodes.Node;
+import org.yaml.snakeyaml.nodes.NodeTuple;
+import org.yaml.snakeyaml.nodes.ScalarNode;
+import org.yaml.snakeyaml.nodes.SequenceNode;
+
+public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
+
+ private final IProblemCollector problems;
+ private final YamlSchema schema;
+ private final YTypeUtil typeUtil;
+
+ public SchemaBasedYamlASTReconciler(IProblemCollector problems, YamlSchema schema) {
+ this.problems = problems;
+ this.schema = schema;
+ this.typeUtil = schema.getTypeUtil();
+ }
+
+ @Override
+ public void reconcile(YamlFileAST ast) {
+ List nodes = ast.getNodes();
+ if (nodes!=null && !nodes.isEmpty()) {
+ for (Node node : nodes) {
+ reconcile(node, schema.getTopLevelType());
+ }
+ }
+ }
+
+ private void reconcile(Node node, YType type) {
+ if (type!=null) {
+ switch (node.getNodeId()) {
+ case mapping:
+ MappingNode map = (MappingNode) node;
+ if (typeUtil.isMap(type)) {
+ for (NodeTuple entry : map.getValue()) {
+ reconcile(entry.getKeyNode(), typeUtil.getKeyType(type));
+ reconcile(entry.getValueNode(), typeUtil.getDomainType(type));
+ }
+ } else if (typeUtil.isBean(type)) {
+ Map beanProperties = typeUtil.getPropertiesMap(type);
+ for (NodeTuple entry : map.getValue()) {
+ Node keyNode = entry.getKeyNode();
+ String key = NodeUtil.asScalar(keyNode);
+ if (key==null) {
+ expectScalar(node);
+ } else {
+ YTypedProperty prop = beanProperties.get(key);
+ if (prop==null) {
+ unknownBeanProperty(keyNode, type, key);
+ } else {
+ reconcile(entry.getValueNode(), prop.getType());
+ }
+ }
+ }
+ } else {
+ expectTypeButFoundMap(type, node);
+ }
+ break;
+ case sequence:
+ SequenceNode seq = (SequenceNode) node;
+ if (typeUtil.isSequencable(type)) {
+ for (Node el : seq.getValue()) {
+ reconcile(el, typeUtil.getDomainType(type));
+ }
+ } else {
+ expectTypeButFoundSequence(type, node);
+ }
+ break;
+ case scalar:
+ if (typeUtil.isAtomic(type)) {
+ ValueParser parser = typeUtil.getValueParser(type);
+ if (parser!=null) {
+ try {
+ parser.parse(NodeUtil.asScalar(node));
+ } catch (Exception e) {
+ String msg = ExceptionUtil.getMessage(e);
+ valueParseError(type, node, msg);
+ }
+ }
+ } else {
+ expectTypeButFoundScalar(type, node);
+ }
+ break;
+ default:
+ // other stuff we don't check
+ }
+ }
+ }
+
+ private void valueParseError(YType type, Node node, String parseErrorMsg) {
+ String msg= "Couldn't parse as '"+describe(type)+"'";
+ if (StringUtil.hasText(parseErrorMsg)) {
+ msg += " ("+parseErrorMsg+")";
+ }
+ problem(node, msg);
+ }
+
+ private void unknownBeanProperty(Node keyNode, YType type, String name) {
+ problem(keyNode, "Unknown property '"+name+"' for type '"+typeUtil.niceTypeName(type)+"'");
+ }
+
+ private void expectScalar(Node node) {
+ problem(node, "Expecting a 'Scalar' node but got "+describe(node));
+ }
+
+ private String describe(Node node) {
+ switch (node.getNodeId()) {
+ case scalar:
+ return "'"+((ScalarNode)node).getValue()+"'";
+ case mapping:
+ return "a 'Mapping' node";
+ case sequence:
+ return "a 'Sequence' node";
+ case anchor:
+ return "a 'Anchor' node";
+ default:
+ throw new IllegalStateException("Missing switch case");
+ }
+ }
+
+ private void expectTypeButFoundScalar(YType type, Node node) {
+ problem(node, "Expecting a '"+describe(type)+"' but found a 'Scalar'");
+ }
+
+ private void expectTypeButFoundSequence(YType type, Node node) {
+ problem(node, "Expecting a '"+describe(type)+"' but found a 'Sequence'");
+ }
+
+ private void expectTypeButFoundMap(YType type, Node node) {
+ problem(node, "Expecting a '"+describe(type)+"' but found a 'Map'");
+ }
+
+ private String describe(YType type) {
+ if (typeUtil.isAtomic(type)) {
+ return typeUtil.niceTypeName(type);
+ }
+ ArrayList expectedNodeTypes = new ArrayList<>();
+ if (typeUtil.isBean(type) || typeUtil.isMap(type)) {
+ expectedNodeTypes.add("Map");
+ }
+ if (typeUtil.isSequencable(type)) {
+ expectedNodeTypes.add("Sequence");
+ }
+ return StringUtil.collectionToDelimitedString(expectedNodeTypes, " or ");
+ }
+
+ private void problem(Node node, String msg) {
+ problems.accept(YamlSchemaProblems.schemaProblem(msg, node));
+ }
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlASTReconciler.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlASTReconciler.java
new file mode 100644
index 000000000..89372f362
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlASTReconciler.java
@@ -0,0 +1,7 @@
+package org.springframework.ide.vscode.yaml.reconcile;
+
+import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
+
+public interface YamlASTReconciler {
+ void reconcile(YamlFileAST ast);
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlReconcileEngine.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlReconcileEngine.java
new file mode 100644
index 000000000..862e20146
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlReconcileEngine.java
@@ -0,0 +1,54 @@
+package org.springframework.ide.vscode.yaml.reconcile;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ide.vscode.commons.reconcile.IDocument;
+import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
+import org.springframework.ide.vscode.commons.reconcile.IReconcileEngine;
+import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
+import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
+import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.parser.ParserException;
+import org.yaml.snakeyaml.scanner.ScannerException;
+
+/**
+ * @author Kris De Volder
+ */
+public abstract class YamlReconcileEngine implements IReconcileEngine {
+
+ final static Logger logger = LoggerFactory.getLogger(YamlReconcileEngine.class);
+
+ protected final YamlASTProvider parser;
+
+ public YamlReconcileEngine(YamlASTProvider parser) {
+ this.parser = parser;
+ }
+
+ @Override
+ public void reconcile(IDocument doc, IProblemCollector problemCollector) {
+ problemCollector.beginCollecting();
+ try {
+ YamlFileAST ast = parser.getAST(doc);
+ YamlASTReconciler reconciler = getASTReconciler(doc, problemCollector);
+ if (reconciler!=null) {
+ reconciler.reconcile(ast);
+ }
+ } catch (ParserException e) {
+ String msg = e.getProblem();
+ Mark mark = e.getProblemMark();
+ problemCollector.accept(syntaxError(msg, mark.getIndex(), 1));
+ } catch (ScannerException e) {
+ String msg = e.getProblem();
+ Mark mark = e.getProblemMark();
+ problemCollector.accept(syntaxError(msg, mark.getIndex(), 1));
+ } catch (Exception e) {
+ logger.error("unexpected error during reconcile", e);
+ } finally {
+ problemCollector.endCollecting();
+ }
+ }
+
+ protected abstract ReconcileProblem syntaxError(String msg, int offset, int length);
+ protected abstract YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problemCollector);
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaBasedReconcileEngine.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaBasedReconcileEngine.java
new file mode 100644
index 000000000..91209febf
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaBasedReconcileEngine.java
@@ -0,0 +1,29 @@
+package org.springframework.ide.vscode.yaml.reconcile;
+
+import org.springframework.ide.vscode.commons.reconcile.IDocument;
+import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
+import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
+import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
+import org.springframework.ide.vscode.yaml.schema.YamlSchema;
+
+/**
+ * @author Kris De Volder
+ */
+public final class YamlSchemaBasedReconcileEngine extends YamlReconcileEngine {
+ private final YamlSchema schema;
+
+ public YamlSchemaBasedReconcileEngine(YamlASTProvider parser, YamlSchema schema) {
+ super(parser);
+ this.schema = schema;
+ }
+
+ @Override
+ protected ReconcileProblem syntaxError(String msg, int offset, int length) {
+ return YamlSchemaProblems.syntaxProblem(msg, offset, length);
+ }
+
+ @Override
+ protected YamlASTReconciler getASTReconciler(IDocument doc, IProblemCollector problems) {
+ return new SchemaBasedYamlASTReconciler(problems, schema);
+ }
+}
\ No newline at end of file
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaProblems.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaProblems.java
new file mode 100644
index 000000000..7808d9425
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/reconcile/YamlSchemaProblems.java
@@ -0,0 +1,45 @@
+package org.springframework.ide.vscode.yaml.reconcile;
+
+import org.springframework.ide.vscode.commons.reconcile.ProblemSeverity;
+import org.springframework.ide.vscode.commons.reconcile.ProblemType;
+import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
+import org.springframework.ide.vscode.commons.reconcile.ReconcileProblemImpl;
+import org.yaml.snakeyaml.nodes.Node;
+
+/**
+ * Methods for creating reconciler problems for Schema based reconciler implementation.
+ *
+ * @author Kris De Volder
+ */
+public class YamlSchemaProblems {
+
+ private static final ProblemType SCHEMA_PROBLEM = problemType("YamlSchemaProblem");
+ private static final ProblemType SYNTAX_PROBLEM = problemType("YamlSyntaxProblem");
+
+ private static ProblemType problemType(final String typeName) {
+ return new ProblemType() {
+ @Override
+ public String toString() {
+ return typeName;
+ }
+ @Override
+ public ProblemSeverity getDefaultSeverity() {
+ return ProblemSeverity.ERROR;
+ }
+ @Override
+ public String getCode() {
+ return typeName;
+ }
+ };
+ }
+
+ public static ReconcileProblem syntaxProblem(String msg, int offset, int len) {
+ return new ReconcileProblemImpl(SYNTAX_PROBLEM, msg, offset, len);
+ }
+
+ public static ReconcileProblem schemaProblem(String msg, Node node) {
+ int start = node.getStartMark().getIndex();
+ int end = node.getEndMark().getIndex();
+ return new ReconcileProblemImpl(SCHEMA_PROBLEM, msg, start, end-start);
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/BasicYValueHint.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/BasicYValueHint.java
new file mode 100644
index 000000000..f5f89eee4
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/BasicYValueHint.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+public class BasicYValueHint implements YValueHint {
+
+ private final String value;
+ private String label;
+
+ public BasicYValueHint(String value, String label) {
+ this.value = value;
+ this.label = label;
+ }
+
+ public BasicYValueHint(String value) {
+ this.value = value;
+ this.label = value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.ide.eclipse.cloudfoundry.manifest.editor.YValueHint#getValue()
+ */
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.ide.eclipse.cloudfoundry.manifest.editor.YValueHint#getLabel()
+ */
+ @Override
+ public String getLabel() {
+ return label;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((label == null) ? 0 : label.hashCode());
+ result = prime * result + ((value == null) ? 0 : value.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BasicYValueHint other = (BasicYValueHint) obj;
+ if (label == null) {
+ if (other.label != null)
+ return false;
+ } else if (!label.equals(other.label))
+ return false;
+ if (value == null) {
+ if (other.value != null)
+ return false;
+ } else if (!value.equals(other.value))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BasicYValueHint [value=" + value + ", label=" + label + "]";
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YType.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YType.java
new file mode 100644
index 000000000..7dd565c83
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YType.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+/**
+ * Marker interface for objects that carry 'type information'.
+ *
+ * It may seem odd that this interface has no actual methods. This
+ * is because the methods for interpreting the types are defined
+ * by an accompanying {@link YTypeUtil}.
+ *
+ * The main reason why it works this way is to allow for 'YType' objects
+ * themselves to be implemented as dumb data objects while making YTypeUtil
+ * implementations define how to interpret these objects using context
+ * information (e.g. types resolved from a project's classpath).
+ *
+ * @author Kris De Volder
+ */
+public interface YType {
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeFactory.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeFactory.java
new file mode 100644
index 000000000..d9421c6db
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeFactory.java
@@ -0,0 +1,364 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Provider;
+
+import org.springframework.ide.vscode.yaml.util.Description;
+import org.springframework.ide.vscode.yaml.util.DescriptionProviders;
+import org.springframework.ide.vscode.yaml.util.EnumValueParser;
+import org.springframework.ide.vscode.yaml.util.ValueParser;
+
+/**
+ * Static utility method for creating YType objects representing either
+ * 'array-like', 'map-like' or 'object-like' types which can be used
+ * to build up a 'Yaml Schema'.
+ *
+ * @author Kris De Volder
+ */
+public class YTypeFactory {
+
+ public YType yseq(YType el) {
+ return new YSeqType(el);
+ }
+
+ public YType ymap(YType key, YType val) {
+ return new YMapType(key, val);
+ }
+
+ public YBeanType ybean(String name, YTypedProperty... properties) {
+ return new YBeanType(name, properties);
+ }
+
+ /**
+ * YTypeUtil instances capable of 'interpreting' the YType objects created by this
+ * YTypeFactory
+ */
+ public final YTypeUtil TYPE_UTIL = new YTypeUtil() {
+
+ @Override
+ public boolean isSequencable(YType type) {
+ return ((AbstractType)type).isSequenceable();
+ }
+
+ @Override
+ public boolean isMap(YType type) {
+ return ((AbstractType)type).isMap();
+ }
+
+ @Override
+ public boolean isAtomic(YType type) {
+ return ((AbstractType)type).isAtomic();
+ }
+
+ @Override
+ public Map getPropertiesMap(YType type) {
+ return ((AbstractType)type).getPropertiesMap();
+ }
+
+ @Override
+ public List getProperties(YType type) {
+ return ((AbstractType)type).getProperties();
+ }
+
+ @Override
+ public YValueHint[] getHintValues(YType type) {
+ return ((AbstractType)type).getHintValues();
+ }
+
+ @Override
+ public YType getDomainType(YType type) {
+ return ((AbstractType)type).getDomainType();
+ }
+
+ @Override
+ public String niceTypeName(YType type) {
+ return type.toString();
+ }
+
+ @Override
+ public YType getKeyType(YType type) {
+ return ((AbstractType)type).getKeyType();
+ }
+
+ @Override
+ public boolean isBean(YType type) {
+ return ((AbstractType)type).isBean();
+ }
+
+ @Override
+ public ValueParser getValueParser(YType type) {
+ return ((AbstractType)type).getParser();
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Provides default implementations for all YType methods.
+ */
+ public static abstract class AbstractType implements YType {
+
+ private ValueParser parser;
+ private List propertyList = new ArrayList<>();
+ private final List hints = new ArrayList<>();
+ private Map cachedPropertyMap;
+ private Provider> hintProvider;
+
+ public boolean isSequenceable() {
+ return false;
+ }
+
+ public boolean isBean() {
+ return false;
+ }
+
+ public YType getKeyType() {
+ return null;
+ }
+
+ public YType getDomainType() {
+ return null;
+ }
+
+ public void addHintProvider(Provider> hintProvider) {
+ this.hintProvider = hintProvider;
+ }
+
+ public YValueHint[] getHintValues() {
+ Collection providerHints = hintProvider != null ? hintProvider.get() : null;
+
+ if (providerHints == null || providerHints.isEmpty()) {
+ return hints.toArray(new YValueHint[hints.size()]);
+ } else {
+ // Only merge if there are provider hints to merge
+ Set mergedHints = new LinkedHashSet<>();
+
+ // Add type hints first
+ for (YValueHint val : hints) {
+ mergedHints.add(val);
+ }
+
+ // merge the provider hints
+ for (YValueHint val : providerHints) {
+ mergedHints.add(val);
+ }
+ return mergedHints.toArray(new YValueHint[mergedHints.size()]);
+ }
+ }
+
+ public final List getProperties() {
+ return Collections.unmodifiableList(propertyList);
+ }
+
+ public final Map getPropertiesMap() {
+ if (cachedPropertyMap==null) {
+ cachedPropertyMap = new LinkedHashMap<>();
+ for (YTypedProperty p : propertyList) {
+ cachedPropertyMap.put(p.getName(), p);
+ }
+ }
+ return Collections.unmodifiableMap(cachedPropertyMap);
+ }
+
+ public boolean isAtomic() {
+ return false;
+ }
+
+ public boolean isMap() {
+ return false;
+ }
+
+ public abstract String toString(); // force each sublcass to implement a (nice) toString method.
+
+ public void addProperty(YTypedProperty p) {
+ cachedPropertyMap = null;
+ propertyList.add(p);
+ }
+
+ public void addProperty(String name, YType type, Provider description) {
+ YTypedPropertyImpl prop;
+ addProperty(prop = new YTypedPropertyImpl(name, type));
+ prop.setDescriptionProvider(description);
+ }
+
+ public void addProperty(String name, YType type) {
+ addProperty(new YTypedPropertyImpl(name, type));
+ }
+ public void addHints(String... strings) {
+ if (strings != null) {
+ for (String value : strings) {
+ BasicYValueHint hint = new BasicYValueHint(value);
+ if (!hints.contains(hint)) {
+ hints.add(hint);
+ }
+ }
+ }
+ }
+ public void parseWith(ValueParser parser) {
+ this.parser = parser;
+ }
+ public ValueParser getParser() {
+ return parser;
+ }
+ }
+
+ public static class YMapType extends AbstractType {
+
+ private final YType key;
+ private final YType val;
+
+ private YMapType(YType key, YType val) {
+ this.key = key;
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "Map<"+key.toString()+", "+val.toString()+">";
+ }
+
+ @Override
+ public boolean isMap() {
+ return true;
+ }
+
+ @Override
+ public YType getKeyType() {
+ return key;
+ }
+
+ @Override
+ public YType getDomainType() {
+ return val;
+ }
+ }
+
+ public static class YSeqType extends AbstractType {
+
+ private YType el;
+
+ private YSeqType(YType el) {
+ this.el = el;
+ }
+
+ @Override
+ public String toString() {
+ return el.toString()+"[]";
+ }
+
+ @Override
+ public boolean isSequenceable() {
+ return true;
+ }
+
+ @Override
+ public YType getDomainType() {
+ return el;
+ }
+ }
+
+ public static class YBeanType extends AbstractType {
+ private final String name;
+
+ public YBeanType(String name, YTypedProperty[] properties) {
+ this.name = name;
+ for (YTypedProperty p : properties) {
+ addProperty(p);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ public boolean isBean() {
+ return true;
+ }
+ }
+
+ public static class YAtomicType extends AbstractType {
+ private final String name;
+ private YAtomicType(String name) {
+ this.name = name;
+ }
+ @Override
+ public String toString() {
+ return name;
+ }
+ @Override
+ public boolean isAtomic() {
+ return true;
+ }
+ }
+
+ public static class YTypedPropertyImpl implements YTypedProperty {
+
+ final private String name;
+ final private YType type;
+ private Provider descriptionProvider = DescriptionProviders.NO_DESCRIPTION;
+
+ private YTypedPropertyImpl(String name, YType type) {
+ this.name = name;
+ this.type = type;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public YType getType() {
+ return this.type;
+ }
+
+ @Override
+ public String toString() {
+ return name + ":" + type;
+ }
+
+ @Override
+ public Description getDescription() {
+ return descriptionProvider.get();
+ }
+
+ public void setDescriptionProvider(Provider descriptionProvider) {
+ this.descriptionProvider = descriptionProvider;
+ }
+
+ }
+
+ public YAtomicType yatomic(String name) {
+ return new YAtomicType(name);
+ }
+
+ public YTypedPropertyImpl yprop(String name, YType type) {
+ return new YTypedPropertyImpl(name, type);
+ }
+
+ public YAtomicType yenum(String name, String... values) {
+ YAtomicType t = yatomic(name);
+ t.addHints(values);
+ t.parseWith(new EnumValueParser(name, values));
+ return t;
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeUtil.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeUtil.java
new file mode 100644
index 000000000..a78d47cdc
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypeUtil.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.ide.vscode.yaml.util.ValueParser;
+
+/**
+ * An implementation of YTypeUtil provides implementations of various
+ * methods operating on YTypes and interpreting them in some context
+ * (e.g. the meaning of YType objects may depend on types resolved
+ * from the current project's classpath).
+ *
+ * @author Kris De Volder
+ */
+public interface YTypeUtil {
+ boolean isAtomic(YType type);
+ boolean isMap(YType type);
+ boolean isSequencable(YType type);
+ boolean isBean(YType type);
+ YType getDomainType(YType type);
+ YValueHint[] getHintValues(YType yType);
+ String niceTypeName(YType type);
+ YType getKeyType(YType type);
+ ValueParser getValueParser(YType type);
+
+ //TODO: only one of these two should be enough?
+ List getProperties(YType type);
+ Map getPropertiesMap(YType yType);
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypedProperty.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypedProperty.java
new file mode 100644
index 000000000..bbc64e41b
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YTypedProperty.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+import org.springframework.ide.vscode.yaml.util.Description;
+
+/**
+ * @author Kris De Volder
+ */
+public interface YTypedProperty {
+ String getName();
+ YType getType();
+ Description getDescription();
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YValueHint.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YValueHint.java
new file mode 100644
index 000000000..bac9d7f9c
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YValueHint.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+public interface YValueHint {
+
+ String getValue();
+
+ String getLabel();
+
+}
\ No newline at end of file
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YamlSchema.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YamlSchema.java
new file mode 100644
index 000000000..47717841b
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/schema/YamlSchema.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.schema;
+
+/**
+ * A 'schema' provides a toplevel type, which dictates the valid structure of a
+ * YamlDocument and a {@link YTypeUtil} which provides the means to 'interpret'
+ * the types.
+ *
+ * @author Kris De Volder
+ */
+public interface YamlSchema {
+
+ YType getTopLevelType();
+ YTypeUtil getTypeUtil();
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/Description.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/Description.java
new file mode 100644
index 000000000..3caa74646
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/Description.java
@@ -0,0 +1,31 @@
+package org.springframework.ide.vscode.yaml.util;
+
+public abstract class Description {
+
+ public abstract void renderAsText(StringBuilder buf);
+
+ public void renderAsHtml(StringBuilder buf) {
+ throw new UnsupportedOperationException("Rendering as html not supported");
+ }
+
+ public static Description text(String text) {
+ return new Description() {
+ @Override
+ public void renderAsText(StringBuilder buf) {
+ buf.append(text);
+ }
+ };
+ }
+
+ public static Description italic(Description d) {
+ //Not really supported, we just ignore italic and display as is
+ return d;
+ }
+
+ public String toText() {
+ StringBuilder buf = new StringBuilder();
+ renderAsText(buf);
+ return buf.toString();
+ }
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/DescriptionProviders.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/DescriptionProviders.java
new file mode 100644
index 000000000..09e1816be
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/DescriptionProviders.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml.util;
+
+import java.io.InputStream;
+
+import javax.inject.Provider;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.springframework.ide.vscode.yaml.util.Description.*;
+
+/**
+ * Static methods and convenience constants for creating some 'description providers'.
+ *
+ * @author Kris De Volder
+ */
+public class DescriptionProviders {
+
+ final static Logger logger = LoggerFactory.getLogger(DescriptionProviders.class);
+
+ public static final Provider NO_DESCRIPTION = () -> italic(text("no description"));
+
+ public static Provider snippet(final Description snippet) {
+ return new Provider() {
+ @Override
+ public String toString() {
+ return snippet.toString();
+ }
+ @Override
+ public Description get() {
+ return snippet;
+ }
+ };
+ }
+
+ public static Provider fromClasspath(final Class> klass, final String resourcePath) {
+ return new Provider() {
+ @Override
+ public String toString() {
+ return "DescriptionFromClassPth(class="+klass.getSimpleName()+", "+resourcePath+")";
+ }
+ @Override
+ public Description get() {
+ try {
+ InputStream stream = klass.getResourceAsStream(resourcePath);
+ if (stream!=null) {
+ return Description.text(IOUtil.toString(stream));
+ }
+ } catch (Exception e) {
+ logger.error("Error", e);;
+ }
+ return NO_DESCRIPTION.get();
+ }
+ };
+ }
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/EnumValueParser.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/EnumValueParser.java
new file mode 100644
index 000000000..1c373754b
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/EnumValueParser.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2014-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.vscode.yaml.util;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * Parser for checking a 'Enum' style values.
+ *
+ * @author Kris De Volder
+ */
+public class EnumValueParser implements ValueParser {
+
+ private String typeName;
+ private Set values;
+
+ public EnumValueParser(String typeName, String... values) {
+ this(typeName, ImmutableSet.copyOf(values));
+ }
+
+ public EnumValueParser(String typeName, Collection values) {
+ this.typeName = typeName;
+ this.values = ImmutableSet.copyOf(values);
+ }
+
+ public Object parse(String str) {
+ if (values.contains(str)) {
+ return str;
+ } else {
+ throw new IllegalArgumentException("'"+str+"' is not valid for Enum '"+typeName+"'");
+ }
+ }
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/IOUtil.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/IOUtil.java
new file mode 100644
index 000000000..ac4fc12d7
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/IOUtil.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Pivotal Software, 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 Software, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.ide.vscode.yaml.util;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class IOUtil {
+
+ /**
+ * Copy data from an inputstream into a file until end of the inputstream
+ * is reached.
+ *
+ * The input stream is closed automatically.
+ */
+ public static void pipe(InputStream data, File target) throws IOException {
+ target.getParentFile().mkdirs(); //try to create dirs for parent if they don't exist.
+ OutputStream out = new BufferedOutputStream(new FileOutputStream(target));
+ try {
+ pipe(data, out);
+ } finally {
+ out.close();
+ }
+ }
+
+ /**
+ * Copy input stream to output stream until end of the inputstream is reached.
+ * The intpustream is closed automatically, but the output stream is not.
+ */
+ public static void pipe(InputStream input, OutputStream output) throws IOException {
+ try {
+ byte[] buf = new byte[1024*4];
+ int n = input.read(buf);
+ while (n >= 0) {
+ output.write(buf, 0, n);
+ n = input.read(buf);
+ }
+ output.flush();
+ } finally {
+ input.close();
+ }
+ }
+
+ public static String toString(InputStream input) throws Exception {
+ return toString(input, "UTF8");
+ }
+
+ private static String toString(InputStream input, String encoding) throws Exception {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ pipe(input, buf);
+ return buf.toString(encoding);
+ }
+
+ /**
+ * Sick and tired of writing try-catch around close calls... If something can't close, it usually means it
+ * was already closed, no longer exists etc. This method catches and ignores the exceptions.
+ */
+ public static void close(Closeable closeable) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ public static byte[] toBytes(InputStream stream) throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ pipe(stream, bytes);
+ return bytes.toByteArray();
+ }
+
+}
diff --git a/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/ValueParser.java b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/ValueParser.java
new file mode 100644
index 000000000..262052002
--- /dev/null
+++ b/vscode-extensions/commons/yaml-commons/src/main/java/org/springframework/ide/vscode/yaml/util/ValueParser.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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.vscode.yaml.util;
+
+/**
+ * A ValueParser provides the means to Strings into some kind of
+ * value.
+ *
+ * @author Kris De Volder
+ */
+public interface ValueParser {
+ /**
+ * Parse the string and return its parsed representation.
+ * May either return null, or throw an {@link IllegalArgumentException} to indicate
+ * that the String is not the format this parser expects.
+ */
+ Object parse(String str);
+}
\ No newline at end of file
diff --git a/vscode-extensions/vscode-application-yaml/pom.xml b/vscode-extensions/vscode-application-yaml/pom.xml
index 9debdb749..e4d278e72 100644
--- a/vscode-extensions/vscode-application-yaml/pom.xml
+++ b/vscode-extensions/vscode-application-yaml/pom.xml
@@ -58,6 +58,13 @@
guava
18.0
+
+
+ org.springframework.ide.vscode
+ language-server-test-harness
+ ${project.version}
+ test
+
diff --git a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java b/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java
deleted file mode 100644
index 46a3a6209..000000000
--- a/vscode-extensions/vscode-application-yaml/src/test/java/org/springframework/ide/vscode/testharness/Editor.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.springframework.ide.vscode.testharness;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import javax.swing.text.BadLocationException;
-
-import io.typefox.lsapi.Diagnostic;
-import io.typefox.lsapi.Position;
-import io.typefox.lsapi.PublishDiagnosticsParams;
-import io.typefox.lsapi.Range;
-
-public class Editor {
-
- private static final Comparator PROBLEM_COMPARATOR = new Comparator() {
- @Override
- public int compare(Diagnostic o1, Diagnostic o2) {
- int diff = compare(o1.getRange().getStart(), o2.getRange().getStart());
- if (diff!=0) return diff;
- return compare(o1.getRange().getEnd(), o2.getRange().getEnd());
- }
-
- private int compare(Position p1, Position p2) {
- int d = p1.getLine() - p2.getLine();
- if (d!=0) return d;
- return p1.getCharacter() - p2.getCharacter();
- }
- };
-
- private LanguageServerHarness harness;
- private TextDocumentInfo document;
-
- public Editor(LanguageServerHarness harness, String contents) throws Exception {
- this.harness = harness;
- this.document = harness.openDocument(harness.createWorkingCopy(contents));
- }
-
- /**
- * Check that a 'expectedProblems' are found by the reconciler. Expected problems are
- * specified by string of the form "${badSnippet}|${messageSnippet}". The badSnippet
- * is the text expected to be covered by the marker's region and the message snippet must
- * be found in the error marker's message.
- *
- * The expected problems are matched one-to-one in the order given (so markers in the
- * editor must appear in the expected order for the assert to pass).
- *
- * @param editor
- * @param expectedProblems
- * @throws BadLocationException
- */
- public void assertProblems(String... expectedProblems) throws Exception {
- Editor editor = this;
- List actualProblems = new ArrayList<>(editor.reconcile());
- Collections.sort(actualProblems, PROBLEM_COMPARATOR);
- String bad = null;
- if (actualProblems.size()!=expectedProblems.length) {
- bad = "Wrong number of problems (expecting "+expectedProblems.length+" but found "+actualProblems.size()+")";
- } else {
- for (int i = 0; i < expectedProblems.length; i++) {
- if (!matchProblem(editor, actualProblems.get(i), expectedProblems[i])) {
- bad = "First mismatch at index "+i+": "+expectedProblems[i]+"\n";
- break;
- }
- }
- }
- if (bad!=null) {
- fail(bad+problemSumary(editor, actualProblems));
- }
- }
-
- private String problemSumary(Editor editor, List actualProblems) throws Exception {
- StringBuilder buf = new StringBuilder();
- for (Diagnostic p : actualProblems) {
- buf.append("\n----------------------\n");
-
- String snippet = editor.getText(p.getRange());
- buf.append("("+p.getRange().getStart().getLine()+", "+p.getRange().getStart().getCharacter()+")["+snippet+"]:\n");
- buf.append(" "+p.getMessage());
- }
- return buf.toString();
- }
-
- public String getText(Range range) {
- return document.getText(range);
- }
-
- public void setText(String newContent) throws Exception {
- document = harness.changeDocument(document.getUri(), newContent);
- }
-
- private boolean matchProblem(Editor editor, Diagnostic problem, String expect) {
- String[] parts = expect.split("\\|");
- assertEquals(2, parts.length);
- String badSnippet = parts[0];
- String messageSnippet = parts[1];
- boolean spaceSensitive = badSnippet.trim().length() reconcile() {
- // We assume the language server works synchronously for now and it does an immediate reconcile
- // when the document changes. In the future this is probably not going to be the case though and then this
- // method will need to somehow ensure the linter is done working before retrieving the problems from the
- // test harness.
- PublishDiagnosticsParams diagnostics = harness.getDiagnostics(document);
- if (diagnostics!=null) {
- return (List) diagnostics.getDiagnostics();
- }
- return Collections.emptyList();
- }
-
-}
diff --git a/vscode-extensions/vscode-manifest-yaml/lib/Main.ts b/vscode-extensions/vscode-manifest-yaml/lib/Main.ts
index dd0e042c7..17e2bafb9 100644
--- a/vscode-extensions/vscode-manifest-yaml/lib/Main.ts
+++ b/vscode-extensions/vscode-manifest-yaml/lib/Main.ts
@@ -42,6 +42,7 @@ function getClasspath(context: VSCode.ExtensionContext):string {
/** Called when extension is activated */
export function activate(context: VSCode.ExtensionContext) {
+ VSCode.window.showInformationMessage("Activating manifet.yml extension");
let javaExecutablePath = findJavaExecutable('java');
if (javaExecutablePath == null) {
@@ -103,7 +104,7 @@ export function activate(context: VSCode.ExtensionContext) {
let args = [
'-Dserver.port=' + port,
'-cp', classpath,
- 'org.springframework.ide.vscode.yaml.Main'
+ 'org.springframework.ide.vscode.cloudfoundry.manifest.editor.Main'
];
if (DEBUG) {
args.unshift(DEBUG_ARG);
diff --git a/vscode-extensions/vscode-manifest-yaml/pom.xml b/vscode-extensions/vscode-manifest-yaml/pom.xml
index 270ec1a1a..cc5daa209 100644
--- a/vscode-extensions/vscode-manifest-yaml/pom.xml
+++ b/vscode-extensions/vscode-manifest-yaml/pom.xml
@@ -34,19 +34,20 @@
org.springframework.ide.vscode
language-server-commons
- 0.0.1-SNAPSHOT
+ ${project.version}
- org.yaml
- snakeyaml
- 1.17
+ org.springframework.ide.vscode
+ yaml-commons
+ ${project.version}
-
+
- com.google.guava
- guava
- 18.0
+ org.springframework.ide.vscode
+ language-server-test-harness
+ ${project.version}
+ test
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/Main.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/Main.java
similarity index 95%
rename from vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/Main.java
rename to vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/Main.java
index db9b7a4c5..08041aea6 100644
--- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/Main.java
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/Main.java
@@ -1,4 +1,4 @@
-package org.springframework.ide.vscode.yaml;
+package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
import java.io.IOException;
import java.io.InputStream;
@@ -79,7 +79,7 @@ public class Main {
* When the request stream is closed, wait for 5s for all outstanding responses to compute, then return.
*/
public static void run(Connection connection) {
- YamlLanguageServer server = new YamlLanguageServer();
+ ManifestYamlLanguageServer server = new ManifestYamlLanguageServer();
LoggingJsonAdapter jsonServer = new LoggingJsonAdapter(server);
jsonServer.setMessageLog(new PrintWriter(System.out));
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/YamlLanguageServer.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYamlLanguageServer.java
similarity index 66%
rename from vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/YamlLanguageServer.java
rename to vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYamlLanguageServer.java
index 558b307c0..f024d24aa 100644
--- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/yaml/YamlLanguageServer.java
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYamlLanguageServer.java
@@ -1,15 +1,28 @@
-package org.springframework.ide.vscode.yaml;
+package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
import java.io.StringReader;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import javax.inject.Provider;
+
+import org.springframework.ide.vscode.commons.reconcile.IDocument;
+import org.springframework.ide.vscode.commons.reconcile.IProblemCollector;
+import org.springframework.ide.vscode.commons.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.util.Futures;
import org.springframework.ide.vscode.util.SimpleLanguageServer;
import org.springframework.ide.vscode.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.util.TextDocument;
+import org.springframework.ide.vscode.yaml.ErrorCodes;
+import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
+import org.springframework.ide.vscode.yaml.reconcile.SchemaBasedYamlASTReconciler;
+import org.springframework.ide.vscode.yaml.reconcile.YamlASTReconciler;
+import org.springframework.ide.vscode.yaml.reconcile.YamlSchemaBasedReconcileEngine;
+import org.springframework.ide.vscode.yaml.schema.YValueHint;
+import org.springframework.ide.vscode.yaml.schema.YamlSchema;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.MarkedYAMLException;
import org.yaml.snakeyaml.error.YAMLException;
@@ -29,15 +42,17 @@ import io.typefox.lsapi.RangeImpl;
import io.typefox.lsapi.ServerCapabilities;
import io.typefox.lsapi.ServerCapabilitiesImpl;
-public class YamlLanguageServer extends SimpleLanguageServer {
+public class ManifestYamlLanguageServer extends SimpleLanguageServer {
+ private static final Provider> NO_BUILDPACKS = () -> ImmutableList.of();
+
private Yaml yaml = new Yaml();
- public YamlLanguageServer() {
+ public ManifestYamlLanguageServer() {
SimpleTextDocumentService documents = getTextDocumentService();
+
// SimpleWorkspaceService workspace = getWorkspaceService();
documents.onDidChangeContent(params -> {
- System.out.println("Document changed: "+params);
TextDocument doc = params.getDocument();
validateDocument(documents, doc);
});
@@ -104,55 +119,36 @@ public class YamlLanguageServer extends SimpleLanguageServer {
}
private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) {
- List diagnostics = reconcile(documents, doc);
- documents.publishDiagnostics(doc, diagnostics);
+ IProblemCollector problems = new IProblemCollector() {
+
+ private List diagnostics = new ArrayList<>();
+
+ @Override
+ public void endCollecting() {
+ documents.publishDiagnostics(doc, diagnostics);
+ }
+
+ @Override
+ public void beginCollecting() {
+ diagnostics.clear();
+ }
+
+ @Override
+ public void accept(ReconcileProblem problem) {
+ DiagnosticImpl d = new DiagnosticImpl();
+ d.setCode(problem.getCode());
+ d.setMessage(problem.getMessage());
+ d.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
+ diagnostics.add(d);
+ }
+ };
+
+ YamlSchema schema = new ManifestYmlSchema(NO_BUILDPACKS);
+ YamlASTProvider parser = new YamlParser(yaml);
+ YamlSchemaBasedReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema);
+ engine.reconcile(doc, problems);
}
- protected List reconcile(SimpleTextDocumentService documents, TextDocument doc) {
- try {
- Iterator asts = yaml.composeAll(new StringReader(doc.getText())).iterator();
- while (asts.hasNext()) {
- asts.next();
- }
- return ImmutableList.of();
- } catch (YAMLException e) {
- return ImmutableList.of(parseError(e));
- }
- }
-
- private DiagnosticImpl parseError(YAMLException e) {
- DiagnosticImpl d = new DiagnosticImpl();
- d.setMessage(getMessage(e));
- d.setRange(getRange(e));
- d.setSeverity(Diagnostic.SEVERITY_ERROR);
- d.setCode(ErrorCodes.YAML_SYNTAX_ERROR);
- d.setSource("yaml");
- return d;
- }
-
- private String getMessage(YAMLException e) {
- if (e instanceof MarkedYAMLException) {
- return ((MarkedYAMLException) e).getProblem();
- }
- return e.getMessage();
- }
-
- private RangeImpl getRange(YAMLException _e) {
- if (_e instanceof MarkedYAMLException) {
- MarkedYAMLException e = (MarkedYAMLException) _e;
-
- PositionImpl start = new PositionImpl();
- start.setLine(e.getProblemMark().getLine());
- start.setCharacter(e.getProblemMark().getColumn());
-
- RangeImpl rng = new RangeImpl();
- rng.setStart(start);
- rng.setEnd(start);
- return rng;
- }
- return null;
- }
-
@Override
protected ServerCapabilitiesImpl getServerCapabilities() {
ServerCapabilitiesImpl c = new ServerCapabilitiesImpl();
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlSchema.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlSchema.java
new file mode 100644
index 000000000..7b9e82bae
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlSchema.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.cloudfoundry.manifest.editor;
+
+import java.util.Collection;
+import java.util.Set;
+
+import javax.inject.Provider;
+
+import org.springframework.ide.vscode.yaml.schema.YType;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory.YAtomicType;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory.YBeanType;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory.YTypedPropertyImpl;
+import org.springframework.ide.vscode.yaml.schema.YTypeUtil;
+import org.springframework.ide.vscode.yaml.schema.YValueHint;
+import org.springframework.ide.vscode.yaml.schema.YamlSchema;
+import org.springframework.ide.vscode.yaml.util.Description;
+import org.springframework.ide.vscode.yaml.util.DescriptionProviders;
+
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * @author Kris De Volder
+ */
+public class ManifestYmlSchema implements YamlSchema {
+
+ private final YBeanType TOPLEVEL_TYPE;
+ private final YTypeUtil TYPE_UTIL;
+ private final Provider> buildpackProvider;
+
+ private static final Set TOPLEVEL_EXCLUDED = ImmutableSet.of(
+ "name", "host", "hosts"
+ );
+
+ public ManifestYmlSchema(Provider> buildpackProvider) {
+ this.buildpackProvider = buildpackProvider;
+ YTypeFactory f = new YTypeFactory();
+ TYPE_UTIL = f.TYPE_UTIL;
+
+ // define schema types
+ TOPLEVEL_TYPE = f.ybean("manifest.yml schema");
+
+ YBeanType application = f.ybean("Application");
+ YAtomicType t_path = f.yatomic("Path");
+
+ YAtomicType t_buildpack = f.yatomic("Buildpack");
+
+ t_buildpack.addHintProvider(this.buildpackProvider);
+
+ YAtomicType t_boolean = f.yenum("boolean", "true", "false");
+ YType t_string = f.yatomic("String");
+ YType t_strings = f.yseq(t_string);
+
+ YAtomicType t_memory = f.yatomic("Memory");
+ t_memory.addHints("256M", "512M", "1024M");
+ t_memory.parseWith(ManifestYmlValueParsers.MEMORY);
+
+ YAtomicType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer");
+ t_strictly_pos_integer.parseWith(ManifestYmlValueParsers.integerAtLeast(1));
+
+ YAtomicType t_pos_integer = f.yatomic("Positive Integer");
+ t_pos_integer.parseWith(ManifestYmlValueParsers.POS_INTEGER);
+
+ YType t_env = f.ymap(t_string, t_string);
+
+ // define schema structure...
+ TOPLEVEL_TYPE.addProperty("applications", f.yseq(application));
+ TOPLEVEL_TYPE.addProperty("inherit", t_string, descriptionFor("inherit"));
+
+ YTypedPropertyImpl[] props = {
+ f.yprop("buildpack", t_buildpack),
+ f.yprop("command", t_string),
+ f.yprop("disk_quota", t_memory),
+ f.yprop("domain", t_string),
+ f.yprop("domains", t_strings),
+ f.yprop("env", t_env),
+ f.yprop("host", t_string),
+ f.yprop("hosts", t_strings),
+ f.yprop("instances", t_strictly_pos_integer),
+ f.yprop("memory", t_memory),
+ f.yprop("name", t_string),
+ f.yprop("no-hostname", t_boolean),
+ f.yprop("no-route", t_boolean),
+ f.yprop("path", t_path),
+ f.yprop("random-route", t_boolean),
+ f.yprop("services", t_strings),
+ f.yprop("stack", t_string),
+ f.yprop("timeout", t_pos_integer)
+ };
+
+ for (YTypedPropertyImpl prop : props) {
+ prop.setDescriptionProvider(descriptionFor(prop));
+ if (!TOPLEVEL_EXCLUDED.contains(prop.getName())) {
+ TOPLEVEL_TYPE.addProperty(prop);
+ }
+ application.addProperty(prop);
+ }
+ }
+
+ private Provider descriptionFor(String propName) {
+ return DescriptionProviders.fromClasspath(this.getClass(), "/description-by-prop-name/"+propName+".html");
+ }
+
+ private Provider descriptionFor(YTypedPropertyImpl prop) {
+ return descriptionFor(prop.getName());
+ }
+
+ @Override
+ public YBeanType getTopLevelType() {
+ return TOPLEVEL_TYPE;
+ }
+
+ @Override
+ public YTypeUtil getTypeUtil() {
+ return TYPE_UTIL;
+ }
+}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlValueParsers.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlValueParsers.java
new file mode 100644
index 000000000..765b3ed6f
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/ManifestYmlValueParsers.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.cloudfoundry.manifest.editor;
+
+import java.util.Set;
+
+import org.springframework.ide.vscode.util.Assert;
+import org.springframework.ide.vscode.yaml.util.ValueParser;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+/**
+ * Methods and constants to create/get parsers for some atomic types
+ * used in manifest yml schema.
+ *
+ * @author Kris De Volder
+ */
+public class ManifestYmlValueParsers {
+
+ public static final ValueParser POS_INTEGER = integerRange(0, null);
+
+ public static final ValueParser MEMORY = new ValueParser() {
+
+ private final ImmutableSet GIGABYTE = ImmutableSet.of("G", "GB");
+ private final ImmutableSet MEGABYTE = ImmutableSet.of("M", "MB");
+ private final Set UNITS = Sets.union(GIGABYTE, MEGABYTE);
+
+ @Override
+ public Object parse(String str) {
+ str = str.trim();
+ String unit = getUnit(str.toUpperCase());
+ if (unit==null) {
+ throw new NumberFormatException(
+ "'"+str+"' doesn't end with a valid unit of memory ('M', 'MB', 'G' or 'GB')"
+ );
+ }
+ str = str.substring(0, str.length()-unit.length());
+ int unitSize = GIGABYTE.contains(unit)?1024:1;
+ int value = Integer.parseInt(str);
+ if (value<0) {
+ throw new NumberFormatException("Negative value is not allowed");
+ }
+ return value * unitSize;
+ }
+
+ private String getUnit(String str) {
+ for (String u : UNITS) {
+ if (str.endsWith(u)) {
+ return u;
+ }
+ }
+ return null;
+ }
+ };
+
+ public static ValueParser integerAtLeast(final Integer lowerBound) {
+ return integerRange(lowerBound, null);
+ }
+
+ public static ValueParser integerRange(final Integer lowerBound, final Integer upperBound) {
+ Assert.isLegal(lowerBound==null || upperBound==null || lowerBound <= upperBound);
+ return new ValueParser() {
+ @Override
+ public Object parse(String str) {
+ int value = Integer.parseInt(str);
+ if (lowerBound!=null && valueupperBound) {
+ throw new NumberFormatException("Value must be at most "+upperBound);
+ }
+ return value;
+ }
+ };
+ }
+
+}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/YamlParser.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/YamlParser.java
new file mode 100644
index 000000000..2ffef0bda
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/cloudfoundry/manifest/editor/YamlParser.java
@@ -0,0 +1,23 @@
+package org.springframework.ide.vscode.cloudfoundry.manifest.editor;
+
+import java.io.StringReader;
+
+import org.springframework.ide.vscode.commons.reconcile.IDocument;
+import org.springframework.ide.vscode.yaml.ast.YamlASTProvider;
+import org.springframework.ide.vscode.yaml.ast.YamlFileAST;
+import org.yaml.snakeyaml.Yaml;
+
+public class YamlParser implements YamlASTProvider {
+
+ private Yaml yaml;
+
+ public YamlParser(Yaml yaml) {
+ this.yaml = yaml;
+ }
+
+ @Override
+ public YamlFileAST getAST(IDocument doc) throws Exception {
+ return new YamlFileAST(yaml.composeAll(new StringReader(doc.get())));
+ }
+
+}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/buildpack.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/buildpack.html
new file mode 100644
index 000000000..2f3fb4cb2
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/buildpack.html
@@ -0,0 +1,11 @@
+If your application requires a custom buildpack, you can use the buildpack attribute to specify its URL or name:
+
+
+---
+ ...
+ buildpack: buildpack_URL
+
+
+Note: The cf buildpacks command lists the buildpacks that you can refer to by name in a manifest or a command line option.
+
+The command line option that overrides this attribute is -b.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/command.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/command.html
new file mode 100644
index 000000000..aab1e4651
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/command.html
@@ -0,0 +1,34 @@
+Some languages and frameworks require that you provide a custom command to start an application. Refer to the buildpack documentation to determine if you need to provide a custom start command.
+
+You can provide the custom start command in your application manifest or on the command line.
+
+To specify the custom start command in your application manifest, add it in the command: START-COMMAND format as the following example shows:
+
+
+---
+ ...
+ command: bundle exec rake VERBOSE=true
+
+
+On the command line, use the -c option to specify the custom start command as the following example shows:
+
+
+$ cf push my-app -c "bundle exec rake VERBOSE=true"
+
+
+Note: The -c option with a value of ‘null’ forces cf push to use the buildpack start command. See About Starting Applications for more information.
+
+If you override the start command for a Buildpack application, Linux uses
+bash -c YOUR-COMMAND to invoke your application.
+If you override the start command for a Docker application, Linux uses sh -c YOUR-COMMAND to invoke your application.
+Because of this, if you override a start command, you should prefix exec to the final command in your custom composite start command.
+
+exec causes the last command to become the root process of your application. The Cloud Foundry Updates and Your Application section of the Considerations for Designing and Running an Application in the Cloud topic explains why your application should handle a termination signal during Cloud Foundry updates.
+Without an exec statement, the parent process remains as the implied bash process, and does not propagate signals to your application process.
+
+For example, both of the following composite start commands run database migrations when the first instance of the app starts, then start the app to serve requests, but they behave differently on graceful shutdown.
+
+
+bin/rake cf:on_first_instance db:migrate && bin/rails server -p $PORT -e $RAILS_ENV: The process tree is bash -> ruby, so on graceful shutdown only the bash process receives the TERM signal, and not the ruby process.
+bin/rake cf:on_first_instance db:migrate && exec bin/rails server -p $PORT -e $RAILS_ENV: Because of the exec prefix on the final command, the ruby process invoked by rails takes over the bash process managing the execution of the composite command. The process tree is only ruby, so the ruby web server receives the TERM signal can shutdown gracefully for 10 seconds.
+
\ No newline at end of file
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/disk_quota.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/disk_quota.html
new file mode 100644
index 000000000..f2463dbea
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/disk_quota.html
@@ -0,0 +1,9 @@
+Use the disk_quota attribute to allocate the disk space for your app instance. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case.
+
+
+---
+ ...
+ disk_quota: 1024M
+
+
+The command line option that overrides this attribute is -k.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domain.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domain.html
new file mode 100644
index 000000000..8f1af1c01
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domain.html
@@ -0,0 +1,27 @@
+Every cf push deploys applications to one particular Cloud Foundry instance.
+Every Cloud Foundry instance may have a shared domain set by an admin.
+Unless you specify a domain, Cloud Foundry incorporates that shared domain in the route to your application.
+
+You can use the domain attribute when you want your application to be served from a domain other than the default shared domain.
+
+
+---
+ ...
+ domain: unique-example.com
+
+
+The command line option that overrides this attribute is -d.
+
+The domains attribute
+
+Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.
+
+
+---
+ ...
+ domains:
+ - domain-example1.com
+ - domain-example2.org
+
+
+The command line option that overrides this attribute is -d.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domains.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domains.html
new file mode 100644
index 000000000..0df6ef33b
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/domains.html
@@ -0,0 +1,10 @@
+Use the domains attribute to provide multiple domains. If you define both domain and domains attributes, Cloud Foundry creates routes for domains defined in both of these fields.
+
+---
+ ...
+ domains:
+ - domain-example1.com
+ - domain-example2.org
+
+
+The command line option that overrides this attribute is -d.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/env.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/env.html
new file mode 100644
index 000000000..4c35fe3d7
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/env.html
@@ -0,0 +1,28 @@
+The env block consists of a heading, then one or more environment variable/value pairs.
+
+For example:
+
+
+---
+ ...
+ env:
+ RAILS_ENV: production
+ RACK_ENV: production
+
+
+cf push deploys the application to a container on the server. The variables belong to the container environment.
+
+While the application is running, Cloud Foundry allows you to operate on environment variables.
+
+
+- View all variables:
cf env my-app
+- Set an individual variable:
cf set-env my-app my-variable_name my-variable_value
+- Unset an individual variable:
cf unset-env my-app my-variable_name my-variable_value
+
+
+Environment variables interact with manifests in the following ways:
+
+
+When you deploy an application for the first time, Cloud Foundry reads the variables described in the environment block of the manifest, and adds them to the environment of the container where the application is deployed.
+When you stop and then restart an application, its environment variables persist.
+
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/host.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/host.html
new file mode 100644
index 000000000..04de4aa7b
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/host.html
@@ -0,0 +1,9 @@
+Use the host attribute to provide a hostname, or subdomain, in the form of a string. This segment of a route helps to ensure that the route is unique. If you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN.
+
+
+---
+ ...
+ host: my-app
+
+
+The command line option that overrides this attribute is -n.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/hosts.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/hosts.html
new file mode 100644
index 000000000..de25f32ef
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/hosts.html
@@ -0,0 +1,11 @@
+Use the hosts attribute to provide multiple hostnames, or subdomains. Each hostname generates a unique route for the app. hosts can be used in conjunction with host. If you define both attributes, Cloud Foundry creates routes for hostnames defined in both host and hosts.
+
+
+---
+ ...
+ hosts:
+ - app_host1
+ - app_host2
+
+
+The command line option that overrides this attribute is -n.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/inherit.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/inherit.html
new file mode 100644
index 000000000..184350b67
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/inherit.html
@@ -0,0 +1,62 @@
+A single manifest can describe multiple applications. Another powerful technique is to create multiple manifests with inheritance. Here, manifests have parent-child relationships such that children inherit descriptions from a parent. Children can use inherited descriptions as-is, extend them, or override them.
+
+Content in the child manifest overrides content in the parent manifest, if the two conflict.
+
+This technique helps in these and other scenarios:
+
+
+An application has a set of different deployment modes, such as debug, local, and public. Each deployment mode is described in child manifests that extend the settings in a base parent manifest.
+An application is packaged with a basic configuration described by a parent manifest. Users can extend the basic configuration by creating child manifests that add new properties or override those in the parent manifest.
+
+
+The benefits of multiple manifests with inheritance are similar to those of minimizing duplicated content within single manifests. With inheritance, though, we “promote” content by placing it in the parent manifest.
+
+Every child manifest must contain an “inherit” line that points to the parent manifest. Place the inherit line immediately after the three dashes at the top of the child manifest. For example, every child of a parent manifest called base-manifest.yml begins like this:
+
+---
+ ...
+ inherit: base-manifest.yml
+
+
+You do not need to add anything to the parent manifest.
+
+In the simple example below, a parent manifest gives each application minimal resources, while a production child manifest scales them up.
+
+simple-base-manifest.yml
+
+---
+path: .
+domain: shared-domain.com
+memory: 256M
+instances: 1
+services:
+- singular-backend
+
+# app-specific configuration
+applications:
+ - name: springtock
+ host: 765shower
+ path: ./april/build/libs/april-weather.war
+ - name: wintertick
+ host: 321flurry
+ path: ./december/target/december-weather.war
+
+
+simple-prod-manifest.yml
+
+---
+inherit: simple-base-manifest.yml
+applications:
+ - name:springstorm
+ memory: 512M
+ instances: 1
+ host: 765deluge
+ path: ./april/build/libs/april-weather.war
+ - name: winterblast
+ memory: 1G
+ instances: 2
+ host: 321blizzard
+ path: ./december/target/december-weather.war
+
+
+Note: Inheritance can add an additional level of complexity to manifest creation and maintenance. Comments that precisely explain how the child manifest extends or overrides the descriptions in the parent manifest can alleviate this complexity.
\ No newline at end of file
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/instances.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/instances.html
new file mode 100644
index 000000000..021991fa5
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/instances.html
@@ -0,0 +1,11 @@
+
Use the instances attribute to specify the number of app instances that you want to start upon push:
+
+
+---
+ ...
+ instances: 2
+
+
+We recommend that you run at least two instances of any apps for which fault tolerance matters.
+
+The command line option that overrides this attribute is -i.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/memory.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/memory.html
new file mode 100644
index 000000000..e25ee286e
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/memory.html
@@ -0,0 +1,11 @@
+Use the memory attribute to specify the memory limit for all instances of an app. This attribute requires a unit of measurement: M, MB, G, or GB, in upper case or lower case. For example:
+
+
+---
+ ...
+ memory: 1024M
+
+
+The default memory limit is 1G. You might want to specify a smaller limit to conserve quota space if you know that your app instances do not require 1G of memory.
+
+The command line option that overrides this attribute is -m.
\ No newline at end of file
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/name.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/name.html
new file mode 100644
index 000000000..39d0aac6a
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/name.html
@@ -0,0 +1,10 @@
+The name attribute is the only required attribute
+for an application in a manifest file.
+
+This is an example of a minimal manifest:
+
+
+---
+applications:
+- name: nifty-gui
+
\ No newline at end of file
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-hostname.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-hostname.html
new file mode 100644
index 000000000..b3a6047de
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-hostname.html
@@ -0,0 +1,9 @@
+By default, if you do not provide a hostname, the URL for the app takes the form of APP-NAME.DOMAIN. If you want to override this and map the root domain to this app then you can set no-hostname as true.
+
+
+---
+ ...
+ no-hostname: true
+
+
+The command line option that corresponds to this attribute is --no-hostname.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-route.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-route.html
new file mode 100644
index 000000000..d631024fd
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/no-route.html
@@ -0,0 +1,18 @@
+By default, cf push assigns a route to every application. But some applications process data while running in the background, and should not be assigned routes.
+
+You can use the no-route attribute with a value of true to prevent a route from being created for your application.
+
+
+---
+ ...
+ no-route: true
+
+
+The command line option that corresponds to this attribute is --no-route.
+
+If you find that an application which should not have a route does have one:
+
+
+- Remove the route using the
cf unmap-route command.
+- Push the app again with the
no-route: true attribute in the manifest or the --no-route command line option.
+
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/path.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/path.html
new file mode 100644
index 000000000..a1b1cfb23
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/path.html
@@ -0,0 +1,9 @@
+You can use the path attribute to tell Cloud Foundry where to find your application. This is generally not necessary when you run cf push from the directory where an application is located.
+
+
+---
+ ...
+ path: path_to_application_bits
+
+
+The command line option that overrides this attribute is -p.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/random-route.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/random-route.html
new file mode 100644
index 000000000..ef432da78
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/random-route.html
@@ -0,0 +1,11 @@
+Use the random-route attribute to create a URL that includes the app name and
+random words.
+Use this attribute to avoid URL collision when pushing the same app to multiple spaces, or to avoid managing app URLs.
+
+The command line option that corresponds to this attribute is --random-route.
+
+
+---
+ ...
+ random-route: true
+
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/services.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/services.html
new file mode 100644
index 000000000..c10713143
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/services.html
@@ -0,0 +1,18 @@
+Applications can bind to services such as databases, messaging, and key-value stores.
+
+Applications are deployed into App Spaces. An application can only bind to services instances that exist in the target App Space before the application is deployed.
+
+The services block consists of a heading, then one or more service instance names.
+
+Whoever creates the service chooses the service instance names. These names can convey logical information, as in backend_queue, describe the nature of the service, as in mysql_5.x, or do neither, as in the example below.
+
+
+---
+ ...
+ services:
+ - instance_ABC
+ - instance_XYZ
+
+
+Binding to a service instance is a special case of setting an environment
+variable, namely VCAP_SERVICES.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/stack.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/stack.html
new file mode 100644
index 000000000..021e2788a
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/stack.html
@@ -0,0 +1,11 @@
+
Use the stack attribute to specify which stack to deploy your application to.
+
+To see a list of available stacks, run cf stacks from the cf cli.
+
+
+---
+ ...
+ stack: cflinuxfs2
+
+
+The command line option that overrides this attribute is -s.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/timeout.html b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/timeout.html
new file mode 100644
index 000000000..063e26405
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/main/resources/description-by-prop-name/timeout.html
@@ -0,0 +1,14 @@
+The timeout attribute defines the number of seconds Cloud Foundry allocates for starting your application.
+
+For example:
+
+
+---
+ ...
+ timeout: 80
+
+
+You can increase the timeout length for very large apps that require more time to start. The default timeout is 60 seconds with an upper bound of 180 seconds.
+Note: Administrators can set the upper bound of the maximum_health_check_timeout property to any value. Any changes to Cloud Controller properties in the deployment manifest require running bosh deploy.
+
+The command line option that overrides the timeout attribute for the shell is -t. Manifest values still apply to applications pushed to the deployment.
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
deleted file mode 100644
index d7e81a611..000000000
--- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/LanguageServerHarness.java
+++ /dev/null
@@ -1,246 +0,0 @@
-package org.springframework.ide.vscode.testharness;
-
-import java.io.File;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.Callable;
-import java.util.stream.Collectors;
-
-import org.assertj.core.api.Condition;
-
-import io.typefox.lsapi.ClientCapabilitiesImpl;
-import io.typefox.lsapi.CompletionItem;
-import io.typefox.lsapi.CompletionList;
-import io.typefox.lsapi.Diagnostic;
-import io.typefox.lsapi.DidChangeTextDocumentParamsImpl;
-import io.typefox.lsapi.DidOpenTextDocumentParamsImpl;
-import io.typefox.lsapi.InitializeParamsImpl;
-import io.typefox.lsapi.InitializeResult;
-import io.typefox.lsapi.Position;
-import io.typefox.lsapi.PositionImpl;
-import io.typefox.lsapi.PublishDiagnosticsParams;
-import io.typefox.lsapi.Range;
-import io.typefox.lsapi.ServerCapabilities;
-import io.typefox.lsapi.TextDocumentContentChangeEventImpl;
-import io.typefox.lsapi.TextDocumentItemImpl;
-import io.typefox.lsapi.TextDocumentPositionParamsImpl;
-import io.typefox.lsapi.VersionedTextDocumentIdentifierImpl;
-import io.typefox.lsapi.services.LanguageServer;
-
-public class LanguageServerHarness {
-
- //Warning this 'harness' is not very good yet. It just implements bare minimum to
- // be able to test the MyLanguageServer example.
-
- private Random random = new Random();
-
- private Callable extends LanguageServer> factory;
-
- private LanguageServer server;
-
- private InitializeResult initResult;
-
- private Map documents = new HashMap<>();
- private Map diagnostics = new HashMap<>();
-
- public LanguageServerHarness(Callable extends LanguageServer> factory) throws Exception {
- this.factory = factory;
- }
-
- public synchronized TextDocumentInfo getOrReadFile(File file) throws Exception {
- String uri = file.toURI().toString();
- TextDocumentInfo d = documents.get(uri);
- if (d==null) {
- documents.put(uri, d = readFile(file));
- }
- return d;
- }
-
- public TextDocumentInfo readFile(File file) throws Exception {
- byte[] encoded = Files.readAllBytes(file.toPath());
- String content = new String(encoded, getEncoding());
- TextDocumentItemImpl document = new TextDocumentItemImpl();
- document.setText(content);
- document.setUri(file.toURI().toString());
- document.setVersion(getFirstVersion());
- document.setLanguageId(getLanguageId());
- return new TextDocumentInfo(document);
- }
-
- private synchronized TextDocumentItemImpl setDocumentContent(String uri, String newContent) {
- TextDocumentInfo o = documents.get(uri);
- TextDocumentItemImpl n = new TextDocumentItemImpl();
- n.setLanguageId(o.getLanguageId());
- n.setText(newContent);
- n.setVersion(o.getVersion()+1);
- n.setUri(o.getUri());
- documents.put(uri, new TextDocumentInfo(n));
- return n;
- }
-
- protected Charset getEncoding() {
- return Charset.forName("utf8");
- }
-
- protected String getLanguageId() {
- return "plaintext";
- }
-
- protected String getFileExtension() {
- return ".txt";
- }
-
- private synchronized void receiveDiagnostics(PublishDiagnosticsParams diags) {
- this.diagnostics.put(diags.getUri(), diags);
- }
-
- public InitializeResult intialize(File workspaceRoot) throws Exception {
- server = factory.call();
- int parentPid = random.nextInt(40000)+1000;
- InitializeParamsImpl initParams = new InitializeParamsImpl();
- initParams.setRootPath(workspaceRoot== null?null:workspaceRoot.toString());
- initParams.setProcessId(parentPid);
- ClientCapabilitiesImpl clientCap = new ClientCapabilitiesImpl();
- initParams.setCapabilities(clientCap);
- initResult = server.initialize(initParams).get();
-
- server.getTextDocumentService().onPublishDiagnostics(this::receiveDiagnostics);
- return initResult;
- }
-
- public TextDocumentInfo openDocument(TextDocumentInfo documentInfo) throws Exception {
- DidOpenTextDocumentParamsImpl didOpen = new DidOpenTextDocumentParamsImpl();
- didOpen.setTextDocument(documentInfo.getDocument());
- didOpen.setText(documentInfo.getText());
- didOpen.setUri(documentInfo.getUri());
- server.getTextDocumentService().didOpen(didOpen);
- return documentInfo;
- }
-
- public TextDocumentInfo openDocument(File file) throws Exception {
- return openDocument(getOrReadFile(file));
- }
-
- public TextDocumentInfo changeDocument(String uri, String newContent) throws Exception {
- TextDocumentItemImpl textDocument = setDocumentContent(uri, newContent);
- DidChangeTextDocumentParamsImpl didChange = new DidChangeTextDocumentParamsImpl();
- VersionedTextDocumentIdentifierImpl version = new VersionedTextDocumentIdentifierImpl();
- version.setUri(uri);
- version.setVersion(textDocument.getVersion());
- didChange.setTextDocument(version);
- switch (getDocumentSyncMode()) {
- case ServerCapabilities.SYNC_NONE:
- break; //nothing todo
- case ServerCapabilities.SYNC_INCREMENTAL:
- throw new IllegalStateException("Incremental sync not yet supported by this test harness");
- case ServerCapabilities.SYNC_FULL:
- TextDocumentContentChangeEventImpl change = new TextDocumentContentChangeEventImpl();
- change.setText(newContent);
- didChange.setContentChanges(Collections.singletonList(change));
- break;
- default:
- throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
- }
- server.getTextDocumentService().didChange(didChange);
- return documents.get(uri);
- }
-
- private int getDocumentSyncMode() {
- Integer mode = initResult.getCapabilities().getTextDocumentSync();
- return mode==null ? ServerCapabilities.SYNC_NONE : mode;
- }
-
- public PublishDiagnosticsParams getDiagnostics(TextDocumentInfo doc) {
- return diagnostics.get(doc.getUri());
- }
-
- public static Condition isDiagnosticWithSeverity(int severity) {
- return new Condition<>(
- (d) -> d.getSeverity()==severity,
- "Diagnostic with severity '"+severity+"'"
- );
- }
-
- public static Condition isDiagnosticCovering(TextDocumentInfo doc, String string) {
- return new Condition<>(
- (d) -> isDiagnosticCovering(d, doc, string),
- "Diagnostic covering '"+string+"'"
- );
- }
-
- public static final Condition isWarning = isDiagnosticWithSeverity(Diagnostic.SEVERITY_WARNING);
-
- public static boolean isDiagnosticCovering(Diagnostic diag, TextDocumentInfo doc, String string) {
- Range rng = diag.getRange();
- String actualText = doc.getText(rng);
- return string.equals(actualText);
- }
-
- public static Condition isDiagnosticOnLine(int line) {
- return new Condition<>(
- (d) -> d.getRange().getStart().getLine()==line,
- "Diagnostic on line "+line
- );
- }
-
- public CompletionList getCompletions(TextDocumentInfo doc, Position cursor) throws Exception {
- TextDocumentPositionParamsImpl params = new TextDocumentPositionParamsImpl();
- params.setPosition(toImpl(cursor));
- params.setTextDocument(doc.getId());
- return server.getTextDocumentService().completion(params).get();
- }
-
- private PositionImpl toImpl(Position pos) {
- if (pos instanceof PositionImpl) {
- return (PositionImpl) pos;
- } else {
- PositionImpl imp = new PositionImpl();
- imp.setCharacter(pos.getCharacter());
- imp.setLine(pos.getLine());
- return imp;
- }
- }
-
- private CompletionItem resolveCompletionItem(CompletionItem unresolved) {
- try {
- return server.getTextDocumentService().resolveCompletionItem(unresolved).get();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- public List resolveCompletions(CompletionList completions) {
- return completions.getItems().stream()
- .map(this::resolveCompletionItem)
- .collect(Collectors.toList());
- }
-
- public Editor newEditor(String contents) throws Exception {
- return new Editor(this, contents);
- }
-
- public synchronized TextDocumentInfo createWorkingCopy(String contents) throws Exception {
- TextDocumentItemImpl doc = new TextDocumentItemImpl();
- doc.setLanguageId(getLanguageId());
- doc.setText(contents);
- doc.setUri(createTempUri());
- doc.setVersion(getFirstVersion());
- TextDocumentInfo docinfo = new TextDocumentInfo(doc);
- documents.put(docinfo.getUri(), docinfo);
- return docinfo;
- }
-
- protected int getFirstVersion() {
- return 1;
- }
-
- protected String createTempUri() throws Exception {
- return File.createTempFile("workingcopy", getFileExtension()).toURI().toString();
- }
-
-}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java
deleted file mode 100644
index 1062f6bfb..000000000
--- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/testharness/TextDocumentInfo.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package org.springframework.ide.vscode.testharness;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import io.typefox.lsapi.Position;
-import io.typefox.lsapi.PositionImpl;
-import io.typefox.lsapi.Range;
-import io.typefox.lsapi.TextDocumentIdentifierImpl;
-import io.typefox.lsapi.TextDocumentItemImpl;
-
-public class TextDocumentInfo {
-
- Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r");
-
- private final TextDocumentItemImpl document;
-
- private int[] _lineStarts;
-
- public TextDocumentInfo(TextDocumentItemImpl document) {
- this.document = document;
- }
-
- public String getLanguageId() {
- return getDocument().getLanguageId();
- }
-
- public int getVersion() {
- return getDocument().getVersion();
- }
-
- public String getText() {
- return getDocument().getText();
- }
-
- public String getUri() {
- return getDocument().getUri();
- }
-
- public TextDocumentItemImpl getDocument() {
- return document;
- }
-
- public String getText(Range rng) {
- int start = toOffset(rng.getStart());
- int end = toOffset(rng.getEnd());
- return getText().substring(start, end);
- }
-
- public int toOffset(Position p) {
- int startOfLine = startOfLine(p.getLine());
- return startOfLine+p.getCharacter();
- }
-
- private int startOfLine(int line) {
- return lineStarts()[line];
- }
-
- private int[] lineStarts() {
- if (_lineStarts==null) {
- _lineStarts = parseLines();
- }
- return _lineStarts;
- }
-
- private int[] parseLines() {
- List lineStarts = new ArrayList<>();
- lineStarts.add(0);
- Matcher matcher = NEWLINE.matcher(getText());
- int pos = 0;
- while (matcher.find(pos)) {
- lineStarts.add(pos = matcher.end());
- }
- int[] array = new int[lineStarts.size()];
- for (int i = 0; i < array.length; i++) {
- array[i] = lineStarts.get(i);
- }
- return array;
- }
-
- /**
- * Find and return the (first) position of a given text snippet in the
- * document.
- *
- * @return The position, or null if the snippet can't be found.
- */
- public Position positionOf(String snippet) {
- int offset = getText().indexOf(snippet);
- if (offset>=0) {
- return toPosition(offset);
- }
- return null;
- }
-
- public Position toPosition(int offset) {
- int line = lineNumber(offset);
- int startOfLine = startOfLine(line);
- int column = offset - startOfLine;
- PositionImpl pos = new PositionImpl();
- pos.setCharacter(column);
- pos.setLine(line);
- return pos;
- }
-
- /**
- * Determine the line-number a given offset (i.e. what line is the offset inside of?)
- */
- private int lineNumber(int offset) {
- int[] lineStarts = lineStarts();
- // TODO Could use binary search which is faster
- int lineNumber = 0;
- for (int i = 0; i < lineStarts.length; i++) {
- if (lineStarts[i]<=offset) {
- lineNumber = i;
- } else {
- return lineNumber;
- }
- }
- return lineNumber;
- }
-
- public TextDocumentIdentifierImpl getId() {
- TextDocumentIdentifierImpl id = new TextDocumentIdentifierImpl();
- id.setUri(getUri());
- return id;
- }
-
-}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ApplicationYamlEditorTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ApplicationYamlEditorTest.java
deleted file mode 100644
index 11c33e3ca..000000000
--- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ApplicationYamlEditorTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.springframework.ide.vscode.yaml;
-
-import org.junit.Test;
-import org.springframework.ide.vscode.testharness.Editor;
-import org.springframework.ide.vscode.testharness.LanguageServerHarness;
-import org.springframework.ide.vscode.yaml.YamlLanguageServer;
-
-/**
- * This class is a placeholder where we will attempt to copy and port
- * as many tests a possible from
- * org.springframework.ide.eclipse.boot.properties.editor.test.YamlEditorTests
- *
- * @author Kris De Volder
- */
-public class ApplicationYamlEditorTest {
-
- @Test public void testReconcileCatchesParseError() throws Exception {
- LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
- harness.intialize(null);
-
- Editor editor = harness.newEditor(
- "somemap: val\n"+
- "- sequence"
- );
- editor.assertProblems(
- "-|expected "
- );
- }
-
- @Test public void linterRunsOnDocumentOpenAndChange() throws Exception {
- LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
- harness.intialize(null);
-
- Editor editor = harness.newEditor(
- "somemap: val\n"+
- "- sequence"
- );
-
- editor.assertProblems(
- "-|expected "
- );
-
- editor.setText(
- "- sequence\n" +
- "zomemap: val"
- );
-
- editor.assertProblems(
- "z|expected "
- );
- }
-}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlEditorTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlEditorTest.java
new file mode 100644
index 000000000..06f29bee0
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlEditorTest.java
@@ -0,0 +1,383 @@
+/*******************************************************************************
+ * Copyright (c) 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.vscode.yaml;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.springframework.ide.vscode.cloudfoundry.manifest.editor.ManifestYamlLanguageServer;
+import org.springframework.ide.vscode.testharness.Editor;
+import org.springframework.ide.vscode.testharness.LanguageServerHarness;
+
+public class ManifestYamlEditorTest {
+
+ LanguageServerHarness harness;
+
+ @Before public void setup() throws Exception {
+ harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
+ harness.intialize(null);
+ }
+
+ @Test public void testReconcileCatchesParseError() throws Exception {
+
+ Editor editor = harness.newEditor(
+ "somemap: val\n"+
+ "- sequence"
+ );
+ editor.assertProblems(
+ "-|expected "
+ );
+ }
+
+ @Test public void reconcileRunsOnDocumentOpenAndChange() throws Exception {
+ LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
+ harness.intialize(null);
+
+ Editor editor = harness.newEditor(
+ "somemap: val\n"+
+ "- sequence"
+ );
+
+ editor.assertProblems(
+ "-|expected "
+ );
+
+ editor.setText(
+ "- sequence\n" +
+ "zomemap: val"
+ );
+
+ editor.assertProblems(
+ "z|expected "
+ );
+ }
+
+ @Test
+ public void reconcileMisSpelledPropertyNames() throws Exception {
+ Editor editor;
+
+ editor = harness.newEditor(
+ "memory: 1G\n" +
+ "aplications:\n" +
+ " - buildpack: zbuildpack\n" +
+ " domain: zdomain\n" +
+ " name: foo"
+ );
+ editor.assertProblems("aplications|Unknown property");
+
+ //mispelled or not allowed at toplevel
+ editor = harness.newEditor(
+ "name: foo\n" +
+ "buildpeck: yahah\n" +
+ "memory: 1G\n" +
+ "memori: 1G\n"
+ );
+ editor.assertProblems(
+ "name|Unknown property",
+ "buildpeck|Unknown property",
+ "memori|Unknown property"
+ );
+
+ //mispelled or not allowed as nested
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: fine\n" +
+ " buildpeck: yahah\n" +
+ " memory: 1G\n" +
+ " memori: 1G\n" +
+ " applications: bad\n"
+ );
+ editor.assertProblems(
+ "buildpeck|Unknown property",
+ "memori|Unknown property",
+ "applications|Unknown property"
+ );
+ }
+
+ @Test
+ public void reconcileStructuralProblems() throws Exception {
+ Editor editor;
+
+ //forgot the 'applications:' heading
+ editor = harness.newEditor(
+ "- name: foo"
+ );
+ editor.assertProblems(
+ "- name: foo|Expecting a 'Map' but found a 'Sequence'"
+ );
+
+ //forgot to make the '-' after applications
+ editor = harness.newEditor(
+ "applications:\n" +
+ " name: foo"
+ );
+ editor.assertProblems(
+ "name: foo|Expecting a 'Sequence' but found a 'Map'"
+ );
+
+ //Using a 'composite' element where a scalar type is expected
+ editor = harness.newEditor(
+ "memory:\n"+
+ "- bad sequence\n" +
+ "buildpack:\n" +
+ " bad: map\n"
+ );
+ editor.assertProblems(
+ "- bad sequence|Expecting a 'Memory' but found a 'Sequence'",
+ "bad: map|Expecting a 'Buildpack' but found a 'Map'"
+ );
+ }
+
+ @Test
+ public void reconcileSimpleTypes() throws Exception {
+ Editor editor;
+
+ //check for 'format' errors:
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: foo\n" +
+ " instances: not a number\n" +
+ " no-route: notBool\n"+
+ " memory: 1024\n" +
+ " disk_quota: 2048\n"
+ );
+ editor.assertProblems(
+ "not a number|Positive Integer",
+ "notBool|boolean",
+ "1024|Memory",
+ "2048|Memory"
+ );
+
+ //check for 'range' errors:
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: foo\n" +
+ " instances: -3\n" +
+ " memory: -1024M\n" +
+ " disk_quota: -2048M\n"
+ );
+ editor.assertProblems(
+ "-3|Positive Integer",
+ "-1024M|Memory",
+ "-2048M|Memory"
+ );
+
+ //check that correct values are indeed accepted
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: foo\n" +
+ " instances: 2\n" +
+ " no-route: true\n"+
+ " memory: 1024M\n" +
+ " disk_quota: 2048MB\n"
+ );
+ editor.assertProblems(/*none*/);
+
+ //check that correct values are indeed accepted
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: foo\n" +
+ " instances: 2\n" +
+ " no-route: false\n" +
+ " memory: 1024m\n" +
+ " disk_quota: 2048mb\n"
+ );
+ editor.assertProblems(/*none*/);
+
+ editor = harness.newEditor(
+ "applications:\n" +
+ "- name: foo\n" +
+ " instances: 2\n" +
+ " memory: 1G\n" +
+ " disk_quota: 2g\n"
+ );
+ editor.assertProblems(/*none*/);
+ }
+
+ @Test @Ignore
+ public void toplevelCompletions() throws Exception {
+ Editor editor;
+ editor = harness.newEditor("<*>");
+ editor.assertCompletions(
+ "applications:\n"+
+ " - <*>",
+ // ---------------
+ "buildpack: <*>",
+ // ---------------
+ "command: <*>",
+ // ---------------
+ "disk_quota: <*>",
+ // ---------------
+ "domain: <*>",
+ // ---------------
+ "domains:\n"+
+ " - <*>",
+ // ---------------
+ "env:\n"+
+ " <*>",
+ // ---------------
+// "host: <*>",
+ // ---------------
+// "hosts: \n"+
+// " - <*>",
+ // ---------------
+ "inherit: <*>",
+ // ---------------
+ "instances: <*>",
+ // ---------------
+ "memory: <*>",
+ // ---------------
+// "name: <*>",
+ // ---------------
+ "no-hostname: <*>",
+ // ---------------
+ "no-route: <*>",
+ // ---------------
+ "path: <*>",
+ // ---------------
+ "random-route: <*>",
+ // ---------------
+ "services:\n"+
+ " - <*>",
+ // ---------------
+ "stack: <*>",
+ // ---------------
+ "timeout: <*>"
+ );
+
+ editor = harness.newEditor("ranro<*>");
+ editor.assertCompletions(
+ "random-route: <*>"
+ );
+ }
+
+ @Test @Ignore
+ public void nestedCompletions() throws Exception {
+ Editor editor;
+ editor = harness.newEditor(
+ "applications:\n" +
+ " - <*>"
+ );
+ editor.assertCompletions(
+ // ---------------
+ "applications:\n" +
+ " - buildpack: <*>",
+ // ---------------
+ "applications:\n" +
+ " - command: <*>",
+ // ---------------
+ "applications:\n" +
+ " - disk_quota: <*>",
+ // ---------------
+ "applications:\n" +
+ " - domain: <*>",
+ // ---------------
+ "applications:\n" +
+ " - domains:\n"+
+ " - <*>",
+ // ---------------
+ "applications:\n" +
+ " - env:\n"+
+ " <*>",
+ // ---------------
+ "applications:\n" +
+ " - host: <*>",
+ // ---------------
+ "applications:\n" +
+ " - hosts:\n"+
+ " - <*>",
+ // ---------------
+ "applications:\n" +
+ " - instances: <*>",
+ // ---------------
+ "applications:\n" +
+ " - memory: <*>",
+ // ---------------
+ "applications:\n" +
+ " - name: <*>",
+ // ---------------
+ "applications:\n" +
+ " - no-hostname: <*>",
+ // ---------------
+ "applications:\n" +
+ " - no-route: <*>",
+ // ---------------
+ "applications:\n" +
+ " - path: <*>",
+ // ---------------
+ "applications:\n" +
+ " - random-route: <*>",
+ // ---------------
+ "applications:\n" +
+ " - services:\n"+
+ " - <*>",
+ // ---------------
+ "applications:\n" +
+ " - stack: <*>",
+ // ---------------
+ "applications:\n" +
+ " - timeout: <*>"
+ );
+ }
+
+ @Test @Ignore
+ public void valueCompletions() throws Exception {
+ assertCompletions("disk_quota: <*>",
+ "disk_quota: 1024M<*>",
+ "disk_quota: 256M<*>",
+ "disk_quota: 512M<*>"
+ );
+ assertCompletions("memory: <*>",
+ "memory: 1024M<*>",
+ "memory: 256M<*>",
+ "memory: 512M<*>"
+ );
+ assertCompletions("no-hostname: <*>",
+ "no-hostname: false<*>",
+ "no-hostname: true<*>"
+ );
+ assertCompletions("no-route: <*>",
+ "no-route: false<*>",
+ "no-route: true<*>"
+ );
+ assertCompletions("random-route: <*>",
+ "random-route: false<*>",
+ "random-route: true<*>"
+ );
+ }
+
+ @Test @Ignore
+ public void hoverInfos() throws Exception {
+ Editor editor = harness.newEditor(
+ "memory: 1G\n" +
+ "applications:\n" +
+ " - buildpack: zbuildpack\n" +
+ " domain: zdomain\n" +
+ " name: foo"
+ );
+ editor.assertIsHoverRegion("memory");
+ editor.assertIsHoverRegion("applications");
+ editor.assertIsHoverRegion("buildpack");
+ editor.assertIsHoverRegion("domain");
+ editor.assertIsHoverRegion("name");
+
+ editor.assertHoverContains("memory", "Use the memory attribute to specify the memory limit");
+ editor.assertHoverContains("1G", "Use the memory attribute to specify the memory limit");
+ editor.assertHoverContains("buildpack", "use the buildpack attribute to specify its URL or name");
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ private void assertCompletions(String textBefore, String... textAfter) throws Exception {
+ Editor editor = harness.newEditor(textBefore);
+ editor.assertCompletions(textAfter);
+ }
+}
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/YamlLanguageServerTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlLanguageServerTest.java
similarity index 81%
rename from vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/YamlLanguageServerTest.java
rename to vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlLanguageServerTest.java
index 038ae3fc0..95281eeb0 100644
--- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/YamlLanguageServerTest.java
+++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYamlLanguageServerTest.java
@@ -8,24 +8,24 @@ import java.nio.file.Paths;
import java.util.List;
import org.junit.Test;
+import org.springframework.ide.vscode.cloudfoundry.manifest.editor.ManifestYamlLanguageServer;
import org.springframework.ide.vscode.testharness.LanguageServerHarness;
import org.springframework.ide.vscode.testharness.TextDocumentInfo;
-import org.springframework.ide.vscode.yaml.YamlLanguageServer;
import io.typefox.lsapi.CompletionItem;
import io.typefox.lsapi.CompletionList;
import io.typefox.lsapi.InitializeResult;
import io.typefox.lsapi.ServerCapabilities;
-public class YamlLanguageServerTest {
+public class ManifestYamlLanguageServerTest {
public static File getTestResource(String name) throws URISyntaxException {
- return Paths.get(YamlLanguageServerTest.class.getResource(name).toURI()).toFile();
+ return Paths.get(ManifestYamlLanguageServerTest.class.getResource(name).toURI()).toFile();
}
@Test
public void createAndInitializeServerWithWorkspace() throws Exception {
- LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
+ LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
File workspaceRoot = getTestResource("/workspace/");
assertExpectedInitResult(harness.intialize(workspaceRoot));
}
@@ -33,13 +33,13 @@ public class YamlLanguageServerTest {
@Test
public void createAndInitializeServerWithoutWorkspace() throws Exception {
File workspaceRoot = null;
- LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
+ LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
assertExpectedInitResult(harness.intialize(workspaceRoot));
}
@Test public void completions() throws Exception {
- LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
+ LanguageServerHarness harness = new LanguageServerHarness(ManifestYamlLanguageServer::new);
File workspaceRoot = getTestResource("/workspace/");
assertExpectedInitResult(harness.intialize(workspaceRoot));
diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYmlSchemaTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYmlSchemaTest.java
new file mode 100644
index 000000000..36399ad99
--- /dev/null
+++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/yaml/ManifestYmlSchemaTest.java
@@ -0,0 +1,144 @@
+package org.springframework.ide.vscode.yaml;
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.ide.vscode.cloudfoundry.manifest.editor.ManifestYmlSchema;
+import org.springframework.ide.vscode.util.StringUtil;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory.YBeanType;
+import org.springframework.ide.vscode.yaml.schema.YTypeFactory.YSeqType;
+import org.springframework.ide.vscode.yaml.schema.YTypedProperty;
+import org.springframework.ide.vscode.yaml.util.DescriptionProviders;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+
+/**
+ * @author Kris De Volder
+ */
+public class ManifestYmlSchemaTest {
+
+ private static final String[] NESTED_PROP_NAMES = {
+// "applications",
+ "buildpack",
+ "command",
+ "disk_quota",
+ "domain",
+ "domains",
+ "env",
+ "host",
+ "hosts",
+// "inherit",
+ "instances",
+ "memory",
+ "name",
+ "no-hostname",
+ "no-route",
+ "path",
+ "random-route",
+ "services",
+ "stack",
+ "timeout"
+ };
+
+ private static final String[] TOPLEVEL_PROP_NAMES = {
+ "applications",
+ "buildpack",
+ "command",
+ "disk_quota",
+ "domain",
+ "domains",
+ "env",
+// "host",
+// "hosts",
+ "inherit",
+ "instances",
+ "memory",
+// "name",
+ "no-hostname",
+ "no-route",
+ "path",
+ "random-route",
+ "services",
+ "stack",
+ "timeout"
+ };
+
+ ManifestYmlSchema schema = new ManifestYmlSchema(null);
+
+ @Test
+ public void toplevelProperties() throws Exception {
+ assertPropNames(schema.getTopLevelType().getProperties(), TOPLEVEL_PROP_NAMES);
+ assertPropNames(schema.getTopLevelType().getPropertiesMap(), TOPLEVEL_PROP_NAMES);
+ }
+
+ @Test
+ public void nestedProperties() throws Exception {
+ assertPropNames(getNestedProps(), NESTED_PROP_NAMES);
+ }
+
+ @Test
+ public void toplevelPropertiesHaveDescriptions() {
+ for (YTypedProperty p : schema.getTopLevelType().getProperties()) {
+ if (!p.getName().equals("applications")) {
+ assertHasRealDescription(p);
+ }
+ }
+ }
+
+ @Test
+ public void nestedPropertiesHaveDescriptions() {
+ for (YTypedProperty p : getNestedProps()) {
+ assertHasRealDescription(p);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ private void assertHasRealDescription(YTypedProperty p) {
+ String noDescriptionText = DescriptionProviders.NO_DESCRIPTION.get().toText();
+ String actual = p.getDescription().toText();
+ String msg = "Description missing for '"+p.getName()+"'";
+ assertTrue(msg, StringUtil.hasText(actual));
+ assertFalse(msg, noDescriptionText.equals(actual));
+ }
+
+ private List getNestedProps() {
+ YSeqType applications = (YSeqType) schema.getTopLevelType().getPropertiesMap().get("applications").getType();
+ YBeanType application = (YBeanType) applications.getDomainType();
+ return application.getProperties();
+ }
+
+ private void assertPropNames(List properties, String... expectedNames) {
+ assertEquals(ImmutableSet.copyOf(expectedNames), getNames(properties));
+ }
+
+ private void assertPropNames(Map propertiesMap, String[] toplevelPropNames) {
+ assertEquals(ImmutableSet.copyOf(toplevelPropNames), ImmutableSet.copyOf(propertiesMap.keySet()));
+ }
+
+ private ImmutableSet getNames(Iterable properties) {
+ Builder builder = ImmutableSet.builder();
+ for (YTypedProperty p : properties) {
+ builder.add(p.getName());
+ }
+ return builder.build();
+ }
+
+}
diff --git a/vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml b/vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml
index 46d24e076..29422dc75 100644
--- a/vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml
+++ b/vscode-extensions/vscode-manifest-yaml/test/examples/manifest.yml
@@ -1,2 +1,5 @@
-foo: bar
-- asss
+applications:
+- name: foo
+ foo: bar
+ builpack: java
+