/*
 * The MIT License
 *
 * Copyright (c) 2004-2009, Sun Microsystems, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.jvnet.hudson.test;

import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.apache.commons.io.FileUtils;
import org.dom4j.Document;
import org.dom4j.io.SAXReader;
import org.jvnet.hudson.test.junit.GroupedTest;
import org.kohsuke.stapler.MetaClassLoader;
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;

import java.io.File;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Builds up a {@link TestSuite} for performing static syntax checks on Jelly scripts.
 *
 * @author Kohsuke Kawaguchi
 */
public class JellyTestSuiteBuilder {
    /**
     * Given a jar file or a class file directory, recursively search all the Jelly files and build a {@link TestSuite}
     * that performs static syntax checks.
     */
    public static TestSuite build(File res) throws Exception {
        TestSuite ts = new JellyTestSuite();

        final JellyClassLoaderTearOff jct = new MetaClassLoader(JellyTestSuiteBuilder.class.getClassLoader()).loadTearOff(JellyClassLoaderTearOff.class);

        if (res.isDirectory()) {
            for (final File jelly : (Collection <File>)FileUtils.listFiles(res,new String[]{"jelly"},true))
                ts.addTest(new JellyCheck(jelly.toURI().toURL(), jct));
        }
        if (res.getName().endsWith(".jar")) {
            String jarUrl = res.toURI().toURL().toExternalForm();
            JarFile jf = new JarFile(res);
            Enumeration<JarEntry> e = jf.entries();
            while (e.hasMoreElements()) {
                JarEntry ent =  e.nextElement();
                if (ent.getName().endsWith(".jelly"))
                    ts.addTest(new JellyCheck(new URL("jar:"+jarUrl+"!/"+ent.getName()), jct));
            }
            jf.close();
        }
        return ts;
    }

    private static class JellyCheck extends TestCase {
        private final URL jelly;
        private final JellyClassLoaderTearOff jct;

        public JellyCheck(URL jelly, JellyClassLoaderTearOff jct) {
            super(jelly.getPath());
            this.jelly = jelly;
            this.jct = jct;
        }

        @Override
        protected void runTest() throws Exception {
            jct.createContext().compileScript(jelly);
            Document dom = new SAXReader().read(jelly);
            checkLabelFor(dom);
            // TODO: what else can we check statically? use of taglibs?
        }

        /**
         * Makes sure that &lt;label for=...> is not used inside config.jelly nor global.jelly
         */
        private void checkLabelFor(Document dom) {
            if (isConfigJelly() || isGlobalJelly()) {
                if (!dom.selectNodes("//label[@for]").isEmpty())
                    throw new AssertionError("<label for=...> shouldn't be used because it doesn't work " +
                            "when the configuration item is repeated. Use <label class=\"attach-previous\"> " +
                            "to have your label attach to the previous DOM node instead.");
            }
        }

        private boolean isConfigJelly() {
            return jelly.toString().endsWith("/config.jelly");
        }

        private boolean isGlobalJelly() {
            return jelly.toString().endsWith("/global.jelly");
        }
    }

    /**
     * Execute all the Jelly tests in a servlet request handling context. To do so, we reuse HudsonTestCase
     */
    private static final class JellyTestSuite extends GroupedTest {
        HudsonTestCase h = new HudsonTestCase("Jelly test wrapper") {};

        @Override
            protected void setUp() throws Exception {
            h.setUp();
        }

        @Override
            protected void tearDown() throws Exception {
            h.tearDown();
        }

        private void doTests(TestResult result) throws Exception {
            super.runGroupedTests(result);
        }

        @Override
            protected void runGroupedTests(final TestResult result) throws Exception {
            h.executeOnServer(new Callable<Object>() {
                // this code now inside a request handling thread
                public Object call() throws Exception {
                    doTests(result);
                    return null;
                }
            });
        }
    }
}

@@ -4841,36 +4841,32 @@
 verride%0A        
-    
 protected void s
@@ -4935,36 +4935,32 @@
       @Override%0A
-    
         protecte
@@ -5168,28 +5168,24 @@
   @Override%0A
-    
         prot

indentation fix