XML LS extension for namespace resolution
This commit is contained in:
@@ -6,4 +6,5 @@ bin.includes = META-INF/,\
|
||||
servers/,\
|
||||
syntaxes/,\
|
||||
icons/,\
|
||||
about.html
|
||||
about.html,\
|
||||
jars/
|
||||
|
||||
@@ -355,5 +355,14 @@
|
||||
class="org.springframework.tooling.boot.ls.prefs.RemoteAppsFromPrefsDataContributor">
|
||||
</injection>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.wildwebdeveloper.xml.lemminxExtension">
|
||||
<jar
|
||||
path="jars/commons-lsp-extensions.jar">
|
||||
</jar>
|
||||
<jar
|
||||
path="jars/xml-ls-extension.jar">
|
||||
</jar>
|
||||
</extension>
|
||||
|
||||
</plugin>
|
||||
|
||||
@@ -43,6 +43,33 @@
|
||||
<outputDirectory>${project.build.directory}/../servers/spring-boot-language-server</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>xml-extension</id>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>copy</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<artifactItems>
|
||||
<artifactItem>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>xml-ls-extension</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<overWrite>true</overWrite>
|
||||
</artifactItem>
|
||||
<artifactItem>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-lsp-extensions</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<overWrite>true</overWrite>
|
||||
</artifactItem>
|
||||
</artifactItems>
|
||||
<stripVersion>true</stripVersion>
|
||||
<overWriteReleases>true</overWriteReleases>
|
||||
<overWriteSnapshots>true</overWriteSnapshots>
|
||||
<outputDirectory>${project.build.directory}/../jars</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
@@ -71,6 +98,9 @@
|
||||
<fileset>
|
||||
<directory>servers</directory>
|
||||
</fileset>
|
||||
<fileset>
|
||||
<directory>jars</directory>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
class="org.springframework.tooling.ls.eclipse.commons.commands.OpenResourceInEditor"
|
||||
commandId="org.springframework.tooling.ls.eclipse.commons.commands.OpenResourceInEditor">
|
||||
</handler>
|
||||
<handler
|
||||
class="org.springframework.tooling.ls.eclipse.commons.commands.JavaWorkspaceCommandHanlder"
|
||||
commandId="java.execute.workspaceCommand">
|
||||
</handler>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.commands">
|
||||
|
||||
@@ -93,7 +93,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
@SuppressWarnings("restriction")
|
||||
public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4LanguageClient {
|
||||
|
||||
private static ReusableClasspathListenerHandler classpathService = new ReusableClasspathListenerHandler (
|
||||
public static final ReusableClasspathListenerHandler CLASSPATH_SERVICE = new ReusableClasspathListenerHandler (
|
||||
Logger.forEclipsePlugin(LanguageServerCommonsActivator::getInstance),
|
||||
new LSP4ECommandExecutor(),
|
||||
() -> new ProjectSorter()
|
||||
@@ -371,7 +371,7 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La
|
||||
|
||||
|
||||
public STS4LanguageClientImpl() {
|
||||
classpathService.addNotificationsSentCallback(projectNames -> {
|
||||
CLASSPATH_SERVICE.addNotificationsSentCallback(projectNames -> {
|
||||
List<IProject> projects = projectNames.stream().map(projectName -> ResourcesPlugin.getWorkspace().getRoot().getProject(projectName)).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
for (IWorkbenchWindow ww : PlatformUI.getWorkbench().getWorkbenchWindows()) {
|
||||
for (IWorkbenchPage page : ww.getPages()) {
|
||||
@@ -397,12 +397,12 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> addClasspathListener(ClasspathListenerParams params) {
|
||||
return CompletableFuture.completedFuture(classpathService.addClasspathListener(params.getCallbackCommandId(), params.isBatched()));
|
||||
return CompletableFuture.completedFuture(CLASSPATH_SERVICE.addClasspathListener(params.getCallbackCommandId(), params.isBatched()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> removeClasspathListener(ClasspathListenerParams params) {
|
||||
return CompletableFuture.completedFuture(classpathService.removeClasspathListener(params.getCallbackCommandId()));
|
||||
return CompletableFuture.completedFuture(CLASSPATH_SERVICE.removeClasspathListener(params.getCallbackCommandId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.springframework.tooling.ls.eclipse.commons.commands;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.core.commands.ExecutionException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.lsp4e.command.LSPCommandHandler;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.springframework.tooling.ls.eclipse.commons.STS4LanguageClientImpl;
|
||||
|
||||
public class JavaWorkspaceCommandHanlder extends LSPCommandHandler {
|
||||
|
||||
public static final String JAVA_WORKSPACE_COMMAND = "java.execute.workspaceCommand";
|
||||
|
||||
public static final String ADD_CLASSPATH_LISTENER_COMMAND = "sts.java.addClasspathListener";
|
||||
public static final String REMOVE_CLASSPATH_LISTENER_COMMAND = "sts.java.removeClasspathListener";
|
||||
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event, Command command, IPath path) throws ExecutionException {
|
||||
if (JAVA_WORKSPACE_COMMAND.equals(command.getCommand())) {
|
||||
String cmdId = (String) command.getArguments().get(0);
|
||||
List<Object> arguments = command.getArguments().subList(1, command.getArguments().size());
|
||||
|
||||
switch (cmdId) {
|
||||
case ADD_CLASSPATH_LISTENER_COMMAND:
|
||||
String callbackCommandIdForAdd = (String) arguments.get(0);
|
||||
Boolean batched = (Boolean) arguments.get(1);
|
||||
return STS4LanguageClientImpl.CLASSPATH_SERVICE.addClasspathListener(callbackCommandIdForAdd, batched);
|
||||
case REMOVE_CLASSPATH_LISTENER_COMMAND:
|
||||
String callbackCommandIdForRemove = (String) arguments.get(0);
|
||||
return STS4LanguageClientImpl.CLASSPATH_SERVICE.removeClasspathListener(callbackCommandIdForRemove);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -165,6 +165,11 @@
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/tm4e/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>wwd</id>
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/wildwebdeveloper/releases/latest/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
|
||||
@@ -210,6 +215,11 @@
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/tm4e/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>wwd</id>
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/wildwebdeveloper/releases/latest/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
|
||||
@@ -260,6 +270,11 @@
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/tm4e/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>wwd</id>
|
||||
<layout>p2</layout>
|
||||
<url>https://download.eclipse.org/wildwebdeveloper/releases/latest/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</profile>
|
||||
|
||||
|
||||
@@ -40,6 +40,23 @@
|
||||
<artifactId>target-platform-configuration</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
<configuration>
|
||||
<environments>
|
||||
<environment>
|
||||
<os>win32</os>
|
||||
<ws>win32</ws>
|
||||
<arch>x86_64</arch>
|
||||
</environment>
|
||||
<environment>
|
||||
<os>linux</os>
|
||||
<ws>gtk</ws>
|
||||
<arch>x86_64</arch>
|
||||
</environment>
|
||||
<environment>
|
||||
<os>macosx</os>
|
||||
<ws>cocoa</ws>
|
||||
<arch>x86_64</arch>
|
||||
</environment>
|
||||
</environments>
|
||||
<dependency-resolution>
|
||||
<extraRequirements>
|
||||
<requirement>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<module>spring-boot-language-server</module>
|
||||
<module>bosh-language-server</module>
|
||||
<module>jdt-ls-extension</module>
|
||||
<module>xml-ls-extension</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -18,3 +18,10 @@ else
|
||||
-am \
|
||||
clean install
|
||||
fi
|
||||
cd ../xml-ls-extension
|
||||
../mvnw \
|
||||
-DtrimStackTrace=false \
|
||||
-f ../pom.xml \
|
||||
-pl xml-ls-extension \
|
||||
-am \
|
||||
clean install
|
||||
|
||||
97
headless-services/xml-ls-extension/pom.xml
Normal file
97
headless-services/xml-ls-extension/pom.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>xml-ls-extension</artifactId>
|
||||
<name>xml-ls-extension</name>
|
||||
<description>Lemminx XML Language Server Extension for contributing project classpath specific NSURI resolution</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-parent</artifactId>
|
||||
<version>1.23.0-SNAPSHOT</version>
|
||||
<relativePath>../commons/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<dependencies.version>${project.version}</dependencies.version>
|
||||
<lsp4xml.version>0.14.0-SNAPSHOT</lsp4xml.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>lemminx-snapshots</id>
|
||||
<url>https://repo.eclipse.org/content/repositories/lemminx-snapshots/</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.lsp4j</groupId>
|
||||
<artifactId>org.eclipse.lsp4j</artifactId>
|
||||
<version>${lsp4j-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.lemminx</groupId>
|
||||
<artifactId>org.eclipse.lemminx</artifactId>
|
||||
<version>${lsp4xml.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.lsp4j</groupId>
|
||||
<artifactId>org.eclipse.lsp4j</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.eclipse.lsp4j</groupId>
|
||||
<artifactId>org.eclipse.lsp4j.jsonrpc</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-lsp-extensions</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<stripVersion>true</stripVersion>
|
||||
<outputDirectory>${project.build.directory}/dependencies</outputDirectory>
|
||||
<overWriteReleases>true</overWriteReleases>
|
||||
<overWriteSnapshots>true</overWriteSnapshots>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<version>6.0.3</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.springframework.ide.vscode.xml;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
|
||||
public interface IJavaProjectProvider {
|
||||
|
||||
interface IJavaProjectData {
|
||||
|
||||
String getName();
|
||||
|
||||
String getUri();
|
||||
|
||||
Classpath getClasspath();
|
||||
|
||||
}
|
||||
|
||||
Collection<IJavaProjectData> all();
|
||||
|
||||
IJavaProjectData get(String name);
|
||||
|
||||
void addListener(Consumer<IJavaProjectData> listener);
|
||||
|
||||
void removeListener(Consumer<IJavaProjectData> listener);
|
||||
|
||||
default IJavaProjectData findProject(String fileUri) {
|
||||
IJavaProjectData bestMatch = null;
|
||||
for (IJavaProjectData p : all()) {
|
||||
if (fileUri.startsWith(p.getUri())) {
|
||||
if (bestMatch == null) {
|
||||
bestMatch = p;
|
||||
} else {
|
||||
if (bestMatch.getUri().length() < p.getUri().length()) {
|
||||
bestMatch = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
default boolean exists(String name) {
|
||||
return get(name) != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package org.springframework.ide.vscode.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.eclipse.lemminx.services.IXMLDocumentProvider;
|
||||
import org.eclipse.lemminx.services.IXMLValidationService;
|
||||
import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService;
|
||||
import org.eclipse.lsp4j.ExecuteCommandParams;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
class JavaProjectCache implements IJavaProjectProvider {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(JavaProjectCache.class.getName());
|
||||
|
||||
private class JavaProjectData implements IJavaProjectData {
|
||||
|
||||
private String name;
|
||||
private String uri;
|
||||
private Classpath classpath;
|
||||
|
||||
public JavaProjectData(String name, String uri, Classpath classpath) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.uri = uri;
|
||||
this.classpath = classpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Classpath getClasspath() {
|
||||
return classpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getEnclosingInstance().hashCode();
|
||||
result = prime * result + ((classpath == null) ? 0 : classpath.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((uri == null) ? 0 : uri.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;
|
||||
JavaProjectData other = (JavaProjectData) obj;
|
||||
if (!getEnclosingInstance().equals(other.getEnclosingInstance()))
|
||||
return false;
|
||||
if (classpath == null) {
|
||||
if (other.classpath != null)
|
||||
return false;
|
||||
} else if (!classpath.equals(other.classpath))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (uri == null) {
|
||||
if (other.uri != null)
|
||||
return false;
|
||||
} else if (!uri.equals(other.uri))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private JavaProjectCache getEnclosingInstance() {
|
||||
return JavaProjectCache.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final String JAVA_EXECUTE_WORKSPACE_COMMAND = "java.execute.workspaceCommand";
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
private static final long CLASSPATH_TIMEOUT = 30_000;
|
||||
|
||||
private final Map<String, IJavaProjectData> projectsCache = new ConcurrentHashMap<>();
|
||||
private final String callbackCommandId;
|
||||
|
||||
private List<Consumer<IJavaProjectData>> listeners = new ArrayList<>();
|
||||
|
||||
private final IXMLCommandService commandService;
|
||||
|
||||
private final IXMLDocumentProvider documentProvider;
|
||||
|
||||
private final IXMLValidationService validationService;
|
||||
|
||||
public JavaProjectCache(IXMLCommandService commandService, IXMLDocumentProvider documentProvider, IXMLValidationService validationService) {
|
||||
this.callbackCommandId = UUID.randomUUID().toString();
|
||||
this.commandService = commandService;
|
||||
this.documentProvider = documentProvider;
|
||||
this.validationService = validationService;
|
||||
}
|
||||
|
||||
void start() {
|
||||
// Register handler for the classpath change callback command
|
||||
commandService.registerCommand(callbackCommandId, (params, cancelChecker) -> handleClasspathChanged(params));
|
||||
|
||||
// Register classpath listener by executing the command below
|
||||
final ExecuteCommandParams execCmdParams = new ExecuteCommandParams(JAVA_EXECUTE_WORKSPACE_COMMAND, Arrays.asList("sts.java.addClasspathListener", callbackCommandId, true));
|
||||
|
||||
// Keep trying to register classpath listener until success or timeout
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
Object result = null;
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (result == null && System.currentTimeMillis() - startTime < CLASSPATH_TIMEOUT) {
|
||||
CompletableFuture<Object> clientCommand = commandService.executeClientCommand(execCmdParams);
|
||||
try {
|
||||
result = clientCommand.get(1000, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
clientCommand.cancel(true);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e1) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
|
||||
}
|
||||
|
||||
private String handleClasspathChanged(ExecuteCommandParams callbackParams) {
|
||||
List<Object> args = callbackParams.getArguments();
|
||||
//Args are deserialized as com.google.gson.JsonElements.
|
||||
List<String> projectUris = new ArrayList<>();
|
||||
if (((JsonElement) args.get(0)).isJsonArray()) {
|
||||
// If events are batched... then they will arrive as a array of arrays.
|
||||
for (Object arg : args) {
|
||||
JsonArray event = (JsonArray) arg;
|
||||
String projectUri = event.get(0).getAsString();
|
||||
String name = event.get(1).getAsString();
|
||||
boolean deleted = event.get(2).getAsBoolean();
|
||||
Classpath classpath = gson.fromJson((JsonElement)event.get(3), Classpath.class);
|
||||
|
||||
projectUris.add(projectUri);
|
||||
updateProject(name, projectUri, classpath, deleted);
|
||||
}
|
||||
} else {
|
||||
//Still support non-batched events for backwards compatibility with clients
|
||||
// that don't provide batched event support (e.g. IDEA client may only adopt this
|
||||
// later, or not adopt it at all).
|
||||
String projectUri = ((JsonElement) args.get(0)).getAsString();
|
||||
String name = ((JsonElement) args.get(1)).getAsString();
|
||||
boolean deleted = ((JsonElement)args.get(2)).getAsBoolean();
|
||||
Classpath classpath = gson.fromJson((JsonElement)args.get(3), Classpath.class);
|
||||
|
||||
projectUris.add(projectUri);
|
||||
updateProject(name, projectUri, classpath, deleted);
|
||||
}
|
||||
|
||||
documentProvider.getAllDocuments().stream()
|
||||
.filter(dm -> projectUris.stream().filter(uri -> dm.getTextDocument().getUri().startsWith(uri)).findFirst().isPresent())
|
||||
.forEach(dm -> validationService.validate(dm));
|
||||
|
||||
return "done";
|
||||
}
|
||||
|
||||
void stop() {
|
||||
ExecuteCommandParams execCmdParams = new ExecuteCommandParams(JAVA_EXECUTE_WORKSPACE_COMMAND, Arrays.asList("sts.java.removeClasspathListener", callbackCommandId));
|
||||
commandService.executeClientCommand(execCmdParams);
|
||||
commandService.unregisterCommand(callbackCommandId);
|
||||
}
|
||||
|
||||
private void updateProject(String name, String projectUri, Classpath classpath, boolean deleted) {
|
||||
JavaProjectData project = new JavaProjectData(name, projectUri, classpath);
|
||||
if (deleted) {
|
||||
this.projectsCache.remove(project.getName());
|
||||
} else {
|
||||
this.projectsCache.put(name, project);
|
||||
}
|
||||
for (Consumer<IJavaProjectData> l : listeners) {
|
||||
try {
|
||||
l.accept(project);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<IJavaProjectData> all() {
|
||||
return projectsCache.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJavaProjectData get(String name) {
|
||||
return projectsCache.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Consumer<IJavaProjectData> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Consumer<IJavaProjectData> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.ide.vscode.xml;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.lemminx.services.extensions.IXMLExtension;
|
||||
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.springframework.ide.vscode.xml.namespaces.ProjectAwareUrlStreamHandlerFactory;
|
||||
import org.springframework.ide.vscode.xml.namespaces.ProjectClasspathUriResolverExtension;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ProjectResourceLoaderCache;
|
||||
|
||||
public class SpringXmlPlugin implements IXMLExtension {
|
||||
|
||||
private JavaProjectCache javaProjectCache;
|
||||
|
||||
@Override
|
||||
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
|
||||
javaProjectCache = new JavaProjectCache(registry.getCommandService(), registry.getDocumentProvider(), registry.getValidationService());
|
||||
javaProjectCache.start();
|
||||
|
||||
ProjectResourceLoaderCache loaderCache = new ProjectResourceLoaderCache(javaProjectCache);
|
||||
|
||||
URL.setURLStreamHandlerFactory(new ProjectAwareUrlStreamHandlerFactory(javaProjectCache, loaderCache));
|
||||
|
||||
registry.getResolverExtensionManager().registerResolver(new ProjectClasspathUriResolverExtension(javaProjectCache, loaderCache));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(XMLExtensionsRegistry registry) {
|
||||
javaProjectCache.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider;
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider.IJavaProjectData;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ProjectResourceLoaderCache;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ResourceLoader;
|
||||
|
||||
public class ProjectAwareUrlStreamHandlerFactory implements URLStreamHandlerFactory {
|
||||
|
||||
public static final String PROJECT_AWARE_PROTOCOL = "project-aware";
|
||||
public static final String PROJECT_AWARE_PROTOCOL_HEADER = PROJECT_AWARE_PROTOCOL + "://";
|
||||
|
||||
private IJavaProjectProvider javaProjectProvider;
|
||||
private ProjectResourceLoaderCache loaderCache;
|
||||
|
||||
public ProjectAwareUrlStreamHandlerFactory(IJavaProjectProvider javaProjectProvider, ProjectResourceLoaderCache loaderCache) {
|
||||
this.javaProjectProvider = javaProjectProvider;
|
||||
this.loaderCache = loaderCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
if (PROJECT_AWARE_PROTOCOL.equals(protocol)) {
|
||||
return new URLStreamHandler() {
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
String systemId = u.toString();
|
||||
String nameAndLocation = systemId.substring(PROJECT_AWARE_PROTOCOL_HEADER.length());
|
||||
String projectName = nameAndLocation.substring(0, nameAndLocation.indexOf('/'));
|
||||
IJavaProjectData project = javaProjectProvider.get(projectName);
|
||||
String resourceId = nameAndLocation.substring(nameAndLocation.indexOf('/') + 1);
|
||||
ResourceLoader cl = loaderCache.getResourceLoader(project, null);
|
||||
URL resource = cl.getResource(resourceId);
|
||||
if (resource != null) {
|
||||
return resource.openConnection();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of a project-aware protocol URL from
|
||||
* project name and a resource name
|
||||
*
|
||||
* @param projectName
|
||||
* spring project name
|
||||
* @param resourceName
|
||||
* class loader resource name
|
||||
* @return URL string
|
||||
*/
|
||||
public static String createProjectAwareUrl(String projectName, String resourceName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(PROJECT_AWARE_PROTOCOL_HEADER);
|
||||
sb.append(projectName);
|
||||
sb.append('/');
|
||||
sb.append(resourceName);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider.IJavaProjectData;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ProjectResourceLoaderCache;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.PropertiesLoaderUtils;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ResourceLoader;
|
||||
import org.springframework.ide.vscode.xml.namespaces.model.NamespaceDefinition;
|
||||
import org.springframework.ide.vscode.xml.namespaces.util.TargetNamespaceScanner;
|
||||
|
||||
/**
|
||||
* resolves URIs on the project classpath using the protocol established by
|
||||
* <code>spring.schemas</code> files.
|
||||
*
|
||||
* @author Martin Lippert
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public class ProjectClasspathUriResolver {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(ProjectClasspathUriResolver.class.getName());
|
||||
|
||||
private static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "META-INF/spring.schemas";
|
||||
|
||||
|
||||
private final IJavaProjectData project;
|
||||
private boolean disableCaching;
|
||||
|
||||
private Map<String, String> typePublic;
|
||||
private Map<String, String> typeUri;
|
||||
private Map<String, String> schemaMappings;
|
||||
private ProjectResourceLoaderCache loaderCache;
|
||||
|
||||
public ProjectClasspathUriResolver(ProjectResourceLoaderCache loaderCache, IJavaProjectData project, boolean disableCaching) {
|
||||
this.loaderCache = loaderCache;
|
||||
this.project = project;
|
||||
this.disableCaching = disableCaching;
|
||||
if (!disableCaching) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given <code>systemId</code> on the classpath configured by
|
||||
* the <code>file</code>'s project.
|
||||
*/
|
||||
public String resolveOnClasspath(String publicId, String systemId) {
|
||||
if (disableCaching) {
|
||||
return resolveOnClasspathAndSourceFolders(publicId, systemId);
|
||||
}
|
||||
return resolveOnClasspathOnly(publicId, systemId);
|
||||
}
|
||||
|
||||
private String resolveOnClasspathAndSourceFolders(String publicId,
|
||||
String systemId) {
|
||||
ResourceLoader classLoader = loaderCache.getResourceLoader(project, null);
|
||||
Map<String, String> mappings = getSchemaMappings(classLoader);
|
||||
|
||||
if (mappings != null && systemId != null) {
|
||||
if (mappings.containsKey(systemId)) {
|
||||
String xsdPath = mappings.get(systemId);
|
||||
return resolveXsdPathOnClasspath(xsdPath, classLoader);
|
||||
}
|
||||
else if (mappings.containsKey(systemId.replace("https://", "http://"))) {
|
||||
String xsdPath = mappings.get(systemId.replace("https://", "http://"));
|
||||
return resolveXsdPathOnClasspath(xsdPath, classLoader);
|
||||
}
|
||||
else if (mappings.containsKey(systemId.replace("http://", "https://"))) {
|
||||
String xsdPath = mappings.get(systemId.replace("http://", "https://"));
|
||||
return resolveXsdPathOnClasspath(xsdPath, classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String resolveOnClasspathOnly(String publicId, String systemId) {
|
||||
String resolved = null;
|
||||
|
||||
if (systemId != null) {
|
||||
resolved = typeUri.get(systemId);
|
||||
}
|
||||
|
||||
if (resolved == null && systemId != null && systemId.startsWith("https://")) {
|
||||
resolved = typeUri.get(systemId.replace("https://", "http://"));
|
||||
}
|
||||
|
||||
if (resolved == null && systemId != null && systemId.startsWith("http://")) {
|
||||
resolved = typeUri.get(systemId.replace("http://", "https://"));
|
||||
}
|
||||
|
||||
if (resolved == null && publicId != null) {
|
||||
if (!(systemId != null && systemId.endsWith(".xsd"))) {
|
||||
resolved = typePublic.get(publicId);
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
this.typePublic = new ConcurrentHashMap<String, String>();
|
||||
this.typeUri = new ConcurrentHashMap<String, String>();
|
||||
|
||||
Map<String, NamespaceDefinition> namespaceDefinitionRegistry = new HashMap<String, NamespaceDefinition>();
|
||||
ResourceLoader classLoader = loaderCache.getResourceLoader(project, null);
|
||||
|
||||
schemaMappings = getSchemaMappings(classLoader);
|
||||
if (schemaMappings != null) {
|
||||
for (String key : schemaMappings.keySet()) {
|
||||
String path = schemaMappings.get(key);
|
||||
|
||||
// add the resolved path to the list of uris
|
||||
String resolvedPath = resolveXsdPathOnClasspath(path,
|
||||
classLoader);
|
||||
if (resolvedPath != null) {
|
||||
typeUri.put(key, resolvedPath);
|
||||
|
||||
// collect base information to later extract the default uri
|
||||
String namespaceUri = getTargetNamespace(resolvedPath);
|
||||
|
||||
if (namespaceDefinitionRegistry.containsKey(namespaceUri)) {
|
||||
namespaceDefinitionRegistry.get(namespaceUri)
|
||||
.addSchemaLocation(key);
|
||||
namespaceDefinitionRegistry.get(namespaceUri).addUri(
|
||||
path);
|
||||
} else {
|
||||
NamespaceDefinition namespaceDefinition = new NamespaceDefinition(
|
||||
null);
|
||||
namespaceDefinition.addSchemaLocation(key);
|
||||
namespaceDefinition.setNamespaceUri(namespaceUri);
|
||||
namespaceDefinition.addUri(path);
|
||||
namespaceDefinitionRegistry.put(namespaceUri,
|
||||
namespaceDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add catalog entry to namespace uri
|
||||
for (NamespaceDefinition definition : namespaceDefinitionRegistry
|
||||
.values()) {
|
||||
String namespaceKey = definition.getNamespaceUri();
|
||||
String defaultUri = definition.getDefaultUri();
|
||||
|
||||
String resolvedPath = resolveXsdPathOnClasspath(defaultUri,
|
||||
classLoader);
|
||||
if (resolvedPath != null) {
|
||||
typePublic.put(namespaceKey, resolvedPath);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target namespace URI of the XSD identified by the given
|
||||
* <code>resolvedPath</code>.
|
||||
*/
|
||||
private String getTargetNamespace(String resolvedPath) {
|
||||
if (resolvedPath == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URI(
|
||||
ProjectAwareUrlStreamHandlerFactory.createProjectAwareUrl(
|
||||
project.getName(), resolvedPath)).toURL();
|
||||
return TargetNamespaceScanner.getTargetNamespace(url);
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, e, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all schema mappings from all <code>spring.schemas</code> files on
|
||||
* the project classpath.
|
||||
*
|
||||
* @param classLoader
|
||||
* The classloader that is used to load the properties
|
||||
*/
|
||||
private Map<String, String> getSchemaMappings(ResourceLoader classLoader) {
|
||||
Map<String, String> handlerMappings = new ConcurrentHashMap<String, String>();
|
||||
// try {
|
||||
Properties mappings = PropertiesLoaderUtils
|
||||
.loadAllProperties(
|
||||
DEFAULT_SCHEMA_MAPPINGS_LOCATION,
|
||||
classLoader);
|
||||
mergePropertiesIntoMap(mappings, handlerMappings);
|
||||
// } catch (IOException ex) {
|
||||
// // We can ignore this as we simply don't find the xsd file then.
|
||||
// }
|
||||
return handlerMappings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <K, V> void mergePropertiesIntoMap(/*@Nullable*/ Properties props, Map<K, V> map) {
|
||||
if (props != null) {
|
||||
for (Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) {
|
||||
String key = (String) en.nextElement();
|
||||
Object value = props.get(key);
|
||||
if (value == null) {
|
||||
// Allow for defaults fallback or potentially overridden accessor...
|
||||
value = props.getProperty(key);
|
||||
}
|
||||
map.put((K) key, (V) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String resolveXsdPathOnClasspath(String xsdPath, ResourceLoader classLoader) {
|
||||
URL url = classLoader.getResource(xsdPath);
|
||||
|
||||
// fallback, if schema location starts with / and therefore fails to be
|
||||
// found by classloader
|
||||
if (url == null && xsdPath.startsWith("/")) {
|
||||
xsdPath = xsdPath.substring(1);
|
||||
url = classLoader.getResource(xsdPath);
|
||||
}
|
||||
|
||||
return url == null ? null : xsdPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import org.eclipse.lemminx.uriresolver.URIResolverExtension;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider;
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider.IJavaProjectData;
|
||||
import org.springframework.ide.vscode.xml.namespaces.classpath.ProjectResourceLoaderCache;
|
||||
import org.springframework.ide.vscode.xml.namespaces.util.DocumentAccessor;
|
||||
import org.springframework.ide.vscode.xml.namespaces.util.DocumentAccessor.SchemaLocations;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
public class ProjectClasspathUriResolverExtension implements URIResolverExtension {
|
||||
|
||||
private IJavaProjectProvider javaProjectProvider;
|
||||
|
||||
private ConcurrentMap<IJavaProjectData, Future<ProjectClasspathUriResolver>> projectResolvers =
|
||||
new ConcurrentHashMap<IJavaProjectData, Future<ProjectClasspathUriResolver>>();
|
||||
|
||||
private ProjectResourceLoaderCache loaderCache;
|
||||
|
||||
final private Consumer<IJavaProjectData> projectListener = project -> {
|
||||
projectResolvers.remove(project);
|
||||
|
||||
List<String> projectSources = project.getClasspath().getEntries().stream()
|
||||
.filter(cpe -> Classpath.isProjectSource(cpe))
|
||||
.map(cpe -> cpe.getPath())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (IJavaProjectData javaProject : projectResolvers.keySet()) {
|
||||
if (javaProject != null) {
|
||||
for (CPE cpe : javaProject.getClasspath().getEntries()) {
|
||||
if (Classpath.isSource(cpe) && !cpe.isOwn() && !cpe.isSystem() && !cpe.isTest()) {
|
||||
if (projectSources.contains(cpe.getPath())) {
|
||||
projectResolvers.remove(javaProject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public ProjectClasspathUriResolverExtension(IJavaProjectProvider javaProjectProvider, ProjectResourceLoaderCache loaderCache) {
|
||||
this.javaProjectProvider = javaProjectProvider;
|
||||
this.loaderCache = loaderCache;
|
||||
|
||||
javaProjectProvider.addListener(projectListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolve(String file, String publicId, String systemId) {
|
||||
System.out.println("BaseLocation=" + file + " publicId=" + publicId + " systemId=" + systemId);
|
||||
|
||||
// systemId is already resolved; so don't touch
|
||||
if (systemId != null && systemId.startsWith("jar:")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// identify the correct project
|
||||
IJavaProjectData project = null;
|
||||
|
||||
if (file != null) {
|
||||
if (file.startsWith(ProjectAwareUrlStreamHandlerFactory.PROJECT_AWARE_PROTOCOL_HEADER)) {
|
||||
String nameAndLocation = file
|
||||
.substring(ProjectAwareUrlStreamHandlerFactory.PROJECT_AWARE_PROTOCOL_HEADER
|
||||
.length());
|
||||
String projectName = nameAndLocation.substring(0, nameAndLocation.indexOf('/'));
|
||||
project = javaProjectProvider.get(projectName);
|
||||
} else {
|
||||
project = getBestMatchingProject(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (project == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (systemId == null && file != null) {
|
||||
systemId = findSystemIdFromFile(file, publicId);
|
||||
}
|
||||
|
||||
if (systemId == null && publicId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ProjectClasspathUriResolver resolver = getProjectResolver(file, project);
|
||||
if (resolver != null) {
|
||||
String resolved = resolver.resolveOnClasspath(publicId, systemId);
|
||||
if (resolved != null) {
|
||||
resolved = ProjectAwareUrlStreamHandlerFactory.createProjectAwareUrl(project.getName(), resolved);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isCachingDisabled() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
private ProjectClasspathUriResolver getProjectResolver(String fileUrl, final IJavaProjectData project) {
|
||||
|
||||
// if (!XmlNamespaceUtils.useNamespacesFromClasspath(project)) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
if (fileUrl != null && !fileUrl.endsWith(".xml") && !fileUrl.endsWith(".xsd")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Special case for 'pom.xml'. We can skip it entirely because it is not used to define spring beans.
|
||||
// Also... m2e apparantly causes this to be called directly from the UI thread causing major hangs / annoyance.
|
||||
// See: https://github.com/spring-projects/sts4/issues/318
|
||||
if (fileUrl != null && fileUrl.endsWith("pom.xml")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Future<ProjectClasspathUriResolver> future = projectResolvers.get(project);
|
||||
if (future == null) {
|
||||
|
||||
Callable<ProjectClasspathUriResolver> createResolver = new Callable<ProjectClasspathUriResolver>() {
|
||||
public ProjectClasspathUriResolver call() throws InterruptedException {
|
||||
ProjectClasspathUriResolver resolver = new ProjectClasspathUriResolver(loaderCache, project, isCachingDisabled());
|
||||
return resolver;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
FutureTask<ProjectClasspathUriResolver> futureTask = new FutureTask<ProjectClasspathUriResolver>(createResolver);
|
||||
future = projectResolvers.putIfAbsent(project, futureTask);
|
||||
if (future == null) {
|
||||
future = futureTask;
|
||||
futureTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
}
|
||||
catch (CancellationException e) {
|
||||
projectResolvers.remove(project, future);
|
||||
return null;
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
return null;
|
||||
} catch (InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private IJavaProjectData getBestMatchingProject(String file) {
|
||||
try {
|
||||
String fileUri = new URL(file).toURI().toString();
|
||||
return javaProjectProvider.findProject(fileUri);
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String findSystemIdFromFile(String file, String publicId) {
|
||||
InputStream contents = null;
|
||||
try {
|
||||
contents = new URL(file).openStream();
|
||||
DocumentBuilderFactory builderFactory = DocumentBuilderFactory
|
||||
.newInstance();
|
||||
builderFactory.setValidating(false);
|
||||
builderFactory.setNamespaceAware(true);
|
||||
|
||||
builderFactory.setFeature("http://xml.org/sax/features/validation", false);
|
||||
builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
|
||||
builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
|
||||
DocumentBuilder builder = builderFactory.newDocumentBuilder();
|
||||
Document doc = builder.parse(contents);
|
||||
|
||||
DocumentAccessor accessor = new DocumentAccessor();
|
||||
accessor.pushDocument(doc);
|
||||
SchemaLocations locations = accessor.getCurrentSchemaLocations();
|
||||
|
||||
String location = locations.getSchemaLocation(publicId);
|
||||
return location;
|
||||
} catch (Exception e) {
|
||||
// do nothing, systemId cannot be identified
|
||||
} finally {
|
||||
if (contents != null) {
|
||||
try {
|
||||
contents.close();
|
||||
} catch (IOException e) {
|
||||
// do nothing, systemId cannot be identified
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.classpath;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
|
||||
public class FilteringURLResourceLoader extends ResourceLoader {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(FilteringURLResourceLoader.class.getName());
|
||||
|
||||
|
||||
// private static Map<String, AtomicLong> jarScanned = Collections.synchronizedMap(new HashMap<String, AtomicLong>());
|
||||
|
||||
// private static final boolean DEBUG = false;
|
||||
|
||||
private static Set<String> _fetchedResources = Collections.synchronizedSet(new HashSet<>());
|
||||
private static ImmutableSet<String> fetchedResources = ImmutableSet.copyOf(_fetchedResources);
|
||||
|
||||
private URL[] urls;
|
||||
private ResourceLoader parent;
|
||||
|
||||
private Set<String> _indexValidFor = ImmutableSet.of();
|
||||
private ImmutableSetMultimap<String, String> resourcesIndex = null;
|
||||
|
||||
|
||||
public FilteringURLResourceLoader(URL[] directories, ResourceLoader parent) {
|
||||
this.urls = directories;
|
||||
this.parent = parent == null ? ResourceLoader.NULL : parent;
|
||||
}
|
||||
|
||||
private static AtomicLong indexBuilt = new AtomicLong();
|
||||
private static AtomicLong indexReused = new AtomicLong();
|
||||
|
||||
|
||||
|
||||
// @Override
|
||||
// public URL getResource(String resourceName) {
|
||||
// try {
|
||||
// if (!shouldFilter(resourceName)) {
|
||||
// URL fromParent = parent.getResource(resourceName);
|
||||
// if (fromParent!=null) {
|
||||
// return fromParent;
|
||||
// }
|
||||
// Collection<String> resources = getResourcesCollection(resourceName);
|
||||
// if (!resources.isEmpty()) {
|
||||
// return new URL(resources.iterator().next());
|
||||
// }
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// SpringXmlNamespacesPlugin.log(e);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
/**
|
||||
* Get's collection of resources from this resource loader, but excluding resources
|
||||
* from the parent.
|
||||
*/
|
||||
private Collection<String> getResourcesCollection(String resourceName) {
|
||||
if (!shouldFilter(resourceName)) {
|
||||
// long start = System.currentTimeMillis();
|
||||
try {
|
||||
ensureIndexed(resourceName);
|
||||
Collection<String> r = resourcesIndex.get(resourceName);
|
||||
return r != null ? r : ImmutableList.of();
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
// } finally {
|
||||
// long duration = System.currentTimeMillis() - start;
|
||||
// long total = timeUsed.addAndGet(duration);
|
||||
// long requestCount = request.incrementAndGet();
|
||||
// System.out.println("Time spent finding resources:");
|
||||
// System.out.println(" requests = " + requestCount);
|
||||
// System.out.println(" avg = " + total / requestCount);
|
||||
// System.out.println(" total = " + total);
|
||||
// System.out.println(" index built/reused = " + indexBuilt.get() +" / "+indexReused.get());
|
||||
|
||||
// System.out.println("Jar scan counts:");
|
||||
// for (Entry<String, AtomicLong> e : jarScanned.entrySet()) {
|
||||
// System.out.println(" "+e.getValue().get() +": "+e.getKey());
|
||||
// }
|
||||
}
|
||||
}
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
private synchronized void ensureIndexed(String resourceName) {
|
||||
if (resourcesIndex!=null && isIndexValidFor(resourceName)) {
|
||||
indexReused.incrementAndGet();
|
||||
} else {
|
||||
indexBuilt.incrementAndGet();
|
||||
synchronized (_fetchedResources) {
|
||||
if (_fetchedResources.add(resourceName)) {
|
||||
fetchedResources = ImmutableSet.copyOf(_fetchedResources);
|
||||
// save(_fetchedResources);
|
||||
}
|
||||
}
|
||||
resourcesIndex = buildIndex(name -> isInterestingByDefault(name) || fetchedResources.contains(name));
|
||||
_indexValidFor = fetchedResources;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIndexValidFor(String resourceName) {
|
||||
return isInterestingByDefault(resourceName) || _indexValidFor.contains(resourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* If you can predict, based on looking at a resourceName that it is likely to
|
||||
* be 'interesting' for future lookups, then this method can be overridden so it returns true
|
||||
* for those 'interesting' resources. This will allow the resource-loader to pre-cache
|
||||
* the interesting values from the get-go, and thereby avoid rebuilding the
|
||||
* index multiple times.
|
||||
* <p>
|
||||
* The default implementation provided here is optimised specifically for resolving
|
||||
* spring .xsd schemas.
|
||||
*/
|
||||
protected boolean isInterestingByDefault(String resourceName) {
|
||||
return resourceName.startsWith("META-INF/spring") || resourceName.endsWith(".xsd");
|
||||
}
|
||||
|
||||
private ImmutableSetMultimap<String, String> buildIndex(Predicate<String> interestingResourceNames) {
|
||||
ImmutableSetMultimap.Builder<String, String> resources = ImmutableSetMultimap.builder();
|
||||
//find in our urls
|
||||
for (URL url : urls) {
|
||||
try {
|
||||
if (isZip(url)) {
|
||||
fetchResourceFromZip(interestingResourceNames, url, resources);
|
||||
} else {
|
||||
url.getProtocol().equals("file");
|
||||
File file = Paths.get(url.toURI()).toFile();
|
||||
if (file.isDirectory()) {
|
||||
fetchResourceFromDirectory(interestingResourceNames, file, resources);
|
||||
} else {
|
||||
fetchResourceFromZip(interestingResourceNames, url, resources);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
}
|
||||
return resources.build();
|
||||
}
|
||||
|
||||
private void fetchResourceFromDirectory(Predicate<String> interesttingResourceNames,
|
||||
File file,
|
||||
ImmutableSetMultimap.Builder<String, String> resources
|
||||
) {
|
||||
try {
|
||||
Path rootDir = file.toPath();
|
||||
FileVisitor<Path> visitor = new FileVisitor<Path>() {
|
||||
|
||||
final FileVisitResult fvr = FileVisitResult.CONTINUE;
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
return fvr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (attrs.isRegularFile()) {
|
||||
String name = rootDir.relativize(file).toString();
|
||||
if (interesttingResourceNames.test(name)) {
|
||||
resources.put(name, file.toUri().toString());
|
||||
}
|
||||
}
|
||||
return fvr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||||
return fvr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
return fvr;
|
||||
}
|
||||
};
|
||||
Files.walkFileTree(rootDir, visitor);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchResourceFromZip(Predicate<String> interestingResourceNames, URL url, ImmutableSetMultimap.Builder<String, String> requestor) {
|
||||
// AtomicLong counter = jarScanned.computeIfAbsent(url.toString(), s -> new AtomicLong());
|
||||
// counter.incrementAndGet();
|
||||
try {
|
||||
try (InputStream input = url.openStream()) {
|
||||
ZipInputStream zip = new ZipInputStream(input);
|
||||
ZipEntry ze = zip.getNextEntry();
|
||||
while (ze!=null) {
|
||||
String resourceName = ze.getName();
|
||||
if (interestingResourceNames.test(resourceName)) {
|
||||
//Example url: jar:file:/home/kdvolder/.m2/repository/org/springframework/boot/spring-boot/2.1.4.RELEASE/spring-boot-2.1.4.RELEASE.jar!/META-INF/spring.factories
|
||||
// System.out.println("FOUND "+resourceName+" in "+url);
|
||||
requestor.put(resourceName, "jar:"+url+"!/"+ze);
|
||||
// } else {
|
||||
// System.out.println("mismatch: "+ze.getName());
|
||||
}
|
||||
ze = zip.getNextEntry();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isZip(URL url) {
|
||||
String path = url.getPath();
|
||||
return path.endsWith(".jar") || path.endsWith(".zip");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<URL> getResources(String resourceName) {
|
||||
if (!shouldFilter(resourceName)) {
|
||||
Stream<URL> localResources =
|
||||
getResourcesCollection(resourceName).stream()
|
||||
.map(resource -> {
|
||||
try {
|
||||
return new URL(resource);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return Stream.concat(parent.getResources(resourceName), localResources);
|
||||
}
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
public static boolean shouldFilter(String name) {
|
||||
if ("commons-logging.properties".equals(name)) return true;
|
||||
if (name != null && name.startsWith("META-INF/services/")) {
|
||||
return (name.indexOf('/', 18) == -1
|
||||
&& !name.startsWith("org.springframework", 18));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.classpath;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath;
|
||||
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider;
|
||||
import org.springframework.ide.vscode.xml.IJavaProjectProvider.IJavaProjectData;
|
||||
|
||||
/**
|
||||
* Internal cache of classpath urls and corresponding resourceloaders.
|
||||
* @author Christian Dupuis
|
||||
* @author Martin Lippert
|
||||
* @since 2.2.5
|
||||
*/
|
||||
public class ProjectResourceLoaderCache {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(ProjectResourceLoaderCache.class.getName());
|
||||
|
||||
private static final int CACHE_SIZE = 200;
|
||||
|
||||
private final List<ResourceLoaderCacheEntry> resourceLoaderCache = new ArrayList<ResourceLoaderCacheEntry>(CACHE_SIZE);
|
||||
|
||||
// private IResourceChangeListener resourceChangeListener = null;
|
||||
|
||||
private IJavaProjectProvider javaProjectProvider;
|
||||
|
||||
public ProjectResourceLoaderCache(IJavaProjectProvider javaProjectProvider) {
|
||||
this.javaProjectProvider = javaProjectProvider;
|
||||
}
|
||||
|
||||
private ResourceLoader addResourceLoaderToCache(IJavaProjectData project, List<URL> urls, ResourceLoader parentResourceLoader) {
|
||||
synchronized (resourceLoaderCache) {
|
||||
int nEntries = resourceLoaderCache.size();
|
||||
if (nEntries >= CACHE_SIZE) {
|
||||
// find obsolete entries or remove entry that was least recently accessed
|
||||
ResourceLoaderCacheEntry oldest = null;
|
||||
List<ResourceLoaderCacheEntry> obsoleteResourceLoaders = new ArrayList<ResourceLoaderCacheEntry>(CACHE_SIZE);
|
||||
for (int i = 0; i < nEntries; i++) {
|
||||
ResourceLoaderCacheEntry entry = (ResourceLoaderCacheEntry) resourceLoaderCache.get(i);
|
||||
IJavaProjectData curr = entry.getProject();
|
||||
if (javaProjectProvider.exists(curr.getName())) {
|
||||
obsoleteResourceLoaders.add(entry);
|
||||
}
|
||||
else {
|
||||
if (oldest == null || entry.getLastAccess() < oldest.getLastAccess()) {
|
||||
oldest = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!obsoleteResourceLoaders.isEmpty()) {
|
||||
for (int i = 0; i < obsoleteResourceLoaders.size(); i++) {
|
||||
removeResourceLoaderEntryFromCache((ResourceLoaderCacheEntry) obsoleteResourceLoaders.get(i));
|
||||
}
|
||||
}
|
||||
else if (oldest != null) {
|
||||
removeResourceLoaderEntryFromCache(oldest);
|
||||
}
|
||||
}
|
||||
ResourceLoaderCacheEntry newEntry = new ResourceLoaderCacheEntry(project, urls, parentResourceLoader);
|
||||
resourceLoaderCache.add(newEntry);
|
||||
return newEntry.getResourceLoader();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link URL}s to the given set of <code>paths</code>.
|
||||
*/
|
||||
private void addClassPathUrls(IJavaProjectData project, List<URL> paths, Set<IJavaProjectData> resolvedProjects) {
|
||||
|
||||
// add project to local cache to prevent adding its classpaths multiple times
|
||||
if (resolvedProjects.contains(project)) {
|
||||
return;
|
||||
} else {
|
||||
resolvedProjects.add(project);
|
||||
}
|
||||
|
||||
// configured classpath
|
||||
Classpath classpath = project.getClasspath();
|
||||
|
||||
// add class path entries
|
||||
for (CPE cpe : classpath.getEntries()) {
|
||||
try {
|
||||
if (Classpath.isBinary(cpe)) {
|
||||
addFile(paths, new File(cpe.getPath()));
|
||||
} else if (Classpath.isSource(cpe)) {
|
||||
addFile(paths, new File(cpe.getPath()));
|
||||
addFile(paths, new File(cpe.getOutputFolder()));
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFile(List<URL> paths, File file) throws MalformedURLException {
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
paths.add(new URL(file.toURI().toString() + File.separator));
|
||||
}
|
||||
else {
|
||||
paths.add(file.toURI().toURL());
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Classpath entry '" + file + "' does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
private ResourceLoader findResourceLoaderInCache(IJavaProjectData project, ResourceLoader parentResourceLoader) {
|
||||
synchronized (resourceLoaderCache) {
|
||||
for (int i = resourceLoaderCache.size() - 1; i >= 0; i--) {
|
||||
ResourceLoaderCacheEntry entry = (ResourceLoaderCacheEntry) resourceLoaderCache.get(i);
|
||||
IJavaProjectData curr = entry.getProject();
|
||||
if (javaProjectProvider.exists(curr.getName())) {
|
||||
removeResourceLoaderEntryFromCache(entry);
|
||||
}
|
||||
else {
|
||||
if (entry.matches(project, parentResourceLoader)) {
|
||||
entry.markAsAccessed();
|
||||
return entry.getResourceLoader();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all class path entries of the given <code>project</code> and all depending projects.
|
||||
* <p>
|
||||
* Note: if <code>useParentResourceLoader</code> is true, the Spring, AspectJ, Commons Logging and ASM bundles are
|
||||
* automatically added to the paths.
|
||||
* @param project the {@link IProject}
|
||||
* @param useParentResourceLoader use the OSGi resourceloader as parent
|
||||
* @return a set of {@link URL}s that can be used to construct a {@link URLResourceLoader}
|
||||
*/
|
||||
public List<URL> getClassPathUrls(IJavaProjectData project, ResourceLoader parentResourceLoader) {
|
||||
|
||||
// needs to be linked to preserve ordering
|
||||
List<URL> paths = new ArrayList<URL>();
|
||||
Set<IJavaProjectData> resolvedProjects = new HashSet<IJavaProjectData>();
|
||||
addClassPathUrls(project, paths, resolvedProjects);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Registers internal listeners that listen to changes relevant to clear out stale cache entries.
|
||||
// */
|
||||
// private void registerListenersIfRequired() {
|
||||
// if (resourceChangeListener == null) {
|
||||
// resourceChangeListener = new SourceAndOutputLocationResourceChangeListener();
|
||||
// ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Removes the given {@link ResourceLoaderCacheEntry} from the internal cache.
|
||||
* @param entry the entry to remove
|
||||
*/
|
||||
private void removeResourceLoaderEntryFromCache(ResourceLoaderCacheEntry entry) {
|
||||
synchronized (resourceLoaderCache) {
|
||||
entry.dispose();
|
||||
resourceLoaderCache.remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResourceLoader} for the given project.
|
||||
*/
|
||||
public ResourceLoader getResourceLoader(IJavaProjectData project, ResourceLoader parentResourceLoader) {
|
||||
synchronized (ProjectResourceLoaderCache.class) {
|
||||
// Setup the root class loader to be used when no explicit parent class loader is given
|
||||
if (parentResourceLoader==null) {
|
||||
parentResourceLoader = ResourceLoader.NULL;
|
||||
}
|
||||
if (project == null) {
|
||||
return parentResourceLoader;
|
||||
}
|
||||
|
||||
// registerListenersIfRequired();
|
||||
}
|
||||
|
||||
ResourceLoader classLoader = findResourceLoaderInCache(project, parentResourceLoader);
|
||||
if (classLoader == null) {
|
||||
List<URL> urls = getClassPathUrls(project, parentResourceLoader);
|
||||
classLoader = addResourceLoaderToCache(project, urls, parentResourceLoader);
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any cached {@link ResourceLoaderCacheEntry} for the given {@link IProject}.
|
||||
* @param project the project to remove {@link ResourceLoaderCacheEntry} for
|
||||
*/
|
||||
protected void removeResourceLoaderEntryFromCache(IJavaProjectData project) {
|
||||
synchronized (resourceLoaderCache) {
|
||||
for (ResourceLoaderCacheEntry entry : new ArrayList<ResourceLoaderCacheEntry>(resourceLoaderCache)) {
|
||||
if (project.equals(entry.getProject())) {
|
||||
entry.dispose();
|
||||
resourceLoaderCache.remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal cache entry
|
||||
*/
|
||||
class ResourceLoaderCacheEntry /*implements IElementChangedListener*/ {
|
||||
|
||||
private URL[] directories;
|
||||
|
||||
private ResourceLoader jarResourceLoader;
|
||||
|
||||
private long lastAccess;
|
||||
|
||||
private ResourceLoader parentResourceLoader;
|
||||
|
||||
private IJavaProjectData project;
|
||||
|
||||
private URL[] urls;
|
||||
|
||||
public ResourceLoaderCacheEntry(IJavaProjectData project, List<URL> urls, ResourceLoader parentResourceLoader) {
|
||||
this.project = project;
|
||||
this.urls = urls.toArray(new URL[urls.size()]);
|
||||
this.parentResourceLoader = parentResourceLoader;
|
||||
markAsAccessed();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
this.urls = null;
|
||||
this.jarResourceLoader = null;
|
||||
}
|
||||
|
||||
// public void elementChanged(ElementChangedEvent event) {
|
||||
// IJavaProject javaProject = JdtUtils.getJavaProject(project);
|
||||
// if (javaProject != null) {
|
||||
// for (IJavaElementDelta delta : event.getDelta().getAffectedChildren()) {
|
||||
// if ((delta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0
|
||||
// || (delta.getFlags() & IJavaElementDelta.F_CLASSPATH_CHANGED) != 0) {
|
||||
// if (javaProject.equals(delta.getElement()) || javaProject.isOnClasspath(delta.getElement())) {
|
||||
// removeResourceLoaderEntryFromCache(this);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public ResourceLoader getResourceLoader() {
|
||||
ResourceLoader parent = getJarResourceLoader();
|
||||
return new FilteringURLResourceLoader(directories, parent);
|
||||
}
|
||||
|
||||
public long getLastAccess() {
|
||||
return lastAccess;
|
||||
}
|
||||
|
||||
public IJavaProjectData getProject() {
|
||||
return this.project;
|
||||
}
|
||||
|
||||
public void markAsAccessed() {
|
||||
lastAccess = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean matches(IJavaProjectData project, ResourceLoader parentResourceLoader) {
|
||||
return this.project.equals(project)
|
||||
&& ((parentResourceLoader == null && this.parentResourceLoader == null) || (parentResourceLoader != null && parentResourceLoader
|
||||
.equals(this.parentResourceLoader)));
|
||||
}
|
||||
|
||||
private synchronized ResourceLoader getJarResourceLoader() {
|
||||
if (jarResourceLoader == null) {
|
||||
Set<URL> jars = new LinkedHashSet<URL>();
|
||||
List<URL> dirs = new ArrayList<URL>();
|
||||
for (URL url : urls) {
|
||||
if (shouldLoadFromParent(url)) {
|
||||
jars.add(url);
|
||||
}
|
||||
else {
|
||||
dirs.add(url);
|
||||
}
|
||||
}
|
||||
if (parentResourceLoader == null) {
|
||||
parentResourceLoader = ResourceLoader.NULL;
|
||||
}
|
||||
jarResourceLoader = new FilteringURLResourceLoader((URL[]) jars.toArray(new URL[jars.size()]),
|
||||
parentResourceLoader);
|
||||
directories = dirs.toArray(new URL[dirs.size()]);
|
||||
}
|
||||
return jarResourceLoader;
|
||||
}
|
||||
|
||||
private boolean shouldLoadFromParent(URL url) {
|
||||
String path = url.getPath();
|
||||
if (path.endsWith(".jar") || path.endsWith(".zip")) {
|
||||
return true;
|
||||
}
|
||||
else if (path.contains("/org.eclipse.osgi/bundles/")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * {@link IResourceChangeListener} to clear the cache whenever new source or output folders are being added.
|
||||
// * @since 2.5.2
|
||||
// */
|
||||
// static class SourceAndOutputLocationResourceChangeListener implements IResourceChangeListener {
|
||||
//
|
||||
// private static final int VISITOR_FLAGS = IResourceDelta.ADDED | IResourceDelta.CHANGED;
|
||||
//
|
||||
// /**
|
||||
// * {@inheritDoc}
|
||||
// */
|
||||
// public void resourceChanged(IResourceChangeEvent event) {
|
||||
// if (event.getSource() instanceof IWorkspace) {
|
||||
// int eventType = event.getType();
|
||||
// switch (eventType) {
|
||||
// case IResourceChangeEvent.POST_CHANGE:
|
||||
// IResourceDelta delta = event.getDelta();
|
||||
// if (delta != null) {
|
||||
// try {
|
||||
// delta.accept(getVisitor(), VISITOR_FLAGS);
|
||||
// }
|
||||
// catch (CoreException e) {
|
||||
// SpringXmlNamespacesPlugin.log("Error while traversing resource change delta", e);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// else if (event.getSource() instanceof IProject) {
|
||||
// int eventType = event.getType();
|
||||
// switch (eventType) {
|
||||
// case IResourceChangeEvent.POST_CHANGE:
|
||||
// IResourceDelta delta = event.getDelta();
|
||||
// if (delta != null) {
|
||||
// try {
|
||||
// delta.accept(getVisitor(), VISITOR_FLAGS);
|
||||
// }
|
||||
// catch (CoreException e) {
|
||||
// SpringXmlNamespacesPlugin.log("Error while traversing resource change delta", e);
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// protected IResourceDeltaVisitor getVisitor() {
|
||||
// return new SourceAndOutputLocationResourceVisitor();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Internal resource delta visitor.
|
||||
// */
|
||||
// protected class SourceAndOutputLocationResourceVisitor implements IResourceDeltaVisitor {
|
||||
//
|
||||
// public final boolean visit(IResourceDelta delta) throws CoreException {
|
||||
// IResource resource = delta.getResource();
|
||||
// switch (delta.getKind()) {
|
||||
// case IResourceDelta.ADDED:
|
||||
// return resourceAdded(resource);
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// protected boolean resourceAdded(IResource resource) {
|
||||
// if (resource instanceof IFolder && JdtUtils.isJavaProject(resource)) {
|
||||
// try {
|
||||
// IJavaProject javaProject = JdtUtils.getJavaProject(resource);
|
||||
// // Safe guard once again
|
||||
// if (javaProject == null) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // Check the default output location
|
||||
// if (javaProject.getOutputLocation() != null
|
||||
// && javaProject.getOutputLocation().equals(resource.getFullPath())) {
|
||||
// removeResourceLoaderEntryFromCache(resource.getProject());
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // Check any source and output folder location
|
||||
// for (IClasspathEntry entry : javaProject.getRawClasspath()) {
|
||||
// if (resource.getFullPath() != null && resource.getFullPath().equals(entry.getPath())) {
|
||||
// removeResourceLoaderEntryFromCache(resource.getProject());
|
||||
// return false;
|
||||
// }
|
||||
// else if (resource.getFullPath() != null
|
||||
// && resource.getFullPath().equals(entry.getOutputLocation())) {
|
||||
// removeResourceLoaderEntryFromCache(resource.getProject());
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (JavaModelException e) {
|
||||
// SpringXmlNamespacesPlugin.log("Error traversing resource change delta", e);
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ide.vscode.xml.namespaces.classpath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Convenient utility methods for loading of {@code java.util.Properties},
|
||||
* performing standard handling of input streams.
|
||||
*
|
||||
* <p>For more configurable properties loading, including the option of a
|
||||
* customized encoding, consider using the PropertiesLoaderSupport class.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @since 2.0
|
||||
* @see PropertiesLoaderSupport
|
||||
*/
|
||||
public abstract class PropertiesLoaderUtils {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PropertiesLoaderUtils.class.getName());
|
||||
|
||||
private static final String XML_FILE_EXTENSION = ".xml";
|
||||
|
||||
/**
|
||||
* Load all properties from the specified class path resource
|
||||
* (in ISO-8859-1 encoding), using the given class loader.
|
||||
* <p>Merges properties if more than one resource of the same name
|
||||
* found in the class path.
|
||||
* @param resourceName the name of the class path resource
|
||||
* @param classLoader the ClassLoader to use for loading
|
||||
* (or {@code null} to use the default class loader)
|
||||
* @return the populated Properties instance
|
||||
* @throws IOException if loading failed
|
||||
*/
|
||||
public static Properties loadAllProperties(String resourceName, ResourceLoader classLoader) {
|
||||
if (resourceName == null) {
|
||||
throw new IllegalArgumentException("Resource name must not be null");
|
||||
}
|
||||
ResourceLoader classLoaderToUse = classLoader;
|
||||
if (classLoaderToUse == null) {
|
||||
classLoaderToUse = ResourceLoader.NULL;
|
||||
}
|
||||
ResourceLoader loader = classLoaderToUse;
|
||||
Properties props = new Properties();
|
||||
loader.getResources(resourceName).parallel().forEach(url -> {
|
||||
try {
|
||||
URLConnection con = url.openConnection();
|
||||
useCachesIfNecessary(con);
|
||||
InputStream is = con.getInputStream();
|
||||
try {
|
||||
if (resourceName.endsWith(XML_FILE_EXTENSION)) {
|
||||
props.loadFromXML(is);
|
||||
} else {
|
||||
props.load(is);
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, e, null);
|
||||
}
|
||||
});
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link URLConnection#setUseCaches "useCaches"} flag on the
|
||||
* given connection, preferring {@code false} but leaving the
|
||||
* flag at {@code true} for JNLP based resources.
|
||||
* @param con the URLConnection to set the flag on
|
||||
*/
|
||||
private static void useCachesIfNecessary(URLConnection con) {
|
||||
con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.classpath;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Subset of {@link ClassLoader} interface. Mimicks behavior of
|
||||
* classloaders but only can be used to load 'resources' as data.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class ResourceLoader {
|
||||
|
||||
public static final ResourceLoader NULL = new ResourceLoader() {
|
||||
@Override
|
||||
public Stream<URL> getResources(String resourceName) {
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceLoader.NULL";
|
||||
}
|
||||
};
|
||||
|
||||
public final URL getResource(String resourceName) {
|
||||
return getResources(resourceName).findAny().orElse(null);
|
||||
}
|
||||
protected abstract Stream<URL> getResources(String resourceName);
|
||||
|
||||
public final InputStream getResourceAsStream(String name) {
|
||||
URL url = getResource(name);
|
||||
try {
|
||||
return url != null ? url.openStream() : null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NamespaceDefinition {
|
||||
|
||||
private Pattern versionPattern = Pattern.compile(".*-([0-9,.]*)\\.xsd");
|
||||
|
||||
private String name;
|
||||
|
||||
private String namespaceUri;
|
||||
|
||||
private String prefix;
|
||||
|
||||
private Set<String> schemaLocations = new CopyOnWriteArraySet<String>();
|
||||
|
||||
private Set<String> uris = new CopyOnWriteArraySet<String>();
|
||||
|
||||
private Properties uriMapping = new Properties();
|
||||
|
||||
private String defaultSchemaLocation = null;
|
||||
|
||||
public NamespaceDefinition(Properties uriMapping) {
|
||||
this.uriMapping = uriMapping;
|
||||
}
|
||||
|
||||
public void addSchemaLocation(String schemaLocation) {
|
||||
schemaLocations.add(schemaLocation);
|
||||
}
|
||||
|
||||
public void addUri(String uri) {
|
||||
uris.add(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public synchronized String getDefaultSchemaLocation() {
|
||||
if (defaultSchemaLocation == null) {
|
||||
// Per convention the version-less XSD is the default
|
||||
for (String schemaLocation : schemaLocations) {
|
||||
if (!versionPattern.matcher(schemaLocation).matches()) {
|
||||
defaultSchemaLocation = schemaLocation;
|
||||
}
|
||||
}
|
||||
if (defaultSchemaLocation == null && schemaLocations.size() > 0) {
|
||||
List<String> locations = new ArrayList<String>(schemaLocations);
|
||||
Collections.sort(locations);
|
||||
defaultSchemaLocation = locations.get(0);
|
||||
}
|
||||
}
|
||||
return defaultSchemaLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getDefaultUri() {
|
||||
String defaultUri = null;
|
||||
NamespaceVersion version = NamespaceVersion.MINIMUM_VERSION;
|
||||
for (String uri : uris) {
|
||||
NamespaceVersion tempVersion = NamespaceVersion.MINIMUM_VERSION;
|
||||
Matcher matcher = versionPattern.matcher(uri);
|
||||
if (matcher.matches()) {
|
||||
tempVersion = new NamespaceVersion(matcher.group(1));
|
||||
}
|
||||
if (tempVersion.compareTo(version) >= 0) {
|
||||
version = tempVersion;
|
||||
defaultUri = uri;
|
||||
}
|
||||
}
|
||||
return defaultUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getNamespaceUri() {
|
||||
return namespaceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public String getPrefix() {
|
||||
if (prefix != null) {
|
||||
return prefix;
|
||||
}
|
||||
int ix = namespaceUri.lastIndexOf('/');
|
||||
if (ix > 0) {
|
||||
return namespaceUri.substring(ix + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Set<String> getSchemaLocations() {
|
||||
return schemaLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void setNamespaceUri(String namespaceUri) {
|
||||
this.namespaceUri = namespaceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public Properties getUriMapping() {
|
||||
return this.uriMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.namespaceUri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.model;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NamespaceVersion implements Comparable<NamespaceVersion> {
|
||||
|
||||
private static final String MINIMUM_VERSION_STRING = "0";
|
||||
|
||||
private static final Pattern versionPattern = Pattern.compile("([0-9]\\d*)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-([a-zA-Z0-9]+))?");
|
||||
|
||||
public static final NamespaceVersion MINIMUM_VERSION = new NamespaceVersion(MINIMUM_VERSION_STRING);
|
||||
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
private final String qualifier;
|
||||
|
||||
public NamespaceVersion(String v) {
|
||||
Matcher matcher = versionPattern.matcher(v);
|
||||
|
||||
if (matcher.matches()) {
|
||||
qualifier = matcher.groupCount() > 3 ? (matcher.group(4) == null ? "" : matcher.group(4)) : "";
|
||||
patch = matcher.groupCount() > 2 ? (matcher.group(3) == null ? 0 : Integer.valueOf(matcher.group(3))) : 0;
|
||||
minor = matcher.groupCount() > 1 ? (matcher.group(2) == null ? 0 : Integer.valueOf(matcher.group(2))) : 0;
|
||||
major = matcher.groupCount() > 0 ? (matcher.group(1) == null ? 0 : Integer.valueOf(matcher.group(1))) : 0;
|
||||
} else {
|
||||
major = 0;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
qualifier = "";
|
||||
}
|
||||
}
|
||||
|
||||
public int compareTo(NamespaceVersion v2) {
|
||||
if (major == v2.major) {
|
||||
if (minor == v2.minor) {
|
||||
if (patch == v2.patch) {
|
||||
return qualifier.compareTo(v2.qualifier);
|
||||
} else {
|
||||
return patch - v2.patch;
|
||||
}
|
||||
} else {
|
||||
return minor - v2.minor;
|
||||
}
|
||||
} else {
|
||||
return major - v2.major;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public int getMinor() {
|
||||
return minor;
|
||||
}
|
||||
|
||||
public int getPatch() {
|
||||
return patch;
|
||||
}
|
||||
|
||||
public String getQualifier() {
|
||||
return qualifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(major);
|
||||
sb.append('.');
|
||||
sb.append(minor);
|
||||
sb.append('.');
|
||||
sb.append(patch);
|
||||
if (!qualifier.isEmpty()) {
|
||||
sb.append('-');
|
||||
sb.append(qualifier);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
/**
|
||||
* Utility that manages an internal stack of {@link Document}.
|
||||
* @author Christian Dupuis
|
||||
* @since 2.2.7
|
||||
*/
|
||||
public class DocumentAccessor {
|
||||
|
||||
private Stack<Document> documents = new Stack<Document>();
|
||||
|
||||
private Stack<Node> elements = new Stack<Node>();
|
||||
|
||||
private Node lastElement = null;
|
||||
|
||||
private Document lastDocument = null;
|
||||
|
||||
private Map<Document, SchemaLocations> schemaLocations = new HashMap<Document, SchemaLocations>();
|
||||
|
||||
/** Push a new document onto the internal stack structure */
|
||||
public void pushDocument(Document doc) {
|
||||
lastDocument = doc;
|
||||
documents.push(doc);
|
||||
}
|
||||
|
||||
/** Push a new element onto the internal stack structure */
|
||||
public void pushElement(Node element) {
|
||||
lastElement = element;
|
||||
elements.push(element);
|
||||
}
|
||||
|
||||
/** Returns the current document; meaning the first document in the stack */
|
||||
public Document getCurrentDocument() {
|
||||
if (!documents.isEmpty()) {
|
||||
return documents.peek();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Retuns the last procssed document */
|
||||
public Document getLastDocument() {
|
||||
return lastDocument;
|
||||
}
|
||||
|
||||
/** Returns the current element; meaning the first element in the stack */
|
||||
public Node getCurrentElement() {
|
||||
if (!elements.isEmpty()) {
|
||||
return elements.peek();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the last processed element */
|
||||
public Node getLastElement() {
|
||||
return lastElement;
|
||||
}
|
||||
|
||||
/** Returns the current values of the schemaLocation attribute */
|
||||
public synchronized SchemaLocations getCurrentSchemaLocations() {
|
||||
Document doc = getCurrentDocument();
|
||||
if (!this.schemaLocations.containsKey(doc)) {
|
||||
if (doc != null && doc.getDocumentElement() != null) {
|
||||
SchemaLocations schemaLocations = new SchemaLocations();
|
||||
schemaLocations.initSchemaLocations(doc.getDocumentElement().getAttributeNS(
|
||||
"http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"));
|
||||
this.schemaLocations.put(doc, schemaLocations);
|
||||
}
|
||||
}
|
||||
return this.schemaLocations.get(doc);
|
||||
}
|
||||
|
||||
/** Removes the first document from the stack */
|
||||
public Document popDocument() {
|
||||
if (!documents.isEmpty()) {
|
||||
return documents.pop();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Removes the first element from the stack */
|
||||
public Node popElement() {
|
||||
if (!elements.isEmpty()) {
|
||||
return elements.pop();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class that parses the value of the <code>schemaLocation</code> attribute and offers accessors to the
|
||||
* mapping.
|
||||
*/
|
||||
public static class SchemaLocations {
|
||||
|
||||
private Map<String, String> mapping = new HashMap<String, String>();
|
||||
|
||||
public void initSchemaLocations(String schemaLocations) {
|
||||
if (schemaLocations != null && !schemaLocations.isEmpty()) {
|
||||
String[] tokens = schemaLocations.split("\\s*\\r?\\n\\s*");
|
||||
if (tokens.length % 2 == 0) {
|
||||
for (int i = 0; i < tokens.length; i = i + 2) {
|
||||
mapping.put(tokens[i], tokens[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSchemaLocation(String namespaceUri) {
|
||||
return mapping.get(namespaceUri);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.springframework.ide.vscode.xml.namespaces.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Scanner to quickly identify the namespace that is declared inside an XSD.
|
||||
* @author Martin Lippert
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public class TargetNamespaceScanner {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(TargetNamespaceScanner.class.getName());
|
||||
|
||||
/**
|
||||
* Returns the target namespace URI of the XSD identified by the given
|
||||
* <code>url</code>.
|
||||
*/
|
||||
public static String getTargetNamespace(URL url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(TargetNamespaceScanner.class.getClassLoader());
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setValidating(false);
|
||||
|
||||
factory.setFeature("http://xml.org/sax/features/validation", false);
|
||||
factory.setFeature("http://apache.org/xml/features/validation/dynamic", false);
|
||||
factory.setFeature("http://apache.org/xml/features/validation/schema", false);
|
||||
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
|
||||
DocumentBuilder docBuilder = factory.newDocumentBuilder();
|
||||
|
||||
Document doc = docBuilder.parse(url.openStream());
|
||||
|
||||
return doc.getDocumentElement().getAttribute("targetNamespace");
|
||||
} catch (SAXException|IOException|ParserConfigurationException e) {
|
||||
LOGGER.log(Level.WARNING, e, null);
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(ccl);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.ide.vscode.xml.SpringXmlPlugin
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.springframework.ide.vscode.xml.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.xml.namespaces.model.NamespaceVersion;
|
||||
|
||||
public class NamespaceVersionTest {
|
||||
|
||||
@Test
|
||||
public void allVersion() throws Exception {
|
||||
assertEquals("11.2.345-alpha", new NamespaceVersion("11.2.345-alpha").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void majorAndMinorVersion() throws Exception {
|
||||
assertEquals("11.262.0", new NamespaceVersion("11.262").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void majorVersion() throws Exception {
|
||||
assertEquals("11.0.0", new NamespaceVersion("11").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void majorAndMinorAndPatchVersion() throws Exception {
|
||||
assertEquals("11.0.78", new NamespaceVersion("11.0.78").toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zeroVersion() throws Exception {
|
||||
assertEquals("0.0.0", new NamespaceVersion("0").toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,10 @@
|
||||
"./jars/jdt-ls-commons.jar",
|
||||
"./jars/jdt-ls-extension.jar"
|
||||
],
|
||||
"xml.javaExtensions": [
|
||||
"./jars/commons-lsp-extensions.jar",
|
||||
"./jars/xml-ls-extension.jar"
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"id": "spring-boot-properties-yaml",
|
||||
@@ -521,7 +525,7 @@
|
||||
"vscode-languageclient": "6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vsce": "^1.74.0",
|
||||
"vsce": "^1.81.1",
|
||||
"typescript": "3.8.3",
|
||||
"@types/node": "^12.7.9",
|
||||
"@types/vscode": "^1.41.0"
|
||||
|
||||
@@ -31,6 +31,7 @@ cd ${workdir}/language-server
|
||||
server_jar_file=$(find ${workdir}/../../headless-services/spring-boot-language-server/target -name '*-exec.jar');
|
||||
jar -xvf ${server_jar_file}
|
||||
|
||||
# JDT LS Extension
|
||||
cd ${workdir}/../../headless-services/jdt-ls-extension
|
||||
find . -name "*-sources.jar" -delete
|
||||
cp org.springframework.tooling.jdt.ls.extension/target/*.jar ${workdir}/jars/jdt-ls-extension.jar
|
||||
@@ -39,3 +40,10 @@ cp org.springframework.tooling.jdt.ls.commons/target/*.jar ${workdir}/jars/jdt-l
|
||||
# Copy Reactor dependency bundles
|
||||
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/io.projectreactor.reactor-core.jar ${workdir}/jars/
|
||||
cp org.springframework.tooling.jdt.ls.commons/target/dependencies/org.reactivestreams.reactive-streams.jar ${workdir}/jars/
|
||||
|
||||
# XML LS Extension
|
||||
cd ${workdir}/../../headless-services/xml-ls-extension
|
||||
find . -name "*-sources.jar" -delete
|
||||
cp target/*.jar ${workdir}/jars/xml-ls-extension.jar
|
||||
cp target/dependencies/commons-lsp-extensions.jar ${workdir}/jars/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user