001 /* 002 * Created on Dec 21, 2009 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 005 * the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 011 * specific language governing permissions and limitations under the License. 012 * 013 * Copyright @2009-2010 the original author or authors. 014 */ 015 package org.fest.swing.security; 016 017 import static org.fest.util.Strings.concat; 018 019 import java.security.Permission; 020 021 import org.fest.util.StackTraces; 022 import org.fest.util.VisibleForTesting; 023 024 /** 025 * Understands a <code>{@link SecurityManager}</code> that does not allow an application under test to terminate the 026 * current JVM. Adapted from Abbot's <code>NoExitSecurityManager</code>. 027 * 028 * @author Alex Ruiz 029 */ 030 public class NoExitSecurityManager extends SecurityManager { 031 032 private static final ExitCallHook NULL_HOOK = new ExitCallHook() { 033 public void exitCalled(int status) {} 034 }; 035 private final ExitCallHook hook; 036 private final StackTraces stackTraces; 037 038 /** 039 * Creates a new </code>{@link NoExitSecurityManager}</code>. 040 */ 041 public NoExitSecurityManager() { 042 this(NULL_HOOK); 043 } 044 045 /** 046 * Creates a new </code>{@link NoExitSecurityManager}</code>. 047 * @param hook notified when an application tries to terminate the current JVM. 048 * @throws NullPointerException if the given hook is <code>null</code>. 049 */ 050 public NoExitSecurityManager(ExitCallHook hook) { 051 this(hook, StackTraces.instance()); 052 } 053 054 @VisibleForTesting 055 NoExitSecurityManager(ExitCallHook hook, StackTraces stackTraces) { 056 if (hook == null) throw new NullPointerException( 057 concat("The given ", ExitCallHook.class.getSimpleName(), " should not be null")); 058 this.hook = hook; 059 this.stackTraces = stackTraces; 060 } 061 062 /** 063 * Allows everything. 064 * @param permission the specified permission. 065 * @param context a system-dependent security context. 066 */ 067 @Override public void checkPermission(Permission permission, Object context) {} 068 069 /** 070 * Allows everything. 071 * @param permission the specified permission. 072 */ 073 @Override public void checkPermission(Permission permission) {} 074 075 /** 076 * Throws an <code>{@link ExitException}</code> if an application tries to terminate the current JVM (through 077 * <code>{@link Runtime#exit(int)}</code> or <code>{@link Runtime#halt(int)}</code>.) 078 * @param status the exit status. 079 * @throws ExitException if an application tries to terminate the current JVM. 080 */ 081 @Override public void checkExit(int status) { 082 if (exitInvoked()) { 083 hook.exitCalled(status); 084 throw new ExitException(concat("Application tried to terminate current JVM with status ", status)); 085 } 086 } 087 088 /** 089 * Indicates whether "exit" has been invoked through a call of <code>{@link Runtime#exit(int)}</code> or 090 * <code>{@link Runtime#halt(int)}</code>. 091 * @return <code>true</code> if an exit has been invoked through a call of <code>Runtime.exit</code> or 092 * <code>Runtime.halt</code>; <code>false</code> otherwise. 093 */ 094 private boolean exitInvoked() { 095 for (StackTraceElement e : stackTraces.stackTraceInCurrentThread()) 096 if (exitInvoked(e)) return true; 097 return false; 098 } 099 100 private boolean exitInvoked(StackTraceElement e) { 101 if (!Runtime.class.getName().equals(e.getClassName())) return false; 102 final String method = e.getMethodName(); 103 return "exit".equals(method) || "halt".equals(method); 104 } 105 }