/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.profileservice.spi.repository;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * OSGI version impl for now.
 * 
 * Version identifier for bundles and packages.
 * 
 * <p>
 * Version identifiers have four components.
 * <ol>
 * <li>Major version. A non-negative integer.</li>
 * <li>Minor version. A non-negative integer.</li>
 * <li>Micro version. A non-negative integer.</li>
 * <li>Qualifier. A text string. See <code>Version(String)</code> for the
 * format of the qualifier string.</li>
 * </ol>
 * 
 * <p>
 * <code>Version</code> objects are immutable.
 * 
 * @version $Revision: 65457 $
 * @since 1.3
 * @author Scott.Stark@jboss.org
 * @author Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
 */
public class Version implements Comparable
{
   private final int major;

   private final int minor;

   private final int micro;

   private final String qualifier;

   private static final String SEPARATOR = "."; //$NON-NLS-1$

   /**
    * The empty version "0.0.0". Equivalent to calling
    * <code>new Version(0,0,0)</code>.
    */
   public static final Version emptyVersion = new Version(0, 0, 0);

   /**
    * Creates a version identifier from the specified numerical components. *
    * <p>
    * The qualifier is set to the empty string.
    * 
    * @param major
    *           Major component of the version identifier.
    * @param minor
    *           Minor component of the version identifier.
    * @param micro
    *           Micro component of the version identifier.
    * @throws IllegalArgumentException
    *            If the numerical components are negative.
    */
   public Version(int major, int minor, int micro)
   {
      this(major, minor, micro, null);
   }

   /**
    * Creates a version identifier from the specifed components.
    * 
    * @param major
    *           Major component of the version identifier.
    * @param minor
    *           Minor component of the version identifier.
    * @param micro
    *           Micro component of the version identifier.
    * @param qualifier
    *           Qualifier component of the version identifier. If
    *           <code>null</code> is specified, then the qualifier will be set *
    *           to the empty string.
    * @throws IllegalArgumentException
    *            If the numerical components are negative or the qualifier
    *            string is invalid.
    */
   public Version(int major, int minor, int micro, String qualifier)
   {
      if (qualifier == null)
      {
         qualifier = ""; //$NON-NLS-1$
      }

      this.major = major;
      this.minor = minor;
      this.micro = micro;
      this.qualifier = qualifier;
      validate();
   }

   /**
    * Created a version identifier from the specified string.
    * 
    * <p>
    * Here is the grammar for version strings.
    * 
    * <pre>
    * version ::= major('.'minor('.'micro('.'qualifier)?)?)?
    * major ::= digit+
    * minor ::= digit+
    * micro ::= digit+
    * qualifier ::= (alpha|digit|'_'|'-')+
    * digit ::= [0..9]
    * alpha ::= [a..zA..Z]
    * </pre>
    * 
    * There must be no whitespace in version.
    * 
    * @param version
    *           String representation of the version identifier.
    * @throws IllegalArgumentException
    *            If <code>version</code> is improperly formatted.
    */
   public Version(String version)
   {
      int major = 0;
      int minor = 0;
      int micro = 0;
      String qualifier = ""; //$NON-NLS-1$

      try
      {
         StringTokenizer st = new StringTokenizer(version, SEPARATOR, true);
         major = Integer.parseInt(st.nextToken());

         if (st.hasMoreTokens())
         {
            st.nextToken(); // consume delimiter
            minor = Integer.parseInt(st.nextToken());

            if (st.hasMoreTokens())
            {
               st.nextToken(); // consume delimiter
               micro = Integer.parseInt(st.nextToken());

               if (st.hasMoreTokens())
               {
                  st.nextToken(); // consume delimiter
                  qualifier = st.nextToken();

                  if (st.hasMoreTokens())
                  {
                     throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
                  }
               }
            }
         }
      }
      catch (NoSuchElementException e)
      {
         throw new IllegalArgumentException("invalid format"); //$NON-NLS-1$
      }

      this.major = major;
      this.minor = minor;
      this.micro = micro;
      this.qualifier = qualifier;
      validate();
   }

   /**
    * Called by the Version constructors to validate the version components.
    * 
    * @throws IllegalArgumentException
    *            If the numerical components are negative or the qualifier
    *            string is invalid.
    */
   private void validate()
   {
      if (major < 0)
      {
         throw new IllegalArgumentException("negative major"); //$NON-NLS-1$
      }
      if (minor < 0)
      {
         throw new IllegalArgumentException("negative minor"); //$NON-NLS-1$
      }
      if (micro < 0)
      {
         throw new IllegalArgumentException("negative micro"); //$NON-NLS-1$
      }
      int length = qualifier.length();
      for (int i = 0; i < length; i++)
      {
         if ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-".indexOf(qualifier.charAt(i)) == -1) { //$NON-NLS-1$
            throw new IllegalArgumentException("invalid qualifier"); //$NON-NLS-1$
         }
      }
   }

   /**
    * Parses a version identifier from the specified string.
    * 
    * <p>
    * See <code>Version(String)</code> for the format of the version string.
    * 
    * @param version
    *           String representation of the version identifier. Leading and
    *           trailing whitespace will be ignored.
    * @return A <code>Version</code> object representing the version
    *         identifier. If <code>version</code> is <code>null</code> or
    *         the empty string then <code>emptyVersion</code> will be
    *         returned.
    * @throws IllegalArgumentException
    *            If <code>version</code> is improperly formatted.
    */
   public static Version parseVersion(String version)
   {
      if (version == null)
      {
         return emptyVersion;
      }

      version = version.trim();
      if (version.length() == 0)
      {
         return emptyVersion;
      }

      return new Version(version);
   }

   /**
    * Returns the major component of this version identifier.
    * 
    * @return The major component.
    */
   public int getMajor()
   {
      return major;
   }

   /**
    * Returns the minor component of this version identifier.
    * 
    * @return The minor component.
    */
   public int getMinor()
   {
      return minor;
   }

   /**
    * Returns the micro component of this version identifier.
    * 
    * @return The micro component.
    */
   public int getMicro()
   {
      return micro;
   }

   /**
    * Returns the qualifier component of this version identifier.
    * 
    * @return The qualifier component.
    */
   public String getQualifier()
   {
      return qualifier;
   }

   /**
    * Returns the string representation of this version identifier.
    * 
    * <p>
    * The format of the version string will be <code>major.minor.micro</code>
    * if qualifier is the empty string or
    * <code>major.minor.micro.qualifier</code> otherwise.
    * 
    * @return The string representation of this version identifier.
    */
   public String toString()
   {
      String base = major + SEPARATOR + minor + SEPARATOR + micro;
      if (qualifier.length() == 0)
      { //$NON-NLS-1$
         return base;
      }
      else
      {
         return base + SEPARATOR + qualifier;
      }
   }

   /**
    * Returns a hash code value for the object.
    * 
    * @return An integer which is a hash code value for this object.
    */
   public int hashCode()
   {
      return (major << 24) + (minor << 16) + (micro << 8)
            + qualifier.hashCode();
   }

   /**
    * Compares this <code>Version</code> object to another object.
    * 
    * <p>
    * A version is considered to be <b>equal to </b> another version if the *
    * major, minor and micro components are equal and the qualifier component is
    * equal (using <code>String.equals</code>).
    * 
    * @param object
    *           The <code>Version</code> object to be compared.
    * @return <code>true</code> if <code>object</code> is a
    *         <code>Version</code> and is equal to this object;
    *         <code>false</code> otherwise.
    */
   public boolean equals(Object object)
   {
      if (object == this)
      { // quicktest
         return true;
      }

      if (!(object instanceof Version))
      {
         return false;
      }

      Version other = (Version) object;
      return (major == other.major) && (minor == other.minor)
            && (micro == other.micro) && qualifier.equals(other.qualifier);
   }

   /**
    * Compares this <code>Version</code> object to another object.
    * 
    * <p>
    * A version is considered to be <b>less than </b> another version if its
    * major component is less than the other version's major component, or the
    * major components are equal and its minor component is less than the other
    * version's minor component, or the major and minor components are equal and
    * its micro component is less than the other version's micro component, or
    * the major, minor and micro components are equal and it's qualifier *
    * component is less than the other version's qualifier component (using *
    * <code>String.compareTo</code>).
    * 
    * <p>
    * A version is considered to be <b>equal to</b> another version if the
    * major, minor and micro components are equal and the qualifier component is
    * equal (using <code>String.compareTo</code>).
    * 
    * @param object
    *           The <code>Version</code> object to be compared.
    * @return A negative integer, zero, or a positive integer if this object is
    *         less than, equal to, or greater than the specified
    *         <code>Version</code> object.
    * @throws ClassCastException
    *            If the specified object is not a <code>Version</code>.
    */
   public int compareTo(Object object)
   {
      if (object == this)
      { // quicktest
         return 0;
      }

      Version other = (Version) object;

      int result = major - other.major;
      if (result != 0)
      {
         return result;
      }

      result = minor - other.minor;
      if (result != 0)
      {
         return result;
      }

      result = micro - other.micro;
      if (result != 0)
      {
         return result;
      }

      return qualifier.compareTo(other.qualifier);
   }
}
