/* * Copyright 2015 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.module.filesystem; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import org.terasology.module.ModuleEnvironment; import org.terasology.util.Varargs; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.WatchService; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * A file system providing access to the contents of a Module environment. * <p> * A ModuleFileSystem has a root for each module ('/moduleName'), and separates each directory and file with '/'. * Modification and write operations are not supported. WatchService is supported though, for detecting external changes - this is only * supported to changes happening on the default filesystem (so directories, not in archives). * * @author Immortius */ class ModuleFileSystem extends FileSystem { private static final Set<String> SUPPORTED_FILE_ATTRIBUTE_VIEWS = ImmutableSet.of("basic"); private final ModuleFileSystemProvider provider; private final ModuleEnvironment environment; private Map<Path, FileSystem> openedFileSystems = Maps.newConcurrentMap(); private boolean open = true; ModuleFileSystem(ModuleFileSystemProvider provider, ModuleEnvironment environment) { this.provider = provider; this.environment = environment; } public ModuleEnvironment getEnvironment() { return environment; } @Override public FileSystemProvider provider() { return provider; } @Override public void close() throws IOException { open = false; provider.removeFileSystem(this); for (FileSystem openedFileSystem : openedFileSystems.values()) { openedFileSystem.close(); } } @Override public boolean isOpen() { return open; } @Override public boolean isReadOnly() { return true; } @Override public String getSeparator() { return ModuleFileSystemProvider.SEPARATOR; } @Override public Iterable<Path> getRootDirectories() { return environment.getModulesOrderedByDependencies().stream() .<Path>map(module -> getPath("/", module.getId().toString())).collect(Collectors.toList()); } @Override public Iterable<FileStore> getFileStores() { throw new UnsupportedOperationException(); } @Override public Set<String> supportedFileAttributeViews() { return SUPPORTED_FILE_ATTRIBUTE_VIEWS; } @Override public ModulePath getPath(String first, String... more) { List<String> parts = Varargs.combineToList(first, more); StringBuilder builder = new StringBuilder(); for (String part : parts) { String trimmedPart = part.trim(); if (!trimmedPart.isEmpty()) { if (trimmedPart.charAt(0) == ModuleFileSystemProvider.SEPARATOR.charAt(0)) { builder.append(ModuleFileSystemProvider.SEPARATOR); } break; } } for (String part : parts) { for (String subPart : part.split(ModuleFileSystemProvider.SEPARATOR, 0)) { if (!subPart.isEmpty()) { if (builder.length() > 0 && builder.charAt(builder.length() - 1) != ModuleFileSystemProvider.SEPARATOR.charAt(0)) { builder.append(ModuleFileSystemProvider.SEPARATOR); } builder.append(subPart); } } } return new ModulePath(builder.toString(), this); } @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { String[] parts = syntaxAndPattern.split(":", 2); if (parts.length == 2) { switch (parts[0]) { case "regex": { final Pattern pattern = Pattern.compile(parts[1]); return path -> pattern.matcher(path.toString()).matches(); } case "glob": { final Pattern pattern = Pattern.compile(GlobSupport.globToRegex(parts[1])); return path -> pattern.matcher(path.toString()).matches(); } default: throw new UnsupportedOperationException("Syntax '" + parts[0] + "' not recognized"); } } else { throw new IllegalArgumentException("Invalid format: '" + syntaxAndPattern + "'"); } } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { throw new UnsupportedOperationException(); } @Override public WatchService newWatchService() throws IOException { return new ModuleWatchService(this); } FileSystem getRealFileSystem(Path moduleLocation) throws IOException { FileSystem containedFileSystem = openedFileSystems.get(moduleLocation); if (containedFileSystem == null) { containedFileSystem = FileSystems.newFileSystem(moduleLocation, null); openedFileSystems.put(moduleLocation, containedFileSystem); } return containedFileSystem; } }