/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, TeleStax Inc. and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* JBoss, Home of Professional Open Source
* Copyright 2007-2011, Red Hat, Inc. 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.jdiameter.api;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.UnknownServiceException;
/**
* This class handles Diameter URIs.
* It only implements parts that are needed for the Diameter URI
*
* @author erick.svenson@yahoo.com
* @author <a href="mailto:brainslog@gmail.com"> Alexandre Mendonca </a>
* @author <a href="mailto:baranowb@gmail.com"> Bartosz Baranowski </a>
* @author Yulian Oifa
* @version 1.5.1 Final
*/
public final class URI implements Comparable, Serializable {
private static final long serialVersionUID = 1L;
private static final String FIELD_PROTOCOL = "protocol=";
private static final String FIELD_TRANSPORT = "transport=";
private static final String SCHEME_SEPARATOR = "://";
private static final String PARAMS_SEPARATOR = ";";
private static final String DEFAULT_SCHEME = "aaa";
private static final int DEFAULT_PORT = 3868;
private String scheme;
private String host;
private int port = -1;
private String path = "";
/**
* Constructor with string parameter
* @param uri String representation of URI
* @throws URISyntaxException which signals that URI has syntax error
* @throws UnknownServiceException which signals that URI has incorrect scheme
*/
public URI(String uri) throws URISyntaxException, UnknownServiceException {
parse(uri);
if (getFQDN() == null || getFQDN().trim().length() == 0) {
throw new URISyntaxException(uri, "Host not found");
}
if (!getScheme().equals("aaa") && !getScheme().equals("aaas")) {
throw new UnknownServiceException(new StringBuilder().append("Unsupported service: ").append(getScheme()).toString());
}
}
/**
* @return scheme for URI
*/
public String getScheme() {
return scheme;
}
/**
* @return host name of URI
*/
public String getFQDN() {
return host;
}
/**
* Returns the port number of this URI, or -1 if this is not set.
* @return the port number of this URI
*/
public int getPort() {
return port == -1 ? DEFAULT_PORT : port;
}
/**
* @return true if this URI is secure
*/
public boolean isSecure() {
return getScheme().endsWith("s");
}
/**
* @return path of this URI
*/
public String getPath() {
return path;
}
/**
* @return protocol parameter of this URI
*/
public String getProtocolParam() {
String[] args = getPath().split(PARAMS_SEPARATOR);
for (String arg : args) {
if (arg.startsWith(FIELD_PROTOCOL)) {
return arg.substring(FIELD_PROTOCOL.length());
}
}
return null;
}
/**
* @return transport parameter of this URI
*/
public String getTransportParam() {
String[] args = getPath().split(PARAMS_SEPARATOR);
for (String arg : args) {
if (arg.startsWith(FIELD_TRANSPORT)) {
return arg.substring(FIELD_TRANSPORT.length());
}
}
return null;
}
/**
* @return String representation of this URI in RFC 3588 format
*/
@Override
public String toString() {
StringBuffer rc = new StringBuffer(scheme).append(SCHEME_SEPARATOR).append(host);
if (port != -1) {
rc.append(":").append(port);
}
if (path != null && path.length() > 0) {
rc.append(PARAMS_SEPARATOR).append(path);
}
return rc.toString();
}
private void parse(String uri) throws URISyntaxException {
try {
int schemeStartIndex = uri.indexOf(SCHEME_SEPARATOR);
int schemeEndIndex = 0;
if (schemeStartIndex == -1) {
scheme = DEFAULT_SCHEME;
}
else {
scheme = uri.substring(0, schemeStartIndex);
schemeEndIndex = schemeStartIndex + 3;
schemeStartIndex = uri.indexOf(';', schemeEndIndex);
}
if (schemeStartIndex == -1) {
host = uri.substring(schemeEndIndex);
}
else {
host = uri.substring(schemeEndIndex, schemeStartIndex);
}
int sepIndex = host.indexOf(':');
if (sepIndex != -1) {
port = Integer.parseInt(host.substring(sepIndex + 1));
host = host.substring(0, sepIndex);
}
if (schemeStartIndex != -1) {
path = uri.substring(schemeStartIndex + 1);
}
}
catch (Exception e) {
throw new URISyntaxException(uri, "URI has incorrect format");
}
}
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The <code>equals</code> method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* <code>x</code>, <code>x.equals(x)</code> should return
* <code>true</code>.
* <li>It is <i>symmetric</i>: for any non-null reference values
* <code>x</code> and <code>y</code>, <code>x.equals(y)</code>
* should return <code>true</code> if and only if
* <code>y.equals(x)</code> returns <code>true</code>.
* <li>It is <i>transitive</i>: for any non-null reference values
* <code>x</code>, <code>y</code>, and <code>z</code>, if
* <code>x.equals(y)</code> returns <code>true</code> and
* <code>y.equals(z)</code> returns <code>true</code>, then
* <code>x.equals(z)</code> should return <code>true</code>.
* <li>It is <i>consistent</i>: for any non-null reference values
* <code>x</code> and <code>y</code>, multiple invocations of
* <tt>x.equals(y)</tt> consistently return <code>true</code>
* or consistently return <code>false</code>, provided no
* information used in <code>equals</code> comparisons on the
* objects is modified.
* <li>For any non-null reference value <code>x</code>,
* <code>x.equals(null)</code> should return <code>false</code>.
* </ul>
* <p>
* The <tt>equals</tt> method for class <code>Object</code> implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values <code>x</code> and
* <code>y</code>, this method returns <code>true</code> if and only
* if <code>x</code> and <code>y</code> refer to the same object
* (<code>x == y</code> has the value <code>true</code>).
* <p>
* Note that it is generally necessary to override the <tt>hashCode</tt>
* method whenever this method is overridden, so as to maintain the
* general contract for the <tt>hashCode</tt> method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return <code>true</code> if this object is the same as the obj
* argument; <code>false</code> otherwise.
* @see #hashCode()
* @see java.util.Hashtable
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
URI that = (URI) obj;
return getPort() == that.getPort() && !(host != null ?
!host.equals(that.host) : that.host != null) &&
!(path != null ? !path.equals(that.path) : that.path != null) &&
!(scheme != null ? !scheme.equals(that.scheme) : that.scheme != null);
}
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hashtables such as those provided by
* <code>java.util.Hashtable</code>.
* <p>
* The general contract of <code>hashCode</code> is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the <tt>equals(Object)</tt>
* method, then calling the <code>hashCode</code> method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link java.lang.Object#equals(java.lang.Object)}
* method, then calling the <tt>hashCode</tt> method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hashtables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class <tt>Object</tt> does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java<font size="-2"><sup>TM</sup></font> programming language.)
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.util.Hashtable
*/
@Override
public int hashCode() {
int result;
result = (scheme != null ? scheme.hashCode() : 0);
result = 31 * result + (host != null ? host.hashCode() : 0);
result = 31 * result + getPort();
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
*
* In the foregoing description, the notation
* <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
* <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
* <tt>0</tt>, or <tt>1</tt> according to whether the value of <i>expression</i>
* is negative, zero or positive.
*
* The implementor must ensure <tt>sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>x.compareTo(y)</tt> must throw an exception iff
* <tt>y.compareTo(x)</tt> throws an exception.)<p>
*
* The implementor must also ensure that the relation is transitive:
* <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
* <tt>x.compareTo(z)>0</tt>.<p>
*
* Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
* implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
* all <tt>z</tt>.<p>
*
* It is strongly recommended, but <i>not</i> strictly required that
* <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
* class that implements the <tt>Comparable</tt> interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* @param obj the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this Object.
*/
@Override
public int compareTo(Object obj) {
if (obj instanceof URI) {
return this.toString().compareTo(obj.toString());
}
else {
return -1;
}
}
}