Merge "Remove the other deprecated constructor to SuggestedWords"
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 5f05e8b..e148622 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -28,7 +28,7 @@
     be_BY: Belarusian (Belarus)/east_slavic
     bg: Bulgarian/bulgarian
     bg: Bulgarian/bulgarian_bds
-    bn_BD: Bengali (Bangladesh)/bengali_akkhor # This is a preliminary keyboard layout.
+    bn_BD: Bengali (Bangladesh)/bengali_akkhor
     bn_IN: Bengali (India)/bengali
     ca: Catalan/spanish
     cs: Czech/qwertz
@@ -181,8 +181,6 @@
             android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian_bds,EmojiCapable"
             android:isAsciiCapable="false"
     />
-    <!-- TODO: This Bengali (Bangladesh) keyboard is a preliminary layout.
-               This isn't based on the final specification. -->
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xa2144b0c"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index b07693c..01980ea 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -25,7 +25,6 @@
 import android.graphics.Paint.Align;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.NinePatchDrawable;
@@ -41,6 +40,7 @@
 
 import java.util.HashSet;
 
+import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 /**
@@ -100,6 +100,8 @@
     private static final float MAX_LABEL_RATIO = 0.90f;
 
     // Main keyboard
+    // TODO: Consider having a dummy keyboard object to make this @Nonnull
+    @Nullable
     private Keyboard mKeyboard;
     protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
 
@@ -108,14 +110,14 @@
     private boolean mInvalidateAllKeys;
     /** The keys that should be drawn */
     private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
-    /** The working rectangle variable */
-    private final Rect mWorkingRect = new Rect();
+    /** The working rectangle for clipping */
+    private final Rect mClipRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
-    /** The clip region to draw keys */
-    private final Region mClipRegion = new Region();
     private Bitmap mOffscreenBuffer;
     /** The canvas for the above mutable keyboard bitmap */
+    @Nonnull
     private final Canvas mOffscreenCanvas = new Canvas();
+    @Nonnull
     private final Paint mPaint = new Paint();
     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
     public KeyboardView(final Context context, final AttributeSet attrs) {
@@ -161,11 +163,12 @@
         mPaint.setAntiAlias(true);
     }
 
+    @Nullable
     public KeyVisualAttributes getKeyVisualAttribute() {
         return mKeyVisualAttributes;
     }
 
-    private static void blendAlpha(final Paint paint, final int alpha) {
+    private static void blendAlpha(@Nonnull final Paint paint, final int alpha) {
         final int color = paint.getColor();
         paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
                 Color.red(color), Color.green(color), Color.blue(color));
@@ -184,7 +187,7 @@
      * @see #getKeyboard()
      * @param keyboard the keyboard to display in this view
      */
-    public void setKeyboard(final Keyboard keyboard) {
+    public void setKeyboard(@Nonnull final Keyboard keyboard) {
         mKeyboard = keyboard;
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
@@ -198,6 +201,7 @@
      * @return the currently attached keyboard
      * @see #setKeyboard(Keyboard)
      */
+    @Nullable
     public Keyboard getKeyboard() {
         return mKeyboard;
     }
@@ -212,13 +216,14 @@
 
     @Override
     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
-        if (mKeyboard == null) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard == null) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             return;
         }
         // The main keyboard expands to the entire this {@link KeyboardView}.
-        final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
-        final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
+        final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
+        final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
         setMeasuredDimension(width, height);
     }
 
@@ -266,52 +271,45 @@
         }
     }
 
-    private void onDrawKeyboard(final Canvas canvas) {
-        if (mKeyboard == null) return;
+    private void onDrawKeyboard(@Nonnull final Canvas canvas) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard == null) {
+            return;
+        }
 
-        final int width = getWidth();
-        final int height = getHeight();
         final Paint paint = mPaint;
-
+        final Drawable background = getBackground();
         // Calculate clip region and set.
         final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
         final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
         // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
         if (drawAllKeys || isHardwareAccelerated) {
-            mClipRegion.set(0, 0, width, height);
-        } else {
-            mClipRegion.setEmpty();
-            for (final Key key : mInvalidatedKeys) {
-                if (mKeyboard.hasKey(key)) {
-                    final int x = key.getX() + getPaddingLeft();
-                    final int y = key.getY() + getPaddingTop();
-                    mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight());
-                    mClipRegion.union(mWorkingRect);
-                }
-            }
-        }
-        if (!isHardwareAccelerated) {
-            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
-            // Draw keyboard background.
-            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-            final Drawable background = getBackground();
-            if (background != null) {
+            if (!isHardwareAccelerated && background != null) {
+                // Need to draw keyboard background on {@link #mOffscreenBuffer}.
+                canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
                 background.draw(canvas);
             }
-        }
-
-        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
-        if (drawAllKeys || isHardwareAccelerated) {
             // Draw all keys.
-            for (final Key key : mKeyboard.getSortedKeys()) {
+            for (final Key key : keyboard.getSortedKeys()) {
                 onDrawKey(key, canvas, paint);
             }
         } else {
-            // Draw invalidated keys.
             for (final Key key : mInvalidatedKeys) {
-                if (mKeyboard.hasKey(key)) {
-                    onDrawKey(key, canvas, paint);
+                if (!keyboard.hasKey(key)) {
+                    continue;
                 }
+                if (background != null) {
+                    // Need to redraw key's background on {@link #mOffscreenBuffer}.
+                    final int x = key.getX() + getPaddingLeft();
+                    final int y = key.getY() + getPaddingTop();
+                    mClipRect.set(x, y, x + key.getWidth(), y + key.getHeight());
+                    canvas.save();
+                    canvas.clipRect(mClipRect);
+                    canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+                    background.draw(canvas);
+                    canvas.restore();
+                }
+                onDrawKey(key, canvas, paint);
             }
         }
 
@@ -319,20 +317,22 @@
         mInvalidateAllKeys = false;
     }
 
-    private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
+    private void onDrawKey(@Nonnull final Key key, @Nonnull final Canvas canvas,
+            @Nonnull final Paint paint) {
         final int keyDrawX = key.getDrawX() + getPaddingLeft();
         final int keyDrawY = key.getY() + getPaddingTop();
         canvas.translate(keyDrawX, keyDrawY);
 
-        final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
         final KeyVisualAttributes attr = key.getVisualAttributes();
-        final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
+        final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(key.getHeight(), attr);
         params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
 
         if (!key.isSpacer()) {
             final Drawable background = key.selectBackgroundDrawable(
                     mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
-            onDrawKeyBackground(key, canvas, background);
+            if (background != null) {
+                onDrawKeyBackground(key, canvas, background);
+            }
         }
         onDrawKeyTopVisuals(key, canvas, paint, params);
 
@@ -340,8 +340,8 @@
     }
 
     // Draw key background.
-    protected void onDrawKeyBackground(final Key key, final Canvas canvas,
-            final Drawable background) {
+    protected void onDrawKeyBackground(@Nonnull final Key key, @Nonnull final Canvas canvas,
+            @Nonnull final Drawable background) {
         final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.getHeight();
         final int bgWidth, bgHeight, bgX, bgY;
@@ -373,15 +373,17 @@
     }
 
     // Draw key top visuals.
-    protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
-            final KeyDrawParams params) {
+    protected void onDrawKeyTopVisuals(@Nonnull final Key key, @Nonnull final Canvas canvas,
+            @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) {
         final int keyWidth = key.getDrawWidth();
         final int keyHeight = key.getHeight();
         final float centerX = keyWidth * 0.5f;
         final float centerY = keyHeight * 0.5f;
 
         // Draw key label.
-        final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
+        final Keyboard keyboard = getKeyboard();
+        final Drawable icon = (keyboard == null) ? null
+                : key.getIcon(keyboard.mIconsSet, params.mAnimAlpha);
         float labelX = centerX;
         float labelBaseline = centerY;
         final String label = key.getLabel();
@@ -500,8 +502,8 @@
     }
 
     // Draw popup hint "..." at the bottom right corner of the key.
-    protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
-            final KeyDrawParams params) {
+    protected void drawKeyPopupHint(@Nonnull final Key key, @Nonnull final Canvas canvas,
+            @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) {
         if (TextUtils.isEmpty(mKeyPopupHintLetter)) {
             return;
         }
@@ -518,15 +520,15 @@
         canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint);
     }
 
-    protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
-            final int y, final int width, final int height) {
+    protected static void drawIcon(@Nonnull final Canvas canvas,@Nonnull final Drawable icon,
+            final int x, final int y, final int width, final int height) {
         canvas.translate(x, y);
         icon.setBounds(0, 0, width, height);
         icon.draw(canvas);
         canvas.translate(-x, -y);
     }
 
-    public Paint newLabelPaint(final Key key) {
+    public Paint newLabelPaint(@Nullable final Key key) {
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
         if (key == null) {
@@ -560,7 +562,7 @@
      * @see #invalidateAllKeys
      */
     public void invalidateKey(@Nullable final Key key) {
-        if (key == null || mInvalidateAllKeys) {
+        if (mInvalidateAllKeys || key == null) {
             return;
         }
         mInvalidatedKeys.add(key);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 70e1167..973e956 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -106,15 +106,17 @@
 
         @Override
         public String toString() {
-            if (!mIsValid) return "INVALID";
-            if (mIsAlphabetMode) {
-                if (mIsAlphabetShiftLocked) return "ALPHABET_SHIFT_LOCKED";
-                return "ALPHABET_" + shiftModeToString(mShiftMode);
-            } else if (mIsEmojiMode) {
-                return "EMOJI";
-            } else {
-                return "SYMBOLS_" + shiftModeToString(mShiftMode);
+            if (!mIsValid) {
+                return "INVALID";
             }
+            if (mIsAlphabetMode) {
+                return mIsAlphabetShiftLocked ? "ALPHABET_SHIFT_LOCKED"
+                        : "ALPHABET_" + shiftModeToString(mShiftMode);
+            }
+            if (mIsEmojiMode) {
+                return "EMOJI";
+            }
+            return "SYMBOLS_" + shiftModeToString(mShiftMode);
         }
     }
 
@@ -133,9 +135,16 @@
         mPrevSymbolsKeyboardWasShifted = false;
         mShiftKeyState.onRelease();
         mSymbolKeyState.onRelease();
-        onRestoreKeyboardState(autoCapsFlags, recapitalizeMode);
+        if (mSavedKeyboardState.mIsValid) {
+            onRestoreKeyboardState(autoCapsFlags, recapitalizeMode);
+            mSavedKeyboardState.mIsValid = false;
+        } else {
+            // Reset keyboard to alphabet mode.
+            setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
+        }
     }
 
+    // Constants for {@link SavedKeyboardState#mShiftMode} and {@link #setShifted(int)}.
     private static final int UNSHIFT = 0;
     private static final int MANUAL_SHIFT = 1;
     private static final int AUTOMATIC_SHIFT = 2;
@@ -165,28 +174,24 @@
             Log.d(TAG, "onRestoreKeyboardState: saved=" + state
                     + " " + stateToString(autoCapsFlags, recapitalizeMode));
         }
-        if (!state.mIsValid || state.mIsAlphabetMode) {
-            setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
-        } else if (state.mIsEmojiMode) {
-            setEmojiKeyboard();
-        } else {
-            if (state.mShiftMode == MANUAL_SHIFT) {
-                setSymbolsShiftedKeyboard();
-            } else {
-                setSymbolsKeyboard();
-            }
-        }
-
-        if (!state.mIsValid) return;
-        state.mIsValid = false;
-
+        mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
         if (state.mIsAlphabetMode) {
+            setAlphabetKeyboard(autoCapsFlags, recapitalizeMode);
             setShiftLocked(state.mIsAlphabetShiftLocked);
             if (!state.mIsAlphabetShiftLocked) {
                 setShifted(state.mShiftMode);
             }
+            return;
+        }
+        if (state.mIsEmojiMode) {
+            setEmojiKeyboard();
+            return;
+        }
+        // Symbol mode
+        if (state.mShiftMode == MANUAL_SHIFT) {
+            setSymbolsShiftedKeyboard();
         } else {
-            mPrevMainKeyboardWasShiftLocked = state.mIsAlphabetShiftLocked;
+            setSymbolsKeyboard();
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index d256f37..e6d69a9 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -255,7 +255,7 @@
         handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_TYPING);
         final String text = performSpecificTldProcessingOnTextInput(rawText);
         if (SpaceState.PHANTOM == mSpaceState) {
-            promotePhantomSpace(settingsValues);
+            insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
         }
         mConnection.commitText(text, 1);
         StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode());
@@ -322,7 +322,7 @@
             final int firstChar = Character.codePointAt(suggestion, 0);
             if (!settingsValues.isWordSeparator(firstChar)
                     || settingsValues.isUsuallyPrecededBySpace(firstChar)) {
-                promotePhantomSpace(settingsValues);
+                insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
             }
         }
 
@@ -584,7 +584,9 @@
                 if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
                     final String[] commitParts = candidate.mWord.split(Constants.WORD_SEPARATOR, 2);
                     batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
-                    promotePhantomSpace(settingsValues);
+                    if (SpaceState.PHANTOM == mSpaceState) {
+                        insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
+                    }
                     mConnection.commitText(commitParts[0], 0);
                     StatsUtils.onWordCommitUserTyped(commitParts[0], mWordComposer.isBatchMode());
                     mSpaceState = SpaceState.PHANTOM;
@@ -861,7 +863,7 @@
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
             }
-            promotePhantomSpace(settingsValues);
+            insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
         }
 
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
@@ -972,7 +974,7 @@
         }
 
         if (needsPrecedingSpace) {
-            promotePhantomSpace(settingsValues);
+            insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
         }
 
         if (tryPerformDoubleSpacePeriod(event, inputTransaction)) {
@@ -1961,14 +1963,14 @@
     }
 
     /**
-     * Promote a phantom space to an actual space.
+     * Insert an automatic space, if the options allow it.
      *
-     * This essentially inserts a space, and that's it. It just checks the options and the text
-     * before the cursor are appropriate before doing it.
+     * This checks the options and the text before the cursor are appropriate before inserting
+     * an automatic space.
      *
      * @param settingsValues the current values of the settings.
      */
-    private void promotePhantomSpace(final SettingsValues settingsValues) {
+    private void insertAutomaticSpaceIfOptionsAndTextAllow(final SettingsValues settingsValues) {
         if (settingsValues.shouldInsertSpacesAutomatically()
                 && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
                 && !mConnection.textBeforeCursorLooksLikeURL()) {
@@ -1991,7 +1993,7 @@
         }
         mConnection.beginBatchEdit();
         if (SpaceState.PHANTOM == mSpaceState) {
-            promotePhantomSpace(settingsValues);
+            insertAutomaticSpaceIfOptionsAndTextAllow(settingsValues);
         }
         final SuggestedWordInfo autoCommitCandidate = mSuggestedWords.getAutoCommitCandidate();
         // Commit except the last word for phrase gesture if the top suggestion is eligible for auto
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 013f024..0e7f471 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -34,6 +34,7 @@
 import java.util.Locale;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 /**
  * A helper class to deal with subtype locales.
@@ -273,7 +274,7 @@
     }
 
     @Nonnull
-    public static String getSubtypeNameForLogging(@Nonnull final InputMethodSubtype subtype) {
+    public static String getSubtypeNameForLogging(@Nullable final InputMethodSubtype subtype) {
         if (subtype == null) {
             return "<null subtype>";
         }
diff --git a/native/dicttoolkit/NativeFileList.mk b/native/dicttoolkit/NativeFileList.mk
index d2c8c3a..9a547b0 100644
--- a/native/dicttoolkit/NativeFileList.mk
+++ b/native/dicttoolkit/NativeFileList.mk
@@ -39,5 +39,6 @@
     $(addprefix offdevice_intermediate_dict/, \
         offdevice_intermediate_dict_test.cpp) \
     $(addprefix utils/, \
+        arguments_parser_test.cpp \
         command_utils_test.cpp \
         utf8_utils_test.cpp)
diff --git a/native/dicttoolkit/src/utils/arguments_parser.cpp b/native/dicttoolkit/src/utils/arguments_parser.cpp
index 039dae3..52cc7b2 100644
--- a/native/dicttoolkit/src/utils/arguments_parser.cpp
+++ b/native/dicttoolkit/src/utils/arguments_parser.cpp
@@ -16,18 +16,32 @@
 
 #include "utils/arguments_parser.h"
 
+#include <unordered_set>
+
 namespace latinime {
 namespace dicttoolkit {
 
 const int ArgumentSpec::UNLIMITED_COUNT = -1;
 
 bool ArgumentsParser::validateSpecs() const {
+    std::unordered_set<std::string> argumentNameSet;
     for (size_t i = 0; i < mArgumentSpecs.size() ; ++i) {
-        if (mArgumentSpecs[i].getMinCount() != mArgumentSpecs[i].getMaxCount()
-                && i != mArgumentSpecs.size() - 1) {
-            AKLOGE("Variable length argument must be at the end.");
+        if (mArgumentSpecs[i].getMinCount() == 0 && mArgumentSpecs[i].getMaxCount() == 0) {
+            AKLOGE("minCount = maxCount = 0 for %s.", mArgumentSpecs[i].getName().c_str());
             return false;
         }
+        if (mArgumentSpecs[i].getMinCount() != mArgumentSpecs[i].getMaxCount()
+                && i != mArgumentSpecs.size() - 1) {
+            AKLOGE("Variable length argument must be at the end.",
+                    mArgumentSpecs[i].getName().c_str()v  );
+            return false;
+        }
+        if (argumentNameSet.count(mArgumentSpecs[i].getName()) > 0) {
+            AKLOGE("Multiple arguments have the same name \"%s\".",
+                    mArgumentSpecs[i].getName().c_str());
+            return false;
+        }
+        argumentNameSet.insert(mArgumentSpecs[i].getName());
     }
     return true;
 }
diff --git a/native/dicttoolkit/src/utils/arguments_parser.h b/native/dicttoolkit/src/utils/arguments_parser.h
index be2dd87..510a872 100644
--- a/native/dicttoolkit/src/utils/arguments_parser.h
+++ b/native/dicttoolkit/src/utils/arguments_parser.h
@@ -97,8 +97,8 @@
 
 class ArgumentsParser {
  public:
-    ArgumentsParser(std::unordered_map<std::string, OptionSpec> &&optionSpecs,
-            std::vector<ArgumentSpec> &&argumentSpecs)
+    ArgumentsParser(const std::unordered_map<std::string, OptionSpec> &&optionSpecs,
+            const std::vector<ArgumentSpec> &&argumentSpecs)
             : mOptionSpecs(std::move(optionSpecs)), mArgumentSpecs(std::move(argumentSpecs)) {}
 
     const ArgumentsAndOptions parseArguments(const int argc, char **argv) const;
diff --git a/native/dicttoolkit/tests/utils/arguments_parser_test.cpp b/native/dicttoolkit/tests/utils/arguments_parser_test.cpp
new file mode 100644
index 0000000..e79425b
--- /dev/null
+++ b/native/dicttoolkit/tests/utils/arguments_parser_test.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils/arguments_parser.h"
+
+#include <gtest/gtest.h>
+
+namespace latinime {
+namespace dicttoolkit {
+namespace {
+
+TEST(ArgumentsParserTests, TestValitadeSpecs) {
+    {
+        std::unordered_map<std::string, OptionSpec> optionSpecs;
+        std::vector<ArgumentSpec> argumentSpecs;
+        EXPECT_TRUE(
+                ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)).validateSpecs());
+    }
+    {
+        std::unordered_map<std::string, OptionSpec> optionSpecs;
+        optionSpecs["a"] = OptionSpec::keyValueOption("valueName", "default", "description");
+        const std::vector<ArgumentSpec> argumentSpecs = {
+            ArgumentSpec::singleArgument("name", "description"),
+            ArgumentSpec::variableLengthArguments("name2", 0 /* minCount */,  1 /* maxCount */,
+                    "description2")
+        };
+        EXPECT_TRUE(
+                ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs)).validateSpecs());
+    }
+    {
+        const std::vector<ArgumentSpec> argumentSpecs = {
+            ArgumentSpec::variableLengthArguments("name", 0 /* minCount */,  0 /* maxCount */,
+                    "description")
+        };
+        EXPECT_FALSE(ArgumentsParser(std::unordered_map<std::string, OptionSpec>(),
+                std::move(argumentSpecs)).validateSpecs());
+    }
+    {
+        const std::vector<ArgumentSpec> argumentSpecs = {
+            ArgumentSpec::singleArgument("name", "description"),
+            ArgumentSpec::variableLengthArguments("name", 0 /* minCount */,  1 /* maxCount */,
+                    "description")
+        };
+        EXPECT_FALSE(ArgumentsParser(std::unordered_map<std::string, OptionSpec>(),
+                std::move(argumentSpecs)).validateSpecs());
+    }
+    {
+        const std::vector<ArgumentSpec> argumentSpecs = {
+            ArgumentSpec::variableLengthArguments("name", 0 /* minCount */,  1 /* maxCount */,
+                    "description"),
+            ArgumentSpec::singleArgument("name2", "description2")
+        };
+        EXPECT_FALSE(ArgumentsParser(std::unordered_map<std::string, OptionSpec>(),
+                std::move(argumentSpecs)).validateSpecs());
+    }
+}
+
+} // namespace
+} // namespace dicttoolkit
+} // namespace latinime