| /*=============================================================================# |
| # Copyright (c) 2018 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; |
| |
| } |