| /*=============================================================================# |
| # Copyright (c) 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.jcommons.util; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollection; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| |
| @NonNullByDefault |
| public final class StringUtils { |
| |
| |
| private static final byte[] U_DIGITS= { |
| '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', |
| 'A', 'B', 'C', 'D', 'E', 'F', |
| }; |
| |
| |
| /** |
| * Formats the Unicode code points in the standard U+XXXX notation. |
| * |
| * @param cp the code point |
| * @return a string with the formatted code point |
| */ |
| @SuppressWarnings("deprecation") |
| public static String formatCodePoint(int cp) { |
| final int nDigits= Math.max(((Integer.SIZE + 7 - Integer.numberOfLeadingZeros(cp)) / 8) * 2, 4); |
| final byte[] latin1= new byte[2 + nDigits]; |
| latin1[0]= 'U'; |
| latin1[1]= '+'; |
| int i= latin1.length; |
| do { |
| latin1[--i]= U_DIGITS[cp & 0xF]; |
| cp >>>= 4; |
| } while (i > 2); |
| return new String(latin1, 0, 0, latin1.length); |
| } |
| |
| |
| public static int firstIndexOfNonTrim(final String s, int start, final int end) { |
| if (start < 0 || end > s.length()) { |
| throw new StringIndexOutOfBoundsException(); |
| } |
| while (start < end && s.charAt(start) <= ' ') { |
| start++; |
| } |
| return start; |
| } |
| |
| public static int firstIndexOfNonTrim(final String s) { |
| int start= 0; |
| final int end= s.length(); |
| while (start < end && s.charAt(start) <= ' ') { |
| start++; |
| } |
| return start; |
| } |
| |
| public static int lastIndexOfNonTrim(final String s, final int start, int end) { |
| if (start < 0 || end > s.length()) { |
| throw new StringIndexOutOfBoundsException(); |
| } |
| while (start < end && s.charAt(end - 1) <= ' ') { |
| end--; |
| } |
| return end; |
| } |
| |
| public static int lastIndexOfNonTrim(final String s) { |
| int end= s.length(); |
| while (0 < end && s.charAt(end - 1) <= ' ') { |
| end--; |
| } |
| return end; |
| } |
| |
| public static String trim(final String s, int start, int end) { |
| if (start < 0 || end > s.length()) { |
| throw new StringIndexOutOfBoundsException(); |
| } |
| if (start == 0 && end == s.length()) { |
| return s.trim(); |
| } |
| while (start < end && s.charAt(start) <= ' ') { |
| start++; |
| } |
| while (start < end && s.charAt(end - 1) <= ' ') { |
| end--; |
| } |
| return s.substring(start, end); |
| } |
| |
| public static boolean isTrimEmpty(final String s) { |
| int start= 0; |
| final int end= s.length(); |
| while (start < end && s.charAt(start) <= ' ') { |
| start++; |
| } |
| return (start == end); |
| } |
| |
| public static boolean isTrimEmpty(final String s, int start, final int end) { |
| if (start < 0 || end > s.length()) { |
| throw new StringIndexOutOfBoundsException(); |
| } |
| while (start < end && s.charAt(start) <= ' ') { |
| start++; |
| } |
| return (start == end); |
| } |
| |
| |
| public static boolean containsAny(final String s, final ImCollection<String> searchStrings) { |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| if (s.indexOf(searchString) >= 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static boolean containsAny(final StringBuilder s, final ImCollection<String> searchStrings) { |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| if (s.indexOf(searchString) >= 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| public static int firstIndexOfAny(final String s, final ImCollection<String> searchStrings, |
| final int fromIndex) { |
| if (fromIndex < 0 || fromIndex > s.length()) { |
| throw new StringIndexOutOfBoundsException(fromIndex); |
| } |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| final int index= s.indexOf(searchString, fromIndex); |
| if (index >= 0 && index < matchIndex) { |
| matchIndex= index; |
| } |
| } |
| return (matchIndex != Integer.MAX_VALUE) ? matchIndex : -1; |
| } |
| |
| public static int firstIndexOfAny(final String s, final ImCollection<String> searchStrings) { |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| final int index= s.indexOf(searchString); |
| if (index >= 0 && index < matchIndex) { |
| matchIndex= index; |
| } |
| } |
| return (matchIndex != Integer.MAX_VALUE) ? matchIndex : -1; |
| } |
| |
| public static int firstIndexOfAny(final StringBuilder s, final ImCollection<String> searchStrings, |
| final int fromIndex) { |
| if (fromIndex < 0 || fromIndex > s.length()) { |
| throw new StringIndexOutOfBoundsException(fromIndex); |
| } |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| final int index= s.indexOf(searchString, fromIndex); |
| if (index >= 0 && index < matchIndex) { |
| matchIndex= index; |
| } |
| } |
| return (matchIndex != Integer.MAX_VALUE) ? matchIndex : -1; |
| } |
| |
| public static int firstIndexOfAny(final StringBuilder s, final ImCollection<String> searchStrings) { |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| continue; |
| } |
| prevString= searchString; |
| final int index= s.indexOf(searchString); |
| if (index >= 0 && index < matchIndex) { |
| matchIndex= index; |
| } |
| } |
| return (matchIndex != Integer.MAX_VALUE) ? matchIndex : -1; |
| } |
| |
| |
| public static final class Match { |
| |
| |
| private final String string; |
| |
| private final int startIndex; |
| |
| |
| public Match(final String string, final int startIndex) { |
| this.string= nonNullAssert(string); |
| this.startIndex= startIndex; |
| } |
| |
| |
| public String getString() { |
| return this.string; |
| } |
| |
| public int getStartIndex() { |
| return this.startIndex; |
| } |
| |
| public int getEndIndex() { |
| return this.startIndex + this.string.length(); |
| } |
| |
| public int getLength() { |
| return this.string.length(); |
| } |
| |
| } |
| |
| @SuppressWarnings("null") |
| public static @Nullable Match firstMatchOfAny(final String s, final ImCollection<String> searchStrings, |
| final int fromIndex) { |
| if (fromIndex < 0 || fromIndex > s.length()) { |
| throw new StringIndexOutOfBoundsException(fromIndex); |
| } |
| String matchString= null; |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| boolean prevMatch= false; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| if (prevMatch && s.startsWith(searchString, matchIndex)) { |
| matchString= prevString= searchString; |
| } |
| continue; |
| } |
| final int index= s.indexOf(searchString, fromIndex); |
| if (prevMatch= (index >= 0 |
| && (index < matchIndex |
| || index == matchIndex && searchString.length() > matchString.length() ))) { |
| matchString= searchString; |
| matchIndex= index; |
| } |
| prevString= searchString; |
| } |
| return (matchString != null) ? new Match(matchString, matchIndex) : null; |
| } |
| |
| @SuppressWarnings("null") |
| public static @Nullable Match firstMatchOfAny(final String s, final ImCollection<String> searchStrings) { |
| String matchString= null; |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| boolean prevMatch= false; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| if (prevMatch && s.startsWith(searchString, matchIndex)) { |
| matchString= searchString; |
| } |
| continue; |
| } |
| final int index= s.indexOf(searchString); |
| if (prevMatch= (index >= 0 |
| && (index < matchIndex |
| || index == matchIndex && searchString.length() > matchString.length() ))) { |
| matchString= searchString; |
| matchIndex= index; |
| } |
| prevString= searchString; |
| } |
| return (matchString != null) ? new Match(matchString, matchIndex) : null; |
| } |
| |
| @SuppressWarnings("null") |
| public static @Nullable Match firstMatchOfAny(final StringBuilder s, final ImCollection<String> searchStrings, |
| final int fromIndex) { |
| if (fromIndex < 0 || fromIndex > s.length()) { |
| throw new StringIndexOutOfBoundsException(fromIndex); |
| } |
| String matchString= null; |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| boolean prevMatch= false; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| if (prevMatch && matchIndex + searchString.length() <= s.length() |
| && s.substring(matchIndex, matchIndex + searchString.length()).equals(searchString) ) { |
| matchString= prevString= searchString; |
| } |
| continue; |
| } |
| final int index= s.indexOf(searchString, fromIndex); |
| if (prevMatch= (index >= 0 |
| && (index < matchIndex |
| || index == matchIndex && searchString.length() > matchString.length() ))) { |
| matchString= searchString; |
| matchIndex= index; |
| } |
| prevString= searchString; |
| } |
| return (matchString != null) ? new Match(matchString, matchIndex) : null; |
| } |
| |
| @SuppressWarnings("null") |
| public static @Nullable Match firstMatchOfAny(final StringBuilder s, final ImCollection<String> searchStrings) { |
| String matchString= null; |
| int matchIndex= Integer.MAX_VALUE; |
| String prevString= null; |
| boolean prevMatch= false; |
| for (final String searchString : searchStrings) { |
| if (prevString != null && searchString.startsWith(prevString)) { |
| if (prevMatch && matchIndex + searchString.length() <= s.length() |
| && s.substring(matchIndex, matchIndex + searchString.length()).equals(searchString) ) { |
| matchString= prevString= searchString; |
| } |
| continue; |
| } |
| final int index= s.indexOf(searchString); |
| if (prevMatch= (index >= 0 |
| && (index < matchIndex |
| || index == matchIndex && searchString.length() > matchString.length() ))) { |
| matchString= searchString; |
| matchIndex= index; |
| } |
| prevString= searchString; |
| } |
| return (matchString != null) ? new Match(matchString, matchIndex) : null; |
| } |
| |
| |
| private static final String STOP= new String("STOP"); |
| |
| public static String toSimpleSingleLine(final String text) { |
| final int length= text.length(); |
| StringBuilder escaped= null; |
| int idxWritten= 0; |
| ITERATE_CHARS : for (int idx= 0; idx < length; ) { |
| final char c= text.charAt(idx); |
| final String replacement; |
| switch (c) { |
| |
| case 0x00: |
| case 0x01: |
| case 0x02: |
| case 0x03: |
| case 0x04: |
| case 0x05: |
| case 0x06: |
| case 0x07: |
| case 0x08: |
| // case 0x09: HT |
| // case 0x0A: LF |
| case 0x0B: |
| // case 0x0C: FF |
| case 0x0E: |
| case 0x0F: |
| case 0x10: |
| case 0x11: |
| case 0x12: |
| case 0x13: |
| case 0x14: |
| case 0x15: |
| case 0x16: |
| case 0x17: |
| case 0x18: |
| case 0x1B: |
| case 0x1C: |
| case 0x1E: |
| case 0x1F: |
| case 0x7F: |
| case 0x80: |
| case 0x81: |
| case 0x82: |
| case 0x83: |
| case 0x84: |
| case 0x85: |
| case 0x86: |
| case 0x87: |
| case 0x88: |
| case 0x89: |
| case 0x8A: |
| case 0x8B: |
| case 0x8C: |
| case 0x8D: |
| case 0x8E: |
| case 0x8F: |
| case 0x90: |
| case 0x91: |
| case 0x92: |
| case 0x93: |
| case 0x94: |
| case 0x95: |
| case 0x96: |
| case 0x97: |
| case 0x98: |
| case 0x99: |
| case 0x9A: |
| case 0x9B: |
| case 0x9C: |
| case 0x9D: |
| case 0x9E: |
| case 0x9F: |
| replacement= null; |
| break; |
| |
| case '\t': // HT |
| replacement= " "; //$NON-NLS-1$ |
| break; |
| case '\n': // LF |
| replacement= (idx < length - 1) ? " \u2014 " : STOP; //$NON-NLS-1$ |
| break; |
| |
| case '\f': // FF |
| replacement= STOP; |
| break; |
| |
| default: |
| idx++; |
| continue ITERATE_CHARS; |
| } |
| |
| if (replacement == STOP) { |
| if (escaped == null) { |
| return text.substring(0, idx); |
| } |
| if (idx > idxWritten) { |
| escaped.append(text, idxWritten, idx); |
| } |
| return escaped.toString(); |
| } |
| |
| if (escaped == null) { |
| escaped= new StringBuilder(length + 16); |
| } |
| if (idx > idxWritten) { |
| escaped.append(text, idxWritten, idx); |
| } |
| if (replacement != null) { |
| escaped.append(replacement); |
| } |
| idxWritten= ++idx; |
| continue ITERATE_CHARS; |
| } |
| |
| if (escaped == null) { |
| return text; |
| } |
| if (length > idxWritten) { |
| escaped.append(text, idxWritten, length); |
| } |
| return escaped.toString(); |
| } |
| |
| |
| private StringUtils() { |
| } |
| |
| } |