/*
 * 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.openide.execution;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.Permission;
import java.util.Arrays;
import java.util.logging.Level;
import org.netbeans.junit.Log;
import org.netbeans.junit.NbTestCase;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.LocalFileSystem;
import org.openide.util.Utilities;

/** Test NbClassLoader.
 * @author Jesse Glick
 */
public class NbClassLoaderTest extends NbTestCase {

    public NbClassLoaderTest(String name) {
        super(name);
    }
    
    /** Ensure that a user-mode class can at least use findResource() to access
     * resources in filesystems.
     * @see "#13038"
     */
    public void testUsingNbfsProtocol() throws Exception {
        System.setProperty("org.netbeans.core.Plain.CULPRIT", "true");
        File here = Utilities.toFile(getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
        assertTrue("Classpath really contains " + here,
                new File(new File(new File(new File(here, "org"), "openide"), "execution"), "NbClassLoaderTest.class").canRead());
        
        File dataDir = new File(new File(new File(new File(here, "org"), "openide"), "execution"), "data");
        if(!dataDir.exists()) {
            dataDir.mkdir();
        }
        File fooFile = new File(dataDir, "foo.xml");
        if(!fooFile.exists()) {
            fooFile.createNewFile();
        }
        
        LocalFileSystem lfs = new LocalFileSystem();
        lfs.setRootDirectory(here);
        lfs.setReadOnly(true);
        ClassLoader cl = new NbClassLoader(new FileObject[] {lfs.getRoot()}, ClassLoader.getSystemClassLoader().getParent(), null);
        System.setSecurityManager(new MySecurityManager());
        // Ensure this class at least has free access:
        System.getProperty("foo");
        Class c = cl.loadClass("org.openide.execution.NbClassLoaderTest$User");
        assertEquals(cl, c.getClassLoader());
        try {
            c.newInstance();
        } catch (ExceptionInInitializerError eiie) {
            Throwable t = eiie.getException();
            if (t instanceof IllegalStateException) {
                fail(t.getMessage());
            } else if (t instanceof Exception) {
                throw (Exception)t;
            } else {
                throw new Exception(t.toString());
            }
        }
    }
    
    public void testFastIsUsedForFileUrl() throws Exception {
        CharSequence log = Log.enable(NbClassLoader.class.getName(), Level.FINE);
        LocalFileSystem lfs = new LocalFileSystem();
        File here = Utilities.toFile(getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
        lfs.setRootDirectory(here);
        lfs.setReadOnly(true);
        ClassLoader cl = new NbClassLoader(new FileObject[]{lfs.getRoot()}, ClassLoader.getSystemClassLoader().getParent(), null);
        Class c = cl.loadClass("org.openide.execution.NbClassLoaderTest$User");
        assertFalse(log.toString().contains("NBFS used!"));
    }
    
    public static final class User {
        public User() throws Exception {
            URLClassLoader ncl = (URLClassLoader)getClass().getClassLoader();
            URL[] urls = ncl.getURLs();
            if (urls.length != 1) throw new IllegalStateException("Weird URLs: " + Arrays.asList(urls));
            URL manual = new URL(urls[0], "org/openide/execution/data/foo.xml");
            URLConnection uc = manual.openConnection();
            uc.connect();
            String ct = uc.getContentType();
            /* May now be a file: URL, in which case content type is hard to control:
            if (!"text/xml".equals(ct)) throw new IllegalStateException("Wrong content type (manual): " + ct);
             */
            URL auto = getClass().getResource("data/foo.xml");
            if (auto == null) throw new IllegalStateException("Could not load data/foo.xml; try uncommenting se.printStackTrace() in MySecurityManager.checkPermission");
            uc = auto.openConnection();
            uc.connect();
            ct = uc.getContentType();
            /* Ditto:
            if (!"text/xml".equals(ct)) throw new IllegalStateException("Wrong content type (auto): " + ct);
             */
            // Ensure this class does *not* have free access to random permissions:
            try {
                System.getProperty("foo");
                throw new IllegalStateException("Was permitted to access sys prop foo");
            } catch (SecurityException se) {
                // good
            }
        }
    }
    
    private static final class MySecurityManager extends SecurityManager {
        public void checkPermission(Permission p) {
            //System.err.println("cP: " + p);
            if (ok()) {/*System.err.println("ok");*/return;}
            try {
                super.checkPermission(p);
            } catch (SecurityException se) {
                //se.printStackTrace();
                //System.err.println("classes: " + Arrays.asList(getClassContext()));
                throw se;
            }
        }
        public void checkPermission(Permission p, Object c) {
            if (ok()) {/*System.err.println("ok");*/return;}
            super.checkPermission(p, c);
        }
        public void checkRead(String file) {
            // Do not honor file read checks. TopSecurityManager actually leaves
            // this blank for performance, but in fact very little would work if
            // we restricted reads. nbfs: protocols would be useless, meaning user
            // classes would not be able to load resources. This could be solved if
            // necessary by creating a special kind of FilePermission returned from
            // FileURL; it would be recognized by TSM.checkPermission and accepted.
        }
        private boolean ok() {
            Class[] cs = getClassContext();
            int i = 0;
            while (i < cs.length && cs[i] == MySecurityManager.class) i++;
            for (; i < cs.length; i++) {
                if (cs[i] == MySecurityManager.class) {
                    // avoid recursion
                    return true;
                }
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
            new Exception().printStackTrace(new PrintStream(baos));
            if (baos.toString().indexOf("\tat java.security.AccessController.doPrivileged") != -1) {
                // Cheap check for privileged actions.
                // For some reason AccessController does not appear in the classContext
                // (perhaps because it is a native method?).
                return true;
            }
            for (int j = 0; j < cs.length; j++) {
                if (cs[j].getClassLoader() instanceof NbClassLoader) {
                    return false;
                }
            }
            return true;
        }
    }
    
}
