[Util] Add additional functions to StringUtils
diff --git a/jcommons/org.eclipse.statet.jcommons.util-tests/src/org/eclipse/statet/jcommons/util/StringUtilsTest.java b/jcommons/org.eclipse.statet.jcommons.util-tests/src/org/eclipse/statet/jcommons/util/StringUtilsTest.java
index 2f2d4ab..46e91f0 100644
--- a/jcommons/org.eclipse.statet.jcommons.util-tests/src/org/eclipse/statet/jcommons/util/StringUtilsTest.java
+++ b/jcommons/org.eclipse.statet.jcommons.util-tests/src/org/eclipse/statet/jcommons/util/StringUtilsTest.java
@@ -15,6 +15,14 @@
 package org.eclipse.statet.jcommons.util;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import static org.eclipse.statet.jcommons.collections.ImCollections.emptyList;
+import static org.eclipse.statet.jcommons.collections.ImCollections.newList;
 
 import org.junit.jupiter.api.Test;
 
@@ -36,4 +44,658 @@
 		assertEquals("U+FFFFFFFF", StringUtils.formatCodePoint(0xFFFFFFFF)); // <invalid>
 	}
 	
+	
+	@Test
+	public void firstIndexOfNonTrim_StringFromToIndex() {
+		assertEquals(0, StringUtils.firstIndexOfNonTrim("", 0, 0));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim(" ", 0, 1));
+		assertEquals(2, StringUtils.firstIndexOfNonTrim(" \t", 0, 2));
+		assertEquals(4, StringUtils.firstIndexOfNonTrim(" \n\t ", 0, 4));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim("\n", 0, 1));
+		assertEquals(0, StringUtils.firstIndexOfNonTrim("abc", 0, 1));
+		assertEquals(3, StringUtils.firstIndexOfNonTrim(" \n\ta ", 0, 5));
+		
+		assertEquals(1, StringUtils.firstIndexOfNonTrim(" ", 1, 1));
+		assertEquals(2, StringUtils.firstIndexOfNonTrim(" \t", 1, 2));
+		assertEquals(4, StringUtils.firstIndexOfNonTrim(" \n\t ", 2, 4));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim("abc", 1, 2));
+		assertEquals(3, StringUtils.firstIndexOfNonTrim(" \n\ta ", 2, 5));
+		
+		assertEquals(0, StringUtils.firstIndexOfNonTrim(" ", 0, 0));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim(" \t", 0, 1));
+		assertEquals(2, StringUtils.firstIndexOfNonTrim(" \n\t ", 0, 2));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim("abc", 1, 2));
+		assertEquals(2, StringUtils.firstIndexOfNonTrim(" \na\t ", 1, 3));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfNonTrim_StringFromToIndex_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfNonTrim(null, 0, 4) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfNonTrim(null, 0, 0) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfNonTrim("abc", -1, 0) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfNonTrim("abc", 0, 4) );
+	}
+	
+	@Test
+	public void firstIndexOfNonTrim_String() {
+		assertEquals(0, StringUtils.firstIndexOfNonTrim(""));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim(" "));
+		assertEquals(2, StringUtils.firstIndexOfNonTrim(" \t"));
+		assertEquals(4, StringUtils.firstIndexOfNonTrim(" \n\t "));
+		assertEquals(1, StringUtils.firstIndexOfNonTrim("\n"));
+		assertEquals(0, StringUtils.firstIndexOfNonTrim("abc"));
+		assertEquals(3, StringUtils.firstIndexOfNonTrim(" \n\ta "));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfNonTrim_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfNonTrim(null) );
+	}
+	
+	@Test
+	public void lastIndexOfNonTrim_StringFromToIndex() {
+		assertEquals(0, StringUtils.lastIndexOfNonTrim("", 0, 0));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" ", 0, 1));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \t", 0, 2));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \n\t ", 0, 4));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim("\n", 0, 1));
+		assertEquals(1, StringUtils.lastIndexOfNonTrim("abc", 0, 1));
+		assertEquals(4, StringUtils.lastIndexOfNonTrim(" \n\ta ", 0, 5));
+		
+		assertEquals(1, StringUtils.lastIndexOfNonTrim(" ", 1, 1));
+		assertEquals(1, StringUtils.lastIndexOfNonTrim(" \t", 1, 2));
+		assertEquals(2, StringUtils.lastIndexOfNonTrim(" \n\t ", 2, 4));
+		assertEquals(2, StringUtils.lastIndexOfNonTrim("abc", 1, 2));
+		assertEquals(4, StringUtils.lastIndexOfNonTrim(" \n\ta ", 2, 5));
+		
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" ", 0, 0));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \t", 0, 1));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \n\t ", 0, 2));
+		assertEquals(2, StringUtils.lastIndexOfNonTrim("abc", 1, 2));
+		assertEquals(3, StringUtils.lastIndexOfNonTrim(" \na\t ", 1, 3));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void lastIndexOfNonTrim_StringFromToIndex_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.lastIndexOfNonTrim(null, 0, 4) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.lastIndexOfNonTrim(null, 0, 0) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.lastIndexOfNonTrim("abc", -1, 0) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.lastIndexOfNonTrim("abc", 0, 4) );
+	}
+	
+	@Test
+	public void lastIndexOfNonTrim_String() {
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(""));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" "));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \t"));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim(" \n\t "));
+		assertEquals(0, StringUtils.lastIndexOfNonTrim("\n"));
+		assertEquals(3, StringUtils.lastIndexOfNonTrim("abc"));
+		assertEquals(4, StringUtils.lastIndexOfNonTrim(" \n\ta "));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void lastIndexOfNonTrim_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.lastIndexOfNonTrim(null) );
+	}
+	
+	
+	@Test
+	public void trim_StringFromToIndex() {
+		assertEquals("", StringUtils.trim("", 0, 0));
+		assertEquals("", StringUtils.trim(" ", 0, 1));
+		assertEquals("", StringUtils.trim(" \t", 0, 2));
+		assertEquals("", StringUtils.trim(" \n\t ", 0, 4));
+		assertEquals("", StringUtils.trim("\n", 0, 1));
+		assertEquals("a", StringUtils.trim("abc", 0, 1));
+		assertEquals("a", StringUtils.trim(" \n\ta ", 0, 5));
+		
+		assertEquals("", StringUtils.trim(" ", 1, 1));
+		assertEquals("", StringUtils.trim(" \t", 1, 2));
+		assertEquals("", StringUtils.trim(" \n\t ", 2, 4));
+		assertEquals("b", StringUtils.trim("abc", 1, 2));
+		assertEquals("a", StringUtils.trim(" \n\ta ", 2, 5));
+		
+		assertEquals("", StringUtils.trim(" ", 0, 0));
+		assertEquals("", StringUtils.trim(" \t", 0, 1));
+		assertEquals("", StringUtils.trim(" \n\t ", 0, 2));
+		assertEquals("b", StringUtils.trim("abc", 1, 2));
+		assertEquals("a", StringUtils.trim(" \na\t ", 1, 3));
+		assertEquals("", StringUtils.trim(" \na\t ", 1, 2));
+	}
+	
+	@Test
+	public void isTrimEmpty_StringFromToIndex() {
+		assertTrue(StringUtils.isTrimEmpty("", 0, 0));
+		assertTrue(StringUtils.isTrimEmpty(" ", 0, 1));
+		assertTrue(StringUtils.isTrimEmpty(" \t", 0, 2));
+		assertTrue(StringUtils.isTrimEmpty(" \n\t ", 0, 4));
+		assertTrue(StringUtils.isTrimEmpty("\n", 0, 1));
+		assertFalse(StringUtils.isTrimEmpty("abc", 0, 1));
+		assertFalse(StringUtils.isTrimEmpty(" \n\ta ", 0, 5));
+		
+		assertTrue(StringUtils.isTrimEmpty(" ", 1, 1));
+		assertTrue(StringUtils.isTrimEmpty(" \t", 1, 2));
+		assertTrue(StringUtils.isTrimEmpty(" \n\t ", 2, 4));
+		assertFalse(StringUtils.isTrimEmpty("abc", 1, 2));
+		assertFalse(StringUtils.isTrimEmpty(" \n\ta ", 2, 5));
+		
+		assertTrue(StringUtils.isTrimEmpty(" ", 0, 0));
+		assertTrue(StringUtils.isTrimEmpty(" \t", 0, 1));
+		assertTrue(StringUtils.isTrimEmpty(" \n\t ", 0, 2));
+		assertFalse(StringUtils.isTrimEmpty("abc", 1, 2));
+		assertFalse(StringUtils.isTrimEmpty(" \na\t ", 1, 3));
+		assertTrue(StringUtils.isTrimEmpty(" \na\t ", 1, 2));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void isTrimEmpty_StringFromToIndex_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.isTrimEmpty(null, 0, 4) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.isTrimEmpty(null, 0, 0) );
+	}
+	
+	@Test
+	public void isTrimEmpty_String() {
+		assertTrue(StringUtils.isTrimEmpty(""));
+		assertTrue(StringUtils.isTrimEmpty(" "));
+		assertTrue(StringUtils.isTrimEmpty(" \t"));
+		assertTrue(StringUtils.isTrimEmpty(" \n\t "));
+		assertTrue(StringUtils.isTrimEmpty("\n"));
+		assertFalse(StringUtils.isTrimEmpty("a"));
+		assertFalse(StringUtils.isTrimEmpty(" \n\ta "));
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void isTrimEmpty_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.isTrimEmpty(null) );
+	}
+	
+	
+	@Test
+	public void containsAny_String() {
+		boolean match;
+		
+		match= StringUtils.containsAny("xxxxxxxxxx", emptyList());
+		assertFalse(match);
+		
+		match= StringUtils.containsAny("xxxxxxxxxx", newList("a", "ab", "abc"));
+		assertFalse(match);
+		
+		match= StringUtils.containsAny("foobarabcd", newList("a", "ab", "abc"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny("foobarabcd", newList("ab", "abc"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny("foobarabcd", newList("ab", "ab"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny("foobarabcd", newList("", "ab", "abc"));
+		assertTrue(match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void containsAny_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.containsAny((String)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.containsAny("foobarabcd", null) );
+//		assertThrows(NullPointerException.class, () ->
+//				StringUtils.containsAny("foobarabcd", newList("abc", null)) );
+	}
+	
+	
+	@Test
+	public void containsAny_StringBuilder() {
+		boolean match;
+		
+		match= StringUtils.containsAny(new StringBuilder("xxxxxxxxxx"), emptyList());
+		assertFalse(match);
+		
+		match= StringUtils.containsAny(new StringBuilder("xxxxxxxxxx"), newList("a", "ab", "abc"));
+		assertFalse(match);
+		
+		match= StringUtils.containsAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny(new StringBuilder("foobarabcd"), newList("ab", "abc"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny(new StringBuilder("foobarabcd"), newList("ab", "ab"));
+		assertTrue(match);
+		
+		match= StringUtils.containsAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"));
+		assertTrue(match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void containsAny_StringBuilder_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.containsAny((StringBuilder)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.containsAny(new StringBuilder("foobarabcd"), null) );
+//		assertThrows(NullPointerException.class, () ->
+//				StringUtils.containsAny(new StringBuilder("foobarabcd"), newList("abc", null)) );
+	}
+	
+	
+	@Test
+	public void firstIndexOfAny_StringFromIndex() {
+		int match;
+		
+		match= StringUtils.firstIndexOfAny("xxxxxxxxxx", emptyList(), 0);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("xxxxxxxxxx", newList("a", "ab", "abc"), 0);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("a", "ab", "abc"), 0);
+		assertEquals(4, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("ab", "ab"), 0);
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("", "ab", "abc"), 0);
+		assertEquals(0, match);
+		
+		match= StringUtils.firstIndexOfAny("xxxxxxxxxx", emptyList(), 5);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("xaxxabcxxx", newList("a", "ab", "abc"), 5);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("a", "ab", "abc"), 5);
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("", "ab", "abc"), 5);
+		assertEquals(5, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("a", "ab", "abc"), "foobarabcd".length());
+		assertEquals(-1, match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfAny_StringFromIndex_argCheck() {
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", newList("abc"), -5) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", newList("abc"), 20) );
+		
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny((String)null, newList("abc"), 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", null, 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", newList("abc", null), 5) );
+	}
+	
+	@Test
+	public void firstIndexOfAny_String() {
+		int match;
+		
+		match= StringUtils.firstIndexOfAny("xxxxxxxxxx", emptyList());
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("xxxxxxxxxx", newList("a", "ab", "abc"));
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("a", "ab", "abc"));
+		assertEquals(4, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("ab", "abc"));
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("ab", "ab"));
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny("foobarabcd", newList("", "ab", "abc"));
+		assertEquals(0, match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfAny_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny((String)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", null) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny("foobarabcd", newList("abc", null)) );
+	}
+	
+	
+	@Test
+	public void firstIndexOfAny_StringBuilderFromIndex() {
+		int match;
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xxxxxxxxxx"), emptyList(), 0);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xxxxxxxxxx"), newList("a", "ab", "abc"), 0);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), 0);
+		assertEquals(4, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("ab", "ab"), 0);
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"), 0);
+		assertEquals(0, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xxxxxxxxxx"), emptyList(), 5);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xaxxabcxxx"), newList("a", "ab", "abc"), 5);
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), 5);
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"), 5);
+		assertEquals(5, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), "foobarabcd".length());
+		assertEquals(-1, match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfAny_StringBuilderFromIndex_argCheck() {
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("abc"), -5) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("abc"), 20) );
+		
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny((StringBuilder)null, newList("abc"), 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), null, 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("abc", null), 5) );
+	}
+	
+	@Test
+	public void firstIndexOfAny_StringBuilder() {
+		int match;
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xxxxxxxxxx"), emptyList());
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("xxxxxxxxxx"), newList("a", "ab", "abc"));
+		assertEquals(-1, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"));
+		assertEquals(4, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("ab", "abc"));
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("ab", "ab"));
+		assertEquals(6, match);
+		
+		match= StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"));
+		assertEquals(0, match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstIndexOfAny_StringBuilder_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny((StringBuilder)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), null) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstIndexOfAny(new StringBuilder("foobarabcd"), newList("abc", null)) );
+	}
+	
+	
+	@Test
+	public void Match() {
+		StringUtils.Match match;
+		
+		match= new StringUtils.Match("abc", 2);
+		assertEquals("abc", match.getString());
+		assertEquals(2, match.getStartIndex());
+		assertEquals(2 + 3, match.getEndIndex());
+		assertEquals(3, match.getLength());
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void Match_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+			new StringUtils.Match(null, 2) );
+	}
+	
+	
+	@Test
+	public void firstMatchOfAny_StringFromIndex() {
+		StringUtils.Match match;
+		
+		match= StringUtils.firstMatchOfAny("xxxxxxxxxx", emptyList(), 0);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("xxxxxxxxxx", newList("a", "ab", "abc"), 0);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("a", "ab", "abc"), 0);
+		assertNotNull(match);
+		assertEquals("a", match.getString());
+		assertEquals(4, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("ab", "ab"), 0);
+		assertNotNull(match);
+		assertEquals("ab", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("", "ab", "abc"), 0);
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(0, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("xxxxxxxxxx", emptyList(), 5);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("xaxxabcxxx", newList("a", "ab", "abc"), 5);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("a", "ab", "abc"), 5);
+		assertNotNull(match);
+		assertEquals("abc", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("", "ab", "abc"), 5);
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(5, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("a", "ab", "abc"), "foobarabcd".length());
+		assertNull(match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstMatchOfAny_StringFromIndex_argCheck() {
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", newList("abc"), -5) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", newList("abc"), 20) );
+		
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny((String)null, newList("abc"), 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", null, 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", newList("abc", null), 5) );
+	}
+	
+	@Test
+	public void firstMatchOfAny_String() {
+		StringUtils.Match match;
+		
+		match= StringUtils.firstMatchOfAny("xxxxxxxxxx", emptyList());
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("xxxxxxxxxx", newList("a", "ab", "abc"));
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("a", "ab", "abc"));
+		assertNotNull(match);
+		assertEquals("a", match.getString());
+		assertEquals(4, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("ab", "abc"));
+		assertNotNull(match);
+		assertEquals("abc", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("ab", "ab"));
+		assertNotNull(match);
+		assertEquals("ab", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny("foobarabcd", newList("", "ab", "abc"));
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(0, match.getStartIndex());
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstMatchOfAny_String_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny((String)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", null) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny("foobarabcd", newList("abc", null)) );
+	}
+	
+	
+	@Test
+	public void firstMatchOfAny_StringBuilderFromIndex() {
+		StringUtils.Match match;
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xxxxxxxxxx"), emptyList(), 0);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xxxxxxxxxx"), newList("a", "ab", "abc"), 0);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), 0);
+		assertNotNull(match);
+		assertEquals("a", match.getString());
+		assertEquals(4, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("ab", "ab"), 0);
+		assertNotNull(match);
+		assertEquals("ab", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"), 0);
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(0, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xxxxxxxxxx"), emptyList(), 5);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xaxxabcxxx"), newList("a", "ab", "abc"), 5);
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), 5);
+		assertNotNull(match);
+		assertEquals("abc", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"), 5);
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(5, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"), "foobarabcd".length());
+		assertNull(match);
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstMatchOfAny_StringBuilderFromIndex_argCheck() {
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("abc"), -5) );
+		assertThrows(StringIndexOutOfBoundsException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("abc"), 20) );
+		
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny((StringBuilder)null, newList("abc"), 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), null, 5) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("abc", null), 5) );
+	}
+	
+	@Test
+	public void firstMatchOfAny_StringBuilder() {
+		StringUtils.Match match;
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xxxxxxxxxx"), emptyList());
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("xxxxxxxxxx"), newList("a", "ab", "abc"));
+		assertNull(match);
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("a", "ab", "abc"));
+		assertNotNull(match);
+		assertEquals("a", match.getString());
+		assertEquals(4, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("ab", "abc"));
+		assertNotNull(match);
+		assertEquals("abc", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("ab", "ab"));
+		assertNotNull(match);
+		assertEquals("ab", match.getString());
+		assertEquals(6, match.getStartIndex());
+		
+		match= StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("", "ab", "abc"));
+		assertNotNull(match);
+		assertEquals("", match.getString());
+		assertEquals(0, match.getStartIndex());
+	}
+	
+	@Test
+	@SuppressWarnings("null")
+	public void firstMatchOfAny_StringBuilder_argCheck() {
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny((StringBuilder)null, newList("abc")) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), null) );
+		assertThrows(NullPointerException.class, () ->
+				StringUtils.firstMatchOfAny(new StringBuilder("foobarabcd"), newList("abc", null)) );
+	}
+	
 }
diff --git a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/util/StringUtils.java b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/util/StringUtils.java
index 7986a9f..f0abe50 100644
--- a/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/util/StringUtils.java
+++ b/jcommons/org.eclipse.statet.jcommons.util/src/org/eclipse/statet/jcommons/util/StringUtils.java
@@ -14,11 +14,15 @@
 
 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 class StringUtils {
+public final class StringUtils {
 	
 	
 	private static final byte[] U_DIGITS= {
@@ -48,6 +52,324 @@
 	}
 	
 	
+	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 StringUtils() {
 	}