/* * Copyright (c) 2005, 2019, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package java.io; import java.util.*; import jdk.internal.access.SharedSecrets; /** * This class holds a set of filenames to be deleted on VM exit through a shutdown hook. * A set is used both to prevent double-insertion of the same file as well as offer * quick removal. */ class DeleteOnExitHook { private static class OrderCount { long order; int count; } private static long nextOrder; private static HashMap files = new HashMap<>(); static { // DeleteOnExitHook must be the last shutdown hook to be invoked. // Application shutdown hooks may add the first file to the // delete on exit list and cause the DeleteOnExitHook to be // registered during shutdown in progress. So set the // registerShutdownInProgress parameter to true. SharedSecrets.getJavaLangAccess() .registerShutdownHook(2 /* Shutdown hook invocation order */, true /* register even if shutdown in progress */, new Runnable() { public void run() { runHooks(); } } ); } private DeleteOnExitHook() {} static synchronized void deleteOnExit(String file) { if(files == null) { // DeleteOnExitHook is running. Too late to add a file throw new IllegalStateException("Shutdown in progress"); } OrderCount oc = files.computeIfAbsent(file, f -> new OrderCount()); oc.order = nextOrder++; oc.count++; } static synchronized void cancelDeleteOnExit(String file) { if(files != null) { files.compute(file, (f, oc) -> { if (oc == null) { throw new IllegalStateException( "Unbalanced cancelDeleteOnExit for missing deleteOnExit"); } return --oc.count <= 0 ? null : oc; }); } } static void runHooks() { HashMap theFiles; synchronized (DeleteOnExitHook.class) { theFiles = files; files = null; } theFiles .entrySet() .stream() // last registered -> first deleted .sorted(Comparator.comparing(e -> -e.getValue().order)) .map(e -> new File(e.getKey())) .forEach(File::delete); } }