gt:     protected void safeRecomputeURI() {
context: /*
   * eXist Open Source Native XML Database
   * Copyright (C) 2001-2015 The eXist Project
   * http://exist-db.org
   *
   * This program 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
   * 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 Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program; if not, write to the Free Software Foundation
   * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   */
  package org.exist.xmldb;
  
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  
  import org.exist.xquery.Constants;
  import org.exist.xquery.util.URIUtils;
  import org.exist.xquery.value.AnyURIValue;
  
  import java.io.Serializable;
  import java.io.UnsupportedEncodingException;
  
  import java.net.MalformedURLException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.net.URLDecoder;
  
  import java.util.regex.Pattern;
  
  /**
   * A utility class for xmldb URis. Since, java.net.URI is <strong>final</strong> this class acts as a wrapper.
   *
   * @author Pierrick Brihaye <pierrick.brihaye@free.fr>
   */
  /*
   * This base class implementation only provides a path.  FullXmldbURI provides
   * full uri support.  The create method creates a minimal object to keep memory
   * usage low.
   */
  public class XmldbURI implements Comparable<Object>, Serializable {
  
      protected final static Logger LOG = LogManager.getLogger(XmldbURI.class);
      public static final int NO_PORT = -1;
      //Should be provided by org.xmldb.api package !!!
      public static final String XMLDB_SCHEME = "xmldb";
      public static final String XMLDB_URI_PREFIX = "xmldb:";
      public static final String DEFAULT_INSTANCE_NAME = "exist";
      public static final String EMBEDDED_SERVER_AUTHORITY = "embedded-eXist-server";
      public static final String EMBEDDED_SERVER_URI_PREFIX = XMLDB_URI_PREFIX + DEFAULT_INSTANCE_NAME + "://";
      public static final String EMBEDDED_SHORT_URI_PREFIX = XMLDB_URI_PREFIX + "/";
  
      /**
       * 'db' collection name
       */
      public final static String ROOT_COLLECTION_NAME = "db";
      /**
       * '/db' collection name
       */
      public final static String ROOT_COLLECTION = "/" + ROOT_COLLECTION_NAME;
      /**
       * 'system' collection name
       */
      public final static String SYSTEM_COLLECTION_NAME = "system";
      /**
       * '/db/system' collection name
       */
      public final static String SYSTEM_COLLECTION = ROOT_COLLECTION + "/" + SYSTEM_COLLECTION_NAME;
      /**
       * 'temp' collection name
       */
      public final static String TEMP_COLLECTION_NAME = "temp";
      /**
       * '/db/system/temp' collection name
       */
      public final static String TEMP_COLLECTION = SYSTEM_COLLECTION + "/" + TEMP_COLLECTION_NAME;
  
      @Deprecated
      /** '/db/system/config' collection name */
      public final static String CONFIG_COLLECTION = SYSTEM_COLLECTION + "/config";
  
      /**
       * '/db' collection *
       */
      public final static XmldbURI DB = create(ROOT_COLLECTION);
      public final static XmldbURI ROOT_COLLECTION_URI = create(ROOT_COLLECTION);
      public final static XmldbURI RELATIVE_ROOT_COLLECTION_URI = create(ROOT_COLLECTION_NAME);
  
      /**
       * '/db/system' *
       */
      public final static XmldbURI SYSTEM = create(SYSTEM_COLLECTION);
      /**
       * '/db/system' *
       */
      @Deprecated
      public final static XmldbURI SYSTEM_COLLECTION_URI = create(SYSTEM_COLLECTION);
  
      @Deprecated
      public final static XmldbURI CONFIG_COLLECTION_URI = create(CONFIG_COLLECTION);
  
      public final static XmldbURI TEMP_COLLECTION_URI = create(TEMP_COLLECTION);
      public final static XmldbURI EMPTY_URI = createInternal("");
      public static final XmldbURI EMBEDDED_SERVER_URI = XmldbURI.create(EMBEDDED_SERVER_URI_PREFIX + EMBEDDED_SERVER_AUTHORITY);
      /**
       * 'xmldb:exist///db'
       */
      public static final String LOCAL_DB = EMBEDDED_SERVER_URI_PREFIX + ROOT_COLLECTION;
      /**
       * 'xmldb:exist///db' XmldbURI
       */
      public static final XmldbURI LOCAL_DB_URI = XmldbURI.create(EMBEDDED_SERVER_URI_PREFIX + ROOT_COLLECTION);
      //TODO : deprecate when we split at root collection
      public final static String API_XMLRPC = "xmlrpc";
      public final static String API_WEBDAV = "webdav";
      public final static String API_REST = "rest-style";
      public final static String API_LOCAL = "local";
  
      private final static XmldbURI[] NO_SEGMENTS = new XmldbURI[0];
  
      private String encodedCollectionPath;
      protected boolean hadXmldbPrefix = false;
  
      protected XmldbURI(final URI xmldbURI) throws URISyntaxException {
          this(xmldbURI, true);
      }
  
      /**
       * Contructs an XmldbURI from given URI. The provided URI must have the XMLDB_SCHEME ("xmldb")
       *
       * @param xmldbURI A string
       * @throws URISyntaxException If the given string is not a valid xmldb URI.
       */
      protected XmldbURI(URI xmldbURI, final boolean mustHaveXMLDB) throws URISyntaxException {
          final String uriStr = xmldbURI.toString().trim();
  
          if (!".".equals(uriStr) && !"..".equals(uriStr) && !uriStr.endsWith("/.") && !uriStr.endsWith("/..")) {
              // Only normalize if uri is not "." or ".." or doesn't end with "/." or "/.." .  If it's a dot uri, then the final segment is assumed to be a document name
              xmldbURI = xmldbURI.normalize();
          }
  
          if (xmldbURI.getScheme() != null && mustHaveXMLDB) {
  
              if (!XMLDB_SCHEME.equals(xmldbURI.getScheme())) {
                  throw new URISyntaxException(xmldbURI.toString(), "xmldb URI scheme does not start with " + XMLDB_URI_PREFIX);
              }
  
              String uri = xmldbURI.toString();
              if (!uri.startsWith(EMBEDDED_SHORT_URI_PREFIX)) {
                  xmldbURI = new URI(uri.substring(XMLDB_URI_PREFIX.length()));
              }
              hadXmldbPrefix = true;
          }
  
          parseURI(xmldbURI, hadXmldbPrefix);
  
          if (mustHaveXMLDB) {
              hadXmldbPrefix = true;
          }
      }
  
      protected XmldbURI(final String collectionPath) {
          this.encodedCollectionPath = collectionPath;
      }
  
      public static XmldbURI xmldbUriFor(final URI uri) throws URISyntaxException {
          return getXmldbURI(uri);
      }
  
      public static XmldbURI xmldbUriFor(final String xmldbURI) throws URISyntaxException {
          return xmldbUriFor(xmldbURI, true);
      }
  
      public static XmldbURI xmldbUriFor(final String xmldbURI, final boolean escape) throws URISyntaxException {
          if (xmldbURI == null) {
              return null;
          }
          final URI uri = new URI(escape ? AnyURIValue.escape(xmldbURI) : xmldbURI);
          return getXmldbURI(uri);
      }
  
      public static XmldbURI xmldbUriFor(final String xmldbURI, final boolean escape, final boolean mustHaveXMLDB) throws URISyntaxException {
          if (xmldbURI == null) {
              return null;
          }
          final URI uri = new URI(escape ? AnyURIValue.escape(xmldbURI) : xmldbURI);
          return getXmldbURI(uri, mustHaveXMLDB);
      }
  
      public static XmldbURI xmldbUriFor(final String accessURI, final String collectionPath) throws URISyntaxException {
          if (collectionPath == null) {
              return null;
          }
          final URI uri = new URI(accessURI + URIUtils.iriToURI(collectionPath));
          return getXmldbURI(uri);
      }
  
      public static XmldbURI create(final URI uri) {
          try {
              return xmldbUriFor(uri);
          } catch (final URISyntaxException e) {
              throw new IllegalArgumentException("Invalid URI: " + e.getMessage());
          }
      }
  
      public static XmldbURI create(final String uri) {
          try {
              return xmldbUriFor(uri);
          } catch (final URISyntaxException e) {
              throw new IllegalArgumentException("Invalid URI: " + e.getMessage());
          }
      }
  
      public static XmldbURI create(final String uri, final boolean mustHaveXMLDB) {
          try {
              return xmldbUriFor(uri, true, mustHaveXMLDB);
          } catch (final URISyntaxException e) {
              throw new IllegalArgumentException("Invalid URI: " + e.getMessage());
          }
      }
  
      public static XmldbURI create(final String accessURI, final String collectionPath) {
          try {
              return xmldbUriFor(accessURI, collectionPath);
          } catch (final URISyntaxException e) {
              throw new IllegalArgumentException("Invalid URI: " + e.getMessage());
          }
      }
  
      public static XmldbURI createInternal(final String collectionPath) {
          return new XmldbURI(collectionPath);
      }
  
      private static XmldbURI getXmldbURI(final URI uri) throws URISyntaxException {
          if ((uri.getScheme() != null) || (uri.getFragment() != null) || (uri.getQuery() != null)) {
              return new FullXmldbURI(uri);
          }
          return new XmldbURI(uri);
          /*
          //TODO : get rid of this and use a more robust approach (dedicated constructor ?) -pb
          //TODO : use named constants
          index = path.lastIndexOf("/xmlrpc");
          if (index > lastIndex) {
          return false;
          }
          //TODO : use named constants
          index = path.lastIndexOf("/webdav");
          if (index > lastIndex) {
          return false;
          }
           */
      }
  
      private static XmldbURI getXmldbURI(final URI uri, final boolean mustHaveXMLDB) throws URISyntaxException {
          if ((uri.getScheme() != null) || (uri.getFragment() != null) || (uri.getQuery() != null)) {
              return new FullXmldbURI(uri, mustHaveXMLDB);
          }
          return new XmldbURI(uri, mustHaveXMLDB);
      }
  
      /**
       * Feeds private members. Receives a URI with the xmldb: scheme already stripped
       *
       * @param xmldbURI       DOCUMENT ME!
       * @param hadXmldbPrefix DOCUMENT ME!
       * @throws URISyntaxException
       */
      protected void parseURI(final URI xmldbURI, final boolean hadXmldbPrefix) throws URISyntaxException {
          splitPath(xmldbURI.getRawPath());
      }
  
      /**
       * Given a java.net.URI.getPath(), <strong>tries</strong> to dispatch the host's context from the collection path as smartly as possible. One
       * would probably prefer a split policy based on the presence of a well-known root collection.
       *
       * @param path The java.net.URI.getPath() provided.
       * @throws URISyntaxException
       */
      protected void splitPath(final String path) throws URISyntaxException {
          encodedCollectionPath = path;
  
          if ((encodedCollectionPath != null) && (encodedCollectionPath.length() > 1) && encodedCollectionPath.endsWith("/")) {
              encodedCollectionPath = encodedCollectionPath.substring(0, encodedCollectionPath.length() - 1);
          }
          //TODO : check that collectionPath starts with DBBroker.ROOT_COLLECTION ?
      }
  
      /**
       * To be called before a context operation with another XmldbURI.
       *
       * @param uri
       * @throws IllegalArgumentException
       */
      protected void checkCompatibilityForContextOperation(final XmldbURI uri) throws IllegalArgumentException {
          if ((this.getInstanceName() != null) && (uri.getInstanceName() != null) && !this.getInstanceName().equals(uri.getInstanceName())) {
              throw new IllegalArgumentException(this.getInstanceName() + " instance differs from " + uri.getInstanceName());
          }
  
          //case insensitive comparison
          if ((this.getHost() != null) && (uri.getHost() != null) && !this.getHost().equalsIgnoreCase(uri.getHost())) {
              throw new IllegalArgumentException(this.getHost() + " host differs from " + uri.getHost());
          }
  
          if ((this.getPort() != NO_PORT) && (uri.getPort() != NO_PORT) && (this.getPort() != uri.getPort())) {
              throw new IllegalArgumentException(this.getPort() + " port differs from " + uri.getPort());
          }
  
          if ((this.getCollectionPath() != null) && (uri.getCollectionPath() != null) && !this.getCollectionPath().equals(uri.getCollectionPath())) {
              throw new IllegalArgumentException(this.getCollectionPath() + " collection differs from " + uri.getCollectionPath());
          }
      }
  
      /**
       * To be called before a collection path operation with another XmldbURI.
       *
       * @param uri
       * @throws IllegalArgumentException
       */
      protected void checkCompatibilityForCollectionOperation(final XmldbURI uri) throws IllegalArgumentException {
          if ((this.getInstanceName() != null) && (uri.getInstanceName() != null) && !this.getInstanceName().equals(uri.getInstanceName())) {
              throw new IllegalArgumentException(this.getInstanceName() + " instance differs from " + uri.getInstanceName());
          }
  
          //case insensitive comparison
          if ((this.getHost() != null) && (uri.getHost() != null) && !this.getHost().equalsIgnoreCase(uri.getHost())) {
              throw new IllegalArgumentException(this.getHost() + " host differs from " + uri.getHost());
          }
  
          if ((this.getPort() != NO_PORT) && (uri.getPort() != NO_PORT) && (this.getPort() != uri.getPort())) {
              throw new IllegalArgumentException(this.getPort() + " port differs from " + uri.getPort());
          }
  
          if ((this.getContext() != null) && (uri.getContext() != null) && !this.getContext().equals(uri.getContext())) {
              throw new IllegalArgumentException(this.getContext() + " context differs from " + uri.getContext());
          }
      }
  
  
      /*
       * It is an error for any of the following private members to throw an exception.
       */
      /*
      private void setInstanceName(String instanceName) {
      String oldInstanceName = this.instanceName;
      try {
      this.instanceName = instanceName;
      recomputeURI();
      } catch (URISyntaxException e) {
      this.instanceName = oldInstanceName;
      throw new IllegalArgumentException("Invalid URI: "+e.getMessage());
      }
      }
  
      private void setContext(String context) throws URISyntaxException {
      String oldContext = this.context;
      try {
      //trims any trailing slash
      if (context != null && context.endsWith("/")) {
      //include root slash if we have a host
      if (this.getHost() != null)
      context = context.substring(0, context.length() - 1);
      }
      this.context = "".equals(context) ? null : context;
      recomputeURI();
      } catch (URISyntaxException e) {
      this.context = oldContext;
      throw e;
      }
      }
  
      private void setCollectionPath(String collectionPath) throws URISyntaxException {
      String oldCollectionPath = collectionPath;
      try {
      if (collectionPath == null)
      this.encodedCollectionPath = null;
      else {
      String escaped = URIUtils.escapeHtmlURI(collectionPath);
      this.encodedCollectionPath = escaped;
      }
      recomputeURI();
      } catch (URISyntaxException e) {
      this.encodedCollectionPath = oldCollectionPath;
      throw e;
      } catch (UnsupportedEncodingException e) {
      wrappedURI = null;
      throw new URISyntaxException(this.toString(), e.getMessage());
      }
      }
       */
  
      /**
       * This returns a proper heirarchical URI - the xmldb scheme is trimmed from the beginning. The scheme will be the instance name, and all other
       * fields will be populated as would be expected from a heirarchical URI
       *
       * @return DOCUMENT ME!
       * @see #getXmldbURI
       */
      public URI getURI() {
          return URI.create(encodedCollectionPath);
      }
  
      /**
       * This returns an xmldb uri. This is the most generic sort of uri - the only fields set in the uri are scheme and schemeSpecificPart
       *
       * @return DOCUMENT ME!
       */
      public URI getXmldbURI() {
          return URI.create(encodedCollectionPath);
      }
  
      public String getInstanceName() {
          return null;
      }
  
      /**
       * Method to return the collection path with reserved characters percent encoded.
       *
       * @return Returns the encoded collection path
       */
      public String getRawCollectionPath() {
          return encodedCollectionPath;
      }
  
      public String getCollectionPath() {
          if (encodedCollectionPath == null) {
              return null;
          }
  
          try {
  
              //TODO: we might want to cache this value
              return URLDecoder.decode(encodedCollectionPath, "UTF-8");
          } catch (final UnsupportedEncodingException e) {
              //Should never happen
              throw new IllegalArgumentException(encodedCollectionPath + " can not be properly escaped");
          }
      }
  
      public XmldbURI toCollectionPathURI() {
          return this instanceof FullXmldbURI ? XmldbURI.create(getRawCollectionPath()) : this;
      }
  
      /**
       * To be called each time a private member that interacts with the wrapped URI is modified.
       *
       * @throws URISyntaxException
       */
      protected void recomputeURI() throws URISyntaxException {
      }
  
      /**
       * To be called each time a private member that interacts with the wrapped URI is modified.
       */
  