Moved commons and concourse editor to 'headless-services'

This commit is contained in:
Kris De Volder
2017-04-06 16:27:12 -07:00
parent 3abf189512
commit bf8f8cffa9
608 changed files with 600 additions and 51 deletions

View File

@@ -0,0 +1,32 @@
/*******************************************************************************
* Copyright (c) 2014-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.util;
/**
* Parser that always fails, regardless of the input. Used for types who's value cannot be
* expressed as a 'scalar' string value.
*
* @author Kris De Volder
*/
public class AlwaysFailingParser implements ValueParser {
private String typeName;
public AlwaysFailingParser(String typeName) {
this.typeName = typeName;
}
@Override
public Object parse(String str) throws Exception {
throw new IllegalArgumentException("'"+str+"' is not valid for type '"+typeName+"'");
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* 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.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author Kris De Volder
*/
public class ArrayUtils {
public static <T> boolean hasElements(T[] arr) {
return arr!=null && arr.length>0;
}
public static <T> T lastElement(T[] arr) {
if (hasElements(arr)) {
return arr[arr.length-1];
}
return null;
}
public static <T> T firstElement(T[] arr) {
if (hasElements(arr)) {
return arr[0];
}
return null;
}
@SuppressWarnings("unchecked")
public static <T> T[] remove(T[] array, T element) {
ArrayList<T> toKeep = new ArrayList<>(Arrays.asList(array));
toKeep.remove(element);
T[] newArray =(T[]) Array.newInstance(array.getClass().getComponentType(), toKeep.size());
return toKeep.toArray(newArray);
}
}

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
public class Assert {
public static void isNull(String msg, Object obj) {
if (obj!=null) {
throw new IllegalStateException(msg);
}
}
public static void isLegal(boolean b) {
if (!b) {
throw new IllegalStateException();
}
}
public static void isLegal(boolean b, String msg) {
if (!b) {
throw new IllegalStateException(msg);
}
}
public static void isNotNull(Object it) {
if (it==null) {
throw new NullPointerException();
}
}
public static void isTrue(boolean b) {
isLegal(b);
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
/**
* Replacement for Eclipse's BadLocationException (so as ot make porting code easier)
*/
public class BadLocationException extends Exception {
private static final long serialVersionUID = 1L;
public BadLocationException(Throwable e) {
super(e);
}
public BadLocationException() {
}
}

View File

@@ -0,0 +1,25 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import java.util.Collection;
/**
* @author Kris De Volder
*/
public class CollectionUtil {
public static <E> boolean hasElements(Collection<E> c) {
return c!=null && !c.isEmpty();
}
}

View File

@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.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<T> implements IRequestor<T> {
@SuppressWarnings("unchecked")
private List<T> nodes = Collections.EMPTY_LIST;
@Override
public void accept(T node) {
if (nodes==Collections.EMPTY_LIST) {
nodes = new ArrayList<T>();
}
nodes.add(node);
}
public List<T> get() {
return nodes;
}
}

View File

@@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2014-2017 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.util;
import java.util.Collection;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import javax.inject.Provider;
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 Provider<Collection<String>> values;
public EnumValueParser(String typeName, String... values) {
this(typeName, ImmutableSet.copyOf(values));
}
public EnumValueParser(String typeName, Collection<String> values) {
this(typeName, provider(values));
}
public EnumValueParser(String typeName, Callable<Collection<String>> values) {
this(typeName, provider(values));
}
public EnumValueParser(String typeName, Provider<Collection<String>> values) {
this.typeName = typeName;
this.values = values;
}
@Override
public Object parse(String str) throws Exception {
// IMPORTANT: check the text FIRST before fetching values
// from the hints provider, as the hints provider may be expensive when
// resolving values
if (!StringUtil.hasText(str)) {
throw errorOnBlank(createBlankTextErrorMessage());
}
Collection<String> values = this.values.get();
// If values is not known (null) then just assume the str is acceptable.
if (values == null || values.contains(str)) {
return str;
} else {
throw errorOnParse(createErrorMessage(str, values));
}
}
protected String createBlankTextErrorMessage() {
return "'" + typeName + "'" + " cannot be blank.";
}
protected String createErrorMessage(String parseString, Collection<String> values) {
return "'" + parseString + "' is an unknown '" + typeName + "'. Valid values are: " + new TreeSet<>(values);
}
protected Exception errorOnParse(String message) {
return new ValueParseException(message);
}
protected Exception errorOnBlank(String message) {
return new ValueParseException(message);
}
private static <T> Provider<T> provider(T values) {
return () -> values;
}
private static <T> Provider<T> provider(Callable<T> values) {
return () -> {
try {
return values.call();
} catch (Exception e) {
// Ignore
return null;
}
};
}
}

View File

@@ -0,0 +1,158 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
/**
* 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;
}
/**
*
* @param e
* @param toLookFor type of throwable to look for in the given throwable.
* @return the throwable instance of the given type, or null if nothing found.
*/
public static Throwable getThrowable(Throwable e, Class<? extends Throwable> toLookFor) {
if (toLookFor.isAssignableFrom(e.getClass())) {
return e;
}
Throwable cause = e;
Throwable parent = e.getCause();
while (parent != null && parent != e) {
cause = parent;
parent = cause.getCause();
if (toLookFor.isAssignableFrom(cause.getClass())) {
return cause;
}
}
return null;
}
/**
* Given an exception, find if any of the exception types to look for is contained in the given exception
* @param e
* @param toLookFor non-null list of exception types to look for
* @return exception of specified type, if found, or null if not found
*/
public static Throwable findThrowable(Throwable e, List<Class<? extends Throwable>> toLookFor) {
for (Class<? extends Throwable> klass : toLookFor) {
Throwable found = getThrowable(e, klass);
if (found != null) {
return found;
}
}
return null;
}
public static String getMessage(Throwable e) {
// The message of nested exception is usually more interesting than the
// one on top.
Throwable cause = getDeepestCause(e);
if (cause != null) {
String msg = getSimpleError(cause) + ": " + cause.getMessage();
return msg;
} else {
return "An error occurred: " + getSimpleError(e);
}
}
public static String getMessageNoAppendedInformation(Throwable e) {
Throwable deepestCause = ExceptionUtil.getDeepestCause(e);
String msg = deepestCause != null ? deepestCause.getMessage() : null;
if (StringUtil.hasText(msg)) {
return msg;
} else {
return "An error occurred: " + getSimpleError(e);
}
}
public static String getSimpleError(Throwable e) {
return e.getClass().getSimpleName();
}
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();
}
/**
* Convert throwables into exception try not to wrap if not needing to.
*/
public static Exception exception(Throwable cause) {
if (cause instanceof Exception) {
return (Exception)cause;
}
return new ExecutionException(cause);
}
public static Exception exception(String message, Throwable error) {
if (message != null) {
// Wrap only if there is an additional message
return new ExecutionException(message, error);
} else {
return exception(error);
}
}
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2012, 2016 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.commons.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
/**
* Encapsulates information about an 'external' command that can be run through the OS.
* <p>
* This is a simplistic implementation. A more sophisticate implementation should allow for
* different OS's (commands may return different information depending on the OS).
*
* @author Kris De Volder
*/
public class ExternalCommand {
private final String[] command;
public ExternalCommand(String... command) {
ArrayList<String> pieces = new ArrayList<String>(command.length);
for (String piece : command) {
if (piece!=null) {
pieces.add(piece);
}
}
this.command = pieces.toArray(new String[pieces.size()]);
}
public String[] getProgramAndArgs() {
return command;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
boolean first = true;
for (String piece : command) {
if (!first) {
buf.append(" ");
}
buf.append(piece);
first = false;
}
return buf.toString();
}
/**
* Just before executing the command with a ProcessBuilder instance, this method is called,
* giving the command a chance to apply some extra configuration (e.g. set some environment
* parameters).
*/
public void configure(ProcessBuilder processBuilder) {
//Default implementation does nothing. Subclasses may override.
}
/**
* A convenient way to execute commands suitable for use in tests. The output and
* result of commands are logged to the console and if the command returns non
* 0 exit value an exception is thrown.
*/
public void exec(File workdir) throws IOException, InterruptedException {
System.out.println(">>> exec: "+this);
ExternalProcess process = new ExternalProcess(workdir, this);
System.out.println(process);
// org.junit.Assert.assertEquals(0, process.getExitValue());
}
}

View File

@@ -0,0 +1,180 @@
/*******************************************************************************
* Copyright (c) 2012, 2016 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.commons.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
/**
* Convenient wrapper around a {@link Process}. Simplifies the synchronous execution of external
* commands by handling reading from out and error streams and either buffering the result output
* for later retrieval, or forwarding the to output to designated streams.
*
* @author Kris De Volder
*/
public class ExternalProcess {
/**
* A thread that keeps reading input from a Stream until the end is reached
* or there's some error reading the Stream.
*/
public static class StreamGobler extends Thread {
private final OutputStream echo;
private InputStream toRead; //Stream to read. This is nulled after all input has been consumed.
/**
* Creates a StreamGobler that reads input from an input stream
* and buffers up all input it has read for later retrieval via
* the getOut() method.
*/
public StreamGobler(InputStream toRead) {
this(toRead, new ByteArrayOutputStream());
}
/**
* Creates a StreamGobler that reads input from an input stream
* and writes it out to an outputstream.
*/
public StreamGobler(InputStream toRead, OutputStream forwardTo) {
this.toRead = toRead;
this.echo = forwardTo;
start();
}
@Override
public void run() {
byte[] buf = new byte[256];
while (toRead!=null) {
try {
int i = toRead.read(buf);
if (i==-1) {
//EOF
toRead = null; //Done!
} else {
append(buf, i);
}
} catch (IOException e) {
toRead = null;
ByteArrayOutputStream errMsg = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(errMsg));
append(errMsg.toByteArray());
}
}
}
private void append(byte[] buf) {
append(buf, buf.length);
}
private void append(byte[] buf, int len) {
if (echo!=null) {
try {
echo.write(buf, 0, len);
} catch (IOException e) {
}
}
}
public String getContents() throws InterruptedException {
try {
this.join();
if (echo instanceof ByteArrayOutputStream) {
return ((ByteArrayOutputStream)echo).toString();
} else {
return null;
}
} finally {
toRead = null;
}
}
}
private Process process;
private StreamGobler err; // Standard error is to be read from here
private StreamGobler out; // Standard out is to be read from here
private int exitValue = -9999;
private ExternalCommand cmd;
/**
* Creates an external process and waits for it to terminate. The output and error streams
* will be read and forwarded to System.out and System.err
*/
public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException {
this(workingDir, cmd, false);
}
private void init(File workingDir, ExternalCommand cmd,
OutputStream outStream, OutputStream errStream) throws IOException,
InterruptedException {
this.cmd = cmd;
ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs());
processBuilder.directory(workingDir);
cmd.configure(processBuilder);
process = processBuilder.start();
err = new StreamGobler(process.getErrorStream(), errStream);
out = new StreamGobler(process.getInputStream(), outStream);
exitValue = process.waitFor();
}
public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException {
if (captureStreams) {
init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream());
} else {
init(workingDir, cmd, System.out, System.err);
}
}
public String getOut() throws InterruptedException {
return out.getContents();
}
public String getErr() throws InterruptedException {
return err.getContents();
}
@Override
public String toString() {
StringBuffer result = new StringBuffer();
try {
process.exitValue();
result.append(">>>> ExternalProcess: ");
result.append(cmd+"\n");
result.append("exitValue = "+exitValue+"\n");
String strOut = getOut();
if (strOut!=null) {
result.append("\n------- System.out -------\n");
result.append(strOut);
}
String strErr = getErr();
if (strErr!=null) {
result.append("\n------- System.err -------\n");
result.append(getOut());
}
result.append("<<<< ExternalProcess");
return result.toString();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return result.toString();
} catch (IllegalThreadStateException e) {
return "ExternalProcess(RUNNING, "+cmd+")";
}
}
public int getExitValue() {
return exitValue;
}
}

View File

@@ -0,0 +1,42 @@
/*******************************************************************************
* 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.commons.util;
import java.io.File;
/**
* Utilitity methods for working with files
*
* @authro Kris De Volder
* @author Alex Boyko
*
*/
public class FileUtils {
/**
* Find file given its fil name in the given folder or its parent folders
* @param folder Starting folder
* @param fileNameToFind Name of the file to find
* @return Found <code>File</code>
*/
public static File findFile(File folder, String fileNameToFind) {
if (folder!=null && folder.exists()) {
File file = new File(folder, fileNameToFind);
if (file.isFile()) {
return file;
} else {
return findFile(folder.getParentFile(), fileNameToFind);
}
}
return null;
}
}

View File

@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import java.util.concurrent.CompletableFuture;
public class Futures {
/**
* Depcrecated. Use {@link CompletableFuture}.completedFuture() instead.
*/
@Deprecated
public static <T> CompletableFuture<T> of(T value) {
return CompletableFuture.completedFuture(value);
}
}

View File

@@ -0,0 +1,170 @@
/*******************************************************************************
* Copyright (c) 2014 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.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.StringUtil;
/**
* A collection of data that can be searched with a simple 'fuzzy' string
* matching algorithm. Clients must override 'getKey' method to define how
* a search 'key' is associated with each data item.
* <p>
* The collection can then be searched for items who's key matches
* simple 'fuzzy' patterns.
*/
public abstract class FuzzyMap<E> implements Iterable<E> {
private static final Logger LOG = Logger.getLogger(FuzzyMap.class.getName());
public static class Match<E> {
public double score;
public final E data;
private String pattern;
public Match(String pattern, double score, E e) {
this.pattern = pattern;
this.score = score;
this.data = e;
}
public static <E> Match<E> getBest(Collection<Match<E>> matches) {
double bestScore = Double.NEGATIVE_INFINITY;
Match<E> best = null;
for (Match<E> match : matches) {
if (match.score>bestScore) {
best = match;
bestScore = match.score;
}
}
return best;
}
@Override
public String toString() {
return "Match(score="+score+", data="+data+")";
}
public String getPattern() {
return pattern;
}
}
@Override
public Iterator<E> iterator() {
return entries.values().iterator();
}
private TreeMap<String,E> entries = new TreeMap<String, E>();
protected abstract String getKey(E entry);
public void add(E value) {
//This assumes no two entries have the same id.
String key = getKey(value);
E existing = entries.get(key);
if (existing==null) {
entries.put(getKey(value), value);
} else {
LOG.warning(FuzzyMap.class.getName()+": Multiple entries for key "+key+" some entries discarded");
}
}
/**
* Search for pattern. A pattern is just a sequence of characters which have to found in
* an entrie's key in the same order as they are in the pattern.
* <p>
* Note that returned list doesn't yet have elements sorted according to score (instead they
* are sorted lexicographically thanks to the fact we use a Tree representation).
*/
public List<Match<E>> find(String pattern) {
if ("".equals(pattern)) {
//Special case because
// 1) no need to search. Matches everything
// 2) want to use different way of sorting / scoring. See https://issuetracker.springsource.com/browse/STS-4008
ArrayList<Match<E>> matches = new ArrayList<Match<E>>(entries.size());
for (E v : entries.values()) {
matches.add(new Match<E>(pattern, 1.0, v));
}
return matches;
} else {
//TODO: optimize somehow with a smarter index? (right now searches all map entries sequentially)
ArrayList<Match<E>> matches = new ArrayList<Match<E>>();
for (Entry<String, E> e : entries.entrySet()) {
String key = e.getKey();
double score = FuzzyMatcher.matchScore(pattern, key);
if (score!=0.0) {
matches.add(new Match<E>(pattern, score, e.getValue()));
}
}
return matches;
}
}
/**
* Searches the index for the longest string which is both
* - a prefix of propertyName
* - a prefix of some key in the map.
* Note: If the map is empty, then this returns null, since
* no string, not even the empty string is a prefix of a
* key in the map.
*/
public String findValidPrefix(String propertyName) {
E best = findLongestCommonPrefixEntry(propertyName);
return best==null?null:StringUtil.commonPrefix(propertyName, getKey(best));
}
/**
* Find property with longest common prefix for given key.
*/
public E findLongestCommonPrefixEntry(String propertyName) {
//We can implementation this O(log(n)) because the properties are kept in a TreeMap which is sorted.
//This means that entries with common prefix will occur 'next to eachother'
//The 'best' entry must therefore be either the entry just before or just after
//the property we are searching for.
Entry<String, E> ceiln = entries.ceilingEntry(propertyName);
Entry<String, E> floor = entries.floorEntry(propertyName);
Entry<String, E> best;
if (floor==null || floor==ceiln) {
best = ceiln;
} else if (ceiln==null) {
best = floor;
} else {
int floorScore = floor==null?0:StringUtil.commonPrefixLength(floor.getKey(), propertyName);
int ceilnScore = ceiln==null?0:StringUtil.commonPrefixLength(ceiln.getKey(), propertyName);
best = floorScore>ceilnScore ? floor : ceiln;
}
return best==null?null:best.getValue();
}
/**
* Find an exact match if it exists.
*/
public E get(String id) {
return entries.get(id);
}
public boolean isEmpty() {
return entries==null || entries.isEmpty();
}
public int size() {
return entries.size();
}
}

View File

@@ -0,0 +1,76 @@
/*******************************************************************************
* 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.util;
/**
* @author Kris De Volder
*/
public class FuzzyMatcher {
/**
* Match given pattern with a given data. The data is considered a 'match' for the
* pattern if all characters in the pattern can be found in the data, in the
* same order but with possible 'gaps' in between.
* <p>
* The function returns 0. when the pattern doesn't match the data and a non-zero
* 'score' when it does. The higher the score, the better the match is considered to
* be.
*/
public static double matchScore(String pattern, String data) {
int ppos = 0; //pos of next char in pattern to look for
int dpos = 0; //pos of next char in data not yet matched
int gaps = 0; //number of 'gaps' in the match. A gap is any non-empty run of consecutive characters in the data that are not used by the match
int skips = 0; //number of skipped characters. This is the sum of the length of all the gaps.
int plen = pattern.length();
int dlen = data.length();
if (plen>dlen) {
return 0.0;
}
while (ppos<plen) {
if (dpos>=dlen) {
//still chars left in pattern but no more data
return 0.0;
}
char c = pattern.charAt(ppos++);
int foundCharAt = data.indexOf(c, dpos);
if (foundCharAt>=0) {
if (foundCharAt>dpos) {
gaps++;
skips+=foundCharAt-dpos;
}
dpos = foundCharAt+1;
} else {
return 0.0;
}
}
//end of pattern reached. All matched.
if (dpos<dlen) {
//data left over
//gaps++; don't count end skipped chars as a real 'gap'. Otherwise we
//tend to favor matches at the end of the string over matches in the middle.
skips+=dlen-dpos; //but do count the extra chars at end => more extra = worse score
}
return score(gaps, skips);
}
private static double score(int gaps, int skips) {
if (gaps==0) {
//gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort
// alphabetic (see STS-4049)
double badness = 0.1; // all scored equally, assumes using a 'stable' sorter.
return -badness; //higher is better
} else {
double badness = 1+gaps + skips/10000.0; // higher is worse
return -badness; //higher is better
}
}
}

View File

@@ -0,0 +1,157 @@
/*******************************************************************************
* 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.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* Helper class to make it a little easier to create simple html page.
*
* @author Kris De Volder
*/
public class HtmlBuffer {
private StringBuilder buffer = new StringBuilder();
/**
* Append text, applies escaping to the text as needed.
*/
public void text(String text) {
raw(convertToHTMLContent(text));
}
/**
* Append 'raw' text. Doesn't apply any escaping.
*/
public void raw(String rawText) {
buffer.append(rawText);
}
/**
* Append text, applies urlencoding to the text.
*/
public void url(String string) {
try {
raw(URLEncoder.encode(string, "utf8"));
} catch (UnsupportedEncodingException e) {
Log.log(e);
}
}
public String toString() {
return buffer.toString();
}
protected void addPrologAndEpilog() {
// HTMLPrinter.insertPageProlog(buffer, 0, getCSSStyles());
// HTMLPrinter.addPageEpilog(buffer);
}
// /**
// * Note: copied from org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal.getCSSStyles()
// * Returns the style information for displaying HTML (Javadoc) content.
// *
// * @return the CSS styles
// * @since 3.3
// */
// public static String getCSSStyles() {
// if (fgCSSStyles == null) {
// Bundle bundle= Platform.getBundle(JavaPlugin.getPluginId());
// URL url= bundle.getEntry("/JavadocHoverStyleSheet.css"); //$NON-NLS-1$
// if (url != null) {
// BufferedReader reader= null;
// try {
// url= FileLocator.toFileURL(url);
// reader= new BufferedReader(new InputStreamReader(url.openStream()));
// StringBuffer buffer= new StringBuffer(200);
// String line= reader.readLine();
// while (line != null) {
// buffer.append(line);
// buffer.append('\n');
// line= reader.readLine();
// }
// fgCSSStyles= buffer.toString();
// } catch (IOException ex) {
// JavaPlugin.log(ex);
// } finally {
// try {
// if (reader != null)
// reader.close();
// } catch (IOException e) {
// }
// }
//
// }
// }
// String css= fgCSSStyles;
// if (css != null) {
// FontData fontData= JFaceResources.getFontRegistry().getFontData(PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0];
// css= HTMLPrinter.convertTopLevelFont(css, fontData);
// }
// return css;
// }
public void hline() {
raw("<hr>");
}
public void p(String string) {
raw("<p>");
text(string);
raw("</p>");
}
public void bold(String string) {
raw("<b>");
text(string);
raw("</b>");
}
/**
* Escapes reserved HTML characters in the given string.
* <p>
* <b>Warning:</b> Does not preserve whitespace.
*
* @param content the input string
* @return the string with escaped characters
*/
public static String convertToHTMLContent(String content) {
content= replace(content, '&', "&amp;"); //$NON-NLS-1$
content= replace(content, '"', "&quot;"); //$NON-NLS-1$
content= replace(content, '<', "&lt;"); //$NON-NLS-1$
return replace(content, '>', "&gt;"); //$NON-NLS-1$
}
private static String replace(String text, char c, String s) {
int previous= 0;
int current= text.indexOf(c, previous);
if (current == -1)
return text;
StringBuffer buffer= new StringBuffer();
while (current > -1) {
buffer.append(text.substring(previous, current));
buffer.append(s);
previous= current + 1;
current= text.indexOf(c, previous);
}
buffer.append(text.substring(previous));
return buffer.toString();
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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.commons.util;
/**
* A snippet that can be rendered into html.
* <p/>
* Deprecated. Use DescriptionProviders instead.
*
* @author Kris De Volder
*
*/
@Deprecated
public abstract class HtmlSnippet {
public abstract void render(HtmlBuffer html);
public String toHtml() {
HtmlBuffer buf = new HtmlBuffer();
render(buf);
return buf.toString();
}
// Create snippets:
public static HtmlSnippet text(final String text) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.text(text);
}
};
}
public static HtmlSnippet raw(final String rawHtml) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.raw(rawHtml);
}
};
}
public static HtmlSnippet italic(String text) {
return italic(text(text));
}
public static HtmlSnippet italic(final HtmlSnippet wrappee) {
return new HtmlSnippet() {
@Override
public void render(HtmlBuffer html) {
html.raw("<i>");
wrappee.render(html);
html.raw("</i>");
}
};
}
// add more as needed ...
}

View File

@@ -0,0 +1,21 @@
/*******************************************************************************
* 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.commons.util;
public class HtmlUtil {
public static String text2html(String s) {
HtmlBuffer buf = new HtmlBuffer();
buf.text(s);
return buf.toString();
}
}

View File

@@ -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.commons.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.
* <p>
* 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();
}
}

View File

@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
public interface IRequestor<T> {
void accept(T node);
}

View File

@@ -0,0 +1,113 @@
/*******************************************************************************
* Copyright (c) 2017 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.util;
/**
* A range of integers between (inclusive) an optional lower and upper bound.
*
* @author Kris De Volder
*/
public class IntegerRange {
public static final IntegerRange ANY = new IntegerRange(null, null);
private final Integer lowerBound;
private final Integer upperBound;
public boolean isInRange(int x) {
return !isTooSmall(x) && !isTooLarge(x);
}
public boolean isTooLarge(int x) {
return upperBound!=null && x > upperBound;
}
public boolean isTooSmall(int x) {
return lowerBound!=null && x < lowerBound;
}
private IntegerRange(Integer lowerBound, Integer upperBound) {
super();
this.lowerBound = lowerBound;
this.upperBound = upperBound;
}
public static IntegerRange atLeast(int lowerBound) {
return new IntegerRange(lowerBound, null);
}
public static IntegerRange atMost(int upperBound) {
return new IntegerRange(null, upperBound);
}
public static IntegerRange exactly(int x) {
return new IntegerRange(x, x);
}
@Override
public String toString() {
return
"IntegerRange(" +
maybeStr(lowerBound) +
".." +
maybeStr(upperBound) +
")";
}
private String maybeStr(Integer bound) {
if (bound!=null) {
return bound.toString();
}
return "";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((lowerBound == null) ? 0 : lowerBound.hashCode());
result = prime * result + ((upperBound == null) ? 0 : upperBound.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;
IntegerRange other = (IntegerRange) obj;
if (lowerBound == null) {
if (other.lowerBound != null)
return false;
} else if (!lowerBound.equals(other.lowerBound))
return false;
if (upperBound == null) {
if (other.upperBound != null)
return false;
} else if (!upperBound.equals(other.upperBound))
return false;
return true;
}
public Integer getUpperBound() {
return upperBound;
}
public Integer getLowerBound() {
return lowerBound;
}
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import javax.inject.Provider;
/**
* An abstract class implementing {@link Provider}. The provided value
* is computed on demand and cached once computed.
* <p>
* Subclass must implement the compute method.
*/
public abstract class LazyProvider<T> implements Provider<T> {
private boolean computed = false;
private T cached = null;
@Override
public synchronized final T get() {
if (!computed) {
cached = compute();
}
return cached;
}
protected abstract T compute();
}

View File

@@ -0,0 +1,250 @@
/*******************************************************************************
* Copyright (c) 2004, 2016 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* This class is a thread safe list that is designed for storing lists of listeners.
* The implementation is optimized for minimal memory footprint, frequent reads
* and infrequent writes. Modification of the list is synchronized and relatively
* expensive, while accessing the listeners is very fast. For legacy code, readers are given access
* to the underlying array data structure for reading, with the trust that they will
* not modify the underlying array.
* <p>
* <a name="same"></a>A listener list handles the <i>same</i> listener being added
* multiple times, and tolerates removal of listeners that are the same as other
* listeners in the list. For this purpose, listeners can be compared with each other
* using either equality or identity, as specified in the list constructor.
* </p>
* <p>
* Use an enhanced 'for' loop to notify listeners. The recommended
* code sequence for notifying all registered listeners of say,
* <code>FooListener#eventHappened(Event)</code>, is:
* </p>
* <pre>
ListenerList&lt;FooListener&gt; fooListeners = new ListenerList<>();
//...
for (FooListener listener : fooListeners) {
listener.eventHappened(event);
}
* </pre>
* <p>
* Legacy code may still call {@link #getListeners()} and then use a 'for' loop
* to iterate the {@code Object[]}. This might be insignificantly faster, but
* it lacks type-safety and risks inadvertent modifications to the array.
* </p>
* <p>
* This class can be used without OSGi running.
* </p>
*
* @param <E> the type of listeners in this list
* @since org.eclipse.equinox.common 3.2
*/
public class ListenerList<E> implements Iterable<E> {
/**
* The empty array singleton instance.
*/
private static final Object[] EmptyArray = new Object[0];
/**
* Mode constant (value 0) indicating that listeners should be considered
* the <a href="ListenerList.html#same">same</a> if they are equal.
*/
public static final int EQUALITY = 0;
/**
* Mode constant (value 1) indicating that listeners should be considered
* the <a href="ListenerList.html#same">same</a> if they are identical.
*/
public static final int IDENTITY = 1;
/**
* Indicates the comparison mode used to determine if two
* listeners are equivalent
*/
private final boolean identity;
/**
* The list of listeners. Initially empty but initialized
* to an array of size capacity the first time a listener is added.
* Maintains invariant: listeners != null
*/
private volatile Object[] listeners = EmptyArray;
/**
* Creates a listener list in which listeners are compared using equality.
*/
public ListenerList() {
this(EQUALITY);
}
/**
* Creates a listener list using the provided comparison mode.
*
* @param mode The mode used to determine if listeners are the <a href="ListenerList.html#same">same</a>.
*/
public ListenerList(int mode) {
if (mode != EQUALITY && mode != IDENTITY)
throw new IllegalArgumentException();
this.identity = mode == IDENTITY;
}
/**
* Adds a listener to this list. This method has no effect if the <a href="ListenerList.html#same">same</a>
* listener is already registered.
*
* @param listener the non-<code>null</code> listener to add
*/
public synchronized void add(E listener) {
// This method is synchronized to protect against multiple threads adding
// or removing listeners concurrently. This does not block concurrent readers.
if (listener == null)
throw new IllegalArgumentException();
// check for duplicates
final int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
Object listener2 = listeners[i];
if (identity ? listener == listener2 : listener.equals(listener2))
return;
}
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize + 1];
System.arraycopy(listeners, 0, newListeners, 0, oldSize);
newListeners[oldSize] = listener;
//atomic assignment
this.listeners = newListeners;
}
/**
* Returns an array containing all the registered listeners.
* The resulting array is unaffected by subsequent adds or removes.
* If there are no listeners registered, the result is an empty array.
* Use this method when notifying listeners, so that any modifications
* to the listener list during the notification will have no effect on
* the notification itself.
* <p>
* Note: Callers of this method <b>must not</b> modify the returned array.
* </p>
* <p>
* Note: The recommended and type-safe way to iterate this list is to use
* an enhanced 'for' statement, see {@link ListenerList}.
* This method is deprecated for new code.
* </p>
*
* @return the list of registered listeners
*/
public Object[] getListeners() {
return listeners;
}
/**
* Returns an iterator over all the registered listeners.
* The resulting iterator is unaffected by subsequent adds or removes.
* Use this method when notifying listeners, so that any modifications
* to the listener list during the notification will have no effect on
* the notification itself.
*
* @return an iterator
* @since org.eclipse.equinox.common 3.8
*/
@Override
public Iterator<E> iterator() {
return new ListenerListIterator<>(listeners);
}
private static class ListenerListIterator<E> implements Iterator<E> {
private Object[] listeners;
private int i;
public ListenerListIterator(Object[] listeners) {
this.listeners = listeners;
}
@Override
public boolean hasNext() {
return i < listeners.length;
}
@Override
public E next() {
if (i >= listeners.length) {
throw new NoSuchElementException();
}
@SuppressWarnings("unchecked") // (E) is safe, because #add(E) only accepts Es
E next = (E) listeners[i++];
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns whether this listener list is empty.
*
* @return <code>true</code> if there are no registered listeners, and
* <code>false</code> otherwise
*/
public boolean isEmpty() {
return listeners.length == 0;
}
/**
* Removes a listener from this list. Has no effect if the <a href="ListenerList.html#same">same</a>
* listener was not already registered.
*
* @param listener the non-<code>null</code> listener to remove
*/
public synchronized void remove(Object listener) {
// This method is synchronized to protect against multiple threads adding
// or removing listeners concurrently. This does not block concurrent readers.
if (listener == null)
throw new IllegalArgumentException();
int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
Object listener2 = listeners[i];
if (identity ? listener == listener2 : listener.equals(listener2)) {
if (oldSize == 1) {
listeners = EmptyArray;
} else {
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize - 1];
System.arraycopy(listeners, 0, newListeners, 0, i);
System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
//atomic assignment to field
this.listeners = newListeners;
}
return;
}
}
}
/**
* Returns the number of registered listeners.
*
* @return the number of registered listeners
*/
public int size() {
return listeners.length;
}
/**
* Removes all listeners from this list.
*/
public synchronized void clear() {
listeners = EmptyArray;
}
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is here to make porting old STS code easier. Instead of using this,
* consider using {@link java.util.logging.Logger} directly
*/
public class Log {
final static Logger logger = LoggerFactory.getLogger(Log.class);
public static void log(Throwable e) {
logger.error("Error", e);
}
public static void log(String message, Throwable t) {
logger.error(message, t);
}
public static void log(String message) {
logger.error(message);
}
}

View File

@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2017 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.util;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.TreeSet;
import com.google.common.net.MediaType;
public class MimeTypes {
public static String[] getKnownMimeTypes() {
try {
Field f = MediaType.class.getDeclaredField("KNOWN_TYPES");
f.setAccessible(true);
@SuppressWarnings("unchecked")
Map<MediaType, MediaType> map = (Map<MediaType, MediaType>) f.get(null);
TreeSet<String> mediaTypes = new TreeSet<>();
for (MediaType m : map.keySet()) {
mediaTypes.add(m.toString());
}
return mediaTypes.toArray(new String[mediaTypes.size()]);
} catch (Exception e) {
Log.log(e);
}
return null;
}
}

View File

@@ -0,0 +1,47 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexpParser implements ValueParser {
final private Pattern pat;
final private String typeName; // used only for error message
final private String patternDescription; //Human readable description of the regexp pay
/**
* Create a RegexpParser which succeeds if the input string matches the given regexp
* and fail otherwise.
*
* @param regexp
* @param typeName Name of the type (used in error message for failing parses)
* @param patternDescription Human readable description of the regexp pattern (included in the error message for failing parses)
*/
public RegexpParser(String regexp, String typeName, String patternDescription) {
super();
this.pat = Pattern.compile(regexp);
this.typeName = typeName;
this.patternDescription = patternDescription;
}
@Override
public Object parse(String str) throws Exception {
Matcher matcher = pat.matcher(str);
if (matcher.matches()) {
return matcher;
}
throw new ValueParseException("'"+str+"' is not a valid '"+typeName+"'. "+patternDescription);
}
}

View File

@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
/**
* Requestor that remembers only the last item received.
*
* @author Kris De Volder
*/
public class RememberLast<T> implements IRequestor<T> {
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;
}
}

View File

@@ -0,0 +1,33 @@
/*******************************************************************************
* 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.commons.util;
/**
* Renderable can be rendered with various mark-up languages
*/
public interface Renderable {
void renderAsHtml(HtmlBuffer buffer);
void renderAsMarkdown(StringBuilder buffer);
default String toMarkdown() {
StringBuilder buffer = new StringBuilder();
renderAsMarkdown(buffer);
return buffer.toString();
}
default String toHtml() {
HtmlBuffer buffer = new HtmlBuffer();
renderAsHtml(buffer);
return buffer.toString();
}
}

View File

@@ -0,0 +1,321 @@
/*******************************************************************************
* 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.commons.util;
import java.io.InputStream;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.overzealous.remark.Remark;
/**
* Static methods and convenience constants for creating some 'description
* providers'.
*
* @author Kris De Volder
*/
public class Renderables {
private static final String NO_DESCRIPTION_TEXT = "no description";
final static Logger logger = LoggerFactory.getLogger(Renderables.class);
public static final Renderable NO_DESCRIPTION = italic(text(NO_DESCRIPTION_TEXT));
public static Remark getHtmlToMarkdownConverter() {
return new Remark();
}
@FunctionalInterface
public interface HtmlContentFiller {
void fill(HtmlBuffer buffer);
}
public static Renderable htmlBlob(HtmlContentFiller contentFiller) {
return new Renderable() {
@Override
public void renderAsHtml(HtmlBuffer buffer) {
contentFiller.fill(buffer);
}
@Override
public void renderAsMarkdown(StringBuilder buffer) {
buffer.append(getHtmlToMarkdownConverter().convert(toHtml()));
}
};
}
public static Renderable htmlBlob(String html) {
return htmlBlob(buffer -> buffer.raw(html));
}
public static Renderable concat(Renderable... pieces) {
return concat(ImmutableList.copyOf(pieces));
}
public static Renderable concat(List<Renderable> pieces) {
if (pieces == null || pieces.size() == 0) {
throw new IllegalArgumentException("At least one hover information is required for concat");
} else if (pieces.size() == 1) {
return pieces.get(0);
} else {
return new ConcatRenderables(pieces);
}
}
public static Renderable italic(Renderable text) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
buffer.append("*");
text.renderAsMarkdown(buffer);
buffer.append("*");
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<i>");
text.renderAsHtml(buffer);
buffer.raw("</i>");
}
};
}
public static Renderable paragraph(Renderable text) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
//TODO: This looks wrong. A paragraphs in markdown are created
// by separating the text between them with TWO newlines. So this isn't
// quite rigth as it provides no guarantees that there will be
// two newlines before or after the paragraph's text.
// The correct implementation should probably check wether text in buffer already
// ends with newline(s) and add more only if needed. Then it should
// also append double newline at its end.
buffer.append("\n");
text.renderAsMarkdown(buffer);
buffer.append("\n");
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<p>");
text.renderAsHtml(buffer);
buffer.raw("</p>");
}
};
}
public static Renderable strikeThrough(Renderable text) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
buffer.append("~~");
text.renderAsMarkdown(buffer);
buffer.append("~~");
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<del>");
text.renderAsHtml(buffer);
buffer.raw("</del>");
}
};
}
public static Renderable link(String text, String url) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
buffer.append('[');
buffer.append(text);
buffer.append(']');
if (url != null) {
buffer.append('(');
buffer.append(url);
buffer.append(')');
}
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<a href=\"");
buffer.url("" + url);
buffer.raw("\">");
buffer.text(text);
buffer.raw("</a>");
}
};
}
public static Renderable lineBreak() {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
if (buffer.charAt(buffer.length() - 1) != '\n') {
// 2 spaces and then new line would create a line break in text
buffer.append(" ");
}
buffer.append("\n");
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<br>");
}
};
}
public static Renderable bold(String text) {
return bold(text(text));
}
public static Renderable bold(Renderable text) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
buffer.append("**");
text.renderAsMarkdown(buffer);
buffer.append("**");
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.raw("<b>");
text.renderAsHtml(buffer);
buffer.raw("</b>");
}
};
}
public static Renderable text(String text) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
// TODO: handle escaping
buffer.append(text);
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
buffer.text(text);
}
};
}
public static Renderable lazy(Supplier<Renderable> supplier) {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
supplier.get().renderAsMarkdown(buffer);
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
supplier.get().renderAsHtml(buffer);
}
};
}
public static Renderable fromClasspath(final Class<?> klass, final String resourcePath) {
return Renderables.lazy(() -> {
String html = getText(klass, resourcePath, ".html");
String markdown = getText(klass, resourcePath, ".md");
if (html==null && markdown==null) {
return NO_DESCRIPTION;
} else {
return new Renderable() {
@Override
public void renderAsMarkdown(StringBuilder buffer) {
if (markdown!=null) {
buffer.append(markdown);
} else {
buffer.append(getHtmlToMarkdownConverter().convert(html));
}
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
if (html!=null) {
buffer.raw(html);
} else {
//TODO: proper conversion to html
buffer.raw("<pre>");
buffer.raw(markdown);
buffer.raw("</pre>");
}
}
};
}
});
}
private static String getText(final Class<?> klass, String resourcePath, String extension) {
if (extension!=null) {
resourcePath = resourcePath + extension;
}
try {
InputStream stream = klass.getResourceAsStream(resourcePath);
if (stream != null) {
return IOUtil.toString(stream);
}
} catch (Exception e) {
logger.error("Error", e);
}
return null;
}
private static class ConcatRenderables implements Renderable {
private Renderable[] pieces;
ConcatRenderables(Renderable... pieces) {
this.pieces = pieces;
}
public ConcatRenderables(List<Renderable> pieces) {
this(pieces.toArray(new Renderable[pieces.size()]));
}
@Override
public void renderAsHtml(HtmlBuffer buffer) {
for (Renderable hoverInfo : pieces) {
hoverInfo.renderAsHtml(buffer);
}
}
@Override
public void renderAsMarkdown(StringBuilder buffer) {
for (Renderable hoverInfo : pieces) {
hoverInfo.renderAsMarkdown(buffer);
}
}
}
}

View File

@@ -0,0 +1,189 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringUtil {
public static boolean hasText(String name) {
return name!=null && !name.trim().equals("");
}
public static String collectionToDelimitedString(Iterable<String> 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();
}
public static String trim(String s) {
if (s!=null) {
return s.trim();
}
return null;
}
public static String trimEnd(String s) {
if (s!=null) {
return s.replaceAll("\\s+\\z", "");
}
return null;
}
public static int commonPrefixLength(CharSequence s, CharSequence t) {
int shortestStringLen = Math.min(s.length(), t.length());
for (int i = 0; i < shortestStringLen; i++) {
if (s.charAt(i)!=t.charAt(i)) {
return i;
}
}
//no difference found upto entire length of shortest string.
return shortestStringLen;
}
/**
* @return longest string which is a prefix of both argument Strings.
*/
public static String commonPrefix(CharSequence s, CharSequence t) {
int len = commonPrefixLength(s, t);
if (len>0) {
return s.subSequence(0,len).toString();
}
return "";
}
public static String camelCaseToHyphens(String value) {
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, matcher.group(1) + '-'
+ matcher.group(2).toLowerCase());
}
matcher.appendTail(result);
return result.toString();
}
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
public static String arrayToCommaDelimitedString(Object[] array) {
return collectionToCommaDelimitedString(Arrays.asList(array));
}
public static String collectionToCommaDelimitedString(Collection<?> items) {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (Object item : items) {
if (!first) {
buf.append(",");
}
buf.append(item);
first = false;
}
return buf.toString();
}
public static String upperCaseToHyphens(String v) {
if (v!=null) {
return v.toLowerCase().replace('_', '-');
}
return null;
}
public static String hyphensToUpperCase(String v) {
if (v!=null) {
return v.toUpperCase().replace('-', '_');
}
return null;
}
public static String hyphensToCamelCase(String propName, boolean startWithUpperCase) {
String [] parts = propName.split("-");
if (startWithUpperCase) {
parts[0] = upCaseFirstChar(parts[0]);
}
StringBuilder camelCased = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
camelCased.append(upCaseFirstChar(parts[i]));
}
return camelCased.toString();
}
public static String upCaseFirstChar(String string) {
if (StringUtil.hasText(string)) {
return Character.toUpperCase(string.charAt(0)) + string.substring(1);
}
return "";
}
public static String datestamp() {
Date d = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd");
return f.format(d);
}
private static final Pattern NEWLINE = Pattern.compile("(\\n|\\r)+");
/**
* Removes a given number of spaces from all lines of text in a String,
* except for the first line.
* <p>
* Note: this method only deals with spaces its not suitable for strings
* which use tabs for indentation.
*/
public static String stripIndentation(int indent, String indentedText) {
StringBuilder out = new StringBuilder();
boolean first = true;
Matcher matcher = NEWLINE.matcher(indentedText);
int pos = 0;
while (matcher.find()) {
int newline = matcher.start();
int newline_end = matcher.end();
String line = indentedText.substring(pos, newline);
if (first) {
first = false;
} else {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
out.append(indentedText.substring(newline, newline_end));
pos = newline_end;
}
String line = indentedText.substring(pos);
if (!first) {
line = stripIndentationFromLine(indent, line);
}
out.append(line);
return out.toString();
}
public static String stripIndentationFromLine(int indent, String line) {
int start = 0;
while (start<line.length() && start < indent && line.charAt(start)==' ') {
start++;
}
return line.substring(start);
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2017 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.util;
/**
* Exception if there is a failure when parsing a value. It does not wrap
* other exceptions such that when thrown, the parse exception is the "deepest"
* error.
*
*/
public class ValueParseException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public ValueParseException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,30 @@
/*******************************************************************************
* 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.commons.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) throws Exception;
static ValueParser of(ValueParser x) {
return x;
}
}

View File

@@ -0,0 +1,56 @@
/*******************************************************************************
* Copyright (c) 2017 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.util;
/**
* Constants and static methods to create generally useful value parsers.
*
* @author Kris De Volder
*/
public class ValueParsers {
public static final ValueParser NE_STRING = (s) -> {
if (StringUtil.hasText(s)) {
return s;
} else {
throw new IllegalArgumentException("String should not be empty");
}
};
public static final ValueParser POS_INTEGER = integerRange(0, 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) throws Exception {
int value = Integer.parseInt(str);
if (lowerBound!=null && value<lowerBound) {
if (lowerBound==0) {
throw new NumberFormatException("Value must be positive");
} else {
throw new NumberFormatException("Value must be at least "+lowerBound);
}
}
if (upperBound!=null && value>upperBound) {
throw new NumberFormatException("Value must be at most "+upperBound);
}
return value;
}
};
}
}

View File

@@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util.text;
import org.springframework.ide.vscode.commons.util.BadLocationException;
public interface IDocument {
String getUri();
String get();
IRegion getLineInformationOfOffset(int offset);
int getLength();
String get(int start, int len) throws BadLocationException;
int getNumberOfLines();
String getDefaultLineDelimiter();
char getChar(int offset) throws BadLocationException;
int getLineOfOffset(int offset) throws BadLocationException;
IRegion getLineInformation(int line);
int getLineOffset(int line) throws BadLocationException;
void replace(int start, int len, String text) throws BadLocationException;
String textBetween(int start, int end) throws BadLocationException;
String getLanguageId();
}

View File

@@ -0,0 +1,22 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util.text;
/**
* Mimicks eclipse IRegion (i.e. a region is a offset + length).
*/
public interface IRegion {
int getOffset();
int getLength();
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util.text;
/**
* Trivial implementation of {@link IRegion}
* @author kdvolder
*
*/
public class Region implements IRegion {
private int ofs;
private int len;
public Region(int ofs, int len) {
super();
this.ofs = ofs;
this.len = len;
}
@Override
public int getOffset() {
return ofs;
}
@Override
public int getLength() {
return len;
}
@Override
public String toString() {
return "Region [ofs=" + ofs + ", len=" + len + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + len;
result = prime * result + ofs;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Region other = (Region) obj;
if (len != other.len)
return false;
if (ofs != other.ofs)
return false;
return true;
}
}

View File

@@ -0,0 +1,259 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util.text;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.linetracker.DefaultLineTracker;
import org.springframework.ide.vscode.commons.util.text.linetracker.ILineTracker;
import javolution.text.Text;
public class TextDocument implements IDocument {
ILineTracker lineTracker = new DefaultLineTracker();
private static final Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r");
private final String languageId;
private final String uri;
private Text text = new Text("");
public TextDocument(String uri, String languageId) {
this.uri = uri;
this.languageId = languageId;
}
private TextDocument(TextDocument other) {
this.uri = other.uri;
this.languageId = other.getLanguageId();
this.text = other.text;
this.lineTracker.set(text.toString());
}
@Override
public String getUri() {
return uri;
}
@Override
public String get() {
return getText().toString();
}
private synchronized Text getText() {
return text;
}
public synchronized void setText(String text) {
this.text = new Text(text);
this.lineTracker.set(text);
}
public void apply(TextDocumentContentChangeEvent change) throws BadLocationException {
Range rng = change.getRange();
if (rng==null) {
//full sync mode
setText(change.getText());
} else {
int start = toOffset(rng.getStart());
int end = toOffset(rng.getEnd());
replace(start, end-start, change.getText());
}
}
/**
* 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 Range toRange(int offset, int length) throws BadLocationException {
int end = Math.min(offset + length, getLength());
Range range = new Range();
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) throws BadLocationException {
return lineTracker.getLineNumberOfOffset(offset);
}
public Position toPosition(int offset) throws BadLocationException {
int line = lineNumber(offset);
int startOfLine = startOfLine(line);
int column = offset - startOfLine;
Position pos = new Position();
pos.setCharacter(column);
pos.setLine(line);
return pos;
}
private int startOfLine(int line) throws BadLocationException {
IRegion region = lineTracker.getLineInformation(line);
return region.getOffset();
}
@Override
public IRegion getLineInformationOfOffset(int offset) {
try {
if (offset<=getLength()) {
int line = lineNumber(offset);
return getLineInformation(line);
}
} catch (BadLocationException e) {
//outside document.
}
return null;
}
@Override
public int getLength() {
return text.length();
}
@Override
public String get(int start, int len) throws BadLocationException {
try {
return text.subtext(start, start+len).toString();
} catch (Exception e) {
throw new BadLocationException(e);
}
}
@Override
public int getNumberOfLines() {
return lineTracker.getNumberOfLines();
}
@Override
public String getDefaultLineDelimiter() {
Matcher newlineFinder = NEWLINE.matcher(text);
if (newlineFinder.find()) {
return text.subtext(newlineFinder.start(), newlineFinder.end()).toString();
}
return System.getProperty("line.separator");
}
@Override
public char getChar(int offset) throws BadLocationException {
if (offset>=0 && offset<text.length()) {
return text.charAt(offset);
}
throw new BadLocationException();
}
@Override
public int getLineOfOffset(int offset) throws BadLocationException {
return lineTracker.getLineNumberOfOffset(offset);
}
@Override
public IRegion getLineInformation(int line) {
try {
return lineTracker.getLineInformation(line);
} catch (BadLocationException e) {
//line doesn't exist
}
return null;
}
@Override
public int getLineOffset(int line) throws BadLocationException {
return lineTracker.getLineOffset(line);
}
public int toOffset(Position position) throws BadLocationException {
IRegion region = lineTracker.getLineInformation(position.getLine());
int lineStart = region.getOffset();
return lineStart + position.getCharacter();
}
@Override
public synchronized void replace(int start, int len, String ins) throws BadLocationException {
int end = start+len;
text = text
.delete(start, end)
.insert(start, new Text(ins));
lineTracker.replace(start, len, ins);
}
public synchronized TextDocument copy() {
return new TextDocument(this);
}
@Override
public String textBetween(int start, int end) throws BadLocationException {
return get(start, end-start);
}
@Override
public String toString() {
return "TextDocument(uri="+uri+",\n"+this.text+"\n)";
}
/**
* Returns the number of leading spaces in front of a line. If the line only contains spaces then
* this returns the number of spaces the line contains.
* <p>
* This may return -1 if, for some reason, a line's indentation cannot be determined (e.g. the line does
* not exist in the document)
*/
public int getLineIndentation(int line) {
//TODO: this works fine only if we assume all indentation is done with spaces only.
// To generalize this it should probably return a String containing exactly the spaces
// and tabs at the front of the line.
IRegion r = getLineInformation(line);
if (r==null) {
//not a line in the document so it has no indentation
return -1;
}
int len = r.getLength();
int startOfLine = r.getOffset();
int leadingSpaces = 0;
while (leadingSpaces<len) {
char c = getSafeChar(startOfLine+leadingSpaces);
if (c==' ') {
leadingSpaces++;
} else if (c!=' ') {
return leadingSpaces;
}
leadingSpaces++;
}
return leadingSpaces;
}
/**
* Like getChar but never throws {@link BadLocationException}. Instead it
* return (char)0 for offsets outside the document.
*/
public char getSafeChar(int offset) {
try {
return getChar(offset);
} catch (BadLocationException e) {
return 0;
}
}
public String getLanguageId() {
return languageId;
}
}

View File

@@ -0,0 +1,297 @@
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IRegion;
/**
* Abstract implementation of <code>ILineTracker</code>. It lets the definition of line
* delimiters to subclasses. Assuming that '\n' is the only line delimiter, this abstract
* implementation defines the following line scheme:
* <ul>
* <li> "" -> [0,0]
* <li> "a" -> [0,1]
* <li> "\n" -> [0,1], [1,0]
* <li> "a\n" -> [0,2], [2,0]
* <li> "a\nb" -> [0,2], [2,1]
* <li> "a\nbc\n" -> [0,2], [2,3], [5,0]
* </ul>
* <p>
* This class must be subclassed.
* </p>
*/
public abstract class AbstractLineTracker implements ILineTracker, ILineTrackerExtension {
/**
* Tells whether this class is in debug mode.
*
* @since 3.1
*/
private static final boolean DEBUG= false;
/**
* Combines the information of the occurrence of a line delimiter. <code>delimiterIndex</code>
* is the index where a line delimiter starts, whereas <code>delimiterLength</code>,
* indicates the length of the delimiter.
*/
protected static class DelimiterInfo {
public int delimiterIndex;
public int delimiterLength;
public String delimiter;
}
/**
* Representation of replace and set requests.
*
* @since 3.1
*/
protected static class Request {
public final int offset;
public final int length;
public final String text;
public Request(int offset, int length, String text) {
this.offset= offset;
this.length= length;
this.text= text;
}
public Request(String text) {
this.offset= -1;
this.length= -1;
this.text= text;
}
public boolean isReplaceRequest() {
return this.offset > -1 && this.length > -1;
}
}
/**
* The active rewrite session.
*
* @since 3.1
*/
private DocumentRewriteSession fActiveRewriteSession;
/**
* The list of pending requests.
*
* @since 3.1
*/
private List<Request> fPendingRequests;
/**
* The implementation that this tracker delegates to.
*
* @since 3.2
*/
private ILineTracker fDelegate= new ListLineTracker() {
@Override
public String[] getLegalLineDelimiters() {
return AbstractLineTracker.this.getLegalLineDelimiters();
}
@Override
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
return AbstractLineTracker.this.nextDelimiterInfo(text, offset);
}
};
/**
* Whether the delegate needs conversion when the line structure is modified.
*/
private boolean fNeedsConversion= true;
/**
* Creates a new line tracker.
*/
protected AbstractLineTracker() {
}
@Override
public int computeNumberOfLines(String text) {
return fDelegate.computeNumberOfLines(text);
}
@Override
public String getLineDelimiter(int line) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineDelimiter(line);
}
@Override
public IRegion getLineInformation(int line) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineInformation(line);
}
@Override
public IRegion getLineInformationOfOffset(int offset) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineInformationOfOffset(offset);
}
@Override
public int getLineLength(int line) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineLength(line);
}
@Override
public int getLineNumberOfOffset(int offset) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineNumberOfOffset(offset);
}
@Override
public int getLineOffset(int line) throws BadLocationException {
checkRewriteSession();
return fDelegate.getLineOffset(line);
}
@Override
public int getNumberOfLines() {
try {
checkRewriteSession();
} catch (BadLocationException x) {
// TODO there is currently no way to communicate that exception back to the document
}
return fDelegate.getNumberOfLines();
}
@Override
public int getNumberOfLines(int offset, int length) throws BadLocationException {
checkRewriteSession();
return fDelegate.getNumberOfLines(offset, length);
}
@Override
public void set(String text) {
if (hasActiveRewriteSession()) {
fPendingRequests.clear();
fPendingRequests.add(new Request(text));
return;
}
fDelegate.set(text);
}
@Override
public void replace(int offset, int length, String text) throws BadLocationException {
if (hasActiveRewriteSession()) {
fPendingRequests.add(new Request(offset, length, text));
return;
}
checkImplementation();
fDelegate.replace(offset, length, text);
}
/**
* Converts the implementation to be a {@link TreeLineTracker} if it isn't yet.
*
* @since 3.2
*/
private void checkImplementation() {
if (fNeedsConversion) {
fNeedsConversion= false;
fDelegate= new TreeLineTracker((ListLineTracker) fDelegate) {
@Override
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
return AbstractLineTracker.this.nextDelimiterInfo(text, offset);
}
@Override
public String[] getLegalLineDelimiters() {
return AbstractLineTracker.this.getLegalLineDelimiters();
}
};
}
}
/**
* Returns the information about the first delimiter found in the given text starting at the
* given offset.
*
* @param text the text to be searched
* @param offset the offset in the given text
* @return the information of the first found delimiter or <code>null</code>
*/
protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset);
@Override
public final void startRewriteSession(DocumentRewriteSession session) {
if (fActiveRewriteSession != null)
throw new IllegalStateException();
fActiveRewriteSession= session;
fPendingRequests= new ArrayList<>(20);
}
@Override
public final void stopRewriteSession(DocumentRewriteSession session, String text) {
if (fActiveRewriteSession == session) {
fActiveRewriteSession= null;
fPendingRequests= null;
set(text);
}
}
/**
* Tells whether there's an active rewrite session.
*
* @return <code>true</code> if there is an active rewrite session, <code>false</code>
* otherwise
* @since 3.1
*/
protected final boolean hasActiveRewriteSession() {
return fActiveRewriteSession != null;
}
/**
* Flushes the active rewrite session.
*
* @throws BadLocationException in case the recorded requests cannot be processed correctly
* @since 3.1
*/
protected final void flushRewriteSession() throws BadLocationException {
if (DEBUG)
System.out.println("AbstractLineTracker: Flushing rewrite session: " + fActiveRewriteSession); //$NON-NLS-1$
Iterator<Request> e= fPendingRequests.iterator();
fPendingRequests= null;
fActiveRewriteSession= null;
while (e.hasNext()) {
Request request= e.next();
if (request.isReplaceRequest())
replace(request.offset, request.length, request.text);
else
set(request.text);
}
}
/**
* Checks the presence of a rewrite session and flushes it.
*
* @throws BadLocationException in case flushing does not succeed
* @since 3.1
*/
protected final void checkRewriteSession() throws BadLocationException {
if (hasActiveRewriteSession())
flushRewriteSession();
}
}

View File

@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.util.text.linetracker;
import java.util.Arrays;
/**
* Standard implementation of {@link org.eclipse.jface.text.ILineTracker}.
* <p>
* The line tracker considers the three common line delimiters which are '\n',
* '\r', '\r\n'.
* <p>
* This class is not intended to be subclassed.
* </p>
* @noextend This class is not intended to be subclassed by clients.
*/
public class DefaultLineTracker extends AbstractLineTracker {
/** The predefined delimiters of this tracker */
public final static String[] DELIMITERS= { "\r", "\n", "\r\n" }; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$
/** A predefined delimiter information which is always reused as return value */
private DelimiterInfo fDelimiterInfo= new DelimiterInfo();
/**
* Creates a standard line tracker.
*/
public DefaultLineTracker() {
}
@Override
public String[] getLegalLineDelimiters() {
return Arrays.copyOf(DELIMITERS, DELIMITERS.length);
}
@Override
protected DelimiterInfo nextDelimiterInfo(String text, int offset) {
char ch;
int length= text.length();
for (int i= offset; i < length; i++) {
ch= text.charAt(i);
if (ch == '\r') {
if (i + 1 < length) {
if (text.charAt(i + 1) == '\n') {
fDelimiterInfo.delimiter= DELIMITERS[2];
fDelimiterInfo.delimiterIndex= i;
fDelimiterInfo.delimiterLength= 2;
return fDelimiterInfo;
}
}
fDelimiterInfo.delimiter= DELIMITERS[0];
fDelimiterInfo.delimiterIndex= i;
fDelimiterInfo.delimiterLength= 1;
return fDelimiterInfo;
} else if (ch == '\n') {
fDelimiterInfo.delimiter= DELIMITERS[1];
fDelimiterInfo.delimiterIndex= i;
fDelimiterInfo.delimiterLength= 1;
return fDelimiterInfo;
}
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
/**
* A document rewrite session.
*
* @see org.eclipse.jface.text.IDocument
* @see org.eclipse.jface.text.IDocumentExtension4
* @see org.eclipse.jface.text.IDocumentRewriteSessionListener
* @since 3.1
*/
public class DocumentRewriteSession {
private DocumentRewriteSessionType fSessionType;
/**
* Prohibit package external object creation.
*
* @param sessionType the type of this session
*/
protected DocumentRewriteSession(DocumentRewriteSessionType sessionType) {
fSessionType= sessionType;
}
/**
* Returns the type of this session.
*
* @return the type of this session
*/
public DocumentRewriteSessionType getSessionType() {
return fSessionType;
}
@Override
public String toString() {
return new StringBuffer().append(hashCode()).toString();
}
}

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
/**
* A document rewrite session type.
* <p>
* Allowed values are:
* <ul>
* <li>{@link DocumentRewriteSessionType#UNRESTRICTED}</li>
* <li>{@link DocumentRewriteSessionType#UNRESTRICTED_SMALL} (since 3.3)</li>
* <li>{@link DocumentRewriteSessionType#SEQUENTIAL}</li>
* <li>{@link DocumentRewriteSessionType#STRICTLY_SEQUENTIAL}</li>
* </ul>
* </p>
*
* @see org.eclipse.jface.text.IDocument
* @see org.eclipse.jface.text.IDocumentExtension4
* @see org.eclipse.jface.text.IDocumentRewriteSessionListener
* @since 3.1
*/
public class DocumentRewriteSessionType {
/**
* An unrestricted rewrite session is a sequence of unrestricted replace operations. This
* session type should only be used for <em>large</em> operations that touch more than about
* fifty lines. Use {@link #UNRESTRICTED_SMALL} for small operations.
*/
public final static DocumentRewriteSessionType UNRESTRICTED= new DocumentRewriteSessionType();
/**
* An small unrestricted rewrite session is a short sequence of unrestricted replace operations.
* This should be used for changes that touch less than about fifty lines.
*
* @since 3.3
*/
public final static DocumentRewriteSessionType UNRESTRICTED_SMALL= new DocumentRewriteSessionType();
/**
* A sequential rewrite session is a sequence of non-overlapping replace
* operations starting at an arbitrary document offset.
*/
public final static DocumentRewriteSessionType SEQUENTIAL= new DocumentRewriteSessionType();
/**
* A strictly sequential rewrite session is a sequence of non-overlapping
* replace operations from the start of the document to its end.
*/
public final static DocumentRewriteSessionType STRICTLY_SEQUENTIAL= new DocumentRewriteSessionType();
/**
* Prohibit external object creation.
*/
private DocumentRewriteSessionType() {
}
}

View File

@@ -0,0 +1,150 @@
/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IRegion;
/**
* A line tracker maps character positions to line numbers and vice versa.
* Initially the line tracker is informed about its underlying text in order to
* initialize the mapping information. After that, the line tracker is informed
* about all changes of the underlying text allowing for incremental updates of
* the mapping information. It is the client's responsibility to actively inform
* the line tacker about text changes. For example, when using a line tracker in
* combination with a document the document controls the line tracker.
* <p>
* In order to provide backward compatibility for clients of <code>ILineTracker</code>, extension
* interfaces are used to provide a means of evolution. The following extension interfaces
* exist:
* <ul>
* <li> {@link org.springframework.ide.org.springframework.ide.vscode.commons.util.linetracker.ILineTrackerExtension} since version 3.1 introducing the concept
* of rewrite sessions.</li>
* </ul>
* <p>
* Clients may implement this interface or use the standard implementation
* </p>
* {@link org.eclipse.jface.text.DefaultLineTracker}or
* {@link org.eclipse.jface.text.ConfigurableLineTracker}.
*/
public interface ILineTracker {
/**
* Returns the strings this tracker considers as legal line delimiters.
*
* @return the legal line delimiters
*/
String[] getLegalLineDelimiters();
/**
* Returns the line delimiter of the specified line. Returns <code>null</code> if the
* line is not closed with a line delimiter.
*
* @param line the line whose line delimiter is queried
* @return the line's delimiter or <code>null</code> if line does not have a delimiter
* @exception BadLocationException if the line number is invalid in this tracker's line structure
*/
String getLineDelimiter(int line) throws BadLocationException;
/**
* Computes the number of lines in the given text.
*
* @param text the text whose number of lines should be computed
* @return the number of lines in the given text
*/
int computeNumberOfLines(String text);
/**
* Returns the number of lines.
* <p>
* Note that a document always has at least one line.
* </p>
*
* @return the number of lines in this tracker's line structure
*/
int getNumberOfLines();
/**
* Returns the number of lines which are occupied by a given text range.
*
* @param offset the offset of the specified text range
* @param length the length of the specified text range
* @return the number of lines occupied by the specified range
* @exception BadLocationException if specified range is unknown to this tracker
*/
int getNumberOfLines(int offset, int length) throws BadLocationException;
/**
* Returns the position of the first character of the specified line.
*
* @param line the line of interest
* @return offset of the first character of the line
* @exception BadLocationException if the line is unknown to this tracker
*/
int getLineOffset(int line) throws BadLocationException;
/**
* Returns length of the specified line including the line's delimiter.
*
* @param line the line of interest
* @return the length of the line
* @exception BadLocationException if line is unknown to this tracker
*/
int getLineLength(int line) throws BadLocationException;
/**
* Returns the line number the character at the given offset belongs to.
*
* @param offset the offset whose line number to be determined
* @return the number of the line the offset is on
* @exception BadLocationException if the offset is invalid in this tracker
*/
int getLineNumberOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the line at the given offset.
* The description contains the start offset and the length of the line
* excluding the line's delimiter.
*
* @param offset the offset whose line should be described
* @return a region describing the line
* @exception BadLocationException if offset is invalid in this tracker
*/
IRegion getLineInformationOfOffset(int offset) throws BadLocationException;
/**
* Returns a line description of the given line. The description
* contains the start offset and the length of the line excluding the line's
* delimiter.
*
* @param line the line that should be described
* @return a region describing the line
* @exception BadLocationException if line is unknown to this tracker
*/
IRegion getLineInformation(int line) throws BadLocationException;
/**
* Informs the line tracker about the specified change in the tracked text.
*
* @param offset the offset of the replaced text
* @param length the length of the replaced text
* @param text the substitution text
* @exception BadLocationException if specified range is unknown to this tracker
*/
void replace(int offset, int length, String text) throws BadLocationException;
/**
* Sets the tracked text to the specified text.
*
* @param text the new tracked text
*/
void set(String text);
}

View File

@@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
/**
* Extension interface for {@link org.eclipse.jface.text.ILineTracker}. Adds the
* concept of rewrite sessions. A rewrite session is a sequence of replace
* operations that form a semantic unit.
*
* @since 3.1
*/
public interface ILineTrackerExtension {
/**
* Tells the line tracker that a rewrite session started. A rewrite session
* is a sequence of replace operations that form a semantic unit. The line
* tracker is allowed to use that information for internal optimization.
*
* @param session the rewrite session
* @throws IllegalStateException in case there is already an active rewrite
* session
*/
void startRewriteSession(DocumentRewriteSession session) throws IllegalStateException;
/**
* Tells the line tracker that the rewrite session has finished. This method
* is only called when <code>startRewriteSession</code> has been called
* before. The text resulting from the rewrite session is passed to the line
* tracker.
*
* @param session the rewrite session
* @param text the text with which to re-initialize the line tracker
*/
void stopRewriteSession(DocumentRewriteSession session, String text);
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
import org.springframework.ide.vscode.commons.util.text.IRegion;
/**
* Describes a line as a particular number of characters beginning at
* a particular offset, consisting of a particular number of characters,
* and being closed with a particular line delimiter.
*/
final class Line implements IRegion {
/** The offset of the line */
public int offset;
/** The length of the line */
public int length;
/** The delimiter of this line */
public final String delimiter;
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param end the last including character offset of the line
* @param delimiter the line's delimiter
*/
public Line(int offset, int end, String delimiter) {
this.offset= offset;
this.length= (end - offset) +1;
this.delimiter= delimiter;
}
/**
* Creates a new Line.
*
* @param offset the offset of the line
* @param length the length of the line
*/
public Line(int offset, int length) {
this.offset= offset;
this.length= length;
this.delimiter= null;
}
@Override
public int getOffset() {
return offset;
}
@Override
public int getLength() {
return length;
}
}

View File

@@ -0,0 +1,342 @@
/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util.text.linetracker;
import java.util.ArrayList;
import java.util.List;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.ide.vscode.commons.util.text.Region;
import org.springframework.ide.vscode.commons.util.text.linetracker.AbstractLineTracker.DelimiterInfo;
/**
* Abstract, read-only implementation of <code>ILineTracker</code>. It lets the definition of
* line delimiters to subclasses. Assuming that '\n' is the only line delimiter, this abstract
* implementation defines the following line scheme:
* <ul>
* <li> "" -> [0,0]
* <li> "a" -> [0,1]
* <li> "\n" -> [0,1], [1,0]
* <li> "a\n" -> [0,2], [2,0]
* <li> "a\nb" -> [0,2], [2,1]
* <li> "a\nbc\n" -> [0,2], [2,3], [5,0]
* </ul>
* This class must be subclassed.
*
* @since 3.2
*/
abstract class ListLineTracker implements ILineTracker {
/** The line information */
private final List<Line> fLines= new ArrayList<>();
/** The length of the tracked text */
private int fTextLength;
/**
* Creates a new line tracker.
*/
protected ListLineTracker() {
}
/**
* Binary search for the line at a given offset.
*
* @param offset the offset whose line should be found
* @return the line of the offset
*/
private int findLine(int offset) {
if (fLines.size() == 0)
return -1;
int left= 0;
int right= fLines.size() - 1;
int mid= 0;
Line line= null;
while (left < right) {
mid= (left + right) / 2;
line= fLines.get(mid);
if (offset < line.offset) {
if (left == mid)
right= left;
else
right= mid - 1;
} else if (offset > line.offset) {
if (right == mid)
left= right;
else
left= mid + 1;
} else if (offset == line.offset) {
left= right= mid;
}
}
line= fLines.get(left);
if (line.offset > offset)
--left;
return left;
}
/**
* Returns the number of lines covered by the specified text range.
*
* @param startLine the line where the text range starts
* @param offset the start offset of the text range
* @param length the length of the text range
* @return the number of lines covered by this text range
* @exception BadLocationException if range is undefined in this tracker
*/
private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException {
if (length == 0)
return 1;
int target= offset + length;
Line l= fLines.get(startLine);
if (l.delimiter == null)
return 1;
if (l.offset + l.length > target)
return 1;
if (l.offset + l.length == target)
return 2;
return getLineNumberOfOffset(target) - startLine + 1;
}
@Override
public final int getLineLength(int line) throws BadLocationException {
int lines= fLines.size();
if (line < 0 || line > lines)
throw new BadLocationException();
if (lines == 0 || lines == line)
return 0;
Line l= fLines.get(line);
return l.length;
}
@Override
public final int getLineNumberOfOffset(int position) throws BadLocationException {
if (position < 0 || position > fTextLength)
throw new BadLocationException();
if (position == fTextLength) {
int lastLine= fLines.size() - 1;
if (lastLine < 0)
return 0;
Line l= fLines.get(lastLine);
return (l.delimiter != null ? lastLine + 1 : lastLine);
}
return findLine(position);
}
@Override
public final IRegion getLineInformationOfOffset(int position) throws BadLocationException {
if (position > fTextLength)
throw new BadLocationException();
if (position == fTextLength) {
int size= fLines.size();
if (size == 0)
return new Region(0, 0);
Line l= fLines.get(size - 1);
return (l.delimiter != null ? new Line(fTextLength, 0) : new Line(fTextLength - l.length, l.length));
}
return getLineInformation(findLine(position));
}
@Override
public final IRegion getLineInformation(int line) throws BadLocationException {
int lines= fLines.size();
if (line < 0 || line > lines)
throw new BadLocationException();
if (lines == 0)
return new Line(0, 0);
if (line == lines) {
Line l= fLines.get(line - 1);
return new Line(l.offset + l.length, 0);
}
Line l= fLines.get(line);
return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l);
}
@Override
public final int getLineOffset(int line) throws BadLocationException {
int lines= fLines.size();
if (line < 0 || line > lines)
throw new BadLocationException();
if (lines == 0)
return 0;
if (line == lines) {
Line l= fLines.get(line - 1);
if (l.delimiter != null)
return l.offset + l.length;
throw new BadLocationException();
}
Line l= fLines.get(line);
return l.offset;
}
@Override
public final int getNumberOfLines() {
int lines= fLines.size();
if (lines == 0)
return 1;
Line l= fLines.get(lines - 1);
return (l.delimiter != null ? lines + 1 : lines);
}
@Override
public final int getNumberOfLines(int position, int length) throws BadLocationException {
if (position < 0 || position + length > fTextLength)
throw new BadLocationException();
if (length == 0) // optimization
return 1;
return getNumberOfLines(getLineNumberOfOffset(position), position, length);
}
@Override
public final int computeNumberOfLines(String text) {
int count= 0;
int start= 0;
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, start);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
++count;
start= delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength;
delimiterInfo= nextDelimiterInfo(text, start);
}
return count;
}
@Override
public final String getLineDelimiter(int line) throws BadLocationException {
int lines= fLines.size();
if (line < 0 || line > lines)
throw new BadLocationException();
if (lines == 0)
return null;
if (line == lines)
return null;
Line l= fLines.get(line);
return l.delimiter;
}
/**
* Returns the information about the first delimiter found in the given text starting at the
* given offset.
*
* @param text the text to be searched
* @param offset the offset in the given text
* @return the information of the first found delimiter or <code>null</code>
*/
protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset);
/**
* Creates the line structure for the given text. Newly created lines are inserted into the line
* structure starting at the given position. Returns the number of newly created lines.
*
* @param text the text for which to create a line structure
* @param insertPosition the position at which the newly created lines are inserted into the
* tracker's line structure
* @param offset the offset of all newly created lines
* @return the number of newly created lines
*/
private int createLines(String text, int insertPosition, int offset) {
int count= 0;
int start= 0;
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, 0);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
int index= delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1);
if (insertPosition + count >= fLines.size())
fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter));
else
fLines.add(insertPosition + count, new Line(offset + start, offset + index, delimiterInfo.delimiter));
++count;
start= index + 1;
delimiterInfo= nextDelimiterInfo(text, start);
}
if (start < text.length()) {
if (insertPosition + count < fLines.size()) {
// there is a line below the current
Line l= fLines.get(insertPosition + count);
int delta= text.length() - start;
l.offset-= delta;
l.length+= delta;
} else {
fLines.add(new Line(offset + start, offset + text.length() - 1, null));
++count;
}
}
return count;
}
@Override
public final void replace(int position, int length, String text) throws BadLocationException {
throw new UnsupportedOperationException();
}
@Override
public final void set(String text) {
fLines.clear();
if (text != null) {
fTextLength= text.length();
createLines(text, 0, 0);
}
}
/**
* Returns the internal data structure, a {@link List} of {@link Line}s. Used only by
* {@link TreeLineTracker#TreeLineTracker(ListLineTracker)}.
*
* @return the internal list of lines.
*/
final List<Line> getLines() {
return fLines;
}
}

View File

@@ -0,0 +1,176 @@
/*******************************************************************************
* 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.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.junit.Test;
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
public class FuzzyMapTest {
@Test
public void testMatches() {
assertMatch(true, "", "");
assertMatch(true, "", "abc");
assertMatch(true, "server.port", "server.port");
assertMatch(true, "port", "server.port");
assertMatch(true, "sport", "server.port");
assertMatch(false, "spox", "server.port");
}
@Test
public void testOrder() {
assertMatchOrder("port",
"port",
"server.port",
"server.port-mapping",
"piano.sorting"
);
}
@Test
public void testPrefixAlfaOrder() {
//all matches are prefix matches so should come in alpha order
assertMatchOrder("spring",
"spring.abracdabra",
"spring.boot",
"spring.candel",
"spring.shoe",
"spring.springer"
);
}
@Test
public void testPrefixMixedOrder() {
assertMatchOrder("spring",
//prefix matches first
"spring.abracdabra",
"spring.boot",
"spring.candel",
"spring.shoe",
"spring.springer",
//non prefix matches after prefix matches in 'similarity order'
"zspring",
"asprouting"
);
}
public class TestMap extends FuzzyMap<String> {
public TestMap(String... entries) {
for (String e : entries) {
add(e);
}
}
protected String getKey(String entry) {
return entry;
}
}
@Test
public void testCommonPrefix() {
String[] entries = {
"a",
"archipel",
"aardappel",
"aardbei",
"aardvark",
"zoroaster"
};
String[] expectPrefix = {
"a",
"a",
"aard",
"aard",
"aard",
""
};
for (int focusOn = 0; focusOn < entries.length; focusOn++) {
TestMap map = new TestMap();
for (int other = 0; other < entries.length; other++) {
if (focusOn!=other) {
map.add(entries[other]);
}
}
String prefix = map.findValidPrefix(entries[focusOn]);
String prefixEntry = map.findLongestCommonPrefixEntry(entries[focusOn]);
assertEquals(expectPrefix[focusOn], prefix);
assertTrue(prefixEntry.startsWith(prefixEntry));
assertTrue(prefixEntry.length()>prefix.length());
}
}
@Test
public void testCommonPrefixWithExactMatch() {
String[] entries = {
"a",
"archipel",
"aardappel",
"aardbei",
"aardvark",
"zoroaster"
};
TestMap map = new TestMap(entries);
for (String find : entries) {
String found = map.findLongestCommonPrefixEntry(find);
assertEquals(find, found);
}
}
@Test
public void testCommonPrefixEmptyMap() {
TestMap empty = new TestMap();
assertEquals(null, empty.findValidPrefix("foo"));
assertEquals(null, empty.findValidPrefix(""));
assertEquals(null, empty.findLongestCommonPrefixEntry("aaa"));
assertEquals(null, empty.findLongestCommonPrefixEntry(""));
}
private void assertMatchOrder(String pattern, String... datas) {
TestMap map = new TestMap(datas);
List<Match<String>> found = map.find(pattern);
//Note that found elements are scored but not sorted.
Collections.sort(found, new Comparator<Match<String>>() {
public int compare(Match<String> o1, Match<String> o2) {
return Double.valueOf(o2.score).compareTo(o1.score);
}
});
//all the datas should be found and be in the order given.
assertEquals(found.size(), datas.length);
for (int i = 0; i < datas.length; i++) {
assertEquals(datas[i], found.get(i).data);
}
// also check that scores are decreasing.
double previousScore = found.get(0).score;
assertTrue(previousScore!=0.0);
for (int i = 1; i < datas.length; i++) {
String data = datas[i];
double score = found.get(i).score;
assertTrue("Wrong score order: '"+datas[i-1]+"'["+previousScore+"] '"+data+"' ["+score+"]", previousScore>=score);
previousScore = score;
}
}
private void assertMatch(boolean expect, String pattern, String data) {
boolean actual = FuzzyMatcher.matchScore(pattern, data)!=0.0;
assertEquals(expect, actual);
}
}

View File

@@ -0,0 +1,72 @@
/*******************************************************************************
* Copyright (c) 2014-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.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.ide.vscode.commons.util.StringUtil;
/**
* @author Kris De Volder
*/
public class StringUtilTest {
@Test
public void testUpperCaseToHyphens() throws Exception {
assertEquals("extra-small", StringUtil.upperCaseToHyphens("EXTRA_SMALL"));
assertEquals("extra-small", StringUtil.upperCaseToHyphens("extra-small")); //can be applied to already converted string without any harm
assertEquals("", StringUtil.upperCaseToHyphens(""));
assertNull(StringUtil.upperCaseToHyphens(null));
}
@Test
public void testHasText() throws Exception {
assertFalse(StringUtil.hasText(null));
assertFalse(StringUtil.hasText(""));
assertFalse(StringUtil.hasText(" \t\n\r"));
assertTrue(StringUtil.hasText("something"));
}
@Test
public void testTrim() throws Exception {
assertNull(StringUtil.trim(null));
assertEquals("foo", StringUtil.trim(" foo \n\r\t"));
}
@Test
public void testCommonPrefixLen() throws Exception {
assertEquals("foo".length(), StringUtil.commonPrefixLength("foobarlonger", "fooshort"));
assertEquals("foo".length(), StringUtil.commonPrefixLength("fooshort", "foobarlonger"));
assertEquals("foo".length(), StringUtil.commonPrefixLength("foo", "foobarlonger"));
assertEquals("foo".length(), StringUtil.commonPrefixLength("foobarlonger", "foo"));
assertEquals(0, StringUtil.commonPrefixLength("", ""));
assertEquals(0, StringUtil.commonPrefixLength("", "something"));
assertEquals(0, StringUtil.commonPrefixLength("something", ""));
assertEquals(0, StringUtil.commonPrefixLength("nothing", "in common"));
}
@Test
public void testCommonPrefix() throws Exception {
assertEquals("foo", StringUtil.commonPrefix("foobarlonger", "fooshort"));
assertEquals("foo", StringUtil.commonPrefix("fooshort", "foobarlonger"));
assertEquals("foo", StringUtil.commonPrefix("foo", "foobarlonger"));
assertEquals("foo", StringUtil.commonPrefix("foobarlonger", "foo"));
assertEquals("", StringUtil.commonPrefix("", ""));
assertEquals("", StringUtil.commonPrefix("", "something"));
assertEquals("", StringUtil.commonPrefix("something", ""));
assertEquals("", StringUtil.commonPrefix("nothing", "in common"));
}
}