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 }