Merge pull request #14 from spring-projects/java-snippet

Snippets for request mappings
This commit is contained in:
nierajsingh
2017-09-28 16:24:17 -07:00
committed by GitHub
21 changed files with 1178 additions and 41 deletions

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
@@ -29,6 +30,9 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider;
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
@@ -52,6 +56,8 @@ import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.MavenProjectFinderStrategy;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
/**
* Language Server for Spring Boot Application Properties files
*
@@ -109,7 +115,66 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE, new ScopeCompletionProcessor());
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueCompletionProcessor(indexProvider));
return new BootJavaCompletionEngine(javaProjectFinder, providers);
JavaSnippetManager snippetManager = new JavaSnippetManager(this::createSnippetBuilder);
snippetManager.add(new JavaSnippet(
"RequestMapping method",
JavaSnippetContext.BOOT_MEMBERS,
CompletionItemKind.Method,
ImmutableList.of(
"org.springframework.web.bind.annotation.RequestMapping",
"org.springframework.web.bind.annotation.RequestMethod",
"org.springframework.web.bind.annotation.RequestParam"
),
"@RequestMapping(value=\"${path}\", method=RequestMethod.${GET})\n" +
"public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {\n" +
" return new ${SomeData}(${cursor});\n" +
"}\n"
));
snippetManager.add(new JavaSnippet(
"GetMapping method",
JavaSnippetContext.BOOT_MEMBERS,
CompletionItemKind.Method,
ImmutableList.of(
"org.springframework.web.bind.annotation.GetMapping",
"org.springframework.web.bind.annotation.RequestParam"
),
"@GetMapping(value=\"${path}\")\n" +
"public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param}) {\n" +
" return new ${SomeData}(${cursor});\n" +
"}\n"
));
snippetManager.add(new JavaSnippet(
"PostMapping method",
JavaSnippetContext.BOOT_MEMBERS,
CompletionItemKind.Method,
ImmutableList.of(
"org.springframework.web.bind.annotation.PostMapping",
"org.springframework.web.bind.annotation.RequestBody"
),
"@PostMapping(value=\"${path}\")\n" +
"public ${SomeEnityData} ${postMethodName}(@RequestBody ${SomeEnityData} ${entity}) {\n" +
" //TODO: process POST request\n" +
" ${cursor}\n" +
" return ${entity};\n" +
"}\n"
));
snippetManager.add(new JavaSnippet(
"PutMapping method",
JavaSnippetContext.BOOT_MEMBERS,
CompletionItemKind.Method,
ImmutableList.of(
"org.springframework.web.bind.annotation.PutMapping",
"org.springframework.web.bind.annotation.RequestBody",
"org.springframework.web.bind.annotation.PathVariable"
),
"@PutMapping(value=\"${path}/{${id}}\")\n" +
"public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n" +
" //TODO: process PUT request\n" +
" ${cursor}\n" +
" return ${entity};\n" +
"}"
));
return new BootJavaCompletionEngine(javaProjectFinder, providers, snippetManager);
}
protected HoverHandler createHoverHandler(JavaProjectFinder javaProjectFinder) {

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.boot.java.handlers;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@@ -24,6 +25,7 @@ import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
@@ -38,10 +40,12 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
private JavaProjectFinder projectFinder;
private Map<String, CompletionProvider> completionProviders;
private JavaSnippetManager snippets;
public BootJavaCompletionEngine(JavaProjectFinder projectFinder, Map<String, CompletionProvider> specificProviders) {
public BootJavaCompletionEngine(JavaProjectFinder projectFinder, Map<String, CompletionProvider> specificProviders, JavaSnippetManager snippets) {
this.projectFinder = projectFinder;
this.completionProviders = specificProviders;
this.snippets = snippets;
}
@Override
@@ -69,7 +73,10 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
if (node != null) {
System.out.println("AST node found: " + node.getClass().getName());
return collectCompletionsForAnnotations(node, offset, document);
Collection<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
completions.addAll(collectCompletionsForAnnotations(node, offset, document));
completions.addAll(snippets.getCompletions(document, offset, node, cu));
return completions;
}
return Collections.emptyList();

View File

@@ -0,0 +1,77 @@
/*******************************************************************************
* 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.boot.java.handlers;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class SimpleCompletionFactory {
public static class SimpleProposal implements ICompletionProposal{
private DocumentEdits edits;
private CompletionItemKind kind;
private Renderable info;
private String detail;
private String label;
public SimpleProposal(DocumentEdits edits, CompletionItemKind kind, Renderable info,
String detail, String label) {
this.edits = edits;
this.kind = kind;
this.info = info;
this.detail = detail;
this.label = label;
}
public SimpleProposal setLabel(String label) {
this.label = label;
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public DocumentEdits getTextEdit() {
return edits;
}
@Override
public CompletionItemKind getKind() {
return kind;
}
@Override
public Renderable getDocumentation() {
return info;
}
@Override
public String getDetail() {
return detail;
}
}
public static SimpleProposal simpleProposal(IDocument doc, int offset, String query, CompletionItemKind kind, String value, String detail, Renderable info) {
DocumentEdits edits = new DocumentEdits(doc);
edits.replace(offset-query.length(), offset, value);
return new SimpleProposal(edits, kind, info, detail, value);
}
}

View File

@@ -0,0 +1,558 @@
/*******************************************************************************
* Derived from:
* org.eclipse.jdt.core.dom.rewrite.ImportRewrite
*
* for use in STS4, where IProject and ICompilationUnit are not available when parsing a Java source.
*
* Original license:
*
* Copyright (c) 2000, 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
* John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
* Lars Vogel <Lars.Vogel@vogella.com> - Contributions for
* Bug 473178
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.jdt.imports;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.util.text.IDocument;
/**
* The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
* <p>
* The import rewrite is created on a compilation unit and collects references to types that are added or removed. When adding imports, e.g. using
* {@link #addImport(String)}, the import rewrite evaluates if the type can be imported and returns the a reference to the type that can be used in code.
* This reference is either unqualified if the import could be added, or fully qualified if the import failed due to a conflict with another element of the same name.
* </p>
* <p>
* On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
* text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
* works on the import statements. It is possible to combine the result of an import rewrite with the result of a {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite}
* as long as no import statements are modified by the AST rewrite.
* </p>
* <p>The options controlling the import order and on-demand thresholds are:
* <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
* <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
* <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
*</ul>
* This class is not intended to be subclassed.
* </p>
* @since 3.2
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class ImportRewrite {
/**
* A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
* give more information about the types visible in the scope. These types can be for example inherited inner types where it is
* unnecessary to add import statements for.
*
* </p>
* <p>
* This class can be implemented by clients.
* </p>
*/
public static abstract class ImportRewriteContext {
/**
* Result constant signaling that the given element is know in the context.
*/
public final static int RES_NAME_FOUND= 1;
/**
* Result constant signaling that the given element is not know in the context.
*/
public final static int RES_NAME_UNKNOWN= 2;
/**
* Result constant signaling that the given element is conflicting with an other element in the context.
*/
public final static int RES_NAME_CONFLICT= 3;
/**
* Result constant signaling that the given element must be imported explicitly (and must not be folded into
* an on-demand import or filtered as an implicit import).
*
* @since 3.11
*/
public final static int RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT= 4;
/**
* Kind constant specifying that the element is a type import.
*/
public final static int KIND_TYPE= 1;
/**
* Kind constant specifying that the element is a static field import.
*/
public final static int KIND_STATIC_FIELD= 2;
/**
* Kind constant specifying that the element is a static method import.
*/
public final static int KIND_STATIC_METHOD= 3;
/**
* Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
* unknown ({@link #RES_NAME_UNKNOWN}), unknown in the context but known to require an explicit import
* ({@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}), or if its name conflicts ({@link #RES_NAME_CONFLICT})
* with an other element.
*
* @param qualifier The qualifier of the element, can be package or the qualified name of a type
* @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
* @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
* {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
* {@link #RES_NAME_UNKNOWN} by default.
* @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN},
* {@link #RES_NAME_CONFLICT}, or {@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}.
*/
public abstract int findInContext(String qualifier, String name, int kind);
}
private static final char STATIC_PREFIX= 's';
private static final char NORMAL_PREFIX= 'n';
private final ImportRewriteContext defaultContext;
private final CompilationUnit astRoot;
private final boolean restoreExistingImports;
private final List existingImports;
private List<String> addedImports;
/**
* Simple names of non-static imports which must not be reduced into on-demand imports
* or filtered out as implicit.
*/
private Set<String> typeExplicitSimpleNames;
private boolean filterImplicitImports;
private boolean useContextToFilterImplicitImports;
/**
* Creates an {@link ImportRewrite} from an AST ({@link CompilationUnit}). The AST has to be created from an
* {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
* AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
* will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
* existing imports will be removed and only the newly added imports will be created.
* <p>
* Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
* </p>
* @param astRoot the AST root node to create the imports for
* @param restoreExistingImports specifies if the existing imports should be kept or removed.
* @return the created import rewriter.
* @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
*/
public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
if (astRoot == null) {
throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
}
List existingImport= null;
if (restoreExistingImports) {
existingImport= new ArrayList();
List imports= astRoot.imports();
for (int i= 0; i < imports.size(); i++) {
ImportDeclaration curr= (ImportDeclaration) imports.get(i);
StringBuffer buf= new StringBuffer();
buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
if (curr.isOnDemand()) {
if (buf.length() > 1)
buf.append('.');
buf.append('*');
}
existingImport.add(buf.toString());
}
}
return new ImportRewrite(astRoot, existingImport);
}
private ImportRewrite(CompilationUnit astRoot, List existingImports) {
this.astRoot= astRoot; // might be null
if (existingImports != null) {
this.existingImports= existingImports;
this.restoreExistingImports= !existingImports.isEmpty();
} else {
this.existingImports= new ArrayList();
this.restoreExistingImports= false;
}
this.filterImplicitImports= true;
// consider that no contexts are used
this.useContextToFilterImplicitImports = false;
this.defaultContext= new ImportRewriteContext() {
@Override
public int findInContext(String qualifier, String name, int kind) {
return findInImports(qualifier, name, kind);
}
};
this.addedImports= new ArrayList<>();
this.typeExplicitSimpleNames = new HashSet<>();
}
/**
* Returns the default rewrite context that only knows about the imported types. Clients
* can write their own context and use the default context for the default behavior.
* @return the default import rewrite context.
*/
public ImportRewriteContext getDefaultImportRewriteContext() {
return this.defaultContext;
}
/**
* Specifies that implicit imports (for types in <code>java.lang</code>, types in the same package as the rewrite
* compilation unit, and types in the compilation unit's main type) should not be created, except if necessary to
* resolve an on-demand import conflict.
* <p>
* The filter is enabled by default.
* </p>
* <p>
* Note: {@link #setUseContextToFilterImplicitImports(boolean)} can be used to filter implicit imports
* when a context is used.
* </p>
*
* @param filterImplicitImports
* if <code>true</code>, implicit imports will be filtered
*
* @see #setUseContextToFilterImplicitImports(boolean)
*/
public void setFilterImplicitImports(boolean filterImplicitImports) {
this.filterImplicitImports= filterImplicitImports;
}
/**
* Sets whether a context should be used to properly filter implicit imports.
* <p>
* By default, the option is disabled to preserve pre-3.6 behavior.
* </p>
* <p>
* When this option is set, the context passed to the <code>addImport*(...)</code> methods is used to determine
* whether an import can be filtered because the type is implicitly visible. Note that too many imports
* may be kept if this option is set and <code>addImport*(...)</code> methods are called without a context.
* </p>
*
* @param useContextToFilterImplicitImports the given setting
*
* @see #setFilterImplicitImports(boolean)
* @since 3.6
*/
public void setUseContextToFilterImplicitImports(boolean useContextToFilterImplicitImports) {
this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
}
private static int compareImport(char prefix, String qualifier, String name, String curr) {
if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
curr= curr.substring(1); // remove the prefix
if (curr.length() == name.length()) {
if (qualifier.length() == 0) {
return ImportRewriteContext.RES_NAME_FOUND;
}
return ImportRewriteContext.RES_NAME_CONFLICT;
}
// at this place: curr.length > name.length
int dotPos= curr.length() - name.length() - 1;
if (curr.charAt(dotPos) != '.') {
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
return ImportRewriteContext.RES_NAME_CONFLICT;
}
return ImportRewriteContext.RES_NAME_FOUND;
}
/**
* Not API, package visibility as accessed from an anonymous type
*/
/* package */ final int findInImports(String qualifier, String name, int kind) {
boolean allowAmbiguity= (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
List imports= this.existingImports;
char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;
for (int i= imports.size() - 1; i >= 0 ; i--) {
String curr= (String) imports.get(i);
int res= compareImport(prefix, qualifier, name, curr);
if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
if (prefix != STATIC_PREFIX) {
return res;
}
}
}
}
String packageName = getPackageName();
if (kind == ImportRewriteContext.KIND_TYPE) {
if (this.filterImplicitImports && this.useContextToFilterImplicitImports) {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// String mainTypeSimpleName= JavaCore.removeJavaLikeExtension(this.compilationUnit.getElementName());
// String mainTypeName= Util.concatenateName(packageName, mainTypeSimpleName, '.');
// if (qualifier.equals(packageName)
// || mainTypeName.equals(Util.concatenateName(qualifier, name, '.'))) {
// return ImportRewriteContext.RES_NAME_FOUND;
// }
if (this.astRoot != null) {
List<AbstractTypeDeclaration> types = this.astRoot.types();
int nTypes = types.size();
for (int i = 0; i < nTypes; i++) {
AbstractTypeDeclaration type = types.get(i);
SimpleName simpleName = type.getName();
if (simpleName.getIdentifier().equals(name)) {
return qualifier.equals(packageName)
? ImportRewriteContext.RES_NAME_FOUND
: ImportRewriteContext.RES_NAME_CONFLICT;
}
}
} else {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// try {
// IType[] types = this.compilationUnit.getTypes();
// int nTypes = types.length;
// for (int i = 0; i < nTypes; i++) {
// IType type = types[i];
// String typeName = type.getElementName();
// if (typeName.equals(name)) {
// return qualifier.equals(packageName)
// ? ImportRewriteContext.RES_NAME_FOUND
// : ImportRewriteContext.RES_NAME_CONFLICT;
// }
// }
// } catch (JavaModelException e) {
// // don't want to throw an exception here
// }
}
}
}
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
private String getPackageName() {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// this.compilationUnit.getParent().getElementName();
return this.astRoot.getPackage().getName().getFullyQualifiedName();
}
/**
* Adds a new import to the rewriter's record and returns a type reference that can be used
* in the code. The type binding can only be an array or non-generic type.
* <p>
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
* </p>
* <p>
* The content of the compilation unit itself is actually not modified
* in any way by this method; rather, the rewriter just records that a new import has been added.
* </p>
* @param qualifiedTypeName the qualified type name of the type to be added
* @param context an optional context that knows about types visible in the current scope or <code>null</code>
* to use the default context only using the available imports.
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
* or else a qualified name if an import conflict prevented an import.
*/
public String addImport(String qualifiedTypeName, ImportRewriteContext context) {
int angleBracketOffset= qualifiedTypeName.indexOf('<');
if (angleBracketOffset != -1) {
return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
}
int bracketOffset= qualifiedTypeName.indexOf('[');
if (bracketOffset != -1) {
return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
}
return internalAddImport(qualifiedTypeName, context);
}
/**
* Adds a new import to the rewriter's record and returns a type reference that can be used
* in the code. The type binding can only be an array or non-generic type.
* <p>
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
* </p>
* <p>
* The content of the compilation unit itself is actually not modified
* in any way by this method; rather, the rewriter just records that a new import has been added.
* </p>
* @param qualifiedTypeName the qualified type name of the type to be added
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
* or else a qualified name if an import conflict prevented an import.
*/
public String addImport(String qualifiedTypeName) {
return addImport(qualifiedTypeName, this.defaultContext);
}
private String internalAddImport(String fullTypeName, ImportRewriteContext context) {
int idx= fullTypeName.lastIndexOf('.');
String typeContainerName, typeName;
if (idx != -1) {
typeContainerName= fullTypeName.substring(0, idx);
typeName= fullTypeName.substring(idx + 1);
} else {
typeContainerName= ""; //$NON-NLS-1$
typeName= fullTypeName;
}
if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
return fullTypeName;
}
if (context == null)
context= this.defaultContext;
int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
return fullTypeName;
}
if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
addEntry(NORMAL_PREFIX + fullTypeName);
}
if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
addEntry(NORMAL_PREFIX + fullTypeName);
this.typeExplicitSimpleNames.add(typeName);
}
return typeName;
}
private void addEntry(String entry) {
this.existingImports.add(entry);
this.addedImports.add(entry);
}
/**
* Returns all non-static imports that are recorded to be added.
*
* @return the imports recorded to be added.
*/
public String[] getAddedImports() {
return filterFromList(this.addedImports, NORMAL_PREFIX);
}
/**
* Returns <code>true</code> if imports have been recorded to be added or removed.
* @return boolean returns if any changes to imports have been recorded.
*/
public boolean hasRecordedChanges() {
return !this.restoreExistingImports
|| !this.addedImports.isEmpty();
}
private static String[] filterFromList(List<String> imports, char prefix) {
if (imports == null) {
return CharOperation.NO_STRINGS;
}
List<String> res= new ArrayList<>();
for (String curr : imports) {
if (prefix == curr.charAt(0)) {
res.add(curr.substring(1));
}
}
return res.toArray(new String[res.size()]);
}
/**
* Reads the positions of each existing import declaration along with any associated comments,
* and returns these in a list whose iteration order reflects the existing order of the imports
* in the compilation unit.
*/
private int getAddedImportsInsertLocation() {
List<ImportDeclaration> importDeclarations = astRoot.imports();
if (importDeclarations == null) {
importDeclarations = Collections.emptyList();
}
List<Comment> comments = astRoot.getCommentList();
int currentCommentIndex = 0;
// Skip over package and file header comments (see https://bugs.eclipse.org/121428).
ImportDeclaration firstImport = importDeclarations.get(0);
PackageDeclaration packageDeclaration = astRoot.getPackage();
int firstImportStartPosition = packageDeclaration == null
? firstImport.getStartPosition()
: astRoot.getExtendedStartPosition(packageDeclaration)
+ astRoot.getExtendedLength(packageDeclaration);
while (currentCommentIndex < comments.size()
&& comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
currentCommentIndex++;
}
int previousExtendedEndPosition = -1;
for (ImportDeclaration currentImport : importDeclarations) {
int extendedEndPosition = astRoot.getExtendedStartPosition(currentImport)
+ astRoot.getExtendedLength(currentImport);
int commentAfterImportIndex = currentCommentIndex;
while (commentAfterImportIndex < comments.size()
&& comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
commentAfterImportIndex++;
}
currentCommentIndex = commentAfterImportIndex;
previousExtendedEndPosition = extendedEndPosition;
}
return previousExtendedEndPosition;
}
public DocumentEdits createEdit(IDocument doc) {
DocumentEdits edits = null;
StringBuffer buffer = new StringBuffer();
String[] createdImprts = getAddedImports();
if (createdImprts != null && createdImprts.length >0) {
edits =new DocumentEdits(doc);
buffer.append('\n');
for (String imp : createdImprts) {
buffer.append("import ");
buffer.append(imp);
buffer.append(';');
buffer.append('\n');
}
edits.insert(getAddedImportsInsertLocation(), buffer.toString());
}
return edits;
}
}

View File

@@ -0,0 +1,79 @@
/*******************************************************************************
* 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.boot.java.snippets;
import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
import com.google.common.base.Supplier;
public class JavaSnippet {
private JavaSnippetContext context;
private String name;
private String template;
private List<String> imports;
private CompletionItemKind kind;
public JavaSnippet(String name, JavaSnippetContext context, CompletionItemKind kind, List<String> imports,
String template) {
super();
this.context = context;
this.name = name;
this.template = template;
this.imports = imports;
this.kind = kind;
}
public Optional<ICompletionProposal> generateCompletion(Supplier<SnippetBuilder> snippetBuilderFactory,
DocumentRegion query, ASTNode node, CompilationUnit cu) {
if (context.appliesTo(node)) {
return Optional.of(
new JavaSnippetCompletion(snippetBuilderFactory,
query,
cu,
this
)
);
}
return Optional.empty();
}
public String getName() {
return this.name;
}
public String getTemplate() {
return this.template;
}
public Optional<List<String>> getImports() {
return Optional.of(this.imports);
}
public CompletionItemKind getKind() {
return kind;
}
}

View File

@@ -0,0 +1,67 @@
/*******************************************************************************
* 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.boot.java.snippets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
import com.google.common.base.Supplier;
/**
* Respobsible for converting eclipse-like template string into lsp snippet text.
* @author Kris De Volder
*/
public class JavaSnippetBuilder{
private Supplier<SnippetBuilder> snippetBuilderFactory;
private static final Pattern PLACE_HOLDER = Pattern.compile("\\$\\{(.+?)\\}");
public JavaSnippetBuilder(Supplier<SnippetBuilder> snippetBuilderFactory) {
this.snippetBuilderFactory = snippetBuilderFactory;
}
public DocumentEdits createEdit(DocumentRegion query, String template) {
DocumentEdits edit = new DocumentEdits(query.getDocument());
edit.replace(query.getStart(), query.getEnd(), createSnippet(template));
return edit;
}
private String createSnippet(String template) {
Matcher matcher = PLACE_HOLDER.matcher(template);
int start = 0;
SnippetBuilder snippet = snippetBuilderFactory.get();
while (matcher.find(start)) {
int matchStart = matcher.start();
snippet.text(template.substring(start, matchStart));
int matchEnd = matcher.end();
String placeHolderImage = template.substring(matcher.start(1), matcher.end(1));
int colon = placeHolderImage.indexOf(':');
String id, value;
if (colon>=0) {
id = placeHolderImage.substring(0, colon);
value = placeHolderImage.substring(colon+1);
} else {
id = placeHolderImage;
value = id;
}
snippet.placeHolder(id, value);
start = matchEnd;
}
snippet.text(template.substring(start));
return snippet.build().toString();
}
}

View File

@@ -0,0 +1,81 @@
/*******************************************************************************
* 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.boot.java.snippets;
import java.util.Optional;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import com.google.common.base.Supplier;
public class JavaSnippetCompletion implements ICompletionProposal{
private DocumentRegion query;
private JavaSnippet javaSnippet;
private Supplier<SnippetBuilder> snippetBuilderFactory;
private CompilationUnit cu;
public JavaSnippetCompletion(Supplier<SnippetBuilder> snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet) {
this.snippetBuilderFactory = snippetBuilderFactory;
this.query = query;
this.cu = cu;
this.javaSnippet = javaSnippet;
}
@Override
public String getLabel() {
return javaSnippet.getName();
}
@Override
public CompletionItemKind getKind() {
return javaSnippet.getKind();
}
@Override
public DocumentEdits getTextEdit() {
return new JavaSnippetBuilder(snippetBuilderFactory).createEdit(query, javaSnippet.getTemplate());
}
@Override
public String getDetail() {
return "Snippet";
}
@Override
public Renderable getDocumentation() {
return Renderables.NO_DESCRIPTION;
}
@Override
public Optional<DocumentEdits> getAdditionalEdit() {
ImportRewrite rewrite = ImportRewrite.create(cu, true);
javaSnippet.getImports().ifPresent((imprts ->
{
for (String imprt : imprts) {
rewrite.addImport(imprt);
}
}));
DocumentEdits edit = rewrite.createEdit(query.getDocument());
return Optional.of(edit);
}
}

View File

@@ -0,0 +1,20 @@
/*******************************************************************************
* 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.boot.java.snippets;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.TypeDeclaration;
public interface JavaSnippetContext {
JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof TypeDeclaration;
boolean appliesTo(ASTNode node);
}

View File

@@ -0,0 +1,65 @@
/*******************************************************************************
* 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.boot.java.snippets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import com.google.common.base.Supplier;
public class JavaSnippetManager {
private List<JavaSnippet> snippets = new ArrayList<>();
private Supplier<SnippetBuilder> snippetBuilderFactory;
private static PrefixFinder PREFIX_FINDER = new PrefixFinder() {
@Override
protected boolean isPrefixChar(char c) {
return Character.isJavaIdentifierPart(c);
}
};
public JavaSnippetManager(Supplier<SnippetBuilder> snippetBuilderFactory) {
this.snippetBuilderFactory = snippetBuilderFactory;
}
public void add(JavaSnippet javaSnippet) {
snippets.add(javaSnippet);
}
public Collection<ICompletionProposal> getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu) {
Collection<ICompletionProposal> completions = new ArrayList<>();
DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset);
for (JavaSnippet javaSnippet : snippets) {
if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0) {
javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu)
.ifPresent((completion) -> completions.add(completion));
}
}
return completions;
}
}

View File

@@ -0,0 +1,50 @@
<!-- These are the original templates. This file will be deleted. It just here for 'inspiration' -->
<templates>
<template autoinsert="true"
id="org.springframework.ide.eclipse.boot.templates.RequestMapping"
name="RequestMapping method" context="boot-members" description="RequestMapping method"
enabled="true">${x:import(org.springframework.web.bind.annotation.RequestMapping,
org.springframework.web.bind.annotation.RequestMethod,
org.springframework.web.bind.annotation.RequestParam)}@RequestMapping(value="${path}",
method=RequestMethod.${GET})
public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {
return new ${SomeData}(${cursor});
}
</template>
<template autoinsert="true"
id="org.springframework.ide.eclipse.boot.templates.GetMapping" name="GetMapping method"
context="boot-members" description="GetMapping method" enabled="true">
${x:import(org.springframework.web.bind.annotation.GetMapping,
org.springframework.web.bind.annotation.RequestParam)}@GetMapping(value="${path}")
public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param})
{
return new ${SomeData}(${cursor});
}
</template>
<template autoinsert="true"
id="org.springframework.ide.eclipse.boot.templates.PostMapping" name="PostMapping method"
context="boot-members" description="PostMapping method" enabled="true">
${x:import(org.springframework.web.bind.annotation.PostMapping,
org.springframework.web.bind.annotation.RequestBody)}@PostMapping(value="${path}")
public ${SomeEnityData} ${postMethodName}(@RequestBody
${SomeEnityData} ${entity}) {
//TODO: process POST request
${cursor}
return ${entity};
}
</template>
<template autoinsert="true"
id="org.springframework.ide.eclipse.boot.templates.PutMapping" name="PutMapping method"
context="boot-members" description="PutMapping method" enabled="true">
${x:import(org.springframework.web.bind.annotation.PutMapping,
org.springframework.web.bind.annotation.RequestBody,
org.springframework.web.bind.annotation.PathVariable)}@PutMapping(value="${path}/{${id}}")
public ${SomeEnityData} ${putMethodName}(@PathVariable
${pvt:link(String,int,long)} ${id}, @RequestBody ${SomeEnityData}
${entity}) {
//TODO: process PUT request
${cursor}
return ${entity};
}
</template>
</templates>