4
\$\begingroup\$

Problem : Create a thread safe class Spliiter that divides strings into substrings, by recognizing a separator which can be expressed as a single character, literal string, regular expression or by using a fixed substring length. Basic Usage Example: Splitter.on(',').split("foo,,bar, quux") will return ["foo", "", "bar", " quux"].

You can also add configuration for trimming result or omit empty strings. For example: Splitter.on(',').trimResults().omitEmptyStrings().split("foo, ,bar, quux,") returns ["foo", "bar", "quux"].

The below code works but I would like to know if there are any multi-threading issue or possibility of further optimizing the code.

Thanks in advance.

Solution:

package com.corejavaspace.testpackage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

/**
 * The Class Splitter.
 */
public final class Splitter {
/** The omit empty. */
private boolean omitEmpty;

/** The trim. */
private boolean trim;

/** The limit. */
private int limit;

/** The split type. */
private SplitType splitType;

/**
 * The Interface SplitType.
 */
public interface SplitType {
    Iterable<String> iterator(Splitter splitter, CharSequence toSplit);
}

/**
 * Instantiates a new splitter.
 *
 * @param split type
 * @param omit empty
 * @param trim
 * @param limit
 */
private Splitter(SplitType splitType, boolean omitEmpty, boolean trim, int limit) {
    this.splitType = splitType;
    this.omitEmpty = omitEmpty;
    this.trim = trim;
    this.limit = limit;
}

/**
 * Adds configuration on which splitting will be performed
 *
 * @param separator
 * @return splitter
 */
public static Splitter on(char separator) {
    return new Splitter(new SplitType() {

        @Override
        public Iterable<String> iterator(Splitter splitter, CharSequence toSplit) {

            final int len = toSplit.length();

            List<String> result = new ArrayList<String>();
            int i = 0, start = 0;

            while (i < len) {
                if (toSplit.charAt(i) == separator) {
                    result.add(((String) toSplit).substring(start, i));
                    start = ++i;
                    continue;
                }

                i++;
            }

            result.add(((String) toSplit).substring(start, i));
            result = handleTrimConfig(result, splitter.trim, splitter.omitEmpty);
            return result;
        }
    }, false, false, Integer.MAX_VALUE);
}

/**
 * Adds configuration on which splitting will be performed
 *
 * @param separator
 * @return splitter
 */
public static Splitter on(final String separator) {
    return new Splitter(new SplitType() {

        @Override
        public Iterable<String> iterator(Splitter splitter, CharSequence toSplit) {

            String[] result = ((String) toSplit).split(separator);

            return handleTrimConfig(Arrays.asList(result), splitter.trim, splitter.omitEmpty);

        }
    }, false, false, Integer.MAX_VALUE);
}

/**
 * Performs trim and omit empty operations based on configuration.
 *
 * @param input list
 * @param trim flag
 * @param omit empty flag
 * @return return list
 */
private static List<String> handleTrimConfig(List<String> list, boolean trim, boolean omitEmpty) {
    List<String> copyList = new ArrayList<String>(list);
    Iterator<String> iterator = copyList.iterator();
    int index = 0;
    while (iterator.hasNext()) {
        String element = iterator.next();
        if (trim && omitEmpty) {
            element = element.trim();
            if (element.equals("")) {
                iterator.remove();
                index--;
            } else {
                copyList.set(index, element);
            }

        } else if (trim) {
            element = element.trim();
            copyList.set(index, element);
        } else if (omitEmpty) {
            if (element.equals("")) {
                iterator.remove();
            }
        }
        index++;
    }

    return copyList;
}

/**
 * Split input into fixed length parts.
 *
 * @param length the length
 * @return the splitter
 */
public static Splitter splitFixedLength(int length) {
    if (length <= 0) {
        throw new IllegalArgumentException("Input to splitFixedLenth is not valid");
    }
    return new Splitter(new SplitType() {

        @Override
        public Iterable<String> iterator(Splitter splitter, CharSequence toSplit) {

            List<String> split = new ArrayList<>();
            for (int i = 0; i <= toSplit.length() / length; i++) {
                split.add(((String) toSplit).substring(i * length, Math.min((i + 1) * length, toSplit.length())));
            }
            split = handleTrimConfig(split, splitter.trim, splitter.omitEmpty);
            return split;
        }
    }, false, false, length);
}

/**
 * Adds configuration on which splitting will be performed
 *
 * @param separator pattern
 * @return splitter
 */
public static Splitter on(Pattern separatorPattern) {
    return new Splitter(new SplitType() {

        @Override
        public Iterable<String> iterator(Splitter splitter, CharSequence toSplit) {
            String[] result = separatorPattern.split(toSplit);
            return handleTrimConfig(Arrays.asList(result), splitter.trim, splitter.omitEmpty);

        }
    }, false, false, Integer.MAX_VALUE);
}

/**
 * Whether to omit empty strings or not.
 *
 * @return splitter
 */
public Splitter omitEmptyStrings() {
    this.omitEmpty = true;
    return this;

}

/**
 * Whether to trim strings or not.
 *
 * @return splitter
 */
public Splitter trimResults() {

    this.trim = true;
    return this;
}

/**
 * Performs splitting based on set configuration.
 *
 * @param  sequence
 * @return Iterable<String>
 */
public Iterable<String> split(CharSequence sequence) {
    if (sequence == null) {
        throw new IllegalArgumentException("Input to split is not valid");
    }
    return splitType.iterator(Splitter.this, sequence);

}

}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Following are some observations that I found in this code -

1) There is no need of taking limit as instance variable in this class. You are not using it and your code will work without it.

2) Your split() method requires CharSequence as instance variable, but in code you are converting it into String e.g. -

result.add(((String) toSplit).substring(start, i));

So, it will break your code, if input is other than String like StringBuffer and StringBuilder

3) Below method will break if input separator contains any dangling character like "*" -

public static Splitter on(final String separator)

This is because in below line, split() method of String class accepts a regex string -

String[] result = ((String) toSplit).split(separator);

4) As per current logic, String separator is also using regex, so you can call Pattern separator method from this method -

public static Splitter on(final String separator) {
     return on(Pattern.compile(separator);
}

5) Regarding multi-threading, code looks fine and it would be able to work in multi-threaded environment.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.