Version.java

/* This software was designed and created by Jason Carroll.
 * Copyright (c) 2002, 2003, 2004 Jason Carroll.
 * The author can be reached at jcarroll@cowsultants.com
 * ITracker website: http://www.cowsultants.com
 * ITracker forums: http://www.cowsultants.com/phpBB/index.php
 *
 * This program is free software; you can redistribute it and/or modify
 * it only under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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.
 */
package org.itracker.model;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

import java.io.Serializable;
import java.util.Comparator;

/**
 * A Project version.
 * <p/>
 * <p>
 * A Version can only belong to 1 Project (composition).
 * </p>
 *
 * @author ready
 */
public class Version extends AbstractEntity implements Comparable<Entity> {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    /**
     * Invariant: never <tt>null</tt>.
     */
    private Project project;

    /**
     * Invariant: never <tt>null</tt>.
     */
    private String number;

    /*
      * major and minor are only used to compare Versions. They can be computed
      * from the <code>number</code> attribute, PENDING: We should also allow
      * to specify them separately in order not to impose any constraint on the
      * format of the version number.
      */
    private int major;
    private int minor;

    private String description;

    /**
     * Version status.
     * <p>
     * Invariant: never <tt>null</tt>.
     * </p>
     */
    private Status status;

    /*
      * This class used to have a <code>issues</code> attribute, which was a
      * Collection<Issue>. This has been removed because the association Version -
      * Issue doesn't need to be navigatable in this direction.
      */

    public static final Comparator<Version> VERSION_COMPARATOR = new VersionComparator();

    /**
     * Default constructor (required by Hibernate).
     * <p/>
     * <p>
     * PENDING: should be <code>private</code> so that it can only be used by
     * Hibernate, to ensure that <code>project</code> and <code>number</code>,
     * which form an instance's identity, are never <tt>null</tt>.
     * </p>
     */
    public Version() {
    }

    /**
     * Creates a new active Version for the given Project.
     *
     * @param project project to which this version belongs
     * @param number  unique within the project
     */
    public Version(Project project, String number) {
        setProject(project);
        setVersionInfo(number);

        // A new version is active by default.
        this.status = Status.ACTIVE;
    }

    public int getMajor() {
        return major;
    }

    public void setMajor(int getMajor) {
        this.major = getMajor;
    }

    public int getMinor() {
        return minor;
    }

    public void setMinor(int getMinor) {
        this.minor = getMinor;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String getNumber) {
        this.number = getNumber;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String getDescription) {
        this.description = getDescription;
    }

    public Project getProject() {
        return project;
    }

    public void setProject(Project project) {
        if (project == null) {
            throw new IllegalArgumentException("null project");
        }
        this.project = project;
    }

    /**
     * Returns this version's status.
     *
     * @return enum constant
     */
    public Status getStatus() {
        return status;
    }

    /**
     * Sets this version's status.
     *
     * @param status enum constant
     * @throws IllegalArgumentException <code>status</code> is <tt>null</tt>
     */
    public void setStatus(Status status) {
        if (status == null) {
            throw new IllegalArgumentException("null status");
        }
        this.status = status;
    }

    /**
     * Convience method to set the number, major and minor fields with a single
     * call. It will first set the number to the provided data, and then attempt
     * to parse the info if in the form major.minor into parts to set the other
     * information.
     *
     * @param versionInfo the version number string to use
     */
    public void setVersionInfo(String versionInfo) {
        setNumber(versionInfo);

        if (null == versionInfo) {
            throw new IllegalArgumentException("version info must not be null.");
        }
        String versionNumber = this.number.trim();
        int firstDot = versionNumber.indexOf('.');
        String major = "0";
        major = (firstDot > 0 ? versionNumber.substring(0, firstDot).trim()
                : versionNumber.trim());

        try {
            setMajor(Integer.parseInt(major));
        } catch (NumberFormatException ex) {
            setMajor(0);
        }

        int secondDot = (firstDot > -1 ? versionNumber.indexOf('.',
                firstDot + 1) : -1);
        String minor = (secondDot > -1 ? versionNumber.substring(firstDot + 1,
                secondDot).trim() : versionNumber.substring(firstDot + 1)
                .trim());
        try {
            setMinor(Integer.parseInt(minor));
        } catch (NumberFormatException ex) {
            setMinor(0);
        }
    }

    /**
     * @return <tt>Version [id=<id>, project=<project>, number=<number>]</tt>
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this).append("id", getId()).append("number",
                getNumber()).append("project", getProject()).append(getMajor()).append(getMinor())
                .append("status", getStatus()).toString();
    }

    /**
     * Compares 2 Versions by major and minor number.
     */
    public static final class VersionComparator implements Comparator<Version>, Serializable {
        /**
         *
         */
        private static final long serialVersionUID = 1L;

        private boolean ascending = true;

        public VersionComparator() {
        }

        public VersionComparator(boolean ascending) {
            setAscending(ascending);
        }

        private boolean isAscending() {
            return ascending;
        }

        private void setAscending(boolean ascending) {
            this.ascending = ascending;
        }

        public int compare(Version a, Version b) {
            int result = new CompareToBuilder().append(a.getNumber(), b.getNumber())
                    .append(a.getMajor(), b.getMajor()).append(a.getMinor(), b.getMinor()).toComparison();

            return (isAscending() ? result : -result);
        }

    }

}