/` remains a jar file unambiguously.
*
* So the authority encodes the identity of the maven project and the path encodes
* what's inside the respective jar file. This is analogous to other schemes for projects
@@ -28,7 +30,7 @@
* Here `version` is an arbitrary string with lots of numbers, dots, dashed and underscores.
* Typically we'd expect the semantic versioning scheme here with some release tag, but
* real maven projects frequently do not adhere to that standard. Hence we have to be "free"
- * here and allow lots of funny version strings. This is also why we use ! again to separate
+ * here and allow lots of funny version strings. This is also why we use -- to separate
* the version from the artifactId.
*
* Locations with the `mvn` scheme are typically produced by configuration code that uses
@@ -77,9 +79,11 @@
* idea; but there is currently no way to register read-only logical schemes.
*/
public class MavenRepositoryURIResolver extends AliasedFileResolver {
+ private static String localRepoLocationCache;
+
private final Pattern authorityRegEx
- = Pattern.compile("^([a-zA-Z0-9-_.]+?)[!]([a-zA-Z0-9-_.]+)([!][a-zA-Z0-9\\-_.]+)$");
- // groupId ! artifactId ! optionAlComplexVersionString
+ = Pattern.compile("^([a-zA-Z0-9-_.]+?)[\\-][\\-]([a-zA-Z0-9-_.]+)[\\-][\\-]([a-zA-Z0-9\\-_.]+)$");
+ // groupId -- artifactId -- optionAlComplexVersionString
public MavenRepositoryURIResolver() throws IOException {
super("mvn", inferMavenRepositoryLocation());
@@ -111,7 +115,7 @@ private static String inferMavenRepositoryLocation() {
// note that since it does not exist this will make all downstream resolutions fail
// to "file does not exist"
}
-
+
return m2HomeFolder;
}
@@ -130,9 +134,14 @@ private static String computeMavenCommandName() {
/**
* This (slow) code runs only if the ~/.m2 folder does not exist and nobody -D'ed its location either.
* That is not necessarily how mvn prioritizes its configuration steps, but it is the way we can
- * get a quick enough answer most of the time.
+ * get a quick enough answer most of the time. It caches its result to make sure repeated calls
+ * to here are faster than the first.
*/
private static String getLocalRepositoryLocationFromMavenCommand() {
+ if (localRepoLocationCache != null) {
+ return localRepoLocationCache;
+ }
+
try {
ProcessBuilder processBuilder = new ProcessBuilder(computeMavenCommandName(),
"-q",
@@ -148,7 +157,7 @@ private static String getLocalRepositoryLocationFromMavenCommand() {
}
try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- return reader.lines().collect(Collectors.joining()).trim();
+ return (localRepoLocationCache = reader.lines().collect(Collectors.joining()).trim());
}
}
catch (IOException | InterruptedException e) {
@@ -157,7 +166,6 @@ private static String getLocalRepositoryLocationFromMavenCommand() {
}
}
-
@Override
public ISourceLocation resolve(ISourceLocation input) throws IOException {
String authority = input.getAuthority();
@@ -169,7 +177,7 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException {
}
else {
// the authority encodes the group, name and version of a maven dependency
- // org.rascalmpl.rascal-34.2.0-RC2
+ // org.rascalmpl--rascal--34.2.0-RC2
var m = authorityRegEx.matcher(authority);
if (m.matches()) {
@@ -177,7 +185,7 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException {
String name = m.group(2);
String version = m.group(3);
- version = version == null ? "" : version.substring(1);
+ // version = version == null ? "" : version.substring(1);
String jarPath
= group.replaceAll("\\.", "/")
@@ -217,4 +225,39 @@ public ISourceLocation resolve(ISourceLocation input) throws IOException {
}
}
}
+
+ public static ISourceLocation make(String groupId, String artifactId, String version, String path) {
+ return URIUtil.correctLocation("mvn", groupId + "--" + artifactId + "--" + version, path);
+ }
+
+ /**
+ * Shortens a location of a jar file that points into a local maven repository, and
+ * leaves all other locations as-is.
+ * */
+ public static ISourceLocation mavenize(ISourceLocation loc) {
+ try {
+ loc = URIResolverRegistry.getInstance().logicalToPhysical(loc);
+
+ if (!URIUtil.getExtension(loc).equals("jar")) {
+ return loc;
+ }
+
+ ISourceLocation repo = URIUtil.createFileLocation(inferMavenRepositoryLocation());
+ ISourceLocation relative = URIUtil.relativize(repo, loc);
+ boolean isFileInRepo = loc.getScheme().equals("file") && relative.getScheme().equals("relative");
+
+ if (isFileInRepo) {
+ String groupId = URIUtil.getParentLocation(URIUtil.getParentLocation(URIUtil.getParentLocation(relative))).getPath().substring(1).replaceAll("/", ".");
+ String artifactId = URIUtil.getLocationName(URIUtil.getParentLocation(URIUtil.getParentLocation(relative)));
+ String version = URIUtil.getLocationName(URIUtil.getParentLocation(relative));
+
+ return make(groupId, artifactId, version, "");
+ }
+
+ return loc;
+ }
+ catch (IOException | URISyntaxException e) {
+ return loc;
+ }
+ }
}
diff --git a/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java b/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java
deleted file mode 100644
index f1c8b1dd656..00000000000
--- a/src/org/rascalmpl/uri/libraries/RascalLibraryURIResolver.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/**
- * Copyright (c) 2019, Jurgen J. Vinju, Centrum Wiskunde & Informatica (NWOi - CWI)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.rascalmpl.uri.libraries;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.checkerframework.checker.nullness.qual.Nullable;
-import org.rascalmpl.interpreter.utils.RascalManifest;
-import org.rascalmpl.uri.ISourceLocationInput;
-import org.rascalmpl.uri.URIResolverRegistry;
-import org.rascalmpl.uri.URIUtil;
-import org.rascalmpl.uri.classloaders.IClassloaderLocationResolver;
-import org.rascalmpl.values.IRascalValueFactory;
-import org.rascalmpl.values.ValueFactoryFactory;
-
-import io.usethesource.vallang.ISourceLocation;
-import io.usethesource.vallang.IValueFactory;
-
-/**
- * The goal of this resolver is to provide |lib://<libName>/| for every Rascal library available in the current run-time environment.
- * To do this, it searches for META-INF/RASCAL.MF files in 2 places, and checks if the Project-Name inside of that file is equal to <libName>:
- *
- * - |plugin://<libName>| is probed first, in order to give precedence to plugins loaded by application containers such as OSGI;
- * - Finally ClassLoader.getResources is probed to resolve to |jar+file://path-to-jar-on-classpath!/| if a RASCAL.MF can be found there with the proper Project-Name in it. So this only searches in the JVM start-up classpath via its URLClassLoaders, ignoring plugin mechanisms like OSGI and the like.
- *
- * CAVEAT 1: this resolver caches the first resolution (plugin or jarfile) and does not rescind it afterwards even if the locations
- * cease to exist. This might happen due to plugin unloading. To re-initialize an
- * already resolved |lib://<libName>| path, either the JVM must be reloaded (restart the IDE) or the current class must be reloaded
- * (restart the plugin which loaded the Rascal run-time). TODO FIXME by allowing re-initialization of this entire resolver by the URIResolverRegistry.
- * CAVEAT 2: it is up to the respective run-time environments (Eclipse, OSGI, MVN, Spring, etc.) to provide the respective implementations
- * of ISourceLocation input for the plugin:// scheme. If it is not provided, this resolver only resolves to resources
- * which can be found via the System classloader.
- */
-public class RascalLibraryURIResolver implements ISourceLocationInput, IClassloaderLocationResolver {
- private final ConcurrentHashMap classpathLibraries = new ConcurrentHashMap<>();
- private final ConcurrentHashMap resolvedLibraries = new ConcurrentHashMap<>();
- private final URIResolverRegistry reg;
-
- public RascalLibraryURIResolver(URIResolverRegistry reg) {
- this.reg = reg;
-
- try {
- IValueFactory vf = ValueFactoryFactory.getValueFactory();
- RascalManifest mf = new RascalManifest();
- Enumeration mfs = getClass().getClassLoader().getResources(RascalManifest.META_INF_RASCAL_MF);
-
- Collections.list(mfs).forEach(url -> {
- try {
- String libName = mf.getProjectName(url.openStream());
-
- if (libName != null && !libName.isEmpty()) {
- ISourceLocation loc;
-
- if (url.getProtocol().equals("jar") && url.getPath().startsWith("file:/")) {
- loc = vf.sourceLocation("jar+file", null, URIUtil.fromURL(new URL(url.getPath())).getPath());
- }
- else {
- loc = vf.sourceLocation(URIUtil.fromURL(url));
- }
-
- loc = URIUtil.changePath(loc, loc.getPath().replace(RascalManifest.META_INF_RASCAL_MF, ""));
-
- registerLibrary("detected", classpathLibraries, libName, loc);
- }
- }
- catch (IOException | URISyntaxException e) {
- System.err.println("WARNING: could not load Rascal manifest for library resolution of: " + url);
- e.printStackTrace();
- }
- });
- }
- catch (IOException e) {
- System.err.println("WARNING: could not resolve any Rascal library locations");
- e.printStackTrace();
- }
- }
-
- private void registerLibrary(String event, ConcurrentHashMap libs, String libName, ISourceLocation loc) {
- /* we want the first match, just like the classpath, so always using the old value */
- if (libs.merge(libName, loc, (o, n) -> o) == loc) {
- /* now we have a new one: report it */
- System.err.println("INFO: " + event + " |lib://" + libName + "| at " + loc);
- }
- }
-
- /**
- * Resolve a lib location to either a plugin or a local classpath location, in that order of precedence.
- */
- private @Nullable ISourceLocation resolve(ISourceLocation uri) {
- String libName = uri.getAuthority();
-
- if (libName == null || libName.isEmpty()) {
- return null;
- }
-
- // if we resolved this library before, we stick with that initial resolution for efficiency's sake
- ISourceLocation resolved = resolvedLibraries.get(libName);
- if (resolved != null) {
- return inheritPositions(uri, URIUtil.getChildLocation(resolved, uri.getPath()));
- }
-
- // then we try plugin libraries, taking precedence over classpath libraries
- ISourceLocation plugin = deferToScheme(uri, "plugin");
- if (plugin != null) {
- return plugin;
- }
-
- // finally we try the classpath libraries
- ISourceLocation classpath = classpathLibraries.get(libName);
- if (classpath != null) {
- return resolvedLocation(uri, libName, classpath);
- }
-
- return null;
- }
-
- /**
- * Tries to find a RASCAL.MF file in the deferred scheme's root and if it's present, the
- * prefix is cached and the child location is returned.
- */
- private ISourceLocation deferToScheme(ISourceLocation uri, String scheme) {
- String libName = uri.getAuthority();
- ISourceLocation libRoot = URIUtil.correctLocation(scheme, libName, "");
-
- if (isValidLibraryRoot(libRoot)) {
- return resolvedLocation(uri, libName, libRoot);
- }
- else {
- return null;
- }
- }
-
- /**
- * Check if this root contains a valid RASCAL.MF file
- */
- private boolean isValidLibraryRoot(ISourceLocation libRoot) {
- if (reg.exists(URIUtil.getChildLocation(libRoot, RascalManifest.META_INF_RASCAL_MF))) {
- assert new RascalManifest().getProjectName(libRoot).equals(libRoot.getAuthority())
- : "Project-Name in RASCAL.MF does not align with authority of the " + libRoot.getScheme() + " scheme";
- return true;
- }
-
- return false;
- }
-
- /**
- * compute the resolved child location and cache the prefix as a side-effect for a future fast path
- */
- private ISourceLocation resolvedLocation(ISourceLocation uri, String libName, ISourceLocation deferredLoc) {
- registerLibrary("resolved", resolvedLibraries, libName, deferredLoc);
- return inheritPositions(uri, URIUtil.getChildLocation(deferredLoc, uri.getPath()));
- }
-
- private static ISourceLocation inheritPositions(ISourceLocation uri, ISourceLocation resolved) {
- if (uri.hasOffsetLength()) {
- if (uri.hasLineColumn()) {
- return IRascalValueFactory.getInstance().sourceLocation(resolved, uri.getOffset(), uri.getLength(), uri.getBeginLine(), uri.getEndLine(), uri.getBeginColumn(), uri.getEndColumn());
- }
- else {
- return IRascalValueFactory.getInstance().sourceLocation(resolved, uri.getOffset(), uri.getLength());
- }
- }
- else {
- return resolved;
- }
- }
-
- /**
- * Resolve a location and if not possible throw an exception
- */
- private ISourceLocation safeResolve(ISourceLocation uri) throws IOException {
- ISourceLocation resolved = resolve(uri);
- if (resolved == null) {
- throw new IOException("lib:// resolver could not resolve " + uri);
- }
- return resolved;
- }
-
- @Override
- public InputStream getInputStream(ISourceLocation uri) throws IOException {
- return reg.getInputStream(safeResolve(uri));
- }
-
- @Override
- public Charset getCharset(ISourceLocation uri) throws IOException {
- return reg.getCharset(safeResolve(uri));
- }
-
- @Override
- public boolean exists(ISourceLocation uri) {
- ISourceLocation resolved = resolve(uri);
- if (resolved == null) {
- return false;
- }
- return reg.exists(resolved);
- }
-
- @Override
- public long lastModified(ISourceLocation uri) throws IOException {
- return reg.lastModified(safeResolve(uri));
- }
-
- @Override
- public boolean isDirectory(ISourceLocation uri) {
- try {
- return URIResolverRegistry.getInstance().isDirectory(safeResolve(uri));
- } catch (IOException e) {
- return false;
- }
- }
-
- @Override
- public boolean isFile(ISourceLocation uri) {
- try {
- return URIResolverRegistry.getInstance().isFile(safeResolve(uri));
- } catch (IOException e) {
- return false;
- }
- }
-
- @Override
- public String[] list(ISourceLocation uri) throws IOException {
- return reg.listEntries(safeResolve(uri));
- }
-
- @Override
- public String scheme() {
- return "lib";
- }
-
- @Override
- public boolean supportsHost() {
- return false;
- }
-
- @Override
- public ClassLoader getClassLoader(ISourceLocation loc, ClassLoader parent) throws IOException {
- ISourceLocation resolved = resolve(loc);
- if (resolved != null) {
- return reg.getClassLoader(resolved, parent);
- }
-
- throw new IOException("Can not resolve classloader for " + loc);
- }
-}
diff --git a/src/org/rascalmpl/uri/resolvers.config b/src/org/rascalmpl/uri/resolvers.config
index a0f096180ce..2b599b78ccc 100644
--- a/src/org/rascalmpl/uri/resolvers.config
+++ b/src/org/rascalmpl/uri/resolvers.config
@@ -14,5 +14,4 @@ org.rascalmpl.uri.file.CurrentWorkingDriveResolver
org.rascalmpl.uri.file.UNCResolver
org.rascalmpl.uri.file.SystemPathURIResolver
org.rascalmpl.uri.libraries.MemoryResolver
-org.rascalmpl.uri.libraries.RascalLibraryURIResolver
diff --git a/src/org/rascalmpl/values/RascalFunctionValueFactory.java b/src/org/rascalmpl/values/RascalFunctionValueFactory.java
index 268d049479f..5e77d2cf679 100644
--- a/src/org/rascalmpl/values/RascalFunctionValueFactory.java
+++ b/src/org/rascalmpl/values/RascalFunctionValueFactory.java
@@ -500,7 +500,7 @@ protected IValue parse(String methodName, ISet filters, IString input, ISourceL
protected IValue firstAmbiguity(String methodName, IString input) {
try {
- return parseObject(methodName, URIUtil.invalidLocation(), input.getValue().toCharArray(), false, false, vf.set());
+ return parseObject(methodName, URIUtil.unknownLocation(), input.getValue().toCharArray(), false, false, vf.set());
}
catch (ParseError pe) {
ISourceLocation errorLoc = pe.getLocation();
diff --git a/test/org/rascalmpl/test/parser/StackNodeTest.java b/test/org/rascalmpl/test/parser/StackNodeTest.java
index bfc52a4f5b8..6b849ca5fa7 100644
--- a/test/org/rascalmpl/test/parser/StackNodeTest.java
+++ b/test/org/rascalmpl/test/parser/StackNodeTest.java
@@ -3,7 +3,6 @@
import org.junit.Assert;
import org.junit.Test;
import org.rascalmpl.parser.gtd.stack.EpsilonStackNode;
-import org.rascalmpl.parser.gtd.stack.LiteralStackNode;
import org.rascalmpl.parser.gtd.stack.filter.ICompletionFilter;
import org.rascalmpl.parser.gtd.stack.filter.IEnterFilter;
import org.rascalmpl.parser.gtd.stack.filter.follow.AtEndOfLineRequirement;
diff --git a/test/org/rascalmpl/test/util/library/RunRascalTestModules.java b/test/org/rascalmpl/test/util/library/RunRascalTestModules.java
new file mode 100644
index 00000000000..b5f36bcf3b0
--- /dev/null
+++ b/test/org/rascalmpl/test/util/library/RunRascalTestModules.java
@@ -0,0 +1,10 @@
+package org.rascalmpl.test.util.library;
+
+import org.junit.runner.RunWith;
+import org.rascalmpl.test.infrastructure.RascalJUnitTestPrefix;
+import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner;
+
+@RunWith(RascalJUnitTestRunner.class)
+@RascalJUnitTestPrefix("lang::rascal::tests::library")
+public class RunRascalTestModules { }
+