0001: /*
0002: * Copyright 2005-2007 Noelios Consulting.
0003: *
0004: * The contents of this file are subject to the terms of the Common Development
0005: * and Distribution License (the "License"). You may not use this file except in
0006: * compliance with the License.
0007: *
0008: * You can obtain a copy of the license at
0009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
0010: * language governing permissions and limitations under the License.
0011: *
0012: * When distributing Covered Code, include this CDDL HEADER in each file and
0013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
0014: * applicable, add the following below this CDDL HEADER, with the fields
0015: * enclosed by brackets "[]" replaced with your own identifying information:
0016: * Portions Copyright [yyyy] [name of copyright owner]
0017: */
0018:
0019: package org.restlet.data;
0020:
0021: import java.io.IOException;
0022: import java.io.UnsupportedEncodingException;
0023: import java.net.URLDecoder;
0024: import java.net.URLEncoder;
0025: import java.util.ArrayList;
0026: import java.util.List;
0027: import java.util.logging.Level;
0028: import java.util.logging.Logger;
0029:
0030: /**
0031: * Reference to a Uniform Resource Identifier (URI). Contrary to the
0032: * java.net.URI class, this interface represents mutable references. It strictly
0033: * conforms to the RFC 3986 specifying URIs and follow its naming conventions.<br/>
0034: *
0035: * <pre>
0036: * URI reference = absolute-reference | relative-reference
0037: *
0038: * absolute-reference = scheme ":" scheme-specific-part [ "#" fragment ]
0039: * scheme-specific-part = ( hierarchical-part [ "?" query ] ) | opaque-part
0040: * hierarchical-part = ( "//" authority path-abempty ) | path-absolute | path-rootless | path-empty
0041: * authority = [ user-info "@" ] host-domain [ ":" host-port ]
0042: *
0043: * relative-reference = relative-part [ "?" query ] [ "#" fragment ]
0044: * relative-part = ( "//" authority path-abempty ) | path-absolute | path-noscheme | path-empty
0045: *
0046: * path-abempty = begins with "/" or is empty
0047: * path-absolute = begins with "/" but not "//"
0048: * path-noscheme = begins with a non-colon segment
0049: * path-rootless = begins with a segment
0050: * path-empty = zero characters
0051: * </pre>
0052: *
0053: * <p>
0054: * Note that this class doesn't encode or decode the reserved characters. It
0055: * assumes that the URIs or the URI parts passed in are properly encoded using
0056: * the standard URI encoding mechanism. You can use the static "encode()" and
0057: * "decode()" methods for this purpose.
0058: * </p>
0059: * <p>
0060: * The fundamental point to underline is the difference between an URI
0061: * "reference" and an URI. Contrary to an URI (the target identifier of a REST
0062: * resource), an URI reference can be relative (with or without query and
0063: * fragment part). This relative URI reference can then be resolved against a
0064: * base reference via the getTargetRef() method which will return a new resolved
0065: * Reference instance, an absolute URI reference with no base reference and with
0066: * no dot-segments (the path segments "." and "..").
0067: * </p>
0068: * <p>
0069: * You can also apply the getTargetRef() method on absolute references in order
0070: * to solve the dot-segments. Note that applying the getRelativeRef() method on
0071: * an absolute reference returns the current reference relatively to a base
0072: * reference, if any, and solves the dot-segments.
0073: * </p>
0074: * <p>
0075: * The Reference stores its data as a single string, the one passed to the
0076: * constructor. This string can always be obtained using the toString() method.
0077: * A couple of integer indexes are maintained to improve the extraction time of
0078: * various reference properties (URI components).
0079: * </p>
0080: * <p>
0081: * When you modify a specific component of the URI reference, via the setPath()
0082: * method for example, the internal string is simply regenerated by updating
0083: * only the relevant part. We try as much as possible to protect the bytes given
0084: * to the Reference class instead of transparently parsing and normalizing the
0085: * URI data. Our idea is to protect encodings and special characters in all case
0086: * and reduce the memory size taken by this class while making Reference
0087: * instances mutable.
0088: * </p>
0089: * <p>
0090: * Because the base reference is only a property of the Reference ("baseRef").
0091: * When you use the "Reference(base, path)" constructor, it is equivalent to
0092: * doing:<br>
0093: * ref = new Reference(path);<br>
0094: * ref.setBaseRef(base);
0095: * </p>
0096: * <p>
0097: * The base ref is not automatically resolved or "merged" with the rest of the
0098: * reference information (the path here). For example, this let's you reuse a
0099: * single reference as the base of several relative references. If you modify
0100: * the base reference, all relative references are still accurate.
0101: * </p>
0102: *
0103: * @author Jerome Louvel (contact@noelios.com)
0104: * @see <a href="http://www.faqs.org/rfcs/rfc3986.html">RFC 3986</a>
0105: */
0106: public class Reference {
0107:
0108: /**
0109: * Decodes a given string using the standard URI encoding mechanism and the
0110: * UTF-8 character set.
0111: *
0112: * @param toDecode
0113: * The string to decode.
0114: * @return The decoded string.
0115: */
0116: public static String decode(String toDecode) {
0117: String result = null;
0118:
0119: try {
0120: result = URLDecoder.decode(toDecode, "UTF-8");
0121: } catch (UnsupportedEncodingException uee) {
0122: Logger
0123: .getLogger(Reference.class.getCanonicalName())
0124: .log(
0125: Level.WARNING,
0126: "Unable to decode the string with the UTF-8 character set.",
0127: uee);
0128: }
0129:
0130: return result;
0131: }
0132:
0133: /**
0134: * Encodes a given string using the standard URI encoding mechanism and the
0135: * UTF-8 character set.
0136: *
0137: * @param toEncode
0138: * The string to encode.
0139: * @return The encoded string.
0140: */
0141: public static String encode(String toEncode) {
0142: String result = null;
0143:
0144: try {
0145: result = URLEncoder.encode(toEncode, "UTF-8");
0146: } catch (UnsupportedEncodingException uee) {
0147: Logger
0148: .getLogger(Reference.class.getCanonicalName())
0149: .log(
0150: Level.WARNING,
0151: "Unable to encode the string with the UTF-8 character set.",
0152: uee);
0153: }
0154:
0155: return result;
0156: }
0157:
0158: /**
0159: * Encodes a given string using the standard URI encoding mechanism. If the
0160: * provided character set is null, the string is returned but not encoded.
0161: *
0162: * <em><strong>Note:</strong> The <a
0163: * href="http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
0164: * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
0165: * used. Not doing so may introduce incompatibilites.</em>
0166: *
0167: * @param toEncode
0168: * The string to encode.
0169: * @param characterSet
0170: * The supported character encoding.
0171: * @return The encoded string or null if the named character encoding is not
0172: * supported.
0173: */
0174: public static String encode(String toEncode,
0175: CharacterSet characterSet) {
0176: String result = null;
0177:
0178: try {
0179: result = (characterSet == null) ? toEncode : URLEncoder
0180: .encode(toEncode, characterSet.getName());
0181: } catch (UnsupportedEncodingException uee) {
0182: Logger
0183: .getLogger(Reference.class.getCanonicalName())
0184: .log(
0185: Level.WARNING,
0186: "Unable to encode the string with the UTF-8 character set.",
0187: uee);
0188: }
0189:
0190: return result;
0191: }
0192:
0193: /**
0194: * Decodes a given string using the standard URI encoding mechanism. If the
0195: * provided character set is null, the string is returned but not decoded.
0196: * <em><strong>Note:</strong> The <a
0197: * href="http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
0198: * World Wide Web Consortium Recommendation</a> states that UTF-8 should be
0199: * used. Not doing so may introduce incompatibilites.</em>
0200: *
0201: * @param toDecode
0202: * The string to decode.
0203: * @param characterSet
0204: * The name of a supported character encoding.
0205: * @return The decoded string or null if the named character encoding is not
0206: * supported.
0207: */
0208: public static String decode(String toDecode,
0209: CharacterSet characterSet) {
0210: String result = null;
0211:
0212: try {
0213: result = (characterSet == null) ? toDecode : URLDecoder
0214: .decode(toDecode, characterSet.getName());
0215: } catch (UnsupportedEncodingException uee) {
0216: Logger
0217: .getLogger(Reference.class.getCanonicalName())
0218: .log(
0219: Level.WARNING,
0220: "Unable to decode the string with the UTF-8 character set.",
0221: uee);
0222: }
0223:
0224: return result;
0225: }
0226:
0227: /**
0228: * Creates a reference string from its parts.
0229: *
0230: * @param scheme
0231: * The scheme ("http", "https" or "ftp").
0232: * @param hostName
0233: * The host name or IP address.
0234: * @param hostPort
0235: * The host port (default ports are correctly ignored).
0236: * @param path
0237: * The path component for hierarchical identifiers.
0238: * @param query
0239: * The optional query component for hierarchical identifiers.
0240: * @param fragment
0241: * The optional fragment identifier.
0242: */
0243: public static String toString(String scheme, String hostName,
0244: Integer hostPort, String path, String query, String fragment) {
0245: String host = hostName;
0246:
0247: // Appends the host port number
0248: if (hostPort != null) {
0249: int defaultPort = Protocol.valueOf(scheme).getDefaultPort();
0250: if (hostPort != defaultPort) {
0251: host = hostName + ':' + hostPort;
0252: }
0253: }
0254:
0255: return toString(scheme, host, path, query, fragment);
0256: }
0257:
0258: /**
0259: * Creates a relative reference string from its parts.
0260: *
0261: * @param relativePart
0262: * The relative part component.
0263: * @param query
0264: * The optional query component for hierarchical identifiers.
0265: * @param fragment
0266: * The optional fragment identifier.
0267: */
0268: public static String toString(String relativePart, String query,
0269: String fragment) {
0270: StringBuilder sb = new StringBuilder();
0271:
0272: // Append the path
0273: if (relativePart != null) {
0274: sb.append(relativePart);
0275: }
0276:
0277: // Append the query string
0278: if (query != null) {
0279: sb.append('?').append(query);
0280: }
0281:
0282: // Append the fragment identifier
0283: if (fragment != null) {
0284: sb.append('#').append(fragment);
0285: }
0286:
0287: // Actually construct the reference
0288: return sb.toString();
0289: }
0290:
0291: /**
0292: * Creates a reference string from its parts.
0293: *
0294: * @param scheme
0295: * The scheme ("http", "https" or "ftp").
0296: * @param host
0297: * The host name or IP address plus the optional port number.
0298: * @param path
0299: * The path component for hierarchical identifiers.
0300: * @param query
0301: * The optional query component for hierarchical identifiers.
0302: * @param fragment
0303: * The optional fragment identifier.
0304: */
0305: public static String toString(String scheme, String host,
0306: String path, String query, String fragment) {
0307: StringBuilder sb = new StringBuilder();
0308:
0309: if (scheme != null) {
0310: // Append the scheme and host name
0311: sb.append(scheme.toLowerCase()).append("://").append(host);
0312: }
0313:
0314: // Append the path
0315: if (path != null) {
0316: sb.append(path);
0317: }
0318:
0319: // Append the query string
0320: if (query != null) {
0321: sb.append('?').append(query);
0322: }
0323:
0324: // Append the fragment identifier
0325: if (fragment != null) {
0326: sb.append('#').append(fragment);
0327: }
0328:
0329: // Actually construct the reference
0330: return sb.toString();
0331: }
0332:
0333: /** The base reference for relative references. */
0334: private Reference baseRef;
0335:
0336: /** The internal reference. */
0337: private String internalRef;
0338:
0339: /** The fragment separator index. */
0340: private int fragmentIndex;
0341:
0342: /** The query separator index. */
0343: private int queryIndex;
0344:
0345: /** The scheme separator index. */
0346: private int schemeIndex;
0347:
0348: /**
0349: * Empty constructor.
0350: */
0351: public Reference() {
0352: this ((Reference) null, (String) null);
0353: }
0354:
0355: /**
0356: * Clone constructor.
0357: *
0358: * @param ref
0359: * The reference to clone.
0360: */
0361: public Reference(Reference ref) {
0362: this (ref.baseRef, ref.internalRef);
0363: }
0364:
0365: /**
0366: * Constructor from an URI reference (most likely relative).
0367: *
0368: * @param baseRef
0369: * The base reference.
0370: * @param uriReference
0371: * The URI reference, either absolute or relative.
0372: */
0373: public Reference(Reference baseRef, String uriReference) {
0374: this .baseRef = baseRef;
0375: this .internalRef = uriReference;
0376: updateIndexes();
0377: }
0378:
0379: /**
0380: * Constructor of relative reference from its parts.
0381: *
0382: * @param baseRef
0383: * The base reference.
0384: * @param relativePart
0385: * The relative part component (most of the time it is the path
0386: * component).
0387: * @param query
0388: * The optional query component for hierarchical identifiers.
0389: * @param fragment
0390: * The optional fragment identifier.
0391: */
0392: public Reference(Reference baseRef, String relativePart,
0393: String query, String fragment) {
0394: this (baseRef, toString(relativePart, query, fragment));
0395: }
0396:
0397: /**
0398: * Constructor from an URI reference.
0399: *
0400: * @param uriReference
0401: * The URI reference, either absolute or relative.
0402: */
0403: public Reference(String uriReference) {
0404: this ((Reference) null, uriReference);
0405: }
0406:
0407: /**
0408: * Constructor from an identifier and a fragment.
0409: *
0410: * @param identifier
0411: * The resource identifier.
0412: * @param fragment
0413: * The fragment identifier.
0414: */
0415: public Reference(String identifier, String fragment) {
0416: this ((fragment == null) ? identifier : identifier + '#'
0417: + fragment);
0418: }
0419:
0420: /**
0421: * Constructor of absolute reference from its parts.
0422: *
0423: * @param scheme
0424: * The scheme ("http", "https" or "ftp").
0425: * @param hostName
0426: * The host name or IP address.
0427: * @param hostPort
0428: * The host port (default ports are correctly ignored).
0429: * @param path
0430: * The path component for hierarchical identifiers.
0431: * @param query
0432: * The optional query component for hierarchical identifiers.
0433: * @param fragment
0434: * The optional fragment identifier.
0435: */
0436: public Reference(String scheme, String hostName, int hostPort,
0437: String path, String query, String fragment) {
0438: this (
0439: toString(scheme, hostName, hostPort, path, query,
0440: fragment));
0441: }
0442:
0443: /**
0444: * Indicates whether some other object is "equal to" this one.
0445: *
0446: * @param object
0447: * The object to compare to.
0448: * @return True if this object is the same as the obj argument.
0449: */
0450: @Override
0451: public boolean equals(Object object) {
0452: if (object instanceof Reference) {
0453: Reference ref = (Reference) object;
0454: if (this .internalRef == null) {
0455: return ref.internalRef == null;
0456: } else {
0457: return this .internalRef.equals(ref.internalRef);
0458: }
0459: } else {
0460: return false;
0461: }
0462: }
0463:
0464: /**
0465: * Returns the authority component for hierarchical identifiers. Includes
0466: * the user info, host name and the host port number.
0467: *
0468: * @return The authority component for hierarchical identifiers.
0469: */
0470: public String getAuthority() {
0471: String part = isRelative() ? getRelativePart()
0472: : getSchemeSpecificPart();
0473:
0474: if (part.startsWith("//")) {
0475: int index = part.indexOf('/', 2);
0476:
0477: if (index != -1) {
0478: return part.substring(2, index);
0479: } else {
0480: index = part.indexOf('?');
0481: if (index != -1) {
0482: return part.substring(2, index);
0483: } else {
0484: return part.substring(2);
0485: }
0486: }
0487: } else {
0488: return null;
0489: }
0490: }
0491:
0492: /**
0493: * Returns the base reference for relative references.
0494: *
0495: * @return The base reference for relative references.
0496: */
0497: public Reference getBaseRef() {
0498: return this .baseRef;
0499: }
0500:
0501: /**
0502: * Returns the fragment identifier.
0503: *
0504: * @return The fragment identifier.
0505: */
0506: public String getFragment() {
0507: if (fragmentIndex != -1) {
0508: return this .internalRef.substring(fragmentIndex + 1);
0509: } else {
0510: return null;
0511: }
0512: }
0513:
0514: /**
0515: * Returns the hierarchical part which is equivalent to the scheme specific
0516: * part less the query component.
0517: *
0518: * @return The hierarchical part .
0519: */
0520: public String getHierarchicalPart() {
0521: if (schemeIndex != -1) {
0522: // Scheme found
0523: if (queryIndex != -1) {
0524: // Query found
0525: return this .internalRef.substring(schemeIndex + 1,
0526: queryIndex);
0527: } else {
0528: // No query found
0529: if (fragmentIndex != -1) {
0530: // Fragment found
0531: return this .internalRef.substring(schemeIndex + 1,
0532: fragmentIndex);
0533: } else {
0534: // No fragment found
0535: return this .internalRef.substring(schemeIndex + 1);
0536: }
0537: }
0538: } else {
0539: // No scheme found
0540: if (queryIndex != -1) {
0541: // Query found
0542: return this .internalRef.substring(0, queryIndex);
0543: } else {
0544: if (fragmentIndex != -1) {
0545: // Fragment found
0546: return this .internalRef.substring(0, fragmentIndex);
0547: } else {
0548: // No fragment found
0549: return this .internalRef;
0550: }
0551: }
0552: }
0553: }
0554:
0555: /**
0556: * Returns the host domain name component for server based hierarchical
0557: * identifiers. It can also be replaced by an IP address when no domain name
0558: * was registered.
0559: *
0560: * @return The host domain name component for server based hierarchical
0561: * identifiers.
0562: */
0563: public String getHostDomain() {
0564: String result = null;
0565: String authority = getAuthority();
0566:
0567: if (authority != null) {
0568: int index1 = authority.indexOf('@');
0569: int index2 = authority.indexOf(':');
0570:
0571: if (index1 != -1) {
0572: // User info found
0573: if (index2 != -1) {
0574: // Port found
0575: result = authority.substring(index1 + 1, index2);
0576: } else {
0577: // No port found
0578: result = authority.substring(index1 + 1);
0579: }
0580: } else {
0581: // No user info found
0582: if (index2 != -1) {
0583: // Port found
0584: result = authority.substring(0, index2);
0585: } else {
0586: // No port found
0587: result = authority;
0588: }
0589: }
0590: }
0591:
0592: return result;
0593: }
0594:
0595: /**
0596: * Returns the host identifier. Includes the scheme, the host name and the
0597: * host port number.
0598: *
0599: * @return The host identifier.
0600: */
0601: public String getHostIdentifier() {
0602: StringBuilder result = new StringBuilder();
0603: result.append(getScheme()).append("://").append(getAuthority());
0604: return result.toString();
0605: }
0606:
0607: /**
0608: * Returns the optional port number for server based hierarchical
0609: * identifiers.
0610: *
0611: * @return The optional port number for server based hierarchical
0612: * identifiers or -1 if the port number does not exist.
0613: */
0614: public int getHostPort() {
0615: int result = -1;
0616: String authority = getAuthority();
0617:
0618: if (authority != null) {
0619: int index = authority.indexOf(':');
0620:
0621: if (index != -1) {
0622: try {
0623: result = Integer.parseInt(authority
0624: .substring(index + 1));
0625: } catch (NumberFormatException nfe) {
0626: Logger
0627: .getLogger(
0628: Reference.class.getCanonicalName())
0629: .log(
0630: Level.WARNING,
0631: "Can't parse hostPort : [hostRef,requestUri]=["
0632: + getBaseRef() + ","
0633: + internalRef + "]");
0634: }
0635: }
0636: }
0637:
0638: return result;
0639: }
0640:
0641: /**
0642: * Returns the absolute resource identifier, without the fragment.
0643: *
0644: * @return The absolute resource identifier, without the fragment.
0645: */
0646: public String getIdentifier() {
0647: if (fragmentIndex != -1) {
0648: // Fragment found
0649: return this .internalRef.substring(0, fragmentIndex);
0650: } else {
0651: // No fragment found
0652: return this .internalRef;
0653: }
0654: }
0655:
0656: /**
0657: * Returns the last segment of a hierarchical path.<br/> For example the
0658: * "/a/b/c" and "/a/b/c/" paths have the same segments: "a", "b", "c.
0659: *
0660: * @return The last segment of a hierarchical path.
0661: */
0662: public String getLastSegment() {
0663: String result = null;
0664: int lastSlash = getPath().lastIndexOf('/');
0665:
0666: if (lastSlash != -1) {
0667: result = getPath().substring(lastSlash + 1);
0668: }
0669:
0670: return result;
0671: }
0672:
0673: /**
0674: * Returns the parent reference of a hierarchical reference. The last slash
0675: * of the path will be considered as the end of the parent path.
0676: *
0677: * @return The parent reference of a hierarchical reference.
0678: */
0679: public Reference getParentRef() {
0680: Reference result = null;
0681:
0682: if (isHierarchical()) {
0683: String parentRef = null;
0684: String path = getPath();
0685: if (!path.equals("/") && !path.equals("")) {
0686: if (path.endsWith("/")) {
0687: path = path.substring(0, path.length() - 1);
0688: }
0689:
0690: parentRef = getHostIdentifier()
0691: + path.substring(0, path.lastIndexOf('/') + 1);
0692: } else {
0693: parentRef = this .internalRef;
0694: }
0695:
0696: result = new Reference(parentRef);
0697: }
0698:
0699: return result;
0700: }
0701:
0702: /**
0703: * Returns the path component for hierarchical identifiers.
0704: *
0705: * @return The path component for hierarchical identifiers.
0706: */
0707: public String getPath() {
0708: String result = null;
0709: String part = isRelative() ? getRelativePart()
0710: : getSchemeSpecificPart();
0711:
0712: if (part != null) {
0713: if (part.startsWith("//")) {
0714: // Authority found
0715: int index1 = part.indexOf('/', 2);
0716:
0717: if (index1 != -1) {
0718: // Path found
0719: int index2 = part.indexOf('?');
0720: if (index2 != -1) {
0721: // Query found
0722: result = part.substring(index1, index2);
0723: } else {
0724: // No query found
0725: result = part.substring(index1);
0726: }
0727: } else {
0728: // Path must be empty in this case
0729: }
0730: } else {
0731: // No authority found
0732: int index = part.indexOf('?');
0733: if (index != -1) {
0734: // Query found
0735: result = part.substring(0, index);
0736: } else {
0737: // No query found
0738: result = part;
0739: }
0740: }
0741: }
0742:
0743: return result;
0744: }
0745:
0746: /**
0747: * Returns the optional query component for hierarchical identifiers.
0748: *
0749: * @return The optional query component for hierarchical identifiers.
0750: */
0751: public String getQuery() {
0752: if (queryIndex != -1) {
0753: // Query found
0754: if (fragmentIndex != -1) {
0755: if (queryIndex < fragmentIndex) {
0756: // Fragment found and query sign not inside fragment
0757: return this .internalRef.substring(queryIndex + 1,
0758: fragmentIndex);
0759: } else {
0760: return null;
0761: }
0762: } else {
0763: // No fragment found
0764: return this .internalRef.substring(queryIndex + 1);
0765: }
0766: } else {
0767: // No query found
0768: return null;
0769: }
0770: }
0771:
0772: /**
0773: * Returns the optional query component as a form submission.
0774: *
0775: * @return The optional query component as a form submission.
0776: * @throws IOException
0777: */
0778: public Form getQueryAsForm() {
0779: return new Form(getQuery());
0780: }
0781:
0782: /**
0783: * Returns the optional query component as a form submission.
0784: *
0785: * @param characterSet
0786: * The supported character encoding.
0787: * @return The optional query component as a form submission.
0788: * @throws IOException
0789: */
0790: public Form getQueryAsForm(CharacterSet characterSet) {
0791: return new Form(getQuery(), characterSet);
0792: }
0793:
0794: /**
0795: * Returns the relative part of relative references, without the query and
0796: * fragment. If the reference is absolute, then null is returned.
0797: *
0798: * @return The relative part.
0799: */
0800: public String getRelativePart() {
0801: return isRelative() ? toString(false, false) : null;
0802: }
0803:
0804: /**
0805: * Returns the part of the resource identifier remaining after the base
0806: * reference. Note that the optional fragment is not returned by this
0807: * method. Must be used with the following prerequisites:
0808: * <ul>
0809: * <li>the reference is absolute</li>
0810: * <li>the reference identifier starts with the resource baseRef</li>
0811: * </ul>
0812: *
0813: * @return The remaining resource part or null if the prerequisites are not
0814: * satisfied.
0815: */
0816: public String getRemainingPart() {
0817: String result = null;
0818: String all = toString(true, false);
0819:
0820: if (getBaseRef() != null) {
0821: String base = getBaseRef().toString(true, false);
0822:
0823: if ((base != null) && all.startsWith(base)) {
0824: result = all.substring(base.length());
0825: }
0826: } else {
0827: result = all;
0828: }
0829:
0830: return result;
0831: }
0832:
0833: /**
0834: * Returns the current reference as a relative reference to the current base
0835: * reference. This method should only be invoked for absolute references,
0836: * otherwise an IllegalArgumentException will be raised.
0837: *
0838: * @return The current reference as a relative reference to the current base
0839: * reference.
0840: * @see #getRelativeRef(Reference)
0841: */
0842: public Reference getRelativeRef() {
0843: return getRelativeRef(getBaseRef());
0844: }
0845:
0846: /**
0847: * Returns the current reference relatively to a base reference. This method
0848: * should only be invoked for absolute references, otherwise an
0849: * IllegalArgumentException will be raised.
0850: *
0851: * @throws IllegalArgumentException
0852: * If the relative reference is computed although the reference
0853: * or the base reference are not absolute or not hierarchical.
0854: * @param base
0855: * The base reference to use.
0856: * @return The current reference relatively to a base reference.
0857: */
0858: public Reference getRelativeRef(Reference base) {
0859: Reference result = null;
0860:
0861: if (base == null) {
0862: result = this ;
0863: } else if (!isAbsolute() || !isHierarchical()) {
0864: throw new IllegalArgumentException(
0865: "The reference must have an absolute hierarchical path component");
0866: } else if (!base.isAbsolute() || !base.isHierarchical()) {
0867: throw new IllegalArgumentException(
0868: "The base reference must have an absolute hierarchical path component");
0869: } else if (!getHostIdentifier()
0870: .equals(base.getHostIdentifier())) {
0871: result = this ;
0872: } else {
0873: String localPath = getPath();
0874: String basePath = base.getPath();
0875: String relativePath = null;
0876:
0877: if ((basePath == null) || (localPath == null)) {
0878: relativePath = localPath;
0879: } else {
0880: // Find the junction point
0881: boolean diffFound = false;
0882: int lastSlashIndex = -1;
0883: int i = 0;
0884: char current;
0885: while (!diffFound && (i < localPath.length())
0886: && (i < basePath.length())) {
0887: current = localPath.charAt(i);
0888:
0889: if (current != basePath.charAt(i)) {
0890: diffFound = true;
0891: } else {
0892: if (current == '/')
0893: lastSlashIndex = i;
0894: i++;
0895: }
0896: }
0897:
0898: if (!diffFound) {
0899: if (localPath.length() == basePath.length()) {
0900: // Both paths are strictely equivalent
0901: relativePath = ".";
0902: } else if (i == localPath.length()) {
0903: // End of local path reached
0904: if (basePath.charAt(i) == '/') {
0905: if ((i + 1) == basePath.length()) {
0906: // Both paths are strictely equivalent
0907: relativePath = ".";
0908: } else {
0909: // The local path is a direct parent of the base
0910: // path
0911: // We need to add enough ".." in the relative
0912: // path
0913: StringBuilder sb = new StringBuilder();
0914: sb.append("..");
0915: boolean canAdd = false;
0916:
0917: for (int j = i + 1; j < basePath
0918: .length(); j++) {
0919: if (basePath.charAt(j) == '/') {
0920: canAdd = true;
0921: } else if (canAdd) {
0922: sb.append("/..");
0923: canAdd = false;
0924: }
0925: }
0926:
0927: relativePath = sb.toString();
0928: }
0929: } else {
0930: // The base path has a segment that starts like
0931: // the last local path segment
0932: // But that is longer. Situation similar to a
0933: // junction
0934: StringBuilder sb = new StringBuilder();
0935: boolean firstAdd = true;
0936: boolean canAdd = false;
0937:
0938: for (int j = i; j < basePath.length(); j++) {
0939: if (basePath.charAt(j) == '/') {
0940: canAdd = true;
0941: } else if (canAdd) {
0942: if (firstAdd) {
0943: firstAdd = false;
0944: } else {
0945: sb.append("/");
0946: }
0947:
0948: sb.append("..");
0949: canAdd = false;
0950: }
0951: }
0952:
0953: if (lastSlashIndex + 1 < localPath.length()) {
0954: if (!firstAdd)
0955: sb.append('/');
0956: sb.append(localPath
0957: .substring(lastSlashIndex + 1));
0958: }
0959:
0960: relativePath = sb.toString();
0961:
0962: if (relativePath.equals(""))
0963: relativePath = ".";
0964: }
0965: } else if (i == basePath.length()) {
0966: if (localPath.charAt(i) == '/') {
0967: if ((i + 1) == localPath.length()) {
0968: // Both paths are strictely equivalent
0969: relativePath = ".";
0970: } else {
0971: // The local path is a direct child of the base
0972: // path
0973: relativePath = localPath
0974: .substring(i + 1);
0975: }
0976: } else {
0977: if (lastSlashIndex == (i - 1)) {
0978: // The local path is a direct subpath of the
0979: // base path
0980: relativePath = localPath.substring(i);
0981: } else {
0982: relativePath = ".."
0983: + localPath
0984: .substring(lastSlashIndex);
0985: }
0986: }
0987: }
0988: } else {
0989: // We found a junction point, we need to add enough ".." in
0990: // the relative path and append the rest of the local path
0991: // the local path is a direct subpath of the base path
0992: StringBuilder sb = new StringBuilder();
0993: boolean canAdd = false;
0994: boolean firstAdd = true;
0995:
0996: for (int j = i; j < basePath.length(); j++) {
0997: if (basePath.charAt(j) == '/') {
0998: canAdd = true;
0999: } else if (canAdd) {
1000: if (firstAdd) {
1001: firstAdd = false;
1002: } else {
1003: sb.append("/");
1004: }
1005:
1006: sb.append("..");
1007: canAdd = false;
1008: }
1009: }
1010:
1011: if (!firstAdd)
1012: sb.append('/');
1013: sb.append(localPath.substring(lastSlashIndex + 1));
1014: relativePath = sb.toString();
1015: }
1016: }
1017:
1018: // Builde the result reference
1019: result = new Reference();
1020: String query = getQuery();
1021: String fragment = getFragment();
1022: boolean modified = false;
1023:
1024: if ((query != null) && (!query.equals(base.getQuery()))) {
1025: result.setQuery(query);
1026: modified = true;
1027: }
1028:
1029: if ((fragment != null)
1030: && (!fragment.equals(base.getFragment()))) {
1031: result.setFragment(fragment);
1032: modified = true;
1033: }
1034:
1035: if (!modified || !relativePath.equals(".")) {
1036: result.setPath(relativePath);
1037: }
1038: }
1039:
1040: return result;
1041: }
1042:
1043: /**
1044: * Returns the scheme component.
1045: *
1046: * @return The scheme component.
1047: */
1048: public String getScheme() {
1049: if (schemeIndex != -1) {
1050: // Scheme found
1051: return this .internalRef.substring(0, schemeIndex);
1052: } else {
1053: // No scheme found
1054: return null;
1055: }
1056: }
1057:
1058: /**
1059: * Returns the protocol associated with the scheme component.
1060: *
1061: * @return The protocol associated with the scheme component.
1062: */
1063: public Protocol getSchemeProtocol() {
1064: return Protocol.valueOf(getScheme());
1065: }
1066:
1067: /**
1068: * Returns the scheme specific part.
1069: *
1070: * @return The scheme specific part.
1071: */
1072: public String getSchemeSpecificPart() {
1073: String result = null;
1074:
1075: if (schemeIndex != -1) {
1076: // Scheme found
1077: if (fragmentIndex != -1) {
1078: // Fragment found
1079: result = this .internalRef.substring(schemeIndex + 1,
1080: fragmentIndex);
1081: } else {
1082: // No fragment found
1083: result = this .internalRef.substring(schemeIndex + 1);
1084: }
1085: }
1086:
1087: return result;
1088: }
1089:
1090: /**
1091: * Returns the segments of a hierarchical path.<br/> A new list is created
1092: * for each call.
1093: *
1094: * @return The segments of a hierarchical path.
1095: */
1096: public List<String> getSegments() {
1097: List<String> result = new ArrayList<String>();
1098: ;
1099: String path = getPath();
1100: int start = -2; // The index of the slash starting the segment
1101: char current;
1102:
1103: if (path != null) {
1104: for (int i = 0; i < path.length(); i++) {
1105: current = path.charAt(i);
1106:
1107: if (current == '/') {
1108: if (start == -2) {
1109: // Beginning of an absolute path or sequence of two
1110: // separators
1111: start = i;
1112: } else {
1113: // End of a segment
1114: result.add(path.substring(start + 1, i));
1115: start = i;
1116: }
1117: } else {
1118: if (start == -2) {
1119: // Starting a new segment for a relative path
1120: start = -1;
1121: } else {
1122: // Looking for the next character
1123: }
1124: }
1125: }
1126:
1127: if (start != -2) {
1128: // Add the last segment
1129: result.add(path.substring(start + 1));
1130: }
1131: }
1132:
1133: return result;
1134: }
1135:
1136: /**
1137: * Returns the target reference. This method resolves relative references
1138: * against the base reference then normalize them.
1139: *
1140: * @throws IllegalArgumentException
1141: * If the base reference (after resolution) is not absolute.
1142: * @throws IllegalArgumentException
1143: * If the reference is relative and not base reference has been
1144: * provided.
1145: *
1146: * @return The target reference.
1147: */
1148: public Reference getTargetRef() {
1149: Reference result = null;
1150:
1151: // Step 1 - Resolve relative reference against their base reference
1152: if (isRelative() && (this .baseRef != null)) {
1153: Reference baseReference = null;
1154: if (this .baseRef.isAbsolute()) {
1155: baseReference = this .baseRef;
1156: } else {
1157: baseReference = this .baseRef.getTargetRef();
1158: }
1159: if (baseReference.isRelative()) {
1160: throw new IllegalArgumentException(
1161: "The base reference must have an absolute hierarchical path component");
1162: }
1163:
1164: // Relative URI detected
1165: String authority = getAuthority();
1166: String path = getPath();
1167: String query = getQuery();
1168: String fragment = getFragment();
1169:
1170: // Create an empty reference
1171: result = new Reference();
1172: result.setScheme(baseReference.getScheme());
1173:
1174: if (authority != null) {
1175: result.setAuthority(authority);
1176: result.setPath(path);
1177: result.setQuery(query);
1178: } else {
1179: result.setAuthority(baseReference.getAuthority());
1180:
1181: if ((path == null) || (path.equals(""))) {
1182: result.setPath(baseReference.getPath());
1183:
1184: if (query != null) {
1185: result.setQuery(query);
1186: } else {
1187: result.setQuery(baseReference.getQuery());
1188: }
1189: } else {
1190: if (path.startsWith("/")) {
1191: result.setPath(path);
1192: } else {
1193: String basePath = baseReference.getPath();
1194: String mergedPath = null;
1195:
1196: if ((baseReference.getAuthority() != null)
1197: && ((basePath == null) || (basePath
1198: .equals("")))) {
1199: mergedPath = "/" + path;
1200: } else {
1201: // Remove the last segment which may be empty if
1202: // the path is ending with a slash
1203: int lastSlash = basePath.lastIndexOf('/');
1204: if (lastSlash == -1) {
1205: mergedPath = path;
1206: } else {
1207: mergedPath = basePath.substring(0,
1208: lastSlash + 1)
1209: + path;
1210: }
1211: }
1212:
1213: result.setPath(mergedPath);
1214: }
1215:
1216: result.setQuery(query);
1217: }
1218: }
1219:
1220: result.setFragment(fragment);
1221: } else if (isRelative()) {
1222: // Relative reference with no baseRef detected
1223: throw new IllegalArgumentException(
1224: "Relative references are only usable when a base reference is set.");
1225: } else {
1226: // Absolute URI detected
1227: result = new Reference(this );
1228: }
1229:
1230: // Step 2 - Normalize the target reference
1231: result.normalize();
1232:
1233: return result;
1234: }
1235:
1236: /**
1237: * Returns the user info component for server based hierarchical
1238: * identifiers.
1239: *
1240: * @return The user info component for server based hierarchical
1241: * identifiers.
1242: */
1243: public String getUserInfo() {
1244: String result = null;
1245: String authority = getAuthority();
1246:
1247: if (authority != null) {
1248: int index = authority.indexOf('@');
1249:
1250: if (index != -1) {
1251: result = authority.substring(0, index);
1252: }
1253: }
1254:
1255: return result;
1256: }
1257:
1258: /**
1259: * Returns a hash code value for the object.
1260: *
1261: * @return A hash code value for the object.
1262: */
1263: @Override
1264: public int hashCode() {
1265: return (this .internalRef == null) ? 0 : this .internalRef
1266: .hashCode();
1267: }
1268:
1269: /**
1270: * Indicates if the reference is absolute.
1271: *
1272: * @return True if the reference is absolute.
1273: */
1274: public boolean isAbsolute() {
1275: return (getScheme() != null);
1276: }
1277:
1278: /**
1279: * Returns true if both reference are equivalent, meaning that they resolve
1280: * to the same target reference.
1281: *
1282: * @param ref
1283: * The reference to compare.
1284: * @return True if both reference are equivalent.
1285: */
1286: public boolean isEquivalentTo(Reference ref) {
1287: return getTargetRef().equals(ref.getTargetRef());
1288: }
1289:
1290: /**
1291: * Indicates if the identifier is hierarchical.
1292: *
1293: * @return True if the identifier is hierarchical, false if it is opaque.
1294: */
1295: public boolean isHierarchical() {
1296: return isRelative()
1297: || (getSchemeSpecificPart().charAt(0) == '/');
1298: }
1299:
1300: /**
1301: * Indicates if the identifier is opaque.
1302: *
1303: * @return True if the identifier is opaque, false if it is hierarchical.
1304: */
1305: public boolean isOpaque() {
1306: return isAbsolute()
1307: && (getSchemeSpecificPart().charAt(0) != '/');
1308: }
1309:
1310: /**
1311: * Indicates if the reference is a parent of the hierarchical child
1312: * reference.
1313: *
1314: * @param childRef
1315: * The hierarchical reference.
1316: * @return True if the reference is a parent of the hierarchical child
1317: * reference.
1318: */
1319: public boolean isParent(Reference childRef) {
1320: boolean result = false;
1321:
1322: if ((childRef != null) && (childRef.isHierarchical())) {
1323: result = childRef.toString(false, false).startsWith(
1324: toString(false, false));
1325: }
1326:
1327: return result;
1328: }
1329:
1330: /**
1331: * Indicates if the reference is relative.
1332: *
1333: * @return True if the reference is relative.
1334: */
1335: public boolean isRelative() {
1336: return (getScheme() == null);
1337: }
1338:
1339: /**
1340: * Normalizes the reference. Useful before comparison between references or
1341: * when building a target reference from a base and a relative references.
1342: *
1343: * @return The current reference.
1344: */
1345: public Reference normalize() {
1346: // 1. The input buffer is initialized with the now-appended path
1347: // components and the output buffer is initialized to the empty string.
1348: StringBuilder output = new StringBuilder();
1349: StringBuilder input = new StringBuilder();
1350: String path = getPath();
1351: if (path != null)
1352: input.append(path);
1353:
1354: // 2. While the input buffer is not empty, loop as follows:
1355: while (input.length() > 0) {
1356: // A. If the input buffer begins with a prefix of "../" or "./",
1357: // then remove that prefix from the input buffer; otherwise,
1358: if ((input.length() >= 3)
1359: && input.substring(0, 3).equals("../")) {
1360: input.delete(0, 3);
1361: } else if ((input.length() >= 2)
1362: && input.substring(0, 2).equals("./")) {
1363: input.delete(0, 2);
1364: }
1365:
1366: // B. if the input buffer begins with a prefix of "/./" or "/.",
1367: // where "." is a complete path segment, then replace that
1368: // prefix with "/" in the input buffer; otherwise,
1369: else if ((input.length() >= 3)
1370: && input.substring(0, 3).equals("/./")) {
1371: input.delete(0, 2);
1372: } else if ((input.length() == 2)
1373: && input.substring(0, 2).equals("/.")) {
1374: input.delete(1, 2);
1375: }
1376:
1377: // C. if the input buffer begins with a prefix of "/../" or "/..",
1378: // where ".." is a complete path segment, then replace that prefix
1379: // with "/" in the input buffer and remove the last segment and its
1380: // preceding "/" (if any) from the output buffer; otherwise,
1381: else if ((input.length() >= 4)
1382: && input.substring(0, 4).equals("/../")) {
1383: input.delete(0, 3);
1384: removeLastSegment(output);
1385: } else if ((input.length() == 3)
1386: && input.substring(0, 3).equals("/..")) {
1387: input.delete(1, 3);
1388: removeLastSegment(output);
1389: }
1390:
1391: // D. if the input buffer consists only of "." or "..", then remove
1392: // that from the input buffer; otherwise,
1393: else if ((input.length() == 1)
1394: && input.substring(0, 1).equals(".")) {
1395: input.delete(0, 1);
1396: } else if ((input.length() == 2)
1397: && input.substring(0, 2).equals("..")) {
1398: input.delete(0, 2);
1399: }
1400:
1401: // E. move the first path segment in the input buffer to the end of
1402: // the output buffer, including the initial "/" character (if any)
1403: // and any subsequent characters up to, but not including, the next
1404: // "/" character or the end of the input buffer.
1405: else {
1406: int max = -1;
1407: for (int i = 1; (max == -1) && (i < input.length()); i++) {
1408: if (input.charAt(i) == '/')
1409: max = i;
1410: }
1411:
1412: if (max != -1) {
1413: // We found the next "/" character.
1414: output.append(input.substring(0, max));
1415: input.delete(0, max);
1416: } else {
1417: // End of input buffer reached
1418: output.append(input);
1419: input.delete(0, input.length());
1420: }
1421: }
1422: }
1423:
1424: // Finally, the output buffer is returned as the result
1425: setPath(output.toString());
1426:
1427: // Ensure that the scheme and host names are reset in lower case
1428: setScheme(getScheme());
1429: setHostDomain(getHostDomain());
1430:
1431: // Remove the port if it is equal to the default port of the reference's
1432: // Protocol.
1433: int hostPort = getHostPort();
1434: if (hostPort != -1) {
1435: int defaultPort = Protocol.valueOf(getScheme())
1436: .getDefaultPort();
1437: if (hostPort == defaultPort) {
1438: setHostPort(null);
1439: }
1440: }
1441: return this ;
1442: }
1443:
1444: /**
1445: * Removes the last segement from the output builder.
1446: *
1447: * @param output
1448: * The output builder to update.
1449: */
1450: private void removeLastSegment(StringBuilder output) {
1451: int min = -1;
1452: for (int i = output.length() - 1; (min == -1) && (i >= 0); i--) {
1453: if (output.charAt(i) == '/')
1454: min = i;
1455: }
1456:
1457: if (min != -1) {
1458: // We found the previous "/" character.
1459: output.delete(min, output.length());
1460: } else {
1461: // End of output buffer reached
1462: output.delete(0, output.length());
1463: }
1464:
1465: }
1466:
1467: /**
1468: * Sets the authority component for hierarchical identifiers.
1469: *
1470: * @param authority
1471: * The authority component for hierarchical identifiers.
1472: */
1473: public void setAuthority(String authority) {
1474: String oldPart = isRelative() ? getRelativePart()
1475: : getSchemeSpecificPart();
1476: String newPart;
1477: String newAuthority = (authority == null) ? "" : "//"
1478: + authority;
1479:
1480: if (oldPart.startsWith("//")) {
1481: int index = oldPart.indexOf('/', 2);
1482:
1483: if (index != -1) {
1484: newPart = newAuthority + oldPart.substring(index);
1485: } else {
1486: index = oldPart.indexOf('?');
1487: if (index != -1) {
1488: newPart = newAuthority + oldPart.substring(index);
1489: } else {
1490: newPart = newAuthority;
1491: }
1492: }
1493: } else {
1494: newPart = newAuthority + oldPart;
1495: }
1496:
1497: if (isAbsolute()) {
1498: setSchemeSpecificPart(newPart);
1499: } else {
1500: setRelativePart(newPart);
1501: }
1502: }
1503:
1504: /**
1505: * Sets the base reference for relative references.
1506: *
1507: * @param baseUri
1508: * The base URI for relative references.
1509: */
1510: public void setBaseRef(String baseUri) {
1511: setBaseRef(new Reference(baseUri));
1512: }
1513:
1514: /**
1515: * Sets the base reference for relative references.
1516: *
1517: * @param baseRef
1518: * The base reference for relative references.
1519: */
1520: public void setBaseRef(Reference baseRef) {
1521: this .baseRef = baseRef;
1522: }
1523:
1524: /**
1525: * Sets the fragment identifier.
1526: *
1527: * @throws IllegalArgumentException
1528: * if the fragment parameter contains the fragment delimiter
1529: * ('#').
1530: *
1531: * @param fragment
1532: * The fragment identifier.
1533: */
1534: public void setFragment(String fragment) {
1535: if ((fragment != null) && (fragment.indexOf('#') != -1)) {
1536: throw new IllegalArgumentException(
1537: "Illegal '#' character detected in parameter");
1538: } else {
1539: if (fragmentIndex != -1) {
1540: // Existing fragment
1541: if (fragment != null) {
1542: this .internalRef = this .internalRef.substring(0,
1543: fragmentIndex + 1)
1544: + fragment;
1545: } else {
1546: this .internalRef = this .internalRef.substring(0,
1547: fragmentIndex);
1548: }
1549: } else {
1550: // No existing fragment
1551: if (fragment != null) {
1552: if (this .internalRef != null) {
1553: this .internalRef = this .internalRef + '#'
1554: + fragment;
1555: } else {
1556: this .internalRef = '#' + fragment;
1557: }
1558: } else {
1559: // Do nothing
1560: }
1561: }
1562: }
1563:
1564: updateIndexes();
1565: }
1566:
1567: /**
1568: * Sets the host domain component for server based hierarchical identifiers.
1569: *
1570: * @param domain
1571: * The host component for server based hierarchical identifiers.
1572: */
1573: public void setHostDomain(String domain) {
1574: String authority = getAuthority();
1575:
1576: if (authority != null) {
1577: if (domain == null) {
1578: domain = "";
1579: } else {
1580: // URI specification indicates that host names should be
1581: // produced in lower case
1582: domain = domain.toLowerCase();
1583: }
1584:
1585: int index1 = authority.indexOf('@');
1586: int index2 = authority.indexOf(':');
1587:
1588: if (index1 != -1) {
1589: // User info found
1590: if (index2 != -1) {
1591: // Port found
1592: setAuthority(authority.substring(0, index1 + 1)
1593: + domain + authority.substring(index2));
1594: } else {
1595: // No port found
1596: setAuthority(authority.substring(0, index1 + 1)
1597: + domain);
1598: }
1599: } else {
1600: // No user info found
1601: if (index2 != -1) {
1602: // Port found
1603: setAuthority(domain + authority.substring(index2));
1604: } else {
1605: // No port found
1606: setAuthority(domain);
1607: }
1608: }
1609: }
1610: }
1611:
1612: /**
1613: * Sets the optional port number for server based hierarchical identifiers.
1614: *
1615: * @throws IllegalArgumentException
1616: * If the autority has not been defined.
1617: * @param port
1618: * The optional port number for server based hierarchical
1619: * identifiers.
1620: */
1621: public void setHostPort(Integer port) {
1622: String authority = getAuthority();
1623:
1624: if (authority != null) {
1625: int index = authority.indexOf(':');
1626: String newPort = (port == null) ? "" : ":" + port;
1627:
1628: if (index != -1) {
1629: setAuthority(authority.substring(0, index) + newPort);
1630: } else {
1631: setAuthority(authority + newPort);
1632: }
1633: } else {
1634: throw new IllegalArgumentException(
1635: "No authority defined, please define a host name first");
1636: }
1637: }
1638:
1639: /**
1640: * Sets the absolute resource identifier.
1641: *
1642: * @throws IllegalArgumentException
1643: * If the identifier parameter contains the fragment delimiter
1644: * ('#').
1645: *
1646: * @param identifier
1647: * The absolute resource identifier.
1648: */
1649: public void setIdentifier(String identifier) {
1650: if (identifier == null)
1651: identifier = "";
1652: if (identifier.indexOf('#') != -1) {
1653: throw new IllegalArgumentException(
1654: "Illegal '#' character detected in parameter");
1655: } else {
1656: if (fragmentIndex != -1) {
1657: // Fragment found
1658: this .internalRef = identifier
1659: + this .internalRef.substring(fragmentIndex);
1660: } else {
1661: // No fragment found
1662: this .internalRef = identifier;
1663: }
1664:
1665: updateIndexes();
1666: }
1667: }
1668:
1669: /**
1670: * Sets the path component for hierarchical identifiers.
1671: *
1672: * @param path
1673: * The path component for hierarchical identifiers.
1674: */
1675: public void setPath(String path) {
1676: String oldPart = isRelative() ? getRelativePart()
1677: : getSchemeSpecificPart();
1678: String newPart = null;
1679:
1680: if (oldPart != null) {
1681: if (path == null)
1682: path = "";
1683:
1684: if (oldPart.startsWith("//")) {
1685: // Authority found
1686: int index1 = oldPart.indexOf('/', 2);
1687:
1688: if (index1 != -1) {
1689: // Path found
1690: int index2 = oldPart.indexOf('?');
1691: if (index2 != -1) {
1692: // Query found
1693: newPart = oldPart.substring(0, index1) + path
1694: + oldPart.substring(index2);
1695: } else {
1696: // No query found
1697: newPart = oldPart.substring(0, index1) + path;
1698: }
1699: } else {
1700: // No path found
1701: int index2 = oldPart.indexOf('?');
1702: if (index2 != -1) {
1703: // Query found
1704: newPart = oldPart.substring(0, index2) + path
1705: + oldPart.substring(index2);
1706: } else {
1707: // No query found
1708: newPart = oldPart + path;
1709: }
1710: }
1711: } else {
1712: // No authority found
1713: int index = oldPart.indexOf('?');
1714: if (index != -1) {
1715: // Query found
1716: newPart = path + oldPart.substring(index);
1717: } else {
1718: // No query found
1719: newPart = path;
1720: }
1721: }
1722: } else {
1723: newPart = path;
1724: }
1725:
1726: if (isAbsolute()) {
1727: setSchemeSpecificPart(newPart);
1728: } else {
1729: setRelativePart(newPart);
1730: }
1731: }
1732:
1733: /**
1734: * Sets the scheme component based on this protocol.
1735: *
1736: * @param protocol
1737: * The protocol of the scheme component.
1738: */
1739: public void setProtocol(Protocol protocol) {
1740: setScheme(protocol.getSchemeName());
1741: }
1742:
1743: /**
1744: * Sets the query component for hierarchical identifiers.
1745: *
1746: * @param query
1747: * The query component for hierarchical identifiers.
1748: */
1749: public void setQuery(String query) {
1750: if (queryIndex != -1) {
1751: // Query found
1752: if (fragmentIndex != -1) {
1753: // Fragment found
1754: if (query != null) {
1755: this .internalRef = this .internalRef.substring(0,
1756: queryIndex + 1)
1757: + query
1758: + this .internalRef.substring(fragmentIndex);
1759: } else {
1760: this .internalRef = this .internalRef.substring(0,
1761: queryIndex)
1762: + this .internalRef.substring(fragmentIndex);
1763: }
1764: } else {
1765: // No fragment found
1766: if (query != null) {
1767: this .internalRef = this .internalRef.substring(0,
1768: queryIndex + 1)
1769: + query;
1770: } else {
1771: this .internalRef = this .internalRef.substring(0,
1772: queryIndex);
1773: }
1774: }
1775: } else {
1776: // No query found
1777: if (fragmentIndex != -1) {
1778: // Fragment found
1779: if (query != null) {
1780: this .internalRef = this .internalRef.substring(0,
1781: fragmentIndex)
1782: + '?'
1783: + query
1784: + this .internalRef.substring(fragmentIndex);
1785: } else {
1786: // Do nothing;
1787: }
1788: } else {
1789: // No fragment found
1790: if (query != null) {
1791: if (this .internalRef != null) {
1792: this .internalRef = this .internalRef + '?'
1793: + query;
1794: } else {
1795: this .internalRef = '?' + query;
1796: }
1797: } else {
1798: // Do nothing;
1799: }
1800: }
1801: }
1802:
1803: updateIndexes();
1804: }
1805:
1806: /**
1807: * Sets the relative part for relative references only.
1808: *
1809: * @param relativePart
1810: * The relative part to set.
1811: */
1812: public void setRelativePart(String relativePart) {
1813: if (relativePart == null)
1814: relativePart = "";
1815: if (schemeIndex == -1) {
1816: // This is a relative reference, no scheme found
1817: if (queryIndex != -1) {
1818: // Query found
1819: this .internalRef = relativePart
1820: + this .internalRef.substring(queryIndex);
1821: } else if (fragmentIndex != -1) {
1822: // Fragment found
1823: this .internalRef = relativePart
1824: + this .internalRef.substring(fragmentIndex);
1825: } else {
1826: // No fragment found
1827: this .internalRef = relativePart;
1828: }
1829: }
1830:
1831: updateIndexes();
1832: }
1833:
1834: /**
1835: * Sets the scheme component.
1836: *
1837: * @param scheme
1838: * The scheme component.
1839: */
1840: public void setScheme(String scheme) {
1841: if (scheme != null) {
1842: // URI specification indicates that scheme names should be
1843: // produced in lower case
1844: scheme = scheme.toLowerCase();
1845: }
1846:
1847: if (schemeIndex != -1) {
1848: // Scheme found
1849: if (scheme != null) {
1850: this .internalRef = scheme
1851: + this .internalRef.substring(schemeIndex);
1852: } else {
1853: this .internalRef = this .internalRef
1854: .substring(schemeIndex + 1);
1855: }
1856: } else {
1857: // No scheme found
1858: if (scheme != null) {
1859: if (this .internalRef == null) {
1860: this .internalRef = scheme + ':';
1861: } else {
1862: this .internalRef = scheme + ':' + this .internalRef;
1863: }
1864: }
1865: }
1866:
1867: updateIndexes();
1868: }
1869:
1870: /**
1871: * Sets the scheme specific part.
1872: *
1873: * @param schemeSpecificPart
1874: * The scheme specific part.
1875: */
1876: public void setSchemeSpecificPart(String schemeSpecificPart) {
1877: if (schemeSpecificPart == null)
1878: schemeSpecificPart = "";
1879: if (schemeIndex != -1) {
1880: // Scheme found
1881: if (fragmentIndex != -1) {
1882: // Fragment found
1883: this .internalRef = this .internalRef.substring(0,
1884: schemeIndex + 1)
1885: + schemeSpecificPart
1886: + this .internalRef.substring(fragmentIndex);
1887: } else {
1888: // No fragment found
1889: this .internalRef = this .internalRef.substring(0,
1890: schemeIndex + 1)
1891: + schemeSpecificPart;
1892: }
1893: } else {
1894: // No scheme found
1895: if (fragmentIndex != -1) {
1896: // Fragment found
1897: this .internalRef = schemeSpecificPart
1898: + this .internalRef.substring(fragmentIndex);
1899: } else {
1900: // No fragment found
1901: this .internalRef = schemeSpecificPart;
1902: }
1903: }
1904:
1905: updateIndexes();
1906: }
1907:
1908: /**
1909: * Sets the segments of a hierarchical path.<br/> A new absolute path will
1910: * replace any existing one.
1911: *
1912: * @param segments
1913: * The segments of the hierarchical path.
1914: */
1915: public void setSegments(List<String> segments) {
1916: StringBuilder sb = new StringBuilder();
1917: for (String segment : segments) {
1918: sb.append('/').append(segment);
1919: }
1920: setPath(sb.toString());
1921: }
1922:
1923: /**
1924: * Sets the user info component for server based hierarchical identifiers.
1925: *
1926: * @throws IllegalArgumentException
1927: * If the autority part has not been defined.
1928: *
1929: * @param userInfo
1930: * The user info component for server based hierarchical
1931: * identifiers.
1932: */
1933: public void setUserInfo(String userInfo) {
1934: String authority = getAuthority();
1935:
1936: if (authority != null) {
1937: int index = authority.indexOf('@');
1938: String newUserInfo = (userInfo == null) ? ""
1939: : userInfo + '@';
1940:
1941: if (index != -1) {
1942: setAuthority(newUserInfo
1943: + authority.substring(index + 1));
1944: } else {
1945: setAuthority(newUserInfo + authority);
1946: }
1947: } else {
1948: throw new IllegalArgumentException(
1949: "No authority defined, please define a host name first");
1950: }
1951: }
1952:
1953: /**
1954: * Returns the reference as an URI string.
1955: *
1956: * @return The reference as an URI string.
1957: */
1958: @Override
1959: public String toString() {
1960: return this .internalRef;
1961: }
1962:
1963: /**
1964: * Returns the URI reference string.
1965: *
1966: * @param query
1967: * Indicates if the query should be included;
1968: * @param fragment
1969: * Indicates if the fragment should be included;
1970: * @return The URI reference string.
1971: */
1972: public String toString(boolean query, boolean fragment) {
1973: if (query) {
1974: if (fragment) {
1975: return this .internalRef;
1976: } else {
1977: if (fragmentIndex != -1) {
1978: return this .internalRef.substring(0, fragmentIndex);
1979: } else {
1980: return this .internalRef;
1981: }
1982: }
1983: } else {
1984: if (fragment) {
1985: if (queryIndex != -1) {
1986: if (fragmentIndex != -1) {
1987: return this .internalRef
1988: .substring(0, queryIndex)
1989: + "#" + getFragment();
1990: } else {
1991: return this .internalRef
1992: .substring(0, queryIndex);
1993: }
1994: } else {
1995: return this .internalRef;
1996: }
1997: } else {
1998: if (queryIndex != -1) {
1999: return this .internalRef.substring(0, queryIndex);
2000: } else {
2001: if (fragmentIndex != -1) {
2002: return this .internalRef.substring(0,
2003: fragmentIndex);
2004: } else {
2005: return this .internalRef;
2006: }
2007: }
2008: }
2009: }
2010: }
2011:
2012: /**
2013: * Update internal indexes.
2014: */
2015: private void updateIndexes() {
2016: if (internalRef != null) {
2017: int firstSlashIndex = this .internalRef.indexOf('/');
2018: this .schemeIndex = this .internalRef.indexOf(':');
2019:
2020: if ((firstSlashIndex != -1)
2021: && (this .schemeIndex > firstSlashIndex)) {
2022: // We are in the rare case of a relative reference where one of
2023: // the path segments contains a colon character. In this case,
2024: // we ignore the colon as a valid scheme index.
2025: // Note that this colon can't be in the first segment as it is
2026: // forbidden by the URI RFC.
2027: this .schemeIndex = -1;
2028: }
2029:
2030: this .queryIndex = this .internalRef.indexOf('?');
2031: this .fragmentIndex = this .internalRef.indexOf('#');
2032: if ((this .queryIndex != -1) && (this .fragmentIndex != -1)
2033: && (this .queryIndex > this .fragmentIndex)) {
2034: // Query sign inside fragment
2035: this .queryIndex = -1;
2036: }
2037: } else {
2038: this .schemeIndex = -1;
2039: this .queryIndex = -1;
2040: this .fragmentIndex = -1;
2041: }
2042: }
2043: }
|