/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache;


import net.jcip.annotations.Immutable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.util.ImmutableListCopy;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * A Fully Qualified Name (Fqn) is a list of names (typically Strings but can be any Object),
 * which represent a path to a particular {@link Node} or sometimes a {@link Region} in a {@link Cache}.
 * <p/>
 * This name can be absolute (i.e., relative from the root node - {@link #ROOT}), or relative to any node in the cache.  Reading the
 * documentation on each API call that makes use of {@link org.jboss.cache.Fqn}s will tell you whether the API expects a
 * relative or absolute Fqn.
 * <p/>
 * For instance, using this class to fetch a particular node might look like
 * this.  (Here data on "Joe" is kept under the "Smith" surname node, under
 * the "people" tree.)
 * <pre>
 * Fqn<String> abc = Fqn.fromString("/people/Smith/Joe/");
 * Node joesmith = Cache.getRoot().getChild(abc);
 * </pre>
 * Alternatively, the same Fqn could be constructed using an array:
 * <pre>
 * Fqn<String> abc = Fqn.fromElements("people", "Smith", "Joe");
 * </pre>
 * This is a bit more efficient to construct.
 * <p/>
 * Note that<br>
 * <p/>
 * <code>Fqn<String> f = new Fqn<String>("/a/b/c");</code>
 * <p/>
 * is <b>not</b> the same as
 * <p/>
 * <code>Fqn<String> f = Fqn.fromString("/a/b/c");</code>
 * <p/>
 * The former will result in a single Fqn, called "/a/b/c" which hangs directly under Fqn.ROOT.
 * <p/>
 * The latter will result in 3 Fqns, called "a", "b" and "c", where "c" is a child of "b", "b" is a child of "a", and "a" hangs off Fqn.ROOT.
 * <p/>
 * Another way to look at it is that the "/" separarator is only parsed when it forms
 * part of a String passed in to Fqn.fromString() and not otherwise.
 * <p/>
 * <B>Best practices</B>: Always creating Fqns - even when using some factory methods - can be expensive in the long run,
 * and as far as possible we recommend that client code holds on to their Fqn references and reuse them.  E.g.:
 * <code>
 * // BAD!!
 * for (int i=0; i<someBigNumber; i++)
 * {
 * cache.get(Fqn.fromString("/a/b/c"), "key" + i);
 * }
 * </code>
 * instead, do:
 * <code>
 * // Much better
 * Fqn f = Fqn.fromString("/a/b/c");
 * for (int i=0; i<someBigNumber; i++)
 * {
 * cache.get(f, "key" + i);
 * }
 * </code>
 * <p/>
 * <B>NOTE</B> public constructors of this class are <b>deprectaed</b> and will be removed in JBoss Cache 3.0.0.  The
 * factory methods provided are the correct way get an instance of an Fqn, since these allow for numerous optimisations.
 * <p/>
 *
 * @version $Revision: 6413 $
 */
@Immutable
public class Fqn<E> implements Cloneable, Externalizable, Comparable<Fqn<?>>
{
   /**
    * Separator between FQN elements.
    */
   public static final String SEPARATOR = "/";

   private static final long serialVersionUID = -5351930616956603651L;

   private static final Log log = LogFactory.getLog(Fqn.class);

   protected List<E> elements;
   private transient int hash_code = 0;
   protected int size = 0;

   /**
    * Immutable root FQN.
    */
   @SuppressWarnings("unchecked")
   public static final Fqn ROOT = new Fqn(true);

   /**
    * A cached string representation of this Fqn, used by toString to it isn't calculated again every time.
    */
   protected String stringRepresentation;

   // ----------------- START: Private constructors for use by factory methods only. ----------------------

   // TODO: 3.0.0: Remove the unnecessary internalMarker boolean parameters to these methods once the deprecated public constructors are removed in 3.0.0.

   protected Fqn(boolean internalMarker)
   {
      elements = Collections.emptyList();
      size = 0;
   }

   @SuppressWarnings("unchecked")
   protected Fqn(boolean internalMarker, List<?> names, boolean safe)
   {
      if (names != null)
      {
         // if not safe make a defensive copy
         elements = safe ? names : new ImmutableListCopy(names);
         size = elements.size();
      }
      else
      {
         elements = Collections.emptyList();
         size = 0;
      }
   }

   @SuppressWarnings("unchecked")
   protected Fqn(boolean internalMarker, Fqn<?> base, List<?> relative)
   {
      elements = new ImmutableListCopy(base.elements, relative);
      size = elements.size();
   }

   // ----------------- END: Private constructors for use by factory methods only. ----------------------

   /**
    * Constructs a root Fqn
    *
    * @deprecated use {@link #ROOT} instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn()
   {
      this(true);
   }

   /**
    * Constructs a FQN from a list of names.
    *
    * @param names List of names
    * @deprecated use {@link #fromList(java.util.List)} instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(List<E> names)
   {
      // the list is unsafe - may be referenced externally
      this(true, names, false);
   }

   /**
    * Retrieves an Fqn that represents the list of elements passed in.
    *
    * @param names list of elements that comprise the Fqn
    * @return an Fqn
    * @since 2.2.0
    */
   public static <E> Fqn<E> fromList(List<E> names)
   {
      return new Fqn<E>(true, names, false);
   }

   /**
    * Retrieves an Fqn that represents the list of elements passed in.
    *
    * @param names list of elements that comprise the Fqn
    * @param safe  if true, the list passed in is not defensively copied but used directly.  <b>Use with care.</b>  Make sure
    *              you know what you are doing before you pass in a <tt>true</tt> value to <tt>safe</tt>, as it can have adverse effects on
    *              performance or correctness.  The defensive copy of list elements is not just for safety but also for performance as
    *              an appropriare List implementation is used, which works well with Fqn operations.
    * @return an Fqn
    */
   public static <E> Fqn<E> fromList(List<E> elements, boolean safe)
   {
      return new Fqn<E>(true, elements, safe);
   }

   /**
    * If safe is false, Collections.unmodifiableList() is used to wrap the list passed in.  This is an optimisation so
    * Fqn.fromString(), probably the most frequently used factory method, doesn't end up needing to use the unmodifiableList()
    * since it creates the list internally.
    *
    * @param names List of names
    * @param safe  whether this list is referenced externally (safe = false) or not (safe = true).
    * @deprecated use {@link #fromList(java.util.List)} instead.  The boolean "safety" hint is calculated internally.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(List<E> names, boolean safe)
   {
      this(true, names, safe);
   }


   /**
    * Constructs a Fqn from an array of names.
    *
    * @param names Names that comprose this Fqn
    * @deprecated use {@link #fromElements(Object[])} instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(E... names)
   {
      // safe - the list is created here.
      this(true, Arrays.asList(names), true);
   }

   /**
    * Retrieves an Fqn that represents the array of elements passed in.
    *
    * @param elements array of elements that comprise the Fqn
    * @return an Fqn
    * @since 2.2.0
    */
   public static <E> Fqn<E> fromElements(E... elements)
   {
      return new Fqn<E>(true, Arrays.asList(elements), true);
   }

   /**
    * Constructs a Fqn from a base and relative Fqn.
    *
    * @param base     parent Fqn
    * @param relative Sub-Fqn relative to the parent
    * @deprecated use {@link #fromRelativeFqn(Fqn, Fqn)} instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(Fqn<E> base, Fqn<E> relative)
   {
      this(true, base, relative.elements);
   }

   /**
    * Retrieves an Fqn that represents the absolute Fqn of the relative Fqn passed in.
    *
    * @param base     base Fqn
    * @param relative relative Fqn
    * @return an Fqn
    * @since 2.2.0
    */
   public static Fqn<Object> fromRelativeFqn(Fqn<?> base, Fqn<?> relative)
   {
      return new Fqn<Object>(true, base, relative.elements);
   }

   /**
    * Constructs a Fqn from a base and a list of relative names.
    *
    * @param base     parent Fqn
    * @param relative List of elements that identify the child Fqn, relative to the parent
    * @deprecated use {@link #fromRelativeList(Fqn, java.util.List)}  instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(Fqn<E> base, List<E> relative)
   {
      this(true, base, relative);
   }

   /**
    * Retrieves an Fqn that represents the List of elements passed in, relative to the base Fqn.
    *
    * @param base             base Fqn
    * @param relativeElements relative List of elements
    * @return an Fqn
    * @since 2.2.0
    */
   public static Fqn<Object> fromRelativeList(Fqn<?> base, List<?> relativeElements)
   {
      return new Fqn<Object>(true, base, relativeElements);
   }

   /**
    * Constructs a Fqn from a base and two relative names.
    *
    * @param base       parent Fqn
    * @param childNames elements that denote the path to the Fqn, under the parent
    * @deprecated use {@link #fromRelativeElements(Fqn, Object[])} instead.  This constructor will be removed in 3.0.0.
    */
   @Deprecated
   public Fqn(Fqn<E> base, E... childNames)
   {
      this(true, base, Arrays.asList(childNames));
   }

   /**
    * Retrieves an Fqn that represents the array of elements passed in, relative to the base Fqn.
    *
    * @param base             base Fqn
    * @param relativeElements relative elements
    * @return an Fqn
    * @since 2.2.0
    */
   public static Fqn<Object> fromRelativeElements(Fqn<?> base, Object... relativeElements)
   {
      return new Fqn<Object>(true, base, Arrays.asList(relativeElements));
   }

   /**
    * Returns a new Fqn from a string, where the elements are deliminated by
    * one or more separator ({@link #SEPARATOR}) characters.<br><br>
    * Example use:<br>
    * <pre>
    * Fqn.fromString("/a/b/c/");
    * </pre><br>
    * is equivalent to:<br>
    * <pre>
    * new Fqn("a", "b", "c");
    * </pre><br>
    * but not<br>
    * <pre>
    * new Fqn("/a/b/c");
    * </pre>
    *
    * @param stringRepresentation String representation of the Fqn
    * @return an Fqn<String> constructed from the string representation passed in
    * @see #Fqn(Object[])
    */
   @SuppressWarnings("unchecked")
   public static Fqn<String> fromString(String stringRepresentation)
   {
      if (stringRepresentation == null || stringRepresentation.equals(SEPARATOR) || stringRepresentation.equals(""))
         return root();

      String toMatch = stringRepresentation.startsWith(SEPARATOR) ? stringRepresentation.substring(1) : stringRepresentation;
      Object[] el = toMatch.split("/");
      return new Fqn(new ImmutableListCopy(el), true);
   }

   /**
    * Retrieves an Fqn read from an object input stream, typically written to using {@link #writeExternal(java.io.ObjectOutput)}.
    *
    * @param in input stream
    * @return an Fqn
    * @throws IOException            in the event of a problem reading the stream
    * @throws ClassNotFoundException in the event of classes that comprise the element list of this Fqn not being found
    * @since 2.2.0
    */
   public static Fqn<Object> fromExternalStream(ObjectInput in) throws IOException, ClassNotFoundException
   {
      Fqn<Object> f = new Fqn<Object>(true);
      f.readExternal(in);
      return f;
   }


   /**
    * Obtains an ancestor of the current Fqn.  Literally performs <code>elements.subList(0, generation)</code>
    * such that if
    * <code>
    * generation == Fqn.size()
    * </code>
    * then the return value is the Fqn itself (current generation), and if
    * <code>
    * generation == Fqn.size() - 1
    * </code>
    * then the return value is the same as
    * <code>
    * Fqn.getParent()
    * </code>
    * i.e., just one generation behind the current generation.
    * <code>
    * generation == 0
    * </code>
    * would return Fqn.ROOT.
    *
    * @param generation the generation of the ancestor to retrieve
    * @return an ancestor of the current Fqn
    */
   public Fqn<E> getAncestor(int generation)
   {
      if (generation == 0) return root();
      return getSubFqn(0, generation);
   }

   /**
    * Obtains a sub-Fqn from the given Fqn.  Literally performs <code>elements.subList(startIndex, endIndex)</code>
    */
   public Fqn<E> getSubFqn(int startIndex, int endIndex)
   {
      List el = elements.subList(startIndex, endIndex);
      return new Fqn<E>(true, el, true);
   }

   /**
    * @return the number of elements in the Fqn.  The root node contains zero.
    */
   public int size()
   {
      return size;
   }

   /**
    * @param n index of the element to return
    * @return Returns the nth element in the Fqn.
    */
   public E get(int n)
   {
      return elements.get(n);
   }

   /**
    * @return the last element in the Fqn.
    * @see #getLastElementAsString
    */
   public E getLastElement()
   {
      if (isRoot()) return null;
      return elements.get(size - 1);
   }

   /**
    * @param element element to find
    * @return true if the Fqn contains this element, false otherwise.
    */
   public boolean hasElement(E element)
   {
      return elements.indexOf(element) != -1;
   }

   /**
    * Clones the Fqn.
    */
   @Override
   @SuppressWarnings("unchecked")
   public Fqn<E> clone() throws CloneNotSupportedException
   {
      try
      {
         return (Fqn<E>) super.clone();
      }
      catch (CloneNotSupportedException e)
      {
         log.error("Unable to clone Fqn " + this, e);
         return null;
      }
   }

   /**
    * Returns true if obj is a Fqn with the same elements.
    */
   @Override
   public boolean equals(Object obj)
   {
      if (this == obj)
      {
         return true;
      }
      if (!(obj instanceof Fqn))
      {
         return false;
      }
      Fqn<?> other = (Fqn<?>) obj;
      return size == other.size() && elements.equals(other.elements);
   }

   /**
    * Returns a hash code with Fqn elements.
    */
   @Override
   public int hashCode()
   {
      if (hash_code == 0)
      {
         hash_code = calculateHashCode();
      }
      return hash_code;
   }

   /**
    * Returns this Fqn as a string, prefixing the first element with a {@link Fqn#SEPARATOR} and
    * joining each subsequent element with a {@link Fqn#SEPARATOR}.
    * If this is the root Fqn, returns {@link Fqn#SEPARATOR}.
    * Example:
    * <pre>
    * new Fqn(new Object[] { "a", "b", "c" }).toString(); // "/a/b/c"
    * Fqn.ROOT.toString(); // "/"
    * </pre>
    */
   @Override
   public String toString()
   {
      if (stringRepresentation == null)
      {
         stringRepresentation = getStringRepresentation(elements);
      }
      return stringRepresentation;
   }

   public void writeExternal(ObjectOutput out) throws IOException
   {
      out.writeShort(size);
      for (E element : elements)
      {
         out.writeObject(element);
      }
   }

   @SuppressWarnings("unchecked")
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
   {
      size = in.readShort();
      this.elements = new ArrayList<E>(size);
      for (int i = 0; i < size; i++)
      {
         E e = (E) in.readObject();
         elements.add(e);
      }
   }


   /**
    * Returns true if this Fqn is child of parentFqn.
    * Example usage:
    * <pre>
    * Fqn<String> f1 = Fqn.fromString("/a/b");
    * Fqn<String> f2 = Fqn.fromString("/a/b/c");
    * assertTrue(f1.isChildOf(f2));
    * assertFalse(f1.isChildOf(f1));
    * assertFalse(f2.isChildOf(f1));
    * </pre>
    *
    * @param parentFqn candidate parent to test against
    * @return true if the target is a child of parentFqn
    */
   public boolean isChildOf(Fqn<E> parentFqn)
   {
      return parentFqn.size() != size && isChildOrEquals(parentFqn);
   }

   /**
    * Returns true if this Fqn is a <i>direct</i> child of a given Fqn.
    *
    * @param parentFqn parentFqn to compare with
    * @return true if this is a direct child, false otherwise.
    */
   public boolean isDirectChildOf(Fqn parentFqn)
   {
      return size == parentFqn.size() + 1 && isChildOf(parentFqn);
   }

   /**
    * Returns true if this Fqn is equals or the child of parentFqn.
    * Example usage:
    * <pre>
    * Fqn<String> f1 = Fqn.fromString("/a/b");
    * Fqn<String> f2 = Fqn.fromString("/a/b/c");
    * assertTrue(f1.isChildOrEquals(f2));
    * assertTrue(f1.isChildOrEquals(f1));
    * assertFalse(f2.isChildOrEquals(f1));
    * </pre>
    *
    * @param parentFqn candidate parent to test against
    * @return true if this Fqn is equals or the child of parentFqn.
    */
   public boolean isChildOrEquals(Fqn<E> parentFqn)
   {
      List<E> parentList = parentFqn.elements;
      if (parentList.size() > size)
      {
         return false;
      }
      for (int i = parentList.size() - 1; i >= 0; i--)
      {
         if (!parentList.get(i).equals(elements.get(i)))
         {
            return false;
         }
      }
      return true;
   }

   /**
    * Calculates a hash code by summing the hash code of all elements.
    *
    * @return a cached hashcode
    */
   protected int calculateHashCode()
   {
      int hashCode = 0;
      int count = 1;
      Object o;
      for (E element : elements)
      {
         o = element;
         hashCode += (o == null) ? 0 : o.hashCode() * count++;
      }
      if (hashCode == 0)// fix degenerate case
      {
         hashCode = 0xFEED;
      }
      return hashCode;
   }

   protected String getStringRepresentation(List elements)
   {
      StringBuilder builder = new StringBuilder();
      for (Object e : elements)
      {
         // incase user element 'e' does not implement equals() properly, don't rely on their implementation.
         if (!SEPARATOR.equals(e) && !"".equals(e))
         {
            builder.append(SEPARATOR);
            builder.append(e);
         }
      }
      return builder.length() == 0 ? SEPARATOR : builder.toString();
   }


   /**
    * Returns the parent of this Fqn.
    * The parent of the root node is {@link #ROOT}.
    * Examples:
    * <pre>
    * Fqn<String> f1 = Fqn.fromString("/a");
    * Fqn<String> f2 = Fqn.fromString("/a/b");
    * assertEquals(f1, f2.getParent());
    * assertEquals(Fqn.ROOT, f1.getParent().getParent());
    * assertEquals(Fqn.ROOT, Fqn.ROOT.getParent());
    * </pre>
    *
    * @return the parent Fqn
    */
   public Fqn<E> getParent()
   {
      switch (size)
      {
         case 0:
         case 1:
            return root();
         default:
            return new Fqn<E>(true, elements.subList(0, size - 1), true);
      }
   }

   @SuppressWarnings("unchecked")
   public static <T> Fqn<T> root()
   {
      return ROOT;
   }

   /**
    * Returns true if this is a root Fqn.
    *
    * @return true if the Fqn is Fqn.ROOT.
    */
   public boolean isRoot()
   {
      return size == 0;
   }

   /**
    * If this is the root, returns {@link Fqn#SEPARATOR}.
    *
    * @return a String representation of the last element that makes up this Fqn.
    */
   public String getLastElementAsString()
   {
      if (isRoot())
      {
         return SEPARATOR;
      }
      else
      {
         Object last = getLastElement();
         if (last instanceof String)
            return (String) last;
         else
            return String.valueOf(getLastElement());
      }
   }

   /**
    * Peeks into the elements that build up this Fqn.  The list returned is
    * read-only, to maintain the immutable nature of Fqn.
    *
    * @return an unmodifiable list
    */
   public List<E> peekElements()
   {
      return elements;
   }

   /**
    * Compares this Fqn to another using {@link FqnComparator}.
    */
   public int compareTo(Fqn<?> Fqn)
   {
      return FqnComparator.INSTANCE.compare(this, Fqn);
   }
}