package org.marketcetera.marketdata; import java.net.URI; import java.net.URISyntaxException; import java.util.regex.Pattern; import org.marketcetera.util.log.I18NBoundMessage1P; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * Creates URIs for a given <a href="http://en.wikipedia.org/wiki/URI_scheme">scheme</a>. * * <p>This class may be used to create URIs for a particular URI scheme. Construct an instance * for a particular URI scheme (like "http" as in "http://"). Thereafter, use that instance to * construct syntactically valid URIs for that scheme ("http://www.marketcetera.com"). For example, * <pre> * UriScheme httpScheme = new UriScheme("http"); * String marketceteraUri = httpScheme.composeUriString("www.marketcetera.com"); * System.out.println(marketceteraUri); * </pre> * yields * <pre> * http://www.marketcetera.com * </pre> * * @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a> * @version $Id: UriScheme.java 16154 2012-07-14 16:34:05Z colin $ * @since 2.0.0 */ @ClassVersion("$Id: UriScheme.java 16154 2012-07-14 16:34:05Z colin $") public class UriScheme implements Messages { /** * Create a new UriScheme instance. * * <p>Create a new scheme as defined by <a href="http://rfc.net/std0066.html">Internet Standard 66</a> and * <a href="http://tools.ietf.org/html/rfc3986">RFC 3986</a>. The given <code>inSchemeName</code> may be * composed of any combination of letters, digits, the plus character("+"), period("."), and hyphen("-"). * * @param inSchemeName a <code>String</code> value containing the name of the <code>UriScheme</code> * @throws IllegalArgumentException if the given scheme name is not valid as defined above */ public UriScheme(String inSchemeName) { if(inSchemeName.isEmpty() || !SCHEME_PATTERN.matcher(inSchemeName).matches()) { throw new IllegalArgumentException(new I18NBoundMessage1P(INVALID_SCHEME_NAME, inSchemeName).getText()); } scheme = inSchemeName; fullScheme = String.format("%s://", //$NON-NLS-1$ scheme); } /** * Gets the scheme. * * @return a <code>String</code> value */ public String getScheme() { return scheme; } /** * Gets the full scheme. * * @return a <code>String</code> value */ public String getFullScheme() { return fullScheme; } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((scheme == null) ? 0 : scheme.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; UriScheme other = (UriScheme) obj; if (scheme == null) { if (other.scheme != null) return false; } else if (!scheme.equals(other.scheme)) return false; return true; } /** * Creates a URI composed of the given host and port and this object's {@link #scheme}. * * <P>The contents of an internet URI are regulated by <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. * This method does not enforce full compliance with this RFC because hostnames might not be internet hostnames * but may be local network hostnames instead. Different standards apply to non-internet hostnames and are difficult * to deterministically predict. * * @param inHostname a <code>String</code> value * @param inPort an <code>int</code> value * @return a <code>String</code> value containing a conforming URI * @throws URISyntaxException if the given URI is not syntactically valid * @throws IllegalArgumentException if the host or port is not valid */ public String composeUriString(String inHostname, int inPort) { validateHost(inHostname); validatePort(inPort); try { return makeUri(getFullScheme(), inHostname, inPort).toString(); } catch (URISyntaxException e) { throw new IllegalArgumentException(INVALID_HOSTNAME.getText(inHostname)); } } /** * Creates a URI composed of the given host and port and this object's {@link #scheme}. * * <P>The contents of an internet URI are regulated by <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>. * This method does not enforce full compliance with this RFC because hostnames might not be internet hostnames * but may be local network hostnames instead. Different standards apply to non-internet hostnames and are difficult * to deterministically predict. * * @param inHostname a <code>String</code> value * @return a <code>String</code> value containing a conforming URI * @throws IllegalArgumentException if the host is not valid */ public String composeUriString(String inHostname) { validateHost(inHostname); try { return makeUri(getFullScheme(), inHostname, null).toString(); } catch (URISyntaxException e) { throw new IllegalArgumentException(INVALID_HOSTNAME.getText(inHostname)); } } /** * Extracts the hostname from the given URI string. * * <p>The URI must be syntactically valid and of this object's scheme. * * @param inUriString a <code>String</code> value * @return a <code>String</code> value * @throws IllegalArgumentException if the given URI is not syntactically valid, is syntactically valid but is not of * the correct scheme, or if the host is missing or invalid */ public String hostnameFromUri(String inUriString) { validate(inUriString); URI uri; try { uri = new URI(inUriString); return uri.getHost(); } catch (URISyntaxException e) { throw new IllegalArgumentException(new I18NBoundMessage1P(SCHEME_REQUIRED, getScheme()).getText()); } } /** * Extracts the port from the given URI string. * * <p>The URI must be syntactically valid and of this object's scheme. * * @param inUriString a <code>String</code> value * @return an <code>int</code> value * @throws IllegalArgumentException if the given URI is not syntactically valid, is syntactically valid but is not of * the correct scheme, or if the hostname or port is missing or invalid */ public int portFromUri(String inUriString) { validate(inUriString); URI uri; try { uri = new URI(inUriString); validatePort(uri.getPort()); return uri.getPort(); } catch (URISyntaxException e) { throw new IllegalArgumentException(new I18NBoundMessage1P(SCHEME_REQUIRED, getScheme()).getText()); } } /** * Validates the given argument to see if it contains a valid URI of this scheme. * * @param inUriString a <code>String</code> value containing a URI to be validated according to this object's scheme * @throws IllegalArgumentException if the given URI is not syntactically valid, is syntactically valid but is not of * the correct scheme, or if the host or port is invalid */ public void validate(String inUriString) { if(inUriString.isEmpty()) { throw new IllegalArgumentException(new I18NBoundMessage1P(SCHEME_REQUIRED, getScheme()).getText()); } URI uri; try { uri = new URI(inUriString); } catch (URISyntaxException e) { throw new IllegalArgumentException(new I18NBoundMessage1P(SCHEME_REQUIRED, getScheme()).getText()); } // a URI must have the correct scheme, host, and port, and must be otherwise a syntactically // valid URI according to the Java JVM if(uri.getScheme() == null || !uri.getScheme().equals(getScheme())) { throw new IllegalArgumentException(new I18NBoundMessage1P(SCHEME_REQUIRED, getScheme()).getText()); } validateHost(uri.getHost()); if(uri.getPort() != -1) { validatePort(uri.getPort()); } } /** * Constructs a <code>URI</code> object of the given pieces. * * @param inFullScheme a <code>String</code> value containing the full scheme * @param inHostname a <code>String</code> value containing the hostname * @param inPort an <code>Integer</code> value containing the port to use or <code>null</code> for no port specified * @return a <code>URI</code> value * @throws URISyntaxException if the URI cannot be constructed because it is syntactically invalid */ private static URI makeUri(String inFullScheme, String inHostname, Integer inPort) throws URISyntaxException { if(inPort == null) { return new URI(new StringBuilder().append(inFullScheme).append(inHostname).toString()); } else { return new URI(new StringBuilder().append(inFullScheme).append(inHostname).append(":").append(inPort).toString()); //$NON-NLS-1$ } } /** * Validates the given hostname value. * * @param inHostname a <code>String</code> value * @throws IllegalArgumentException if <code>inHostname</code> is not valid */ private static void validateHost(String inHostname) { if(inHostname == null) { throw new NullPointerException(INVALID_HOSTNAME.getText(inHostname)); } if(inHostname.isEmpty()) { throw new IllegalArgumentException(INVALID_HOSTNAME.getText(inHostname)); } // this is done to pick up extra validation done by the JVM try { makeUri("validator://", //$NON-NLS-1$ inHostname, null); } catch (URISyntaxException e) { throw new IllegalArgumentException(INVALID_HOSTNAME.getText(inHostname)); } } /** * Validates the given port value. * * @param inPort an <code>int</code> value * @throws IllegalArgumentException if <code>inPort</code> is not valid */ private void validatePort(int inPort) { if(inPort <= 0 || inPort >= 65536) { throw new IllegalArgumentException(PORT_REQUIRED.getText()); } } /** * the scheme value */ private final String scheme; /** * the full scheme value based on the {@link #scheme} */ private final String fullScheme; /** * the pattern used to validate scheme names */ private static final Pattern SCHEME_PATTERN = Pattern.compile("[a-zA-Z0-9+\\-\\.]*"); //$NON-NLS-1$ }