/*
 * @(#)PortRange.java
 *
 * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistribution of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 * 
 *   2. Redistribution in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use in
 * the design, construction, operation or maintenance of any nuclear facility.
 */

package org.wso2.balana.attr;

/**
 * This class represents a port range as specified in the XACML 2.0 description of
 * <code>dnsName</code> and <code>ipAddress</code>. The range may have upper and lower bounds, be
 * specified by a single port number, or may be unbound.
 * 
 * @since 2.0
 * @author Seth Proctor
 */
public class PortRange {

    /**
     * Constant used to specify that the range is unbound on one side.
     */
    public static final int UNBOUND = -1;

    // the port bound values
    private int lowerBound;
    private int upperBound;

    /**
     * Default constructor used to represent an unbound range. This is typically used when an
     * address has no port information.
     */
    public PortRange() {
        this(UNBOUND, UNBOUND);
    }

    /**
     * Creates a <code>PortRange</code> that represents a single port value instead of a range of
     * values.
     * 
     * @param singlePort the single port number
     */
    public PortRange(int singlePort) {
        this(singlePort, singlePort);
    }

    /**
     * Creates a <code>PortRange</code> with upper and lower bounds. Either of the parameters may
     * have the value <code>UNBOUND</code> meaning that there is no bound at the respective end.
     * 
     * @param lowerBound the lower-bound port number or <code>UNBOUND</code>
     * @param upperBound the upper-bound port number or <code>UNBOUND</code>
     */
    public PortRange(int lowerBound, int upperBound) {
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
    }

    /**
     * Creates an instance of <code>PortRange</code> based on the given value.
     * 
     * @param value a <code>String</code> representing the range
     * 
     * @return a new <code>PortRange</code>
     * 
     * @throws NumberFormatException if a port value isn't an integer
     */
    public static PortRange getInstance(String value) {
        int lowerBound = UNBOUND;
        int upperBound = UNBOUND;

        // first off, make sure there's actually content here
        if ((value.length() == 0) || (value.equals("-")))
            return new PortRange();

        // there's content, so figure where the '-' is, if at all
        int dashPos = value.indexOf('-');

        if (dashPos == -1) {
            // there's no dash, so it's just a single number
            lowerBound = upperBound = Integer.parseInt(value);
        } else if (dashPos == 0) {
            // it starts with a dash, so it's just upper-range bound
            upperBound = Integer.parseInt(value.substring(1));
        } else {
            // it's a number followed by a dash, so get the lower-bound...
            lowerBound = Integer.parseInt(value.substring(0, dashPos));
            int len = value.length();

            // ... and see if there is a second port number
            if (dashPos != (len - 1)) {
                // the dash wasn't at the end, so there's an upper-bound
                upperBound = Integer.parseInt(value.substring(dashPos + 1, len));
            }
        }

        return new PortRange(lowerBound, upperBound);
    }

    /**
     * Returns the lower-bound port value. If the range is not lower-bound, then this returns
     * <code>UNBOUND</code>. If the range is actually a single port number, then this returns the
     * same value as <code>getUpperBound</code>.
     * 
     * @return the upper-bound
     */
    public int getLowerBound() {
        return lowerBound;
    }

    /**
     * Returns the upper-bound port value. If the range is not upper-bound, then this returns
     * <code>UNBOUND</code>. If the range is actually a single port number, then this returns the
     * same value as <code>getLowerBound</code>.
     * 
     * @return the upper-bound
     */
    public int getUpperBound() {
        return upperBound;
    }

    /**
     * Returns whether the range is bounded by a lower port number.
     * 
     * @return true if lower-bounded, false otherwise
     */
    public boolean isLowerBounded() {
        return (lowerBound != -1);
    }

    /**
     * Returns whether the range is bounded by an upper port number.
     * 
     * @return true if upper-bounded, false otherwise
     */
    public boolean isUpperBounded() {
        return (upperBound != -1);
    }

    /**
     * Returns whether the range is actually a single port number.
     * 
     * @return true if the range is a single port number, false otherwise
     */
    public boolean isSinglePort() {
        return ((lowerBound == upperBound) && (lowerBound != UNBOUND));
    }

    /**
     * Returns whether the range is unbound, which means that it specifies no port number or range.
     * This is typically used with addresses that include no port information.
     * 
     * @return true if the range is unbound, false otherwise
     */
    public boolean isUnbound() {
        return ((lowerBound == UNBOUND) && (upperBound == UNBOUND));
    }

    /**
     * Returns true if the input is an instance of this class and if its value equals the value
     * contained in this class.
     * 
     * @param o the object to compare
     * 
     * @return true if this object and the input represent the same value
     */
    public boolean equals(Object o) {
        if (!(o instanceof PortRange))
            return false;

        PortRange other = (PortRange) o;

        if (lowerBound != other.lowerBound)
            return false;

        if (upperBound != other.upperBound)
            return false;

        return true;
    }

    @Override
    public int hashCode() {
        
        int result = lowerBound;
        result = 31 * result + upperBound;
        return result;
        
    }

    /**
     *
     */
    public String encode() {
        if (isUnbound())
            return "";

        if (isSinglePort())
            return String.valueOf(lowerBound);

        if (!isLowerBounded())
            return "-" + String.valueOf(upperBound);

        if (!isUpperBounded())
            return String.valueOf(lowerBound) + "-";

        return String.valueOf(lowerBound) + "-" + String.valueOf(upperBound);
    }

}
