135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka/*
235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * Copyright (C) 2012 The Android Open Source Project
335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka *
48aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
58aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * you may not use this file except in compliance with the License.
68aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * You may obtain a copy of the License at
735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka *
88aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka *
1035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
118aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
128aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * See the License for the specific language governing permissions and
148aa9963a895f9dd5bb1bc92ab2e4f461e058f87aTadashi G. Takaoka * limitations under the License.
1535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka */
1635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
1735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokapackage com.android.inputmethod.keyboard.internal;
1835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
1964c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaokaimport android.text.TextUtils;
2064c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka
21f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
22240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaokaimport com.android.inputmethod.latin.Constants;
232dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaokaimport com.android.inputmethod.latin.define.DebugFlags;
24e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaokaimport com.android.inputmethod.latin.utils.CollectionUtils;
25e28eba5074664d5716b8e58b8d0a235746b261ebKen Wakasaimport com.android.inputmethod.latin.utils.StringUtils;
2635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
27e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaokaimport java.util.ArrayList;
28e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaokaimport java.util.Arrays;
2935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaokaimport java.util.Locale;
3035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
31e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka/**
32e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * The more key specification object. The more keys are an array of {@link MoreKeySpec}.
33e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka *
34e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * The more keys specification is comma separated "key specification" each of which represents one
35e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * "more key".
36e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * The key specification might have label or string resource reference in it. These references are
37e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * expanded before parsing comma.
38e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * Special character, comma ',' backslash '\' can be escaped by '\' character.
39e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)}
40e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka * as well.
41e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka */
429684b33b69a62a058c767786ae6a23b809d27385Tadashi G. Takaoka// TODO: Should extend the key specification object.
4364c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaokapublic final class MoreKeySpec {
4435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public final int mCode;
4535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public final String mLabel;
4635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public final String mOutputText;
4735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public final int mIconId;
4835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
497ae6721ffad1e79ee446de87d13f18a27619830bTadashi G. Takaoka    public MoreKeySpec(final String moreKeySpec, boolean needsToUpperCase, final Locale locale) {
50d9c6b332090c90e4d4840e62fe3eb45c834b2e14Tadashi G. Takaoka        if (TextUtils.isEmpty(moreKeySpec)) {
51d9c6b332090c90e4d4840e62fe3eb45c834b2e14Tadashi G. Takaoka            throw new KeySpecParser.KeySpecParserError("Empty more key spec");
52d9c6b332090c90e4d4840e62fe3eb45c834b2e14Tadashi G. Takaoka        }
53639bf62e4fc12c4f75ed0491512f6ed9d2fc2432Tadashi G. Takaoka        mLabel = StringUtils.toUpperCaseOfStringForLocale(
5435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka                KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale);
55639bf62e4fc12c4f75ed0491512f6ed9d2fc2432Tadashi G. Takaoka        final int code = StringUtils.toUpperCaseOfCodeForLocale(
567ae6721ffad1e79ee446de87d13f18a27619830bTadashi G. Takaoka                KeySpecParser.getCode(moreKeySpec), needsToUpperCase, locale);
57240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka        if (code == Constants.CODE_UNSPECIFIED) {
5835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters
5935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            // upper case representation ("SS").
60240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka            mCode = Constants.CODE_OUTPUT_TEXT;
6135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            mOutputText = mLabel;
6235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        } else {
6335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            mCode = code;
64639bf62e4fc12c4f75ed0491512f6ed9d2fc2432Tadashi G. Takaoka            mOutputText = StringUtils.toUpperCaseOfStringForLocale(
6535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka                    KeySpecParser.getOutputText(moreKeySpec), needsToUpperCase, locale);
6635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        }
6735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        mIconId = KeySpecParser.getIconId(moreKeySpec);
6835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    }
6935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka
70f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka    public Key buildKey(final int x, final int y, final int labelFlags,
71f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka            final KeyboardParams params) {
72f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka        return new Key(mLabel, mIconId, mCode, mOutputText, null /* hintLabel */, labelFlags,
73f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka                Key.BACKGROUND_TYPE_NORMAL, x, y, params.mDefaultKeyWidth, params.mDefaultRowHeight,
74f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka                params.mHorizontalGap, params.mVerticalGap);
75f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka    }
76f70bcf3d323b13b60c0567c69768ed986647f86aTadashi G. Takaoka
7735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    @Override
7864c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    public int hashCode() {
7964c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        int hashCode = 1;
8064c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        hashCode = 31 + mCode;
8164c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        hashCode = hashCode * 31 + mIconId;
8264c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        hashCode = hashCode * 31 + (mLabel == null ? 0 : mLabel.hashCode());
8364c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        hashCode = hashCode * 31 + (mOutputText == null ? 0 : mOutputText.hashCode());
8464c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        return hashCode;
8564c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    }
8664c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka
8764c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    @Override
8864c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    public boolean equals(final Object o) {
8964c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        if (this == o) return true;
9064c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        if (o instanceof MoreKeySpec) {
9164c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka            final MoreKeySpec other = (MoreKeySpec)o;
9264c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka            return mCode == other.mCode
9364c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka                    && mIconId == other.mIconId
9464c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka                    && TextUtils.equals(mLabel, other.mLabel)
9564c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka                    && TextUtils.equals(mOutputText, other.mOutputText);
9664c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        }
9764c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka        return false;
9864c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    }
9964c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka
10064c65ce6d780175fe606fdd7ee694a3b5af4e37fTadashi G. Takaoka    @Override
10135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public String toString() {
10235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel
1033b4eb03fa171ab42ad4f38abcfa5184c5362e5aeTadashi G. Takaoka                : KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId));
104240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka        final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText
105240871ecafde7834ebb4270cd7758fc904a5f3a7Tadashi G. Takaoka                : Constants.printableCode(mCode));
10635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) {
10735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            return output;
10835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        } else {
10935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            return label + "|" + output;
11035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        }
11135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    }
112e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
1132dae79b1966a7970c25c8b79beec1c95c13f6c87Tadashi G. Takaoka    private static final boolean DEBUG = DebugFlags.DEBUG_ENABLED;
114e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    // Constants for parsing.
115e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    private static final char COMMA = Constants.CODE_COMMA;
116e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    private static final char BACKSLASH = Constants.CODE_BACKSLASH;
117e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    private static final String ADDITIONAL_MORE_KEY_MARKER =
118e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            StringUtils.newSingleCodePointString(Constants.CODE_PERCENT);
119e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
120e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    /**
121e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * Split the text containing multiple key specifications separated by commas into an array of
122e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * key specifications.
123e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * A key specification can contain a character escaped by the backslash character, including a
124e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * comma character.
125e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * Note that an empty key specification will be eliminated from the result array.
126e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     *
127e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * @param text the text containing multiple key specifications.
128e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * @return an array of key specification text. Null if the specified <code>text</code> is empty
129e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     * or has no key specifications.
130e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka     */
131e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    public static String[] splitKeySpecs(final String text) {
132e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (TextUtils.isEmpty(text)) {
133e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return null;
134e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
135e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final int size = text.length();
136e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        // Optimization for one-letter key specification.
137e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (size == 1) {
138e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return text.charAt(0) == COMMA ? null : new String[] { text };
139e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
140e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
141e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        ArrayList<String> list = null;
142e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        int start = 0;
143e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        // The characters in question in this loop are COMMA and BACKSLASH. These characters never
144e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        // match any high or low surrogate character. So it is OK to iterate through with char
145e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        // index.
146e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        for (int pos = 0; pos < size; pos++) {
147e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final char c = text.charAt(pos);
148e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (c == COMMA) {
149e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                // Skip empty entry.
150e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                if (pos - start > 0) {
151e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    if (list == null) {
152a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka                        list = new ArrayList<>();
153e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    }
154e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    list.add(text.substring(start, pos));
155e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                }
156e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                // Skip comma
157e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                start = pos + 1;
158e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            } else if (c == BACKSLASH) {
159e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                // Skip escape character and escaped character.
160e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                pos++;
161e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
162e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
163e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final String remain = (size - start > 0) ? text.substring(start) : null;
164e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (list == null) {
165e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return remain != null ? new String[] { remain } : null;
166e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
167e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (remain != null) {
168e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            list.add(remain);
169e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
170e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        return list.toArray(new String[list.size()]);
171e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    }
172e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
173e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    private static final String[] EMPTY_STRING_ARRAY = new String[0];
174e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
175e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    private static String[] filterOutEmptyString(final String[] array) {
176e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (array == null) {
177e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return EMPTY_STRING_ARRAY;
178e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
179e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        ArrayList<String> out = null;
180e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        for (int i = 0; i < array.length; i++) {
181e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final String entry = array[i];
182e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (TextUtils.isEmpty(entry)) {
183e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                if (out == null) {
184e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    out = CollectionUtils.arrayAsList(array, 0, i);
185e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                }
186e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            } else if (out != null) {
187e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                out.add(entry);
188e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
189e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
190e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (out == null) {
191e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return array;
192e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
193e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        return out.toArray(new String[out.size()]);
194e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    }
195e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
196e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
197e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final String[] additionalMoreKeySpecs) {
198e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
199e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
200e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final int moreKeysCount = moreKeys.length;
201e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final int additionalCount = additionalMoreKeys.length;
202e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        ArrayList<String> out = null;
203e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        int additionalIndex = 0;
204e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
205e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final String moreKeySpec = moreKeys[moreKeyIndex];
206e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
207e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                if (additionalIndex < additionalCount) {
208e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    // Replace '%' marker with additional more key specification.
209e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    final String additionalMoreKey = additionalMoreKeys[additionalIndex];
210e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    if (out != null) {
211e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        out.add(additionalMoreKey);
212e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    } else {
213e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        moreKeys[moreKeyIndex] = additionalMoreKey;
214e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    }
215e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    additionalIndex++;
216e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                } else {
217e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    // Filter out excessive '%' marker.
218e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    if (out == null) {
219e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex);
220e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    }
221e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                }
222e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            } else {
223e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                if (out != null) {
224e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    out.add(moreKeySpec);
225e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                }
226e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
227e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
228e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (additionalCount > 0 && additionalIndex == 0) {
229e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            // No '%' marker is found in more keys.
230e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            // Insert all additional more keys to the head of more keys.
231e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (DEBUG && out != null) {
232e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                throw new RuntimeException("Internal logic error:"
233e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        + " moreKeys=" + Arrays.toString(moreKeys)
234e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
235e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
236e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
237e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            for (int i = 0; i < moreKeysCount; i++) {
238e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                out.add(moreKeys[i]);
239e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
240e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        } else if (additionalIndex < additionalCount) {
241e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            // The number of '%' markers are less than additional more keys.
242e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            // Append remained additional more keys to the tail of more keys.
243e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (DEBUG && out != null) {
244e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                throw new RuntimeException("Internal logic error:"
245e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        + " moreKeys=" + Arrays.toString(moreKeys)
246e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
247e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
248e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount);
249e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            for (int i = additionalIndex; i < additionalCount; i++) {
250e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                out.add(additionalMoreKeys[additionalIndex]);
251e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
252e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
253e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (out == null && moreKeysCount > 0) {
254e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return moreKeys;
255e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        } else if (out != null && out.size() > 0) {
256e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return out.toArray(new String[out.size()]);
257e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        } else {
258e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return null;
259e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
260e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    }
261e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
262e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    public static int getIntValue(final String[] moreKeys, final String key,
263e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final int defaultValue) {
264e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (moreKeys == null) {
265e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return defaultValue;
266e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
267e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        final int keyLen = key.length();
268e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        boolean foundValue = false;
269e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        int value = defaultValue;
270e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        for (int i = 0; i < moreKeys.length; i++) {
271e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final String moreKeySpec = moreKeys[i];
272e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
273e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                continue;
274e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
275e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            moreKeys[i] = null;
276e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            try {
277e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                if (!foundValue) {
278e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    value = Integer.parseInt(moreKeySpec.substring(keyLen));
279e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                    foundValue = true;
280e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                }
281e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            } catch (NumberFormatException e) {
282e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                throw new RuntimeException(
283e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                        "integer should follow after " + key + ": " + moreKeySpec);
284e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
285e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
286e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        return value;
287e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    }
288e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka
289e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    public static boolean getBooleanValue(final String[] moreKeys, final String key) {
290e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        if (moreKeys == null) {
291e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            return false;
292e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
293e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        boolean value = false;
294e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        for (int i = 0; i < moreKeys.length; i++) {
295e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            final String moreKeySpec = moreKeys[i];
296e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            if (moreKeySpec == null || !moreKeySpec.equals(key)) {
297e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka                continue;
298e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            }
299e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            moreKeys[i] = null;
300e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka            value = true;
301e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        }
302e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka        return value;
303e855093f5513e46f7f2da6d99e74873ac4f1eeefTadashi G. Takaoka    }
30435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka}
305