org.diorite.command.Arguments.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.command.Arguments.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.diorite.command;

import java.util.Collection;
import java.util.Iterator;
import java.util.function.Function;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import org.diorite.BlockLocation;
import org.diorite.Diorite;
import org.diorite.ILocation;
import org.diorite.ImmutableLocation;
import org.diorite.command.exceptions.InvalidCommandArgumentException;
import org.diorite.entity.Entity;
import org.diorite.entity.Player;
import org.diorite.utils.SimpleEnum;
import org.diorite.utils.math.DioriteMathUtils;
import org.diorite.utils.reflections.DioriteReflectionUtils;

/**
 * Class used to read command parametrs instead of String[] to make some stuff easier/simpler
 */
public class Arguments implements Iterable<String> {
    private final String[] args;

    /**
     * Construct new arguments wrapper.
     * WARN: it don't make copy/clone of given array!
     *
     * @param args string array to wrap.
     */
    public Arguments(final String[] args) {
        this.args = args;
    }

    /**
     * @return raw, editable, wrapped String[]
     */
    public String[] getRawArgs() {
        return this.args;
    }

    /**
     * @return length of wrapped string array. returns args.length
     */
    public int length() {
        return this.args.length;
    }

    /**
     * Check if array length is {@code >} than selected index.
     * Remember that elements are counted from 0, not 1.
     *
     * @param index index of element, 0 is first element.
     *
     * @return {@code eargs.length > index}
     */
    public boolean has(final int index) {
        return this.args.length > index;
    }

    /**
     * Get argument of any type using given function{@literal <String, T>}.
     *
     * @param index index of element, 0 is first element.
     * @param func  function used to change string to any type.
     * @param <T>   Type of returned
     *
     * @return selected element after converting to given type.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public <T> T get(final int index, final Function<String, T> func) throws ArrayIndexOutOfBoundsException {
        return func.apply(this.asString(index));
    }

    /**
     * Check if any argument is equals to given string.
     *
     * @param str string to find.
     *
     * @return true if array contains given string.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public boolean contains(final String str) throws ArrayIndexOutOfBoundsException {
        for (final String arg : this.args) {
            if (arg.equals(str)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if any argument is equals with ignore case to given string.
     *
     * @param str string to find.
     *
     * @return true if array contains given string.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public boolean containsIgnoreCase(final String str) throws ArrayIndexOutOfBoundsException {
        for (final String arg : this.args) {
            if (arg.equalsIgnoreCase(str)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get value from enum for given argument,
     * it will try match it as name or ordinal id.
     * Name is ignore-case.
     *
     * @param enumClass class of enum.
     * @param index     index of element, 0 is first element.
     * @param <T>       type of enum.
     *
     * @return enum element or null.
     */
    public <T extends SimpleEnum<T>> T asSimpleEnumValue(final Class<T> enumClass, final int index) {
        final String name = this.asString(index);
        final int num = asInt(name, -1);
        return DioriteReflectionUtils.getSimpleEnumValueSafe(name, num, enumClass);
    }

    /**
     * Get value from enum for given argument,
     * it will try match it as name or ordinal id.
     * Name is ignore-case.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value, can't be null.
     * @param <T>   type of enum.
     *
     * @return enum element or default.
     */
    public <T extends SimpleEnum<T>> T asSimpleEnumValue(final int index, final T def) {
        final String name = this.asString(index);
        final int num = asInt(name, -1);
        return DioriteReflectionUtils.getSimpleEnumValueSafe(name, num, def);
    }

    /**
     * Get value from enum for given argument,
     * it will try match it as name or ordinal id.
     * Name is ignore-case.
     *
     * @param enumClass class of enum.
     * @param index     index of element, 0 is first element.
     * @param <T>       type of enum.
     *
     * @return enum element or null.
     */
    public <T extends Enum<T>> T asEnumValue(final Class<T> enumClass, final int index) {
        final String name = this.asString(index);
        final int num = asInt(name, -1);
        return DioriteReflectionUtils.getEnumValueSafe(name, num, enumClass);
    }

    /**
     * Get value from enum for given argument,
     * it will try match it as name or ordinal id.
     * Name is ignore-case.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value, can't be null.
     * @param <T>   type of enum.
     *
     * @return enum element or default.
     */
    public <T extends Enum<T>> T asEnumValue(final int index, final T def) {
        final String name = this.asString(index);
        final int num = asInt(name, -1);
        return DioriteReflectionUtils.getEnumValueSafe(name, num, def);
    }

    /**
     * Get selected argument as {@link Player}, may return null if player is offline.
     *
     * @param index index of element, 0 is first element.
     *
     * @return {@link Player} or null if player is offline.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Player asPlayer(final int index) throws ArrayIndexOutOfBoundsException {
        return Diorite.getCore().getPlayer(this.asString(index));
    } // TODO: add asOfflinePlayer

    /**
     * Read x, y, z block coordinates from next 3 elements.
     * If argument starts from <B>~</B> then returned coordinates are relative to given origin.
     *
     * @param startIndex index of first coordinate, 0 is first element.
     * @param origin     used to get relative coordinates, if null {@link BlockLocation#ZERO} is used.
     *
     * @return x, y, z block coordinates as {@link BlockLocation}
     *
     * @throws InvalidCommandArgumentException if any of numbers can't be parsed to coordinate.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > startIndex + 2}
     */
    public BlockLocation readCoordinates(final int startIndex, BlockLocation origin)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        this.check(startIndex + 2);
        if (origin == null) {
            origin = BlockLocation.ZERO;
        }
        return new BlockLocation(this.readCoordinate(startIndex, origin.getX()),
                this.readCoordinate(startIndex + 1, origin.getY()),
                this.readCoordinate(startIndex + 2, origin.getZ()), origin.getWorld());
    }

    /**
     * Read x, y, z (and yaw, pitch if needed) coordinates.
     * If argument starts from <B>~</B> then returned coordinates are relative to given entity.
     *
     * @param startIndex   index of first coordinate, 0 is first element.
     * @param withRotation if yaw and pitch should be also read.
     * @param entity       entity to get origin location used to get relative coordinates.
     *
     * @return x, y, z, yaw, pitch as {@link ImmutableLocation}
     *
     * @throws InvalidCommandArgumentException if any of numbers can't be parsed to coordinate.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > startIndex + 2} or + 4 if withRotation is true.
     */
    public ImmutableLocation readCoordinates(final int startIndex, final boolean withRotation, final Entity entity)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        return this.readCoordinates(startIndex, withRotation, entity.getLocation());
    }

    /**
     * Read x, y, z (and yaw, pitch if needed) coordinates.
     * If argument starts from <B>~</B> then returned coordinates are relative to given location.
     *
     * @param startIndex   index of first coordinate, 0 is first element.
     * @param withRotation if yaw and pitch should be also read.
     * @param origin       used to get relative coordinates, if null {@link ImmutableLocation#ZERO} is used.
     *
     * @return x, y, z, yaw, pitch as {@link ImmutableLocation}
     *
     * @throws InvalidCommandArgumentException if any of numbers can't be parsed to coordinate.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > startIndex + 2} or + 4 if withRotation is true.
     */
    public ImmutableLocation readCoordinates(final int startIndex, final boolean withRotation, ILocation origin)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        this.check(startIndex + (withRotation ? 4 : 2));
        if (origin == null) {
            origin = ImmutableLocation.ZERO;
        }
        final double x = this.readCoordinate(startIndex, origin.getX());
        final double y = this.readCoordinate(startIndex + 1, origin.getY());
        final double z = this.readCoordinate(startIndex + 2, origin.getZ());
        if (!withRotation) {
            return new ImmutableLocation(x, y, z, origin.getYaw(), origin.getPitch(), origin.getWorld());
        }
        return new ImmutableLocation(x, y, z, this.readRotation(startIndex + 3, origin.getYaw()),
                this.readRotation(startIndex + 4, origin.getPitch()), origin.getWorld());
    }

    /**
     * Read single coordinate (x, y or z).
     * If argument starts from <B>~</B> then returned coordinate is relative to given origin.
     * {@literal 4   -> 4}
     * {@literal ~   -> origin}
     * {@literal ~3  -> origin + 3}
     * {@literal ~-3 -> origin - 3}
     *
     * @param index  index of element, 0 is first element.
     * @param origin used to get relative coordinates.
     *
     * @return coordinate.
     *
     * @throws InvalidCommandArgumentException if number can't be parsed to coordinate.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > index}
     */
    public double readCoordinate(final int index, final double origin)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        this.check(index);
        final String str = this.args[index];
        if (str.charAt(0) == '~') {
            if (str.length() == 1) {
                return origin;
            }
            final Double d = asDouble(str.substring(1));
            if (d == null) {
                throw new InvalidCommandArgumentException("can't parse coordinate from: " + str);
            }
            return origin + d;
        }
        final Double d = asDouble(str);
        if (d == null) {
            throw new InvalidCommandArgumentException("can't parse coordinate from: " + str);
        }
        return d;
    }

    /**
     * Read single rotation (yaw or pitch).
     * If argument starts from <B>~</B> then returned rotation is relative to given origin.
     * {@literal 0.4   -> 0.4}
     * {@literal ~     -> origin}
     * {@literal ~0.3  -> origin + 0.3}
     * {@literal ~-0.3 -> origin - 0.3}
     *
     * @param index  index of element, 0 is first element.
     * @param origin used to get relative rotation.
     *
     * @return rotation.
     *
     * @throws InvalidCommandArgumentException if number can't be parsed to rotation.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > index}
     */
    public float readRotation(final int index, final float origin)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        this.check(index);
        final String str = this.args[index];
        if (str.charAt(0) == '~') {
            if (str.length() == 1) {
                return origin;
            }
            final Float f = asFloat(str.substring(1));
            if (f == null) {
                throw new InvalidCommandArgumentException("can't parse rotation from: " + str);
            }
            return origin + f;
        }
        final Float f = asFloat(str);
        if (f == null) {
            throw new InvalidCommandArgumentException("can't parse rotation from: " + str);
        }
        return f;
    }

    /**
     * Read single coordinate (x, y or z).
     * If argument starts from <B>~</B> then returned coordinate is relative to given origin.
     * {@literal 4   -> 4}
     * {@literal ~   -> origin}
     * {@literal ~3  -> origin + 3}
     * {@literal ~-3 -> origin - 3}
     *
     * @param index  index of element, 0 is first element.
     * @param origin used to get relative coordinates.
     *
     * @return coordinate.
     *
     * @throws InvalidCommandArgumentException if number can't be parsed to coordinate.
     * @throws ArrayIndexOutOfBoundsException  if {@code args.length > index}
     */
    public int readCoordinate(final int index, final int origin)
            throws ArrayIndexOutOfBoundsException, InvalidCommandArgumentException {
        this.check(index);
        final String str = this.args[index];
        if (str.charAt(0) == '~') {
            if (str.length() == 1) {
                return origin;
            }
            final Integer i = asInt(str.substring(1));
            if (i == null) {
                throw new InvalidCommandArgumentException("can't parse coordinate from: " + str);
            }
            return origin + i;
        }
        final Integer i = asInt(str);
        if (i == null) {
            throw new InvalidCommandArgumentException("can't parse coordinate from: " + str);
        }
        return i;
    }

    /**
     * Return raw value from wrapped array of strings.
     *
     * @param index index of element, 0 is first element.
     *
     * @return raw value from wrapped array of strings.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public String asString(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return this.args[index];
    }

    /**
     * Parse selected argument to int, if argument can't be parsed to int, then it will return default value.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value.
     *
     * @return parsed value or default one.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public int asInt(final int index, final int def) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asInt(this.args[index], def);
    }

    /**
     * Parse selected argument to long, if argument can't be parsed to long, then it will return default value.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value.
     *
     * @return parsed value or default one.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public long asLong(final int index, final long def) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asLong(this.args[index], def);
    }

    /**
     * Parse selected argument to double, if argument can't be parsed to double, then it will return default value.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value.
     *
     * @return parsed value or default one.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public double asDouble(final int index, final double def) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asDouble(this.args[index], def);
    }

    /**
     * Parse selected argument to float, if argument can't be parsed to float, then it will return default value.
     *
     * @param index index of element, 0 is first element.
     * @param def   default value.
     *
     * @return parsed value or default one.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public float asFloat(final int index, final float def) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asFloat(this.args[index], def);
    }

    /**
     * Parse selected argument to int, if argument can't be parsed to int, then it will return null.
     *
     * @param index index of element, 0 is first element.
     *
     * @return parsed value or null.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Integer asInt(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asInt(this.args[index]);
    }

    /**
     * Parse selected argument to long, if argument can't be parsed to long, then it will return null.
     *
     * @param index index of element, 0 is first element.
     *
     * @return parsed value or null.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Long asLong(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asLong(this.args[index]);
    }

    /**
     * Parse selected argument to double, if argument can't be parsed to double, then it will return null.
     *
     * @param index index of element, 0 is first element.
     *
     * @return parsed value or null.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Double asDouble(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asDouble(this.args[index]);
    }

    /**
     * Parse selected argument to float, if argument can't be parsed to float, then it will return null.
     *
     * @param index index of element, 0 is first element.
     *
     * @return parsed value or null.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Float asFloat(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return asFloat(this.args[index]);
    }

    /**
     * Simple parse selected argument to boolean.
     *
     * @param index index of element, 0 is first element.
     *
     * @return parsed value/
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     * @see Boolean#parseBoolean(String)
     */
    public boolean asBoolean(final int index) throws ArrayIndexOutOfBoundsException {
        this.check(index);
        return Boolean.parseBoolean(this.args[index]);
    }

    /**
     * Parse element to boolean using two collections of words, for true and false values.
     * If any of trueWords is equals (equalsIgnoreCase) to given element, then method returns ture.
     * If any of falseWords is equals (equalsIgnoreCase) to given element, then method returns false.
     * If given word don't match any words from collections, then method returns null
     *
     * @param index      index of element, 0 is first element.
     * @param trueWords  words that mean "true"
     * @param falseWords words that mean "false"
     *
     * @return true/false or null.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     */
    public Boolean asBoolean(final int index, final Collection<String> trueWords,
            final Collection<String> falseWords) throws ArrayIndexOutOfBoundsException {
        final String str = this.asString(index);
        if (trueWords.stream().anyMatch(s -> s.equalsIgnoreCase(str))) {
            return Boolean.TRUE;
        }
        if (falseWords.stream().anyMatch(s -> s.equalsIgnoreCase(str))) {
            return Boolean.FALSE;
        }
        return null;
    }

    /**
     * @return all arguments as single string.
     *
     * @see StringUtils#join(Object[], char)
     */
    public String asText() {
        return StringUtils.join(this.args, ' ');
    }

    /**
     * Join all arguments starting from selected index.
     *
     * @param fromIndex index of first element.
     *
     * @return arguments as single string.
     *
     * @see StringUtils#join(Object[], char, int, int)
     */
    public String asText(final int fromIndex) {
        return StringUtils.join(this.args, ' ', fromIndex, this.args.length);
    }

    /**
     * Join arguments starting from selected index to other selected index.
     *
     * @param fromIndex index of first element.
     * @param toIndex   index of last element.
     *
     * @return arguments as single string.
     *
     * @throws ArrayIndexOutOfBoundsException if {@code args.length > index}
     * @see StringUtils#join(Object[], char, int, int)
     */
    public String asText(final int fromIndex, final int toIndex) throws ArrayIndexOutOfBoundsException {
        return StringUtils.join(this.args, ' ', fromIndex, toIndex);
    }

    private void check(final int index) throws ArrayIndexOutOfBoundsException {
        if (this.args.length <= index) {
            throw new ArrayIndexOutOfBoundsException(
                    "Out of range, length: " + Arguments.this.args.length + ", index: " + index);
        }
    }

    /**
     * Parse string to int, if string can't be parsed to int, then it will return null.
     *
     * @param str string to parse
     *
     * @return parsed value or null.
     *
     * @see DioriteMathUtils#asInt(String)
     */
    public static Integer asInt(final String str) {
        return DioriteMathUtils.asInt(str);
    }

    /**
     * Parse string to long, if string can't be parsed to long, then it will return null.
     *
     * @param str string to parse
     *
     * @return parsed value or null.
     *
     * @see DioriteMathUtils#asLong(String)
     */
    public static Long asLong(final String str) {
        return DioriteMathUtils.asLong(str);
    }

    /**
     * Parse string to double, if string can't be parsed to double, then it will return null.
     *
     * @param str string to parse
     *
     * @return parsed value or null.
     *
     * @see DioriteMathUtils#asDouble(String)
     */
    public static Double asDouble(final String str) {
        return DioriteMathUtils.asDouble(str);
    }

    /**
     * Parse string to float, if string can't be parsed to float, then it will return null.
     *
     * @param str string to parse
     *
     * @return parsed value or null.
     *
     * @see DioriteMathUtils#asFloat(String)
     */
    public static Float asFloat(final String str) {
        return DioriteMathUtils.asFloat(str);
    }

    /**
     * Parse string to int, if string can't be parsed to int, then it will return given default value.
     *
     * @param str string to parse
     * @param def default value.
     *
     * @return parsed value or default value.
     *
     * @see DioriteMathUtils#asInt(String, int)
     */
    public static int asInt(final String str, final int def) {
        return DioriteMathUtils.asInt(str, def);
    }

    /**
     * Parse string to long, if string can't be parsed to long, then it will return given default value.
     *
     * @param str string to parse
     * @param def default value.
     *
     * @return parsed value or default value.
     *
     * @see DioriteMathUtils#asLong(String, long)
     */
    public static Long asLong(final String str, final long def) {
        return DioriteMathUtils.asLong(str, def);
    }

    /**
     * Parse string to double, if string can't be parsed to double, then it will return given default value.
     *
     * @param str string to parse
     * @param def default value.
     *
     * @return parsed value or default value.
     *
     * @see DioriteMathUtils#asDouble(String, double)
     */
    public static Double asDouble(final String str, final double def) {
        return DioriteMathUtils.asDouble(str, def);
    }

    /**
     * Parse string to float, if string can't be parsed to float, then it will return given default value.
     *
     * @param str string to parse
     * @param def default value.
     *
     * @return parsed value or default value.
     *
     * @see DioriteMathUtils#asFloat(String, float)
     */
    public static Float asFloat(final String str, final float def) {
        return DioriteMathUtils.asFloat(str, def);
    }

    /**
     * Simple parse boolean.
     *
     * @param str string to parse
     *
     * @return parsed value
     *
     * @see Boolean#parseBoolean(String)
     * @see DioriteMathUtils#asBoolean(String)
     */
    public static boolean asBoolean(final String str) {
        return DioriteMathUtils.asBoolean(str);
    }

    /**
     * Parse string to boolean using two collections of words, for true and false values.
     * If any of trueWords is equals (equalsIgnoreCase) to given string, then method returns ture.
     * If any of falseWords is equals (equalsIgnoreCase) to given string, then method returns false.
     * If given word don't match any words from collections, then method returns null
     *
     * @param str        string to parse.
     * @param trueWords  words that mean "true"
     * @param falseWords words that mean "false"
     *
     * @return true/false or null.
     *
     * @see DioriteMathUtils#asBoolean(String, Collection, Collection)
     */
    public static Boolean asBoolean(final String str, final Collection<String> trueWords,
            final Collection<String> falseWords) {
        return DioriteMathUtils.asBoolean(str, trueWords, falseWords);
    }

    /**
     * Join all strings from array to one single string,
     * separate elements by single space.
     *
     * @param strs string to join.
     *
     * @return joined strings, separated by single space.
     */
    public static String asText(final String[] strs) {
        if (strs == null) {
            return "";
        }
        return StringUtils.join(strs, ' ');
    }

    /**
     * Join all strings from array starting from given index
     * to one single string, separate elements by single space.
     *
     * @param strs      string to join.
     * @param fromIndex starting index.
     *
     * @return joined strings, separated by single space.
     */
    public static String asText(final String[] strs, final int fromIndex) {
        if (strs == null) {
            return "";
        }
        return StringUtils.join(strs, ' ', fromIndex, strs.length);
    }

    /**
     * Join all strings from array starting from given index to other given index,
     * to one single string, separate elements by single space.
     *
     * @param strs      string to join.
     * @param fromIndex starting index.
     * @param toIndex   end index.
     *
     * @return joined strings, separated by single space.
     */
    public static String asText(final String[] strs, final int fromIndex, final int toIndex) {
        if (strs == null) {
            return "";
        }
        return StringUtils.join(strs, ' ', fromIndex, toIndex);
    }

    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            private int index = 0;

            @Override
            public boolean hasNext() {
                return this.index < Arguments.this.args.length;
            }

            @Override
            public String next() {
                if (Arguments.this.args.length <= this.index) {
                    throw new ArrayIndexOutOfBoundsException(
                            "Out of range, length: " + Arguments.this.args.length + ", index: " + this.index);
                }
                return Arguments.this.args[this.index++];
            }
        };
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).appendSuper(super.toString())
                .append("args", this.args).toString();
    }
}