/******************************************************************************* * Copyright (c) 2009-2013 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 * * Paul Klint - Paul.Klint@cwi.nl - CWI * * Mark Hills - Mark.Hills@cwi.nl (CWI) * * Arnold Lankamp - Arnold.Lankamp@cwi.nl *******************************************************************************/ package org.rascalmpl.uri; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.charset.Charset; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.rascalmpl.unicode.UnicodeInputStreamReader; import org.rascalmpl.unicode.UnicodeOffsetLengthReader; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.value.IValueFactory; import org.rascalmpl.values.ValueFactoryFactory; public class URIResolverRegistry { private static final String RESOLVERS_CONFIG = "org/rascalmpl/uri/resolvers.config"; private static final IValueFactory vf = ValueFactoryFactory.getValueFactory(); private final Map<String,ISourceLocationInput> inputResolvers = new HashMap<>(); private final Map<String,ISourceLocationOutput> outputResolvers = new HashMap<>(); private final Map<String, Map<String,ILogicalSourceLocationResolver>> logicalResolvers = new HashMap<>(); private static class InstanceHolder { static URIResolverRegistry sInstance = new URIResolverRegistry(); } private URIResolverRegistry() { loadServices(); } private void loadServices() { try { Enumeration<URL> resources = getClass().getClassLoader().getResources(RESOLVERS_CONFIG); Collections.list(resources).forEach(f -> loadServices(f)); } catch (IOException e) { throw new Error("WARNING: Could not load URIResolverRegistry extensions from " + RESOLVERS_CONFIG, e); } } public Set<String> getRegisteredInputSchemes() { return Collections.unmodifiableSet(inputResolvers.keySet()); } public Set<String> getRegisteredOutputSchemes() { return Collections.unmodifiableSet(outputResolvers.keySet()); } public Set<String> getRegisteredLogicalSchemes() { return Collections.unmodifiableSet(logicalResolvers.keySet()); } private void loadServices(URL nextElement) { try { for (String name : readConfigFile(nextElement)) { name = name.trim(); if (name.startsWith("#") || name.isEmpty()) { // source code comment or empty line continue; } Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(name); Object instance; try { instance = clazz.getDeclaredConstructor(URIResolverRegistry.class).newInstance(this); } catch (NoSuchMethodException e) { instance = clazz.newInstance(); } boolean ok = false; if (instance instanceof ILogicalSourceLocationResolver) { registerLogical((ILogicalSourceLocationResolver) instance); ok = true; } if (instance instanceof ISourceLocationInput) { registerInput((ISourceLocationInput) instance); ok = true; } if (instance instanceof ISourceLocationOutput) { registerOutput((ISourceLocationOutput) instance); ok = true; } if (!ok) { System.err.println("WARNING: could not load resolver " + name + " because it does not implement ISourceLocationInput or ISourceLocationOutput or ILogicalSourceLocationResolver"); } } } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException | IllegalArgumentException | InvocationTargetException | SecurityException | IOException e) { System.err.println("WARNING: could not load resolver due to " + e.getMessage()); e.printStackTrace(); } } private String[] readConfigFile(URL nextElement) throws IOException { try (Reader in = new InputStreamReader(nextElement.openStream())) { StringBuilder res = new StringBuilder(); char[] chunk = new char[1024]; int read; while ((read = in.read(chunk, 0, chunk.length)) != -1) { res.append(chunk, 0, read); } return res.toString().split("\n"); } } public static URIResolverRegistry getInstance() { return InstanceHolder.sInstance; } private static InputStream makeBuffered(InputStream original) { if (original.getClass() != BufferedInputStream.class) { return new BufferedInputStream(original); } return original; } private static OutputStream makeBuffered(OutputStream original) { if (original.getClass() != BufferedOutputStream.class) { return new BufferedOutputStream(original); } return original; } /** * Translates a logical location (i.e. for a specific language * scheme) to a physical location. For this mapping the registered * {@link ILogicalSourceLocationResolver} collection is used. These * are indexed first by scheme and then by authority. If the scheme * is registered but the authority is not, then the same lookup is tried * again without authority. * * @param loc logical source location * @return physical source location * @throws IOException when there is no registered resolver for the logical scheme provided */ public ISourceLocation logicalToPhysical(ISourceLocation loc) throws IOException { ISourceLocation result = physicalLocation(loc); if (result == null) { throw new FileNotFoundException(loc.toString()); } return result; } private ISourceLocation physicalLocation(ISourceLocation loc) { synchronized (logicalResolvers) { while (logicalResolvers.containsKey(loc.getScheme())) { Map<String, ILogicalSourceLocationResolver> map = logicalResolvers.get(loc.getScheme()); String auth = loc.hasAuthority() ? loc.getAuthority() : ""; ILogicalSourceLocationResolver resolver = map.get(auth); ISourceLocation prev = loc; boolean removedOffset = false; if (resolver != null) { loc = resolver.resolve(loc); } 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.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)) { 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()); } } } 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()) { loc = 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()) { loc = vf.sourceLocation(loc, loc.getOffset() + prev.getOffset(), loc.getLength()); } } } } return loc; } private ISourceLocation safeResolve(ISourceLocation loc) { ISourceLocation resolved = null; try { resolved = physicalLocation(loc); } catch (Throwable e) { // robustness } return resolved != null ? resolved : loc; } private void registerInput(ISourceLocationInput resolver) { synchronized (inputResolvers) { inputResolvers.put(resolver.scheme(), resolver); } } private void registerOutput(ISourceLocationOutput resolver) { synchronized (outputResolvers) { outputResolvers.put(resolver.scheme(), resolver); } } public void registerLogical(ILogicalSourceLocationResolver resolver) { synchronized (logicalResolvers) { Map<String, ILogicalSourceLocationResolver> map = logicalResolvers.get(resolver.scheme()); if (map == null) { map = new HashMap<>(); logicalResolvers.put(resolver.scheme(), map); } map.put(resolver.authority(), resolver); } } public void unregisterLogical(String scheme, String auth) { synchronized (logicalResolvers) { Map<String, ILogicalSourceLocationResolver> map = logicalResolvers.get(scheme); if (map != null) { map.remove(auth); } } } private static final Pattern splitScheme = Pattern.compile("^([^\\+]*)\\+"); private ISourceLocationInput getInputResolver(String scheme) { synchronized (inputResolvers) { ISourceLocationInput result = inputResolvers.get(scheme); if (result == null) { Matcher m = splitScheme.matcher(scheme); if (m.find()) { String subScheme = m.group(1); return inputResolvers.get(subScheme); } } return result; } } private ISourceLocationOutput getOutputResolver(String scheme) { synchronized (outputResolvers) { ISourceLocationOutput result = outputResolvers.get(scheme); if (result == null) { Matcher m = splitScheme.matcher(scheme); if (m.find()) { String subScheme = m.group(1); return outputResolvers.get(subScheme); } } 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()); if (resolver == null) { ISourceLocationOutput resolverOther = getOutputResolver(uri.getScheme()); if (resolverOther == null) { return false; } return resolverOther.supportsHost(); } return resolver.supportsHost(); } public boolean exists(ISourceLocation uri) { if (logicalResolvers.containsKey(uri.getScheme())) { uri = physicalLocation(uri); if (uri == null) { return false; } } ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { // TODO: should this not throw an exception? return false; } return resolver.exists(uri); } public boolean isDirectory(ISourceLocation uri) { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { return false; } return resolver.isDirectory(uri); } public void mkDirectory(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationOutput resolver = getOutputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } mkParentDir(uri); resolver.mkDirectory(uri); } public void remove(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationOutput out = getOutputResolver(uri.getScheme()); if (out == null) { throw new UnsupportedSchemeException(uri.getScheme()); } if (isDirectory(uri)) { for (ISourceLocation element : list(uri)) { remove(element); } } out.remove(uri); } public boolean isFile(ISourceLocation uri) { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { return false; } return resolver.isFile(uri); } public long lastModified(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } return resolver.lastModified(uri); } public String[] listEntries(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } return resolver.list(uri); } public ISourceLocation[] list(ISourceLocation uri) throws IOException { String[] entries = listEntries(uri); if (entries == null) { return new ISourceLocation[0]; } ISourceLocation[] list = new ISourceLocation[entries.length]; int i = 0; for (String entry : entries) { list[i++] = URIUtil.getChildLocation(uri, entry); } return list; } public Reader getCharacterReader(ISourceLocation uri) throws IOException { return getCharacterReader(uri, getCharset(uri)); } public Reader getCharacterReader(ISourceLocation uri, String encoding) throws IOException { return getCharacterReader(uri, Charset.forName(encoding)); } public Reader getCharacterReader(ISourceLocation uri, Charset encoding) throws IOException { uri = safeResolve(uri); Reader res = new UnicodeInputStreamReader(getInputStream(uri), encoding); if (uri.hasOffsetLength()) { return new UnicodeOffsetLengthReader(res, uri.getOffset(), uri.getLength()); } else { return res; } } public InputStream getInputStream(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } return makeBuffered(resolver.getInputStream(uri)); } public Charset getCharset(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocationInput resolver = getInputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } return resolver.getCharset(uri); } public OutputStream getOutputStream(ISourceLocation uri, boolean append) throws IOException { uri = safeResolve(uri); ISourceLocationOutput resolver = getOutputResolver(uri.getScheme()); if (resolver == null) { throw new UnsupportedSchemeException(uri.getScheme()); } if (uri.getPath() != null && uri.getPath().startsWith("/..")) { throw new IllegalArgumentException("Can not navigate beyond the root of a URI: " + uri); } mkParentDir(uri); return makeBuffered(resolver.getOutputStream(uri, append)); } private void mkParentDir(ISourceLocation uri) throws IOException { uri = safeResolve(uri); ISourceLocation parentURI = URIUtil.getParentLocation(uri); if (parentURI != null && !parentURI.equals(uri) && !exists(parentURI)) { mkDirectory(parentURI); } } }