/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * SerializationHelper.java
 * Copyright (C) 2007-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Vector;

/**
 * A helper class for determining serialVersionUIDs and checking whether
 * classes contain one and/or need one. One can also serialize and deserialize
 * objects to and fro files or streams.
 * 
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 8596 $
 */
public class SerializationHelper
  implements RevisionHandler {

  /** the field name of serialVersionUID. */
  public final static String SERIAL_VERSION_UID = "serialVersionUID";
  
  /**
   * checks whether a class is serializable.
   * 
   * @param classname	the class to check
   * @return		true if the class or one of its ancestors implements
   * 			the Serializable interface, otherwise false (also if 
   * 			the class cannot be loaded)
   */
  public static boolean isSerializable(String classname) {
    boolean	result;
    
    try {
      result = isSerializable(Class.forName(classname));
    }
    catch (Exception e) {
      result = false;
    }
      
    return result;
  }
  
  /**
   * checks whether a class is serializable.
   * 
   * @param c		the class to check
   * @return		true if the class or one of its ancestors implements
   * 			the Serializable interface, otherwise false
   */
  public static boolean isSerializable(Class c) {
    return ClassDiscovery.hasInterface(Serializable.class, c);
  }
  
  /**
   * checks whether the given class contains a serialVersionUID.
   * 
   * @param classname	the class to check
   * @return		true if the class contains a serialVersionUID, 
   * 			otherwise false (also if the class is not
   * 			implementing serializable or cannot be loaded)
   */
  public static boolean hasUID(String classname) {
    boolean	result;
    
    try {
      result = hasUID(Class.forName(classname));
    }
    catch (Exception e) {
      result = false;
    }
    
    return result;
  }

  /**
   * checks whether the given class contains a serialVersionUID.
   * 
   * @param c		the class to check
   * @return		true if the class contains a serialVersionUID, 
   * 			otherwise false (also if the class is not
   * 			implementing serializable)
   */
  public static boolean hasUID(Class c) {
    boolean	result;
    
    result = false;
    
    if (isSerializable(c)) {
      try {
	c.getDeclaredField(SERIAL_VERSION_UID);
	result = true;
      }
      catch (Exception e) {
	result = false;
      }
    }
    
    return result;
  }
  
  /**
   * checks whether a class needs to declare a serialVersionUID, i.e., it
   * implements the java.io.Serializable interface but doesn't declare a
   * serialVersionUID.
   * 
   * @param classname	the class to check
   * @return		true if the class needs to declare one, false otherwise
   * 			(also if the class cannot be loaded!)
   */
  public static boolean needsUID(String classname) {
    boolean	result;
    
    try {
      result = needsUID(Class.forName(classname));
    }
    catch (Exception e) {
      result = false;
    }
    
    return result;
  }
  
  /**
   * checks whether a class needs to declare a serialVersionUID, i.e., it
   * implements the java.io.Serializable interface but doesn't declare a
   * serialVersionUID.
   * 
   * @param c		the class to check
   * @return		true if the class needs to declare one, false otherwise
   */
  public static boolean needsUID(Class c) {
    boolean	result;
    
    if (isSerializable(c))
      result = !hasUID(c);
    else
      result = false;
    
    return result;
  }
  
  /**
   * reads or creates the serialVersionUID for the given class.
   * 
   * @param classname	the class to get the serialVersionUID for
   * @return		the UID, 0L for non-serializable classes (or if the
   * 			class cannot be loaded)
   */
  public static long getUID(String classname) {
    long	result;
    
    try {
      result = getUID(Class.forName(classname));
    }
    catch (Exception e) {
      result = 0L;
    }
    
    return result;
  }
  
  /**
   * reads or creates the serialVersionUID for the given class.
   * 
   * @param c		the class to get the serialVersionUID for
   * @return		the UID, 0L for non-serializable classes
   */
  public static long getUID(Class c) {
    return ObjectStreamClass.lookup(c).getSerialVersionUID();
  }

  /**
   * serializes the given object to the specified file.
   * 
   * @param filename	the file to write the object to
   * @param o		the object to serialize
   * @throws Exception	if serialization fails
   */
  public static void write(String filename, Object o) throws Exception {
    write(new FileOutputStream(filename), o);
  }

  /**
   * serializes the given object to the specified stream.
   * 
   * @param stream	the stream to write the object to
   * @param o		the object to serialize
   * @throws Exception	if serialization fails
   */
  public static void write(OutputStream stream, Object o) throws Exception {
    ObjectOutputStream	oos;
    
    if (!(stream instanceof BufferedOutputStream))
      stream = new BufferedOutputStream(stream);
    
    oos = new ObjectOutputStream(stream);
    oos.writeObject(o);
    oos.flush();
    oos.close();
  }

  /**
   * serializes the given objects to the specified file.
   * 
   * @param filename	the file to write the object to
   * @param o		the objects to serialize
   * @throws Exception	if serialization fails
   */
  public static void writeAll(String filename, Object[] o) throws Exception {
    writeAll(new FileOutputStream(filename), o);
  }

  /**
   * serializes the given objects to the specified stream.
   * 
   * @param stream	the stream to write the object to
   * @param o		the objects to serialize
   * @throws Exception	if serialization fails
   */
  public static void writeAll(OutputStream stream, Object[] o) throws Exception {
    ObjectOutputStream	oos;
    int			i;
    
    if (!(stream instanceof BufferedOutputStream))
      stream = new BufferedOutputStream(stream);
    
    oos = new ObjectOutputStream(stream);
    for (i = 0; i < o.length; i++)
      oos.writeObject(o[i]);
    oos.flush();
    oos.close();
  }

  /**
   * deserializes the given file and returns the object from it.
   * 
   * @param filename	the file to deserialize from
   * @return		the deserialized object
   * @throws Exception	if deserialization fails
   */
  public static Object read(String filename) throws Exception {
    return read(new FileInputStream(filename));
  }

  /**
   * deserializes from the given stream and returns the object from it.
   * 
   * @param stream	the stream to deserialize from
   * @return		the deserialized object
   * @throws Exception	if deserialization fails
   */
  public static Object read(InputStream stream) throws Exception {
    ObjectInputStream 	ois;
    Object		result;
    
    if (!(stream instanceof BufferedInputStream))
      stream = new BufferedInputStream(stream);
    
    ois    = new ObjectInputStream(stream);
    result = ois.readObject();
    ois.close();
    
    return result;
  }

  /**
   * deserializes the given file and returns the objects from it.
   * 
   * @param filename	the file to deserialize from
   * @return		the deserialized objects
   * @throws Exception	if deserialization fails
   */
  public static Object[] readAll(String filename) throws Exception {
    return readAll(new FileInputStream(filename));
  }

  /**
   * deserializes from the given stream and returns the object from it.
   * 
   * @param stream	the stream to deserialize from
   * @return		the deserialized object
   * @throws Exception	if deserialization fails
   */
  public static Object[] readAll(InputStream stream) throws Exception {
    ObjectInputStream 	ois;
    Vector<Object>	result;
    
    if (!(stream instanceof BufferedInputStream))
      stream = new BufferedInputStream(stream);
    
    ois    = new ObjectInputStream(stream);
    result = new Vector<Object>();
    try {
      while (true) {
	result.add(ois.readObject());
      }
    }
    catch (IOException e) {
      // ignored
    }
    ois.close();
    
    return result.toArray(new Object[result.size()]);
  }
  
  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8596 $");
  }
  
  /**
   * Outputs information about a class on the commandline, takes class
   * name as arguments.
   * 
   * @param args	the classnames to check
   * @throws Exception	if something goes wrong
   */
  public static void main(String[] args) throws Exception {
    if (args.length == 0) {
      System.out.println("\nUsage: " + SerializationHelper.class.getName() + " classname [classname [classname [...]]]\n");
      System.exit(1);
    }
    
    // check all the classes
    System.out.println();
    for (int i = 0; i < args.length; i++) {
      System.out.println(args[i]);
      System.out.println("- is serializable: " + isSerializable(args[i]));
      System.out.println("- has " + SERIAL_VERSION_UID + ": " + hasUID(args[i]));
      System.out.println("- needs " + SERIAL_VERSION_UID + ": " + needsUID(args[i]));
      System.out.println("- " + SERIAL_VERSION_UID + ": private static final long serialVersionUID = " + getUID(args[i]) + "L;");
      System.out.println();
    }
  }
}
