From 26b95f32636e1786d1d1e201e4eb8bfa52265e15 Mon Sep 17 00:00:00 2001
From: BoykoAlex
Date: Fri, 18 Nov 2016 20:01:26 -0500
Subject: [PATCH] Javadoc from Javadoc jars
---
.../commons/commons-java/pom.xml | 6 +
.../ide/vscode/commons/jandex/Wrappers.java | 27 +-
.../ide/vscode/commons/java/IMethod.java | 2 +
.../ide/vscode/commons/java/IType.java | 1 +
.../javadoc/DefaultHtmlJavadocIndex.java | 45 +
.../vscode/commons/javadoc/HtmlJavadoc.java | 35 +
.../commons/javadoc/HtmlJavadocIndex.java | 14 +
.../commons/javadoc/HtmlJavadocProvider.java | 66 +
.../SourceUrlProviderFromSourceContainer.java | 24 +
.../javadoc/internal/CharOperation.java | 4190 +++++++++++++++++
.../internal/HashtableOfObjectToIntArray.java | 158 +
.../javadoc/internal/JavadocConstants.java | 35 +
.../javadoc/internal/JavadocContents.java | 539 +++
.../javadoc/internal/ScannerHelper.java | 569 +++
.../ide/vscode/commons/maven/MavenCore.java | 44 +-
.../maven/java/MavenProjectClasspath.java | 56 +-
.../vscode/commons/maven/JavaIndexTest.java | 154 +-
.../pom.xml | 10 +
.../src/main/java/hello/Greeting.java | 4 +-
19 files changed, 5948 insertions(+), 31 deletions(-)
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java
create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java
diff --git a/vscode-extensions/commons/commons-java/pom.xml b/vscode-extensions/commons/commons-java/pom.xml
index ca5b0540f..558fca823 100644
--- a/vscode-extensions/commons/commons-java/pom.xml
+++ b/vscode-extensions/commons/commons-java/pom.xml
@@ -29,6 +29,12 @@
jandex
2.0.3.Final
+
+
+ com.kotcrab.remark
+ remark
+ 1.0.0
+
com.github.javaparser
javaparser-core
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java
index e7128ed0e..7b78b36a0 100644
--- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java
@@ -11,7 +11,6 @@ import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
-import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type;
@@ -30,6 +29,8 @@ import org.springframework.ide.vscode.commons.javadoc.IJavadoc;
public class Wrappers {
+ private static final String JANDEX_CONTRUCTOR_NAME = "";
+
public static IType wrap(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) {
if (info == null) {
return null;
@@ -80,9 +81,14 @@ public class Wrappers {
@Override
public boolean isInterface() {
- return false;
+ return Flags.isInterface(info.flags());
}
+ @Override
+ public boolean isAnnotation() {
+ return Flags.isAnnotation(info.flags());
+ }
+
@Override
public String getFullyQualifiedName() {
return info.name().toString();
@@ -117,7 +123,7 @@ public class Wrappers {
public String toString() {
return info.toString();
}
-
+
};
}
@@ -180,6 +186,11 @@ public class Wrappers {
public int getFlags() {
return method.flags();
}
+
+ @Override
+ public boolean isConstructor() {
+ return method.name().equals(JANDEX_CONTRUCTOR_NAME);
+ }
@Override
public IType getDeclaringType() {
@@ -188,7 +199,7 @@ public class Wrappers {
@Override
public String getElementName() {
- return method.name();
+ return isConstructor() ? getDeclaringType().getElementName() : method.name();
}
@Override
@@ -230,6 +241,7 @@ public class Wrappers {
public Stream parameters() {
return method.parameters().stream().map(Wrappers::wrap);
}
+
};
}
@@ -289,13 +301,6 @@ public class Wrappers {
};
}
- public static Type createParameterTypeFromSignature(IndexView index, String signature) {
- if (signature == null) {
- return null;
- }
- throw new UnsupportedOperationException("Not yet implemented");
- }
-
public static IPrimitiveType wrap(PrimitiveType type) {
switch (type.primitive()) {
case SHORT:
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java
index eed8b3953..06440c551 100644
--- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java
@@ -51,5 +51,7 @@ public interface IMethod extends IMember {
* @return
*/
Stream parameters();
+
+ boolean isConstructor();
}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
index dae58510e..d5e1e22cf 100644
--- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
@@ -23,6 +23,7 @@ public interface IType extends IMember {
boolean isClass();
boolean isEnum();
boolean isInterface();
+ boolean isAnnotation();
/**
* Returns the fully qualified name of this type,
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java
new file mode 100644
index 000000000..9bf2d56e5
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java
@@ -0,0 +1,45 @@
+package org.springframework.ide.vscode.commons.javadoc;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+public class DefaultHtmlJavadocIndex implements HtmlJavadocIndex {
+
+ private Cache cache = CacheBuilder.newBuilder().build();
+
+ private static JavadocContents NO_HTML_CONTENT = new JavadocContents(null);
+
+ @Override
+ public JavadocContents getHtmlJavadoc(URL url) {
+ try {
+ JavadocContents content = cache.get(url, () -> {
+ InputStream stream = null;
+ try {
+ stream = url.openStream();
+ BufferedReader buffer = new BufferedReader(new InputStreamReader(stream));
+ return new JavadocContents(buffer.lines().collect(Collectors.joining("\n")));
+ } catch (IOException e) {
+ return NO_HTML_CONTENT;
+ } finally {
+ if (stream != null) {
+ stream.close();
+ }
+ }
+ });
+ return content == NO_HTML_CONTENT ? null : content;
+ } catch (ExecutionException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java
new file mode 100644
index 000000000..3e95c61b9
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java
@@ -0,0 +1,35 @@
+package org.springframework.ide.vscode.commons.javadoc;
+
+import com.overzealous.remark.Remark;
+
+public class HtmlJavadoc implements IJavadoc {
+
+ private String html;
+ private Remark remark;
+
+ public HtmlJavadoc(String html) {
+ this.html = html;
+ this.remark = new Remark();
+ }
+
+ @Override
+ public String raw() {
+ throw new UnsupportedOperationException("Not yet implemnted");
+ }
+
+ @Override
+ public String plainText() {
+ throw new UnsupportedOperationException("Not yet implemnted");
+ }
+
+ @Override
+ public String html() {
+ return html;
+ }
+
+ @Override
+ public String markdown() {
+ return remark.convertFragment(html);
+ }
+
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java
new file mode 100644
index 000000000..434d48ad5
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java
@@ -0,0 +1,14 @@
+package org.springframework.ide.vscode.commons.javadoc;
+
+import java.net.URL;
+
+import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents;
+
+
+public interface HtmlJavadocIndex {
+
+ public static final HtmlJavadocIndex DEFAULT = new DefaultHtmlJavadocIndex();
+
+ JavadocContents getHtmlJavadoc(URL url);
+
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java
new file mode 100644
index 000000000..4adb55985
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java
@@ -0,0 +1,66 @@
+package org.springframework.ide.vscode.commons.javadoc;
+
+import java.net.URL;
+
+import org.springframework.ide.vscode.commons.java.IAnnotation;
+import org.springframework.ide.vscode.commons.java.IField;
+import org.springframework.ide.vscode.commons.java.IJavadocProvider;
+import org.springframework.ide.vscode.commons.java.IMethod;
+import org.springframework.ide.vscode.commons.java.IType;
+import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents;
+import org.springframework.ide.vscode.commons.util.Log;
+
+public class HtmlJavadocProvider implements IJavadocProvider {
+
+ private SourceUrlProvider htmlUrlProvider;
+
+ public HtmlJavadocProvider(SourceUrlProvider htmlUrlProvider) {
+ this.htmlUrlProvider = htmlUrlProvider;
+ }
+
+ @Override
+ public IJavadoc getJavadoc(IType type) {
+ try {
+ JavadocContents javadocContents = findHtml(type);
+ return new HtmlJavadoc(javadocContents.getTypeDoc(type));
+ } catch (Exception e) {
+ Log.log(e);
+ return null;
+ }
+ }
+
+ @Override
+ public IJavadoc getJavadoc(IField field) {
+ try {
+ IType declaringType = field.getDeclaringType();
+ JavadocContents javadocContents = findHtml(declaringType);
+ return new HtmlJavadoc(javadocContents.getFieldDoc(field));
+ } catch (Exception e) {
+ Log.log(e);
+ return null;
+ }
+ }
+
+ @Override
+ public IJavadoc getJavadoc(IMethod method) {
+ try {
+ IType declaringType = method.getDeclaringType();
+ JavadocContents javadocContents = findHtml(declaringType);
+ return new HtmlJavadoc(javadocContents.getMethodDoc(method));
+ } catch (Exception e) {
+ Log.log(e);
+ return null;
+ }
+ }
+
+ @Override
+ public IJavadoc getJavadoc(IAnnotation annotation) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ private JavadocContents findHtml(IType type) throws Exception {
+ URL url = htmlUrlProvider.sourceUrl(type);
+ return HtmlJavadocIndex.DEFAULT.getHtmlJavadoc(url);
+ }
+
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java
index c92f3e5ef..633a31ade 100644
--- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java
@@ -24,6 +24,30 @@ public interface SourceUrlProviderFromSourceContainer {
return Paths.get(sourceContainerUrl.toURI()).resolve(type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toUri().toURL();
};
+ public static final SourceUrlProviderFromSourceContainer JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, type) -> {
+ StringBuilder sourceUrlStr = new StringBuilder();
+ sourceUrlStr.append("jar:");
+ sourceUrlStr.append(javadocContainerUrl);
+ sourceUrlStr.append("!");
+ sourceUrlStr.append('/');
+ // Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files
+ sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", "."));
+ sourceUrlStr.append(".html");
+ return new URL(sourceUrlStr.toString());
+
+ };
+
+ public static final SourceUrlProviderFromSourceContainer JAVADOC_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> {
+ String urlStr = sourceContainerUrl.toString();
+ StringBuilder sb = new StringBuilder(urlStr);
+ if (!urlStr.endsWith("/")) {
+ sb.append('/');
+ }
+ // Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files
+ sb.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", ".") + ".html");
+ return new URL(sb.toString());
+ };
+
URL sourceUrl(URL sourceContainerUrl, IType type) throws Exception;
}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java
new file mode 100644
index 000000000..b3a554f52
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java
@@ -0,0 +1,4190 @@
+package org.springframework.ide.vscode.commons.javadoc.internal;
+
+/**
+ * This class is a collection of helper methods to manipulate char arrays.
+ *
+ * @since 2.1
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ */
+public final class CharOperation {
+
+ /**
+ * Constant for an empty char array
+ */
+ public static final char[] NO_CHAR = new char[0];
+
+ /**
+ * Constant for an empty char array with two dimensions.
+ */
+ public static final char[][] NO_CHAR_CHAR = new char[0][];
+
+ /**
+ * Constant for an empty String array.
+ * @since 3.1
+ */
+ public static final String[] NO_STRINGS = new String[0];
+
+/**
+ * Answers a new array with appending the suffix character at the end of the array.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a', 'b' }
+ * suffix = 'c'
+ * => result = { 'a', 'b' , 'c' }
+ *
+ *
+ *
+ * array = null
+ * suffix = 'c'
+ * => result = { 'c' }
+ *
+ *
+ *
+ * @param array the array that is concatenated with the suffix character
+ * @param suffix the suffix character
+ * @return the new array
+ */
+public static final char[] append(char[] array, char suffix) {
+ if (array == null)
+ return new char[] { suffix };
+ int length = array.length;
+ System.arraycopy(array, 0, array = new char[length + 1], 0, length);
+ array[length] = suffix;
+ return array;
+}
+
+/**
+ * Answers a new array with appending the sub-array at the end of the array.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a', 'b' }
+ * suffix = { 'c', 'd' }
+ * => result = { 'a', 'b' , 'c' , d' }
+ *
+ *
+ *
+ * array = null
+ * suffix = { 'c' }
+ * => result = { 'c' }
+ *
+ *
+ *
+ * @param target the array that is concatenated with the suffix array.
+ * @param suffix the array that will be concatenated to the target
+ * @return the new array
+ * @throws NullPointerException if the target array is null
+ * @since 3.11
+ */
+public static final char[] append(char[] target, char[] suffix) {
+ if(suffix == null || suffix.length == 0)
+ return target;
+ int targetLength = target.length;
+ int subLength = suffix.length;
+ int newTargetLength = targetLength + subLength;
+ if (newTargetLength > targetLength) {
+ System.arraycopy(target, 0, target = new char[newTargetLength], 0, targetLength);
+ }
+ System.arraycopy(suffix, 0, target, targetLength, subLength);
+ return target;
+}
+
+/**
+ * Append the given sub-array to the target array starting at the given index in the target array.
+ * The start of the sub-array is inclusive, the end is exclusive.
+ * Answers a new target array if it needs to grow, otherwise answers the same target array.
+ *
+ * For example:
+ *
+ *
+ * target = { 'a', 'b', '0' }
+ * index = 2
+ * array = { 'c', 'd' }
+ * start = 0
+ * end = 1
+ * => result = { 'a', 'b' , 'c' }
+ *
+ *
+ *
+ * target = { 'a', 'b' }
+ * index = 2
+ * array = { 'c', 'd' }
+ * start = 0
+ * end = 1
+ * => result = { 'a', 'b' , 'c', '0', '0' , '0' } (new array)
+ *
+ *
+ * target = { 'a', 'b', 'c' }
+ * index = 1
+ * array = { 'c', 'd', 'e', 'f' }
+ * start = 1
+ * end = 4
+ * => result = { 'a', 'd' , 'e', 'f', '0', '0', '0', '0' } (new array)
+ *
+ *
+ *
+ * @param target the given target
+ * @param index the given index
+ * @param array the given array
+ * @param start the given start index
+ * @param end the given end index
+ *
+ * @return the new array
+ * @throws NullPointerException if the target array is null
+ */
+public static final char[] append(char[] target, int index, char[] array, int start, int end) {
+ int targetLength = target.length;
+ int subLength = end-start;
+ int newTargetLength = subLength+index;
+ if (newTargetLength > targetLength) {
+ System.arraycopy(target, 0, target = new char[newTargetLength*2], 0, index);
+ }
+ System.arraycopy(array, start, target, index, subLength);
+ return target;
+}
+
+/**
+ * Answers the concatenation of the two arrays. It answers null if the two arrays are null.
+ * If the first array is null, then the second array is returned.
+ * If the second array is null, then the first array is returned.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * => result = null
+ *
+ *
+ *
+ * first = { { ' a' } }
+ * second = null
+ * => result = { { ' a' } }
+ *
+ *
+ *
+ * first = null
+ * second = { { ' a' } }
+ * => result = { { ' a' } }
+ *
+ *
+ *
+ * first = { { ' b' } }
+ * second = { { ' a' } }
+ * => result = { { ' b' }, { ' a' } }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the second array to concatenate
+ * @return the concatenation of the two arrays, or null if the two arrays are null.
+ */
+public static final char[][] arrayConcat(char[][] first, char[][] second) {
+ if (first == null)
+ return second;
+ if (second == null)
+ return first;
+
+ int length1 = first.length;
+ int length2 = second.length;
+ char[][] result = new char[length1 + length2][];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ return result;
+}
+
+/**
+ * Answers true if the pattern matches the given name using CamelCase rules, or
+ * false otherwise. char[] CamelCase matching does NOT accept explicit wild-cards
+ * '*' and '?' and is inherently case sensitive.
+ *
+ * CamelCase denotes the convention of writing compound names without spaces,
+ * and capitalizing every term. This function recognizes both upper and lower
+ * CamelCase, depending whether the leading character is capitalized or not.
+ * The leading part of an upper CamelCase pattern is assumed to contain a
+ * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will
+ * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern
+ * uses a lowercase first character. In Java, type names follow the upper
+ * CamelCase convention, whereas method or field names follow the lower
+ * CamelCase convention.
+ *
+ * The pattern may contain lowercase characters, which will be matched in a case
+ * sensitive way. These characters must appear in sequence in the name.
+ * For instance, 'NPExcep' will match 'NullPointerException', but not
+ * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not
+ * 'NoPointerException'.
+ *
+ * Digit characters are treated in a special way. They can be used in the pattern
+ * but are not always considered as leading character. For instance, both
+ * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'.
+ *
+ * Using this method allows matching names to have more parts than the specified
+ * pattern (see {@link #camelCaseMatch(char[], char[], boolean)}).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap',
+ * 'HatMapper' and also 'HashMapEntry'.
+ *
+ *
+ * Examples:
+ * - pattern = "NPE".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => true
+ * - pattern = "NPE".toCharArray()
+ * name = "NoPermissionException".toCharArray()
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * name = "NoPermissionException".toCharArray()
+ * result => false
+ * - pattern = "npe".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => false
+ * - pattern = "IPL3".toCharArray()
+ * name = "IPerspectiveListener3".toCharArray()
+ * result => true
+ * - pattern = "HM".toCharArray()
+ * name = "HashMapEntry".toCharArray()
+ * result => true
+ *
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @return true if the pattern matches the given name, false otherwise
+ * @since 3.2
+ */
+public static final boolean camelCaseMatch(char[] pattern, char[] name) {
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+ if (name == null)
+ return false; // null name cannot match
+
+ return camelCaseMatch(pattern, 0, pattern.length, name, 0, name.length, false/*not the same count of parts*/);
+}
+
+/**
+ * Answers true if the pattern matches the given name using CamelCase rules, or
+ * false otherwise. char[] CamelCase matching does NOT accept explicit wild-cards
+ * '*' and '?' and is inherently case sensitive.
+ *
+ * CamelCase denotes the convention of writing compound names without spaces,
+ * and capitalizing every term. This function recognizes both upper and lower
+ * CamelCase, depending whether the leading character is capitalized or not.
+ * The leading part of an upper CamelCase pattern is assumed to contain a
+ * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will
+ * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern
+ * uses a lowercase first character. In Java, type names follow the upper
+ * CamelCase convention, whereas method or field names follow the lower
+ * CamelCase convention.
+ *
+ * The pattern may contain lowercase characters, which will be matched in a case
+ * sensitive way. These characters must appear in sequence in the name.
+ * For instance, 'NPExcep' will match 'NullPointerException', but not
+ * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not
+ * 'NoPointerException'.
+ *
+ * Digit characters are treated in a special way. They can be used in the pattern
+ * but are not always considered as leading character. For instance, both
+ * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'.
+ *
+ * CamelCase can be restricted to match only the same count of parts. When this
+ * restriction is specified the given pattern and the given name must have exactly
+ * the same number of parts (i.e. the same number of uppercase characters).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap' and
+ * 'HatMapper' but not 'HashMapEntry'.
+ *
+ *
+ * Examples:
+ * - pattern = "NPE".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => true
+ * - pattern = "NPE".toCharArray()
+ * name = "NoPermissionException".toCharArray()
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * name = "NoPermissionException".toCharArray()
+ * result => false
+ * - pattern = "npe".toCharArray()
+ * name = "NullPointerException".toCharArray()
+ * result => false
+ * - pattern = "IPL3".toCharArray()
+ * name = "IPerspectiveListener3".toCharArray()
+ * result => true
+ * - pattern = "HM".toCharArray()
+ * name = "HashMapEntry".toCharArray()
+ * result => (samePartCount == false)
+ *
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @param samePartCount flag telling whether the pattern and the name should
+ * have the same count of parts or not.
+ * For example:
+ *
+ * - 'HM' type string pattern will match 'HashMap' and 'HtmlMapper' types,
+ * but not 'HashMapEntry'
+ * - 'HMap' type string pattern will still match previous 'HashMap' and
+ * 'HtmlMapper' types, but not 'HighMagnitude'
+ *
+ * @return true if the pattern matches the given name, false otherwise
+ * @since 3.4
+ */
+public static final boolean camelCaseMatch(char[] pattern, char[] name, boolean samePartCount) {
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+ if (name == null)
+ return false; // null name cannot match
+
+ return camelCaseMatch(pattern, 0, pattern.length, name, 0, name.length, samePartCount);
+}
+
+/**
+ * Answers true if a sub-pattern matches the sub-part of the given name using
+ * CamelCase rules, or false otherwise. char[] CamelCase matching does NOT
+ * accept explicit wild-cards '*' and '?' and is inherently case sensitive.
+ * Can match only subset of name/pattern, considering end positions as non-inclusive.
+ * The sub-pattern is defined by the patternStart and patternEnd positions.
+ *
+ * CamelCase denotes the convention of writing compound names without spaces,
+ * and capitalizing every term. This function recognizes both upper and lower
+ * CamelCase, depending whether the leading character is capitalized or not.
+ * The leading part of an upper CamelCase pattern is assumed to contain a
+ * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will
+ * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern
+ * uses a lowercase first character. In Java, type names follow the upper
+ * CamelCase convention, whereas method or field names follow the lower
+ * CamelCase convention.
+ *
+ * The pattern may contain lowercase characters, which will be matched in a case
+ * sensitive way. These characters must appear in sequence in the name.
+ * For instance, 'NPExcep' will match 'NullPointerException', but not
+ * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not
+ * 'NoPointerException'.
+ *
+ * Digit characters are treated in a special way. They can be used in the pattern
+ * but are not always considered as leading character. For instance, both
+ * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'.
+ *
+ * Digit characters are treated in a special way. They can be used in the pattern
+ * but are not always considered as leading character. For instance, both
+ * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'.
+ *
+ * Using this method allows matching names to have more parts than the specified
+ * pattern (see {@link #camelCaseMatch(char[], int, int, char[], int, int, boolean)}).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap',
+ * 'HatMapper' and also 'HashMapEntry'.
+ *
+ * Examples:
+ *
+ * - pattern = "NPE".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => true
+ * - pattern = "NPE".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NoPermissionException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * patternStart = 0
+ * patternEnd = 6
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * patternStart = 0
+ * patternEnd = 6
+ * name = "NoPermissionException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => false
+ * - pattern = "npe".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => false
+ * - pattern = "IPL3".toCharArray()
+ * patternStart = 0
+ * patternEnd = 4
+ * name = "IPerspectiveListener3".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => true
+ * - pattern = "HM".toCharArray()
+ * patternStart = 0
+ * patternEnd = 2
+ * name = "HashMapEntry".toCharArray()
+ * nameStart = 0
+ * nameEnd = 12
+ * result => true
+ *
+ *
+ * @param pattern the given pattern
+ * @param patternStart the start index of the pattern, inclusive
+ * @param patternEnd the end index of the pattern, exclusive
+ * @param name the given name
+ * @param nameStart the start index of the name, inclusive
+ * @param nameEnd the end index of the name, exclusive
+ * @return true if a sub-pattern matches the sub-part of the given name, false otherwise
+ * @since 3.2
+ */
+public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd) {
+ return camelCaseMatch(pattern, patternStart, patternEnd, name, nameStart, nameEnd, false/*not the same count of parts*/);
+}
+
+/**
+ * Answers true if a sub-pattern matches the sub-part of the given name using
+ * CamelCase rules, or false otherwise. char[] CamelCase matching does NOT
+ * accept explicit wild-cards '*' and '?' and is inherently case sensitive.
+ * Can match only subset of name/pattern, considering end positions as
+ * non-inclusive. The sub-pattern is defined by the patternStart and patternEnd
+ * positions.
+ *
+ * CamelCase denotes the convention of writing compound names without spaces,
+ * and capitalizing every term. This function recognizes both upper and lower
+ * CamelCase, depending whether the leading character is capitalized or not.
+ * The leading part of an upper CamelCase pattern is assumed to contain
+ * a sequence of capitals which are appearing in the matching name; e.g. 'NPE' will
+ * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern
+ * uses a lowercase first character. In Java, type names follow the upper
+ * CamelCase convention, whereas method or field names follow the lower
+ * CamelCase convention.
+ *
+ * The pattern may contain lowercase characters, which will be matched in a case
+ * sensitive way. These characters must appear in sequence in the name.
+ * For instance, 'NPExcep' will match 'NullPointerException', but not
+ * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not
+ * 'NoPointerException'.
+ *
+ * Digit characters are treated in a special way. They can be used in the pattern
+ * but are not always considered as leading character. For instance, both
+ * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'.
+ *
+ * CamelCase can be restricted to match only the same count of parts. When this
+ * restriction is specified the given pattern and the given name must have exactly
+ * the same number of parts (i.e. the same number of uppercase characters).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap' and
+ * 'HatMapper' but not 'HashMapEntry'.
+ *
+ *
+ * Examples:
+ *
+ * - pattern = "NPE".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => true
+ * - pattern = "NPE".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NoPermissionException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * patternStart = 0
+ * patternEnd = 6
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => true
+ * - pattern = "NuPoEx".toCharArray()
+ * patternStart = 0
+ * patternEnd = 6
+ * name = "NoPermissionException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => false
+ * - pattern = "npe".toCharArray()
+ * patternStart = 0
+ * patternEnd = 3
+ * name = "NullPointerException".toCharArray()
+ * nameStart = 0
+ * nameEnd = 20
+ * result => false
+ * - pattern = "IPL3".toCharArray()
+ * patternStart = 0
+ * patternEnd = 4
+ * name = "IPerspectiveListener3".toCharArray()
+ * nameStart = 0
+ * nameEnd = 21
+ * result => true
+ * - pattern = "HM".toCharArray()
+ * patternStart = 0
+ * patternEnd = 2
+ * name = "HashMapEntry".toCharArray()
+ * nameStart = 0
+ * nameEnd = 12
+ * result => (samePartCount == false)
+ *
+ *
+ *
+ * @param pattern the given pattern
+ * @param patternStart the start index of the pattern, inclusive
+ * @param patternEnd the end index of the pattern, exclusive
+ * @param name the given name
+ * @param nameStart the start index of the name, inclusive
+ * @param nameEnd the end index of the name, exclusive
+ * @param samePartCount flag telling whether the pattern and the name should
+ * have the same count of parts or not.
+ * For example:
+ *
+ * - 'HM' type string pattern will match 'HashMap' and 'HtmlMapper' types,
+ * but not 'HashMapEntry'
+ * - 'HMap' type string pattern will still match previous 'HashMap' and
+ * 'HtmlMapper' types, but not 'HighMagnitude'
+ *
+ * @return true if a sub-pattern matches the sub-part of the given name, false otherwise
+ * @since 3.4
+ */
+public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, boolean samePartCount) {
+
+ /* !!!!!!!!!! WARNING !!!!!!!!!!
+ * The algorithm implemented in this method has been heavily used in
+ * StringOperation#getCamelCaseMatchingRegions(String, int, int, String, int, int, boolean)
+ * method.
+ *
+ * So, if any change needs to be applied in the current algorithm,
+ * do NOT forget to also apply the same change in the StringOperation method!
+ */
+
+ if (name == null)
+ return false; // null name cannot match
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+ if (patternEnd < 0) patternEnd = pattern.length;
+ if (nameEnd < 0) nameEnd = name.length;
+
+ if (patternEnd <= patternStart) return nameEnd <= nameStart;
+ if (nameEnd <= nameStart) return false;
+ // check first pattern char
+ if (name[nameStart] != pattern[patternStart]) {
+ // first char must strictly match (upper/lower)
+ return false;
+ }
+
+ char patternChar, nameChar;
+ int iPattern = patternStart;
+ int iName = nameStart;
+
+ // Main loop is on pattern characters
+ while (true) {
+
+ iPattern++;
+ iName++;
+
+ if (iPattern == patternEnd) { // we have exhausted pattern...
+ // it's a match if the name can have additional parts (i.e. uppercase characters) or is also exhausted
+ if (!samePartCount || iName == nameEnd) return true;
+
+ // otherwise it's a match only if the name has no more uppercase characters
+ while (true) {
+ if (iName == nameEnd) {
+ // we have exhausted the name, so it's a match
+ return true;
+ }
+ nameChar = name[iName];
+ // test if the name character is uppercase
+ if (nameChar < ScannerHelper.MAX_OBVIOUS) {
+ if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & ScannerHelper.C_UPPER_LETTER) != 0) {
+ return false;
+ }
+ }
+ else if (!Character.isJavaIdentifierPart(nameChar) || Character.isUpperCase(nameChar)) {
+ return false;
+ }
+ iName++;
+ }
+ }
+
+ if (iName == nameEnd){
+ // We have exhausted the name (and not the pattern), so it's not a match
+ return false;
+ }
+
+ // For as long as we're exactly matching, bring it on (even if it's a lower case character)
+ if ((patternChar = pattern[iPattern]) == name[iName]) {
+ continue;
+ }
+
+ // If characters are not equals, then it's not a match if patternChar is lowercase
+ if (patternChar < ScannerHelper.MAX_OBVIOUS) {
+ if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_DIGIT)) == 0) {
+ return false;
+ }
+ }
+ else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar) && !Character.isDigit(patternChar)) {
+ return false;
+ }
+
+ // patternChar is uppercase, so let's find the next uppercase in name
+ while (true) {
+ if (iName == nameEnd){
+ // We have exhausted name (and not pattern), so it's not a match
+ return false;
+ }
+
+ nameChar = name[iName];
+ if (nameChar < ScannerHelper.MAX_OBVIOUS) {
+ int charNature = ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar];
+ if ((charNature & (ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_SPECIAL)) != 0) {
+ // nameChar is lowercase
+ iName++;
+ } else if ((charNature & ScannerHelper.C_DIGIT) != 0) {
+ // nameChar is digit => break if the digit is current pattern character otherwise consume it
+ if (patternChar == nameChar) break;
+ iName++;
+ // nameChar is uppercase...
+ } else if (patternChar != nameChar) {
+ //.. and it does not match patternChar, so it's not a match
+ return false;
+ } else {
+ //.. and it matched patternChar. Back to the big loop
+ break;
+ }
+ }
+ // Same tests for non-obvious characters
+ else if (Character.isJavaIdentifierPart(nameChar) && !Character.isUpperCase(nameChar)) {
+ iName++;
+ } else if (Character.isDigit(nameChar)) {
+ if (patternChar == nameChar) break;
+ iName++;
+ } else if (patternChar != nameChar) {
+ return false;
+ } else {
+ break;
+ }
+ }
+ // At this point, either name has been exhausted, or it is at an uppercase letter.
+ // Since pattern is also at an uppercase letter
+ }
+}
+
+/**
+ * Answers true if the characters of the pattern are contained in the
+ * name as a substring, in a case-insensitive way.
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @return true if the pattern matches the given name, false otherwise
+ * @since 3.12
+ */
+public static final boolean substringMatch(String pattern, String name) {
+ if (pattern == null || pattern.length() == 0) {
+ return true;
+ }
+ if (name == null) {
+ return false;
+ }
+ return checkSubstringMatch(pattern.toCharArray(), name.toCharArray());
+}
+
+/**
+ * Answers true if the characters of the pattern are contained in the
+ * name as a substring, in a case-insensitive way.
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @return true if the pattern matches the given name, false otherwise
+ * @since 3.12
+ */
+public static final boolean substringMatch(char[] pattern, char[] name) {
+ if (pattern == null || pattern.length == 0) {
+ return true;
+ }
+ if (name == null) {
+ return false;
+ }
+ return checkSubstringMatch(pattern, name);
+}
+
+/**
+ * Internal substring matching method; called after the null and length
+ * checks are performed.
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @return true if the pattern matches the given name, false otherwise
+ *
+ * @see CharOperation#substringMatch(char[], char[])
+ */
+private static final boolean checkSubstringMatch(char[] pattern, char[] name) {
+
+/* XXX: to be revised/enabled
+
+ // allow non-consecutive occurrence of pattern characters
+ if (pattern.length >= 3) {
+ int pidx = 0;
+
+ for (int nidx = 0; nidx < name.length; nidx++) {
+ if (Character.toLowerCase(name[nidx]) ==
+ Character.toLowerCase(pattern[pidx]))
+ pidx++;
+ if (pidx == pattern.length)
+ return true;
+ }
+
+ // for short patterns only allow consecutive occurrence
+ } else {
+*/
+ // outer loop iterates on the characters of the name; trying to
+ // match at any possible position
+ outer: for (int nidx = 0; nidx < name.length - pattern.length + 1; nidx++) {
+ // inner loop iterates on pattern characters
+ for (int pidx = 0; pidx < pattern.length; pidx++) {
+ if (Character.toLowerCase(name[nidx + pidx]) !=
+ Character.toLowerCase(pattern[pidx])) {
+ // no match until parameter list; do not match parameter list
+ if ((name[nidx + pidx] == '(') || (name[nidx + pidx] == ':'))
+ return false;
+ continue outer;
+ }
+ if (pidx == pattern.length - 1)
+ return true;
+ }
+ }
+ // XXX: }
+
+ return false;
+}
+
+/**
+ * Returns the char arrays as an array of Strings
+ *
+ * @param charArrays the char array to convert
+ * @return the char arrays as an array of Strings or null if the given char arrays is null.
+ * @since 3.0
+ */
+public static String[] charArrayToStringArray(char[][] charArrays) {
+ if (charArrays == null)
+ return null;
+ int length = charArrays.length;
+ if (length == 0)
+ return NO_STRINGS;
+ String[] strings= new String[length];
+ for (int i= 0; i < length; i++)
+ strings[i]= new String(charArrays[i]);
+ return strings;
+}
+
+/**
+ * Returns the char array as a String
+
+ * @param charArray the char array to convert
+ * @return the char array as a String or null if the given char array is null.
+ * @since 3.0
+ */
+public static String charToString(char[] charArray) {
+ if (charArray == null) return null;
+ return new String(charArray);
+}
+
+/**
+ * Answers a new array adding the second array at the end of first array.
+ * It answers null if the first and second are null.
+ * If the first array is null, then a new array char[][] is created with second.
+ * If the second array is null, then the first array is returned.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = { 'a' }
+ * => result = { { ' a' } }
+ *
+ *
+ * first = { { ' a' } }
+ * second = null
+ * => result = { { ' a' } }
+ *
+ *
+ *
+ * first = { { ' a' } }
+ * second = { ' b' }
+ * => result = { { ' a' } , { ' b' } }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the array to add at the end of the first array
+ * @return a new array adding the second array at the end of first array, or null if the two arrays are null.
+ */
+public static final char[][] arrayConcat(char[][] first, char[] second) {
+ if (second == null)
+ return first;
+ if (first == null)
+ return new char[][] { second };
+
+ int length = first.length;
+ char[][] result = new char[length + 1][];
+ System.arraycopy(first, 0, result, 0, length);
+ result[length] = second;
+ return result;
+}
+/**
+ * Compares the two char arrays lexicographically.
+ *
+ * Returns a negative integer if array1 lexicographically precedes the array2,
+ * a positive integer if this array1 lexicographically follows the array2, or
+ * zero if both arrays are equal.
+ *
+ * @param array1 the first given array
+ * @param array2 the second given array
+ * @return the returned value of the comparison between array1 and array2
+ * @throws NullPointerException if one of the arrays is null
+ * @since 3.3
+ */
+public static final int compareTo(char[] array1, char[] array2) {
+ int length1 = array1.length;
+ int length2 = array2.length;
+ int min = Math.min(length1, length2);
+ for (int i = 0; i < min; i++) {
+ if (array1[i] != array2[i]) {
+ return array1[i] - array2[i];
+ }
+ }
+ return length1 - length2;
+}
+/**
+ * Compares the two char arrays lexicographically between the given start and end positions.
+ *
+ * Returns a negative integer if array1 lexicographically precedes the array2,
+ * a positive integer if this array1 lexicographically follows the array2, or
+ * zero if both arrays are equal.
+ * The comparison is done between start and end positions.
+ *
+ * @param array1 the first given array
+ * @param array2 the second given array
+ * @param start the starting position to compare (inclusive)
+ * @param end the ending position to compare (exclusive)
+ *
+ * @return the returned value of the comparison between array1 and array2
+ * @throws NullPointerException if one of the arrays is null
+ * @since 3.7.1
+ */
+public static final int compareTo(char[] array1, char[] array2, int start, int end) {
+ int length1 = array1.length;
+ int length2 = array2.length;
+ int min = Math.min(length1, length2);
+ min = Math.min(min, end);
+ for (int i = start; i < min; i++) {
+ if (array1[i] != array2[i]) {
+ return array1[i] - array2[i];
+ }
+ }
+ return length1 - length2;
+}
+/**
+ * Compares the contents of the two arrays array and prefix. Returns
+ *
+ * - zero if the array starts with the prefix contents
+ * - the difference between the first two characters that are not equal
+ * - one if array length is lower than the prefix length and that the prefix starts with the
+ * array contents.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = null
+ * prefix = null
+ * => result = NullPointerException
+ *
+ *
+ *
+ * array = { 'a', 'b', 'c', 'd', 'e' }
+ * prefix = { 'a', 'b', 'c'}
+ * => result = 0
+ *
+ *
+ *
+ * array = { 'a', 'b', 'c', 'd', 'e' }
+ * prefix = { 'a', 'B', 'c'}
+ * => result = 32
+ *
+ *
+ *
+ * array = { 'd', 'b', 'c', 'd', 'e' }
+ * prefix = { 'a', 'b', 'c'}
+ * => result = 3
+ *
+ *
+ *
+ * array = { 'a', 'b', 'c', 'd', 'e' }
+ * prefix = { 'd', 'b', 'c'}
+ * => result = -3
+ *
+ *
+ *
+ * array = { 'a', 'a', 'c', 'd', 'e' }
+ * prefix = { 'a', 'e', 'c'}
+ * => result = -4
+ *
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param prefix the given prefix
+ * @return the result of the comparison (>=0 if array>prefix)
+ * @throws NullPointerException if either array or prefix is null
+ */
+public static final int compareWith(char[] array, char[] prefix) {
+ int arrayLength = array.length;
+ int prefixLength = prefix.length;
+ int min = Math.min(arrayLength, prefixLength);
+ int i = 0;
+ while (min-- != 0) {
+ char c1 = array[i];
+ char c2 = prefix[i++];
+ if (c1 != c2)
+ return c1 - c2;
+ }
+ if (prefixLength == i)
+ return 0;
+ return -1; // array is shorter than prefix (e.g. array:'ab' < prefix:'abc').
+}
+
+/**
+ * Answers the concatenation of the two arrays. It answers null if the two arrays are null.
+ * If the first array is null, then the second array is returned.
+ * If the second array is null, then the first array is returned.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = { 'a' }
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = null
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = { ' b' }
+ * => result = { ' a' , ' b' }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the second array to concatenate
+ * @return the concatenation of the two arrays, or null if the two arrays are null.
+ */
+public static final char[] concat(char[] first, char[] second) {
+ if (first == null)
+ return second;
+ if (second == null)
+ return first;
+
+ int length1 = first.length;
+ int length2 = second.length;
+ char[] result = new char[length1 + length2];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ return result;
+}
+
+/**
+ * Answers the concatenation of the three arrays. It answers null if the three arrays are null.
+ * If first is null, it answers the concatenation of second and third.
+ * If second is null, it answers the concatenation of first and third.
+ * If third is null, it answers the concatenation of first and second.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = { 'a' }
+ * third = { 'b' }
+ * => result = { ' a', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = null
+ * third = { 'b' }
+ * => result = { ' a', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'b' }
+ * third = null
+ * => result = { ' a', 'b' }
+ *
+ *
+ *
+ * first = null
+ * second = null
+ * third = null
+ * => result = null
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'b' }
+ * third = { 'c' }
+ * => result = { 'a', 'b', 'c' }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the second array to concatenate
+ * @param third the third array to concatenate
+ *
+ * @return the concatenation of the three arrays, or null if the three arrays are null.
+ */
+public static final char[] concat(
+ char[] first,
+ char[] second,
+ char[] third) {
+ if (first == null)
+ return concat(second, third);
+ if (second == null)
+ return concat(first, third);
+ if (third == null)
+ return concat(first, second);
+
+ int length1 = first.length;
+ int length2 = second.length;
+ int length3 = third.length;
+ char[] result = new char[length1 + length2 + length3];
+ System.arraycopy(first, 0, result, 0, length1);
+ System.arraycopy(second, 0, result, length1, length2);
+ System.arraycopy(third, 0, result, length1 + length2, length3);
+ return result;
+}
+
+/**
+ * Answers the concatenation of the two arrays inserting the separator character between the two arrays.
+ * It answers null if the two arrays are null.
+ * If the first array is null, then the second array is returned.
+ * If the second array is null, then the first array is returned.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = { 'a' }
+ * separator = '/'
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = null
+ * separator = '/'
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = { ' b' }
+ * separator = '/'
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the second array to concatenate
+ * @param separator the character to insert
+ * @return the concatenation of the two arrays inserting the separator character
+ * between the two arrays , or null if the two arrays are null.
+ */
+public static final char[] concat(
+ char[] first,
+ char[] second,
+ char separator) {
+ if (first == null)
+ return second;
+ if (second == null)
+ return first;
+
+ int length1 = first.length;
+ if (length1 == 0)
+ return second;
+ int length2 = second.length;
+ if (length2 == 0)
+ return first;
+
+ char[] result = new char[length1 + length2 + 1];
+ System.arraycopy(first, 0, result, 0, length1);
+ result[length1] = separator;
+ System.arraycopy(second, 0, result, length1 + 1, length2);
+ return result;
+}
+
+/**
+ * Answers the concatenation of the three arrays inserting the sep1 character between the
+ * first two arrays and sep2 between the last two.
+ * It answers null if the three arrays are null.
+ * If the first array is null, then it answers the concatenation of second and third inserting
+ * the sep2 character between them.
+ * If the second array is null, then it answers the concatenation of first and third inserting
+ * the sep1 character between them.
+ * If the third array is null, then it answers the concatenation of first and second inserting
+ * the sep1 character between them.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * sep1 = '/'
+ * second = { 'a' }
+ * sep2 = ':'
+ * third = { 'b' }
+ * => result = { ' a' , ':', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = null
+ * sep2 = ':'
+ * third = { 'b' }
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = { 'b' }
+ * sep2 = ':'
+ * third = null
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = { 'b' }
+ * sep2 = ':'
+ * third = { 'c' }
+ * => result = { ' a' , '/', 'b' , ':', 'c' }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param sep1 the character to insert
+ * @param second the second array to concatenate
+ * @param sep2 the character to insert
+ * @param third the second array to concatenate
+ * @return the concatenation of the three arrays inserting the sep1 character between the
+ * two arrays and sep2 between the last two.
+ */
+public static final char[] concat(
+ char[] first,
+ char sep1,
+ char[] second,
+ char sep2,
+ char[] third) {
+ if (first == null)
+ return concat(second, third, sep2);
+ if (second == null)
+ return concat(first, third, sep1);
+ if (third == null)
+ return concat(first, second, sep1);
+
+ int length1 = first.length;
+ int length2 = second.length;
+ int length3 = third.length;
+ char[] result = new char[length1 + length2 + length3 + 2];
+ System.arraycopy(first, 0, result, 0, length1);
+ result[length1] = sep1;
+ System.arraycopy(second, 0, result, length1 + 1, length2);
+ result[length1 + length2 + 1] = sep2;
+ System.arraycopy(third, 0, result, length1 + length2 + 2, length3);
+ return result;
+}
+/**
+ * Answers the concatenation of the two arrays inserting the separator character between the two arrays.
+ * It answers null if the two arrays are null.
+ * If the first array is null or is empty, then the second array is returned.
+ * If the second array is null or is empty, then the first array is returned.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = { 'a' }
+ * separator = '/'
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = null
+ * separator = '/'
+ * => result = { ' a' }
+ *
+ *
+ *
+ * first = { ' a' }
+ * second = { ' b' }
+ * separator = '/'
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ * *
+ * first = { ' a' }
+ * second = { }
+ * separator = '/'
+ * => result = { ' a'}
+ *
+ *
+
+ *
+ *
+ * @param first the first array to concatenate
+ * @param second the second array to concatenate
+ * @param separator the character to insert
+ * @return the concatenation of the two arrays inserting the separator character
+ * between the two arrays , or null if the two arrays are null.
+ * @since 3.12
+ */
+public static final char[] concatNonEmpty(
+ char[] first,
+ char[] second,
+ char separator) {
+ if (first == null || first.length == 0)
+ return second;
+ if (second == null || second.length == 0)
+ return first;
+ return concat(first, second, separator);
+}
+/**
+ * Answers the concatenation of the three arrays inserting the sep1 character between the
+ * first two arrays and sep2 between the last two.
+ * It answers null if the three arrays are null.
+ * If the first array is null or empty, then it answers the concatenation of second and third inserting
+ * the sep2 character between them.
+ * If the second array is null or empty, then it answers the concatenation of first and third inserting
+ * the sep1 character between them.
+ * If the third array is null or empty, then it answers the concatenation of first and second inserting
+ * the sep1 character between them.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * sep1 = '/'
+ * second = { 'a' }
+ * sep2 = ':'
+ * third = { 'b' }
+ * => result = { ' a' , ':', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = null
+ * sep2 = ':'
+ * third = { 'b' }
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = { 'b' }
+ * sep2 = ':'
+ * third = null
+ * => result = { ' a' , '/', 'b' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = { 'b' }
+ * sep2 = ':'
+ * third = { 'c' }
+ * => result = { ' a' , '/', 'b' , ':', 'c' }
+ *
+ *
+ *
+ * first = { 'a' }
+ * sep1 = '/'
+ * second = { }
+ * sep2 = ':'
+ * third = { 'c' }
+ * => result = { ' a', ':', 'c' }
+ *
+ *
+ *
+ *
+ * @param first the first array to concatenate
+ * @param sep1 the character to insert
+ * @param second the second array to concatenate
+ * @param sep2 the character to insert
+ * @param third the second array to concatenate
+ * @return the concatenation of the three arrays inserting the sep1 character between the
+ * two arrays and sep2 between the last two.
+ * @since 3.12
+ */
+public static final char[] concatNonEmpty(
+ char[] first,
+ char sep1,
+ char[] second,
+ char sep2,
+ char[] third) {
+ if (first == null || first.length == 0)
+ return concatNonEmpty(second, third, sep2);
+ if (second == null || second.length == 0)
+ return concatNonEmpty(first, third, sep1);
+ if (third == null || third.length == 0)
+ return concatNonEmpty(first, second, sep1);
+
+ return concat(first, sep1, second, sep2, third);
+}
+
+/**
+ * Answers a new array with prepending the prefix character and appending the suffix
+ * character at the end of the array. If array is null, it answers a new array containing the
+ * prefix and the suffix characters.
+ *
+ *
+ * For example:
+ *
+ *
+ * prefix = 'a'
+ * array = { 'b' }
+ * suffix = 'c'
+ * => result = { 'a', 'b' , 'c' }
+ *
+ *
+ *
+ * prefix = 'a'
+ * array = null
+ * suffix = 'c'
+ * => result = { 'a', 'c' }
+ *
+ *
+ *
+ * @param prefix the prefix character
+ * @param array the array that is concatenated with the prefix and suffix characters
+ * @param suffix the suffix character
+ * @return the new array
+ */
+public static final char[] concat(char prefix, char[] array, char suffix) {
+ if (array == null)
+ return new char[] { prefix, suffix };
+
+ int length = array.length;
+ char[] result = new char[length + 2];
+ result[0] = prefix;
+ System.arraycopy(array, 0, result, 1, length);
+ result[length + 1] = suffix;
+ return result;
+}
+
+/**
+ * Answers the concatenation of the given array parts using the given separator between each
+ * part and prepending the given name at the beginning.
+ *
+ *
+ * For example:
+ *
+ *
+ * name = { 'c' }
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' , '.', 'c' }
+ *
+ *
+ *
+ * name = null
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' }
+ *
+ *
+ * name = { ' c' }
+ * array = null
+ * separator = '.'
+ * => result = { 'c' }
+ *
+ *
+ *
+ * @param name the given name
+ * @param array the given array
+ * @param separator the given separator
+ * @return the concatenation of the given array parts using the given separator between each
+ * part and prepending the given name at the beginning
+ */
+public static final char[] concatWith(
+ char[] name,
+ char[][] array,
+ char separator) {
+ int nameLength = name == null ? 0 : name.length;
+ if (nameLength == 0)
+ return concatWith(array, separator);
+
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return name;
+
+ int size = nameLength;
+ int index = length;
+ while (--index >= 0)
+ if (array[index].length > 0)
+ size += array[index].length + 1;
+ char[] result = new char[size];
+ index = size;
+ for (int i = length - 1; i >= 0; i--) {
+ int subLength = array[i].length;
+ if (subLength > 0) {
+ index -= subLength;
+ System.arraycopy(array[i], 0, result, index, subLength);
+ result[--index] = separator;
+ }
+ }
+ System.arraycopy(name, 0, result, 0, nameLength);
+ return result;
+}
+
+/**
+ * Answers the concatenation of the given array parts using the given separator between each
+ * part and appending the given name at the end.
+ *
+ *
+ * For example:
+ *
+ *
+ * name = { 'c' }
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' , '.', 'c' }
+ *
+ *
+ *
+ * name = null
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' }
+ *
+ *
+ * name = { ' c' }
+ * array = null
+ * separator = '.'
+ * => result = { 'c' }
+ *
+ *
+ *
+ * @param array the given array
+ * @param name the given name
+ * @param separator the given separator
+ * @return the concatenation of the given array parts using the given separator between each
+ * part and appending the given name at the end
+ */
+public static final char[] concatWith(
+ char[][] array,
+ char[] name,
+ char separator) {
+ int nameLength = name == null ? 0 : name.length;
+ if (nameLength == 0)
+ return concatWith(array, separator);
+
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return name;
+
+ int size = nameLength;
+ int index = length;
+ while (--index >= 0)
+ if (array[index].length > 0)
+ size += array[index].length + 1;
+ char[] result = new char[size];
+ index = 0;
+ for (int i = 0; i < length; i++) {
+ int subLength = array[i].length;
+ if (subLength > 0) {
+ System.arraycopy(array[i], 0, result, index, subLength);
+ index += subLength;
+ result[index++] = separator;
+ }
+ }
+ System.arraycopy(name, 0, result, index, nameLength);
+ return result;
+}
+
+/**
+ * Answers the concatenation of the given array parts using the given separator between each part.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' }
+ *
+ *
+ *
+ * array = null
+ * separator = '.'
+ * => result = { }
+ *
+ *
+ *
+ * @param array the given array
+ * @param separator the given separator
+ * @return the concatenation of the given array parts using the given separator between each part
+ */
+public static final char[] concatWith(char[][] array, char separator) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return CharOperation.NO_CHAR;
+
+ int size = length - 1;
+ int index = length;
+ while (--index >= 0) {
+ if (array[index].length == 0)
+ size--;
+ else
+ size += array[index].length;
+ }
+ if (size <= 0)
+ return CharOperation.NO_CHAR;
+ char[] result = new char[size];
+ index = length;
+ while (--index >= 0) {
+ length = array[index].length;
+ if (length > 0) {
+ System.arraycopy(
+ array[index],
+ 0,
+ result,
+ (size -= length),
+ length);
+ if (--size >= 0)
+ result[size] = separator;
+ }
+ }
+ return result;
+}
+
+/**
+ * Answers the concatenation of the given array parts using the given separator between each part
+ * irrespective of whether an element is a zero length array or not.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { { 'a' }, {}, { 'b' } }
+ * separator = ''
+ * => result = { 'a', '/', '/', 'b' }
+ *
+ *
+ *
+ * array = { { 'a' }, { 'b' } }
+ * separator = '.'
+ * => result = { 'a', '.', 'b' }
+ *
+ *
+ *
+ * array = null
+ * separator = '.'
+ * => result = { }
+ *
+ *
+ *
+ * @param array the given array
+ * @param separator the given separator
+ * @return the concatenation of the given array parts using the given separator between each part
+ * @since 3.12
+ */
+public static final char[] concatWithAll(char[][] array, char separator) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return CharOperation.NO_CHAR;
+
+ int size = length - 1;
+ int index = length;
+ while (--index >= 0) {
+ size += array[index].length;
+ }
+ char[] result = new char[size];
+ index = length;
+ while (--index >= 0) {
+ length = array[index].length;
+ if (length > 0) {
+ System.arraycopy(
+ array[index],
+ 0,
+ result,
+ (size -= length),
+ length);
+ }
+ if (--size >= 0)
+ result[size] = separator;
+ }
+ return result;
+}
+
+/**
+ * Answers true if the array contains an occurrence of character, false otherwise.
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ * character = 'c'
+ * array = { { ' a' }, { ' b' } }
+ * result => false
+ *
+ *
+ *
+ * character = 'a'
+ * array = { { ' a' }, { ' b' } }
+ * result => true
+ *
+ *
+ *
+ *
+ * @param character the character to search
+ * @param array the array in which the search is done
+ * @return true if the array contains an occurrence of character, false otherwise.
+ * @throws NullPointerException if array is null.
+ */
+public static final boolean contains(char character, char[][] array) {
+ for (int i = array.length; --i >= 0;) {
+ char[] subarray = array[i];
+ for (int j = subarray.length; --j >= 0;)
+ if (subarray[j] == character)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Answers true if the array contains an occurrence of character, false otherwise.
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ * character = 'c'
+ * array = { ' b' }
+ * result => false
+ *
+ *
+ *
+ * character = 'a'
+ * array = { ' a' , ' b' }
+ * result => true
+ *
+ *
+ *
+ *
+ * @param character the character to search
+ * @param array the array in which the search is done
+ * @return true if the array contains an occurrence of character, false otherwise.
+ * @throws NullPointerException if array is null.
+ */
+public static final boolean contains(char character, char[] array) {
+ for (int i = array.length; --i >= 0;)
+ if (array[i] == character)
+ return true;
+ return false;
+}
+
+/**
+ * Answers true if the array contains an occurrence of one of the characters, false otherwise.
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ * characters = { 'c', 'd' }
+ * array = { 'a', ' b' }
+ * result => false
+ *
+ *
+ *
+ * characters = { 'c', 'd' }
+ * array = { 'a', ' b', 'c' }
+ * result => true
+ *
+ *
+ *
+ *
+ * @param characters the characters to search
+ * @param array the array in which the search is done
+ * @return true if the array contains an occurrence of one of the characters, false otherwise.
+ * @throws NullPointerException if array is null.
+ * @since 3.1
+ */
+public static final boolean contains(char[] characters, char[] array) {
+ for (int i = array.length; --i >= 0;)
+ for (int j = characters.length; --j >= 0;)
+ if (array[i] == characters[j])
+ return true;
+ return false;
+}
+
+/**
+ * Answers a deep copy of the toCopy array.
+ *
+ * @param toCopy the array to copy
+ * @return a deep copy of the toCopy array.
+ */
+
+public static final char[][] deepCopy(char[][] toCopy) {
+ int toCopyLength = toCopy.length;
+ char[][] result = new char[toCopyLength][];
+ for (int i = 0; i < toCopyLength; i++) {
+ char[] toElement = toCopy[i];
+ int toElementLength = toElement.length;
+ char[] resultElement = new char[toElementLength];
+ System.arraycopy(toElement, 0, resultElement, 0, toElementLength);
+ result[i] = resultElement;
+ }
+ return result;
+}
+
+/**
+ * Return true if array ends with the sequence of characters contained in toBeFound,
+ * otherwise false.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a', 'b', 'c', 'd' }
+ * toBeFound = { 'b', 'c' }
+ * result => false
+ *
+ *
+ *
+ * array = { 'a', 'b', 'c' }
+ * toBeFound = { 'b', 'c' }
+ * result => true
+ *
+ *
+ *
+ *
+ * @param array the array to check
+ * @param toBeFound the array to find
+ * @return true if array ends with the sequence of characters contained in toBeFound,
+ * otherwise false.
+ * @throws NullPointerException if array is null or toBeFound is null
+ */
+public static final boolean endsWith(char[] array, char[] toBeFound) {
+ int i = toBeFound.length;
+ int j = array.length - i;
+
+ if (j < 0)
+ return false;
+ while (--i >= 0)
+ if (toBeFound[i] != array[i + j])
+ return false;
+ return true;
+}
+
+/**
+ * Answers true if the two arrays are identical character by character, otherwise false.
+ * The equality is case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * result => true
+ *
+ *
+ *
+ * first = { { } }
+ * second = null
+ * result => false
+ *
+ *
+ *
+ * first = { { 'a' } }
+ * second = { { 'a' } }
+ * result => true
+ *
+ *
+ *
+ * first = { { 'A' } }
+ * second = { { 'a' } }
+ * result => false
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @return true if the two arrays are identical character by character, otherwise false
+ */
+public static final boolean equals(char[][] first, char[][] second) {
+ if (first == second)
+ return true;
+ if (first == null || second == null)
+ return false;
+ if (first.length != second.length)
+ return false;
+
+ for (int i = first.length; --i >= 0;)
+ if (!equals(first[i], second[i]))
+ return false;
+ return true;
+}
+
+/**
+ * If isCaseSensite is true, answers true if the two arrays are identical character
+ * by character, otherwise false.
+ * If it is false, answers true if the two arrays are identical character by
+ * character without checking the case, otherwise false.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * first = { { } }
+ * second = null
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * first = { { 'A' } }
+ * second = { { 'a' } }
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * first = { { 'A' } }
+ * second = { { 'a' } }
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @param isCaseSensitive check whether or not the equality should be case sensitive
+ * @return true if the two arrays are identical character by character according to the value
+ * of isCaseSensitive, otherwise false
+ */
+public static final boolean equals(
+ char[][] first,
+ char[][] second,
+ boolean isCaseSensitive) {
+
+ if (isCaseSensitive) {
+ return equals(first, second);
+ }
+ if (first == second)
+ return true;
+ if (first == null || second == null)
+ return false;
+ if (first.length != second.length)
+ return false;
+
+ for (int i = first.length; --i >= 0;)
+ if (!equals(first[i], second[i], false))
+ return false;
+ return true;
+}
+
+/**
+ * Answers true if the two arrays are identical character by character, otherwise false.
+ * The equality is case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * result => true
+ *
+ *
+ *
+ * first = { }
+ * second = null
+ * result => false
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'a' }
+ * result => true
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'A' }
+ * result => false
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @return true if the two arrays are identical character by character, otherwise false
+ */
+public static final boolean equals(char[] first, char[] second) {
+ if (first == second)
+ return true;
+ if (first == null || second == null)
+ return false;
+ if (first.length != second.length)
+ return false;
+
+ for (int i = first.length; --i >= 0;)
+ if (first[i] != second[i])
+ return false;
+ return true;
+}
+
+/**
+ * Answers true if the first array is identical character by character to a portion of the second array
+ * delimited from position secondStart (inclusive) to secondEnd(exclusive), otherwise false.
+ * The equality is case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * secondStart = 0
+ * secondEnd = 0
+ * result => true
+ *
+ *
+ *
+ * first = { }
+ * second = null
+ * secondStart = 0
+ * secondEnd = 0
+ * result => false
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'a' }
+ * secondStart = 0
+ * secondEnd = 1
+ * result => true
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'A' }
+ * secondStart = 0
+ * secondEnd = 1
+ * result => false
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @param secondStart inclusive start position in the second array to compare
+ * @param secondEnd exclusive end position in the second array to compare
+ * @return true if the first array is identical character by character to fragment of second array ranging from secondStart to secondEnd-1, otherwise false
+ * @since 3.0
+ */
+public static final boolean equals(char[] first, char[] second, int secondStart, int secondEnd) {
+ return equals(first, second, secondStart, secondEnd, true);
+}
+/**
+ * Answers true if the first array is identical character by character to a portion of the second array
+ * delimited from position secondStart (inclusive) to secondEnd(exclusive), otherwise false. The equality could be either
+ * case sensitive or case insensitive according to the value of the isCaseSensitive parameter.
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * secondStart = 0
+ * secondEnd = 0
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ * first = { }
+ * second = null
+ * secondStart = 0
+ * secondEnd = 0
+ * isCaseSensitive = false
+ * result => false
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'a' }
+ * secondStart = 0
+ * secondEnd = 1
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'A' }
+ * secondStart = 0
+ * secondEnd = 1
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * first = { 'a' }
+ * second = { 'A' }
+ * secondStart = 0
+ * secondEnd = 1
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @param secondStart inclusive start position in the second array to compare
+ * @param secondEnd exclusive end position in the second array to compare
+ * @param isCaseSensitive check whether or not the equality should be case sensitive
+ * @return true if the first array is identical character by character to fragment of second array ranging from secondStart to secondEnd-1, otherwise false
+ * @since 3.2
+ */
+public static final boolean equals(char[] first, char[] second, int secondStart, int secondEnd, boolean isCaseSensitive) {
+ if (first == second)
+ return true;
+ if (first == null || second == null)
+ return false;
+ if (first.length != secondEnd - secondStart)
+ return false;
+ if (isCaseSensitive) {
+ for (int i = first.length; --i >= 0;)
+ if (first[i] != second[i+secondStart])
+ return false;
+ } else {
+ for (int i = first.length; --i >= 0;)
+ if (ScannerHelper.toLowerCase(first[i]) != ScannerHelper.toLowerCase(second[i+secondStart]))
+ return false;
+ }
+ return true;
+}
+
+/**
+ * If isCaseSensite is true, answers true if the two arrays are identical character
+ * by character, otherwise false.
+ * If it is false, answers true if the two arrays are identical character by
+ * character without checking the case, otherwise false.
+ *
+ *
+ * For example:
+ *
+ *
+ * first = null
+ * second = null
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * first = { }
+ * second = null
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * first = { 'A' }
+ * second = { 'a' }
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * first = { 'A' }
+ * second = { 'a' }
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ *
+ * @param first the first array
+ * @param second the second array
+ * @param isCaseSensitive check whether or not the equality should be case sensitive
+ * @return true if the two arrays are identical character by character according to the value
+ * of isCaseSensitive, otherwise false
+ */
+public static final boolean equals(
+ char[] first,
+ char[] second,
+ boolean isCaseSensitive) {
+
+ if (isCaseSensitive) {
+ return equals(first, second);
+ }
+ if (first == second)
+ return true;
+ if (first == null || second == null)
+ return false;
+ if (first.length != second.length)
+ return false;
+
+ for (int i = first.length; --i >= 0;)
+ if (ScannerHelper.toLowerCase(first[i])
+ != ScannerHelper.toLowerCase(second[i]))
+ return false;
+ return true;
+}
+
+/**
+ * If isCaseSensite is true, the equality is case sensitive, otherwise it is case insensitive.
+ *
+ * Answers true if the name contains the fragment at the starting index startIndex, otherwise false.
+ *
+ *
+ * For example:
+ *
+ *
+ * fragment = { 'b', 'c' , 'd' }
+ * name = { 'a', 'b', 'c' , 'd' }
+ * startIndex = 1
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * fragment = { 'b', 'c' , 'd' }
+ * name = { 'a', 'b', 'C' , 'd' }
+ * startIndex = 1
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * fragment = { 'b', 'c' , 'd' }
+ * name = { 'a', 'b', 'C' , 'd' }
+ * startIndex = 0
+ * isCaseSensitive = false
+ * result => false
+ *
+ *
+ *
+ * fragment = { 'b', 'c' , 'd' }
+ * name = { 'a', 'b'}
+ * startIndex = 0
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ *
+ * @param fragment the fragment to check
+ * @param name the array to check
+ * @param startIndex the starting index
+ * @param isCaseSensitive check whether or not the equality should be case sensitive
+ * @return true if the name contains the fragment at the starting index startIndex according to the
+ * value of isCaseSensitive, otherwise false.
+ * @throws NullPointerException if fragment or name is null.
+ */
+public static final boolean fragmentEquals(
+ char[] fragment,
+ char[] name,
+ int startIndex,
+ boolean isCaseSensitive) {
+
+ int max = fragment.length;
+ if (name.length < max + startIndex)
+ return false;
+ if (isCaseSensitive) {
+ for (int i = max;
+ --i >= 0;
+ ) // assumes the prefix is not larger than the name
+ if (fragment[i] != name[i + startIndex])
+ return false;
+ return true;
+ }
+ for (int i = max;
+ --i >= 0;
+ ) // assumes the prefix is not larger than the name
+ if (ScannerHelper.toLowerCase(fragment[i])
+ != ScannerHelper.toLowerCase(name[i + startIndex]))
+ return false;
+ return true;
+}
+
+/**
+ * Answers a hashcode for the array
+ *
+ * @param array the array for which a hashcode is required
+ * @return the hashcode
+ * @throws NullPointerException if array is null
+ */
+public static final int hashCode(char[] array) {
+ int length = array.length;
+ int hash = length == 0 ? 31 : array[0];
+ if (length < 8) {
+ for (int i = length; --i > 0;)
+ hash = (hash * 31) + array[i];
+ } else {
+ // 8 characters is enough to compute a decent hash code, don't waste time examining every character
+ for (int i = length - 1, last = i > 16 ? i - 16 : 0; i > last; i -= 2)
+ hash = (hash * 31) + array[i];
+ }
+ return hash & 0x7FFFFFFF;
+}
+
+/**
+ * Answers true if c is a whitespace according to the JLS (\u0009, \u000a, \u000c, \u000d, \u0020), otherwise false.
+ *
+ *
+ * For example:
+ *
+ *
+ * c = ' '
+ * result => true
+ *
+ *
+ *
+ * c = '\u3000'
+ * result => false
+ *
+ *
+ *
+ *
+ * @param c the character to check
+ * @return true if c is a whitespace according to the JLS, otherwise false.
+ */
+public static boolean isWhitespace(char c) {
+ return c < ScannerHelper.MAX_OBVIOUS && ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_JLS_SPACE) != 0);
+}
+
+/**
+ * Answers the first index in the array for which the corresponding character is
+ * equal to toBeFound. Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @return the first index in the array for which the corresponding character is
+ * equal to toBeFound, -1 otherwise
+ * @throws NullPointerException if array is null
+ */
+public static final int indexOf(char toBeFound, char[] array) {
+ return indexOf(toBeFound, array, 0);
+}
+
+/**
+ * Answers the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule. Answers -1 if no match is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = { 'c' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = { 'e' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the subarray to search
+ * @param array the array to be searched
+ * @param isCaseSensitive flag to know if the matching should be case sensitive
+ * @return the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule, -1 otherwise
+ * @throws NullPointerException if array is null or toBeFound is null
+ * @since 3.2
+ */
+public static final int indexOf(char[] toBeFound, char[] array, boolean isCaseSensitive) {
+ return indexOf(toBeFound, array, isCaseSensitive, 0);
+}
+
+/**
+ * Answers the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule starting at the index start. Answers -1 if no match is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = { 'c' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = { 'e' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the subarray to search
+ * @param array the array to be searched
+ * @param isCaseSensitive flag to know if the matching should be case sensitive
+ * @param start the starting index
+ * @return the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule starting at the index start, -1 otherwise
+ * @throws NullPointerException if array is null or toBeFound is null
+ * @since 3.2
+ */
+public static final int indexOf(final char[] toBeFound, final char[] array, final boolean isCaseSensitive, final int start) {
+ return indexOf(toBeFound, array, isCaseSensitive, start, array.length);
+}
+
+/**
+ * Answers the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule starting at the index start. Answers -1 if no match is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = { 'c' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = { 'e' }
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the subarray to search
+ * @param array the array to be searched
+ * @param isCaseSensitive flag to know if the matching should be case sensitive
+ * @param start the starting index (inclusive)
+ * @param end the end index (exclusive)
+ * @return the first index in the array for which the toBeFound array is a matching
+ * subarray following the case rule starting at the index start, -1 otherwise
+ * @throws NullPointerException if array is null or toBeFound is null
+ * @since 3.2
+ */
+public static final int indexOf(final char[] toBeFound, final char[] array, final boolean isCaseSensitive, final int start, final int end) {
+ final int arrayLength = end;
+ final int toBeFoundLength = toBeFound.length;
+ if (toBeFoundLength > arrayLength || start < 0) return -1;
+ if (toBeFoundLength == 0) return 0;
+ if (toBeFoundLength == arrayLength) {
+ if (isCaseSensitive) {
+ for (int i = start; i < arrayLength; i++) {
+ if (array[i] != toBeFound[i]) return -1;
+ }
+ return 0;
+ } else {
+ for (int i = start; i < arrayLength; i++) {
+ if (ScannerHelper.toLowerCase(array[i]) != ScannerHelper.toLowerCase(toBeFound[i])) return -1;
+ }
+ return 0;
+ }
+ }
+ if (isCaseSensitive) {
+ arrayLoop: for (int i = start, max = arrayLength - toBeFoundLength + 1; i < max; i++) {
+ if (array[i] == toBeFound[0]) {
+ for (int j = 1; j < toBeFoundLength; j++) {
+ if (array[i + j] != toBeFound[j]) continue arrayLoop;
+ }
+ return i;
+ }
+ }
+ } else {
+ arrayLoop: for (int i = start, max = arrayLength - toBeFoundLength + 1; i < max; i++) {
+ if (ScannerHelper.toLowerCase(array[i]) == ScannerHelper.toLowerCase(toBeFound[0])) {
+ for (int j = 1; j < toBeFoundLength; j++) {
+ if (ScannerHelper.toLowerCase(array[i + j]) != ScannerHelper.toLowerCase(toBeFound[j])) continue arrayLoop;
+ }
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+/**
+ * Answers the first index in the array for which the corresponding character is
+ * equal to toBeFound starting the search at index start.
+ * Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 2
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 3
+ * result => -1
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 1
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @param start the starting index
+ * @return the first index in the array for which the corresponding character is
+ * equal to toBeFound, -1 otherwise
+ * @throws NullPointerException if array is null
+ * @throws ArrayIndexOutOfBoundsException if start is lower than 0
+ */
+public static final int indexOf(char toBeFound, char[] array, int start) {
+ for (int i = start; i < array.length; i++)
+ if (toBeFound == array[i])
+ return i;
+ return -1;
+}
+
+/**
+ * Answers the first index in the array for which the corresponding character is
+ * equal to toBeFound starting the search at index start and before the ending index.
+ * Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 2
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 3
+ * result => -1
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * start = 1
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @param start the starting index (inclusive)
+ * @param end the ending index (exclusive)
+ * @return the first index in the array for which the corresponding character is
+ * equal to toBeFound, -1 otherwise
+ * @throws NullPointerException if array is null
+ * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or ending greater than array length
+ * @since 3.2
+ */
+public static final int indexOf(char toBeFound, char[] array, int start, int end) {
+ for (int i = start; i < end; i++)
+ if (toBeFound == array[i])
+ return i;
+ return -1;
+}
+
+/**
+ * Answers the last index in the array for which the corresponding character is
+ * equal to toBeFound starting from the end of the array.
+ * Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' , 'c', 'e' }
+ * result => 4
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @return the last index in the array for which the corresponding character is
+ * equal to toBeFound starting from the end of the array, -1 otherwise
+ * @throws NullPointerException if array is null
+ */
+public static final int lastIndexOf(char toBeFound, char[] array) {
+ for (int i = array.length; --i >= 0;)
+ if (toBeFound == array[i])
+ return i;
+ return -1;
+}
+
+/**
+ * Answers the last index in the array for which the corresponding character is
+ * equal to toBeFound stopping at the index startIndex.
+ * Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * startIndex = 2
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd', 'e' }
+ * startIndex = 3
+ * result => -1
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * startIndex = 0
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @param startIndex the stopping index
+ * @return the last index in the array for which the corresponding character is
+ * equal to toBeFound stopping at the index startIndex, -1 otherwise
+ * @throws NullPointerException if array is null
+ * @throws ArrayIndexOutOfBoundsException if startIndex is lower than 0
+ */
+public static final int lastIndexOf(
+ char toBeFound,
+ char[] array,
+ int startIndex) {
+ for (int i = array.length; --i >= startIndex;)
+ if (toBeFound == array[i])
+ return i;
+ return -1;
+}
+
+/**
+ * Answers the last index in the array for which the corresponding character is
+ * equal to toBeFound starting from endIndex to startIndex.
+ * Answers -1 if no occurrence of this character is found.
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd' }
+ * startIndex = 2
+ * endIndex = 2
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { ' a', 'b', 'c', 'd', 'e' }
+ * startIndex = 3
+ * endIndex = 4
+ * result => -1
+ *
+ *
+ *
+ * toBeFound = 'e'
+ * array = { ' a', 'b', 'c', 'd' }
+ * startIndex = 0
+ * endIndex = 3
+ * result => -1
+ *
+ *
+ *
+ *
+ * @param toBeFound the character to search
+ * @param array the array to be searched
+ * @param startIndex the stopping index
+ * @param endIndex the starting index
+ * @return the last index in the array for which the corresponding character is
+ * equal to toBeFound starting from endIndex to startIndex, -1 otherwise
+ * @throws NullPointerException if array is null
+ * @throws ArrayIndexOutOfBoundsException if endIndex is greater or equals to array length or starting is lower than 0
+ */
+public static final int lastIndexOf(
+ char toBeFound,
+ char[] array,
+ int startIndex,
+ int endIndex) {
+ for (int i = endIndex; --i >= startIndex;)
+ if (toBeFound == array[i])
+ return i;
+ return -1;
+}
+
+/**
+ * Answers the last portion of a name given a separator.
+ *
+ *
+ * For example,
+ *
+ * lastSegment("java.lang.Object".toCharArray(),'.') --> Object
+ *
+ *
+ * @param array the array
+ * @param separator the given separator
+ * @return the last portion of a name given a separator
+ * @throws NullPointerException if array is null
+ */
+final static public char[] lastSegment(char[] array, char separator) {
+ int pos = lastIndexOf(separator, array);
+ if (pos < 0)
+ return array;
+ return subarray(array, pos + 1, array.length);
+}
+
+/**
+ * Answers true if the pattern matches the given name, false otherwise. This char[] pattern matching
+ * accepts wild-cards '*' and '?'.
+ *
+ * When not case sensitive, the pattern is assumed to already be lowercased, the
+ * name will be lowercased character per character as comparing.
+ * If name is null, the answer is false.
+ * If pattern is null, the answer is true if name is not null.
+ *
+ * For example:
+ *
+ *
+ * pattern = { '?', 'b', '*' }
+ * name = { 'a', 'b', 'c' , 'd' }
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * pattern = { '?', 'b', '?' }
+ * name = { 'a', 'b', 'c' , 'd' }
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ * pattern = { 'b', '*' }
+ * name = { 'a', 'b', 'c' , 'd' }
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ *
+ * @param pattern the given pattern
+ * @param name the given name
+ * @param isCaseSensitive flag to know whether or not the matching should be case sensitive
+ * @return true if the pattern matches the given name, false otherwise
+ */
+public static final boolean match(
+ char[] pattern,
+ char[] name,
+ boolean isCaseSensitive) {
+
+ if (name == null)
+ return false; // null name cannot match
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+
+ return match(
+ pattern,
+ 0,
+ pattern.length,
+ name,
+ 0,
+ name.length,
+ isCaseSensitive);
+}
+
+/**
+ * Answers true if a sub-pattern matches the subpart of the given name, false otherwise.
+ * char[] pattern matching, accepting wild-cards '*' and '?'. Can match only subset of name/pattern.
+ * end positions are non-inclusive.
+ * The subpattern is defined by the patternStart and pattternEnd positions.
+ * When not case sensitive, the pattern is assumed to already be lowercased, the
+ * name will be lowercased character per character as comparing.
+ *
+ *
+ * For example:
+ *
+ *
+ * pattern = { '?', 'b', '*' }
+ * patternStart = 1
+ * patternEnd = 3
+ * name = { 'a', 'b', 'c' , 'd' }
+ * nameStart = 1
+ * nameEnd = 4
+ * isCaseSensitive = true
+ * result => true
+ *
+ *
+ *
+ * pattern = { '?', 'b', '*' }
+ * patternStart = 1
+ * patternEnd = 2
+ * name = { 'a', 'b', 'c' , 'd' }
+ * nameStart = 1
+ * nameEnd = 4
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ *
+ * @param pattern the given pattern
+ * @param patternStart the given pattern start
+ * @param patternEnd the given pattern end
+ * @param name the given name
+ * @param nameStart the given name start
+ * @param nameEnd the given name end
+ * @param isCaseSensitive flag to know if the matching should be case sensitive
+ * @return true if a sub-pattern matches the subpart of the given name, false otherwise
+ */
+public static final boolean match(
+ char[] pattern,
+ int patternStart,
+ int patternEnd,
+ char[] name,
+ int nameStart,
+ int nameEnd,
+ boolean isCaseSensitive) {
+
+ if (name == null)
+ return false; // null name cannot match
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+ int iPattern = patternStart;
+ int iName = nameStart;
+
+ if (patternEnd < 0)
+ patternEnd = pattern.length;
+ if (nameEnd < 0)
+ nameEnd = name.length;
+
+ /* check first segment */
+ char patternChar = 0;
+ while (true) {
+ if (iPattern == patternEnd) {
+ if (iName == nameEnd) return true; // the chars match
+ return false; // pattern has ended but not the name, no match
+ }
+ if ((patternChar = pattern[iPattern]) == '*') {
+ break;
+ }
+ if (iName == nameEnd) {
+ return false; // name has ended but not the pattern
+ }
+ if (patternChar
+ != (isCaseSensitive
+ ? name[iName]
+ : ScannerHelper.toLowerCase(name[iName]))
+ && patternChar != '?') {
+ return false;
+ }
+ iName++;
+ iPattern++;
+ }
+ /* check sequence of star+segment */
+ int segmentStart;
+ if (patternChar == '*') {
+ segmentStart = ++iPattern; // skip star
+ } else {
+ segmentStart = 0; // force iName check
+ }
+ int prefixStart = iName;
+ checkSegment : while (iName < nameEnd) {
+ if (iPattern == patternEnd) {
+ iPattern = segmentStart; // mismatch - restart current segment
+ iName = ++prefixStart;
+ continue checkSegment;
+ }
+ /* segment is ending */
+ if ((patternChar = pattern[iPattern]) == '*') {
+ segmentStart = ++iPattern; // skip start
+ if (segmentStart == patternEnd) {
+ return true;
+ }
+ prefixStart = iName;
+ continue checkSegment;
+ }
+ /* check current name character */
+ if ((isCaseSensitive ? name[iName] : ScannerHelper.toLowerCase(name[iName]))
+ != patternChar
+ && patternChar != '?') {
+ iPattern = segmentStart; // mismatch - restart current segment
+ iName = ++prefixStart;
+ continue checkSegment;
+ }
+ iName++;
+ iPattern++;
+ }
+
+ return (segmentStart == patternEnd)
+ || (iName == nameEnd && iPattern == patternEnd)
+ || (iPattern == patternEnd - 1 && pattern[iPattern] == '*');
+}
+
+/**
+ * Answers true if the pattern matches the filepath using the pathSepatator, false otherwise.
+ *
+ * Path char[] pattern matching, accepting wild-cards '**', '*' and '?' (using Ant directory tasks
+ * conventions, also see "http://jakarta.apache.org/ant/manual/dirtasks.html#defaultexcludes").
+ * Path pattern matching is enhancing regular pattern matching in supporting extra rule where '**' represent
+ * any folder combination.
+ * Special rule:
+ * - foo\ is equivalent to foo\**
+ * When not case sensitive, the pattern is assumed to already be lowercased, the
+ * name will be lowercased character per character as comparing.
+ *
+ * @param pattern the given pattern
+ * @param filepath the given path
+ * @param isCaseSensitive to find out whether or not the matching should be case sensitive
+ * @param pathSeparator the given path separator
+ * @return true if the pattern matches the filepath using the pathSepatator, false otherwise
+ */
+public static final boolean pathMatch(
+ char[] pattern,
+ char[] filepath,
+ boolean isCaseSensitive,
+ char pathSeparator) {
+
+ if (filepath == null)
+ return false; // null name cannot match
+ if (pattern == null)
+ return true; // null pattern is equivalent to '*'
+
+ // offsets inside pattern
+ int pSegmentStart = pattern[0] == pathSeparator ? 1 : 0;
+ int pLength = pattern.length;
+ int pSegmentEnd = CharOperation.indexOf(pathSeparator, pattern, pSegmentStart+1);
+ if (pSegmentEnd < 0) pSegmentEnd = pLength;
+
+ // special case: pattern foo\ is equivalent to foo\**
+ boolean freeTrailingDoubleStar = pattern[pLength - 1] == pathSeparator;
+
+ // offsets inside filepath
+ int fSegmentStart, fLength = filepath.length;
+ if (filepath[0] != pathSeparator){
+ fSegmentStart = 0;
+ } else {
+ fSegmentStart = 1;
+ }
+ if (fSegmentStart != pSegmentStart) {
+ return false; // both must start with a separator or none.
+ }
+ int fSegmentEnd = CharOperation.indexOf(pathSeparator, filepath, fSegmentStart+1);
+ if (fSegmentEnd < 0) fSegmentEnd = fLength;
+
+ // first segments
+ while (pSegmentStart < pLength
+ && !(pSegmentEnd == pLength && freeTrailingDoubleStar
+ || (pSegmentEnd == pSegmentStart + 2
+ && pattern[pSegmentStart] == '*'
+ && pattern[pSegmentStart + 1] == '*'))) {
+
+ if (fSegmentStart >= fLength)
+ return false;
+ if (!CharOperation
+ .match(
+ pattern,
+ pSegmentStart,
+ pSegmentEnd,
+ filepath,
+ fSegmentStart,
+ fSegmentEnd,
+ isCaseSensitive)) {
+ return false;
+ }
+
+ // jump to next segment
+ pSegmentEnd =
+ CharOperation.indexOf(
+ pathSeparator,
+ pattern,
+ pSegmentStart = pSegmentEnd + 1);
+ // skip separator
+ if (pSegmentEnd < 0)
+ pSegmentEnd = pLength;
+
+ fSegmentEnd =
+ CharOperation.indexOf(
+ pathSeparator,
+ filepath,
+ fSegmentStart = fSegmentEnd + 1);
+ // skip separator
+ if (fSegmentEnd < 0) fSegmentEnd = fLength;
+ }
+
+ /* check sequence of doubleStar+segment */
+ int pSegmentRestart;
+ if ((pSegmentStart >= pLength && freeTrailingDoubleStar)
+ || (pSegmentEnd == pSegmentStart + 2
+ && pattern[pSegmentStart] == '*'
+ && pattern[pSegmentStart + 1] == '*')) {
+ pSegmentEnd =
+ CharOperation.indexOf(
+ pathSeparator,
+ pattern,
+ pSegmentStart = pSegmentEnd + 1);
+ // skip separator
+ if (pSegmentEnd < 0) pSegmentEnd = pLength;
+ pSegmentRestart = pSegmentStart;
+ } else {
+ if (pSegmentStart >= pLength) return fSegmentStart >= fLength; // true if filepath is done too.
+ pSegmentRestart = 0; // force fSegmentStart check
+ }
+ int fSegmentRestart = fSegmentStart;
+ checkSegment : while (fSegmentStart < fLength) {
+
+ if (pSegmentStart >= pLength) {
+ if (freeTrailingDoubleStar) return true;
+ // mismatch - restart current path segment
+ pSegmentEnd =
+ CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentRestart);
+ if (pSegmentEnd < 0) pSegmentEnd = pLength;
+
+ fSegmentRestart =
+ CharOperation.indexOf(pathSeparator, filepath, fSegmentRestart + 1);
+ // skip separator
+ if (fSegmentRestart < 0) {
+ fSegmentRestart = fLength;
+ } else {
+ fSegmentRestart++;
+ }
+ fSegmentEnd =
+ CharOperation.indexOf(pathSeparator, filepath, fSegmentStart = fSegmentRestart);
+ if (fSegmentEnd < 0) fSegmentEnd = fLength;
+ continue checkSegment;
+ }
+
+ /* path segment is ending */
+ if (pSegmentEnd == pSegmentStart + 2
+ && pattern[pSegmentStart] == '*'
+ && pattern[pSegmentStart + 1] == '*') {
+ pSegmentEnd =
+ CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentEnd + 1);
+ // skip separator
+ if (pSegmentEnd < 0) pSegmentEnd = pLength;
+ pSegmentRestart = pSegmentStart;
+ fSegmentRestart = fSegmentStart;
+ if (pSegmentStart >= pLength) return true;
+ continue checkSegment;
+ }
+ /* chech current path segment */
+ if (!CharOperation.match(
+ pattern,
+ pSegmentStart,
+ pSegmentEnd,
+ filepath,
+ fSegmentStart,
+ fSegmentEnd,
+ isCaseSensitive)) {
+ // mismatch - restart current path segment
+ pSegmentEnd =
+ CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentRestart);
+ if (pSegmentEnd < 0) pSegmentEnd = pLength;
+
+ fSegmentRestart =
+ CharOperation.indexOf(pathSeparator, filepath, fSegmentRestart + 1);
+ // skip separator
+ if (fSegmentRestart < 0) {
+ fSegmentRestart = fLength;
+ } else {
+ fSegmentRestart++;
+ }
+ fSegmentEnd =
+ CharOperation.indexOf(pathSeparator, filepath, fSegmentStart = fSegmentRestart);
+ if (fSegmentEnd < 0) fSegmentEnd = fLength;
+ continue checkSegment;
+ }
+ // jump to next segment
+ pSegmentEnd =
+ CharOperation.indexOf(
+ pathSeparator,
+ pattern,
+ pSegmentStart = pSegmentEnd + 1);
+ // skip separator
+ if (pSegmentEnd < 0)
+ pSegmentEnd = pLength;
+
+ fSegmentEnd =
+ CharOperation.indexOf(
+ pathSeparator,
+ filepath,
+ fSegmentStart = fSegmentEnd + 1);
+ // skip separator
+ if (fSegmentEnd < 0)
+ fSegmentEnd = fLength;
+ }
+
+ return (pSegmentRestart >= pSegmentEnd)
+ || (fSegmentStart >= fLength && pSegmentStart >= pLength)
+ || (pSegmentStart == pLength - 2
+ && pattern[pSegmentStart] == '*'
+ && pattern[pSegmentStart + 1] == '*')
+ || (pSegmentStart == pLength && freeTrailingDoubleStar);
+}
+
+/**
+ * Answers the number of occurrences of the given character in the given array, 0 if any.
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'b'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => 3
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => 0
+ *
+ *
+ *
+ *
+ * @param toBeFound the given character
+ * @param array the given array
+ * @return the number of occurrences of the given character in the given array, 0 if any
+ * @throws NullPointerException if array is null
+ */
+public static final int occurencesOf(char toBeFound, char[] array) {
+ int count = 0;
+ for (int i = 0; i < array.length; i++)
+ if (toBeFound == array[i])
+ count++;
+ return count;
+}
+
+/**
+ * Answers the number of occurrences of the given character in the given array starting
+ * at the given index, 0 if any.
+ *
+ *
+ *
+ * For example:
+ *
+ *
+ * toBeFound = 'b'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * start = 2
+ * result => 2
+ *
+ *
+ *
+ * toBeFound = 'c'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * start = 0
+ * result => 0
+ *
+ *
+ *
+ *
+ * @param toBeFound the given character
+ * @param array the given array
+ * @param start the given index
+ * @return the number of occurrences of the given character in the given array, 0 if any
+ * @throws NullPointerException if array is null
+ * @throws ArrayIndexOutOfBoundsException if start is lower than 0
+ */
+public static final int occurencesOf(
+ char toBeFound,
+ char[] array,
+ int start) {
+ int count = 0;
+ for (int i = start; i < array.length; i++)
+ if (toBeFound == array[i])
+ count++;
+ return count;
+}
+/**
+ * Return the int value represented by the designated subpart of array. The
+ * calculation of the result for single-digit positive integers is optimized in
+ * time.
+ * @param array the array within which the int value is to be parsed
+ * @param start first character of the int value in array
+ * @param length length of the int value in array
+ * @return the int value of a subpart of array
+ * @throws NumberFormatException if the designated subpart of array does not
+ * parse to an int
+ * @since 3.4
+ */
+public static final int parseInt(char[] array, int start, int length) throws NumberFormatException {
+ if (length == 1) {
+ int result = array[start] - '0';
+ if (result < 0 || result > 9) {
+ throw new NumberFormatException("invalid digit"); //$NON-NLS-1$
+ }
+ return result;
+ } else {
+ return Integer.parseInt(new String(array, start, length));
+ }
+}
+/**
+ * Answers true if the given name starts with the given prefix, false otherwise.
+ * The comparison is case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * prefix = { 'a' , 'b' }
+ * name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => true
+ *
+ *
+ *
+ * prefix = { 'a' , 'c' }
+ * name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => false
+ *
+ *
+ *
+ *
+ * @param prefix the given prefix
+ * @param name the given name
+ * @return true if the given name starts with the given prefix, false otherwise
+ * @throws NullPointerException if the given name is null or if the given prefix is null
+ */
+public static final boolean prefixEquals(char[] prefix, char[] name) {
+
+ int max = prefix.length;
+ if (name.length < max)
+ return false;
+ for (int i = max;
+ --i >= 0;
+ ) // assumes the prefix is not larger than the name
+ if (prefix[i] != name[i])
+ return false;
+ return true;
+}
+
+/**
+ * Answers true if the given name starts with the given prefix, false otherwise.
+ * isCaseSensitive is used to find out whether or not the comparison should be case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * prefix = { 'a' , 'B' }
+ * name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ * prefix = { 'a' , 'B' }
+ * name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ *
+ * @param prefix the given prefix
+ * @param name the given name
+ * @param isCaseSensitive to find out whether or not the comparison should be case sensitive
+ * @return true if the given name starts with the given prefix, false otherwise
+ * @throws NullPointerException if the given name is null or if the given prefix is null
+ */
+public static final boolean prefixEquals(
+ char[] prefix,
+ char[] name,
+ boolean isCaseSensitive) {
+ return prefixEquals(prefix, name, isCaseSensitive, 0);
+}
+
+/**
+ * Answers true if the given name, starting from the given index, starts with the given prefix,
+ * false otherwise. isCaseSensitive is used to find out whether or not the comparison should be
+ * case sensitive.
+ *
+ *
+ * For example:
+ *
+ *
+ * prefix = { 'a' , 'B' }
+ * name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * startIndex = 2
+ * isCaseSensitive = false
+ * result => true
+ *
+ *
+ *
+ * prefix = { 'a' , 'B' }
+ * name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * startIndex = 2
+ * isCaseSensitive = true
+ * result => false
+ *
+ *
+ *
+ *
+ * @param prefix the given prefix
+ * @param name the given name
+ * @param isCaseSensitive to find out whether or not the comparison should be case sensitive
+ * @param startIndex index from which the prefix should be searched in the name
+ * @return true if the given name starts with the given prefix, false otherwise
+ * @throws NullPointerException if the given name is null or if the given prefix is null
+ * @since 3.7
+ */
+public static final boolean prefixEquals(
+ char[] prefix,
+ char[] name,
+ boolean isCaseSensitive,
+ int startIndex) {
+
+ int max = prefix.length;
+ if (name.length - startIndex < max)
+ return false;
+ if (isCaseSensitive) {
+ for (int i = max; --i >= 0;) // assumes the prefix is not larger than the name
+ if (prefix[i] != name[startIndex + i])
+ return false;
+ return true;
+ }
+
+ for (int i = max; --i >= 0;) // assumes the prefix is not larger than the name
+ if (ScannerHelper.toLowerCase(prefix[i])
+ != ScannerHelper.toLowerCase(name[startIndex + i]))
+ return false;
+ return true;
+}
+
+/**
+ * Answers a new array removing a given character. Answers the given array if there is
+ * no occurrence of the character to remove.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'c', 'b', 'a' }
+ * toBeRemoved = 'b'
+ * return { 'a' , 'c', 'a' }
+ *
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeRemoved = 'c'
+ * return array
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeRemoved the character to be removed
+ * @return a new array removing given character
+ * @since 3.2
+ */
+public static final char[] remove(char[] array, char toBeRemoved) {
+
+ if (array == null) return null;
+ int length = array.length;
+ if (length == 0) return array;
+ char[] result = null;
+ int count = 0;
+ for (int i = 0; i < length; i++) {
+ char c = array[i];
+ if (c == toBeRemoved) {
+ if (result == null) {
+ result = new char[length];
+ System.arraycopy(array, 0, result, 0, i);
+ count = i;
+ }
+ } else if (result != null) {
+ result[count++] = c;
+ }
+ }
+ if (result == null) return array;
+ System.arraycopy(result, 0, result = new char[count], 0, count);
+ return result;
+}
+
+/**
+ * Replace all occurrence of the character to be replaced with the replacement character in the
+ * given array.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = 'b'
+ * replacementChar = 'a'
+ * result => No returned value, but array is now equals to { 'a' , 'a', 'a', 'a', 'a', 'a' }
+ *
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = 'c'
+ * replacementChar = 'a'
+ * result => No returned value, but array is now equals to { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeReplaced the character to be replaced
+ * @param replacementChar the replacement character
+ * @throws NullPointerException if the given array is null
+ */
+public static final void replace(
+ char[] array,
+ char toBeReplaced,
+ char replacementChar) {
+ if (toBeReplaced != replacementChar) {
+ for (int i = 0, max = array.length; i < max; i++) {
+ if (array[i] == toBeReplaced)
+ array[i] = replacementChar;
+ }
+ }
+}
+
+/**
+ * Replace all occurrences of characters to be replaced with the replacement character in the
+ * given array.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'c', 'a', 'b', 'c', 'a' }
+ * toBeReplaced = { 'b', 'c' }
+ * replacementChar = 'a'
+ * result => No returned value, but array is now equals to { 'a' , 'a', 'a', 'a', 'a', 'a', 'a', 'a' }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeReplaced characters to be replaced
+ * @param replacementChar the replacement character
+ * @throws NullPointerException if arrays are null.
+ * @since 3.1
+ */
+public static final void replace(char[] array, char[] toBeReplaced, char replacementChar) {
+ replace(array, toBeReplaced, replacementChar, 0, array.length);
+}
+
+/**
+ * Replace all occurrences of characters to be replaced with the replacement character in the
+ * given array from the start position (inclusive) to the end position (exclusive).
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'c', 'a', 'b', 'c', 'a' }
+ * toBeReplaced = { 'b', 'c' }
+ * replacementChar = 'a'
+ * start = 4
+ * end = 8
+ * result => No returned value, but array is now equals to { 'a' , 'b', 'b', 'c', 'a', 'a', 'a', 'a' }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeReplaced characters to be replaced
+ * @param replacementChar the replacement character
+ * @param start the given start position (inclusive)
+ * @param end the given end position (exclusive)
+ * @throws NullPointerException if arrays are null.
+ * @since 3.2
+ */
+public static final void replace(char[] array, char[] toBeReplaced, char replacementChar, int start, int end) {
+ for (int i = end; --i >= start;)
+ for (int j = toBeReplaced.length; --j >= 0;)
+ if (array[i] == toBeReplaced[j])
+ array[i] = replacementChar;
+}
+/**
+ * Answers a new array of characters with substitutions. No side-effect is operated on the original
+ * array, in case no substitution happened, then the result is the same as the
+ * original one.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = { 'b' }
+ * replacementChar = { 'a', 'a' }
+ * result => { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }
+ *
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = { 'c' }
+ * replacementChar = { 'a' }
+ * result => { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeReplaced characters to be replaced
+ * @param replacementChars the replacement characters
+ * @return a new array of characters with substitutions or the given array if none
+ * @throws NullPointerException if the given array is null
+ */
+public static final char[] replace(
+ char[] array,
+ char[] toBeReplaced,
+ char[] replacementChars) {
+
+ int max = array.length;
+ int replacedLength = toBeReplaced.length;
+ int replacementLength = replacementChars.length;
+
+ int[] starts = new int[5];
+ int occurrenceCount = 0;
+
+ if (!equals(toBeReplaced, replacementChars)) {
+
+ next : for (int i = 0; i < max;) {
+ int index = indexOf(toBeReplaced, array, true, i);
+ if (index == -1) {
+ i++;
+ continue next;
+ }
+ if (occurrenceCount == starts.length) {
+ System.arraycopy(
+ starts,
+ 0,
+ starts = new int[occurrenceCount * 2],
+ 0,
+ occurrenceCount);
+ }
+ starts[occurrenceCount++] = index;
+ i = index + replacedLength;
+ }
+ }
+ if (occurrenceCount == 0)
+ return array;
+ char[] result =
+ new char[max
+ + occurrenceCount * (replacementLength - replacedLength)];
+ int inStart = 0, outStart = 0;
+ for (int i = 0; i < occurrenceCount; i++) {
+ int offset = starts[i] - inStart;
+ System.arraycopy(array, inStart, result, outStart, offset);
+ inStart += offset;
+ outStart += offset;
+ System.arraycopy(
+ replacementChars,
+ 0,
+ result,
+ outStart,
+ replacementLength);
+ inStart += replacedLength;
+ outStart += replacementLength;
+ }
+ System.arraycopy(array, inStart, result, outStart, max - inStart);
+ return result;
+}
+
+/**
+ * Replace all occurrence of the character to be replaced with the replacement character
+ * in a copy of the given array. Returns the given array if no occurrences of the character
+ * to be replaced are found.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = 'b'
+ * replacementChar = 'a'
+ * result => A new array that is equals to { 'a' , 'a', 'a', 'a', 'a', 'a' }
+ *
+ *
+ *
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * toBeReplaced = 'c'
+ * replacementChar = 'a'
+ * result => The original array that remains unchanged.
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param toBeReplaced the character to be replaced
+ * @param replacementChar the replacement character
+ * @throws NullPointerException if the given array is null
+ * @since 3.1
+ */
+public static final char[] replaceOnCopy(
+ char[] array,
+ char toBeReplaced,
+ char replacementChar) {
+
+ char[] result = null;
+ for (int i = 0, length = array.length; i < length; i++) {
+ char c = array[i];
+ if (c == toBeReplaced) {
+ if (result == null) {
+ result = new char[length];
+ System.arraycopy(array, 0, result, 0, i);
+ }
+ result[i] = replacementChar;
+ } else if (result != null) {
+ result[i] = c;
+ }
+ }
+ if (result == null) return array;
+ return result;
+}
+
+/**
+ * Return a new array which is the split of the given array using the given divider and trimming each subarray to remove
+ * whitespaces equals to ' '.
+ *
+ *
+ * For example:
+ *
+ *
+ * divider = 'b'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => { { 'a' }, { }, { 'a' }, { 'a' } }
+ *
+ *
+ *
+ * divider = 'c'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
+ *
+ *
+ *
+ * divider = 'b'
+ * array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' }
+ * result => { { 'a' }, { }, { 'a' }, { 'a' } }
+ *
+ *
+ *
+ * divider = 'c'
+ * array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
+ * result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
+ *
+ *
+ *
+ *
+ * @param divider the given divider
+ * @param array the given array
+ * @return a new array which is the split of the given array using the given divider and trimming each subarray to remove
+ * whitespaces equals to ' '
+ */
+public static final char[][] splitAndTrimOn(char divider, char[] array) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return NO_CHAR_CHAR;
+
+ int wordCount = 1;
+ for (int i = 0; i < length; i++)
+ if (array[i] == divider)
+ wordCount++;
+ char[][] split = new char[wordCount][];
+ int last = 0, currentWord = 0;
+ for (int i = 0; i < length; i++) {
+ if (array[i] == divider) {
+ int start = last, end = i - 1;
+ while (start < i && array[start] == ' ')
+ start++;
+ while (end > start && array[end] == ' ')
+ end--;
+ split[currentWord] = new char[end - start + 1];
+ System.arraycopy(
+ array,
+ start,
+ split[currentWord++],
+ 0,
+ end - start + 1);
+ last = i + 1;
+ }
+ }
+ int start = last, end = length - 1;
+ while (start < length && array[start] == ' ')
+ start++;
+ while (end > start && array[end] == ' ')
+ end--;
+ split[currentWord] = new char[end - start + 1];
+ System.arraycopy(
+ array,
+ start,
+ split[currentWord++],
+ 0,
+ end - start + 1);
+ return split;
+}
+
+/**
+ * Return a new array which is the split of the given array using the given divider.
+ *
+ *
+ * For example:
+ *
+ *
+ * divider = 'b'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => { { 'a' }, { }, { 'a' }, { 'a' } }
+ *
+ *
+ *
+ * divider = 'c'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
+ *
+ *
+ *
+ * divider = 'c'
+ * array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
+ * result => { { ' ', 'a', 'b', 'b', 'a', 'b', 'a', ' ' } }
+ *
+ *
+ *
+ *
+ * @param divider the given divider
+ * @param array the given array
+ * @return a new array which is the split of the given array using the given divider
+ */
+public static final char[][] splitOn(char divider, char[] array) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0)
+ return NO_CHAR_CHAR;
+
+ int wordCount = 1;
+ for (int i = 0; i < length; i++)
+ if (array[i] == divider)
+ wordCount++;
+ char[][] split = new char[wordCount][];
+ int last = 0, currentWord = 0;
+ for (int i = 0; i < length; i++) {
+ if (array[i] == divider) {
+ split[currentWord] = new char[i - last];
+ System.arraycopy(
+ array,
+ last,
+ split[currentWord++],
+ 0,
+ i - last);
+ last = i + 1;
+ }
+ }
+ split[currentWord] = new char[length - last];
+ System.arraycopy(array, last, split[currentWord], 0, length - last);
+ return split;
+}
+
+/**
+ * Return a new array which is the split of the given array using the given divider. The given end
+ * is exclusive and the given start is inclusive.
+ *
+ *
+ * For example:
+ *
+ *
+ * divider = 'b'
+ * array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
+ * start = 2
+ * end = 5
+ * result => { { }, { 'a' }, { } }
+ *
+ *
+ *
+ *
+ * @param divider the given divider
+ * @param array the given array
+ * @param start the given starting index
+ * @param end the given ending index
+ * @return a new array which is the split of the given array using the given divider
+ * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length
+ */
+public static final char[][] splitOn(
+ char divider,
+ char[] array,
+ int start,
+ int end) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0 || start > end)
+ return NO_CHAR_CHAR;
+
+ int wordCount = 1;
+ for (int i = start; i < end; i++)
+ if (array[i] == divider)
+ wordCount++;
+ char[][] split = new char[wordCount][];
+ int last = start, currentWord = 0;
+ for (int i = start; i < end; i++) {
+ if (array[i] == divider) {
+ split[currentWord] = new char[i - last];
+ System.arraycopy(
+ array,
+ last,
+ split[currentWord++],
+ 0,
+ i - last);
+ last = i + 1;
+ }
+ }
+ split[currentWord] = new char[end - last];
+ System.arraycopy(array, last, split[currentWord], 0, end - last);
+ return split;
+}
+
+/**
+ * Return a new array which is the split of the given array using the given divider ignoring the
+ * text between (possibly nested) openEncl and closingEncl. If there are no openEncl in the code
+ * this is identical to {@link CharOperation#splitOn(char, char[], int, int)}. The given end
+ * is exclusive and the given start is inclusive.
+ *
+ *
+ * For example:
+ *
+ *
+ * divider = ','
+ * array = { 'A' , '<', 'B', ',', 'C', '>', ',', 'D' }
+ * start = 0
+ * end = 8
+ * result => { { 'A' , '<', 'B', ',', 'C', '>'}, { 'D' }}
+ *
+ *
+ *
+ *
+ * @param divider the given divider
+ * @param openEncl the opening enclosure
+ * @param closeEncl the closing enclosure
+ * @param array the given array
+ * @param start the given starting index
+ * @param end the given ending index
+ * @return a new array which is the split of the given array using the given divider
+ * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length
+ * @since 3.12
+ */
+public static final char[][] splitOnWithEnclosures(
+ char divider,
+ char openEncl,
+ char closeEncl,
+ char[] array,
+ int start,
+ int end) {
+ int length = array == null ? 0 : array.length;
+ if (length == 0 || start > end)
+ return NO_CHAR_CHAR;
+
+ int wordCount = 1;
+ int enclCount = 0;
+ for (int i = start; i < end; i++) {
+ if (array[i] == openEncl)
+ enclCount++;
+ else if (array[i] == divider)
+ wordCount++;
+ }
+ if (enclCount == 0)
+ return CharOperation.splitOn(divider, array, start, end);
+
+ int nesting = 0;
+ if (openEncl == divider || closeEncl == divider) // divider should be distinct
+ return CharOperation.NO_CHAR_CHAR;
+
+ int[][] splitOffsets = new int[wordCount][2]; //maximum
+ int last = start, currentWord = 0, prevOffset = start;
+ for (int i = start; i < end; i++) {
+ if (array[i] == openEncl) {
+ ++nesting;
+ continue;
+ }
+ if (array[i] == closeEncl) {
+ if (nesting > 0)
+ --nesting;
+ continue;
+ }
+ if (array[i] == divider && nesting == 0) {
+ splitOffsets[currentWord][0] = prevOffset;
+ last = splitOffsets[currentWord++][1] = i;
+ prevOffset = last + 1;
+ }
+ }
+ if (last < end - 1) {
+ splitOffsets[currentWord][0] = prevOffset;
+ splitOffsets[currentWord++][1] = end;
+ }
+ char[][] split = new char[currentWord][];
+ for (int i = 0; i < currentWord; ++i) {
+ int sStart = splitOffsets[i][0];
+ int sEnd = splitOffsets[i][1];
+ int size = sEnd - sStart;
+ split[i] = new char[size];
+ System.arraycopy(array, sStart, split[i], 0, size);
+ }
+ return split;
+ }
+
+/**
+ * Answers a new array which is a copy of the given array starting at the given start and
+ * ending at the given end. The given start is inclusive and the given end is exclusive.
+ * Answers null if start is greater than end, if start is lower than 0 or if end is greater
+ * than the length of the given array. If end equals -1, it is converted to the array length.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { { 'a' } , { 'b' } }
+ * start = 0
+ * end = 1
+ * result => { { 'a' } }
+ *
+ *
+ *
+ * array = { { 'a' } , { 'b' } }
+ * start = 0
+ * end = -1
+ * result => { { 'a' }, { 'b' } }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param start the given starting index
+ * @param end the given ending index
+ * @return a new array which is a copy of the given array starting at the given start and
+ * ending at the given end
+ * @throws NullPointerException if the given array is null
+ */
+public static final char[][] subarray(char[][] array, int start, int end) {
+ if (end == -1)
+ end = array.length;
+ if (start > end)
+ return null;
+ if (start < 0)
+ return null;
+ if (end > array.length)
+ return null;
+
+ char[][] result = new char[end - start][];
+ System.arraycopy(array, start, result, 0, end - start);
+ return result;
+}
+
+/**
+ * Answers a new array which is a copy of the given array starting at the given start and
+ * ending at the given end. The given start is inclusive and the given end is exclusive.
+ * Answers null if start is greater than end, if start is lower than 0 or if end is greater
+ * than the length of the given array. If end equals -1, it is converted to the array length.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { 'a' , 'b' }
+ * start = 0
+ * end = 1
+ * result => { 'a' }
+ *
+ *
+ *
+ * array = { 'a', 'b' }
+ * start = 0
+ * end = -1
+ * result => { 'a' , 'b' }
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @param start the given starting index
+ * @param end the given ending index
+ * @return a new array which is a copy of the given array starting at the given start and
+ * ending at the given end
+ * @throws NullPointerException if the given array is null
+ */
+public static final char[] subarray(char[] array, int start, int end) {
+ if (end == -1)
+ end = array.length;
+ if (start > end)
+ return null;
+ if (start < 0)
+ return null;
+ if (end > array.length)
+ return null;
+
+ char[] result = new char[end - start];
+ System.arraycopy(array, start, result, 0, end - start);
+ return result;
+}
+
+/**
+ * Answers the result of a char[] conversion to lowercase. Answers null if the given chars array is null.
+ *
+ * NOTE: If no conversion was necessary, then answers back the argument one.
+ *
+ *
+ * For example:
+ *
+ *
+ * chars = { 'a' , 'b' }
+ * result => { 'a' , 'b' }
+ *
+ *
+ *
+ * array = { 'A', 'b' }
+ * result => { 'a' , 'b' }
+ *
+ *
+ *
+ *
+ * @param chars the chars to convert
+ * @return the result of a char[] conversion to lowercase
+ */
+final static public char[] toLowerCase(char[] chars) {
+ if (chars == null)
+ return null;
+ int length = chars.length;
+ char[] lowerChars = null;
+ for (int i = 0; i < length; i++) {
+ char c = chars[i];
+ char lc = ScannerHelper.toLowerCase(c);
+ if ((c != lc) || (lowerChars != null)) {
+ if (lowerChars == null) {
+ System.arraycopy(
+ chars,
+ 0,
+ lowerChars = new char[length],
+ 0,
+ i);
+ }
+ lowerChars[i] = lc;
+ }
+ }
+ return lowerChars == null ? chars : lowerChars;
+}
+
+/**
+ * Answers the result of a char[] conversion to uppercase. Answers null if the given chars array is null.
+ *
+ * NOTE: If no conversion was necessary, then answers back the argument one.
+ *
+ *
+ * For example:
+ *
+ *
+ * chars = { 'A' , 'B' }
+ * result => { 'A' , 'B' }
+ *
+ *
+ *
+ * array = { 'a', 'B' }
+ * result => { 'A' , 'B' }
+ *
+ *
+ *
+ *
+ * @param chars the chars to convert
+ * @return the result of a char[] conversion to uppercase
+ *
+ * @since 3.5
+ */
+final static public char[] toUpperCase(char[] chars) {
+ if (chars == null)
+ return null;
+ int length = chars.length;
+ char[] upperChars = null;
+ for (int i = 0; i < length; i++) {
+ char c = chars[i];
+ char lc = ScannerHelper.toUpperCase(c);
+ if ((c != lc) || (upperChars != null)) {
+ if (upperChars == null) {
+ System.arraycopy(
+ chars,
+ 0,
+ upperChars = new char[length],
+ 0,
+ i);
+ }
+ upperChars[i] = lc;
+ }
+ }
+ return upperChars == null ? chars : upperChars;
+}
+
+/**
+ * Answers a new array removing leading and trailing spaces (' '). Answers the given array if there is no
+ * space characters to remove.
+ *
+ *
+ * For example:
+ *
+ *
+ * chars = { ' ', 'a' , 'b', ' ', ' ' }
+ * result => { 'a' , 'b' }
+ *
+ *
+ *
+ * array = { 'A', 'b' }
+ * result => { 'A' , 'b' }
+ *
+ *
+ *
+ *
+ * @param chars the given array
+ * @return a new array removing leading and trailing spaces (' ')
+ */
+final static public char[] trim(char[] chars) {
+
+ if (chars == null)
+ return null;
+
+ int start = 0, length = chars.length, end = length - 1;
+ while (start < length && chars[start] == ' ') {
+ start++;
+ }
+ while (end > start && chars[end] == ' ') {
+ end--;
+ }
+ if (start != 0 || end != length - 1) {
+ return subarray(chars, start, end + 1);
+ }
+ return chars;
+}
+
+/**
+ * Answers a string which is the concatenation of the given array using the '.' as a separator.
+ *
+ *
+ * For example:
+ *
+ *
+ * array = { { 'a' } , { 'b' } }
+ * result => "a.b"
+ *
+ *
+ *
+ * array = { { ' ', 'a' } , { 'b' } }
+ * result => " a.b"
+ *
+ *
+ *
+ *
+ * @param array the given array
+ * @return a string which is the concatenation of the given array using the '.' as a separator
+ */
+final static public String toString(char[][] array) {
+ char[] result = concatWith(array, '.');
+ return new String(result);
+}
+
+/**
+ * Answers an array of strings from the given array of char array.
+ *
+ * @param array the given array
+ * @return an array of strings
+ * @since 3.0
+ */
+final static public String[] toStrings(char[][] array) {
+ if (array == null) return NO_STRINGS;
+ int length = array.length;
+ if (length == 0) return NO_STRINGS;
+ String[] result = new String[length];
+ for (int i = 0; i < length; i++)
+ result[i] = new String(array[i]);
+ return result;
+}
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java
new file mode 100644
index 000000000..c51fc3672
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java
@@ -0,0 +1,158 @@
+package org.springframework.ide.vscode.commons.javadoc.internal;
+
+/**
+ * Hashtable of {Object --> int[] }
+ */
+public final class HashtableOfObjectToIntArray implements Cloneable {
+
+ // to avoid using Enumerations, walk the individual tables skipping nulls
+ public Object[] keyTable;
+ public int[][] valueTable;
+
+ public int elementSize; // number of elements in the table
+ int threshold;
+
+ public HashtableOfObjectToIntArray() {
+ this(13);
+ }
+
+ public HashtableOfObjectToIntArray(int size) {
+
+ this.elementSize = 0;
+ this.threshold = size; // size represents the expected number of elements
+ int extraRoom = (int) (size * 1.75f);
+ if (this.threshold == extraRoom)
+ extraRoom++;
+ this.keyTable = new Object[extraRoom];
+ this.valueTable = new int[extraRoom][];
+ }
+
+ public Object clone() throws CloneNotSupportedException {
+ HashtableOfObjectToIntArray result = (HashtableOfObjectToIntArray) super.clone();
+ result.elementSize = this.elementSize;
+ result.threshold = this.threshold;
+
+ int length = this.keyTable.length;
+ result.keyTable = new Object[length];
+ System.arraycopy(this.keyTable, 0, result.keyTable, 0, length);
+
+ length = this.valueTable.length;
+ result.valueTable = new int[length][];
+ System.arraycopy(this.valueTable, 0, result.valueTable, 0, length);
+ return result;
+ }
+
+ public boolean containsKey(Object key) {
+ int length = this.keyTable.length,
+ index = (key.hashCode()& 0x7FFFFFFF) % length;
+ Object currentKey;
+ while ((currentKey = this.keyTable[index]) != null) {
+ if (currentKey.equals(key))
+ return true;
+ if (++index == length) {
+ index = 0;
+ }
+ }
+ return false;
+ }
+
+ public int[] get(Object key) {
+ int length = this.keyTable.length,
+ index = (key.hashCode()& 0x7FFFFFFF) % length;
+ Object currentKey;
+ while ((currentKey = this.keyTable[index]) != null) {
+ if (currentKey.equals(key))
+ return this.valueTable[index];
+ if (++index == length) {
+ index = 0;
+ }
+ }
+ return null;
+ }
+
+ public void keysToArray(Object[] array) {
+ int index = 0;
+ for (int i=0, length=this.keyTable.length; i this.threshold)
+ rehash();
+ return value;
+ }
+
+ public int[] removeKey(Object key) {
+ int length = this.keyTable.length,
+ index = (key.hashCode()& 0x7FFFFFFF) % length;
+ Object currentKey;
+ while ((currentKey = this.keyTable[index]) != null) {
+ if (currentKey.equals(key)) {
+ int[] value = this.valueTable[index];
+ this.elementSize--;
+ this.keyTable[index] = null;
+ rehash();
+ return value;
+ }
+ if (++index == length) {
+ index = 0;
+ }
+ }
+ return null;
+ }
+
+ private void rehash() {
+
+ HashtableOfObjectToIntArray newHashtable = new HashtableOfObjectToIntArray(this.elementSize * 2); // double the number of expected elements
+ Object currentKey;
+ for (int i = this.keyTable.length; --i >= 0;)
+ if ((currentKey = this.keyTable[i]) != null)
+ newHashtable.put(currentKey, this.valueTable[i]);
+
+ this.keyTable = newHashtable.keyTable;
+ this.valueTable = newHashtable.valueTable;
+ this.threshold = newHashtable.threshold;
+ }
+
+ public int size() {
+ return this.elementSize;
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ Object key;
+ for (int i = 0, length = this.keyTable.length; i < length; i++) {
+ if ((key = this.keyTable[i]) != null) {
+ buffer.append(key).append(" -> "); //$NON-NLS-1$
+ int[] ints = this.valueTable[i];
+ buffer.append('[');
+ if (ints != null) {
+ for (int j = 0, max = ints.length; j < max; j++) {
+ if (j > 0) {
+ buffer.append(',');
+ }
+ buffer.append(ints[j]);
+ }
+ }
+ buffer.append("]\n"); //$NON-NLS-1$
+ }
+ }
+ return String.valueOf(buffer);
+ }
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java
new file mode 100644
index 000000000..01ca87833
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java
@@ -0,0 +1,35 @@
+package org.springframework.ide.vscode.commons.javadoc.internal;
+
+public interface JavadocConstants {
+
+ String ANCHOR_PREFIX_END = "\""; //$NON-NLS-1$
+ char[] ANCHOR_PREFIX_START = ""; //$NON-NLS-1$
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java
new file mode 100644
index 000000000..8c4d920f9
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java
@@ -0,0 +1,539 @@
+package org.springframework.ide.vscode.commons.javadoc.internal;
+
+import java.util.stream.Collectors;
+
+import org.springframework.ide.vscode.commons.java.IField;
+import org.springframework.ide.vscode.commons.java.IMethod;
+import org.springframework.ide.vscode.commons.java.IType;
+
+public class JavadocContents {
+ private static final int[] UNKNOWN_FORMAT = new int[0];
+
+// private IType type;
+ private char[] content;
+
+ private int childrenStart;
+
+ private boolean hasComputedChildrenSections = false;
+ private int indexOfFieldDetails;
+ private int indexOfConstructorDetails;
+ private int indexOfMethodDetails;
+ private int indexOfEndOfClassData;
+
+ private int indexOfFieldsBottom;
+ private int indexOfAllMethodsTop;
+ private int indexOfAllMethodsBottom;
+
+ private int[] typeDocRange;
+ private HashtableOfObjectToIntArray fieldDocRanges;
+ private HashtableOfObjectToIntArray methodDocRanges;
+
+ private int[] fieldAnchorIndexes;
+ private int fieldAnchorIndexesCount;
+ private int fieldLastAnchorFoundIndex;
+ private int[] methodAnchorIndexes;
+ private int methodAnchorIndexesCount;
+ private int methodLastAnchorFoundIndex;
+ private int[] unknownFormatAnchorIndexes;
+ private int unknownFormatAnchorIndexesCount;
+ private int unknownFormatLastAnchorFoundIndex;
+ private int[] tempAnchorIndexes;
+ private int tempAnchorIndexesCount;
+ private int tempLastAnchorFoundIndex;
+
+// public JavadocContents(IType type, String content) {
+// this(content);
+// this.type = type;
+// }
+
+ public JavadocContents(String content) {
+ this.content = content != null ? content.toCharArray() : null;
+ }
+ /*
+ * Returns the part of the javadoc that describe the type
+ */
+ public String getTypeDoc(IType type) throws Exception {
+ if (this.content == null) return null;
+
+ synchronized (this) {
+ if (this.typeDocRange == null) {
+ computeTypeRange(type);
+ }
+ }
+
+ if (this.typeDocRange != null) {
+ if (this.typeDocRange == UNKNOWN_FORMAT) throw new Exception("Unknown javadoc format " + type);
+ return String.valueOf(CharOperation.subarray(this.content, this.typeDocRange[0], this.typeDocRange[1]));
+ }
+ return null;
+ }
+
+// public String getPackageDoc() throws JavaModelException {
+// if (this.content == null) return null;
+// int[] range = null;
+// int index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START2, this.content, false, 0);
+// if (index == -1) {
+// index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START, this.content, false, 0);
+// }
+// if (index == -1) return null;
+// index = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, index);
+// if (index == -1) return null;
+//
+// int start = CharOperation.indexOf(JavadocConstants.H2_PREFIX, this.content, false, index);
+// if (start != -1) {
+// start = CharOperation.indexOf(JavadocConstants.H2_SUFFIX, this.content, false, start);
+// if (start != -1) index = start + JavadocConstants.H2_SUFFIX_LENGTH;
+// }
+// if (index != -1) {
+// int end = CharOperation.indexOf(JavadocConstants.BOTTOM_NAVBAR, this.content, false, index);
+// if (end == -1) end = this.content.length -1;
+// range = new int[]{index, end};
+// return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
+// }
+// return null;
+// }
+
+ /*
+ * Returns the part of the javadoc that describe a field of the type
+ */
+ public String getFieldDoc(IField child) throws Exception {
+ if (this.content == null) return null;
+
+ int[] range = null;
+ synchronized (this) {
+ if (this.fieldDocRanges == null) {
+ this.fieldDocRanges = new HashtableOfObjectToIntArray();
+ } else {
+ range = this.fieldDocRanges.get(child);
+ }
+
+ if (range == null) {
+ range = computeFieldRange(child);
+ this.fieldDocRanges.put(child, range);
+ }
+ }
+
+ if (range != null) {
+ if (range == UNKNOWN_FORMAT) throw new Exception("Unknown javadoc format " + child);
+ return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
+ }
+ return null;
+ }
+
+ /*
+ * Returns the part of the javadoc that describe a method of the type
+ */
+ public String getMethodDoc(IMethod method) throws Exception {
+ if (this.content == null) return null;
+
+ int[] range = null;
+ synchronized (this) {
+ if (this.methodDocRanges == null) {
+ this.methodDocRanges = new HashtableOfObjectToIntArray();
+ } else {
+ range = this.methodDocRanges.get(method);
+ }
+
+ if (range == null) {
+ range = computeMethodRange(method);
+ this.methodDocRanges.put(method, range);
+ }
+ }
+
+ if (range != null) {
+ if (range == UNKNOWN_FORMAT) {
+ throw new Exception("Unknown javadoc format " + method);
+ }
+ return String.valueOf(CharOperation.subarray(this.content, range[0], range[1]));
+ }
+ return null;
+ }
+
+ /*
+ * Compute the ranges of the parts of the javadoc that describe each method of the type
+ */
+ private int[] computeChildRange(char[] anchor, int indexOfSectionBottom) throws Exception {
+
+ // checks each known anchor locations
+ if (this.tempAnchorIndexesCount > 0) {
+ for (int i = 0; i < this.tempAnchorIndexesCount; i++) {
+ int anchorEndStart = this.tempAnchorIndexes[i];
+
+ if (anchorEndStart != -1 && CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) {
+
+ this.tempAnchorIndexes[i] = -1;
+
+ return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom);
+ }
+ }
+ }
+
+ int fromIndex = this.tempLastAnchorFoundIndex;
+ int index;
+
+ // check each next unknown anchor locations
+ while ((index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex)) != -1 && (index < indexOfSectionBottom || indexOfSectionBottom == -1)) {
+ fromIndex = index + 1;
+
+ int anchorEndStart = index + JavadocConstants.ANCHOR_PREFIX_START_LENGHT;
+
+ this.tempLastAnchorFoundIndex = anchorEndStart;
+
+ if (CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) {
+ return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom);
+ } else {
+ if (this.tempAnchorIndexes.length == this.tempAnchorIndexesCount) {
+ System.arraycopy(this.tempAnchorIndexes, 0, this.tempAnchorIndexes = new int[this.tempAnchorIndexesCount + 20], 0, this.tempAnchorIndexesCount);
+ }
+
+ this.tempAnchorIndexes[this.tempAnchorIndexesCount++] = anchorEndStart;
+ }
+ }
+
+ return null;
+ }
+
+ private int[] computeChildRange(int anchorEndStart, char[] anchor, int indexOfBottom) {
+ int[] range = null;
+
+ // try to find the bottom of the section
+ if (indexOfBottom != -1) {
+ // try to find the end of the anchor
+ int indexOfEndLink = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, anchorEndStart + anchor.length);
+ if (indexOfEndLink != -1) {
+ // try to find the next anchor
+ int indexOfNextElement = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, indexOfEndLink);
+
+ int javadocStart = indexOfEndLink + JavadocConstants.ANCHOR_SUFFIX_LENGTH;
+ int javadocEnd = indexOfNextElement == -1 ? indexOfBottom : Math.min(indexOfNextElement, indexOfBottom);
+ range = new int[]{javadocStart, javadocEnd};
+ } else {
+ // the anchor has no suffix
+ range = UNKNOWN_FORMAT;
+ }
+ } else {
+ // the detail section has no bottom
+ range = UNKNOWN_FORMAT;
+ }
+
+ return range;
+ }
+
+ private void computeChildrenSections() {
+ // try to find the next separator part
+ int lastIndex = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false, this.childrenStart);
+ lastIndex = lastIndex == -1 ? this.childrenStart : lastIndex;
+
+ // try to find field detail start
+ this.indexOfFieldDetails = CharOperation.indexOf(JavadocConstants.FIELD_DETAIL, this.content, false, lastIndex);
+ lastIndex = this.indexOfFieldDetails == -1 ? lastIndex : this.indexOfFieldDetails;
+
+ // try to find constructor detail start
+ this.indexOfConstructorDetails = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_DETAIL, this.content, false, lastIndex);
+ lastIndex = this.indexOfConstructorDetails == -1 ? lastIndex : this.indexOfConstructorDetails;
+
+ // try to find method detail start
+ this.indexOfMethodDetails = CharOperation.indexOf(JavadocConstants.METHOD_DETAIL, this.content, false, lastIndex);
+ lastIndex = this.indexOfMethodDetails == -1 ? lastIndex : this.indexOfMethodDetails;
+
+ // we take the end of class data
+ this.indexOfEndOfClassData = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false, lastIndex);
+
+ // try to find the field detail end
+ this.indexOfFieldsBottom =
+ this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails :
+ this.indexOfMethodDetails != -1 ? this.indexOfMethodDetails:
+ this.indexOfEndOfClassData;
+
+ this.indexOfAllMethodsTop =
+ this.indexOfConstructorDetails != -1 ?
+ this.indexOfConstructorDetails :
+ this.indexOfMethodDetails;
+
+ this.indexOfAllMethodsBottom = this.indexOfEndOfClassData;
+
+ this.hasComputedChildrenSections = true;
+ }
+
+ /*
+ * Compute the ranges of the parts of the javadoc that describe each child of the type (fields, methods)
+ */
+ private int[] computeFieldRange(IField field) throws Exception {
+ IType type = field.getDeclaringType();
+
+ if (!this.hasComputedChildrenSections) {
+ computeChildrenSections();
+ }
+
+ StringBuffer buffer = new StringBuffer(field.getElementName());
+ buffer.append(JavadocConstants.ANCHOR_PREFIX_END);
+ char[] anchor = String.valueOf(buffer).toCharArray();
+
+ int[] range = null;
+
+ if (this.indexOfFieldDetails == -1 || this.indexOfFieldsBottom == -1) {
+ // the detail section has no top or bottom, so the doc has an unknown format
+ if (this.unknownFormatAnchorIndexes == null) {
+ this.unknownFormatAnchorIndexes = new int[(int)type.getFields().count()];
+ this.unknownFormatAnchorIndexesCount = 0;
+ this.unknownFormatLastAnchorFoundIndex = this.childrenStart;
+ }
+
+ this.tempAnchorIndexes = this.unknownFormatAnchorIndexes;
+ this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount;
+ this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex;
+
+ range = computeChildRange(anchor, this.indexOfFieldsBottom);
+
+ this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
+ this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount;
+ this.unknownFormatAnchorIndexes = this.tempAnchorIndexes;
+ } else {
+ if (this.fieldAnchorIndexes == null) {
+ this.fieldAnchorIndexes = new int[(int)type.getFields().count()];
+ this.fieldAnchorIndexesCount = 0;
+ this.fieldLastAnchorFoundIndex = this.indexOfFieldDetails;
+ }
+
+ this.tempAnchorIndexes = this.fieldAnchorIndexes;
+ this.tempAnchorIndexesCount = this.fieldAnchorIndexesCount;
+ this.tempLastAnchorFoundIndex = this.fieldLastAnchorFoundIndex;
+
+ range = computeChildRange(anchor, this.indexOfFieldsBottom);
+
+ this.fieldLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
+ this.fieldAnchorIndexesCount = this.tempAnchorIndexesCount;
+ this.fieldAnchorIndexes = this.tempAnchorIndexes;
+ }
+
+ return range;
+ }
+
+ /*
+ * Compute the ranges of the parts of the javadoc that describe each method of the type
+ */
+ private int[] computeMethodRange(IMethod method) throws Exception {
+ IType type = method.getDeclaringType();
+
+ if (!this.hasComputedChildrenSections) {
+ computeChildrenSections();
+ }
+
+ char[] anchor = computeMethodAnchorPrefixEnd(method).toCharArray();
+
+ int[] range = null;
+
+
+ if (this.indexOfAllMethodsTop == -1 || this.indexOfAllMethodsBottom == -1) {
+ // the detail section has no top or bottom, so the doc has an unknown format
+ if (this.unknownFormatAnchorIndexes == null) {
+ int childernCount = (int)(type.getMethods().count() + type.getFields().count());
+ this.unknownFormatAnchorIndexes = new int[childernCount];
+ this.unknownFormatAnchorIndexesCount = 0;
+ this.unknownFormatLastAnchorFoundIndex = this.childrenStart;
+ }
+
+ this.tempAnchorIndexes = this.unknownFormatAnchorIndexes;
+ this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount;
+ this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex;
+
+ range = computeChildRange(anchor, this.indexOfFieldsBottom);
+ if (range == null) {
+ range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom);
+ }
+
+ this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
+ this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount;
+ this.unknownFormatAnchorIndexes = this.tempAnchorIndexes;
+ } else {
+ if (this.methodAnchorIndexes == null) {
+ this.methodAnchorIndexes = new int[(int)type.getMethods().count()];
+ this.methodAnchorIndexesCount = 0;
+ this.methodLastAnchorFoundIndex = this.indexOfAllMethodsTop;
+ }
+
+ this.tempAnchorIndexes = this.methodAnchorIndexes;
+ this.tempAnchorIndexesCount = this.methodAnchorIndexesCount;
+ this.tempLastAnchorFoundIndex = this.methodLastAnchorFoundIndex;
+
+ range = computeChildRange(anchor, this.indexOfAllMethodsBottom);
+ if (range == null) {
+ range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom);
+ }
+
+ this.methodLastAnchorFoundIndex = this.tempLastAnchorFoundIndex;
+ this.methodAnchorIndexesCount = this.tempAnchorIndexesCount;
+ this.methodAnchorIndexes = this.tempAnchorIndexes;
+ }
+
+ return range;
+ }
+
+ private static char[] getJavadoc8Anchor(char[] anchor) {
+ // fix for bug 432284: [1.8] Javadoc-8-style anchors not found by IMethod#getAttachedJavadoc(..)
+ char[] anchor8 = new char[anchor.length];
+ int i8 = 0;
+ for (int i = 0; i < anchor.length; i++) {
+ char ch = anchor[i];
+ switch (ch) {
+ case '(':
+ case ')':
+ case ',':
+ anchor8[i8++] = '-';
+ break;
+ case '[':
+ anchor8[i8++] = ':';
+ anchor8[i8++] = 'A';
+ break;
+ case ' ': // handled by preceding ','
+ case ']': // handled by preceding '['
+ break;
+ default:
+ anchor8[i8++] = ch;
+ }
+ }
+ if (i8 != anchor.length) {
+ anchor8 = CharOperation.subarray(anchor8, 0, i8);
+ }
+ return anchor8;
+ }
+
+ private String computeMethodAnchorPrefixEnd(IMethod method) throws Exception {
+// String typeQualifiedName = null;
+// if (this.type.isMember()) {
+// IType currentType = this.type;
+// StringBuffer buffer = new StringBuffer();
+// while (currentType != null) {
+// buffer.insert(0, currentType.getElementName());
+// currentType = currentType.getDeclaringType();
+// if (currentType != null) {
+// buffer.insert(0, '.');
+// }
+// }
+// typeQualifiedName = new String(buffer.toString());
+// } else {
+// typeQualifiedName = this.type.getElementName();
+// }
+
+// String methodName = method.getElementName();
+// if (method.getElementName().equals(method.getDeclaringType().getElementName())) {
+// methodName = typeQualifiedName;
+// }
+
+ String anchor = createMethodAnchor(method);
+
+// char[] genericSignature = info.getGenericSignature();
+// if (genericSignature != null) {
+// genericSignature = CharOperation.replaceOnCopy(genericSignature, '/', '.');
+// anchor = Util.toAnchor(0, genericSignature, methodName, Flags.isVarargs(method.getFlags()));
+// if (anchor == null) throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, method));
+// } else {
+// anchor = Signature.toString(method.getSignature().replace('/', '.'), methodName, null, true, false, Flags.isVarargs(method.getFlags()));
+// }
+
+// IType declaringType = /*this.type*/method.getDeclaringType();
+// if (declaringType.isMember()) {
+// // might need to remove a part of the signature corresponding to the synthetic argument (only for constructor)
+// if (method.getElementName().equals(method.getDeclaringType().getElementName()) && !Flags.isStatic(declaringType.getFlags())) {
+// int indexOfOpeningParen = anchor.indexOf('(');
+// if (indexOfOpeningParen == -1) {
+// // should not happen as this is a method signature
+// return null;
+// }
+// int index = indexOfOpeningParen;
+// indexOfOpeningParen++;
+// int indexOfComma = anchor.indexOf(',', index);
+// if (indexOfComma != -1) {
+// index = indexOfComma + 2;
+// } else {
+// // no argument, but a synthetic argument
+// index = anchor.indexOf(')', index);
+// }
+// anchor = anchor.substring(0, indexOfOpeningParen) + anchor.substring(index);
+// }
+// }
+ return anchor + JavadocConstants.ANCHOR_PREFIX_END;
+ }
+
+ private String createMethodAnchor(IMethod method) {
+ StringBuilder sb = new StringBuilder(method.getElementName());
+ sb.append('(');
+ sb.append(String.join(",", method.parameters().map(p -> p.toString()).collect(Collectors.toList())));
+ sb.append(')');
+ return sb.toString();
+ }
+
+ /*
+ * Compute the range of the part of the javadoc that describe the type
+ */
+ private void computeTypeRange(IType type) throws Exception {
+ final int indexOfStartOfClassData = CharOperation.indexOf(JavadocConstants.START_OF_CLASS_DATA, this.content, false);
+ if (indexOfStartOfClassData == -1) {
+ this.typeDocRange = UNKNOWN_FORMAT;
+ return;
+ }
+ int indexOfNextSeparator = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false, indexOfStartOfClassData);
+ if (indexOfNextSeparator == -1) {
+ this.typeDocRange = UNKNOWN_FORMAT;
+ return;
+ }
+ int indexOfNextSummary = CharOperation.indexOf(JavadocConstants.NESTED_CLASS_SUMMARY, this.content, false, indexOfNextSeparator);
+ if (indexOfNextSummary == -1 && type.isEnum()) {
+ // try to find enum constant summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ENUM_CONSTANT_SUMMARY, this.content, false, indexOfNextSeparator);
+ }
+ if (indexOfNextSummary == -1 && type.isAnnotation()) {
+ // try to find required enum constant summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY, this.content, false, indexOfNextSeparator);
+ if (indexOfNextSummary == -1) {
+ // try to find optional enum constant summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY, this.content, false, indexOfNextSeparator);
+ }
+ }
+ if (indexOfNextSummary == -1) {
+ // try to find field summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.FIELD_SUMMARY, this.content, false, indexOfNextSeparator);
+ }
+ if (indexOfNextSummary == -1) {
+ // try to find constructor summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_SUMMARY, this.content, false, indexOfNextSeparator);
+ }
+ if (indexOfNextSummary == -1) {
+ // try to find method summary start
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.METHOD_SUMMARY, this.content, false, indexOfNextSeparator);
+ }
+
+ if (indexOfNextSummary == -1) {
+ // we take the end of class data
+ indexOfNextSummary = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false, indexOfNextSeparator);
+ } else {
+ // improve performance of computation of children ranges
+ this.childrenStart = indexOfNextSummary + 1;
+ }
+
+ if (indexOfNextSummary == -1) {
+ this.typeDocRange = UNKNOWN_FORMAT;
+ return;
+ }
+ /*
+ * Cut off the type hierarchy, see bug 119844.
+ * We remove the contents between the start of class data and where
+ * we guess the actual class comment starts.
+ */
+ int start = indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA_LENGTH;
+ int indexOfFirstParagraph = CharOperation.indexOf(JavadocConstants.P.toCharArray(), this.content, false, start, indexOfNextSummary);
+ int indexOfFirstDiv = CharOperation.indexOf(JavadocConstants.DIV_CLASS_BLOCK.toCharArray(), this.content, false, start, indexOfNextSummary);
+ int afterHierarchy = indexOfNextSummary;
+ if (indexOfFirstParagraph != -1 && indexOfFirstParagraph < afterHierarchy) {
+ afterHierarchy = indexOfFirstParagraph;
+ }
+ if (indexOfFirstDiv != -1 && indexOfFirstDiv < afterHierarchy) {
+ afterHierarchy = indexOfFirstDiv;
+ }
+ if (afterHierarchy != indexOfNextSummary) {
+ start = afterHierarchy;
+ }
+
+ this.typeDocRange = new int[]{start, indexOfNextSummary};
+ }
+}
diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java
new file mode 100644
index 000000000..c1cb3cc11
--- /dev/null
+++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java
@@ -0,0 +1,569 @@
+package org.springframework.ide.vscode.commons.javadoc.internal;
+
+public class ScannerHelper {
+
+ // storage for internal flags (32 bits) BIT USAGE
+ public final static int Bit1 = 0x1; // return type (operator) | name reference kind (name ref) | add assertion (type decl) | useful empty statement (empty statement)
+ public final static int Bit2 = 0x2; // return type (operator) | name reference kind (name ref) | has local type (type, method, field decl) | if type elided (local)
+ public final static int Bit3 = 0x4; // return type (operator) | name reference kind (name ref) | implicit this (this ref) | is argument(local)
+ public final static int Bit4 = 0x8; // return type (operator) | first assignment to local (name ref,local decl) | undocumented empty block (block, type and method decl)
+ public final static int Bit5 = 0x10; // value for return (expression) | has all method bodies (unit) | supertype ref (type ref) | resolved (field decl)
+ public final static int Bit6 = 0x20; // depth (name ref, msg) | ignore need cast check (cast expression) | error in signature (method declaration/ initializer) | is recovered (annotation reference)
+ public final static int Bit7 = 0x40; // depth (name ref, msg) | operator (operator) | need runtime checkcast (cast expression) | label used (labelStatement) | needFreeReturn (AbstractMethodDeclaration)
+ public final static int Bit8 = 0x80; // depth (name ref, msg) | operator (operator) | unsafe cast (cast expression) | is default constructor (constructor declaration) | isElseStatementUnreachable (if statement)
+ public final static int Bit9 = 0x100; // depth (name ref, msg) | operator (operator) | is local type (type decl) | isThenStatementUnreachable (if statement) | can be static
+ public final static int Bit10= 0x200; // depth (name ref, msg) | operator (operator) | is anonymous type (type decl)
+ public final static int Bit11 = 0x400; // depth (name ref, msg) | operator (operator) | is member type (type decl)
+ public final static int Bit12 = 0x800; // depth (name ref, msg) | operator (operator) | has abstract methods (type decl)
+ public final static int Bit13 = 0x1000; // depth (name ref, msg) | is secondary type (type decl)
+ public final static int Bit14 = 0x2000; // strictly assigned (reference lhs) | discard enclosing instance (explicit constr call) | hasBeenGenerated (type decl)
+ public final static int Bit15 = 0x4000; // is unnecessary cast (expression) | is varargs (type ref) | isSubRoutineEscaping (try statement) | superAccess (javadoc allocation expression/javadoc message send/javadoc return statement)
+ public final static int Bit16 = 0x8000; // in javadoc comment (name ref, type ref, msg)
+ public final static int Bit17 = 0x10000; // compound assigned (reference lhs) | unchecked (msg, alloc, explicit constr call)
+ public final static int Bit18 = 0x20000; // non null (expression) | onDemand (import reference)
+ public final static int Bit19 = 0x40000; // didResolve (parameterized qualified type ref/parameterized single type ref) | empty (javadoc return statement) | needReceiverGenericCast (msg/fieldref)
+ public final static int Bit20 = 0x80000; // contains syntax errors (method declaration, type declaration, field declarations, initializer), typeref: <> name ref: lambda capture)
+ public final static int Bit21 = 0x100000;
+ public final static int Bit22 = 0x200000; // parenthesis count (expression) | used (import reference) shadows outer local (local declarations)
+ public final static int Bit23 = 0x400000; // parenthesis count (expression)
+ public final static int Bit24 = 0x800000; // parenthesis count (expression)
+ public final static int Bit25 = 0x1000000; // parenthesis count (expression)
+ public final static int Bit26 = 0x2000000; // parenthesis count (expression)
+ public final static int Bit27 = 0x4000000; // parenthesis count (expression)
+ public final static int Bit28 = 0x8000000; // parenthesis count (expression)
+ public final static int Bit29 = 0x10000000; // parenthesis count (expression)
+ public final static int Bit30 = 0x20000000; // elseif (if statement) | try block exit (try statement) | fall-through (case statement) | ignore no effect assign (expression ref) | needScope (for statement) | isAnySubRoutineEscaping (return statement) | blockExit (synchronized statement)
+ public final static int Bit31 = 0x40000000; // local declaration reachable (local decl) | ignore raw type check (type ref) | discard entire assignment (assignment) | isSynchronized (return statement) | thenExit (if statement)
+ public final static int Bit32 = 0x80000000; // reachable (statement)
+
+// public final static long[] Bits = {
+// ASTNode.Bit1, ASTNode.Bit2, ASTNode.Bit3, ASTNode.Bit4, ASTNode.Bit5, ASTNode.Bit6,
+// ASTNode.Bit7, ASTNode.Bit8, ASTNode.Bit9, ASTNode.Bit10, ASTNode.Bit11, ASTNode.Bit12,
+// ASTNode.Bit13, ASTNode.Bit14, ASTNode.Bit15, ASTNode.Bit16, ASTNode.Bit17, ASTNode.Bit18,
+// ASTNode.Bit19, ASTNode.Bit20, ASTNode.Bit21, ASTNode.Bit22, ASTNode.Bit23, ASTNode.Bit24,
+// ASTNode.Bit25, ASTNode.Bit26, ASTNode.Bit27, ASTNode.Bit28, ASTNode.Bit29, ASTNode.Bit30,
+// ASTNode.Bit31, ASTNode.Bit32L, ASTNode.Bit33L, ASTNode.Bit34L, ASTNode.Bit35L, ASTNode.Bit36L,
+// ASTNode.Bit37L, ASTNode.Bit38L, ASTNode.Bit39L, ASTNode.Bit40L, ASTNode.Bit41L, ASTNode.Bit42L,
+// ASTNode.Bit43L, ASTNode.Bit44L, ASTNode.Bit45L, ASTNode.Bit46L, ASTNode.Bit47L, ASTNode.Bit48L,
+// ASTNode.Bit49L, ASTNode.Bit50L, ASTNode.Bit51L, ASTNode.Bit52L, ASTNode.Bit53L, ASTNode.Bit54L,
+// ASTNode.Bit55L, ASTNode.Bit56L, ASTNode.Bit57L, ASTNode.Bit58L, ASTNode.Bit59L, ASTNode.Bit60L,
+// ASTNode.Bit61L, ASTNode.Bit62L, ASTNode.Bit63L, ASTNode.Bit64L,
+// };
+//
+// private static final int START_INDEX = 0;
+// private static final int PART_INDEX = 1;
+//
+// private static long[][][] Tables;
+// private static long[][][] Tables7;
+// private static long[][][] Tables8;
+
+ public final static int MAX_OBVIOUS = 128;
+ public final static int[] OBVIOUS_IDENT_CHAR_NATURES = new int[MAX_OBVIOUS];
+
+ public final static int C_JLS_SPACE = Bit9;
+ public final static int C_SPECIAL = Bit8;
+ public final static int C_IDENT_START = Bit7;
+ public final static int C_UPPER_LETTER = Bit6;
+ public final static int C_LOWER_LETTER = Bit5;
+ public final static int C_IDENT_PART = Bit4;
+ public final static int C_DIGIT = Bit3;
+ public final static int C_SEPARATOR = Bit2;
+ public final static int C_SPACE = Bit1;
+
+ static {
+ OBVIOUS_IDENT_CHAR_NATURES[0] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[1] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[2] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[3] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[4] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[5] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[6] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[7] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[8] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[14] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[15] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[16] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[17] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[18] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[19] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[20] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[21] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[22] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[23] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[24] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[25] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[26] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[27] = C_IDENT_PART;
+ OBVIOUS_IDENT_CHAR_NATURES[127] = C_IDENT_PART;
+
+ for (int i = '0'; i <= '9'; i++)
+ OBVIOUS_IDENT_CHAR_NATURES[i] = C_DIGIT | C_IDENT_PART;
+
+ for (int i = 'a'; i <= 'z'; i++)
+ OBVIOUS_IDENT_CHAR_NATURES[i] = C_LOWER_LETTER | C_IDENT_PART | C_IDENT_START;
+ for (int i = 'A'; i <= 'Z'; i++)
+ OBVIOUS_IDENT_CHAR_NATURES[i] = C_UPPER_LETTER | C_IDENT_PART | C_IDENT_START;
+
+ OBVIOUS_IDENT_CHAR_NATURES['_'] = C_SPECIAL | C_IDENT_PART | C_IDENT_START;
+ OBVIOUS_IDENT_CHAR_NATURES['$'] = C_SPECIAL | C_IDENT_PART | C_IDENT_START;
+
+ OBVIOUS_IDENT_CHAR_NATURES[9] = C_SPACE | C_JLS_SPACE; // \ u0009: HORIZONTAL TABULATION
+ OBVIOUS_IDENT_CHAR_NATURES[10] = C_SPACE | C_JLS_SPACE; // \ u000a: LINE FEED
+ OBVIOUS_IDENT_CHAR_NATURES[11] = C_SPACE;
+ OBVIOUS_IDENT_CHAR_NATURES[12] = C_SPACE | C_JLS_SPACE; // \ u000c: FORM FEED
+ OBVIOUS_IDENT_CHAR_NATURES[13] = C_SPACE | C_JLS_SPACE; // \ u000d: CARRIAGE RETURN
+ OBVIOUS_IDENT_CHAR_NATURES[28] = C_SPACE;
+ OBVIOUS_IDENT_CHAR_NATURES[29] = C_SPACE;
+ OBVIOUS_IDENT_CHAR_NATURES[30] = C_SPACE;
+ OBVIOUS_IDENT_CHAR_NATURES[31] = C_SPACE;
+ OBVIOUS_IDENT_CHAR_NATURES[32] = C_SPACE | C_JLS_SPACE; // \ u0020: SPACE
+
+ OBVIOUS_IDENT_CHAR_NATURES['.'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES[':'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES[';'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES[','] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['['] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES[']'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['('] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES[')'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['{'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['}'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['+'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['-'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['*'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['/'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['='] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['&'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['|'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['?'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['<'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['>'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['!'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['%'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['^'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['~'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['"'] = C_SEPARATOR;
+ OBVIOUS_IDENT_CHAR_NATURES['\''] = C_SEPARATOR;
+ }
+//static void initializeTable() {
+// Tables = initializeTables("unicode"); //$NON-NLS-1$
+//}
+//static void initializeTable17() {
+// Tables7 = initializeTables("unicode6"); //$NON-NLS-1$
+//}
+//static void initializeTable18() {
+// Tables8 = initializeTables("unicode6_2"); //$NON-NLS-1$
+//}
+//static long[][][] initializeTables(String unicode_path) {
+// long[][][] tempTable = new long[2][][];
+// tempTable[START_INDEX] = new long[3][];
+// tempTable[PART_INDEX] = new long[4][];
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start0.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[START_INDEX][0] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start1.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[START_INDEX][1] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start2.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[START_INDEX][2] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part0.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[PART_INDEX][0] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part1.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[PART_INDEX][1] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part2.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[PART_INDEX][2] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// try {
+// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part14.rsc"))); //$NON-NLS-1$
+// long[] readValues = new long[1024];
+// for (int i = 0; i < 1024; i++) {
+// readValues[i] = inputStream.readLong();
+// }
+// inputStream.close();
+// tempTable[PART_INDEX][3] = readValues;
+// } catch (FileNotFoundException e) {
+// e.printStackTrace();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// return tempTable;
+//}
+//private final static boolean isBitSet(long[] values, int i) {
+// try {
+// return (values[i / 64] & Bits[i % 64]) != 0;
+// } catch (NullPointerException e) {
+// return false;
+// }
+//}
+//public static boolean isJavaIdentifierPart(char c) {
+// if (c < MAX_OBVIOUS) {
+// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_PART) != 0;
+// }
+// return Character.isJavaIdentifierPart(c);
+//}
+///**
+// * @param complianceLevel
+// * @param c
+// * @return
+// */
+//public static boolean isJavaIdentifierPart(long complianceLevel, char c) {
+// if (c < MAX_OBVIOUS) {
+// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_PART) != 0;
+// }
+// return isJavaIdentifierPart(complianceLevel, (int) c);
+//}
+///**
+// * @param complianceLevel
+// * @param codePoint
+// * @return
+// */
+//public static boolean isJavaIdentifierPart(long complianceLevel, int codePoint) {
+// if (complianceLevel <= ClassFileConstants.JDK1_6) {
+// if (Tables == null) {
+// initializeTable();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables[PART_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables[PART_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables[PART_INDEX][2], codePoint & 0xFFFF);
+// case 14 :
+// return isBitSet(Tables[PART_INDEX][3], codePoint & 0xFFFF);
+// }
+// } else if (complianceLevel <= ClassFileConstants.JDK1_7) {
+// // java 7 supports Unicode 6
+// if (Tables7 == null) {
+// initializeTable17();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables7[PART_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables7[PART_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables7[PART_INDEX][2], codePoint & 0xFFFF);
+// case 14 :
+// return isBitSet(Tables7[PART_INDEX][3], codePoint & 0xFFFF);
+// }
+// } else {
+// // java 7 supports Unicode 6.2
+// if (Tables8 == null) {
+// initializeTable18();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables8[PART_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables8[PART_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables8[PART_INDEX][2], codePoint & 0xFFFF);
+// case 14 :
+// return isBitSet(Tables8[PART_INDEX][3], codePoint & 0xFFFF);
+// }
+// }
+// return false;
+//}
+///**
+// * @param complianceLevel
+// * @param high
+// * @param low
+// * @return
+// */
+//public static boolean isJavaIdentifierPart(long complianceLevel, char high, char low) {
+// return isJavaIdentifierPart(complianceLevel, toCodePoint(high, low));
+//}
+///**
+// * @param c
+// * @return
+// */
+//public static boolean isJavaIdentifierStart(char c) {
+// if (c < MAX_OBVIOUS) {
+// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_START) != 0;
+// }
+// return Character.isJavaIdentifierStart(c);
+//}
+///**
+// * @param complianceLevel
+// * @param c
+// * @return
+// */
+//public static boolean isJavaIdentifierStart(long complianceLevel, char c) {
+// if (c < MAX_OBVIOUS) {
+// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_START) != 0;
+// }
+// return ScannerHelper.isJavaIdentifierStart(complianceLevel, (int) c);
+//}
+///**
+// * @param complianceLevel
+// * @param high
+// * @param low
+// * @return
+// */
+//public static boolean isJavaIdentifierStart(long complianceLevel, char high, char low) {
+// return isJavaIdentifierStart(complianceLevel, toCodePoint(high, low));
+//}
+///**
+// * @param complianceLevel
+// * @param codePoint
+// * @return
+// */
+//public static boolean isJavaIdentifierStart(long complianceLevel, int codePoint) {
+// if (complianceLevel <= ClassFileConstants.JDK1_6) {
+// if (Tables == null) {
+// initializeTable();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables[START_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables[START_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables[START_INDEX][2], codePoint & 0xFFFF);
+// }
+// } else if (complianceLevel <= ClassFileConstants.JDK1_7) {
+// // java 7 supports Unicode 6
+// if (Tables7 == null) {
+// initializeTable17();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables7[START_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables7[START_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables7[START_INDEX][2], codePoint & 0xFFFF);
+// }
+// } else {
+// // java 7 supports Unicode 6
+// if (Tables8 == null) {
+// initializeTable18();
+// }
+// switch((codePoint & 0x1F0000) >> 16) {
+// case 0 :
+// return isBitSet(Tables8[START_INDEX][0], codePoint & 0xFFFF);
+// case 1 :
+// return isBitSet(Tables8[START_INDEX][1], codePoint & 0xFFFF);
+// case 2 :
+// return isBitSet(Tables8[START_INDEX][2], codePoint & 0xFFFF);
+// }
+// }
+// return false;
+//}
+//private static int toCodePoint(char high, char low) {
+// return (high - Scanner.HIGH_SURROGATE_MIN_VALUE) * 0x400 + (low - Scanner.LOW_SURROGATE_MIN_VALUE) + 0x10000;
+//}
+//public static boolean isDigit(char c) throws InvalidInputException {
+// if(c < ScannerHelper.MAX_OBVIOUS) {
+// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_DIGIT) != 0;
+// }
+// if (Character.isDigit(c)) {
+// throw new InvalidStateException(Scanner.INVALID_DIGIT);
+// }
+// return false;
+//}
+public static int digit(char c, int radix) {
+ if (c < ScannerHelper.MAX_OBVIOUS) {
+ switch(radix) {
+ case 8 :
+ if (c >= 48 && c <= 55) {
+ return c - 48;
+ }
+ return -1;
+ case 10 :
+ if (c >= 48 && c <= 57) {
+ return c - 48;
+ }
+ return -1;
+ case 16 :
+ if (c >= 48 && c <= 57) {
+ return c - 48;
+ }
+ if (c >= 65 && c <= 70) {
+ return c - 65 + 10;
+ }
+ if (c >= 97 && c <= 102) {
+ return c - 97 + 10;
+ }
+ return -1;
+ }
+ }
+ return Character.digit(c, radix);
+}
+public static int getNumericValue(char c) {
+ if (c < ScannerHelper.MAX_OBVIOUS) {
+ switch(ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c]) {
+ case C_DIGIT :
+ return c - '0';
+ case C_LOWER_LETTER :
+ return 10 + c - 'a';
+ case C_UPPER_LETTER :
+ return 10 + c - 'A';
+ }
+ }
+ return Character.getNumericValue(c);
+}
+public static int getHexadecimalValue(char c) {
+ switch(c) {
+ case '0' :
+ return 0;
+ case '1' :
+ return 1;
+ case '2' :
+ return 2;
+ case '3' :
+ return 3;
+ case '4' :
+ return 4;
+ case '5' :
+ return 5;
+ case '6' :
+ return 6;
+ case '7' :
+ return 7;
+ case '8' :
+ return 8;
+ case '9' :
+ return 9;
+ case 'A' :
+ case 'a' :
+ return 10;
+ case 'B' :
+ case 'b' :
+ return 11;
+ case 'C' :
+ case 'c' :
+ return 12;
+ case 'D' :
+ case 'd' :
+ return 13;
+ case 'E' :
+ case 'e' :
+ return 14;
+ case 'F' :
+ case 'f' :
+ return 15;
+ default:
+ return -1;
+ }
+}
+public static char toUpperCase(char c) {
+ if (c < MAX_OBVIOUS) {
+ if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0) {
+ return c;
+ } else if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0) {
+ return (char) (c - 32);
+ }
+ }
+ return Character.toUpperCase(c);
+}
+public static char toLowerCase(char c) {
+ if (c < MAX_OBVIOUS) {
+ if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0) {
+ return c;
+ } else if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0) {
+ return (char) (32 + c);
+ }
+ }
+ return Character.toLowerCase(c);
+}
+public static boolean isLowerCase(char c) {
+ if (c < MAX_OBVIOUS) {
+ return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0;
+ }
+ return Character.isLowerCase(c);
+}
+public static boolean isUpperCase(char c) {
+ if (c < MAX_OBVIOUS) {
+ return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0;
+ }
+ return Character.isUpperCase(c);
+}
+/**
+ * Include also non JLS whitespaces.
+ *
+ * return true if Character.isWhitespace(c) would return true
+ */
+public static boolean isWhitespace(char c) {
+ if (c < MAX_OBVIOUS) {
+ return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_SPACE) != 0;
+ }
+ return Character.isWhitespace(c);
+}
+public static boolean isLetter(char c) {
+ if (c < MAX_OBVIOUS) {
+ return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_LOWER_LETTER)) != 0;
+ }
+ return Character.isLetter(c);
+}
+public static boolean isLetterOrDigit(char c) {
+ if (c < MAX_OBVIOUS) {
+ return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_DIGIT)) != 0;
+ }
+ return Character.isLetterOrDigit(c);
+}
+}
diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java
index 54aebce25..e55ed3966 100644
--- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java
+++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java
@@ -15,6 +15,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -49,6 +51,8 @@ import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor;
import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor;
import org.springframework.ide.vscode.commons.jandex.JandexIndex;
+import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider;
+import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.ExternalProcess;
import org.springframework.ide.vscode.commons.util.Log;
@@ -82,7 +86,22 @@ public class MavenCore {
private Supplier javaCoreIndex = Suppliers.memoize(() -> {
try {
- return new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), null);
+ return new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), (classpathResource) -> {
+ try {
+ String javaVersion = "8";
+ try {
+ String fullVersion = getJavaRuntimeVersion();
+ javaVersion = fullVersion.substring(fullVersion.indexOf('.') + 1, fullVersion.lastIndexOf('.'));
+ } catch (MavenException e) {
+ Log.log("Cannot determine Java runtime version. Defaulting to version 8", e);
+ }
+ URL javadocUrl = new URL("http://docs.oracle.com/javase/" + javaVersion + "/docs/api/");
+ return new HtmlJavadocProvider((type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER.sourceUrl(javadocUrl, type));
+ } catch (MalformedURLException e) {
+ Log.log(e);
+ return null;
+ }
+ });
} catch (MavenException e) {
return null;
}
@@ -115,17 +134,34 @@ public class MavenCore {
* @param Path of the project
* @throws Exception
*/
- public static void buildMavenProject(Path testProjectPath) throws Exception {
+ public static void buildMavenProject(Path projectPath) throws Exception {
Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win")
- ? testProjectPath.resolve("mvnw.cmd") : testProjectPath.resolve("mvnw");
+ ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw");
mvnwPath.toFile().setExecutable(true);
- ExternalProcess process = new ExternalProcess(testProjectPath.toFile(),
+ ExternalProcess process = new ExternalProcess(projectPath.toFile(),
new ExternalCommand(mvnwPath.toAbsolutePath().toString(), "clean", "package", "-DskipTests"), true);
if (process.getExitValue() != 0) {
throw new RuntimeException("Failed to build test project");
}
}
+ /**
+ * Generate javadoc jar for maven project
+ *
+ * @param Path of the project
+ * @throws Exception
+ */
+ public static void generateJavadocFolderForMavenProject(Path projectPath) throws Exception {
+ Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win")
+ ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw");
+ mvnwPath.toFile().setExecutable(true);
+ ExternalProcess process = new ExternalProcess(projectPath.toFile(),
+ new ExternalCommand(mvnwPath.toAbsolutePath().toString(), "javadoc:javadoc"), true);
+ if (process.getExitValue() != 0) {
+ throw new RuntimeException("Failed to build test project");
+ }
+ }
+
/**
* Creates Maven Project descriptor based on the pom file.
*
diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java
index 2d2e2e9a1..92a7259bc 100644
--- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java
+++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java
@@ -27,6 +27,7 @@ import org.springframework.ide.vscode.commons.java.IJavadocProvider;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.java.parser.ParserJavadocProvider;
import org.springframework.ide.vscode.commons.java.roaster.RoasterJavadocProvider;
+import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider;
import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer;
import org.springframework.ide.vscode.commons.maven.MavenCore;
import org.springframework.ide.vscode.commons.maven.MavenException;
@@ -43,8 +44,14 @@ import com.google.common.base.Suppliers;
*/
public class MavenProjectClasspath implements IClasspath {
- public static boolean USE_JAVA_PARSER = false;
-
+ public static JavadocProviderTypes providerType = JavadocProviderTypes.JAVA_PARSER;
+
+ public enum JavadocProviderTypes {
+ JAVA_PARSER,
+ ROASTER,
+ HTML
+ }
+
private MavenCore maven;
private MavenProject project;
private Supplier javaIndex;
@@ -64,7 +71,14 @@ public class MavenProjectClasspath implements IClasspath {
Log.log(e);
}
return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> {
- return USE_JAVA_PARSER ? createParserJavadocProvider(classpathResource) : createRoasterJavadocProvider(classpathResource);
+ switch (providerType) {
+ case JAVA_PARSER:
+ return createParserJavadocProvider(classpathResource);
+ case ROASTER:
+ return createRoasterJavadocProvider(classpathResource);
+ default:
+ return createHtmlJavdocProvider(classpathResource);
+ }
}, maven.getJavaIndexForJreLibs());
});
}
@@ -105,7 +119,7 @@ public class MavenProjectClasspath implements IClasspath {
return Arrays.stream(scanner.getIncludedFiles());
});
}
-
+
private IJavadocProvider createRoasterJavadocProvider(File classpathResource) {
if (classpathResource.isDirectory()) {
if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) {
@@ -174,4 +188,38 @@ public class MavenProjectClasspath implements IClasspath {
}
}
+ private IJavadocProvider createHtmlJavdocProvider(File classpathResource) {
+ if (classpathResource.isDirectory()) {
+ if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) {
+ return new HtmlJavadocProvider(type -> {
+ return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
+ .sourceUrl(new File(project.getModel().getReporting().getOutputDirectory(), "apidocs").toURI().toURL(), type);
+ });
+ } else if (classpathResource.toString().startsWith(project.getBuild().getTestOutputDirectory())) {
+ return new ParserJavadocProvider(type -> {
+ return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER
+ .sourceUrl(new File(project.getModel().getReporting().getOutputDirectory(), "apidocs").toURI().toURL(), type);
+ });
+ } else {
+ throw new IllegalArgumentException("Cannot find source folder for " + classpathResource);
+ }
+ } else {
+ // Assume it's a JAR file
+ return new HtmlJavadocProvider(type -> {
+ try {
+ Artifact artifact = getArtifactFromJarFile(classpathResource).get();
+ URL sourceContainer = maven.getJavadoc(artifact).getFile().toURI().toURL();
+ return SourceUrlProviderFromSourceContainer.JAR_JAVADOC_URL_PROVIDER.sourceUrl(sourceContainer,
+ type);
+ } catch (MavenException e) {
+ Log.log("Failed to find sources JAR for " + classpathResource, e);
+ } catch (MalformedURLException e) {
+ Log.log("Invalid URL for sources JAR for " + classpathResource, e);
+ }
+ return null;
+ });
+ }
+ }
+
+
}
diff --git a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java
index b2aaa00e7..7bd17c687 100644
--- a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java
+++ b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java
@@ -3,6 +3,7 @@ package org.springframework.ide.vscode.commons.maven;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -18,6 +19,7 @@ import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.java.IVoidType;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
import org.springframework.ide.vscode.commons.maven.java.MavenProjectClasspath;
+import org.springframework.ide.vscode.commons.maven.java.MavenProjectClasspath.JavadocProviderTypes;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
@@ -89,7 +91,7 @@ public class JavaIndexTest {
IType type = project.findType("java.util.ArrayList");
assertNotNull(type);
IMethod m = type.getMethod("", Stream.empty());
- assertEquals("", m.getElementName());
+ assertEquals(type.getElementName(), m.getElementName());
assertEquals(IVoidType.DEFAULT, m.getReturnType());
assertEquals(0, m.parameters().count());
}
@@ -100,14 +102,14 @@ public class JavaIndexTest {
IType type = project.findType("java.util.ArrayList");
assertNotNull(type);
IMethod m = type.getMethod("", Stream.of(IPrimitiveType.INT));
- assertEquals("", m.getElementName());
+ assertEquals(m.getDeclaringType().getElementName(), m.getElementName());
assertEquals(IVoidType.DEFAULT, m.getReturnType());
assertEquals(Collections.singletonList(IPrimitiveType.INT), m.parameters().collect(Collectors.toList()));
}
@Test
public void parser_testClassJavadocForOutputFolder() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = true;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("hello.Greeting");
@@ -140,7 +142,7 @@ public class JavaIndexTest {
@Test
public void parser_testInnerClassJavadocForOutputFolder() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = true;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("hello.Greeting$TestInnerClass");
assertNotNull(type);
@@ -157,7 +159,7 @@ public class JavaIndexTest {
@Test
public void parser_testClassJavadocForJar() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = true;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener");
@@ -180,7 +182,7 @@ public class JavaIndexTest {
@Test
public void parser_testFieldAndMethodJavadocForJar() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = true;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("org.springframework.boot.SpringApplication");
@@ -206,7 +208,7 @@ public class JavaIndexTest {
@Test
public void roaster_testClassJavadocForOutputFolder() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = false;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("hello.Greeting");
@@ -224,7 +226,7 @@ public class JavaIndexTest {
@Test
public void roaster_testInnerClassJavadocForOutputFolder() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = false;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER;
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("hello.Greeting$TestInnerClass");
assertNotNull(type);
@@ -241,7 +243,8 @@ public class JavaIndexTest {
@Test
public void roaster_testClassJavadocForJar() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = false;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER;
+
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener");
@@ -256,7 +259,8 @@ public class JavaIndexTest {
@Test
public void roaster_testFieldAndMethodJavadocForJar() throws Exception {
- MavenProjectClasspath.USE_JAVA_PARSER = false;
+ MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER;
+
MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
IType type = project.findType("org.springframework.boot.SpringApplication");
@@ -273,4 +277,134 @@ public class JavaIndexTest {
}
+ @Test
+ public void html_testClassJavadoc() throws Exception {
+ MavenProjectClasspath.providerType = JavadocProviderTypes.HTML;
+
+ MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
+
+ IType type = project.findType("java.util.Map");
+ assertNotNull(type);
+ String expected = String.join("\n",
+ "An object that maps keys to values. A map cannot contain duplicate keys;",
+ " each key can map to at most one value."
+ );
+ assertEquals(expected, type.getJavaDoc().html().substring(0, expected.length()));
+ }
+
+ @Test
+ public void html_testNestedClassJavadoc() throws Exception {
+ MavenProjectClasspath.providerType = JavadocProviderTypes.HTML;
+
+ MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
+
+ IType type = project.findType("java.util.Map$Entry");
+ assertNotNull(type);
+ String expected = String.join("\n",
+ "
A map entry (key-value pair). The
Map.entrySet method returns",
+ " a collection-view of the map, whose elements are of this class. The");
+ assertEquals(expected, type.getJavaDoc().html().substring(0, expected.length()));
+ }
+
+ @Test
+ public void html_testMethodJavadoc() throws Exception {
+ MavenProjectClasspath.providerType = JavadocProviderTypes.HTML;
+
+ MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"));
+
+ IType type = project.findType("java.util.ArrayList");
+ assertNotNull(type);
+ IMethod method = type.getMethod("size", Stream.empty());
+ assertNotNull(method);
+
+ String expected = String.join("\n",
+ "",
+ "