GraphicCreator.java

/*
 *                      ..::jDrawingLib::..
 *
 * Copyright (C) Federico Vera 2012 - 2023 <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.dkt.graphics.extras;

import com.dkt.graphics.elements.GArc;
import com.dkt.graphics.elements.GCircle;
import com.dkt.graphics.elements.GFillableE;
import com.dkt.graphics.elements.GLine;
import com.dkt.graphics.elements.GOval;
import com.dkt.graphics.elements.GPath;
import com.dkt.graphics.elements.GPoint;
import com.dkt.graphics.elements.GPoly;
import com.dkt.graphics.elements.GRectangle;
import com.dkt.graphics.elements.GRegPoly;
import com.dkt.graphics.elements.GString;
import com.dkt.graphics.elements.GVector;
import com.dkt.graphics.elements.Graphic;
import com.dkt.graphics.elements.GraphicE;
import com.dkt.graphics.exceptions.InvalidArgumentException;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Stroke;
import java.awt.geom.NoninvertibleTransformException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * This class came as an exercise to create {@link Graphic} objects from text
 * without modifying the classes themselves, it was later used on Graphic
 * Designer, and finally decided to add it to add it to the extras package.<br>
 * This class is pretty handy to create simple drawing based GUIs, since
 * manipulating strings is orders of magnitude easier than using Graphics. This
 * is not meant to be a programming language, it's simply a way of creating
 * simple (and not so simple) graphics.
 *
 * @author Federico Vera {@literal<[email protected]>}
 */
public class GraphicCreator {
    private final Pattern linespl = Pattern.compile("\\s+");
    private final Pattern comment = Pattern.compile("(?=[#])(.*\\n?)(?=\\n)");
    private final Pattern initial = Pattern.compile("[\\n|;]+(?=([^\"]*\"[^\"]*\")*[^\"]*$)");

    private int errCount, lineCount;
    private GradientPaint paint = null;
    private Stroke stroke = null;
    private Color color   = null;
    private Color colorf  = null;
    private Font font     = null;

    private static final Class<?> GCC = GraphicCreator.class;
    private static final Class<?> SAC = String[].class;
    private GClip clip;
    private GTransform transf;

    public GraphicCreator() {

    }

    private GraphicCreator(GraphicCreator gc) {
        transf = gc.transf;
        stroke = gc.stroke;
        colorf = gc.colorf;
        paint  = gc.paint;
        color  = gc.color;
        font   = gc.font;
        clip   = gc.clip;
    }

    public void clear() {
        transf = null;
        stroke = null;
        colorf = null;
        color  = null;
        paint  = null;
        font   = null;
        clip   = null;
    }

    public Graphic parse(String line) {
        //Remove comments
        String data    = comment.matcher(line).replaceAll("");
        //Split avoiding what's between " "
        String[] lines = initial.split(data);
        return parse(lines);
    }

    /**
     * Parses a {@code String} (that respects the documentation) into a
     * {@link Graphic}
     *
     * @param lines String representation of the graphic
     * @return {@code Graphic} object
     */
    public Graphic parse(String... lines) {
        final Graphic graphic = new Graphic(lines.length + 10);
        errCount = 0;
        lineCount = lines.length;

        for (final String line : lines) {
            final Object out = parseOne(line);

            if (out instanceof GraphicE ge) {
                graphic.add(ge);

                if (stroke != null) {
                    ge.setStroke(stroke);
                }

                if (color != null) {
                    ge.setPaint(color);
                }

                if (out instanceof GFillableE gf) {
                    if (paint != null) {
                        gf.setFillPaint(paint);
                        gf.setFill(true);
                        paint = null;
                    } else if (colorf != null) {
                        gf.setFillPaint(colorf);
                        gf.setFill(true);
                    }
                } else {
                    if (paint != null) {
                        ge.setPaint(paint);
                        paint = null;
                    }
                }
            } else if (out instanceof Color) {
                if (line.contains("colorf")) {
                    colorf = (Color)out;
                } else {
                    color  = (Color)out;
                }
            } else if (out instanceof Stroke) {
                stroke = (Stroke)out;
            } else if (out instanceof Font) {
                font = (Font)out;
            } else if (out instanceof GradientPaint) {
                paint = (GradientPaint)out;
            }
        }

        return graphic;
    }

    private Object parseOne(String line) {
        if (line == null) {
            return null;
        }

        String lline = line.trim();

        try {
            if (lline.isEmpty() ||
                lline.charAt(0) == '#' ||
                lline.charAt(0) == '!') {
                return null;
            }

            final String[] foo  = linespl.split(lline);
            final Method method = GCC.getMethod(foo[0], SAC);

            return method.invoke(this, (Object)foo);

        } catch (SecurityException |
                 NoSuchMethodException |
                 IllegalAccessException |
                 InvalidArgumentException |
                 InvocationTargetException ex) {
            errCount++;
        }

        return null;
    }

    /**
     * Retrieves the number of errors encountered during the parsing of a
     * given text.
     *
     * @return error count
     */
    public int getErrorCount() {
        return errCount;
    }

    public int getTotalLineCount() {
        return lineCount;
    }

    public Stroke stroke(String[] args) {
        final float w = Float.parseFloat(args[1]);

        int cap  = BasicStroke.CAP_SQUARE;
        int join = BasicStroke.JOIN_BEVEL;

        if (args.length <= 3) {
            return new BasicStroke(w, cap, join);
        }

        float offset = 0;
        boolean offsetSet = false;
        final ArrayList<Float> dashes = new ArrayList<>(args.length - 1);
        for (int i = 2; i < args.length; i++) {
            String arg = args[i].toLowerCase();
            switch (arg) {
                case "cap_butt"    -> cap = BasicStroke.CAP_BUTT;
                case "cap_round"   -> cap = BasicStroke.CAP_ROUND;
                case "cap_square"  -> cap = BasicStroke.CAP_SQUARE;
                case "join_bevel"  -> join = BasicStroke.JOIN_BEVEL;
                case "join_mitter" -> join = BasicStroke.JOIN_MITER;
                case "join_round"  -> join = BasicStroke.JOIN_ROUND;
                default -> {
                    if (!offsetSet) {
                        offset = Float.parseFloat(arg);
                        offsetSet = true;
                    } else {
                        dashes.add(Float.parseFloat(arg));
                    }
                }
            }
        }

        final float[] arr = new float[dashes.size()];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = dashes.get(i);
        }

        return new BasicStroke(w, cap, join, 0, arr, offset);
    }

    /**
     * Clears the global configurations {@code color}, {@code colorf}, {@code
     * stroke}, {@code paint} and {@code font}
     *
     * @param args these are ignored
     * @return {@code null}
     */
    public Object reset(String[] args) {
        checkArgs(args, 0);
        clear();
        return null;
    }

    /**
     * Sets a global font, if the argument is {@code no} then it will reset the
     * global font
     *
     * @param args the arguments will be used in {@link Font#decode(String)},
     * so for further documentation, please refer to it
     * @return Font
     */
    public Font font(String[] args) {
        if (args.length == 2 && args[1] != null && args[1].equals("no")){
            font = null;
            return null;
        }

        String name = concatenate(args, 1);

        return Font.decode(name);
    }

    /**
     * Set's the global fill color. If the argument is {@code no} then it will
     * reset the fill color. If this color is set all {@link GFillableE} will
     * be set to fill.<br>This color is also used as the secondary color on
     * gradients.<br><ul>
     * <li>If 3 arguments are passed there should be: {@code R[0-255] G[0-255]
     * B[0-255]},
     * <li>if 4 arguments are passed the format should be: {@code A[0-255]
     * R[0-255] G[0-255] B[0-255]}</ul>
     *
     * @param args color info
     * @return Fill color
     * @see GraphicCreator#color(String[])
     * @see GraphicCreator#gradient(String[])
     * @see GraphicCreator#gradient2(String[])
     * @see GFillableE#setFill(boolean)
     */
    public Color colorf(String[] args) {
        if (args.length == 2 &&
            args[1] != null &&
            args[1].equals("no")) {
            colorf = null;
            return null;
        }
        return color(args);
    }

    /**
     * Set's the global color.
     * This color is also used as the primary color on gradients.<br><ul>
     * <li>If 3 arguments are passed there should be: {@code R[0-255] G[0-255]
     * B[0-255]},
     * <li>if 4 arguments are passed the format should be: {@code A[0-255]
     * R[0-255] G[0-255] B[0-255]}</ul>
     *
     * @param args color info
     * @return Fill color
     * @see GraphicCreator#colorf(String[])
     * @see GraphicCreator#gradient(String[])
     * @see GraphicCreator#gradient2(String[])
     * @see GFillableE#setFill(boolean)
     */
    public Color color(String[] args) {
        checkArgs(args, 1, 3, 4);
        switch (args.length - 1) {
            case 1 -> { //[argb]
                return new Color(getInt(args[1]), true);
            }
            case 3 -> { //[r, g, b]
                final int r = getInt(args[1], 0, 255);
                final int g = getInt(args[2], 0, 255);
                final int b = getInt(args[3], 0, 255);
                return new Color(r, g, b);
            }
            case 4 -> { //[a, r, g, b]
                final int a = getInt(args[1], 0, 255);
                final int r = getInt(args[2], 0, 255);
                final int g = getInt(args[3], 0, 255);
                final int b = getInt(args[4], 0, 255);
                return new Color(r, g, b, a);
            }
        }
        return null;
    }

    /**
     * Creates a linear gradient between {@code color} and {@code colorf}, so
     * they need to be set before calling this method.<br>
     * The arguments should be {@code x1, y1, x2, y2}, that is, the coordinates
     * of the first point of the gradient vector and the coordinates of the last
     * point
     *
     * @param args vector arguments
     * @return Linear gradient
     * @see GraphicCreator#color(String[])
     * @see GraphicCreator#colorf(String[])
     * @see GraphicCreator#gradient2(String[])
     */
    public GradientPaint gradient(String[] args) {
        return grad(args, false);
    }

    /**
     * Creates a linear cyclic gradient between {@code color} and
     * {@code colorf}, so they need to be set before calling this method.<br>
     * The arguments should be {@code x1, y1, x2, y2}, that is, the coordinates
     * of the first point of the gradient vector and the coordinates of the last
     * point
     *
     * @param args vector arguments
     * @return Linear cyclic gradient
     * @see GraphicCreator#color(String[])
     * @see GraphicCreator#colorf(String[])
     * @see GraphicCreator#gradient(String[])
     */
    public GradientPaint gradient2(String[] args) {
        return grad(args, true);
    }

    private GradientPaint grad(String[] args, boolean cyclic) {
        checkArgs(args, 4);
        return new GradientPaint(
                getInt(args[1]),
                getInt(args[2]),
                color,
                getInt(args[3]),
                getInt(args[4]),
                colorf,
                cyclic
        );
    }

    public Graphic for1(String[] args) {
        final int i = getInt(args[1]);
        final int s = getInt(args[2]);
        final int f = getInt(args[3]);

        if (s == 0) {
            return new Graphic();
        }

        String txt = concatenate(args, 4);
        //Check the format before creating the arrays
        String.format(txt, 0, 0, 0);
        txt = txt.replace("\"", "");

        StringBuilder foo = new StringBuilder(txt.length() * (f - i) / s * 2);
        final int[] inter = getInterval(i, s, f);

        for (int ii : inter) {
           foo.append(String.format(txt, ii)).append("\n");
        }

        String[] lines = foo.toString().split("\\n|;");
        lineCount+= lines.length;

        return new GraphicCreator(this).parse(lines);
    }

    public Graphic for2(String[] args) {
        final int i1 = getInt(args[1]);
        final int s1 = getInt(args[2]);
        final int f1 = getInt(args[3]);
        final int i2 = getInt(args[4]);
        final int s2 = getInt(args[5]);
        final int f2 = getInt(args[6]);

        if (s1 == 0 | s2 == 0) {
            return new Graphic();
        }

        String txt = concatenate(args, 7);
        //Check the format before creating the arrays
        String.format(txt, 0, 0, 0);
        txt = txt.replace("\"", "");

        final int[] int1 = getInterval(i1, s1, f1);
        final int[] int2 = getInterval(i2, s2, f2);

        StringBuilder foo = new StringBuilder(txt.length() * (f1 - i1) / s1 * 2);

        final int n = Math.min(int1.length, int2.length);
        for (int i = 0; i < n; i++) {
           foo.append(String.format(txt, int1[i], int2[i])).append("\n");
        }

        String[] lines = foo.toString().split("\\n|;");
        lineCount+= lines.length;

        return new GraphicCreator(this).parse(lines);
    }

    public Graphic for3(String[] args) {
        final int i1 = getInt(args[1]);
        final int s1 = getInt(args[2]);
        final int f1 = getInt(args[3]);
        final int i2 = getInt(args[4]);
        final int s2 = getInt(args[5]);
        final int f2 = getInt(args[6]);

        if (s1 == 0 | s2 == 0) {
            return new Graphic();
        }

        String txt = concatenate(args, 7).replace("\"", "");
        //Check the format before creating the arrays
        String.format(txt, 0, 0, 0);

        StringBuilder foo = new StringBuilder(txt.length() * (f1 - i1) / s1 * 2);

        final int[] int1 = getInterval(i1, s1, f1);
        final int[] int2 = getInterval(i2, s2, f2);

        for (int i : int1) {
            for (int j : int2) {
                foo.append(String.format(txt, i, j)).append("\n");
            }
        }

        String[] lines = foo.toString().split("\\n|;");
        lineCount+= lines.length;

        return new GraphicCreator(this).parse(lines);
    }

    public Graphic for4(String[] args) {
        final int i1 = getInt(args[1]);
        final int s1 = getInt(args[2]);
        final int f1 = getInt(args[3]);
        final int i2 = getInt(args[4]);
        final int s2 = getInt(args[5]);
        final int f2 = getInt(args[6]);
        final int i3 = getInt(args[7]);
        final int s3 = getInt(args[8]);
        final int f3 = getInt(args[9]);

        if (s1 == 0 | s2 == 0) {
            return new Graphic();
        }

        String txt = concatenate(args, 10).replace("\"", "");
        //Check the format before creating the arrays
        String.format(txt, 0, 0, 0);

        StringBuilder foo = new StringBuilder(txt.length() * (f1 - i1) / s1 * 2);


        final int[] int1 = getInterval(i1, s1, f1);
        final int[] int2 = getInterval(i2, s2, f2);
        final int[] int3 = getInterval(i3, s3, f3);

        for (int i : int1) {
            for (int j : int2) {
                for (int k : int3) {
                    foo.append(String.format(txt, i, j, k)).append("\n");
                }
            }
        }

        String[] lines = foo.toString().split("\\n|;");
        lineCount+= lines.length;

        return new GraphicCreator(this).parse(lines);
    }

    public GClip clipadd(String[] args) {
        if (clip == null) {
            clip = new GClip();
            clip.add((GFillableE)parseOne(concatenate(args, 1)));
            return clip;
        }
        clip.add((GFillableE)parseOne(concatenate(args, 1)));
        return null;
    }

    public GClip clipsub(String[] args) {
        if (clip == null) {
            clip = new GClip();
            clip.subtract((GFillableE)parseOne(concatenate(args, 1)));
            return clip;
        }
        clip.subtract((GFillableE)parseOne(concatenate(args, 1)));
        return null;
    }

    public GClip clipoff(String[] args) {
        checkArgs(args, 0);
        clip = null;
        return new GClip();
    }

    public GTransform scale(String[] args) {
        checkArgs(args, 2);
        if (transf == null) {
            transf = new GTransform(
                getDouble(args[1]),
                getDouble(args[2])
            );
            return transf;
        }
        transf.scale(
            getDouble(args[1]),
            getDouble(args[2])
        );
        return null;
    }

    public GTransform rotate(String[] args) {
        checkArgs(args, 3);
        if (transf == null) {
            transf = new GTransform(
                getInt(args[1]),
                getInt(args[2]),
                getDouble(args[3])
            );
            return transf;
        }
        transf.rotate(
            getInt(args[1]),
            getInt(args[2]),
            getDouble(args[3])
        );
        return null;
    }

    public GTransform traslate(String[] args) {
        checkArgs(args, 2);
        if (transf == null) {
            transf = new GTransform(1, 1);
            transf.traslate(
                getInt(args[1]),
                getInt(args[2])
            );
            return transf;
        }
        transf.traslate(
            getInt(args[1]),
            getInt(args[2])
        );
        return null;
    }

    public GTransform transoff(String[] args) throws NoninvertibleTransformException {
        checkArgs(args, 0);
        GTransform foo = transf.invert();
        transf = null;
        return foo;
    }

    /**
     * Creates a new point.<br>
     * The first two arguments should be {@code x} and {@code y} coordinates of
     * the point. The third optional argument is the cross size used to
     * represent the point
     *
     * @param args point arguments
     * @return {@code point}
     */
    public GPoint point(String[] args) {
        checkArgs(args, 2, 3);
        switch (args.length - 1) {
            case 2 -> { //[x, y]
                final int x = getInt(args[1]);
                final int y = getInt(args[2]);
                return new GPoint(x, y);
            }
            case 3 -> { //[x, y, cs]
                final int x = getInt(args[1]);
                final int y = getInt(args[2]);
                final int c = getInt(args[3]);
                return new GPoint(x, y, c);
            }
        }
        return null;
    }

    /**
     * Creates a line based on cartesian coordinates.<br>
     * The arguments are {@code x1, y1, x2, y2}.
     *
     * @param args coordinates of the line
     * @return Line
     */
    public GLine linec(String[] args) {
        checkArgs(args, 4);

        final int x1 = getInt(args[1]);
        final int y1 = getInt(args[2]);
        final int x2 = getInt(args[3]);
        final int y2 = getInt(args[4]);

        return new GLine(x1, y1, x2, y2);
    }

    /**
     * Creates a line based on polar coordinates.<br>
     * The arguments are {@code x, y, l, a}. With {@code x, y} being the initial
     * point, {@code l} the length of the vector and {@code a} the angle in
     * degrees.
     *
     * @param args coordinates of the line
     * @return Line
     */
    public GLine linep(String[] args) {
        checkArgs(args, 4);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final double l = getDouble(args[3]);
        final double a = getDouble(args[4]);

        return new GLine(x, y, l, a);
    }

//* lpath  [x1, y1, x2, y2, ..., xn, yn]
    public GPath lpath(String[] args) {
        GPath path = new GPath(args.length / 2);

        for (int i = 1; i < args.length; i += 2) {
            final int x = getInt(args[i]);
            final int y = getInt(args[i + 1]);

            path.append(x, y);
        }

        return path;
    }

//* rectf  [x1, y1, x2, y2]
    public GRectangle rectf(String[] args) {
        checkArgs(args, 4);

        final int x1 = getInt(args[1]);
        final int y1 = getInt(args[2]);
        final int x2 = getInt(args[3]);
        final int y2 = getInt(args[4]);

        final int w = Math.abs(x1 - x2);
        final int h = Math.abs(y1 - y2);
        final int x = Math.min(x1, x2) + w / 2;
        final int y = Math.min(y1, y2) + h / 2;

        return new GRectangle(x, y, w, h);
    }

//* rectm  [x, y, w, h]
    public GRectangle rectc(String[] args) {
        checkArgs(args, 4);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final int w = getInt(args[3]);
        final int h = getInt(args[4]);

        return new GRectangle(x, y, w, h);
    }

//* circle  [x, y, r]
    public GCircle circle(String[] args) {
        checkArgs(args, 3);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final int r = getInt(args[3]);

        return new GCircle(x, y, r);
    }

//* oval  [x, y, w, h]
    public GOval oval(String[] args) {
        checkArgs(args, 4);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final int w = getInt(args[3]);
        final int h = getInt(args[4]);

        return new GOval(x, y, w, h);
    }

//* polyp  [x1, y1, x2, y2, ..., xn, yn]
    public GPoly polyp(String[] args) {
        final GPoly poly = new GPoly(args.length / 2);
        final int n = args.length;

        for (int i = 1; i < n; i += 2) {
            final int x = getInt(args[i]);
            final int y = getInt(args[i + 1]);

            poly.append(x, y);
        }

        return poly;
    }

//* polyn  [x, y, r, n] [x, y, r, n, a]
    public GRegPoly polyn(String[] args) {
        checkArgs(args, 4, 5);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final int r = getInt(args[3]);
        final int n = getInt(args[4]);
        int a = 0;

        if (args.length == 6) {
            a = getInt(args[5]);
        }

        return new GRegPoly(x, y, r, n, a);
    }

//* vectc  [x1, y1, x2, y2]
    public GVector vectc(String[] args) {
        checkArgs(args, 4);

        final int x1 = getInt(args[1]);
        final int y1 = getInt(args[2]);
        final int x2 = getInt(args[3]);
        final int y2 = getInt(args[4]);

        return new GVector(x1, y1, x2, y2);
    }

//* vectp  [x, y, l, a]
    public GVector vectp(String[] args) {
        checkArgs(args, 4);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final double l = getDouble(args[3]);
        final double a = getDouble(args[4]);

        return new GVector(x, y, l, a);
    }

//* arc    [x, y, r, s, o] [x, y, w, h, s, a]
    public GArc arc(String[] args) {
        checkArgs(args, 6);

        final int x  = getInt(args[1]);
        final int y  = getInt(args[2]);
        final int w  = getInt(args[3]);
        final int h  = getInt(args[4]);
        final int sa = getInt(args[5]);
        final int so = getInt(args[6]);

        return new GArc(x, y, w, h, sa, so);
    }

    public GArc arcc(String[] args) {
        checkArgs(args, 5);

        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final int r = getInt(args[3]);
        final int s = getInt(args[4]);
        final int o = getInt(args[5]);

        return new GArc(x, y, r, s, o);
    }

    public GString string(String[] args) {
        final int x = getInt(args[1]);
        final int y = getInt(args[2]);

        final GString str = new GString(x, y, concatenate(args, 3));
        if (font != null) {
            str.setFont(font);
        }

        return str;
    }

    public GString string2(String[] args) {
        final int x = getInt(args[1]);
        final int y = getInt(args[2]);
        final double a = getDouble(args[3]);

        final GString str = new GString(x, y, a,concatenate(args, 4));
        if (font != null) {
            str.setFont(font);
        }

        return str;
    }

    public GGrid grid(String[] args) {
        checkArgs(args, 4, 6);

        if (args.length - 1 == 4) {
            return new GGrid(
                getInt(args[1]),
                getInt(args[2]),
                getInt(args[3]),
                getInt(args[4])
            );
        } else {
            return new GGrid(
                getInt(args[1]),
                getInt(args[2]),
                getInt(args[3]),
                getInt(args[4]),
                getInt(args[5]),
                getInt(args[6])
            );
        }
    }

    private String concatenate(String[] args, int init) {
        final StringBuilder sb = new StringBuilder(64);

        for (int i = init; i < args.length; i++) {
            sb.append(args[i]);
            sb.append(" ");
        }

        return sb.substring(0, sb.length() - 1);
    }

    private static double getDouble(String ar) {
        return Double.parseDouble(ar);
    }

    private static int getInt(String ar) {
        return Integer.parseInt(ar);
    }

    private static int getInt(String ar, int min, int max) {
        int val = Integer.parseInt(ar);
        val = Math.min(val, max);
        val = Math.max(val, min);
        return val;
    }

    private static void checkArgs(String[] args, int ... lens) {
        final int len = args.length - 1;
        boolean status = false;

        for (int l : lens) {
            status |= len == l;
        }

        if (!status) {
            String msg = "'%s' must have one of the following argument lenghts %s";
            String lengths = Arrays.toString(lens);
            throw new InvalidArgumentException(String.format(msg, args[0], lengths));
        }
    }

    private int[] getInterval(final int i, final int s, final int f) {
        if (s == 0 | i == f) {
            return new int[0];
        }

        int ii = i;
        int ss = Math.abs(s);
        int n;

        if (ii > f) {
            n = (ii - f) / ss + 1;
            ss = -ss;
        } else {
            n = (f - ii) / ss + 1;
        }

        int[] arr = new int[n];

        for (int j = 0; j < n; j++) {
            arr[j] = ii;
            ii += ss;
        }

        return arr;
    }

}