diff --git a/src/org/rascalmpl/uri/ILogicalSourceLocationResolver.java b/src/org/rascalmpl/uri/ILogicalSourceLocationResolver.java index 05f17c91303..75bdbb2c694 100644 --- a/src/org/rascalmpl/uri/ILogicalSourceLocationResolver.java +++ b/src/org/rascalmpl/uri/ILogicalSourceLocationResolver.java @@ -1,11 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2009-2025 CWI + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI + *******************************************************************************/ + package org.rascalmpl.uri; import java.io.IOException; import io.usethesource.vallang.ISourceLocation; +/** + * A logical resolver translates locations keyed by their scheme and (optionally) their authority + * to a lower level scheme. Logical resolvers may translate to other logical resolvers, but + * most of them map directly to a physical location like file:/// + */ public interface ILogicalSourceLocationResolver { ISourceLocation resolve(ISourceLocation input) throws IOException; String scheme(); String authority(); + + /** + * Some logical resolver may collect entries from more than one location, + * hence first resolving and then calling ISourceLocationInput.list(..) won't do. + * An example is the PATH:/// resolver. + */ + default String[] resolveList(ISourceLocation input) throws IOException { + return URIResolverRegistry.getInstance().listEntries(resolve(input)); + } } diff --git a/src/org/rascalmpl/uri/URIResolverRegistry.java b/src/org/rascalmpl/uri/URIResolverRegistry.java index 4e55fc2d7bc..5503cc24c20 100644 --- a/src/org/rascalmpl/uri/URIResolverRegistry.java +++ b/src/org/rascalmpl/uri/URIResolverRegistry.java @@ -709,16 +709,22 @@ private boolean isRootLogical(ISourceLocation uri) { } public String[] listEntries(ISourceLocation uri) throws IOException { - uri = safeResolve(uri); - if (isRootLogical(uri)) { - // if it's a location without any path and authority - // we want to list possible authorities if it's a logical one - // (logical resolvers cannot handle this call themselves) - Map candidates = logicalResolvers.get(uri.getScheme()); - if (candidates != null) { + Map candidates = logicalResolvers.get(uri.getScheme()); + + if (candidates != null && !candidates.isEmpty()) { + // this is a logical uri. we defer to resolveList to allow for the logical resolver to concatenate from + // different sources. But not if the authority and the path are empty, then we list the available authorities + if (isRootLogical(uri) && candidates.size() > 1) { return candidates.keySet().toArray(new String[0]); } + + String auth = uri.hasAuthority() ? uri.getAuthority() : ""; + ILogicalSourceLocationResolver resolver = candidates.get(auth); + + return resolver.resolveList(uri); } + + uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { diff --git a/src/org/rascalmpl/uri/file/SystemPathURIResolver.java b/src/org/rascalmpl/uri/file/SystemPathURIResolver.java index 4a8f4abc6d2..a0a79551c45 100644 --- a/src/org/rascalmpl/uri/file/SystemPathURIResolver.java +++ b/src/org/rascalmpl/uri/file/SystemPathURIResolver.java @@ -14,6 +14,8 @@ package org.rascalmpl.uri.file; import java.io.File; +import java.io.IOException; +import java.util.stream.Stream; import java.util.Arrays; import org.rascalmpl.uri.ILogicalSourceLocationResolver; @@ -32,16 +34,20 @@ public String scheme() { return "PATH"; } - - @Override - public ISourceLocation resolve(ISourceLocation input) { + private Stream pathStream() { String thePath = System.getenv("PATH"); if (thePath == null) { - return input; + return Stream.empty(); } return Arrays.stream(thePath.split(File.pathSeparator)) .map(FileURIResolver::constructFileURI) + .filter(URIResolverRegistry.getInstance()::exists); + } + + @Override + public ISourceLocation resolve(ISourceLocation input) { + return pathStream() .map(r -> URIUtil.getChildLocation(r, input.getPath())) .filter(URIResolverRegistry.getInstance()::exists) .findFirst() @@ -49,6 +55,29 @@ public ISourceLocation resolve(ISourceLocation input) { ; } + @Override + public String[] resolveList(ISourceLocation input) { + return pathStream() + .map(r -> URIUtil.getChildLocation(r, input.getPath())) + .filter(URIResolverRegistry.getInstance()::exists) + .flatMap(r -> { + // here we concatenate the `.list()` for all folders in the path + try { + if (URIResolverRegistry.getInstance().isDirectory(r)) { + return Arrays.stream(URIResolverRegistry.getInstance().listEntries(r)); + } + else { + return Stream.empty(); + } + } + catch (IOException e) { + return null; + } + }) + .filter(o -> o != null) + .toArray(String[]::new); + } + @Override public String authority() { return "";