/// <summary>
        /// Given a Specs object, return a SingleID representing the special inverse
        /// of that ID. If there is no special inverse then return null.
        /// </summary>
        ///
        /// <returns>a SingleID or null. Returned object always has 'filter' field of
        /// null.</returns>
        private static TransliteratorIDParser.SingleID  SpecsToSpecialInverse(TransliteratorIDParser.Specs specs)
        {
            if (!specs.source.Equals(ANY, StringComparison.InvariantCultureIgnoreCase))
            {
                return(null);
            }
            String inverseTarget = (String)SPECIAL_INVERSES[new CaseInsensitiveString(specs.target)];

            if (inverseTarget != null)
            {
                // If the original ID contained "Any-" then make the
                // special inverse "Any-Foo"; otherwise make it "Foo".
                // So "Any-NFC" => "Any-NFD" but "NFC" => "NFD".
                StringBuilder buf = new StringBuilder();
                if (specs.filter != null)
                {
                    buf.Append(specs.filter);
                }
                if (specs.sawSource)
                {
                    buf.Append(ANY).Append(TARGET_SEP);
                }
                buf.Append(inverseTarget);

                String basicID_0 = ANY + TARGET_SEP + inverseTarget;

                if (specs.variant != null)
                {
                    buf.Append(VARIANT_SEP).Append(specs.variant);
                    basicID_0 = basicID_0 + VARIANT_SEP + specs.variant;
                }
                return(new TransliteratorIDParser.SingleID(buf.ToString(), basicID_0));
            }
            return(null);
        }
        /// <summary>
        /// Parse a filter ID, that is, an ID of the general form "[f1] s1-t1/v1",
        /// with the filters optional, and the variants optional.
        /// </summary>
        ///
        /// <param name="id">the id to be parsed</param>
        /// <param name="pos">INPUT-OUTPUT parameter. On input, the position of the firstcharacter to parse. On output, the position after the lastcharacter parsed.</param>
        /// <returns>a SingleID object or null if the parse fails</returns>
        public static TransliteratorIDParser.SingleID  ParseFilterID(String id, int[] pos)
        {
            int start = pos[0];

            TransliteratorIDParser.Specs specs = ParseFilterID(id, pos, true);
            if (specs == null)
            {
                pos[0] = start;
                return(null);
            }

            // Assemble return results
            TransliteratorIDParser.SingleID single = SpecsToID(specs, FORWARD);
            single.filter = specs.filter;
            return(single);
        }
        /// <summary>
        /// Givens a Spec object, convert it to a SingleID object. The Spec object is
        /// a more unprocessed parse result. The SingleID object contains information
        /// about canonical and basic IDs.
        /// </summary>
        ///
        /// <returns>a SingleID; never returns null. Returned object always has
        /// 'filter' field of null.</returns>
        private static TransliteratorIDParser.SingleID  SpecsToID(TransliteratorIDParser.Specs specs, int dir)
        {
            String canonID_0   = "";
            String basicID_1   = "";
            String basicPrefix = "";

            if (specs != null)
            {
                StringBuilder buf = new StringBuilder();
                if (dir == FORWARD)
                {
                    if (specs.sawSource)
                    {
                        buf.Append(specs.source).Append(TARGET_SEP);
                    }
                    else
                    {
                        basicPrefix = specs.source + TARGET_SEP;
                    }
                    buf.Append(specs.target);
                }
                else
                {
                    buf.Append(specs.target).Append(TARGET_SEP)
                    .Append(specs.source);
                }
                if (specs.variant != null)
                {
                    buf.Append(VARIANT_SEP).Append(specs.variant);
                }
                basicID_1 = basicPrefix + buf.ToString();
                if (specs.filter != null)
                {
                    buf.Insert(0, specs.filter);
                }
                canonID_0 = buf.ToString();
            }
            return(new TransliteratorIDParser.SingleID(canonID_0, basicID_1));
        }
        /// <summary>
        /// Parse a single ID, that is, an ID of the general form
        /// "[f1] s1-t1/v1 ([f2] s2-t3/v2)", with the parenthesized element optional,
        /// the filters optional, and the variants optional.
        /// </summary>
        ///
        /// <param name="id">the id to be parsed</param>
        /// <param name="pos">INPUT-OUTPUT parameter. On input, the position of the firstcharacter to parse. On output, the position after the lastcharacter parsed.</param>
        /// <param name="dir">the direction. If the direction is REVERSE then the SingleIDis constructed for the reverse direction.</param>
        /// <returns>a SingleID object or null</returns>
        public static TransliteratorIDParser.SingleID  ParseSingleID(String id, int[] pos, int dir)
        {
            int start = pos[0];

            // The ID will be of the form A, A(), A(B), or (B), where
            // A and B are filter IDs.
            TransliteratorIDParser.Specs specsA = null;
            TransliteratorIDParser.Specs specsB = null;
            bool sawParen = false;

            // On the first pass, look for (B) or (). If this fails, then
            // on the second pass, look for A, A(B), or A().
            for (int pass = 1; pass <= 2; ++pass)
            {
                if (pass == 2)
                {
                    specsA = ParseFilterID(id, pos, true);
                    if (specsA == null)
                    {
                        pos[0] = start;
                        return(null);
                    }
                }
                if (IBM.ICU.Impl.Utility.ParseChar(id, pos, OPEN_REV))
                {
                    sawParen = true;
                    if (!IBM.ICU.Impl.Utility.ParseChar(id, pos, CLOSE_REV))
                    {
                        specsB = ParseFilterID(id, pos, true);
                        // Must close with a ')'
                        if (specsB == null ||
                            !IBM.ICU.Impl.Utility.ParseChar(id, pos, CLOSE_REV))
                        {
                            pos[0] = start;
                            return(null);
                        }
                    }
                    break;
                }
            }

            // Assemble return results
            TransliteratorIDParser.SingleID single;
            if (sawParen)
            {
                if (dir == FORWARD)
                {
                    single         = SpecsToID(specsA, FORWARD);
                    single.canonID = single.canonID + OPEN_REV
                                     + SpecsToID(specsB, FORWARD).canonID + CLOSE_REV;
                    if (specsA != null)
                    {
                        single.filter = specsA.filter;
                    }
                }
                else
                {
                    single         = SpecsToID(specsB, FORWARD);
                    single.canonID = single.canonID + OPEN_REV
                                     + SpecsToID(specsA, FORWARD).canonID + CLOSE_REV;
                    if (specsB != null)
                    {
                        single.filter = specsB.filter;
                    }
                }
            }
            else
            {
                // assert(specsA != null);
                if (dir == FORWARD)
                {
                    single = SpecsToID(specsA, FORWARD);
                }
                else
                {
                    single = SpecsToSpecialInverse(specsA);
                    if (single == null)
                    {
                        single = SpecsToID(specsA, REVERSE);
                    }
                }
                single.filter = specsA.filter;
            }

            return(single);
        }