Moved commons and concourse editor to 'headless-services'
This commit is contained in:
@@ -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+"'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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, '&', "&"); //$NON-NLS-1$
|
||||
content= replace(content, '"', """); //$NON-NLS-1$
|
||||
content= replace(content, '<', "<"); //$NON-NLS-1$
|
||||
return replace(content, '>', ">"); //$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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ...
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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<FooListener> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user