Java tutorial
/******************************************************************************* * 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; } }