/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
package org.jboss.remoting.loading;

import org.jboss.logging.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.Set;


/**
 * ClassUtil is a set of generic class utlities.
 *
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @author <a href="mailto:tom@jboss.org">Tom Elrod</a>
 * @version $Revision: 1.2.14.1 $
 */
public class ClassUtil
{
   protected final static Logger log = Logger.getLogger(ClassUtil.class);

    public static Object deserialize (ClassBytes cb, ClassLoader cl)
        throws IOException, ClassNotFoundException
    {
        if (cb.getClassBytes()==null)
        {
            return null;
        }
        java.io.ByteArrayInputStream bis=new java.io.ByteArrayInputStream(cb.getClassBytes());
        java.io.ObjectInputStream ois=new ObjectInputStreamWithClassLoader(bis,cl);
        Object result = ois.readObject();
        bis=null;
        ois=null;
        return result;
    }
    public static Object deserialize (byte buf[])
        throws IOException, ClassNotFoundException
    {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null)
        {
            cl = ClassUtil.class.getClassLoader();
        }
        return deserialize(buf,cl);
    }
    public static Object deserialize (byte buf[], ClassLoader cl)
        throws IOException, ClassNotFoundException
    {
        if (buf==null)
        {
            return null;
        }
        java.io.ByteArrayInputStream bis=new java.io.ByteArrayInputStream(buf);
        java.io.ObjectInputStream ois=new ObjectInputStreamWithClassLoader(bis,cl);
        Object result = ois.readObject();
        bis=null;
        ois=null;
        return result;
    }
    public static byte[] serialize (Object obj)
        throws java.io.IOException
    {
        java.io.ByteArrayOutputStream bos=new java.io.ByteArrayOutputStream();
        java.io.ObjectOutputStream oos=new java.io.ObjectOutputStream(bos);
        oos.writeObject(obj);
        oos.flush();
        bos.flush();
        byte buf[]=bos.toByteArray();
        bos=null;
        oos=null;
        return buf;
    }
    public static boolean isArrayClass (String className)
    {
        return (className.startsWith("[L") && className.endsWith(";"));
    }
    public static String getArrayClassPart (String className)
    {
        String cn = className;
        int i = className.indexOf("[L");
        if (i>-1)
        {
            cn = className.substring(i+2,className.length()-1);
        }
        return cn;
    }
    public static String getPackageName (Class cl)
    {
        String n = cl.getName();
        int i = n.lastIndexOf(".");
        return (i>-1) ? n.substring(0,i) : n;
    }
    public static String getShortClassName (Class cl)
    {
        String n = cl.getName();
        int i = n.lastIndexOf(".");
        return (i>-1) ? n.substring(i+1) : n;
    }
    /**
     * given a class, recurse its dependency graph and find all its implemented interfaces
     *
     * @param clazz
     * @return array of interfaces
     */
    public static Class [] getInterfacesFor (Class clazz)
    {
        // use a set to eliminate duplicates, since you'll get a
        // java.lang.ClassFormatError: $Proxy8 (Repetitive interface name))
        Set set = new HashSet();
        addInterfaces(set,clazz);
        return (Class[])set.toArray(new Class[set.size()]);
    }
    private static void addInterfaces (Set list, Class clazz)
    {
        if (clazz!=null && clazz!=Object.class)
        {
            if (clazz.isInterface() && list.contains(clazz)==false)
            {
                list.add(clazz);
            }
            Class interfaces[]=clazz.getInterfaces();
            if (interfaces!=null && interfaces.length>0)
            {
                for (int c=0;c<interfaces.length;c++)
                {
                    Class interfaceClass = interfaces[c];
                    if (list.contains(interfaceClass)==false)
                    {
                        list.add(interfaceClass);
                    }
                    addInterfaces(list,interfaceClass);
                }
            }
            addInterfaces(list,clazz.getSuperclass());
        }
    }

   /**
    * method is called to retrieve a byte array of a Class for a given class name
    *
    * @param className
    * @return
    */
   public static byte[] getClassBytes (String className, ClassLoader classbyteloader)
   {
       String cn = null;
       if (isArrayClass(className))
       {
           // if requesting an array, of course, that would be found in our class path, so we
           // need to strip the class data and just return the class part, the other side
           // will properly load the class as an array
           cn = getArrayClassPart(className).replace('.','/') + ".class";
       }
       else
       {
           cn = className.replace('.','/') + ".class";
       }
       if (log.isDebugEnabled())
       {
           log.debug("trying to load class: "+className+" from path: "+cn);
       }
       InputStream in = null;
       ClassLoader cl = classbyteloader;

       if (cl==null)
       {
           cl = ClassLoader.getSystemClassLoader();
       }
       if (cl!=null)
       {
           in = cl.getResourceAsStream(cn);
           if (in!=null)
           {
               if (log.isDebugEnabled())
               {
                   log.debug("looking for classes at: "+cl);
               }
               try
               {
                   byte data[] = read(in);
                   if (log.isDebugEnabled())
                   {
                       log.debug("found class at classloader: "+cl);
                   }
                   return data;
               }
               catch (IOException io)
               {
               }
               finally
               {
                   if (in!=null)
                   {
                       try { in.close(); } catch (Exception ig) {}
                       in = null;
                   }
               }
           }
       }
       return null;
   }

       /**
     * simple utility method for reading bytes from an input stream
     *
     * @param in
     * @return
     * @throws IOException
     */
    protected static byte[] read (InputStream in)
        throws IOException
    {
        ByteArrayOutputStream out=new ByteArrayOutputStream();
        byte buf[]=new byte[4096];
        while(true)
        {
            int c = in.read(buf);
            if (c<0)
            {
                break;
            }
            out.write(buf,0,c);
        }
        return out.toByteArray();
    }

}