QuotedStringTokenizer.java revision 953:221a84ef44c0
116359Sasami/*
216359Sasami * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
316359Sasami * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
416359Sasami *
516359Sasami * This code is free software; you can redistribute it and/or modify it
616359Sasami * under the terms of the GNU General Public License version 2 only, as
716359Sasami * published by the Free Software Foundation.  Oracle designates this
816359Sasami * particular file as subject to the "Classpath" exception as provided
916359Sasami * by Oracle in the LICENSE file that accompanied this code.
1016359Sasami *
1116359Sasami * This code is distributed in the hope that it will be useful, but WITHOUT
1216359Sasami * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1316359Sasami * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1416359Sasami * version 2 for more details (a copy is included in the LICENSE file that
1516359Sasami * accompanied this code).
1616359Sasami *
1716359Sasami * You should have received a copy of the GNU General Public License version
1816359Sasami * 2 along with this work; if not, write to the Free Software Foundation,
1916359Sasami * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2016359Sasami *
2116359Sasami * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2216359Sasami * or visit www.oracle.com if you need additional information or have any
2316359Sasami * questions.
2416359Sasami */
2516359Sasami
2616359Sasamipackage jdk.nashorn.internal.runtime;
2716359Sasami
2816359Sasamiimport java.util.LinkedList;
2950477Speterimport java.util.Stack;
3016359Sasamiimport java.util.StringTokenizer;
3145816Skato
3216359Sasami/**
3316359Sasami * A string tokenizer that supports entries with quotes and nested quotes. If
3416359Sasami * the separators are quoted either by ' and ", or whatever quotes the user
3531778Seivind * supplies they will be ignored and considered part of another token
36131939Smarcel */
37131939Smarcelpublic final class QuotedStringTokenizer {
3846871Skato    private final LinkedList<String> tokens;
3916359Sasami
4016359Sasami    private final char quotes[];
4116359Sasami
4216359Sasami    /**
4316359Sasami     * Constructor
4416359Sasami     *
4516359Sasami     * @param str string to tokenize
4616359Sasami     */
4716359Sasami    public QuotedStringTokenizer(final String str) {
4816359Sasami        this(str, " ");
4916359Sasami    }
5016359Sasami
5116359Sasami    /**
5216359Sasami     * Create a quoted string tokenizer
5316359Sasami     *
5416359Sasami     * @param str
5516359Sasami     *            a string to tokenize
5616359Sasami     * @param delim
5716359Sasami     *            delimiters between tokens
5816359Sasami     *
5916359Sasami     */
6016359Sasami    public QuotedStringTokenizer(final String str, final String delim) {
6116359Sasami        this(str, delim, new char[] { '"', '\'' });
6216359Sasami    }
6316359Sasami
6416359Sasami    /**
6516359Sasami     * Create a quoted string tokenizer
6616359Sasami     *
6716359Sasami     * @param str
6816359Sasami     *            a string to tokenize
6916359Sasami     * @param delim
7016359Sasami     *            delimiters between tokens
7116359Sasami     * @param quotes
7216359Sasami     *            all the characters that should be accepted as quotes, default
7316359Sasami     *            is ' or "
7442262Skato     */
7542262Skato    private QuotedStringTokenizer(final String str, final String delim, final char[] quotes) {
7642262Skato        this.quotes = quotes;
7754174Snyan
7854174Snyan        boolean delimIsWhitespace = true;
7954174Snyan        for (int i = 0; i < delim.length(); i++) {
8042262Skato            if (!Character.isWhitespace(delim.charAt(i))) {
8116359Sasami                delimIsWhitespace = false;
8276212Skato                break;
8365877Skato            }
8416359Sasami        }
8524132Sbde
8638297Skato        final StringTokenizer st = new StringTokenizer(str, delim);
87131939Smarcel        tokens = new LinkedList<>();
8816359Sasami        while (st.hasMoreTokens()) {
89114216Skan            String token = st.nextToken();
9076212Skato
9176212Skato            while (unmatchedQuotesIn(token)) {
9276212Skato                if (!st.hasMoreTokens()) {
9376212Skato                    throw new IndexOutOfBoundsException(token);
9476212Skato                }
9576212Skato                token += (delimIsWhitespace ? " " : delim) + st.nextToken();
96131125Snyan            }
9776212Skato            tokens.add(stripQuotes(token));
9816359Sasami        }
9976212Skato    }
10045783Skato
10145783Skato    /**
10245226Skato     * @return the number of tokens in the tokenizer
10393934Snyan     */
104119525Snyan    public int countTokens() {
10516359Sasami        return tokens.size();
10645783Skato    }
10716359Sasami
10845783Skato    /**
10945783Skato     * @return true if there are tokens left
11085302Simp     */
11186912Snyan    public boolean hasMoreTokens() {
11245783Skato        return countTokens() > 0;
11386912Snyan    }
11486912Snyan
11586912Snyan    /**
11686912Snyan     * @return the next token in the tokenizer
11786912Snyan     */
11816359Sasami    public String nextToken() {
11977962Snyan        return tokens.removeFirst();
12016359Sasami    }
12177962Snyan
12242265Skato    private String stripQuotes(final String value0) {
12377962Snyan        String value = value0.trim();
12477962Snyan        for (final char q : quotes) {
12542265Skato            if (value.length() >= 2 && value.startsWith("" + q) && value.endsWith("" + q)) {
12616359Sasami                // also go over the value and remove \q sequences. they are just
12716359Sasami                // plain q now
12816359Sasami                value = value.substring(1, value.length() - 1);
12916359Sasami                value = value.replace("\\" + q, "" + q);
13016359Sasami            }
13116359Sasami        }
13216359Sasami        return value;
13316359Sasami    }
13493934Snyan
13593934Snyan    private boolean unmatchedQuotesIn(final String str) {
13693934Snyan        final Stack<Character> quoteStack = new Stack<>();
13793934Snyan        for (int i = 0; i < str.length(); i++) {
13816359Sasami            final char c = str.charAt(i);
139128796Snyan            for (final char q : this.quotes) {
140128796Snyan                if (c == q) {
141128796Snyan                    if (quoteStack.isEmpty()) {
142128796Snyan                        quoteStack.push(c);
143128796Snyan                    } else {
144128796Snyan                        final char top = quoteStack.pop();
145128796Snyan                        if (top != c) {
146128796Snyan                            quoteStack.push(top);
147128796Snyan                            quoteStack.push(c);
148128796Snyan                        }
149128796Snyan                    }
150128796Snyan                }
151128796Snyan            }
152128796Snyan        }
153128796Snyan
154128796Snyan        return !quoteStack.isEmpty();
155128796Snyan    }
15616359Sasami}
15716359Sasami