org.eclipse.fx.core.text.TextUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.fx.core.text.TextUtil.java

Source

/*******************************************************************************
 * Copyright (c) 2016 BestSolution.at and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
 *******************************************************************************/
package org.eclipse.fx.core.text;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.BreakIterator;
import java.text.CharacterIterator;
import java.text.MessageFormat;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.lang.text.StrSubstitutor;
import org.eclipse.fx.core.IntTuple;
import org.eclipse.fx.core.Triple;
import org.eclipse.fx.core.function.BiIntPredicate;
import org.eclipse.fx.core.function.BiIntUnaryOperator;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

/**
 * Utility methods to deal with texts
 *
 * @since 2.4.0
 */
public class TextUtil {
    // TODO This would work with ICU
    // private static final BreakIterator POSIX_ITERATOR =
    // BreakIterator.getWordInstance(new Locale("en", "US", "POSIX"));
    // //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$

    /**
     * A {@link CharSequence} who can provide an {@link CharacterIterator}
     */
    public interface IterableCharSequence extends CharSequence {
        /**
         * @return the iterator to use
         */
        public default CharacterIterator getIterator() {
            return new StringCharacterIterator(subSequence(0, length()).toString());
        }
    }

    /**
     * Find the end offset of the word
     *
     * @param content
     *            the content
     * @param offset
     *            the offset to start the search from
     * @param pointAsBoundary
     *            should the '.' treated as word boundary
     * @return the end offset or {@link BreakIterator#DONE}
     */
    public static int findWordEndOffset(IterableCharSequence content, int offset, boolean pointAsBoundary) {
        BreakIterator wordInstance = BreakIterator.getWordInstance();
        wordInstance.setText(content.getIterator());
        int rv = wordInstance.following(offset);
        if (rv != BreakIterator.DONE && pointAsBoundary) {
            String s = content.subSequence(offset, rv).toString();
            int idx = s.indexOf('.');
            if (idx >= 0) {
                rv = offset + idx;
            }

            if (rv == offset) {
                rv = offset + 1;
            }
        }
        return rv;
    }

    /**
     * Find the start offset of the word
     *
     * @param content
     *            the content
     * @param offset
     *            the offset to start the search from
     * @param pointAsBoundary
     *            should the '.' treated as word boundary
     * @return the start offset or or {@link BreakIterator#DONE}
     */
    public static int findWordStartOffset(IterableCharSequence content, int offset, boolean pointAsBoundary) {
        BreakIterator wordInstance = BreakIterator.getWordInstance();
        wordInstance.setText(content.getIterator());
        int rv = wordInstance.preceding(offset);

        if (rv != BreakIterator.DONE && pointAsBoundary) {
            String s = content.subSequence(rv, offset).toString();
            int idx = s.lastIndexOf('.');
            if (idx > 0) {
                rv += idx + 1;
            }

            // move before the point
            if (rv == offset) {
                rv -= 1;
            }
        }

        return rv;
    }

    /**
     * Find the bounds of the word
     *
     * @param content
     *            the content
     * @param offset
     *            the offset
     * @param pointAsBoundary
     *            should the '.' treated as word boundary
     * @return a tuple of value representing start and end
     */
    public static IntTuple findWordBounds(IterableCharSequence content, int offset, boolean pointAsBoundary) {
        BreakIterator wordInstance = BreakIterator.getWordInstance();
        wordInstance.setText(content.getIterator());
        int previous = wordInstance.preceding(offset);
        int next = wordInstance.following(offset);

        if (pointAsBoundary && previous != BreakIterator.DONE && next != BreakIterator.DONE) {
            String preMatch = content.subSequence(previous, offset).toString();
            String postMatch = content.subSequence(offset, next).toString();

            int idx = preMatch.lastIndexOf('.');
            if (idx > 0) {
                previous += idx + 1;
            }

            idx = postMatch.indexOf('.');
            if (idx > 0) {
                next = offset + idx;
            }
        }

        return new IntTuple(previous, next);
    }

    /**
     * Substitute template values (including child-properties) and format them
     *
     * The following examples are possible:
     * <p>
     *
     * <pre>
     * The name is ${person.firstname}.
     * The birthdate is ${person.birthdate,date,dd.MM.yyyy}.
     * </pre>
     * </p>
     *
     * @param template
     *            the template
     * @param data
     *            the data
     * @return the final string
     * @since 2.4.0
     */
    public static String templateValuSubstitutor(String template, Map<String, Object> data) {
        return new StrSubstitutor(new StrLookupImpl(data)).replace(template);
    }

    /**
     * Make the first character in the string upper case
     *
     * @param value
     *            the value
     * @return the value with the first character as uppercase
     * @since 2.4.0
     */
    public static String toFirstUpper(String value) {
        char[] cs = value.toCharArray();
        cs[0] = Character.toUpperCase(cs[0]);
        return String.valueOf(cs);
    }

    /**
     * Apply the consumer for each matched char
     *
     * @param content
     *            the content
     * @param c
     *            the character to find
     * @param consumer
     *            the consumer who gets passed the position of the matched char
     * @since 2.4.0
     */
    public static void foreachCharPosition(String content, char c, IntConsumer consumer) {
        char[] cs = content.toCharArray();
        for (int i = 0; i < cs.length; i++) {
            if (cs[i] == c) {
                consumer.accept(i);
            }
        }
    }

    /**
     * Apply the consumer for each matched char
     *
     * @param content
     *            the content
     * @param c
     *            the character to find
     * @param consumer
     *            the function who gets passed the position of the matched char
     * @return stream with objects produced by the function
     * @since 2.4.0
     */
    public static <R> Stream<R> foreachCharPosition(String content, char c, IntFunction<R> consumer) {
        // TODO We should not precreate the list
        List<R> list = new ArrayList<>();
        char[] cs = content.toCharArray();
        for (int i = 0; i < cs.length; i++) {
            if (cs[i] == c) {
                list.add(consumer.apply(i));
            }
        }
        return list.stream();
    }

    /**
     * Strip characters matched by the filter
     *
     * @param content
     *            the content
     * @param filter
     *            the filter
     * @return string without the filtered characters
     * @since 2.4.0
     */
    public static String stripOff(String content, IntPredicate filter) {
        char[] cs = content.toCharArray();
        char[] target = new char[cs.length];
        int j = 0;
        for (int i = 0; i < cs.length; i++) {
            if (!filter.test(cs[i])) {
                target[j++] = cs[i];
            }
        }

        if (j < cs.length) {
            return new String(target, 0, j);
        }

        return content;
    }

    /**
     * Make use the value is not null
     *
     * @param value
     *            the nullable value
     * @param defaultValue
     *            the default if the value is null
     * @return a nonnull string
     * @since 2.0
     */
    public static @NonNull String notNull(@Nullable String value, @NonNull String defaultValue) {
        return value == null ? defaultValue : value;
    }

    /**
     * Create a string of the same char
     *
     * @param c
     *            the character
     * @param length
     *            the length
     * @return the created string
     * @since 2.4.0
     */
    public static String createRepeatedString(char c, int length) {
        char[] vals = new char[length];
        Arrays.fill(vals, c);
        return String.valueOf(vals);
    }

    static class StrLookupImpl extends StrLookup {
        private final Map<String, Object> data;

        public StrLookupImpl(Map<String, Object> data) {
            this.data = data;
        }

        @Override
        public String lookup(String key) {
            String[] pathAndFormat = key.split(","); //$NON-NLS-1$
            String[] path = pathAndFormat[0].split("\\."); //$NON-NLS-1$

            Object object = this.data.get(path[0]);
            if (object != null && path.length > 1) {
                int i = 1;
                while (object != null && i < path.length) {
                    Method m = null;
                    try {
                        m = object.getClass().getDeclaredMethod("get" + toFirstUpper(path[i])); //$NON-NLS-1$
                    } catch (NoSuchMethodException | SecurityException e) {
                        try {
                            m = object.getClass().getDeclaredMethod("is" + toFirstUpper(path[i])); //$NON-NLS-1$
                        } catch (NoSuchMethodException | SecurityException e1) {
                            // nothing todo
                        }
                    }
                    if (m == null) {
                        throw new IllegalStateException(
                                "Unable to locate accessor property for property '" + path[i] //$NON-NLS-1$
                                        + "' on object " + object + "."); //$NON-NLS-1$//$NON-NLS-2$
                    }

                    m.setAccessible(true);
                    try {
                        object = m.invoke(object);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                        throw new IllegalStateException(e);
                    }
                    i++;
                }
            }

            if (pathAndFormat.length > 1) {
                String msg = "{0," + Stream.of(pathAndFormat).skip(1).collect(Collectors.joining(",")) + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                return MessageFormat.format(msg, object);
            }

            return object == null ? null : object.toString();
        }

    }

    /**
     * Replace tabs through spaces return
     * <ul>
     * <li>{@link Triple#value1}: the new char array</li>
     * <li>{@link Triple#value2}: the indices the tabs have been found</li>
     * <li>{@link Triple#value3}: the new indices where a "tab" is starts
     * now</li>
     * </ul>
     *
     * @param source
     *            the source array
     * @param tabAdvance
     * @return a triple with the values
     */
    public static Triple<char[], int[], int[]> replaceTabBySpace(char[] source, int tabAdvance) {
        if (tabAdvance <= 0) {
            throw new IllegalArgumentException("tabAdvance must be greater 0"); //$NON-NLS-1$
        }
        int tabCount = 0;
        for (int i = 0; i < source.length; i++) {
            if (source[i] == '\t') {
                tabCount++;
            }
        }

        if (tabCount == 0) {
            char[] rv = new char[source.length];
            System.arraycopy(source, 0, rv, 0, rv.length);
            return new Triple<>(rv, new int[0], new int[0]);
        }

        int[] tabPositions = new int[tabCount];
        int[] newTabPositions = new int[tabCount];
        char[] rv = new char[source.length + (tabCount * (tabAdvance - 1))];

        int count = 0;
        int tabIdx = 0;
        for (int i = 0; i < source.length; i++) {
            if (source[i] == '\t') {
                tabPositions[tabIdx] = i;
                newTabPositions[tabIdx] = count;
                tabIdx++;
                for (int j = 0; j < tabAdvance; j++) {
                    rv[count++] = ' ';
                }
            } else {
                rv[count++] = source[i];
            }
        }

        return new Triple<>(rv, tabPositions, newTabPositions);
    }

    /**
     * Replaces all occurences of the provided character in the source
     *
     * @param source
     *            the source
     * @param c
     *            the character to replace
     * @param newChar
     *            the replacement
     * @return a new array with all character replaced
     */
    public static char[] replaceAll(char[] source, char c, char[] newChar) {
        StringBuilder b = new StringBuilder(source.length);

        for (int i = 0; i < source.length; i++) {
            if (source[i] == c) {
                b.append(newChar);
            } else {
                b.append(source[i]);
            }
        }

        char[] rv = new char[b.length()];
        b.getChars(0, b.length(), rv, 0);
        return rv;
    }

    /**
     * Transform the provided source array by applying the provided function
     *
     * @param source
     *            the source
     * @param transformer
     *            the transformation function, first argument is the character
     *            index, second argument is the character
     * @return transformed character array
     */
    public static char[] transform(char[] source, BiIntUnaryOperator transformer) {
        char[] rv = new char[source.length];
        for (int i = 0; i < source.length; i++) {
            rv[i] = (char) transformer.applyAsInt(i, source[i]);
        }
        return rv;
    }

    /**
     * Replace entries matching the provided predicate with the provided char
     *
     * @param source
     *            the source array
     * @param c
     *            the replacement character
     * @param predicate
     *            the predicate to decided if a character is replaced, first
     *            argument is the character index, second argument is the
     *            character
     * @return transformed character array
     */
    public static char[] replace(char[] source, char c, BiIntPredicate predicate) {
        char[] rv = new char[source.length];
        for (int i = 0; i < source.length; i++) {
            rv[i] = predicate.test(i, source[i]) ? c : source[i];
        }
        return rv;
    }

    private static String[] BASIC_STRING_CACHE = new String[256];

    /**
     * Get the matching string for the char. The string object returned might be
     * the same instance.
     *
     * @param c
     *            the character
     * @return a (cached) string object
     */
    public static String toString(char c) {
        String rv = null;
        if (c < BASIC_STRING_CACHE.length) {
            rv = BASIC_STRING_CACHE[c];
            if (rv == null) {
                BASIC_STRING_CACHE[c] = String.valueOf(c);
            }
        }
        if (rv == null) {
            rv = String.valueOf(c);
        }
        return rv;
    }
}