blob: 7e8f8d17a0a8950a6721e57f9970785f4d95a80c [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2018, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.server.util;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import static org.eclipse.statet.jcommons.runtime.CommonsRuntime.log;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.runtime.ClassLoaderUtils;
import org.eclipse.statet.jcommons.runtime.UriUtils;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.rj.RjInitFailedException;
@NonNullByDefault
public abstract class PathEntryProvider {
public static class JarFilePathEntryProvider extends PathEntryProvider {
private final Pattern namePattern;
public JarFilePathEntryProvider(final ImList<Path> baseDirectories, final Pattern namePattern,
final List<Closeable> closeables) {
super(baseDirectories, closeables);
this.namePattern= namePattern;
}
@Override
public void getEntries(final Path baseDirectory, final List<PathEntry> entries)
throws IOException {
try (final DirectoryStream<Path> children= Files.newDirectoryStream(baseDirectory)) {
for (final Path child : children) {
final Matcher nameMatcher= this.namePattern.matcher(child.getFileName().toString());
if (nameMatcher.matches() && Files.isRegularFile(child)) {
final String bundleId= nonNullAssert(nameMatcher.group(1));
entries.add(new PathEntry.Jar(bundleId, child));
}
}
}
}
}
public static class DevBinPathEntryProvider extends PathEntryProvider {
public DevBinPathEntryProvider(final ImList<Path> baseDirectories,
final List<Closeable> closeables) {
super(baseDirectories, closeables);
}
@Override
public void getEntries(final Path baseDirectory, final List<PathEntry> entries)
throws IOException {
try (final DirectoryStream<Path> children= Files.newDirectoryStream(baseDirectory)) {
for (final Path child : children) {
final Path devBin;
if (Files.isDirectory(devBin= child.resolve("bin"))) {
final String bundleId= child.getFileName().toString();
entries.add(new PathEntry(bundleId, devBin) {
@Override
public @Nullable Path getResourcePath(final String resource) {
Path path= super.getResourcePath(resource);
if (path != null) {
return path;
}
path= getPath().resolveSibling(resource);
if (Files.exists(path)) {
return path;
}
return null;
}
});
}
}
}
}
}
// Pattern to detect:
// file:/../org.eclipse.statet-rj/core/org.eclipse.statet.rj.server/bin/
// jar:file:/../org.eclipse.statet-rj/core/org.eclipse.statet.rj.server/target/org.eclipse.statet.rj.server-3.0.0-SNAPSHOT.jar!/
// jar:file:/../rserver/org.eclipse.statet.rj.server.jar!/
// jar:file:/../rhelp.server-4.0.0-SNAPSHOT.jar!/BOOT-INF/lib/org.eclipse.statet.rj.server-3.0.0-SNAPSHOT.jar!/
private static final String FILE_PROTOCOL_REGEX= "\\Qfile:/\\E";
private static final String JAR_FILE_PROTOCOL_REGEX= "\\Qjar:file:/\\E";
private static final String SERVER_BUNDLE_ID_REGEX= "\\Q" + ServerUtils.RJ_SERVER_ID + "\\E";
private static final String VER_1_REGEX= "\\_\\d+\\.\\d+[^!/]+";
private static final String VER_2_REGEX= "\\-\\d+\\.\\d+[^!/]+";
private static final String JAR_REGEX= "\\Q.jar\\E";
private static final String AUTODETECT_REGEX=
"(?:" +
"(" + FILE_PROTOCOL_REGEX + ".*)/" + SERVER_BUNDLE_ID_REGEX + "\\Q/bin/\\E" + // match 1= file: ..
"|" +
"(" + JAR_FILE_PROTOCOL_REGEX + ".*)/" + SERVER_BUNDLE_ID_REGEX + // match 2= jar:file: ..
"(?:(" + VER_1_REGEX + ")|(" + VER_2_REGEX + "))?" + // match 3= ver_1, match 4= ver_2
JAR_REGEX + "\\Q!/\\E" +
")";
private static final Pattern AUTODETECT_PATTERN= Pattern.compile(AUTODETECT_REGEX);
private static final Pattern JAR_VER_0_PATTERN= Pattern.compile("(.+)" + JAR_REGEX); //$NON-NLS-1$
private static final Pattern JAR_VER_1_PATTERN= Pattern.compile("(.+)" + VER_1_REGEX + JAR_REGEX); //$NON-NLS-1$
private static final Pattern JAR_VER_2_PATTERN= Pattern.compile("(.+)" + VER_2_REGEX + JAR_REGEX); //$NON-NLS-1$
public static PathEntryProvider detectLibPaths(final Class<?> refClass,
final @Nullable List<Path> expliciteBaseDirectories) throws RjInitFailedException {
String refUrl= null;
List<Closeable> closeables= ImCollections.emptyList();
try {
refUrl= ClassLoaderUtils.getClassLocationUrlString(refClass);
final Matcher matcher= AUTODETECT_PATTERN.matcher(refUrl);
if (matcher.matches()) {
int detectedType;
final URI detectedBaseUri;
if (matcher.start(1) != -1) { // file:
detectedType= 1;
final String s= nonNullAssert(matcher.group(1));
detectedBaseUri= new URI(s);
}
else if (matcher.start(2) != -1) { // jar:file:
detectedType= 2;
final String s= nonNullAssert(matcher.group(2));
if (s.indexOf(UriUtils.JAR_SEPARATOR) == -1) {
detectedBaseUri= new URI(s.substring(4)); // remove jar
}
else {
detectedBaseUri= new URI(s);
}
}
else {
throw new IllegalStateException();
}
final ImList<Path> baseDirectories;
{ Path detectedBaseDirectory;
while (true) {
try {
detectedBaseDirectory= Paths.get(detectedBaseUri);
break;
}
catch (final FileSystemNotFoundException e) {
final Map<String, String> fsEnv= new HashMap<>();
fsEnv.put("create", "true");
try {
final FileSystem fs= FileSystems.newFileSystem(detectedBaseUri, fsEnv);
// closeables= ImCollections.newList(fs);
}
catch (final FileSystemAlreadyExistsException exists) {}
}
}
detectedBaseDirectory= detectedBaseDirectory.normalize();
if (expliciteBaseDirectories != null && !expliciteBaseDirectories.isEmpty()) {
final List<Path> uniqueList= new ArrayList<>();
for (Path baseDirectory : expliciteBaseDirectories) {
baseDirectory= baseDirectory.normalize();
if (!uniqueList.contains(baseDirectory)) {
uniqueList.add(baseDirectory);
}
}
if (!uniqueList.contains(detectedBaseDirectory)) {
uniqueList.add(detectedBaseDirectory);
}
baseDirectories= ImCollections.toList(uniqueList);
}
else {
baseDirectories= ImCollections.newList(detectedBaseDirectory);
}
}
final PathEntryProvider provider;
if (detectedType == 1) {
provider= new DevBinPathEntryProvider(baseDirectories,
closeables );
}
else {
final Pattern fileNamePattern;
if (matcher.start(3) != -1) {
fileNamePattern= JAR_VER_1_PATTERN;
}
else if (matcher.start(4) != -1) {
fileNamePattern= JAR_VER_2_PATTERN;
}
else {
fileNamePattern= JAR_VER_0_PATTERN;
}
provider= new JarFilePathEntryProvider(baseDirectories, fileNamePattern,
closeables );
}
closeables= null;
return provider;
}
throw new UnsupportedOperationException("url= " + refUrl);
}
catch (final Exception e) {
throw new RjInitFailedException(
String.format("Failed to autodetect RJ library location" +
"\n\tclass= %1$s" + //$NON-NLS-1$
"\n\turl= %2$s", //$NON-NLS-1$
(refClass != null) ? refClass.getName() : "<NA>",
(refUrl != null) ? '\'' + refUrl + '\'' : "<NA>" ),
e );
}
finally {
if (closeables != null) {
close(closeables);
}
}
}
private static void close(final List<Closeable> closeables) {
for (final Closeable closeable : closeables) {
try {
closeable.close();
}
catch (final Exception e) {
log(new ErrorStatus(ServerUtils.RJ_SERVER_ID,
"An error occurred when disposing closable of path entry provider.",
e ));
}
}
}
private final ImList<Path> baseDirectories;
private final List<Closeable> closeables;
protected PathEntryProvider(final ImList<Path> baseDirectories,
final List<Closeable> closeables) {
this.baseDirectories= baseDirectories;
this.closeables= closeables;
}
public void dispose() {
close(this.closeables);
}
protected ImList<Path> getBaseDirectories() {
return this.baseDirectories;
}
public void getEntries(final List<PathEntry> entries) {
for (final Path baseDirectory : this.baseDirectories) {
try {
getEntries(baseDirectory, entries);
}
catch (final Exception e) {
log(new ErrorStatus(ServerUtils.RJ_SERVER_ID,
String.format("An error occurred when looking for path entries in '%1$s'.",
baseDirectory ),
e ));
}
}
}
protected abstract void getEntries(final Path baseDirectory, final List<PathEntry> entries)
throws IOException;
}