/**************************************************************************************
 * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.codehaus.aspectwerkz.hook.impl;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.codehaus.aspectwerkz.hook.ClassPreProcessor;

/**
 * A simple implementation of class preprocessor.
 * <p/>
 * It does not modify the bytecode. It just prints on stdout some messages.
 *
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
 */
public class StdoutPreProcessor implements ClassPreProcessor {

    /**
     * Classloaders repository, based on a synchronized weak hashmap key = classloader value = List of URL[]
     * representing the local search path for .class files of the classloader value is completed at each class loading
     */
    private static Map classloaders;

    /**
     * ms interval betwee classloader hierarchy printing
     */
    private static final long stepms = 15000;

    private static transient long lastPrinted = 0;

    private void log(String s) {
        System.out.println(Thread.currentThread().getName() + ": StdoutPreProcessor: " + s);
    }

    public void initialize(Hashtable hashtable) {
        log("initialize");
        log("loaded by " + this.getClass().getClassLoader());

        classloaders = Collections.synchronizedMap(new WeakHashMap());
        // register the classloader - except bootstrapCL
        registerClassLoader(this.getClass().getClassLoader(), this.getClass().getName());
    }

    public byte[] preProcess(String klass, byte abyte[], ClassLoader caller) {
        // emulate a -verbose:class mode
        klass = klass.replace('.', '/') + ".class";
        URL u = caller.getResource(klass);
        log("> " + klass + " [" + ((u == null) ? "?" : u.toString()) + "] [" + caller + "]");
        /*
        URL uRoot = null;
        if (u!=null) {
            // u = jar:file:/C:/bea/weblogic81/server/lib/weblogic.jar!/weblogic/t3/srvr/T3Srvr.class
            // getPath = file:/C:/bea/weblogic81/server/lib/weblogic.jar!/weblogic/t3/srvr/T3Srvr.class
            // getFile = file:/C:/bea/weblogic81/server/lib/weblogic.jar!/weblogic/t3/srvr/T3Srvr.class
            int i = u.toString().indexOf('!');
            if (i > 0) {
                try {
                    uRoot = new URL(u.toString().substring(0, i)+"!/");
                } catch (MalformedURLException e) {
                    ;
                }
            }
        }
        */

        // register the classloader
        registerClassLoader(caller, klass);

        // complete the url path of the classloader
        registerSearchPath(caller, klass);

        // dump the hierarchy if needed
        if (System.currentTimeMillis() > lastPrinted + stepms) {
            lastPrinted = System.currentTimeMillis();
            log("*******************************");
            log("size=" + classloaders.size());
            dumpHierarchy(null, "");
            log("*******************************");
        }

        return abyte;
    }

    /**
     * Register a weak reference on the classloader Looks for META-INF/manifest.mf resource and log a line
     *
     * @param loader
     * @param firstClassLoaded
     */
    private void registerClassLoader(ClassLoader loader, String firstClassLoaded) {
        if (loader != null) {
            if (!classloaders.containsKey(loader)) {
                // register the loader and the parent hierarchy if not already registered
                registerClassLoader(loader.getParent(), loader.getClass().getName());
                registerSearchPath(loader.getParent(), loader.getClass().getName());

                URL u = null;
                // *** THIS IS NOT WORKING for other than .class files
                // *** since manifest.mf can be several time
                // *** but getResource follow parent first delegation model
                // try to locate the first META-INF/manifest.mf (should be the aw.xml)
                // case sensitive of META-INF, meta-inf, Meta-Inf, Meta-inf ? YES IT IS
                //u = loader.getResource("META-INF/MANIFEST.MF");

                // *** THIS could be enough for early registration
                // add resources in some struct, allow multiple deploy of same resource
                // if in parallel CL hierarchy - got it ?
                try {
                    //@todo - case sensitive stuff is dangerous
                    // we could merge all aw.xml in META-INF and WEB-INF and meta-inf ...
                    Enumeration ue = loader.getResources("META-INF/MANIFEST.MF");
                    if (ue.hasMoreElements()) {
                        log("--- in scope for " + loader);
                    }
                    while (ue.hasMoreElements()) {
                        log("--- " + ue.nextElement().toString());
                    }
                }
                catch (IOException e) {
                    ;
                }

                // register this loader
                log("****" + loader + " [" + ((u == null) ? "?" : u.toString()) + "] [" + firstClassLoaded + ']');
                classloaders.put(loader, new ArrayList());
            }

            // register search path based on firstClassLoaded
        }
    }

    /**
     * Dumps on stdout the registered classloader hierarchy child of "parent" Using the depth to track recursivity
     * level
     */
    private void dumpHierarchy(ClassLoader parent, String depth) {
        // do a copy of the registered CL to allow access on classloaders structure
        List cl = new ArrayList(classloaders.keySet());

        ClassLoader current = null;
        for (Iterator i = cl.iterator(); i.hasNext();) {
            current = (ClassLoader)i.next();
            if (current.getParent() == parent) {

                log(depth + current + '[' + classloaders.get(current));

                // handcheck for duplicate path (?)
                List path = (List)classloaders.get(current);
                ClassLoader currentParent = current.getParent();
                while (currentParent != null) {
                    for (Iterator us = path.iterator(); us.hasNext();) {
                        URL u = (URL)us.next();
                        if (((List)classloaders.get(currentParent)).contains(u)) {
                            log("!!!! duplicate detected for " + u + " in " + current);
                        }
                    }
                    currentParent = currentParent.getParent();
                }

                dumpHierarchy(current, depth + "  ");
            }
        }
    }

    private void registerSearchPath(final ClassLoader loader, final String klass) {
        // bootCL
        if (loader == null) {
            return;
        }

        // locate the klass
        String klassFile = klass.replace('.', '/') + ".class";
        URL uKlass = loader.getResource(klassFile);
        if (uKlass == null) {
            return;
        }

        // retrieve the location root (jar/zip or directory)
        URL uRoot = null;
        int i = uKlass.toString().indexOf('!');
        if (i > 0) {
            // jar/zip
            try {
                // remove the jar: zip: prefix
                //@todo !! zip: seems to be BEA specific
                uRoot = (new File(uKlass.toString().substring(4, i))).getCanonicalFile().toURL();

                //uRoot = new URL(uKlass.toString().substring(0, i)+"!/");
            }
            catch (MalformedURLException e) {
                e.printStackTrace();
                return;
            }
            catch (IOException e2) {
                e2.printStackTrace();
                return;
            }
        }
        else {
            // directory
            i = uKlass.toString().indexOf(klassFile);
            try {
                uRoot = (new File(uKlass.toString().substring(0, i))).getCanonicalFile().toURL();
            }
            catch (MalformedURLException e) {
                e.printStackTrace();
                return;
            }
            catch (IOException e2) {
                e2.printStackTrace();
                return;
            }
        }

        // check if the location is not in a parent
        ClassLoader parent = loader.getParent();
        while (parent != null) {
            if (((List)classloaders.get(parent)).contains(uRoot)) {
                return;
            }
            parent = parent.getParent();
        }

        // add the location if not already registered
        // @todo !! not thread safe
        List path = (List)classloaders.get(loader);
        if (!path.contains(uRoot)) {
            log("adding path " + uRoot + " to " + loader);
            path.add(uRoot);
        }

    }

    public static void main(String args[]) throws Exception {

        URL u = new URL(
                "jar:file:/C:/bea/user_projects/domains/mydomain/myserver/.wlnotdelete/gallery/gallery-rar.jar!/"
        );
        // differ from a "/./"
        URL u2 = new URL(
                "jar:file:/C:/bea/user_projects/domains/mydomain/./myserver/.wlnotdelete/gallery/gallery-rar.jar!/"
        );
        if (u.sameFile(u2)) {
            System.out.println("same");
        }
        else {
            System.out.println("differ");
        }

    }

}
