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:
+ *
    + *
  1. + *    array = { 'a', 'b' }
    + *    suffix = 'c'
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    suffix = 'c'
    + *    => result = { 'c' }
    + * 
  4. + *
+ * + * @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:
+ *
    + *
  1. + *    array = { 'a', 'b' }
    + *    suffix = { 'c', 'd' }
    + *    => result = { 'a', 'b' , 'c' , d' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    suffix = { 'c' }
    + *    => result = { 'c' }
    + * 
  4. + *
+ * + * @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:
+ *
    + *
  1. + *    target = { 'a', 'b', '0' }
    + *    index = 2
    + *    array = { 'c', 'd' }
    + *    start = 0
    + *    end = 1
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    target = { 'a', 'b' }
    + *    index = 2
    + *    array = { 'c', 'd' }
    + *    start = 0
    + *    end = 1
    + *    => result = { 'a', 'b' , 'c', '0', '0' , '0' } (new array)
    + * 
  4. + *
  5. + *    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)
    + * 
  6. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    => result = null
    + * 
    + *
  2. + *
  3. + *    first = { { ' a' } }
    + *    second = null
    + *    => result = { { ' a' } }
    + * 
    + *
  4. + *
  5. + *    first = null
    + *    second = { { ' a' } }
    + *    => result = { { ' a' } }
    + * 
    + *
  6. + *
  7. + *    first = { { ' b' } }
    + *    second = { { ' a' } }
    + *    => result = { { ' b' }, { ' a' } }
    + * 
    + *
  8. + *
+ * + * @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:
    + *
  1. pattern = "NPE".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * name = "NullPointerException".toCharArray() + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * name = "IPerspectiveListener3".toCharArray() + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * name = "HashMapEntry".toCharArray() + * result => true
  14. + *
+ * + * @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:
    + *
  1. pattern = "NPE".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * name = "NullPointerException".toCharArray() + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * name = "IPerspectiveListener3".toCharArray() + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * name = "HashMapEntry".toCharArray() + * result => (samePartCount == false)
  14. + *
+ * + * @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: + *

    + *
  1. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * patternStart = 0 + * patternEnd = 4 + * name = "IPerspectiveListener3".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * patternStart = 0 + * patternEnd = 2 + * name = "HashMapEntry".toCharArray() + * nameStart = 0 + * nameEnd = 12 + * result => true
  14. + *
+ * + * @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:
+ * 
    + *
  1. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * patternStart = 0 + * patternEnd = 4 + * name = "IPerspectiveListener3".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * patternStart = 0 + * patternEnd = 2 + * name = "HashMapEntry".toCharArray() + * nameStart = 0 + * nameEnd = 12 + * result => (samePartCount == false)
  14. + *
+ *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    => result = { { ' a' } }
    + * 
    + *
  2. + *    first = { { ' a' } }
    + *    second = null
    + *    => result = { { ' a' } }
    + * 
    + *
  3. + *
  4. + *    first = { { ' a' } }
    + *    second = { ' b' }
    + *    => result = { { ' a' } , { ' b' } }
    + * 
    + *
  5. + *
+ * + * @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: + *

    + *
  1. + *    array = null
    + *    prefix = null
    + *    => result = NullPointerException
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'b', 'c', 'd', 'e' }
    + *    prefix = { 'a', 'b', 'c'}
    + *    => result = 0
    + * 
    + *
  4. + *
  5. + *    array = { 'a', 'b', 'c', 'd', 'e' }
    + *    prefix = { 'a', 'B', 'c'}
    + *    => result = 32
    + * 
    + *
  6. + *
  7. + *    array = { 'd', 'b', 'c', 'd', 'e' }
    + *    prefix = { 'a', 'b', 'c'}
    + *    => result = 3
    + * 
    + *
  8. + *
  9. + *    array = { 'a', 'b', 'c', 'd', 'e' }
    + *    prefix = { 'd', 'b', 'c'}
    + *    => result = -3
    + * 
    + *
  10. + *
  11. + *    array = { 'a', 'a', 'c', 'd', 'e' }
    + *    prefix = { 'a', 'e', 'c'}
    + *    => result = -4
    + * 
    + *
  12. + *
+ *

+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    => result = { ' a' , ' b' }
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    third = { 'b' }
    + *    => result = { ' a', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    second = null
    + *    third = { 'b' }
    + *    => result = { ' a', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'b' }
    + *    third = null
    + *    => result = { ' a', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = null
    + *    second = null
    + *    third = null
    + *    => result = null
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    second = { 'b' }
    + *    third = { 'c' }
    + *    => result = { 'a', 'b', 'c' }
    + * 
    + *
  10. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    separator = '/'
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    sep1 = '/'
    + *    second = { 'a' }
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , ':', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = null
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = null
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a' , '/', 'b' , ':', 'c' }
    + * 
    + *
  8. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    separator = '/'
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + * *
  7. + *    first = { ' a' }
    + *    second = {  }
    + *    separator = '/'
    + *    => result = { ' a'}
    + * 
    + *
  8. + + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    sep1 = '/'
    + *    second = { 'a' }
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , ':', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = null
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = null
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a' , '/', 'b' , ':', 'c' }
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a', ':', 'c' }
    + * 
    + *
  10. + *
+ * + * @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:
+ *
    + *
  1. + *    prefix = 'a'
    + *    array = { 'b' }
    + *    suffix = 'c'
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    prefix = 'a'
    + *    array = null
    + *    suffix = 'c'
    + *    => result = { 'a', 'c' }
    + * 
  4. + *
+ * + * @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:
+ *
    + *
  1. + *    name = { 'c' }
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' , '.', 'c' }
    + * 
    + *
  2. + *
  3. + *    name = null
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
  4. + *
  5. + *    name = { ' c' }
    + *    array = null
    + *    separator = '.'
    + *    => result = { 'c' }
    + * 
  6. + *
+ * + * @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:
+ *
    + *
  1. + *    name = { 'c' }
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' , '.', 'c' }
    + * 
    + *
  2. + *
  3. + *    name = null
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
  4. + *
  5. + *    name = { ' c' }
    + *    array = null
    + *    separator = '.'
    + *    => result = { 'c' }
    + * 
  6. + *
+ * + * @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:
+ *
    + *
  1. + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    separator = '.'
    + *    => result = { }
    + * 
  4. + *
+ * + * @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:
+ *
    + *
  1. + *    array = { { 'a' }, {}, { 'b' } }
    + *    separator = ''
    + *    => result = { 'a', '/', '/', 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
    + *
  4. + *
  5. + *    array = null
    + *    separator = '.'
    + *    => result = { }
    + * 
  6. + *
+ * + * @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: + *
    + *
  1. + *    character = 'c'
    + *    array = { { ' a' }, { ' b' } }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    character = 'a'
    + *    array = { { ' a' }, { ' b' } }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    character = 'c'
    + *    array = { ' b'  }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    character = 'a'
    + *    array = { ' a' , ' b' }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    characters = { 'c', 'd' }
    + *    array = { 'a', ' b'  }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    characters = { 'c', 'd' }
    + *    array = { 'a', ' b', 'c'  }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    array = { 'a', 'b', 'c', 'd' }
    + *    toBeFound = { 'b', 'c' }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'b', 'c' }
    + *    toBeFound = { 'b', 'c' }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { { } }
    + *    second = null
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { { 'a' } }
    + *    second = { { 'a' } }
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    result => false
    + * 
    + *
  8. + *
+ * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { { } }
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  8. + *
+ * + * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    result => false
    + * 
    + *
  8. + *
+ * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    result => false
    + * 
    + *
  8. + *
+ * @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:

+ *
    + *
  1. + *    first = null
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    isCaseSensitive = false
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  10. + *
+ * @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: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'A' }
    + *    second = { 'a' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    first = { 'A' }
    + *    second = { 'a' }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  8. + *
+ * + * @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: + *
    + *
  1. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    startIndex = 1
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'C' , 'd' }
    + *    startIndex = 1
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'C' , 'd' }
    + *    startIndex = 0
    + *    isCaseSensitive = false
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b'}
    + *    startIndex = 0
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  8. + *
+ * + * @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: + *
    + *
  1. + *    c = ' '
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    c = '\u3000'
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 1
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 1
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' , 'c', 'e' }
    + *    result => 4
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd', 'e' }
    + *    startIndex = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 0
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 2
    + *    endIndex = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd', 'e' }
    + *    startIndex = 3
    + *    endIndex = 4
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 0
    + *    endIndex = 3
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    pattern = { '?', 'b', '*' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    pattern = { '?', 'b', '?' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    pattern = { 'b', '*' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    pattern = { '?', 'b', '*' }
    + *    patternStart = 1
    + *    patternEnd = 3
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    nameStart = 1
    + *    nameEnd = 4
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    pattern = { '?', 'b', '*' }
    + *    patternStart = 1
    + *    patternEnd = 2
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    nameStart = 1
    + *    nameEnd = 4
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => 3
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => 0
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    toBeFound = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 0
    + *    result => 0
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    prefix = { 'a' , 'b' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'c' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    prefix = { 'a' , 'B' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'B' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    prefix = { 'a' , 'B' }
    + *    name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    startIndex = 2
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'B' }
    + *    name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    startIndex = 2
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'c', 'b', 'a' }
    + *    toBeRemoved = 'b'
    + *    return { 'a' , 'c', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeRemoved = 'c'
    + *    return array
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    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' }
    + * 
    + *
  2. + *
  3. + *    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' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    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' }
    + * 
    + *
  2. + *
+ * + * @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: + *
    + *
  1. + *    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' }
    + * 
    + *
  2. + *
+ * + * @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: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = { 'b' }
    + *    replacementChar = { 'a', 'a' }
    + *    result => { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = { 'c' }
    + *    replacementChar = { 'a' }
    + *    result => { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    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' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = 'c'
    + *    replacementChar = 'a'
    + *    result => The original array that remains unchanged.
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  2. + *
  3. + *    divider = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  4. + *
  5. + *    divider = 'b'
    + *    array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  6. + *
  7. + *    divider = 'c'
    + *    array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  8. + *
+ * + * @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: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  2. + *
  3. + *    divider = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  4. + *
  5. + *    divider = 'c'
    + *    array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
    + *    result => { { ' ', 'a', 'b', 'b', 'a', 'b', 'a', ' ' } }
    + * 
    + *
  6. + *
+ * + * @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: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 2
    + *    end = 5
    + *    result => { {  }, { 'a' }, {  } }
    + * 
    + *
  2. + *
+ * + * @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: + *
    + *
  1. + *    divider = ','
    + *    array = { 'A' , '<', 'B', ',', 'C', '>', ',', 'D' }
    + *    start = 0
    + *    end = 8
    + *    result => { {  'A' , '<', 'B', ',', 'C', '>'}, { 'D' }}
    + * 
    + *
  2. + *
+ * + * @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: + *
    + *
  1. + *    array = { { 'a' } , { 'b' } }
    + *    start = 0
    + *    end = 1
    + *    result => { { 'a' } }
    + * 
    + *
  2. + *
  3. + *    array = { { 'a' } , { 'b' } }
    + *    start = 0
    + *    end = -1
    + *    result => { { 'a' }, { 'b' } }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    array = { 'a' , 'b' }
    + *    start = 0
    + *    end = 1
    + *    result => { 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'b' }
    + *    start = 0
    + *    end = -1
    + *    result => { 'a' , 'b' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    chars = { 'a' , 'b' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { 'A', 'b' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    chars = { 'A' , 'B' }
    + *    result => { 'A' , 'B' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'B' }
    + *    result => { 'A' , 'B' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    chars = { ' ', 'a' , 'b', ' ',  ' ' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { 'A', 'b' }
    + *    result => { 'A' , 'b' }
    + * 
    + *
  4. + *
+ * + * @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: + *
    + *
  1. + *    array = { { 'a' } , { 'b' } }
    + *    result => "a.b"
    + * 
    + *
  2. + *
  3. + *    array = { { ' ',  'a' } , { 'b' } }
    + *    result => " a.b"
    + * 
    + *
  4. + *
+ * + * @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", + "", + "
    ", + "
  • ", + "

    size

    ", + "
    public int size()
    ", + "
    Returns the number of elements in this list.
    " + ); + assertEquals(expected, method.getJavaDoc().html().substring(0, expected.length())); + } + + @Test + public void html_testConstructorJavadoc() 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("", Stream.empty()); + assertNotNull(method); + + String expected = String.join("\n", + "", + "
      ", + "
    • ", + "

      ArrayList

      " + ); + assertEquals(expected, method.getJavaDoc().html().substring(0, expected.length())); + + } + + @Test + public void html_testFieldAndMethodJavadocForJar() 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("org.springframework.boot.SpringApplication"); + assertNotNull(type); + + IField field = type.getField("BANNER_LOCATION_PROPERTY_VALUE"); + assertNotNull(field); + assertTrue(field.getJavaDoc().html().contains("

      BANNER_LOCATION_PROPERTY_VALUE

      ")); + + IMethod method = type.getMethod("getListeners", Stream.empty()); + assertNotNull(method); + String expected = String.join("\n", + "", + "
        ", + "
      • ", + "

        getListeners

        " + ); + assertEquals(expected, method.getJavaDoc().html().substring(0, expected.length())); + + } + + @Test + public void html_testJavadocOutputFolder() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenCore.generateJavadocFolderForMavenProject(projectPath); + MavenJavaProject project = createMavenProject(projectPath); + IType type = project.findType("hello.Greeting"); + + assertNotNull(type); + String expected = "
        Comment for Greeting class
        "; + assertEquals(expected, type.getJavaDoc().html().substring(0, expected.length())); + + IField field = type.getField("id"); + assertNotNull(field); + expected = String.join("\n", + "", + "
          ", + "
        • ", + "

          id

          " + ); + assertEquals(expected, field.getJavaDoc().html().substring(0, expected.length())); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "", + "
            ", + "
          • ", + "

            getId

            " + ); + assertEquals(expected, method.getJavaDoc().html().substring(0, expected.length())); + } + } diff --git a/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/pom.xml b/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/pom.xml index 3235b9143..01cf92a03 100644 --- a/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/pom.xml +++ b/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/pom.xml @@ -61,4 +61,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + diff --git a/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/src/main/java/hello/Greeting.java b/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/src/main/java/hello/Greeting.java index 7baa70971..d85c9c8cd 100644 --- a/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/src/main/java/hello/Greeting.java +++ b/vscode-extensions/commons/commons-maven/src/test/resources/gs-rest-service-cors-boot-1.4.1-with-classpath-file/src/main/java/hello/Greeting.java @@ -8,7 +8,7 @@ public class Greeting { /** * Comment for id field */ - private final long id; + protected final long id; private final String content; public Greeting() { @@ -35,7 +35,7 @@ public class Greeting { /** * Comment for inner class */ - private static class TestInnerClass { + public class TestInnerClass { /** * Comment for inner field