Skip to content

Commit

Permalink
added one single in-memory file system resolver to replace other more…
Browse files Browse the repository at this point in the history
… ad-hoc classes later
  • Loading branch information
jurgenvinju committed Sep 12, 2023
1 parent d55cb74 commit 309e333
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
219 changes: 219 additions & 0 deletions src/org/rascalmpl/uri/libraries/MemoryResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*******************************************************************************
* Copyright (c) 2009-2015 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:
* * Paul Klint - [email protected] - CWI
* * Davy Landman [email protected] - CWI
*******************************************************************************/

package org.rascalmpl.uri.libraries;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;

import org.rascalmpl.uri.FileTree;
import org.rascalmpl.uri.ISourceLocationInputOutput;

import io.usethesource.vallang.ISourceLocation;

/**
* This resolver implements the scheme "memory" scheme, which implements
* in-memory file systems for use during testing.
*
* The in-memory "file system" that we use to implement this feature guarantees
* that "lastModified" is monotone increasing, i.e. after a write to a file lastModified
* is ALWAYS larger than for the previous version of the same file.
* When files are written at high speeed (e.g. with 10-30 ms intervals), this property is,
* unfortunately, not guaranteed on all operating systems, in all situations
* So if you are writing temporary files very frequently and use lastModified to mark the fields
* as dirty, use an instance of this resolver to guarantee the dirty marking.
*
* The locations should not use the autority field, as that is ignored.
*
* BE AWARE that the information in this in-memory file system is volatile and does not survive:
* - program execution
* - replacement by another in-memory filesystem for the same scheme
*
* ALSO BE AWARE that during the existence of the JVM files stored in this scheme
* are never garbage collected. Use `rm` to clean up after yourselves.
*
* The resolver supports the following trick to simulate readonly files, used for testing purposes:
* If the scheme of a URI ends with `+readonly` the writing part of the resolved throws exceptions.
*/

public class MemoryResolver implements ISourceLocationInputOutput {

private final class InMemoryFileTree extends FileTree {
public ConcurrentNavigableMap<String, FSEntry> getFileSystem() {
return fs;
}
}

private final ConcurrentMap<String, InMemoryFileTree> fileSystems = new ConcurrentHashMap<>();

public MemoryResolver() {

}

@Override
public String scheme() {
return "memory";
}

private static final class File extends FileTree.FSEntry {
byte[] contents;
public File() {
super(System.currentTimeMillis());
contents = null;
}
public void newContent(byte[] byteArray) {
long newTimestamp = System.currentTimeMillis();
if (newTimestamp <= lastModified) {
newTimestamp = lastModified + 1;
}
lastModified = newTimestamp;
contents = byteArray;
}

public String toString(){
return String.valueOf(lastModified) ;//+ ":\n" +new String(contents, StandardCharsets.UTF_8);
}
}

private InMemoryFileTree getFS(String authority) {
return fileSystems.computeIfAbsent(authority, a -> new InMemoryFileTree());
}

private File get(ISourceLocation uri) {
return (File) getFS(uri.getAuthority()).getFileSystem().get(uri.getPath());
}

@Override
public InputStream getInputStream(ISourceLocation uri)
throws IOException {
File file = get(uri);
if (file == null) {
throw new FileNotFoundException();
}
return new ByteArrayInputStream(file.contents);
}

@Override
public OutputStream getOutputStream(ISourceLocation uri, boolean append)
throws IOException {
if (uri.getScheme().endsWith("+readonly")) {
throw new AccessDeniedException(uri.toString());
}

ByteArrayOutputStream result = new ByteArrayOutputStream() {
@Override
public void close() throws IOException {
File file = get(uri);
byte[] content = this.toByteArray();
if (file == null) {
file = new File();
getFS(uri.getAuthority()).getFileSystem().put(uri.getPath(), file);
}
file.newContent(content);
super.close();
}
};
if (append) {
File file = get(uri);
if (file == null) {
throw new FileNotFoundException();
}
// load data to write, makes the closing code simpler
result.write(file.contents);
}
return result;
}

@Override
public long lastModified(ISourceLocation uri) throws IOException {
File file = get(uri);
if (file == null) {
throw new FileNotFoundException();
}
return file.lastModified;
}

@Override
public void setLastModified(ISourceLocation uri, long timestamp) throws IOException {
File file = get(uri);

if (file == null) {
throw new FileNotFoundException(uri.toString());
}

file.lastModified = timestamp;
}

@Override
public Charset getCharset(ISourceLocation uri) throws IOException {
return null;
}

@Override
public boolean exists(ISourceLocation uri) {
return getFS(uri.getAuthority()).exists(uri.getPath());
}

@Override
public boolean isDirectory(ISourceLocation uri) {
return getFS(uri.getAuthority()).isDirectory(uri.getPath());
}

@Override
public boolean isFile(ISourceLocation uri) {
return getFS(uri.getAuthority()).isFile(uri.getPath());
}

@Override
public String[] list(ISourceLocation uri) throws IOException {
return getFS(uri.getAuthority()).directChildren(uri.getPath());
}

@Override
public boolean supportsHost() {
return false;
}

@Override
public void mkDirectory(ISourceLocation uri) throws IOException {
if (uri.getScheme().endsWith("+readonly")) {
throw new AccessDeniedException(uri.toString());
}
}

@Override
public void remove(ISourceLocation uri) throws IOException {
if (uri.getScheme().endsWith("+readonly")) {
throw new AccessDeniedException(uri.toString());
}

var ft = getFS(uri.getAuthority()).getFileSystem();

// remove the specific file
ft.remove(uri.getPath());

// clean up the entire map if this was the last entry in the filesystem
if (ft.isEmpty()) {
fileSystems.remove(uri.getAuthority());
}
}
}
1 change: 1 addition & 0 deletions src/org/rascalmpl/uri/resolvers.config
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ org.rascalmpl.uri.file.HomeURIResolver
org.rascalmpl.uri.file.CWDURIResolver
org.rascalmpl.uri.file.SystemPathURIResolver
org.rascalmpl.uri.libraries.TestDataURIResolver
org.rascalmpl.uri.libraries.MemoryResolver
org.rascalmpl.uri.libraries.BenchmarkURIResolver
org.rascalmpl.uri.libraries.RascalLibraryURIResolver
org.rascalmpl.shell.ManifestURIResolver

0 comments on commit 309e333

Please sign in to comment.