# Uri.java

    /*
    ** Module   : Uri.java
    ** Abstract : Implementation of the builtin class.
    **
    ** Copyright (c) 2019-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 CA  20190526 First version, stubs taken by converting the skeleton using FWD.
    ** 002 CA  20190710 Runtime implementation for some APIs.
    ** 003 CA  20191024 Added method support levels and updated the class support level.
    ** 004 CA  20200427 Fixed toLegacyString() and a fix for URI parse.
    ** 005 CA  20200910 Fixed QueryString property - if missing in the URI, must be empty space and not unknown.
    **     ME  20200912 Accumulated changes.
    ** 006 ME  20200925 Rewrite the 4GL way (encode/decode/parse), add missing/new methods as per OE12.2.
    **     ME  20210111 Only add fragment part to encode if not null or empty.
    **     CA  20210113 Renamed clear() to clear_(), to follow NameConverter's rules.
    **     ME  20210205 Use a StringStringMap variable to access the internal map, use longchar for put method.
    ** 007 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy signature.
    **     OM  20210323 Reworked I18n in LOB implementation.
    **     VVT 20210917 Javadoc fixed.
    **     OM  20210917 Code maintenance.
    **     CA  20220120 Do not used TypeFactory.object when the OO reference must not be tracked or registered.
    **                  Do not use TypeFactory.object for internal usages, use ObjectVar if the reference must
    **                  be tracked.
    **                  All TypeFactory.object variable definitions must be done outside of the top-level block.
    **     ME  20210929 Fix return object on parse/resolveRelativeReference.
    **     CA  20220923 Variable definitions (including associated with parameters) must be done always outside of
    **                  the BlockManager API
    ** 008 HC  20230918 Changed fragment part of URI to be decoded if _p2 of parse holds true (#7653-6).
    ** 009 CA  20231113 The 'execute' method must be annotated with LegacySignature Type.Execute, and also can be
    **                  dropped if is a no-op.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it 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 may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.oo.net;

    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.BlockManager.Action;
    import com.goldencode.p2j.util.BlockManager.Condition;
    import com.goldencode.p2j.oo.core.Assert;
    import com.goldencode.p2j.oo.core.LegacyString;
    import com.goldencode.p2j.oo.core.collections.StringStringMap;
    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.security.ContextLocal;

    import static com.goldencode.p2j.util.ArrayAssigner.assignSingle;
    import static com.goldencode.p2j.util.BlockManager.*;
    import static com.goldencode.p2j.report.ReportConstants.*;
    import static com.goldencode.p2j.util.InternalEntry.Type;

    import java.io.UnsupportedEncodingException;
    import java.util.Arrays;
    import java.util.BitSet;
    import java.util.Set;
    import java.util.stream.Collectors;
    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.CharacterCodingException;
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;

    /**
     * Business logic (converted to Java from the 4GL source code in
     * OpenEdge/Net/URI.cls).
     */
    @LegacyResource(resource = "OpenEdge.Net.URI")
    @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_PARTIAL)
    public class Uri extends BaseObject
    {

       private static final int _schemePartIdx = 0;
       private static final int _authorityPartIdx = 1;
       private static final int _pathPartIdx = 2;
       private static final int _queryPartIdx = 3;
       private static final int _fragmentPartIdx = 4;

       @LegacySignature(type = Type.PROPERTY, name = "SCHEME_PART_IDX")
       private static ContextLocal<integer> schemePartIdx = new ContextLocal<integer>()
       {
          protected integer initialValue()
          {
             return TypeFactory.integer(Long.valueOf(_schemePartIdx + 1));
          }
       };

       @LegacySignature(type = Type.PROPERTY, name = "AUTHORITY_PART_IDX")
       private static ContextLocal<integer> authorityPartIdx = new ContextLocal<integer>()
       {
          protected integer initialValue()
          {
             return TypeFactory.integer(Long.valueOf(_authorityPartIdx + 1));
          }
       };

       @LegacySignature(type = Type.PROPERTY, name = "PATH_PART_IDX")
       private static ContextLocal<integer> pathPartIdx = new ContextLocal<integer>()
       {
          protected integer initialValue()
          {
             return TypeFactory.integer(Long.valueOf(_pathPartIdx + 1));
          }
       };

       @LegacySignature(type = Type.PROPERTY, name = "QUERY_PART_IDX")
       private static ContextLocal<integer> queryPartIdx = new ContextLocal<integer>()
       {
          protected integer initialValue()
          {
             return TypeFactory.integer(Long.valueOf(_queryPartIdx + 1));
          }
       };

       @LegacySignature(type = Type.PROPERTY, name = "FRAGMENT_PART_IDX")
       private static ContextLocal<integer> fragmentPartIdx = new ContextLocal<integer>()
       {
          protected integer initialValue()
          {
             return TypeFactory.integer(Long.valueOf(_fragmentPartIdx + 1));
          }
       };

       private static BitSet defaultAllowed;
       private static BitSet schemeAllowed;
       private static BitSet userinfoAllowed;
       private static BitSet hostAllowed;
       private static BitSet pathAllowed;
       private static BitSet queryAllowed;
       private static BitSet fragmentAllowed;
       private static BitSet cookieAllowed;
       private static BitSet localAllowed;

       private enum HostType
       {
          NAME, IPV4, IPLIT
       }

       private character fragment = TypeFactory.character();

       private character host = TypeFactory.character();

       private HostType hostType;

       // default unknown
       private character password = new character();

       private character path = TypeFactory.character();

       // default unknown
       private integer port = new integer();

       private object<? extends com.goldencode.p2j.oo.core.collections.StringStringMap> queryMap = TypeFactory
                .object(com.goldencode.p2j.oo.core.collections.StringStringMap.class);

       private character scheme = TypeFactory.character();

       // default unknown
       private character user = new character();

       // encoding, from java.net.URI
       private final static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'A', 'B', 'C', 'D', 'E', 'F' };

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "SCHEME_PART_IDX")
       public static integer getSchemePartIdx()
       {
          return function(Uri.class, "SCHEME_PART_IDX", integer.class, new Block((Body) () -> {
             returnNormal(schemePartIdx.get());
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "AUTHORITY_PART_IDX")
       public static integer getAuthorityPartIdx()
       {
          return function(Uri.class, "AUTHORITY_PART_IDX", integer.class, new Block((Body) () -> {
             returnNormal(authorityPartIdx.get());
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "PATH_PART_IDX")
       public static integer getPathPartIdx()
       {
          return function(Uri.class, "PATH_PART_IDX", integer.class, new Block((Body) () -> {
             returnNormal(pathPartIdx.get());
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "QUERY_PART_IDX")
       public static integer getQueryPartIdx()
       {
          return function(Uri.class, "QUERY_PART_IDX", integer.class, new Block((Body) () -> {
             returnNormal(queryPartIdx.get());
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "FRAGMENT_PART_IDX")
       public static integer getFragmentPartIdx()
       {
          return function(Uri.class, "FRAGMENT_PART_IDX", integer.class, new Block((Body) () -> {
             returnNormal(fragmentPartIdx.get());
          }));
       }

       private static void _appendEscape(StringBuffer sb, byte b)
       {
          sb.append('%');
          sb.append(hexDigits[(b >> 4) & 0x0f]);
          sb.append(hexDigits[(b >> 0) & 0x0f]);
       }

       private static void _appendEncoded(StringBuffer sb, char c)
       {
          ByteBuffer bb = null;
          try
          {
             bb = StandardCharsets.UTF_8.newEncoder().encode(CharBuffer.wrap("" + c));
          }
          catch (CharacterCodingException x)
          {
             assert false;
          }
          while (bb.hasRemaining())
          {
             int b = bb.get() & 0xff;
             _appendEscape(sb, (byte) b);
          }
       }

       static
       {
          defaultAllowed = new BitSet(128);
          schemeAllowed = new BitSet(128);
          userinfoAllowed = new BitSet(128);
          hostAllowed = new BitSet(128);
          pathAllowed = new BitSet(128);
          queryAllowed = new BitSet(128);
          fragmentAllowed = new BitSet(128);
          cookieAllowed = new BitSet(128);
          localAllowed = new BitSet(128);

          int i;
          for (i = 'a'; i <= 'z'; i++)
          {
             localAllowed.set(i);
          }
          for (i = 'A'; i <= 'Z'; i++)
          {
             localAllowed.set(i);
          }
          for (i = '0'; i <= '9'; i++)
          {
             localAllowed.set(i);
          }

          schemeAllowed.or(localAllowed);

          // [-,.,_,~]
          localAllowed.set('-');
          localAllowed.set('_');
          localAllowed.set('.');
          localAllowed.set('~');

          defaultAllowed.or(localAllowed);

          // [!,$,&,',(,),*,+]+[,]+[;]+[=]
          localAllowed.set('!');
          localAllowed.set('$');
          localAllowed.set('&');
          localAllowed.set(39);
          localAllowed.set('(');
          localAllowed.set(')');
          localAllowed.set('*');
          localAllowed.set('+');
          localAllowed.set(',');
          localAllowed.set(';');
          localAllowed.set('=');

          hostAllowed.or(localAllowed);
          userinfoAllowed.or(localAllowed);

          // [:,@]
          localAllowed.set(':');
          localAllowed.set('@');

          pathAllowed.or(localAllowed);
          queryAllowed.or(localAllowed);
          fragmentAllowed.or(localAllowed);

          // final touch on scheme - add [+,-,.]
          schemeAllowed.set('+');
          schemeAllowed.set('-');
          schemeAllowed.set('.');

          // final touch on userInfo - add [:]
          userinfoAllowed.set(':');

          // final touch on query - add [/,?] remove [&]
          queryAllowed.set('/');
          queryAllowed.set('?');
          queryAllowed.clear('&');

          // TODO: in OE 1.7, queryAllowed must contain '=' char; in OE 1.6, '=' gets encoded.

          // final touch on fragment - add [/,?]
          fragmentAllowed.set('/');
          fragmentAllowed.set('?');

          // cookie
          for (i = 33; i <= 126; i++)
          {
             cookieAllowed.set(i);
          }
          cookieAllowed.clear(34);
          cookieAllowed.clear(44);
          cookieAllowed.clear(59);
          cookieAllowed.clear(92);

       }

       @LegacySignature(type = Type.EXECUTE)
       public void __net_Uri_execute__()
       {
          onBlockLevel(Condition.ERROR, Action.THROW);
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "BaseURI")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getBaseUri()
       {
          return function(this, "BaseURI", character.class, new Block((Body) () -> {
             returnNormal(new character(_getBaseUri()));
          }));
       }

       public String _getBaseUri()
       {
          StringBuilder sb = new StringBuilder(getScheme().getValue()).append("://");

          sb.append(_getAuthority());

          return sb.toString();

       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Fragment")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public character getFragment()
       {
          return function(this, "Fragment", character.class, new Block((Body) () -> {
             returnNormal(fragment);
          }));

       }

       @LegacySignature(type = Type.SETTER, name = "Fragment", parameters = {
                @LegacyParameter(name = "_var", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | CVT_LVL_FULL)
       public void setFragment(final character _var)
       {
          character var = TypeFactory.initInput(_var);

          internalProcedure(Uri.class, this, "Fragment", new Block((Body) () ->
          {
             fragment.assign(var.isUnknown() ? "" : var);
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Host")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getHost()
       {
          return function(this, "Host", character.class, new Block((Body) () -> {
             returnNormal(host);
          }));
       }

       protected String _getAuthority() {
          StringBuilder sb = new StringBuilder();

          if (!user.isUnknown())
          {
             sb.append(user.getValue());
             if (!password.isUnknown())
             {
                sb.append(":").append(getPassword().getValue());
             }
             sb.append("@");
          }

          if (hostType.equals(HostType.IPLIT))
          {
             sb.append("[").append(host.getValue()).append("]");
          }
          else
          {
             sb.append(host.getValue());
          }

          if (!port.isUnknown())
          {
             sb.append(":").append(getPort().intValue());
          }

          return sb.toString();
       }

       private void _setHost(final character host)
       {
          if (host.getValue().startsWith("["))
          {
             this.hostType = HostType.IPLIT;
             int endPos = host.getValue().indexOf("]");
             // 4gl implementation doesn't enforce closing element,
             // it actually trims everything after first closing bracket
             this.host.assign(_substring(host.getValue(), 1, endPos));
          }
          else
          {
             this.hostType = HostType.NAME;
             this.host.assign(host);
          }
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Password")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public character getPassword()
       {
          return function(this, "Password", character.class, new Block((Body) () -> {
             returnNormal(password);
          }));
       }

       @LegacySignature(type = Type.SETTER, name = "Password", parameters = {
                @LegacyParameter(name = "_var", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | CVT_LVL_FULL)
       public void setPassword(final character _var)
       {
          character var = TypeFactory.initInput(_var);

          internalProcedure(Uri.class, this, "Password", new Block((Body) () ->
          {
             password.assign(var);
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Path")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getPath()
       {
          return function(this, "Path", character.class, new Block((Body) () -> {
             returnNormal(path);
          }));
       }

       @LegacySignature(type = Type.SETTER, name = "Path", parameters = {
                @LegacyParameter(name = "_pPath", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void setPath(final character _pPath)
       {
          character pPath = TypeFactory.initInput(_pPath);

          internalProcedure(Uri.class, this, "Path", new Block((Body) () ->
          {
             Assert.notNullOrEmpty(pPath, new character("path"));

             // no validation nor encoding, this is probably a bug and should just call protected setPath method instead
             String newPath = pPath.getValue();

             if (!newPath.startsWith("/"))
                newPath = "/" + newPath;

             path.assign(newPath);
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "SetPath", parameters = {
                @LegacyParameter(name = "_pPath", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       protected void setPath_1(final character _pPath)
       {
          character pPath = TypeFactory.initInput(_pPath);

          internalProcedure(Uri.class, this, "SetPath", new Block((Body) () ->
          {
             String newPath = pPath.isUnknown() ? "/" : pPath.getValue();

             if (!newPath.startsWith("/"))
                newPath = "/" + newPath;

             if (newPath.length() > 1) {
                String[] entries = TextOps.entries(newPath, "/");

                newPath = Arrays.stream(entries).map(p -> _encodeString(p, null, pathAllowed, false)).collect(Collectors.joining("/"));
             }

             setPath(new character(newPath));
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "Port")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public integer getPort()
       {
          return function(this, "Port", integer.class, new Block((Body) () -> {
             returnNormal(port);
          }));
       }

       @LegacySignature(type = Type.SETTER, name = "Port", parameters = {
                @LegacyParameter(name = "_var", type = "INTEGER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void setPort(final integer _var)
       {
          integer var = TypeFactory.initInput(_var);

          internalProcedure(Uri.class, this, "Port", new Block((Body) () ->
          {
             port.assign(var);
          }));
       }

       @LegacySignature(returns = "OBJECT", qualified = "OpenEdge.Core.Collections.IStringStringMap", type = Type.GETTER, name = "QueryMap")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       protected object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> getQueryMap()
       {
          return function(this, "QueryMap", object.class, new Block((Body) () -> {
             if (queryMap.isUnknown())
                queryMap.assign(ObjectOps.newInstance(StringStringMap.class));

             returnNormal(queryMap);
          }));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetQueryMap", qualified = "openedge.core.collections.istringstringmap")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> getQueryMap_1()
       {
          return function(this, "GetQueryMap", object.class, new Block((Body) () -> {
             returnNormal(ObjectOps.newInstance(StringStringMap.class, "I", getQueryMap()));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "QueryString")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getQueryString()
       {
          return function(this, "QueryString", character.class, new Block((Body) () -> {
             returnNormal(new character(_getQueryString(false)));
          }));
       }

       private void _setQueryString(final character var)
       {
          getQueryMap().ref().clear_();
          addQueryString(var, new logical(true));
       }

       public String _getQueryString(boolean encode)
       {
          if (queryMap.isUnknown() || queryMap.ref().isEmpty().booleanValue())
          {
             return "";
          }

          String qryString = queryMap.ref().getMap().entrySet().stream().map(e -> {
             LegacyString key = (LegacyString) e.getKey().ref();
             LegacyString value = (LegacyString) e.getValue().ref();

             String qry = key.toLegacyString().getValue();

             if (!value.getValue().isUnknown())
                qry += "=";

             if (!value.isNullOrEmpty().booleanValue())
                qry += value.toLegacyString().getValue();

             return encode ? encodeQuery(qry) : qry;
          }).reduce("?", (s, e) -> {
             if ("?".equals(s))
                return s + e;

             return s + "&" + e;
          });

          return qryString;
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "RelativeURI")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getRelativeUri()
       {
          return function(this, "RelativeURI", character.class, new Block((Body) () -> {
             returnNormal(new character(_getRelativeUri()));
          }));
       }

       public String _getRelativeUri()
       {
          StringBuilder sb = new StringBuilder(path.getValue());
          String extra = _getQueryString(false);

          if (!extra.isEmpty())
             sb.append(extra);

          extra = getFragment().getValue();
          if (extra != null && !extra.isEmpty())
             sb.append("#").append(extra);

          return sb.toString();
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Scheme")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getScheme()
       {
          return function(this, "Scheme", character.class, new Block((Body) () -> {
             returnNormal(scheme);
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "User")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character getUser()
       {
          return function(this, "User", character.class, new Block((Body) () -> {
             returnNormal(user);
          }));
       }

       @LegacySignature(type = Type.SETTER, name = "User", parameters = {
                @LegacyParameter(name = "_var", type = "CHARACTER", mode = "INPUT") })
       public void setUser(final character _var)
       {
          character var = TypeFactory.initInput(_var);

          internalProcedure(Uri.class, this, "User", new Block((Body) () ->
          {
             user.assign(var);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR)
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static void __net_Uri_constructor__static__()
       {
          externalProcedure(Uri.class, new Block((Body) () ->
          {
             onBlockLevel(Condition.ERROR, Action.THROW);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "host", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void __net_Uri_constructor__(final character _host)
       {
          __net_Uri_constructor__(UriSchemeEnum.http.ref().toLegacyString(), _host);
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "scheme", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "host", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void __net_Uri_constructor__(final character _scheme, final character _host)
       {
          __net_Uri_constructor__(_scheme, _host, new integer());
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "scheme", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "host", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "port", type = "INTEGER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void __net_Uri_constructor__(final character _scheme, final character _host,
                final integer _port)
       {
          __net_Uri_constructor__(_scheme, _host, _port, new character("/"),
                   ObjectOps.newInstance(StringStringMap.class), TypeFactory.character());
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "scheme", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "host", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "port", type = "INTEGER", mode = "INPUT"),
                @LegacyParameter(name = "path", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "query", type = "OBJECT", qualified = "openedge.core.collections.istringstringmap", mode = "INPUT"),
                @LegacyParameter(name = "fragment", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void __net_Uri_constructor__(final character _scheme, final character _host,
                final integer _port, final character _path,
                final object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> _query,
                final character _fragment)
       {
          character scheme = TypeFactory.initInput(_scheme);
          character host = TypeFactory.initInput(_host);
          integer port = TypeFactory.initInput(_port);
          character path = TypeFactory.initInput(_path);
          object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> query = TypeFactory
                   .initInput(_query);
          character fragment = TypeFactory.initInput(_fragment);

          internalProcedure(this, "__net_Uri_constructor__", new Block((Body) () -> {
             __lang_BaseObject_constructor__();
             Assert.notNullOrEmpty(scheme, new character("Scheme"));
             Assert.notNullOrEmpty(host, new character("Host"));
             Assert.notNull(query, new character("QueryMap"));
             Assert.notNullOrEmpty(path, new character("Path"));

             this.scheme.assign(scheme);
             this.queryMap.assign(query);

             _setHost(host);
             setPort(port);
             setPath(path);
             setFragment(fragment);
          }));
       }

       @LegacySignature(type = Type.CONSTRUCTOR, parameters = {
                @LegacyParameter(name = "pScheme", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pAuthority", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pPath", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pQuery", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pFragment", type = "CHARACTER", mode = "INPUT") })
       public void __net_Uri_constructor__(final character pScheme, final character pAuthority,
                final character pPath, final character pQuery, final character pFragment)
       {
          character scheme = TypeFactory.initInput(pScheme);
          character authority = TypeFactory.initInput(pAuthority);
          character path = TypeFactory.initInput(pPath);
          character fragment = TypeFactory.initInput(pFragment);
          character query = TypeFactory.initInput(pQuery);

          internalProcedure(this, "__net_Uri_constructor__", new Block((Body) () -> {
             __lang_BaseObject_constructor__();
             Assert.notNullOrEmpty(scheme, new character("URI Scheme"));
             this.scheme.assign(scheme);

             setAuthority(authority, new logical(true));

             setPath_1(path);

             addQueryString(query, new logical(true));
             setFragment(fragment);
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "AddPathSegment", parameters = {
                @LegacyParameter(name = "_p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | CVT_LVL_FULL)
       public void addPathSegment(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          internalProcedure(this, "AddPathSegment", new Block((Body) () -> {
             character seg = encodePath(p1);

             if (!seg.getValue().trim().isEmpty())
             {
                if (this.path.getValue().endsWith("/"))
                   this.path.assign(this.path.getValue() + seg.getValue());
                else
                   this.path.assign(this.path.getValue() + "/" + seg.getValue());

             }
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "AddQuery", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void addQuery(final character _p1)
       {
          addQuery(_p1, new character());
       }

       @LegacySignature(type = Type.METHOD, name = "AddQuery", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public void addQuery(final character _p1, final character _p2)
       {
          character p1 = TypeFactory.initInput(_p1);
          character p2 = TypeFactory.initInput(_p2);

          internalProcedure(this, "AddQuery", new Block((Body) () -> {
             Assert.notNullOrEmpty(p1, new character("Query name"));
             getQueryMap().ref().put(p1, p2.isUnknown() ? new longchar() : new longchar(encodeQuery(p2)));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Decode")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public character decode()
       {
          return function(this, "Decode", character.class, new Block((Body) () -> {
             returnNormal(decode(this.toLegacyString()));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Decode", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character decode(final character p1)
       {
          return function(Uri.class, "Decode", character.class, new Block((Body) () -> {
             returnNormal(decode(p1, new character()));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Decode", parameters = {
                @LegacyParameter(name = "data", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "encoding", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character decode(final character _data, final character _encoding)
       {
          return function(Uri.class, "Decode", character.class, new Block((Body) () -> {
             String data = _data.isUnknown() ? "" : _data.toStringMessage();
             String encoding = _encoding.isUnknown() ? "" : _encoding.getValue().trim();

             if (data.trim().isEmpty())
             {
                returnNormal(_data);
             }

             try
             {
                String uri = _decode(data);
                // in FWD, the [character] always uses the internal CP, CP conversion is a NOP
                returnNormal(new character(uri));
             }
             catch (UnsupportedEncodingException e)
             {
                undoThrow(AppError.newInstance(String.format("Cannot decode malformed string %s", data), 0));
             }
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Decode", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static character decode(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "Decode", character.class, new Block((Body) () -> {
             returnNormal(decode(p1.ref().toLegacyString(), p1.ref().getEncoding()));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Encode")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encode()
       {
          return function(this, "Encode", character.class, new Block((Body) () -> {
             returnNormal(encodeUri(new object(this)));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Encode", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "OBJECT", qualified = "openedge.net.uriencodingtypeenum", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static character encode(final character p1,
                final object<? extends com.goldencode.p2j.oo.net.UriEncodingTypeEnum> p2)
       {
          return encode(p1, p2, new logical(false));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Encode", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "OBJECT", qualified = "openedge.net.uriencodingtypeenum", mode = "INPUT"),
                @LegacyParameter(name = "p3", type = "LOGICAL", mode = "INPUT") })
       public static character encode(final character _p1,
                final object<? extends com.goldencode.p2j.oo.net.UriEncodingTypeEnum> _p2,
                final logical _p3)
       {
          character p1 = TypeFactory.initInput(_p1);
          object<? extends com.goldencode.p2j.oo.net.UriEncodingTypeEnum> p2 = TypeFactory
                   .initInput(_p2);
          logical reEncode = TypeFactory.initInput(_p3);

          return function(Uri.class, "Encode", character.class, new Block((Body) () -> {

             if (p1.isUnknown() || p1.getValue().trim().isEmpty())
                returnNormal(new character(""));

             if (reEncode.isUnknown())
             {
                reEncode.assign(true);
             }

             if (!p2._isValid())
             {
                returnNormal(new character(
                         _encodeString(p1.getValue(), "?", defaultAllowed, reEncode.booleanValue())));
             }

             switch (p2.ref().getValue().toString())
             {
                case "1":
                   //scheme
                   returnNormal(new character(_encodeString(p1.getValue(), "?", schemeAllowed,
                            reEncode.booleanValue())));

                case "2":
                   //host
                   returnNormal(new character(
                            _encodeString(p1.getValue(), "?", hostAllowed, reEncode.booleanValue())));

                case "3":
                   //path
                   returnNormal(new character(
                            _encodeString(p1.getValue(), "?", pathAllowed, reEncode.booleanValue())));

                case "4":
                   //query
                   returnNormal(new character(
                            _encodeString(p1.getValue(), "?", queryAllowed, reEncode.booleanValue())));

                case "5":
                   //fragment
                   returnNormal(new character(_encodeString(p1.getValue(), "?", fragmentAllowed,
                            reEncode.booleanValue())));

                case "6":
                   //cookie
                   returnNormal(new character(_encodeString(p1.getValue(), "?", cookieAllowed,
                            reEncode.booleanValue())));

                default:
                   returnNormal(new character(_encodeString(p1.getValue(), "?", defaultAllowed,
                            reEncode.booleanValue())));
             }

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Encode", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.net.uri", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static character encode(final object<? extends com.goldencode.p2j.oo.net.Uri> p1)
       {
          return encodeUri(p1);
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeCookie", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeCookie(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeCookie", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.getValue(), "?", cookieAllowed, false));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeCookie", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeCookie(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeCookie", character.class, new Block((Body) () -> {
             returnNormal(encodeCookie(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue()));
          }));
       }

       public static String encodeCookie(final String data, final String codePage)
       {
          return _encodeString(data, codePage, cookieAllowed, false);
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeFragment")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encodeFragment()
       {
          return function(this, "EncodeFragment", character.class, new Block((Body) () -> {

             returnNormal(encodeFragment(this.fragment));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeFragment", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeFragment(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeFragment", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.getValue(), "?", fragmentAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeFragment", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeFragment(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeFragment", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), fragmentAllowed, false));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeHost")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encodeHost()
       {
          return function(this, "EncodeHost", character.class, new Block((Body) () -> {

             if (this.hostType == HostType.IPLIT)
             {
                returnNormal(new character("[" + encodeHost(this.host).getValue() + "]"));
             }
             else
                returnNormal(encodeHost(this.host));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeHost", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeHost(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeHost", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.getValue(), "?", hostAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeHost", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeHost(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeHost", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), hostAllowed, false));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodePath", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodePath(final character p1)
       {
          return new character(_encodeString(p1.getValue(), "?", pathAllowed, false));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodePath", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodePath(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodePath", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), pathAllowed, false));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeQuery")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encodeQuery()
       {
          return function(this, "EncodeQuery", character.class, new Block((Body) () -> {
             returnNormal(encodeQuery_1(new object(this)));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeQuery", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeQuery(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeQuery", character.class, new Block((Body) () -> {
             returnNormal(encodeQuery(p1.getValue()));
          }));
       }

       public static String encodeQuery(String query) {
          return _encodeString(query, "?", queryAllowed, false);
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeQuery", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeQuery(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeQuery", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), queryAllowed, false));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeQuery", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.net.uri", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeQuery_1(
                final object<? extends com.goldencode.p2j.oo.net.Uri> _p1)
       {
          object<? extends com.goldencode.p2j.oo.net.Uri> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeQuery", character.class, new Block((Body) () -> {

             Assert.notNull(p1, new character("URI"));

             returnNormal(p1.ref()._getQueryString(true));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeScheme")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encodeScheme()
       {
          return encodeScheme(this.scheme);
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeScheme", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeScheme(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeScheme", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.getValue(), "?", schemeAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeScheme", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeScheme(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeScheme", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), schemeAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeString", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeString(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeString", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.getValue(), "?", defaultAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeString", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeString(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeString", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), defaultAllowed, false));
          }));
       }

       private static String _encodeString(String str, String sourceCodepage, BitSet allowed,
                boolean reEncode)
       {

          StringBuffer sb = null;

          if (str == null || str.trim().isEmpty())
             return "";

          if (sourceCodepage == null)
             sourceCodepage = I18nOps.getCPInternal().getValue();

          // TODO: codepage conversion
          // str = I18nOps.codePageConvertWorker(str, sourceCodepage, "UTF-8");

          for (int i = 0; i < str.length(); i++)
          {
             char c = str.charAt(i);

             if (c == '\u0025' && !reEncode)
             {
                if (sb != null)
                {
                   sb.append(str.substring(i, Math.min(i + 3, str.length())));
                }
                i = i + 2;
             }
             else if (c >= '\u007F' || c <= '\u001F' || !allowed.get(c))
             {
                if (sb == null)
                {
                   sb = new StringBuffer();
                   sb.append(str.substring(0, i));
                }

                _appendEncoded(sb, c);
             }
             else
             {
                if (sb != null)
                {
                   sb.append(c);
                }
             }
          }

          return (sb == null) ? str : sb.toString();
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeURI", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.net.uri", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeUri(final object<? extends com.goldencode.p2j.oo.net.Uri> _p1)
       {
          object<? extends com.goldencode.p2j.oo.net.Uri> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeURI", character.class, new Block((Body) () -> {

             String encodedUri = new String();

             Assert.notNull(p1, new character("URI"));

             //scheme
             encodedUri = encodeScheme(p1.ref().getScheme()).getValue() + "://";

             //user+pass
             if (!p1.ref().getUser().isUnknown())
             {
                encodedUri = encodedUri + p1.ref().encodeUserinfo().getValue() + "@";
             }

             //host
             encodedUri = encodedUri + p1.ref().encodeHost().getValue();

             //port
             if (!p1.ref().getPort().isUnknown())
             {
                encodedUri = encodedUri + ":" + p1.ref().getPort().getValue();
             }

             //path + query
             encodedUri = encodedUri + p1.ref().getPath().getValue()
                      + p1.ref().encodeQuery().getValue();

             //fragment
             if (!TextOps.isEmpty(p1.ref().getFragment()))
             {
                encodedUri = encodedUri + "#" + encodeFragment(p1.ref().getFragment()).getValue();
             }

             returnNormal(new character(encodedUri));
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeUserinfo")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character encodeUserinfo()
       {
          return function(Uri.class, "EncodeUserinfo", character.class, new Block((Body) () -> {

             if (this.user.isUnknown())
             {
                returnNormal(new character(""));
             }

             character u = this.getUser();

             String authority = new String(_encodeString(u.getValue(), "?", hostAllowed, false));

             if (!this.password.isUnknown())
             {
                authority = authority + ":"
                         + _encodeString(this.getPassword().getValue(), "?", hostAllowed, false);
             }

             returnNormal(new character(authority));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeUserinfo", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeUserinfo(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeUserinfo", character.class, new Block((Body) () -> {

             returnNormal(_encodeString(p1.getValue(), "?", userinfoAllowed, false));

          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "EncodeUserinfo", parameters = {
                @LegacyParameter(name = "p1", type = "OBJECT", qualified = "openedge.core.string", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character encodeUserinfo(
                final object<? extends com.goldencode.p2j.oo.core.LegacyString> _p1)
       {
          object<? extends com.goldencode.p2j.oo.core.LegacyString> p1 = TypeFactory.initInput(_p1);

          return function(Uri.class, "EncodeUserinfo", character.class, new Block((Body) () -> {
             returnNormal(_encodeString(p1.ref().toLegacyString().getValue(),
                      p1.ref().getEncoding().getValue(), userinfoAllowed, false));
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "GetPathSegments", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", extent = -1, mode = "OUTPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public integer getPathSegments(final OutputExtentParameter<character> extp1)
       {
          character[][] p1 = { TypeFactory.initOutput(extp1) };

          return function(this, "GetPathSegments", integer.class, new Block((Body) () -> {
             String[] entries = TextOps.entries(this.path.getValue(), "/");

             p1[0] = ArrayAssigner.resize(p1[0], entries.length - 1);

             for (int i = 0; i < p1[0].length; i++)
             {
                assignSingle(p1[0], i + 1, decode(new character(entries[i + 1])));
             }

             returnNormal(p1[0].length);
          }));
       }

       @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "GetQueryNames", parameters = {
                @LegacyParameter(name = "np1", type = "CHARACTER", extent = -1, mode = "OUTPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public integer getQueryNames(final OutputExtentParameter<character> extnp1)
       {
          character[][] np1 = { TypeFactory.initOutput(extnp1) };

          return function(this, "GetQueryNames", integer.class, new Block((Body) () -> {
             if (queryMap.isUnknown())
             {
                returnNormal(0);
             }
             else
             {
                Set<object> keys = this.queryMap.ref().getMap().keySet();

                if (!keys.isEmpty())
                {
                   np1[0] = ArrayAssigner.resize(np1[0], keys.size());

                   int idx = 0;

                   for (object key : keys)
                   {
                      assignSingle(np1[0], ++idx, key.toString());
                   }
                }
                returnNormal(keys.size());
             }
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetQueryValue", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public character getQueryValue(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(this, "GetQueryValue", character.class, new Block((Body) () -> {
             Assert.notNullOrEmpty(p1, new character("Query name"));

             if (queryMap.isUnknown())
             {
                returnNormal(new character());
             }
             else
             {
                returnNormal(new character(this.queryMap.ref().get(p1)));
             }
          }));
       }

       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "HasQueryName", parameters = {
                @LegacyParameter(name = "pName", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public logical hasQueryName(final character pName)
       {
          return function(Uri.class, this, "HasQueryName", logical.class, new Block((Body) () -> {
             returnNormal(!queryMap.isUnknown() && queryMap.ref().containsKey(pName).booleanValue());
          }));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "Parse", qualified = "openedge.net.uri", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_BASIC)
       public static object<? extends com.goldencode.p2j.oo.net.Uri> parse(final character _p1)
       {
          return parse(_p1, new logical(true));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "Parse", qualified = "openedge.net.uri", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "p2", type = "LOGICAL", mode = "INPUT") })
       public static object<? extends Uri> parse(final character _p1, final logical _p2)
       {

          character p1 = TypeFactory.initInput(_p1);
          logical p2 = TypeFactory.initInput(_p2);
          object<Uri> obj = TypeFactory.object(Uri.class);

          return function(Uri.class, "Parse", object.class, new Block((Body) () -> {
             Assert.notNullOrEmpty(p1, new character("URI string"));

             character[] relative = splitUri(p1);

             // defaults to HTTP
             if (TextOps.isEmpty(relative[_schemePartIdx]))
                relative[_schemePartIdx] = UriSchemeEnum.http.ref().toLegacyString();

             // check authority as this is mandatory
             if (TextOps.isEmpty(relative[_authorityPartIdx]))
                undoThrow(AppError.newInstance(TextOps.substitute("Unable to parse malformed URI: &1", p1), new integer(0)));

             // authority seems to always be decoded
             relative[_authorityPartIdx] = decode(relative[_authorityPartIdx]);

             // unless false decode by default schema, path, query, fragment
             if (p2.isUnknown() || p2.booleanValue())
             {
                relative[_schemePartIdx] = decode(relative[_schemePartIdx]);
                relative[_pathPartIdx] = decode(relative[_pathPartIdx]);
                relative[_fragmentPartIdx] = decode(relative[_fragmentPartIdx]);
                relative[_queryPartIdx] = decode(relative[_queryPartIdx]);

                obj.assign(ObjectOps.newInstance(Uri.class, "IIIII", relative[_schemePartIdx],
                         relative[_authorityPartIdx], relative[_pathPartIdx], relative[_queryPartIdx],
                         relative[_fragmentPartIdx]));
             }
             else
             {
                // don't set query string in ctor to avoid decode
                obj.assign(ObjectOps.newInstance(Uri.class, "IIIII", relative[_schemePartIdx],
                         relative[_authorityPartIdx], relative[_pathPartIdx], "",
                         relative[_fragmentPartIdx]));

                obj.ref().addQueryString(relative[_queryPartIdx], new logical(false));
             }

             returnNormal(obj);
          }));
       }

       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "RemoveQuery", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public logical removeQuery(final character _p1)
       {
          character p1 = TypeFactory.initInput(_p1);

          return function(this, "RemoveQuery", logical.class, new Block((Body) () -> {

             Assert.notNullOrEmpty(p1, new character("Query name"));

             boolean hasQuery = !queryMap.isUnknown() && queryMap.ref().containsKey(p1).booleanValue();

             if (hasQuery)
                this.queryMap.ref().remove(p1);

             returnNormal(hasQuery);
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "SetPath", parameters = {
                @LegacyParameter(name = "p1", type = "CHARACTER", extent = -1, mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void setPath(final character[] _p1)
       {
          character[] p1 = TypeFactory.initInput(_p1);

          internalProcedure(this, "SetPath", new Block((Body) () -> {
             String path = new String();
             String pathDelim = "";
             for (character a : p1)
             {
                String partialSeg = encodePath(a).getValue();
                if (partialSeg != "")
                {
                   if (path == "")
                   {
                      path = "/";
                   }
                   path = path + pathDelim + partialSeg;
                   pathDelim = "/";
                }
             }
             setPath(new character(path));
          }));
       }

       @LegacySignature(type = Type.METHOD, name = "SetQueryMap", parameters = {
                @LegacyParameter(name = "pQueryMap", type = "OBJECT", qualified = "openedge.core.collections.istringstringmap", mode = "INPUT") })
       public void setQueryMap_1(
                final object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> _pQueryMap)
       {
          object<? extends com.goldencode.p2j.oo.core.collections.IStringStringMap> pQueryMap = TypeFactory
                   .initInput(_pQueryMap);

          internalProcedure(Uri.class, this, "SetQueryMap", new Block((Body) () -> {
             if (_pQueryMap._isValid() && pQueryMap.ref().getSize().getSize() > 0)
             {
                getQueryMap().ref().putAll_2(pQueryMap);
             }
          }));
       }

       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "ToString")
       @Override
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character toLegacyString()
       {
          return new character(_getBaseUri() + _getRelativeUri());
       }

       @LegacySignature(type = Type.METHOD, name = "SetAuthority", parameters = {
                @LegacyParameter(name = "pAuthority", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pDecode", type = "LOGICAL", mode = "INPUT") })
       protected void setAuthority(final character pAuthority, final logical pDecode)
       {
          Assert.notNullOrEmpty(pAuthority, new character("Authority"));

          String auth = pAuthority.getValue();
          int idx = auth.indexOf("@");

          // credentials set (user:passwd)
          if (idx != -1)
          {
             String[] credentials = auth.substring(0, idx).split(":");
             user.assign(credentials[0]);

             if (credentials.length > 1)
                password.assign(credentials[1]);

             auth = auth.substring(idx + 1);
          }

          idx = auth.indexOf(":");
          if (idx != -1)
          {
             port.assign(auth.substring(idx + 1));
             auth = auth.substring(0, idx);
          }

          _setHost(pDecode.isUnknown() || pDecode.booleanValue() ? decode(new character(auth)) : new character(auth));
       }

       @LegacySignature(type = Type.METHOD, name = "AddQueryString", parameters = {
                @LegacyParameter(name = "pQuery", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "pDecode", type = "LOGICAL", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public void addQueryString(character pQuery, logical pDecode)
       {
          if (!pQuery.isUnknown() && !pQuery.getValue().trim().isEmpty())
          {
             Arrays.stream(pQuery.getValue().split("&")).forEach(e -> {
                String[] entries = e.split("=");
                character pName = pDecode.booleanValue() ? decode(new character(entries[0])) : new character(entries[0]);
                if (entries.length == 1)
                   addQuery(pName);
                else
                   addQuery(pName, new character(entries[1]));
             });
          }
       }

       /**
        * Parses this URI into its component parts
        * [1] scheme (http/https)
        * [2] authority (host:port)
        * [3] path
        * [4] query
        * [5] fragment

        * @return An array of component parts
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "Split", extent = -1)
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public character[] split()
       {
          return extentFunction(Uri.class, this, "Split", character.class, new Block((Body) () -> {
             String[] entries = new String[] {scheme.getValue(), _getAuthority(), path.getValue(), _getQueryString(false), fragment.getValue()};

             if (entries[_queryPartIdx].startsWith("?"))
                entries[_queryPartIdx] = entries[_queryPartIdx].substring(1);

             returnExtentNormal(TypeFactory.characterExtent(entries.length, entries));
          }));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "ResolveRelativeReference", qualified = "openedge.net.uri", parameters = {
                @LegacyParameter(name = "oUriBase", type = "OBJECT", qualified = "openedge.net.uri", mode = "INPUT"),
                @LegacyParameter(name = "cRelativeRef", type = "CHARACTER", mode = "INPUT"), })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static object<? extends Uri> resolveRelativeReference(object<? extends Uri> oUriBase,
                character cRelativeRef)
       {
          object<Uri> obj = TypeFactory.object(Uri.class);

          return function(Uri.class, "ResolveRelativeReference", object.class,
                   new Block((Body) () -> {
                      Assert.notNull(oUriBase, new character("Base URI"));

                      String relRef = cRelativeRef.getValue();
                      character[] base = oUriBase.ref().split();

                      // nothing to do, return base object
                      if (relRef == null || relRef.trim().isEmpty())
                      {
                         // same schema, authority, path and query but fragment is reset
                         obj.assign(ObjectOps.newInstance(Uri.class, "IIIII",
                                  base[_schemePartIdx], base[_authorityPartIdx],
                                  base[_pathPartIdx], base[_queryPartIdx], ""));
                      }
                      else
                      {

                         String[] relative = _splitUri(relRef);

                         // no scheme, everything is considered path
                         if ("".equals(relative[0]))
                         {
                            // if new path is absolute
                            if (relative[_pathPartIdx].startsWith("/")) {
                               obj.assign(ObjectOps.newInstance(Uri.class, "IIIII",
                                        base[_schemePartIdx], base[_authorityPartIdx],
                                        relative[_pathPartIdx], relative[_queryPartIdx],
                                        relative[_fragmentPartIdx]));
                            }
                            else {
                               // relative path, resolve it against current
                               obj.assign(ObjectOps.newInstance(Uri.class, "IIIII",
                                        base[_schemePartIdx], base[_authorityPartIdx],
                                        _resolvePath(base[_pathPartIdx].getValue(), relative[_pathPartIdx]),
                                        relative[_queryPartIdx], relative[_fragmentPartIdx]));
                            }
                         }
                         else
                         {
                            // different scheme, complete override
                            obj.assign(ObjectOps.newInstance(Uri.class, "IIIII",
                                     relative[_schemePartIdx], relative[_authorityPartIdx],
                                     relative[_pathPartIdx], relative[_queryPartIdx],
                                     relative[_fragmentPartIdx]));
                         }

                      }

                      returnNormal(obj);
                   }));
       }

       /**
        * Parses an potentially-URI-containing string into URI component parts.
        * This method will not fail, and will return empty values for an element of the
        * URI if the string cannot be parsed or fails to parse somehow. So if the string contains
        * http//example.com
        * the auhority will be 'example.com' and the scheme empty (since there's no :).
        * [1] scheme (http/https)
        * [2] authority (host:port)
        * [3] path
        * [4] query
        * [5] fragment
        *
        * @param pData the URI string
        *
        * @return An array of component parts
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "SplitUri", extent = -1, parameters = {
                @LegacyParameter(name = "pData", type = "CHARACTER", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_STUB)
       public static character[] splitUri(final character pData)
       {
          character[] res = TypeFactory.characterExtent();

          return extentFunction(Uri.class, "SplitUri", character.class, new Block((Body) () -> {
             String[] entries = _splitUri(pData.getValue());
             if (entries.length == 0)
             {
                returnExtentNormal(res);
             }

             returnExtentNormal(TypeFactory.characterExtent(entries.length, entries));
          }));
       }

       private static String[] _splitUri(final String pData) {

          String[] entries = new String[] {"", "", "", "", ""};

          if (pData != null && !pData.trim().isEmpty()) {
             // first position seems to be used, doesn't even have to be after scheme/authority
             int endIdx = pData.indexOf("#");

             // fragment seems to be extracted first, everything after first # is considered fragment
             if (endIdx != -1) {
                entries[_fragmentPartIdx] = _substring(pData, endIdx + 1, -1);
             }

             // query comes second, everything between first ? and fragment separator or end of string
             int qryIdx = pData.indexOf("?");

             if (qryIdx != -1 && (endIdx == -1 || qryIdx < endIdx)) {
                entries[_queryPartIdx] = _substring(pData, qryIdx + 1, endIdx);
                endIdx = qryIdx;
             }

             // if no scheme/authority everything lands in path
             int pathIdx = 0;

             // scheme separator must be before query/fragment else is ignored
             int schemaIdx = pData.indexOf(":");

             if (schemaIdx > 0 && (endIdx == -1 || schemaIdx < endIdx))
             {
                // remaining // separator is not required for schema extraction
                entries[_schemePartIdx] = _substring(pData, 0, schemaIdx++);
                pathIdx = schemaIdx;
             }

             int authIdx = pData.indexOf("//", schemaIdx);

             if (authIdx != -1 && (endIdx == -1 || authIdx < endIdx)) {
                // if we have authority everything up to path/query/fragment is used
                authIdx += 2;
                pathIdx = pData.indexOf("/", authIdx);

                entries[_authorityPartIdx] = _substring(pData, authIdx, pathIdx != -1 ? pathIdx : endIdx);
             }

             // everything up to query/fragment seems to land in path
             if(pathIdx != -1)
                entries[_pathPartIdx] = _substring(pData, pathIdx, endIdx);

          }

          return entries;
       }

       // From URLDecoder, 4GL does not throw error on incomplete or invalid escape sequence (%)
       // nor does replace plus with space like Java decoder does
       private static String _decode(String s) throws UnsupportedEncodingException
       {

          boolean needToChange = false;
          int numChars = s.length();
          StringBuffer sb = new StringBuffer(numChars > 500 ? numChars / 2 : numChars);
          int i = 0;

          char c;
          while (i < numChars)
          {
             c = s.charAt(i);
             byte[] bytes = null;
             switch (c)
             {
                case '%':
                   /*
                    * Starting with this instance of %, process all
                    * consecutive substrings of the form %xy. Each
                    * substring %xy will yield a byte. Convert all
                    * consecutive  bytes obtained this way to whatever
                    * character(s) they represent in the provided
                    * encoding.
                    */
                   if (bytes == null)
                      bytes = new byte[4];
                   int pos = 0;
                   int expected = -1;

                   while (((i + 2) < numChars) && (c == '%'))
                   {
                      try
                      {
                         int v = Integer.parseInt(s.substring(i + 1, i + 3), 16);
                         // not an error in 4gl, left not decoded
                         if (v < 0)
                            break;

                         if (pos == 0)
                         {
                            if (v <= 128)
                            {
                               expected = 1;
                            }
                            // for UTF-8 only sequence start seems to be validated
                            // - C0-C1, F5-FF invalid
                            // - C2-DF (2 bytes)
                            // - E0-EF (3 bytes)
                            // - F0-F4 (4 bytes)
                            else if (v >= 194 && v < 245)
                            {
                               if (v < 224)
                                  expected = 2;
                               else if (v < 240)
                                  expected = 3;
                               else
                                  expected = 4;
                            }
                            else
                            {
                               expected = -1;
                            }
                         }

                         bytes[pos++] = (byte) v;
                         i += 3;
                         if (i < numChars)
                            c = s.charAt(i);

                         // already a full sequence, reset position
                         if (c == '%' && pos == expected)
                         {
                            sb.append(new String(bytes, 0, pos, StandardCharsets.UTF_8));
                            needToChange = true;
                            pos = 0;
                         }
                      }
                      catch (NumberFormatException nfe)
                      {
                         pos = -1;
                         break;
                      }
                   }

                   // at least one escape sequence found
                   if (pos >= 0)
                   {
                      // last escape sequence in the list, not yet decoded
                      if (pos > 0)
                      {
                         if (expected != pos)
                            throw new UnsupportedEncodingException();

                         sb.append(new String(bytes, 0, pos, StandardCharsets.UTF_8));
                         needToChange = true;
                      }
                      break;
                   }

                default:
                   sb.append(c);
                   i++;
                   break;
             }
          }

          return (needToChange ? sb.toString() : s);
       }

       private static String _substring(String data, int start, int end) {
          if (data == null || start < 0)
             return data;

          if (end == -1)
             return data.substring(start);

          return data.substring(start, end);
       }

       private static String _resolvePath(String base, String relative) {
          if (!base.endsWith("/"))
             base += "/";

          String[] entries = TextOps.entries(base + "../" + relative, "/");
          int crt = 0;

          for (int i = 0; i < entries.length; i++)
          {
             if (".".equals(entries[i])) {
                // current, ignored
             } else if ("..".equals(entries[i])) {
                // safeguard, can't pass up the root
                if (crt > 0)
                   crt--;
             } else {
                entries[crt++] = entries[i];
             }
          }

          return Arrays.stream(entries, 0, crt).reduce("/", (s, e) -> {
             if ("/".equals(s))
                return s + e;

             return s + "/" + e;
          });
       }

    }