//----------------------------BEGIN LICENSE----------------------------
/* 
 * Willow : the Open Source WorkFlow Project
 * Distributable under GNU LGPL license by gun.org
 *
 * Copyright (C) 2004-2010 huihoo.org 
 * Copyright (C) 2004-2010  ZosaTapo <dertyang@hotmail.com> 
 * 
 * ====================================================================
 * Project Homepage : http://www.huihoo.org/willow
 * Source Forge     : http://sourceforge.net/projects/huihoo
 * Mailing list     : willow@lists.sourceforge.net 
 */
//----------------------------END  LICENSE----------------------------

package org.huihoo.workflow.util;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.security.SecureRandom;
import java.util.Random;

import org.huihoo.commons.util.NumberFormat;
import org.huihoo.workflow.WorkflowSerializable;

public final class ObjectUUID implements WorkflowSerializable
{
	private static final UUID mySpace;
	private static final Random generator;

	/**
	 * @serial object number
	 * @see #hashCode
	 */
	private final long objNum;

	/**
	 * @serial address space identifier (unique to host over time)
	 */
	private final UUID space;

	/**
	 * Generates a unique object identifier.
	 *
	 * <p>If the system property <code>org.huihoo.workflow.randomIDs</code>
	 * is defined to equal the string <code>"true"</code> (case insensitive),
	 * then this constructor will use a cryptographically
	 * strong random number generator to choose the object number of the
	 * returned <code>ObjID</code>.
	 */
	public ObjectUUID()
	{
		/*
		 * Create a new UUID if the SecureRandom generator is used (mySpace
		 * will be null in this case).  Using a different UUID for each ObjID
		 * ensures that ObjIDs will be unique in this VM incarnation when
		 * paired with the result of the secure random number generator.
		 */
		space = (mySpace != null) ? mySpace : new UUID();
		objNum = generator.nextLong();
	}

	/**
	 * Creates a "well-known" object identifier.
	 *
	 * <p>An <code>ObjID</code> created via this constructor will not
	 * clash with any <code>ObjID</code>s generated via the no-arg
	 * constructor.
	 *
	 * @param	objNum object number for well-known object identifier
	 */
	public ObjectUUID(int objNum)
	{
		space = new UUID((short) 0);
		this.objNum = objNum;
	}

	/**
	 * Constructs an object identifier given data read from a stream.
	 */
	private ObjectUUID(long objNum, UUID space)
	{
		this.objNum = objNum;
		this.space = space;
	}

	/**
	 * Marshals a binary representation of this <code>ObjID</code> to
	 * an <code>ObjectOutput</code> instance.
	 *
	 * <p>Specifically, this method first invokes the given stream's
	 * {@link ObjectOutput#writeLong(long)} method with this object
	 * identifier's object number, and then it writes its address
	 * space identifier by invoking its {@link UUID#write(DataOutput)}
	 * method with the stream.
	 *
	 * @param	out the <code>ObjectOutput</code> instance to write
	 * this <code>ObjID</code> to
	 *
	 * @throws	IOException if an I/O error occurs while performing
	 * this operation
	 */
	public void write(ObjectOutput out) throws IOException
	{
		out.writeLong(objNum);
		space.write(out);
	}

	/**
	 * Constructs and returns a new <code>ObjID</code> instance by
	 * unmarshalling a binary representation from an
	 * <code>ObjectInput</code> instance.
	 *
	 * <p>Specifically, this method first invokes the given stream's
	 * {@link ObjectInput#readLong()} method to read an object number,
	 * then it invokes {@link UUID#read(DataInput)} with the
	 * stream to read an address space identifier, and then it
	 * creates and returns a new <code>ObjID</code> instance that
	 * contains the object number and address space identifier that
	 * were read from the stream.
	 *
	 * @param	in the <code>ObjectInput</code> instance to read
	 * <code>ObjID</code> from
	 *
	 * @return	unmarshalled <code>ObjID</code> instance
	 *
	 * @throws	IOException if an I/O error occurs while performing
	 * this operation
	 */
	public static ObjectUUID read(ObjectInput in) throws IOException
	{
		long num = in.readLong();
		UUID space = UUID.read(in);
		return new ObjectUUID(num, space);
	}

	/**
	 * Returns the hash code value for this object identifier, the
	 * object number.
	 *
	 * @return	the hash code value for this object identifier
	 */
	public int hashCode()
	{
		return (int) objNum;
	}

	/**
	 * Compares the specified object with this <code>ObjID</code> for
	 * equality.
	 *
	 * This method returns <code>true</code> if and only if the
	 * specified object is an <code>ObjID</code> instance with the same
	 * object number and address space identifier as this one.
	 *
	 * @param	obj the object to compare this <code>ObjID</code> to
	 *
	 * @return	<code>true</code> if the given object is equivalent to
	 * this one, and <code>false</code> otherwise
	 */
	public boolean equals(Object obj)
	{
		if (obj instanceof ObjectUUID)
		{
			ObjectUUID id = (ObjectUUID) obj;
			return objNum == id.objNum && space.equals(id.space);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Returns a string representation of this object identifier.
	 *
	 * @return	a string representation of this object identifier
	 */
	public String toString()
	{
		return  space.toString() + ":" + NumberFormat.toString(objNum,4);
	}

	private static final class InsecureRandom extends Random
	{
		private long nextNum;

		public synchronized long nextLong()
		{
			return nextNum++;
		}
	}

	static {
		boolean randomIDs = new Boolean(System.getProperty("org.huihoo.workflow.randomIDs")).booleanValue();

		if (randomIDs)
		{
			generator = new SecureRandom();
			mySpace = null;
		}
		else
		{
			generator = new InsecureRandom();
			/*
			 * The InsecureRandom implementation guarantees that object
			 * numbers will not repeat, so the same UUID value can be used
			 * for all instances of ObjID.
			 */
			mySpace = new UUID();
		}
	}
}