diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index e096c3b0e5c..60d5edb2e55 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -1333,10 +1333,13 @@ private void writeFileEnc(ISourceLocation sloc, IString charset, IList V, boolea try { sloc = reg.logicalToPhysical(sloc); - if (reg.supportsInputScheme(sloc.getScheme())) { - if (sloc.hasOffsetLength()) { + + if (sloc.hasOffsetLength()) { + try { prefix = new UnicodeOffsetLengthReader(reg.getCharacterReader(sloc.top(), charset.getValue()), 0, sloc.getOffset() + ( append ? sloc.getLength() : 0 )); postfix = new UnicodeOffsetLengthReader(reg.getCharacterReader(sloc.top(), charset.getValue()), sloc.getOffset() + sloc.getLength(), -1); + } catch (UnsupportedSchemeException e) { + // silently ignoring that we cannot do an append for this scheme as there is no input stream resolver defined for this scheme } } diff --git a/src/org/rascalmpl/uri/URIResolverRegistry.java b/src/org/rascalmpl/uri/URIResolverRegistry.java index bc7e65de4cb..8f981bbdc3b 100644 --- a/src/org/rascalmpl/uri/URIResolverRegistry.java +++ b/src/org/rascalmpl/uri/URIResolverRegistry.java @@ -33,10 +33,12 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.checkerframework.checker.nullness.qual.Nullable; import org.rascalmpl.unicode.UnicodeInputStreamReader; import org.rascalmpl.unicode.UnicodeOffsetLengthReader; import org.rascalmpl.uri.ISourceLocationWatcher.ISourceLocationChanged; @@ -57,6 +59,14 @@ public class URIResolverRegistry { private final Map watchers = new ConcurrentHashMap<>(); private final Map>> watching = new ConcurrentHashMap<>(); + // we allow the user to define (using -Drascal.fallbackResolver=fully.qualified.classname) a single class that will handle + // scheme's not statically registered. That class should implement at least one of these interfaces + private volatile @Nullable ISourceLocationInput fallbackInputResolver; + private volatile @Nullable ISourceLocationOutput fallbackOutputResolver; + private volatile @Nullable ILogicalSourceLocationResolver fallbackLogicalResolver; + private volatile @Nullable IClassloaderLocationResolver fallbackClassloaderResolver; + private volatile @Nullable ISourceLocationWatcher fallbackWatcher; + private static class InstanceHolder { static URIResolverRegistry sInstance = new URIResolverRegistry(); } @@ -98,6 +108,10 @@ private void loadServices() { try { Enumeration resources = getClass().getClassLoader().getResources(RESOLVERS_CONFIG); Collections.list(resources).forEach(f -> loadServices(f)); + var fallbackResolverClassName = System.getProperty("rascal.fallbackResolver"); + if (fallbackResolverClassName != null) { + loadFallback(fallbackResolverClassName); + } } catch (IOException e) { throw new Error("WARNING: Could not load URIResolverRegistry extensions from " + RESOLVERS_CONFIG, e); @@ -120,6 +134,57 @@ public Set getRegisteredClassloaderSchemes() { return Collections.unmodifiableSet(classloaderResolvers.keySet()); } + private Object constructService(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException { + Class clazz = Thread.currentThread().getContextClassLoader().loadClass(name); + + try { + return clazz.getDeclaredConstructor(URIResolverRegistry.class).newInstance(this); + } + catch (NoSuchMethodException e) { + return clazz.newInstance(); + } + } + + private void loadFallback(String fallbackClass) { + try { + Object instance = constructService(fallbackClass); + boolean ok = false; + if (instance instanceof ILogicalSourceLocationResolver) { + fallbackLogicalResolver = (ILogicalSourceLocationResolver) instance; + ok = true; + } + + if (instance instanceof ISourceLocationInput) { + fallbackInputResolver = (ISourceLocationInput) instance; + ok = true; + } + + if (instance instanceof ISourceLocationOutput) { + fallbackOutputResolver = (ISourceLocationOutput) instance; + ok = true; + } + + if (instance instanceof IClassloaderLocationResolver) { + fallbackClassloaderResolver = (IClassloaderLocationResolver) instance; + ok = true; + } + + if (instance instanceof ISourceLocationWatcher) { + fallbackWatcher = (ISourceLocationWatcher) instance; + } + if (!ok) { + System.err.println("WARNING: could not load fallback resolver " + fallbackClass + + " because it does not implement ISourceLocationInput or ISourceLocationOutput or ILogicalSourceLocationResolver"); + } + } + catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException + | IllegalArgumentException | InvocationTargetException | SecurityException e) { + System.err.println("WARNING: could not load resolver due to " + e.getMessage()); + e.printStackTrace(); + } + + } + private void loadServices(URL nextElement) { try { for (String name : readConfigFile(nextElement)) { @@ -130,15 +195,7 @@ private void loadServices(URL nextElement) { continue; } - Class clazz = Thread.currentThread().getContextClassLoader().loadClass(name); - Object instance; - - try { - instance = clazz.getDeclaredConstructor(URIResolverRegistry.class).newInstance(this); - } - catch (NoSuchMethodException e) { - instance = clazz.newInstance(); - } + Object instance = constructService(name); boolean ok = false; @@ -268,75 +325,85 @@ public ISourceLocation logicalToPhysical(ISourceLocation loc) throws IOException return result; } - private ISourceLocation physicalLocation(ISourceLocation loc) throws IOException { - while (logicalResolvers.containsKey(loc.getScheme())) { - Map map = logicalResolvers.get(loc.getScheme()); - String auth = loc.hasAuthority() ? loc.getAuthority() : ""; - ILogicalSourceLocationResolver resolver = map.get(auth); - ISourceLocation prev = loc; - boolean removedOffset = false; + private static ISourceLocation resolveAndFixOffsets(ISourceLocation loc, ILogicalSourceLocationResolver resolver, Iterable backups) throws IOException { + ISourceLocation prev = loc; + boolean removedOffset = false; - if (resolver != null) { - loc = resolver.resolve(loc); - } + if (resolver != null) { + loc = resolver.resolve(loc); + } - if (loc == null && prev.hasOffsetLength()) { - loc = resolver.resolve(URIUtil.removeOffset(prev)); - removedOffset = true; - } + if (loc == null && prev.hasOffsetLength()) { + loc = resolver.resolve(URIUtil.removeOffset(prev)); + removedOffset = true; + } - if (loc == null || prev.equals(loc)) { - for (ILogicalSourceLocationResolver backup : map.values()) { - removedOffset = false; - loc = backup.resolve(prev); + if (loc == null || prev.equals(loc)) { + for (ILogicalSourceLocationResolver backup : backups) { + removedOffset = false; + loc = backup.resolve(prev); - if (loc == null && prev.hasOffsetLength()) { - loc = backup.resolve(URIUtil.removeOffset(prev)); - removedOffset = true; - } + if (loc == null && prev.hasOffsetLength()) { + loc = backup.resolve(URIUtil.removeOffset(prev)); + removedOffset = true; + } - if (loc != null && !prev.equals(loc)) { - break; // continue to offset/length handling below with found location - } + if (loc != null && !prev.equals(loc)) { + break; // continue to offset/length handling below with found location } } + } - if (loc == null || prev.equals(loc)) { - return null; - } + if (loc == null || prev.equals(loc)) { + return null; + } - if (removedOffset || !loc.hasOffsetLength()) { // then copy the offset from the logical one - if (prev.hasLineColumn()) { - loc = vf.sourceLocation(loc, prev.getOffset(), prev.getLength(), prev.getBeginLine(), - prev.getEndLine(), prev.getBeginColumn(), prev.getEndColumn()); - } - else if (prev.hasOffsetLength()) { - if (loc.hasOffsetLength()) { - loc = vf.sourceLocation(loc, prev.getOffset() + loc.getOffset(), prev.getLength()); - } - else { - loc = vf.sourceLocation(loc, prev.getOffset(), prev.getLength()); - } - } + if (removedOffset || !loc.hasOffsetLength()) { // then copy the offset from the logical one + if (prev.hasLineColumn()) { + return vf.sourceLocation(loc, prev.getOffset(), prev.getLength(), prev.getBeginLine(), + prev.getEndLine(), prev.getBeginColumn(), prev.getEndColumn()); } - else if (loc.hasLineColumn()) { // the logical location offsets relative to the physical offset, possibly - // including line numbers - if (prev.hasLineColumn()) { - loc = vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength(), - loc.getBeginLine() + prev.getBeginLine() - 1, loc.getEndLine() + prev.getEndLine() - 1, - loc.getBeginColumn(), loc.getEndColumn()); + else if (prev.hasOffsetLength()) { + if (loc.hasOffsetLength()) { + return vf.sourceLocation(loc, prev.getOffset() + loc.getOffset(), prev.getLength()); } - else if (prev.hasOffsetLength()) { - loc = vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength()); + else { + return vf.sourceLocation(loc, prev.getOffset(), prev.getLength()); } } - else if (loc.hasOffsetLength()) { // the logical location offsets relative to the physical one - if (prev.hasOffsetLength()) { - loc = vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength()); - } + } + else if (loc.hasLineColumn()) { // the logical location offsets relative to the physical offset, possibly + // including line numbers + if (prev.hasLineColumn()) { + return vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength(), + loc.getBeginLine() + prev.getBeginLine() - 1, loc.getEndLine() + prev.getEndLine() - 1, + loc.getBeginColumn(), loc.getEndColumn()); + } + else if (prev.hasOffsetLength()) { + return vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength()); + } + } + else if (loc.hasOffsetLength()) { // the logical location offsets relative to the physical one + if (prev.hasOffsetLength()) { + return vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength()); } } + // otherwise we return the loc without any offsets + return loc; + } + private ISourceLocation physicalLocation(ISourceLocation loc) throws IOException { + ISourceLocation original = loc; + while (loc != null && logicalResolvers.containsKey(loc.getScheme())) { + Map map = logicalResolvers.get(loc.getScheme()); + String auth = loc.hasAuthority() ? loc.getAuthority() : ""; + ILogicalSourceLocationResolver resolver = map.get(auth); + loc = resolveAndFixOffsets(loc, resolver, map.values()); + } + var fallBack = fallbackLogicalResolver; + if (fallBack != null) { + return resolveAndFixOffsets(loc == null ? original : loc, fallBack, Collections.emptyList()); + } return loc; } @@ -392,6 +459,7 @@ private ISourceLocationInput getInputResolver(String scheme) { String subScheme = m.group(1); return inputResolvers.get(subScheme); } + return fallbackInputResolver; } return result; } @@ -404,6 +472,7 @@ private IClassloaderLocationResolver getClassloaderResolver(String scheme) { String subScheme = m.group(1); return classloaderResolvers.get(subScheme); } + return fallbackClassloaderResolver; } return result; } @@ -416,18 +485,11 @@ private ISourceLocationOutput getOutputResolver(String scheme) { String subScheme = m.group(1); return outputResolvers.get(subScheme); } + return fallbackOutputResolver; } return result; } - public boolean supportsInputScheme(String scheme) { - return getInputResolver(scheme) != null; - } - - public boolean supportsOutputScheme(String scheme) { - return getOutputResolver(scheme) != null; - } - public boolean supportsHost(ISourceLocation uri) { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); @@ -864,7 +926,7 @@ public void watch(ISourceLocation loc, boolean recursive, Consumer