--- old/src/share/classes/org/openjdk/jigsaw/Files.java Thu Apr 26 16:20:18 2012 +++ new/src/share/classes/org/openjdk/jigsaw/Files.java Thu Apr 26 16:20:18 2012 @@ -26,10 +26,18 @@ package org.openjdk.jigsaw; import java.io.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.FileVisitor; +import java.nio.file.FileVisitResult; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.jar.*; import java.util.zip.*; import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.lang.String.format; public final class Files { @@ -109,6 +117,55 @@ delete(dst); } + static List deleteAllUnchecked(Path dir) { + final List excs = new ArrayList<>(); + try { + java.nio.file.Files.walkFileTree(dir, new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + try { + java.nio.file.Files.delete(file); + } catch (IOException x) { + excs.add(new IOException(format("Unable to delete %s: %s\n", file, x))); + } + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + try { + java.nio.file.Files.delete(dir); + } catch (IOException x) { + excs.add(new IOException(format("Unable to delete %s: %s\n", dir, x))); + } + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + excs.add(new IOException(format("Unable to visit %s: %s\n", file, exc))); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException x) { + excs.add(x); + } + return excs; + } + + static IOException deleteUnchecked(Path file) + { + try { + java.nio.file.Files.delete(file); + } catch (IOException exc) { + return new IOException(format("Unable to delete %s: %s\n", file, exc)); + } + return null; + } + + private static void copy(File src, File dst) throws IOException { --- old/src/share/classes/org/openjdk/jigsaw/Library.java Thu Apr 26 16:20:19 2012 +++ new/src/share/classes/org/openjdk/jigsaw/Library.java Thu Apr 26 16:20:19 2012 @@ -78,7 +78,7 @@ public abstract int minorVersion(); public abstract Library parent(); - + /** *

Read the module-info class bytes for the module with the given * identifier, from this library only.

@@ -281,6 +281,43 @@ throws ConfigurationException, IOException, SignatureException; /** + * Remove one or more modules from this library. + * + * @param mids + * The module identifiers + * + * @return A list of IOExceptions, if any, that were encountered when + * removing the actual content of the given modules + * + * @throws ConfigurationException + * If the configuration of any root modules in the library + * require any of the given modules + * + * @throws IOException + * If an I/O error occurs while accessing the module library + */ + public abstract List remove(List mids, boolean dry) + throws ConfigurationException, IOException; + + /** + * Forcibly remove one or more modules from this library. + * + *

No regard is given to configuration of any root modules in the + * library that may require any of the given modules.

+ * + * @param mids + * The module identifiers + * + * @return A list of IOExceptions, if any, that were encountered when + * removing the actual content of the given modules + * + * @throws IOException + * If an I/O error occurs while accessing the module library + */ + public abstract List removeForcibly(List mids) + throws IOException; + + /** * Find a resource within the given module in this library. * * @param mid @@ -333,7 +370,7 @@ public abstract RemoteRepositoryList repositoryList() throws IOException; /** - *

Read the CodeSigners for the module with the given identifier, from + *

Read the CodeSigners for the module with the given identifier, from * this library only.

* * @param mid --- old/src/share/classes/org/openjdk/jigsaw/ModuleFile.java Thu Apr 26 16:20:20 2012 +++ new/src/share/classes/org/openjdk/jigsaw/ModuleFile.java Thu Apr 26 16:20:20 2012 @@ -509,12 +509,13 @@ filesWriter.flush(); } - void remove() throws IOException { - ModuleFile.Reader.remove(destination); + List remove() { + return ModuleFile.Reader.remove(destination); } // Removes a module, given its module install directory - static void remove(File moduleDir) throws IOException { + static List remove(File moduleDir) { + List excs = new ArrayList<>(); // Firstly remove any files installed outside of the module dir File files = new File(moduleDir, "files"); if (files.exists()) { @@ -522,19 +523,25 @@ InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); BufferedReader in = new BufferedReader(isr)) { String filename; - while ((filename = in.readLine()) != null) - Files.delete(new File(moduleDir, - Files.platformSeparator(filename))); + while ((filename = in.readLine()) != null) { + IOException x = Files.deleteUnchecked(new File(moduleDir, + Files.platformSeparator(filename)).toPath()); + if (x != null) + excs.add(x); + } + } catch (IOException x) { + excs.add(x); } } - Files.deleteTree(moduleDir); + excs.addAll(Files.deleteAllUnchecked(moduleDir.toPath())); + return excs; } // Returns the absolute path of the given section type. private File getDirOfSection(SectionType type) { if (type == SectionType.NATIVE_LIBS) - return natlibs; + return natlibs; else if (type == SectionType.NATIVE_CMDS) return natcmds; else if (type == SectionType.CONFIG) --- old/src/share/classes/org/openjdk/jigsaw/SimpleLibrary.java Thu Apr 26 16:20:21 2012 +++ new/src/share/classes/org/openjdk/jigsaw/SimpleLibrary.java Thu Apr 26 16:20:21 2012 @@ -1472,6 +1472,105 @@ install(res, verifySignature, false); } + @Override + public List removeForcibly(List mids) + throws IOException + { + try { + return remove(mids, true, false); + } catch (ConfigurationException x) { + throw new Error("should not be thrown when forcibly removing", x); + } + } + + @Override + public List remove(List mids, boolean dry) + throws ConfigurationException, IOException + { + return remove(mids, false, dry); + } + + private List remove(List mids, boolean force, boolean dry) + throws ConfigurationException, IOException + { + boolean needRefresh = false; + List excs = new ArrayList<>(); + IOException ioe = null; + FileChannel fc = FileChannel.open(lockf.toPath(), WRITE); + try { + fc.lock(); + for (ModuleId mid : mids) { + if (moduleDictionary.findDeclaringModuleDir(mid) == null) + throw new IllegalArgumentException(mid + ": No such module"); + } + if (!force) + checkRootsRequire(mids); + if (dry) + return Collections.emptyList(); + + // The library may be altered after this point, so the modules + // dictionary needs to be refreshed + needRefresh = true; + excs.addAll(removeWhileLocked(mids)); + } catch (IOException x) { + ioe = x; + } finally { + if (needRefresh) { + try { + moduleDictionary.refresh(); + moduleDictionary.store(); + } catch (IOException x) { + if (ioe == null) + ioe = x; + else + ioe.addSuppressed(x); + } + } + fc.close(); + if (ioe != null) + throw ioe; + } + return excs; + } + + private void checkRootsRequire(List mids) + throws ConfigurationException, IOException + { + // ## We do not know if a root module in a child library depends on one + // ## of the 'to be removed' modules. We would break it's configuration. + + // check each root configuration for reference to a module in mids + for (ModuleId rootid : libraryRoots()) { + // skip any root modules being removed + if (mids.contains(rootid)) + continue; + + Configuration cf = readConfiguration(rootid); + for (Context cx : cf.contexts()) { + for (ModuleId mid : cx.modules()) { + if (mids.contains(mid)) + throw new ConfigurationException(mid + + ": being used by " + rootid); + } + } + } + } + + private List removeWhileLocked(List mids) { + List excs = new ArrayList<>(); + for (ModuleId mid : mids) { + File md = moduleDir(root, mid); + excs.addAll(ModuleFile.Reader.remove(md)); + File p = md.getParentFile(); + if (p.list().length == 0) { + IOException x = Files.deleteUnchecked(p.toPath()); + if (x != null) + excs.add(x); + } + } + return excs; + } + /** *

Pre-install one or more modules to an arbitrary destination * directory.

@@ -1542,10 +1641,22 @@ throws ConfigurationException, IOException { // ## mids not used yet + for (ModuleId mid : libraryRoots()) { + // ## We could be a lot more clever about this! + Configuration cf + = Configurator.configure(this, mid.toQuery()); + File md = moduleDictionary.findDeclaringModuleDir(mid); + new StoredConfiguration(md, cf).store(); + } + } + + private List libraryRoots() + throws IOException + { List roots = new ArrayList<>(); for (ModuleId mid : listLocalDeclaringModuleIds()) { - // each module can have multiple entry points - // only configure once for each module. + // each module can have multiple entry points, but + // only one configuration for each module. ModuleInfo mi = readModuleInfo(mid); for (ModuleView mv : mi.views()) { if (mv.mainClass() != null) { @@ -1554,14 +1665,7 @@ } } } - - for (ModuleId mid : roots) { - // ## We could be a lot more clever about this! - Configuration cf - = Configurator.configure(this, mid.toQuery()); - File md = moduleDictionary.findDeclaringModuleDir(mid); - new StoredConfiguration(md, cf).store(); - } + return roots; } public URI findLocalResource(ModuleId mid, String name) @@ -1798,12 +1902,13 @@ return; checkModuleDir(md); + // ## TODO: ModuleFile.Reader.remove(md); File parent = md.getParentFile(); if (parent.list().length == 0) parent.delete(); } - + void refresh() throws IOException { providingModuleIds = new LinkedHashMap<>(); moduleIdsForName = new LinkedHashMap<>(); --- old/src/share/classes/org/openjdk/jigsaw/cli/Librarian.java Thu Apr 26 16:20:22 2012 +++ new/src/share/classes/org/openjdk/jigsaw/cli/Librarian.java Thu Apr 26 16:20:22 2012 @@ -44,7 +44,7 @@ public class Librarian { - private static JigsawModuleSystem jms + private static final JigsawModuleSystem jms = JigsawModuleSystem.instance(); private static final File homeLibrary = Library.systemLibraryPath(); @@ -294,6 +294,39 @@ } } + static class Remove extends Command { + protected void go(SimpleLibrary lib) + throws Command.Exception + { + if (dry && force) + throw new Command.Exception("%s: specify only one of " + + "-n (--dry-run) or -f (--force)", + command); + List mids = new ArrayList(); + try { + while (hasArg()) + mids.add(jms.parseModuleId(takeArg())); + } catch (IllegalArgumentException x) { + throw new Command.Exception(x.getMessage()); + } + try { + boolean quiet = false; // ## Need -q + List excs; + if (force) + excs = lib.removeForcibly(mids); + else + excs = lib.remove(mids, dry); + + if (!quiet) { + for (IOException ioe : excs) + out.format("Warning: %s%n", ioe.getMessage()); + } + } catch (ConfigurationException | IOException x) { + throw new Command.Exception(x); + } + } + } + static class DumpConfig extends Command { protected void go(SimpleLibrary lib) throws Command.Exception @@ -466,7 +499,7 @@ try { // refresh the module directory lib.refresh(); - + // refresh the repository catalog RemoteRepositoryList rl = lib.repositoryList(); int n = 0; @@ -488,7 +521,7 @@ } } - private static Map>> commands + private static final Map>> commands = new HashMap<>(); static { @@ -507,6 +540,8 @@ commands.put("preinstall", PreInstall.class); commands.put("refresh", Refresh.class); commands.put("reindex", ReIndex.class); + commands.put("remove", Remove.class); + commands.put("rm", Remove.class); commands.put("repos", Repos.class); } @@ -522,7 +557,6 @@ private void usage() { out.format("%n"); out.format("usage: jmod add-repo [-i ] URL%n"); - out.format(" jmod extract ...%n"); out.format(" jmod config [ ...]%n"); out.format(" jmod create [-L ] [-P ]" + " [--natlib ] [--natcmd ] [--config ]%n"); @@ -529,6 +563,7 @@ out.format(" jmod del-repo URL%n"); out.format(" jmod dump-class %n"); out.format(" jmod dump-config %n"); + out.format(" jmod extract ...%n"); out.format(" jmod identify%n"); out.format(" jmod install [--noverify] [-n] ...%n"); out.format(" jmod install [--noverify] ...%n"); @@ -537,6 +572,7 @@ out.format(" jmod preinstall ...%n"); out.format(" jmod refresh [-f] [-n] [-v]%n"); out.format(" jmod reindex [ ...]%n"); + out.format(" jmod remove [-f] [-n] [ ...]%n"); out.format(" jmod repos [-v]%n"); out.format("%n"); try { --- /dev/null Thu Apr 26 16:20:23 2012 +++ new/test/org/openjdk/jigsaw/cli/jmod-rm.sh Thu Apr 26 16:20:23 2012 @@ -0,0 +1,187 @@ +#! /bin/sh + +# Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. +# +# This code 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 General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. + +# @test +# @summary jmod remove tests + +set -e + +BIN=${TESTJAVA:-../../../../../build}/bin +SRC=${TESTSRC:-.} +VMOPTS="${TESTVMOPTS} -esa -ea" +test=000jmod-rm + +mk() { + d=`dirname $1` + if [ ! -d $d ]; then mkdir -p $d; fi + cat - >$1 +} + +compare() { + if [ "$1" != "$2" ]; then + echo "FAIL: expected $1, got $2" + exit 1 + fi +} + +rm -rf $test/z.* + +mk $test/z.src/foo/module-info.java < /dev/null 2>&1`; then + echo "FAIL: foo@1.0 should not be removed as baz@1.0 depends on it" + exit 1 +fi +set -e +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib rm foo@1.0 baz@1.0 +compare "" `$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib list` +rm -rf $test/z.lib + +## Check views, foo.internal and dependent qux, should fail to remove foo +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib create +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib install $test/z.modules foo qux +## Expect next command to fail +set +e +if `$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib rm foo@1.0 > /dev/null 2>&1`; then + echo "FAIL: foo@1.0 should not be removed as qux@1.0 depends on it" + exit 1 +fi +set -e +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib rm qux@1.0 foo@1.0 +compare "" `$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib list` +rm -rf $test/z.lib + +## Check -f (--force), foo and dependent baz, should remove foo +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib create +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib install $test/z.modules foo baz +$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib rm -f foo@1.0 +compare baz@1.0 `$BIN/jmod ${TESTTOOLVMOPTS} -L $test/z.lib list` +rm -rf $test/z.lib