/* Serializables.java

	Purpose:

	Description:

	History:
		Sun Jun 25 22:54:45     2006, Created by tomyeh

Copyright (C) 2006 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.io;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utilities to handle java.io.Serializable.
 *
 * @author tomyeh
 */
public class Serializables {
	/** The logger called org.zkoss.io.serializable used to log serialization
	 * information. You could set it to DEBUG, such that the non-serializable
	 * and ignored values will be logged.
	 * @since 5.0.7
	 */
	public static final Logger logio = LoggerFactory.getLogger("org.zkoss.io.serializable");

	private Serializables() {}

	/** Writes only serializable entries of the specified map.
	 * Non-serializable attributes are ignored.
	 */
	public static <K, V> void smartWrite(ObjectOutputStream s, Map<K, V> map)
	throws IOException {
		if (map != null) {
			final boolean debug = logio.isDebugEnabled();
			for (Map.Entry<K, V> me: map.entrySet()) {
				final K nm = me.getKey();
				final V val = me.getValue();
				if (((nm instanceof Serializable) || (nm instanceof Externalizable))
				&& (val == null || (val instanceof Serializable) || (val instanceof Externalizable))) {
					try {
						s.writeObject(nm);
						s.writeObject(val);
					} catch (java.io.NotSerializableException ex) {
						logio.error("Unable to serialize entry: {}={}", nm,
								val);
						throw ex;
					}
				} else if (nm != null && debug) {
					if (logio.isDebugEnabled()) {
						logio.debug("Skip not-serializable entry: {}={}", nm,
								val);
					}
				}
			}
		}
		s.writeObject(null); //denote end-of-map
	}
	/** Reads serializable entries back (serialized by {@link #smartWrite(ObjectOutputStream,Map)}).
	 *
	 * @param map the map to hold the data being read. If null and any data
	 * is read, a new map (HashMap) is created and returned.
	 * @return the map being read
	 */
	@SuppressWarnings("unchecked")
	public static <K, V> Map<K, V> smartRead(ObjectInputStream s, Map<K, V> map)
	throws IOException, ClassNotFoundException {
		for (;;) {
			final Object nm = s.readObject();
			if (nm == null) break; //no more

			if (map == null) map = new HashMap<K,V>();
			map.put((K)nm, (V)s.readObject());
		}
		return map;
	}
	/** Writes only serializable elements of the specified collection.
	 */
	public static <T> void smartWrite(ObjectOutputStream s, Collection<T> col)
	throws IOException {
		if (col != null) {
			final boolean debug = logio.isDebugEnabled();
			for (T val: col) {
				if ((val instanceof Serializable)
				|| (val instanceof Externalizable)) {
					try {
						s.writeObject(val);
					} catch (java.io.NotSerializableException ex) {
						logio.error("Unable to serialize item: {}", val);
						throw ex;
					}
				} else if (val != null && debug) {
					if (logio.isDebugEnabled()) {
						logio.debug("Skip not-serializable item: {}", val);
					}
				}
			}
		}
		s.writeObject(null);
	}
	/** Reads serializable elements back (serialized by {@link #smartWrite(ObjectOutputStream,Collection)})
	 *
	 * @param col the collection to hold the data being read. If null and
	 * and data is read, a new collection (LinkedList) is created and returned.
	 * @return the collection being read
	 */
	@SuppressWarnings("unchecked")
	public static <T> Collection<T> smartRead(ObjectInputStream s, Collection<T> col)
	throws IOException, ClassNotFoundException {
		for (;;) {
			final Object val = s.readObject();
			if (val == null) break; //no more

			if (col == null) col = new LinkedList<T>();
			col.add((T)val);
		}
		return col;
	}
	/** Reads serializable elements back (serialized by {@link #smartWrite(ObjectOutputStream,Collection)})
	 *
	 * @param col the collection to hold the data being read. If null and
	 * and data is read, a new collection (LinkedList) is created and returned.
	 * @return the collection being read
	 * @since 6.0.0
	 */
	@SuppressWarnings("unchecked")
	public static <T> List<T> smartRead(ObjectInputStream s, List<T> col)
	throws IOException, ClassNotFoundException {
		for (;;) {
			final Object val = s.readObject();
			if (val == null) break; //no more

			if (col == null) col = new LinkedList<T>();
			col.add((T)val);
		}
		return col;
	}

	/** Writes only serializable elements of the specified array.
	 * <p>To read back, use {@link #smartRead(ObjectInputStream, Collection)}.
	 * @since 3.0.0
	 */
	public static <T> void smartWrite(ObjectOutputStream s, T[] ary)
	throws IOException {
		if (ary != null) {
			final boolean debug = logio.isDebugEnabled();
			for (int j = 0; j < ary.length; ++j) {
				final T val = ary[j];
				if ((val instanceof Serializable)
				|| (val instanceof Externalizable)) {
					try {
						s.writeObject(val);
					} catch (java.io.NotSerializableException ex) {
						logio.error("Unable to serialize item: {}", val);
						throw ex;
					}
				} else if (val != null && debug) {
					if (logio.isDebugEnabled()) {
						logio.debug("Skip not-serializable item: {}", val);
					}
				}
			}
		}
		s.writeObject(null);
	}
	/** Writes the given value only if it is serializable.
	 * If not, null is written.
	 * @since 5.0.7
	 */
	public static void smartWrite(ObjectOutputStream s, Object val)
	throws IOException {
		final boolean bser = val instanceof java.io.Serializable
			|| val instanceof java.io.Externalizable;
		s.writeObject(bser ? val : null);
		if (!bser && val != null && logio.isDebugEnabled())
			logio.debug("Skip not-serializable object: {}", val);
	}
}
