/*
 * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javax.naming;

import java.util.Locale;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Properties;
import java.util.NoSuchElementException;

/**
  * The implementation class for CompoundName and CompositeName.
  * This class is package private.
  *
  * @author Rosanna Lee
  * @author Scott Seligman
  * @author Aravindan Ranganathan
  * @since 1.3
  */

class NameImpl {
    private static final byte LEFT_TO_RIGHT = 1;
    private static final byte RIGHT_TO_LEFT = 2;
    private static final byte FLAT = 0;

    private Vector<String> components;

    private byte syntaxDirection = LEFT_TO_RIGHT;
    private String syntaxSeparator = "/";
    private String syntaxSeparator2 = null;
    private boolean syntaxCaseInsensitive = false;
    private boolean syntaxTrimBlanks = false;
    private String syntaxEscape = "\\";
    private String syntaxBeginQuote1 = "\"";
    private String syntaxEndQuote1 = "\"";
    private String syntaxBeginQuote2 = "'";
    private String syntaxEndQuote2 = "'";
    private String syntaxAvaSeparator = null;
    private String syntaxTypevalSeparator = null;

    // escapingStyle gives the method used at creation time for
    // quoting or escaping characters in the name.  It is set to the
    // first style of quote or escape encountered if and when the name
    // is parsed.
    private static final int STYLE_NONE = 0;
    private static final int STYLE_QUOTE1 = 1;
    private static final int STYLE_QUOTE2 = 2;
    private static final int STYLE_ESCAPE = 3;
    private int escapingStyle = STYLE_NONE;

    // Returns true if "match" is not null, and n contains "match" at
    // position i.
    private final boolean isA(String n, int i, String match) {
        return (match != null && n.startsWith(match, i));
    }

    private final boolean isMeta(String n, int i) {
        return (isA(n, i, syntaxEscape) ||
                isA(n, i, syntaxBeginQuote1) ||
                isA(n, i, syntaxBeginQuote2) ||
                isSeparator(n, i));
    }

    private final boolean isSeparator(String n, int i) {
        return (isA(n, i, syntaxSeparator) ||
                isA(n, i, syntaxSeparator2));
    }

    private final int skipSeparator(String name, int i) {
        if (isA(name, i, syntaxSeparator)) {
            i += syntaxSeparator.length();
        } else if (isA(name, i, syntaxSeparator2)) {
            i += syntaxSeparator2.length();
        }
        return i;
    }

    private final int extractComp(String name, int i, int len, Vector<String> comps)
    throws InvalidNameException {
        String beginQuote;
        String endQuote;
        boolean start = true;
        boolean one = false;
        StringBuilder answer = new StringBuilder(len);

        while (i < len) {
            // handle quoted strings
            if (start && ((one = isA(name, i, syntaxBeginQuote1)) ||
                          isA(name, i, syntaxBeginQuote2))) {

                // record choice of quote chars being used
                beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
                endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;
                if (escapingStyle == STYLE_NONE) {
                    escapingStyle = one ? STYLE_QUOTE1 : STYLE_QUOTE2;
                }

                // consume string until matching quote
                for (i += beginQuote.length();
                     ((i < len) && !name.startsWith(endQuote, i));
                     i++) {
                    // skip escape character if it is escaping ending quote
                    // otherwise leave as is.
                    if (isA(name, i, syntaxEscape) &&
                        isA(name, i + syntaxEscape.length(), endQuote)) {
                        i += syntaxEscape.length();
                    }
                    answer.append(name.charAt(i));  // copy char
                }

                // no ending quote found
                if (i >= len)
                    throw
                        new InvalidNameException(name + ": no close quote");
//                      new Exception("no close quote");

                i += endQuote.length();

                // verify that end-quote occurs at separator or end of string
                if (i == len || isSeparator(name, i)) {
                    break;
                }
//              throw (new Exception(
                throw (new InvalidNameException(name +
                    ": close quote appears before end of component"));

            } else if (isSeparator(name, i)) {
                break;

            } else if (isA(name, i, syntaxEscape)) {
                if (isMeta(name, i + syntaxEscape.length())) {
                    // if escape precedes meta, consume escape and let
                    // meta through
                    i += syntaxEscape.length();
                    if (escapingStyle == STYLE_NONE) {
                        escapingStyle = STYLE_ESCAPE;
                    }
                } else if (i + syntaxEscape.length() >= len) {
                    throw (new InvalidNameException(name +
                        ": unescaped " + syntaxEscape + " at end of component"));
                }
            } else if (isA(name, i, syntaxTypevalSeparator) &&
        ((one = isA(name, i+syntaxTypevalSeparator.length(), syntaxBeginQuote1)) ||
            isA(name, i+syntaxTypevalSeparator.length(), syntaxBeginQuote2))) {
                // Handle quote occurring after typeval separator
                beginQuote = one ? syntaxBeginQuote1 : syntaxBeginQuote2;
                endQuote = one ? syntaxEndQuote1 : syntaxEndQuote2;

                i += syntaxTypevalSeparator.length();
                answer.append(syntaxTypevalSeparator).append(beginQuote); // add back

                // consume string until matching quote
                for (i += beginQuote.length();
                     ((i < len) && !name.startsWith(endQuote, i));
                     i++) {
                    // skip escape character if it is escaping ending quote
                    // otherwise leave as is.
                    if (isA(name, i, syntaxEscape) &&
                        isA(name, i + syntaxEscape.length(), endQuote)) {
                        i += syntaxEscape.length();
                    }
                    answer.append(name.charAt(i));  // copy char
                }

                // no ending quote found
                if (i >= len)
                    throw
                        new InvalidNameException(name + ": typeval no close quote");

                i += endQuote.length();
                answer.append(endQuote); // add back

                // verify that end-quote occurs at separator or end of string
                if (i == len || isSeparator(name, i)) {
                    break;
                }
                throw (new InvalidNameException(name.substring(i) +
                    ": typeval close quote appears before end of component"));
            }

            answer.append(name.charAt(i++));
            start = false;
        }

        if (syntaxDirection == RIGHT_TO_LEFT)
            comps.insertElementAt(answer.toString(), 0);
        else
            comps.addElement(answer.toString());
        return i;
    }

    private static boolean getBoolean(Properties p, String name) {
        return toBoolean(p.getProperty(name));
    }

    private static boolean toBoolean(String name) {
        return ((name != null) &&
            name.toLowerCase(Locale.ENGLISH).equals("true"));
    }

    private final void recordNamingConvention(Properties p) {
        String syntaxDirectionStr =
            p.getProperty("jndi.syntax.direction", "flat");
        if (syntaxDirectionStr.equals("left_to_right")) {
            syntaxDirection = LEFT_TO_RIGHT;
        } else if (syntaxDirectionStr.equals("right_to_left")) {
            syntaxDirection = RIGHT_TO_LEFT;
        } else if (syntaxDirectionStr.equals("flat")) {
            syntaxDirection = FLAT;
        } else {
            throw new IllegalArgumentException(syntaxDirectionStr +
                " is not a valid value for the jndi.syntax.direction property");
        }

        if (syntaxDirection != FLAT) {
            syntaxSeparator = p.getProperty("jndi.syntax.separator");
            syntaxSeparator2 = p.getProperty("jndi.syntax.separator2");
            if (syntaxSeparator == null) {
                throw new IllegalArgumentException(
                    "jndi.syntax.separator property required for non-flat syntax");
            }
        } else {
            syntaxSeparator = null;
        }
        syntaxEscape = p.getProperty("jndi.syntax.escape");

        syntaxCaseInsensitive = getBoolean(p, "jndi.syntax.ignorecase");
        syntaxTrimBlanks = getBoolean(p, "jndi.syntax.trimblanks");

        syntaxBeginQuote1 = p.getProperty("jndi.syntax.beginquote");
        syntaxEndQuote1 = p.getProperty("jndi.syntax.endquote");
        if (syntaxEndQuote1 == null && syntaxBeginQuote1 != null)
            syntaxEndQuote1 = syntaxBeginQuote1;
        else if (syntaxBeginQuote1 == null && syntaxEndQuote1 != null)
            syntaxBeginQuote1 = syntaxEndQuote1;
        syntaxBeginQuote2 = p.getProperty("jndi.syntax.beginquote2");
        syntaxEndQuote2 = p.getProperty("jndi.syntax.endquote2");
        if (syntaxEndQuote2 == null && syntaxBeginQuote2 != null)
            syntaxEndQuote2 = syntaxBeginQuote2;
        else if (syntaxBeginQuote2 == null && syntaxEndQuote2 != null)
            syntaxBeginQuote2 = syntaxEndQuote2;

        syntaxAvaSeparator = p.getProperty("jndi.syntax.separator.ava");
        syntaxTypevalSeparator =
            p.getProperty("jndi.syntax.separator.typeval");
    }

    NameImpl(Properties syntax) {
        if (syntax != null) {
            recordNamingConvention(syntax);
        }
        components = new Vector<>();
    }

    NameImpl(Properties syntax, String n) throws InvalidNameException {
        this(syntax);

        boolean rToL = (syntaxDirection == RIGHT_TO_LEFT);
        boolean compsAllEmpty = true;
        int len = n.length();

        for (int i = 0; i < len; ) {
            i = extractComp(n, i, len, components);

            String comp = rToL
                ? components.firstElement()
                : components.lastElement();
            if (comp.length() >= 1) {
                compsAllEmpty = false;
            }

            if (i < len) {
                i = skipSeparator(n, i);
                if ((i == len) && !compsAllEmpty) {
                    // Trailing separator found.  Add an empty component.
                    if (rToL) {
                        components.insertElementAt("", 0);
                    } else {
                        components.addElement("");
                    }
                }
            }
        }
    }

    NameImpl(Properties syntax, Enumeration<String> comps) {
        this(syntax);

        // %% comps could shrink in the middle.
        while (comps.hasMoreElements())
            components.addElement(comps.nextElement());
    }
/*
    // Determines whether this component needs any escaping.
    private final boolean escapingNeeded(String comp) {
        int len = comp.length();
        for (int i = 0; i < len; i++) {
            if (i == 0) {
                if (isA(comp, 0, syntaxBeginQuote1) ||
                    isA(comp, 0, syntaxBeginQuote2)) {
                    return (true);
                }
            }
            if (isSeparator(comp, i)) {
                return (true);
            }
            if (isA(comp, i, syntaxEscape)) {
                i += syntaxEscape.length();
                if (i >= len || isMeta(comp, i)) {
                    return (true);
                }
            }
        }
        return (false);
    }
*/
    private String stringifyComp(String comp) {
        int len = comp.length();
        boolean escapeSeparator = false, escapeSeparator2 = false;
        String beginQuote = null, endQuote = null;
        StringBuilder strbuf = new StringBuilder(len);

        // determine whether there are any separators; if so escape
        // or quote them
        if (syntaxSeparator != null &&
            comp.contains(syntaxSeparator)) {
            if (syntaxBeginQuote1 != null) {
                beginQuote = syntaxBeginQuote1;
                endQuote = syntaxEndQuote1;
            } else if (syntaxBeginQuote2 != null) {
                beginQuote = syntaxBeginQuote2;
                endQuote = syntaxEndQuote2;
            } else if (syntaxEscape != null)
                escapeSeparator = true;
        }
        if (syntaxSeparator2 != null &&
            comp.contains(syntaxSeparator2)) {
            if (syntaxBeginQuote1 != null) {
                if (beginQuote == null) {
                    beginQuote = syntaxBeginQuote1;
                    endQuote = syntaxEndQuote1;
                }
            } else if (syntaxBeginQuote2 != null) {
                if (beginQuote == null) {
                    beginQuote = syntaxBeginQuote2;
                    endQuote = syntaxEndQuote2;
                }
            } else if (syntaxEscape != null)
                escapeSeparator2 = true;
        }

        // if quoting component,
        if (beginQuote != null) {

            // start string off with opening quote
            strbuf = strbuf.append(beginQuote);

            // component is being quoted, so we only need to worry about
            // escaping end quotes that occur in component
            for (int i = 0; i < len; ) {
                if (comp.startsWith(endQuote, i)) {
                    // end-quotes must be escaped when inside a quoted string
                    strbuf.append(syntaxEscape).append(endQuote);
                    i += endQuote.length();
                } else {
                    // no special treatment required
                    strbuf.append(comp.charAt(i++));
                }
            }

            // end with closing quote
            strbuf.append(endQuote);

        } else {

            // When component is not quoted, add escape for:
            // 1. leading quote
            // 2. an escape preceding any meta char
            // 3. an escape at the end of a component
            // 4. separator

            // go through characters in component and escape where necessary
            boolean start = true;
            for (int i = 0; i < len; ) {
                // leading quote must be escaped
                if (start && isA(comp, i, syntaxBeginQuote1)) {
                    strbuf.append(syntaxEscape).append(syntaxBeginQuote1);
                    i += syntaxBeginQuote1.length();
                } else if (start && isA(comp, i, syntaxBeginQuote2)) {
                    strbuf.append(syntaxEscape).append(syntaxBeginQuote2);
                    i += syntaxBeginQuote2.length();
                } else

                // Escape an escape preceding meta characters, or at end.
                // Other escapes pass through.
                if (isA(comp, i, syntaxEscape)) {
                    if (i + syntaxEscape.length() >= len) {
                        // escape an ending escape
                        strbuf.append(syntaxEscape);
                    } else if (isMeta(comp, i + syntaxEscape.length())) {
                        // escape meta strings
                        strbuf.append(syntaxEscape);
                    }
                    strbuf.append(syntaxEscape);
                    i += syntaxEscape.length();
                } else

                // escape unescaped separator
                if (escapeSeparator && comp.startsWith(syntaxSeparator, i)) {
                    // escape separator
                    strbuf.append(syntaxEscape).append(syntaxSeparator);
                    i += syntaxSeparator.length();
                } else if (escapeSeparator2 &&
                           comp.startsWith(syntaxSeparator2, i)) {
                    // escape separator2
                    strbuf.append(syntaxEscape).append(syntaxSeparator2);
                    i += syntaxSeparator2.length();
                } else {
                    // no special treatment required
                    strbuf.append(comp.charAt(i++));
                }
                start = false;
            }
        }
        return strbuf.toString();
    }

    public String toString() {
        StringBuilder answer = new StringBuilder();
        String comp;
        boolean compsAllEmpty = true;
        int size = components.size();

        for (int i = 0; i < size; i++) {
            if (syntaxDirection == RIGHT_TO_LEFT) {
                comp =
                    stringifyComp(components.elementAt(size - 1 - i));
            } else {
                comp = stringifyComp(components.elementAt(i));
            }
            if ((i != 0) && (syntaxSeparator != null))
                answer.append(syntaxSeparator);
            if (comp.length() >= 1)
                compsAllEmpty = false;
            answer = answer.append(comp);
        }
        if (compsAllEmpty && (size >= 1) && (syntaxSeparator != null))
            answer = answer.append(syntaxSeparator);
        return answer.toString();
    }

    public boolean equals(Object obj) {
        if (obj instanceof NameImpl target) {
            if (target.size() ==  this.size()) {
                Enumeration<String> mycomps = getAll();
                Enumeration<String> comps = target.getAll();
                while (mycomps.hasMoreElements()) {
                    // %% comps could shrink in the middle.
                    String my = mycomps.nextElement();
                    String his = comps.nextElement();
                    if (syntaxTrimBlanks) {
                        my = my.trim();
                        his = his.trim();
                    }
                    if (syntaxCaseInsensitive) {
                        if (!(my.equalsIgnoreCase(his)))
                            return false;
                    } else {
                        if (!(my.equals(his)))
                            return false;
                    }
                }
                return true;
            }
        }
        return false;
    }

    /**
      * Compares obj to this NameImpl to determine ordering.
      * Takes into account syntactic properties such as
      * elimination of blanks, case-ignore, etc, if relevant.
      *
      * Note: using syntax of this NameImpl and ignoring
      * that of comparison target.
      */
    public int compareTo(NameImpl obj) {
        if (this == obj) {
            return 0;
        }

        int len1 = size();
        int len2 = obj.size();
        int n = Math.min(len1, len2);

        int index1 = 0, index2 = 0;

        while (n-- != 0) {
            String comp1 = get(index1++);
            String comp2 = obj.get(index2++);

            // normalize according to syntax
            if (syntaxTrimBlanks) {
                comp1 = comp1.trim();
                comp2 = comp2.trim();
            }

            int local;
            if (syntaxCaseInsensitive) {
                local = comp1.compareToIgnoreCase(comp2);
            } else {
                local = comp1.compareTo(comp2);
            }

            if (local != 0) {
                return local;
            }
        }

        return len1 - len2;
    }

    public int size() {
        return components.size();
    }

    public Enumeration<String> getAll() {
        return components.elements();
    }

    public String get(int posn) {
        return components.elementAt(posn);
    }

    public Enumeration<String> getPrefix(int posn) {
        if (posn < 0 || posn > size()) {
            throw new ArrayIndexOutOfBoundsException(posn);
        }
        return new NameImplEnumerator(components, 0, posn);
    }

    public Enumeration<String> getSuffix(int posn) {
        int cnt = size();
        if (posn < 0 || posn > cnt) {
            throw new ArrayIndexOutOfBoundsException(posn);
        }
        return new NameImplEnumerator(components, posn, cnt);
    }

    public boolean isEmpty() {
        return (components.isEmpty());
    }

    public boolean startsWith(int posn, Enumeration<String> prefix) {
        if (posn < 0 || posn > size()) {
            return false;
        }
        try {
            Enumeration<String> mycomps = getPrefix(posn);
            while (mycomps.hasMoreElements()) {
                String my = mycomps.nextElement();
                String his = prefix.nextElement();
                if (syntaxTrimBlanks) {
                    my = my.trim();
                    his = his.trim();
                }
                if (syntaxCaseInsensitive) {
                    if (!(my.equalsIgnoreCase(his)))
                        return false;
                } else {
                    if (!(my.equals(his)))
                        return false;
                }
            }
        } catch (NoSuchElementException e) {
            return false;
        }
        return true;
    }

    public boolean endsWith(int posn, Enumeration<String> suffix) {
        // posn is number of elements in suffix
        // startIndex is the starting position in this name
        // at which to start the comparison. It is calculated by
        // subtracting 'posn' from size()
        int startIndex = size() - posn;
        if (startIndex < 0 || startIndex > size()) {
            return false;
        }
        try {
            Enumeration<String> mycomps = getSuffix(startIndex);
            while (mycomps.hasMoreElements()) {
                String my = mycomps.nextElement();
                String his = suffix.nextElement();
                if (syntaxTrimBlanks) {
                    my = my.trim();
                    his = his.trim();
                }
                if (syntaxCaseInsensitive) {
                    if (!(my.equalsIgnoreCase(his)))
                        return false;
                } else {
                    if (!(my.equals(his)))
                        return false;
                }
            }
        } catch (NoSuchElementException e) {
            return false;
        }
        return true;
    }

    public boolean addAll(Enumeration<String> comps) throws InvalidNameException {
        boolean added = false;
        while (comps.hasMoreElements()) {
            try {
                String comp = comps.nextElement();
                if (size() > 0 && syntaxDirection == FLAT) {
                    throw new InvalidNameException(
                        "A flat name can only have a single component");
                }
                components.addElement(comp);
                added = true;
            } catch (NoSuchElementException e) {
                break;  // "comps" has shrunk.
            }
        }
        return added;
    }

    public boolean addAll(int posn, Enumeration<String> comps)
    throws InvalidNameException {
        boolean added = false;
        for (int i = posn; comps.hasMoreElements(); i++) {
            try {
                String comp = comps.nextElement();
                if (size() > 0 && syntaxDirection == FLAT) {
                    throw new InvalidNameException(
                        "A flat name can only have a single component");
                }
                components.insertElementAt(comp, i);
                added = true;
            } catch (NoSuchElementException e) {
                break;  // "comps" has shrunk.
            }
        }
        return added;
    }

    public void add(String comp) throws InvalidNameException {
        if (size() > 0 && syntaxDirection == FLAT) {
            throw new InvalidNameException(
                "A flat name can only have a single component");
        }
        components.addElement(comp);
    }

    public void add(int posn, String comp) throws InvalidNameException {
        if (size() > 0 && syntaxDirection == FLAT) {
            throw new InvalidNameException(
                "A flat name can only zero or one component");
        }
        components.insertElementAt(comp, posn);
    }

    public Object remove(int posn) {
        Object r = components.elementAt(posn);
        components.removeElementAt(posn);
        return r;
    }

    public int hashCode() {
        int hash = 0;
        for (Enumeration<String> e = getAll(); e.hasMoreElements();) {
            String comp = e.nextElement();
            if (syntaxTrimBlanks) {
                comp = comp.trim();
            }
            if (syntaxCaseInsensitive) {
                comp = comp.toLowerCase(Locale.ENGLISH);
            }

            hash += comp.hashCode();
        }
        return hash;
    }
}

final
class NameImplEnumerator implements Enumeration<String> {
    Vector<String> vector;
    int count;
    int limit;

    NameImplEnumerator(Vector<String> v, int start, int lim) {
        vector = v;
        count = start;
        limit = lim;
    }

    public boolean hasMoreElements() {
        return count < limit;
    }

    public String nextElement() {
        if (count < limit) {
            return vector.elementAt(count++);
        }
        throw new NoSuchElementException("NameImplEnumerator");
    }
}
