/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans;

import java.awt.AWTPermission;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LoggingPermission;
import org.openide.util.Lookup;
import org.openide.util.WeakSet;

/** NetBeans security manager implementation.
* @author Ales Novak, Jesse Glick
*/
public class TopSecurityManager extends SecurityManager {
    private static final boolean check = !Boolean.getBoolean("netbeans.security.nocheck"); // NOI18N
    private static final Logger LOG = Logger.getLogger(TopSecurityManager.class.getName());

    private Permission allPermission;
    
    /* JVMPI sometimes deadlocks sync getForeignClassLoader
        and Class.forName
    */
    private static final Class<?> classLoaderClass = ClassLoader.class;
    private static final Class<?> URLClass = URL.class;
    private static final Class<?> runtimePermissionClass = RuntimePermission.class;
    private static final Class<?> accessControllerClass = AccessController.class;
    private static final Class<?> awtPermissionClass = AWTPermission.class;
    private static SecurityManager fsSecManager;

    private static final List<SecurityManager> delegates = new ArrayList<SecurityManager>();
    /** Register a delegate security manager that can handle some checks for us.
     * Currently only checkExit and checkTopLevelWindow are supported.
     * @param sm the delegate to register
     * @throws SecurityException without RuntimePermission "TopSecurityManager.register"
     */
    public static void register(SecurityManager sm) throws SecurityException {
/*        if (check) {
            try {
                AccessController.checkPermission(new RuntimePermission("TopSecurityManager.register")); // NOI18N
            } catch (SecurityException se) {
                // Something is probably wrong; debug it better.
                ProtectionDomain pd = sm.getClass().getProtectionDomain();
                CodeSource cs = pd.getCodeSource();
                System.err.println("Code source of attempted secman: " + (cs != null ? cs.getLocation().toExternalForm() : "<none>")); // NOI18N
                System.err.println("Its permissions: " + pd); // NOI18N
                throw se;
            }
        }
*/
        synchronized (delegates) {
            if (delegates.contains(sm)) throw new SecurityException();
            delegates.add(sm);
            if (fsSecManager == null) {
                for (Lookup.Item<SecurityManager> item : Lookup.getDefault().lookupResult(SecurityManager.class).allItems()) {
                    if (item != null && "org.netbeans.modules.masterfs.filebasedfs.utils.FileChangedManager".equals(item.getId())) {//NOI18N
                        fsSecManager = item.getInstance();
                        break;
                    }
                }
                assert fsSecManager != null;
            }            
        }
    }
    /** Unregister a delegate security manager.
     * @param sm the delegate to unregister
     * @throws SecurityException without RuntimePermission "TopSecurityManager.unregister"
     */
    public static void unregister(SecurityManager sm) throws SecurityException {
/*        if (check) {
            AccessController.checkPermission(new RuntimePermission("TopSecurityManager.unregister")); // NOI18N
        }
*/
        synchronized (delegates) {
            if (!delegates.contains(sm)) throw new SecurityException();
            delegates.remove(sm);
        }
    }

    /**
    * constructs new TopSecurityManager
    */
    public TopSecurityManager () {
        allPermission = new AllPermission();
    }

    public @Override void checkExit(int status) throws SecurityException {
        if (! check) {
            return;
        }
        
        synchronized (delegates) {
            Iterator<SecurityManager> it = delegates.iterator();
            while (it.hasNext()) {
                it.next().checkExit(status);
            }
        }
        
        PrivilegedCheck.checkExit(status, this);
    }

    SecurityManager getSecurityManager() {
        if (fsSecManager == null) {
            synchronized (delegates) {
                return fsSecManager;
            }        
        }
        return fsSecManager;
    }
    
    private void notifyDelete(String file) {
        SecurityManager s = getSecurityManager();
        if (s != null) {
            s.checkDelete(file);
        }
    }

    private void notifyRead(String file) {
        SecurityManager s = getSecurityManager();
        if (s != null) {
            s.checkRead(file);
        }
    }

    private void notifyWrite(String file) {
        SecurityManager s = getSecurityManager();
        if (s != null) {
            s.checkWrite(file);
        }
    }
    
    static boolean officialExit = false;
    static Class<?>[] getStack() {
        SecurityManager s = System.getSecurityManager();
        TopSecurityManager t;
        if (s instanceof TopSecurityManager) {
            t = (TopSecurityManager)s;
        } else {
            t = new TopSecurityManager();
        }
        return t.getClassContext();
    }
    
    /** Can be called from core classes to exit the system.
     * Direct calls to System.exit will not be honored, for safety.
     * @param status the status code to exit with
     * @see "#20751"
     */
    public static void exit(int status) {
        if (officialExit) {
            return; // already inside a shutdown hook
        }
        officialExit = true;
        System.exit(status);
    }

    final void checkExitImpl(int status, AccessControlContext acc) throws SecurityException {             
        if (!officialExit) {
            throw new ExitSecurityException("Illegal attempt to exit early"); // NOI18N
        }

        super.checkExit(status);
    }

    @SuppressWarnings("deprecation")
    public boolean checkTopLevelWindow(Object window) {
        return checkTopLevelWindow(new AWTPermission("showWindowWithoutWarningBanner"), window); // NOI18N
    }

    private boolean checkTopLevelWindow(Permission windowPermission, Object window) {
        synchronized (delegates) {
            for (SecurityManager sm : delegates) {
                sm.checkPermission(windowPermission, window);
            }
        }
        return true;
    }

    /* XXX probably unnecessary:
    // Hack against permissions of Launcher$AppLoader.
    public void checkPackageAccess(String pckg) {
        if (pckg == null) return;
        if (pckg.startsWith("sun.")) { // NOI18N
            if (inClazz("sun.misc.Launcher") || inClazz("java.lang.Class")) { // NOI18N
                return;
            }
        }
        super.checkPackageAccess(pckg);
    }

    private boolean inClazz(String s) {
        Class[] classes = getClassContext();
        int i = 0;
        for (; (i < classes.length) && (classes[i] == TopSecurityManager.class); i++);
        if (i == classes.length) {
            return false;
        }
        return classes[i].getName().startsWith(s);
    }
     */

    /** Performance - all props accessible */
    public @Override final void checkPropertyAccess(String x) {
        if ("netbeans.debug.exceptions".equals(x)) { // NOI18N
            // Get rid of this old system property.
            for (Class<?> c : getClassContext()) {
                if (c != TopSecurityManager.class &&
                        c != System.class &&
                        c != Boolean.class) {
                    String n = c.getName();
                    synchronized (warnedClassesNDE) {
                        if (warnedClassesNDE.add(n)) {
                            LOG.log(Level.WARNING, "use of system property netbeans.debug.exceptions has been obsoleted in favor of java.util.logging.Logger at {0}", findCallStackLine(n));
                        }
                    }
                    break;
                }
            }
        }
        if ("netbeans.home".equals(x) || "netbeans.user".equals(x)) { // NOI18N
            // Control access to this system property.
            for (Class<?> c : getClassContext()) {
                if (c != TopSecurityManager.class &&
                        c != System.class &&
                        c != Boolean.class) {
                    String n = c.getName();
                    boolean log;
                    synchronized (warnedClassesNH) {
                            log = warnedClassesNH.add(n);
                    }
                    if (log) {
                        LOG.log(Level.WARNING, "use of system property {0} has been obsoleted in favor of InstalledFileLocator/Places at {1}", new Object[] {x, findCallStackLine(n)});
                    }
                    break;
                }
            }
        }
    }
    private static String findCallStackLine(String callerClazz) {
        for (StackTraceElement line : Thread.currentThread().getStackTrace()) {
            if (line.getClassName().equals(callerClazz)) {
                return line.toString();
            }
        }
        return callerClazz;
    }
    private final Set<String> warnedClassesNDE = new HashSet<String>(25);
    private static final Set<String> warnedClassesNH = new HashSet<String>(25);
    static {
        // XXX cleaner would be to use @SuppressWarnings, but that has Retention(SOURCE), and not all these can use org.netbeans.api.annotations.common
        warnedClassesNH.add("org.openide.modules.Places");
        warnedClassesNH.add("org.netbeans.MainImpl"); // NOI18N
        warnedClassesNH.add("org.netbeans.MainImpl$BootClassLoader");
        warnedClassesNH.add("org.netbeans.CLIHandler");
        warnedClassesNH.add("org.netbeans.Stamps"); // NOI18N
        warnedClassesNH.add("org.netbeans.Clusters"); // NOI18N
        warnedClassesNH.add("org.netbeans.core.startup.InstalledFileLocatorImpl"); // NOI18N
        warnedClassesNH.add("org.netbeans.core.startup.CLIOptions");
        warnedClassesNH.add("org.netbeans.core.startup.preferences.RelPaths");
        warnedClassesNH.add("org.netbeans.core.startup.layers.BinaryFS");
        warnedClassesNH.add("org.netbeans.modules.netbinox.NetbinoxFactory");
        warnedClassesNH.add("org.netbeans.updater.UpdateTracking"); // NOI18N
        warnedClassesNH.add("org.netbeans.core.ui.ProductInformationPanel"); // #47429; NOI18N
        warnedClassesNH.add("org.netbeans.lib.uihandler.LogFormatter");
        warnedClassesNH.add("org.netbeans.modules.project.libraries.LibrariesStorage");
        warnedClassesNH.add("org.netbeans.modules.j2ee.sun.ide.j2ee.PluginProperties"); // AS bundle is not in any cluster
        warnedClassesNH.add("org.netbeans.modules.apisupport.project.universe.NbPlatform"); // defaultPlatformLocation
    }

    /* ----------------- private methods ------------- */

    /**
     * The method is empty. This is not "secure", but on the other hand,
     * it reduces performance penalty of startup about 10%
     */
    public @Override void checkRead(String file) {
        notifyRead(file);
    }
    
    public @Override void checkRead(FileDescriptor fd) {
    }

    
    public @Override void checkWrite(FileDescriptor fd) {
    }

    /** The method has awful performance in super class */
    public @Override void checkDelete(String file) {
        notifyDelete(file);
        try {
            checkPermission(allPermission);
            return;
        } catch (SecurityException e) {
            super.checkDelete(file);
        }
    }
           
    /** The method has awful performance in super class */
    public @Override void checkWrite(String file) {
        notifyWrite(file);
        try {
            checkPermission(allPermission);
            return;
        } catch (SecurityException e) {
            super.checkWrite(file);
        }
    }
    
    /** Checks connect */
    public @Override void checkConnect(String host, int port) {
        if (! check) {
            return;
        }
        
        try {
            checkPermission(allPermission);
            return;
        } catch (SecurityException e) {
        }
        
        try {
            super.checkConnect(host, port);
            return;
        } catch (SecurityException e) {
        }
        
        PrivilegedCheck.checkConnect(host, port, this);
    }
     
    final void checkConnectImpl(String host, int port) {
        Class<?> insecure = getInsecureClass();
        if (insecure != null) {  
            URL ctx = getClassURL(insecure);
            if (ctx != null) {
                try {
                    String fromHost = ctx.getHost();
                    InetAddress ia2 = InetAddress.getByName(host);
                    InetAddress ia3 = InetAddress.getByName(fromHost);
                    if (ia2.equals(ia3)) {
                        return;
                    }
                } catch (UnknownHostException e) { // ignore
                    e.printStackTrace();
                }
            }
            throw new SecurityException();
        }
    }

    public @Override void checkConnect(String s, int port, Object context) {
        checkConnect(s, port);
    }

    private final Set<Class<?>> warnedSunMisc = new WeakSet<>();
    private final Set<String> callerWhiteList = createCallerWhiteList();
    @SuppressWarnings("deprecation")
    public void checkMemberAccess(Class<?> clazz, int which) {
        final String n = clazz.getName();
        if (n.startsWith("sun.misc")) { // NOI18N
            Class<?> caller = null;
            Class<?>[] arr = getClassContext();
            for (int i = 0; i < arr.length; i++) {
                if (arr[i] == TopSecurityManager.class) {
                    continue;
                }
                if (arr[i] != Class.class) {
                    caller = arr[i];
                    break;
                }
            }
            StringBuilder msg = new StringBuilder();
            msg.append("Dangerous reflection access to ").append(n).append(" by ").append(caller).append(" detected!");
            if (caller != null && caller.getProtectionDomain() != null) {
                CodeSource cs = caller.getProtectionDomain().getCodeSource();
                msg.append("\ncode location: ").append(cs == null ? "null" : cs.getLocation());
            }
            if (caller != null && isDangerous(caller.getName(), n)) {
                throw new SecurityException(msg.toString());
            }
            Level l;
            if (caller != null && callerWhiteList.contains(caller.getName())) {
                l = Level.FINEST;
            } else {
                l = Level.FINE;
                assert (l = Level.INFO) != null;
            }
            if (!warnedSunMisc.add(caller)) {
                LOG.log(l, msg.toString());
                return; 
            }
            Exception ex = new Exception(msg.toString()); // NOI18N
            LOG.log(l, null, ex);
        }
    }
    
    /**
     * Create list of safe callers for {@link #checkMemberAccess(Class, int)}.
     */
    private static Set<String> createCallerWhiteList() {
        Set<String> wl = new HashSet<String>();
        wl.add("org.netbeans.core.output2.FileMapStorage");             //NOI18N
        wl.add("com.sun.tools.javac.util.CloseableURLClassLoader");     //NOI18N
        wl.add("java.lang.Thread$1");                                   //NOI18N
        wl.add("org.clank.support.NativeMemory");                       //NOI18N
        wl.add("org.apache.lucene.store.MMapDirectory$1");              //NOI18N
        wl.add("org.apache.lucene.util.Constants"); //#217037
        wl.add("org.apache.lucene.util.RamUsageEstimator");//#217037
        wl.add("com.google.gson.internal.UnsafeAllocator"); //#219464   //NOI18N
        wl.add("org.netbeans.modules.web.jspparser_ext.WebAppParseSupport$ParserClassLoader"); //#218690 // NOI18N
        return wl;
    }
    private static boolean isDangerous(String caller, String accessTo) {
        if ("com.sun.istack.tools.ProtectedTask".equals(caller)) { // NOI18N
            if ("sun.misc.ClassLoaderUtil".equals(accessTo)) { // NOI18N
                // calling ClassLoaderUtil is allowed
                return false;
            }
            return true;
        }
        return false;
    }
    
    public @Override void checkPermission(Permission perm) {
//        assert checkLogger(perm); //#178013 & JDK bug 1694855
        checkSetSecurityManager(perm);
        
        //
        // part of makeSwingUseSpecialClipboard that makes it work on
        // JDK 1.5
        //
        if (awtPermissionClass.isInstance(perm)) {
            if ("accessClipboard".equals (perm.getName ())) { // NOI18N
                ThreadLocal<Object> t;
                synchronized (TopSecurityManager.class) {
                    t = CLIPBOARD_FORBIDDEN;
                }
                if (t == null) {
                    return;
                }
                
                if (t.get () != null) {
                    t.set (this);
                    throw new SecurityException ();
                } else {
                    checkWhetherAccessedFromSwingTransfer ();
                }
            }
            if ("showWindowWithoutWarningBanner".equals(perm.getName())) { // NOI18N
                checkTopLevelWindow(perm, null);
            }
        }
        return;
    }
    
    public @Override void checkPermission(Permission perm, Object context) {
//        assert checkLogger(perm); //#178013 & JDK bug 1694855
        checkSetSecurityManager(perm);
        return;
    }

    private boolean checkLogger(Permission perm) {
        //Do not allow foreign code to replace NetBeans logger with its own
        //(particularly java.util.logging.FileLogger, which will deadlock)
        //see http://netbeans.org/bugzilla/show_bug.cgi?id=178013
        if (LoggingPermission.class.isInstance(perm)) {
            //This code will run every time a logger is created;  if this
            //proves too performance-degrading, replace the assertion test
            //with a system property so that mysterious logger-related deadlocks
            //can still be done, but leave it off by default
            Throwable t = new Exception().fillInStackTrace();
            for (StackTraceElement e : t.getStackTrace()) {
                //Currently no other reliable way to determine that the call
                //is to reset the logging infrastructure, not just create
                //a logger - see JDK bug 1694855
                if ("java.util.logging.LogManager".equals(e.getClassName()) && "reset".equals(e.getMethodName())) { //NOI18N
                    SecurityException se = new SecurityException("Illegal attempt to reset system logger"); //NOI18N
                    throw se;
                }
                if ("java.util.logging.LogManager".equals(e.getClassName()) && "readConfiguration".equals(e.getMethodName())) { //NOI18N
                    SecurityException se = new SecurityException("Illegal attempt to replace system logger configuration"); //NOI18N
                    throw se;
                }
            }
        }
        return true;
    }
    
    public static void install() {
        try {
            System.setSecurityManager(new TopSecurityManager());
        } catch (SecurityException ex) {
            LOG.log(Level.WARNING, "Cannot associated own security manager"); // NOI18N
            LOG.log(Level.INFO, "Cannot associated own security manager", ex); // NOI18N
        }
    }
    static void uninstall() {
        System.setSecurityManager(null);
    }
    
    /** Prohibits to set another SecurityManager */
    private void checkSetSecurityManager(Permission perm) {
        if (runtimePermissionClass.isInstance(perm)) {
            if (perm.getName().equals("setSecurityManager")) { // NOI18N - hardcoded in java.lang
                if (!check) {
                    return;
                }
                Class<?>[] arr = getClassContext();
                boolean seenJava = false;
                for (int i = 0; i < arr.length; i++) {
                    if (arr[i].getName().equals("org.netbeans.TopSecurityManager")) { // NOI18N
                        if (seenJava) {
                            // if the change of security manager is called from my own
                            // class or the class loaded by other classloader, then it is likely ok
                            return;
                        } else {
                            continue;
                        }
                    }
                    if (arr[i] != System.class) {
                        // if there is a non-java class on stack, skip and throw exception
                        break;
                    }
                    seenJava = true;
                }
                throw new SecurityException();
            }
        }
    }

//    
//    public void checkMemberAccess(Class clazz, int which) {
//        if ((which == java.lang.reflect.Member.PUBLIC) ||
//                javax.swing.text.JTextComponent.class.isAssignableFrom(clazz)) {
//            return;
//        } else {
//            super.checkMemberAccess(clazz, which);
//        }
//    }
//
    private Class<?> getInsecureClass() {

        Class<?>[] ctx = getClassContext();
        boolean firstACClass = false;

LOOP:   for (int i = 0; i < ctx.length; i++) {

            if (ctx[i] == accessControllerClass) {
                // privileged action is on the stack before an untrusted class loader
                // #3950
                if (firstACClass) {
                    return null;
                } else {
                    firstACClass = true;
                    continue LOOP;
                }
            } else if (ctx[i].getClassLoader() != null) {

                if (isSecureClass(ctx[i])) {
                    if (classLoaderClass.isAssignableFrom(ctx[i])) {
                        return null;
                    } else {
                        // OK process next one
                        continue LOOP;
                    }
                }

                return ctx[i];
            } else if (classLoaderClass.isAssignableFrom(ctx[i])) { // cloader == null
                return null; // foreign classloader wants to do work...
            }
        }

        return null;
    }

    /** Checks if the class is loaded through the nbfs URL */
    static boolean isSecureClass(final Class<?> clazz) {
        URL source = getClassURL(clazz);
        if (source != null) {
            return isSecureProtocol(source.getProtocol());
        } else {
            return true;
        }
    }
    
    /** @return a protocol through which was the class loaded (file://...) or null
    */
    static URL getClassURL(Class<?> clazz) {
        java.security.CodeSource cs = clazz.getProtectionDomain().getCodeSource();                                                     
        if (cs != null) {
            URL url = cs.getLocation();
            return url;
        } else { // PROXY CLASS?
            return null;
        }
    }

    static Field getUrlField(Class<?> clazz) {
        if (urlField == null) {
            try {
                Field[] fds = clazz.getDeclaredFields();
                for (int i = 0; i < fds.length; i++) {
                    if (fds[i].getType() == URLClass) {
                        fds[i].setAccessible(true);
                        urlField = fds[i];
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return urlField;
    }

    private static Field urlField;

    /** @return Boolean.TRUE iff the string is a safe protocol (file, nbfs, ...) */
    static boolean isSecureProtocol(String protocol) {
        if (protocol.equals("http") || // NOI18N
            protocol.equals("ftp") || // NOI18N
            protocol.equals("rmi")) { // NOI18N
            return false;
        } else {
            return true;
        }
    }

    // Workaround for bug 
    // 
    // http://developer.java.sun.com/developer/bugParade/bugs/4818143.html
    //
    // sun.awt.datatransfer.ClipboardTransferable.getClipboardData() can hang
    // for very long time (maxlong == eternity).  We tries to avoid the hang by
    // access the system clipboard from a separate thread.  If the hang happens
    // the thread will wait for the system clipboard forever but not the whole
    // IDE.  See also NbClipboard
    
    private static ThreadLocal<Object> CLIPBOARD_FORBIDDEN;
    
    /** Convinces Swing components that they should use special clipboard
     * and not Toolkit.getSystemClipboard.
     *
     * @param clip clipboard to use
     */
    public static void makeSwingUseSpecialClipboard (java.awt.datatransfer.Clipboard clip) {
        try {
            synchronized (TopSecurityManager.class) {
                if (! (System.getSecurityManager() instanceof TopSecurityManager)) {
                    LOG.warning("Our manager has to be active: " + System.getSecurityManager());
                    return;
                } // NOI18N
                if (CLIPBOARD_FORBIDDEN != null) {
                    return;
                }
                CLIPBOARD_FORBIDDEN = new ThreadLocal<Object>();
                CLIPBOARD_FORBIDDEN.set (clip);
            }
            
            javax.swing.JComponent source = new javax.swing.JPanel ();
            javax.swing.TransferHandler.getPasteAction ().actionPerformed (
                new java.awt.event.ActionEvent (source, 0, "")
            );
            javax.swing.TransferHandler.getCopyAction ().actionPerformed (
                new java.awt.event.ActionEvent (source, 0, "")
            );
            javax.swing.TransferHandler.getCutAction ().actionPerformed (
                new java.awt.event.ActionEvent (source, 0, "")
            );
            Object forb = CLIPBOARD_FORBIDDEN.get ();
            CLIPBOARD_FORBIDDEN.set(null);
            if (! (forb instanceof TopSecurityManager) ) {
                System.err.println("Cannot install our clipboard to swing components, TopSecurityManager is not the security manager: " + forb); // NOI18N
                return;
            }

            Class<?> appContextClass = ClassLoader.getSystemClassLoader().loadClass("sun.awt.AppContext"); // NOI18N
            Method getAppContext = appContextClass.getMethod ("getAppContext"); // NOI18N
            Object appContext = getAppContext.invoke (null, new Object[0]);
            
            Class<?> actionClass = javax.swing.TransferHandler.getCopyAction ().getClass ();
            java.lang.reflect.Field sandboxKeyField = actionClass.getDeclaredField ("SandboxClipboardKey"); // NOI18N
            sandboxKeyField.setAccessible (true);
            Object value = sandboxKeyField.get (null);
            
            Method put = appContextClass.getMethod ("put", Object.class, Object.class); // NOI18N
            put.invoke (appContext, new Object[] { value, clip });
        } catch (ThreadDeath ex) {
            throw ex;
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            if (CLIPBOARD_FORBIDDEN != null) {
                CLIPBOARD_FORBIDDEN.set (null);
            }
        }
    }
    
    /** Throws exception if accessed from javax.swing.TransferHandler class
     */
    private void checkWhetherAccessedFromSwingTransfer () throws SecurityException {
        boolean throwExc = false;
        for (Class<?> c : getClassContext()) {
            if (c.getName().equals("org.netbeans.editor.BaseCaret")) { // NOI18N
                return;
            }
            if (c.getName().equals("javax.swing.TransferHandler$TransferAction")) { // NOI18N
                throwExc = true;
            }
        }
        if (throwExc) {
            throw new SecurityException("All swing access to clipboard should be redirected to ExClipboard"); // NOI18N
        }
    }


    private static final class PrivilegedCheck implements PrivilegedExceptionAction<Object> {
        int action;
        TopSecurityManager tsm;
        
        // exit
        int status;
        AccessControlContext acc;

        // connect
        String host;
        int port;
        
        
        public PrivilegedCheck(int action, TopSecurityManager tsm) {
            this.action = action;
            this.tsm = tsm;
            
            if (action == 0) {
                acc = AccessController.getContext();
            }
        }
        
        public Object run() throws Exception {
            switch (action) {
                case 0 : 
                    tsm.checkExitImpl(status, acc);
                    break;
                case 1 :
                    tsm.checkConnectImpl(host, port);
                    break;
                default :
            }
            return null;
        }
        
        static void checkExit(int status, TopSecurityManager tsm) {
            PrivilegedCheck pea = new PrivilegedCheck(0, tsm);
            pea.status = status;
            check(pea);
        }
        
        static void checkConnect(String host, int port, TopSecurityManager tsm) {
            PrivilegedCheck pea = new PrivilegedCheck(1, tsm);
            pea.host = host;
            pea.port = port;
            check(pea);
        }
        
        private static void check(PrivilegedCheck action) {
            try {
                AccessController.doPrivileged(action);
            } catch (PrivilegedActionException e) {
                Exception orig = e.getException();
                if (orig instanceof RuntimeException) {
                    throw ((RuntimeException) orig);
                }
                orig.printStackTrace();
            }
        }
    }
    
}
