Java tutorial
/* * This file is part of Spout. * * Copyright (c) 2011 Spout LLC <http://www.spout.org/> * Spout is licensed under the Spout License Version 1. * * Spout is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * In addition, 180 days after any changes are published, you can use the * software, incorporating those changes, under the terms of the MIT license, * as described in the Spout License Version 1. * * Spout is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License, * the MIT license and the Spout License Version 1 along with this program. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public * License and see <http://spout.in/licensev1> for the full license, including * the MIT license. */ package org.spout.engine.filesystem; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import java.util.logging.Level; import org.apache.commons.io.FileUtils; import org.spout.api.Spout; import org.spout.api.command.Command; import org.spout.api.command.CommandArguments; import org.spout.api.command.CommandSource; import org.spout.api.command.Executor; import org.spout.api.exception.CommandException; import org.spout.api.exception.SpoutRuntimeException; import org.spout.api.resource.FileSystem; import org.spout.api.resource.LoaderNotFoundException; import org.spout.api.resource.ResourceLoader; import org.spout.api.resource.ResourceNotFoundException; import org.spout.api.resource.ResourcePathResolver; import org.spout.engine.filesystem.path.FilePathResolver; import org.spout.engine.filesystem.path.JarFilePathResolver; import org.spout.engine.filesystem.path.ZipFilePathResolver; import org.spout.engine.filesystem.resource.loader.CommandBatchLoader; public abstract class CommonFileSystem implements FileSystem { public static final File PLUGINS_DIRECTORY = new File("plugins"); public static final File RESOURCES_DIRECTORY = new File("resources"); public static final File CACHE_DIRECTORY = new File("cache"); public static final File CONFIG_DIRECTORY = new File("config"); public static final File UPDATES_DIRECTORY = new File("updates"); public static final File DATA_DIRECTORY = new File("data"); public static final File WORLDS_DIRECTORY = new File("worlds"); protected final Set<ResourceLoader> loaders = new HashSet<>(); protected final Map<URI, Object> loadedResources = new HashMap<>(); protected final List<ResourcePathResolver> pathResolvers = new ArrayList<>(); protected final Map<String, URI> requestedInstallations = new HashMap<>(); protected boolean initialized; private void createDirs() { if (!PLUGINS_DIRECTORY.exists()) { PLUGINS_DIRECTORY.mkdirs(); } if (!RESOURCES_DIRECTORY.exists()) { RESOURCES_DIRECTORY.mkdirs(); } if (!CACHE_DIRECTORY.exists()) { CACHE_DIRECTORY.mkdirs(); } if (!CONFIG_DIRECTORY.exists()) { CONFIG_DIRECTORY.mkdirs(); } if (!UPDATES_DIRECTORY.exists()) { UPDATES_DIRECTORY.mkdirs(); } if (!DATA_DIRECTORY.exists()) { DATA_DIRECTORY.mkdirs(); } if (!WORLDS_DIRECTORY.exists()) { WORLDS_DIRECTORY.mkdirs(); } } @Override public void init() { registerLoader(new CommandBatchLoader()); createDirs(); pathResolvers.add(new FilePathResolver(CACHE_DIRECTORY.getPath())); pathResolvers.add(new ZipFilePathResolver(RESOURCES_DIRECTORY.getPath())); pathResolvers.add(new JarFilePathResolver()); initInstallations(); initialized = true; } private void initInstallations() { Spout.getCommandManager().getCommand("install").setPermission(INSTALLATION_PERMISSION) .setHelp("Replies to an installation request.").setUsage("<list|allow|deny> [plugin|all]") .setExecutor(new Executor() { @Override public void execute(CommandSource source, Command command, CommandArguments args) throws CommandException { // list the requested installations String arg = args.popString("action"); if (arg.equalsIgnoreCase("list")) { source.sendMessage("Listing pending installations..."); for (Map.Entry<String, URI> e : requestedInstallations.entrySet()) { source.sendMessage(e.getKey() + " from " + e.getValue()); } return; } // install all pending installations String plugin = args.popString("plugin"); if (plugin.equalsIgnoreCase("all")) { for (String p : requestedInstallations.keySet()) { allowInstallation(source, p); } return; } // specified plugin is not pending if (!requestedInstallations.containsKey(plugin)) { throw new CommandException("There is no install pending for that plugin."); } // allow or disallow the specified plugin if (arg.equalsIgnoreCase("allow")) { allowInstallation(source, plugin); return; } else if (arg.equalsIgnoreCase("deny")) { denyInstallation(source, plugin); return; } throw new CommandException("Unknown argument: " + arg); } }); } private void loadFallback(ResourceLoader loader) { String fallback = loader.getFallback(); if (fallback != null) { try { loadResource(fallback); } catch (LoaderNotFoundException e) { throw new IllegalArgumentException("Specified fallback has no associated loader", e); } catch (ResourceNotFoundException e) { throw new IllegalArgumentException("Specified fallback does not exist.", e); } catch (IOException e) { throw new IllegalStateException("Error while loading fallback resource", e); } } } @Override public void postStartup() { // load fallbacks for (ResourceLoader loader : loaders) { loadFallback(loader); } } @Override public Set<ResourceLoader> getLoaders() { return Collections.unmodifiableSet(loaders); } @Override public ResourceLoader getLoader(String scheme) { for (ResourceLoader loader : loaders) { if (loader.getScheme().equalsIgnoreCase(scheme)) { return loader; } } return null; } @Override public void registerLoader(ResourceLoader loader) { // load the fallback loaders.add(loader); if (initialized) { loadFallback(loader); } } @Override public InputStream getResourceStream(URI path) throws ResourceNotFoundException { // Find the correct search path ResourcePathResolver searchPath = getPathResolver(path); if (searchPath != null) { return searchPath.getStream(path); } // No path found? Open our jar and grab the fallback 'file' scheme Spout.getEngine().getLogger().warning("Tried to load " + path + " it isn't found! Using system fallback"); String scheme = path.getScheme(); if (!scheme.equals("file")) { throw new ResourceNotFoundException(path.toString()); } return CommonFileSystem.class.getResourceAsStream("/fallbacks/" + path.getPath()); } @Override public InputStream getResourceStream(String path) { try { return getResourceStream(new URI(path)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Tried to get a Resource Stream URI, but" + path + " Isn't a URI", e); } catch (ResourceNotFoundException e) { throw new IllegalArgumentException("Resource not found at path '" + path + "':", e); } } private ResourcePathResolver getPathResolver(URI uri) { for (ResourcePathResolver resolver : pathResolvers) { if (resolver.existsInPath(uri)) { return resolver; } } return null; } @Override public void loadResource(URI uri) throws LoaderNotFoundException, ResourceNotFoundException, IOException { // find the loader // this needs to be thrown first, so we can use a fallback loader and know it exists String scheme = uri.getScheme(); ResourceLoader loader = getLoader(scheme); if (loader == null) { throw new LoaderNotFoundException(scheme); } // grab the input stream ResourcePathResolver resolver = getPathResolver(uri); if (resolver == null) { throw new ResourceNotFoundException(uri.toString()); } try (InputStream in = new BufferedInputStream(resolver.getStream(uri))) { Object resource = loader.load(in); if (resource == null) { throw new IllegalStateException("Loader for scheme '" + scheme + "' returned a null resource."); } loadedResources.put(uri, resource); } } @Override public void loadResource(String uri) throws LoaderNotFoundException, ResourceNotFoundException, IOException { try { loadResource(new URI(uri)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Specified URI is not valid."); } } @SuppressWarnings("unchecked") private <R> R tryCast(Object obj, String scheme) { try { return (R) obj; } catch (ClassCastException e) { throw new IllegalArgumentException( "Specified scheme '" + scheme + "' does not point to the inferred resource type."); } } @Override public <R> R getResource(URI uri) { if (loadedResources.containsKey(uri)) { // already loaded return tryCast(loadedResources.get(uri), uri.getScheme()); } try { // not loaded yet loadResource(uri); } catch (LoaderNotFoundException e) { // scheme has not loader throw new IllegalArgumentException("No loader found for scheme " + uri.getScheme(), e); } catch (IOException e) { // error closing the stream throw new IllegalArgumentException( "An exception occurred when loading the resource at " + uri.toString(), e); } catch (ResourceNotFoundException e) { // not found in path, try to load fallback resource Spout.getLogger().warning("No resource found at " + uri.toString() + ", loading fallback..."); String fallback = getLoader(uri.getScheme()).getFallback(); // assumption: loader is never null here if (fallback == null) { Spout.getLogger() .warning("No resource found at " + uri.toString() + " and has no fallback resource."); return null; } try { return tryCast(loadedResources.get(new URI(fallback)), uri.getScheme()); } catch (URISyntaxException se) { throw new IllegalStateException("Fallback name for scheme " + uri.getScheme() + " is invalid.", e); } } return tryCast(loadedResources.get(uri), uri.getScheme()); } @Override public <R> R getResource(String uri) { try { return getResource(new URI(uri)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Specified URI '" + uri + "' is invalid.", e); } } @Override public <R> List<R> getResources(URI uri) { ResourcePathResolver resolver = getPathResolver(uri); if (resolver == null) { throw new IllegalArgumentException("Could not resolve path '" + uri.toString() + "'"); } String[] files = resolver.list(uri); List<R> resources = new ArrayList<>(); for (String file : files) { resources.add((R) getResource(uri.getScheme() + "://" + uri.getHost() + uri.getPath() + file)); } return resources; } @Override public <R> List<R> getResources(String uri) { try { return getResources(new URI(uri)); } catch (URISyntaxException e) { throw new IllegalArgumentException("Specified uri is invalid", e); } } @Override public List<ResourcePathResolver> getPathResolvers() { return Collections.unmodifiableList(pathResolvers); } @Override public void addPathResolver(ResourcePathResolver pathResolver) { pathResolvers.add(pathResolver); } @Override public void removePathResolver(ResourcePathResolver pathResolver) { pathResolvers.remove(pathResolver); } private void allowInstallation(final CommandSource source, final String plugin) { Spout.getScheduler().scheduleAsyncTask(Spout.getEngine(), new Runnable() { @Override public void run() { synchronized (requestedInstallations) { JarFile jar = null; InputStream in = null; try { // obtain plugin stream URI uri = requestedInstallations.get(plugin); in = new BufferedInputStream(uri.toURL().openStream()); String path = uri.toString(); File file = new File(UPDATES_DIRECTORY, path.substring(path.lastIndexOf('/') + 1)); // copy to updates source.sendMessage("Downloading " + plugin + " to the updates folder..."); FileUtils.copyInputStreamToFile(in, file); source.sendMessage("Done."); // check the validity of plugin jar = new JarFile(file); if (jar.getJarEntry("properties.yml") == null && jar.getJarEntry("plugin.yml") == null) { source.sendMessage( "The downloaded file has no valid plugin description file, marking file to be deleted."); if (!file.delete()) { file.deleteOnExit(); } return; } source.sendMessage(plugin + " has been successfully downloaded to the updates folder, it will be installed on next run."); } catch (MalformedURLException e) { throw new SpoutRuntimeException("The plugin's URL is invalid", e); } catch (IOException e) { throw new SpoutRuntimeException("Error downloading the plugin", e); } finally { // close the jar try { if (jar != null) { jar.close(); } } catch (IOException e) { Spout.getLogger().log(Level.WARNING, "Error closing JAR file", e); } // close the input stream try { if (in != null) { in.close(); } } catch (IOException e) { Spout.getLogger().log(Level.WARNING, "Error closing plugin stream", e); } } requestedInstallations.remove(plugin); } } }); } private void denyInstallation(CommandSource source, String plugin) { source.sendMessage("Installation of " + plugin + " cancelled."); requestedInstallations.remove(plugin); } @Override public void requestPluginInstall(String name, URI uri) { // TODO: Restrict to Spout Hub only? if (name == null) { throw new IllegalArgumentException("Plugin name cannot be null"); } if (uri == null) { throw new IllegalArgumentException("URI cannot be null"); } requestedInstallations.put(name, uri); } }