diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/hints/StsValueHint.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/hints/StsValueHint.java index adbb8af6f..d7c6f0c1f 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/hints/StsValueHint.java +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/hints/StsValueHint.java @@ -7,13 +7,12 @@ import org.springframework.ide.vscode.application.properties.metadata.util.Depre import org.springframework.ide.vscode.commons.java.IJavaElement; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IType; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; import org.springframework.ide.vscode.commons.util.Assert; -import org.springframework.ide.vscode.commons.util.HtmlBuffer; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.StringUtil; -import org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders; /** * Sts version of {@link ValueHint} contains similar data, but accomoates @@ -29,7 +28,7 @@ public class StsValueHint { private final String value; - private final HoverInfo description; + private final Renderable description; private final Deprecation deprecation; /** @@ -38,7 +37,7 @@ public class StsValueHint { * This constructor is private. Use one of the provided * static 'create' methods instead. */ - private StsValueHint(String value, HoverInfo description, Deprecation deprecation) { + private StsValueHint(String value, Renderable description, Deprecation deprecation) { this.value = value==null?"null":value.toString(); Assert.isLegal(!this.value.startsWith("StsValueHint")); this.description = description; @@ -58,7 +57,7 @@ public class StsValueHint { } public static StsValueHint create(String value) { - return new StsValueHint(value, DescriptionProviders.NO_DESCRIPTION, null); + return new StsValueHint(value, Renderables.NO_DESCRIPTION, null); } public static StsValueHint create(ValueHint hint) { @@ -92,46 +91,33 @@ public class StsValueHint { /** * Create a html snippet from a text snippet. */ - private static HoverInfo textSnippet(String description) { + private static Renderable textSnippet(String description) { if (StringUtil.hasText(description)) { - return DescriptionProviders.text(description); + return Renderables.text(description); } - return DescriptionProviders.NO_DESCRIPTION; + return Renderables.NO_DESCRIPTION; } public String getValue() { return value; } - public HoverInfo getDescription() { + public Renderable getDescription() { return description; } - public HoverInfo getDescriptionProvider() { + public Renderable getDescriptionProvider() { return description; } - public static HoverInfo javaDocSnippet(IJavaElement je) { - try { - HtmlSnippet jdoc = je.getJavaDoc(); + private static Renderable javaDocSnippet(IJavaElement je) { + return Renderables.lazy(() -> { + IJavadoc jdoc = je.getJavaDoc(); if (jdoc != null) { - return new HoverInfo() { - - @Override - public void renderAsMarkdown(StringBuilder buffer) { - // TODO not correct md - buffer.append(jdoc.toString()); - } - - @Override - public void renderAsHtml(HtmlBuffer buffer) { - buffer.raw(jdoc.toHtml()); - } - }; + return jdoc.getRenderable(); + } else { + return Renderables.NO_DESCRIPTION; } - } catch (Exception e) { - Log.log(e); - } - return DescriptionProviders.NO_DESCRIPTION; + }); } @Override diff --git a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypedProperty.java b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypedProperty.java index 02e7f3df3..5e0da039d 100644 --- a/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypedProperty.java +++ b/vscode-extensions/commons/application-properties-metadata/src/main/java/org/springframework/ide/vscode/application/properties/metadata/types/TypedProperty.java @@ -1,11 +1,9 @@ package org.springframework.ide.vscode.application.properties.metadata.types; -import javax.inject.Provider; - import org.springframework.boot.configurationmetadata.Deprecation; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; -import org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders; /** * Represents a property on a Type that can be accessed by name. @@ -27,15 +25,15 @@ public class TypedProperty implements YTypedProperty { /** * Provides a description for this property. */ - private final HoverInfo descriptionProvider; + private final Renderable descriptionProvider; private final Deprecation deprecation; public TypedProperty(String name, Type type, Deprecation deprecation) { - this(name, type, DescriptionProviders.NO_DESCRIPTION, deprecation); + this(name, type, Renderables.NO_DESCRIPTION, deprecation); } - public TypedProperty(String name, Type type, HoverInfo descriptionProvider, Deprecation deprecation) { + public TypedProperty(String name, Type type, Renderable descriptionProvider, Deprecation deprecation) { this.name = name; this.type = type; this.descriptionProvider = descriptionProvider; @@ -56,7 +54,7 @@ public class TypedProperty implements YTypedProperty { } @Override - public HoverInfo getDescription() { + public Renderable getDescription() { //TODO: real implementation that somehow gets this from somewhere (i.e. the JavaDoc) // Note that presently the application.yml and application.properties editor do not actually // use this description provider but produce hover infos in a different way (so this is only diff --git a/vscode-extensions/commons/commons-java/pom.xml b/vscode-extensions/commons/commons-java/pom.xml index abb8bdd51..558fca823 100644 --- a/vscode-extensions/commons/commons-java/pom.xml +++ b/vscode-extensions/commons/commons-java/pom.xml @@ -12,6 +12,12 @@ ../pom.xml + + + 2.19.2.Final + + + org.springframework.ide.vscode @@ -23,5 +29,27 @@ jandex 2.0.3.Final + + + com.kotcrab.remark + remark + 1.0.0 + + + com.github.javaparser + javaparser-core + 2.5.1 + + + org.jboss.forge.roaster + roaster-api + ${roaster.version} + + + org.jboss.forge.roaster + roaster-jdt + ${roaster.version} + runtime + \ No newline at end of file diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexIndex.java index c713c0ac4..4bd7f7a70 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexIndex.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/JandexIndex.java @@ -5,69 +5,114 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexReader; import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; import org.jboss.jandex.JarIndexer; +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.IJavadoc; import org.springframework.ide.vscode.commons.util.Log; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; public class JandexIndex { + private static class Entry { + K key; + V value; + Entry(K key, V value) { + this.key = key; + this.value = value; + } + } + @FunctionalInterface public static interface IndexFileFinder { File findIndexFile(File jarFile); } - private Supplier index; - - public JandexIndex(Stream classpathEntries) { - this(classpathEntries, jarFile -> null, Optional.empty()); + @FunctionalInterface + public static interface JavadocProviderFactory { + IJavadocProvider createJavadocProvider(File jarContainer); } - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder) { - this(classpathEntries, indexFileFinder, Optional.empty()); + private static final IJavadocProvider ABSENT_JAVADOC_PROVIDER = new IJavadocProvider() { + + @Override + public IJavadoc getJavadoc(IType type) { + return null; + } + + @Override + public IJavadoc getJavadoc(IField field) { + return null; + } + + @Override + public IJavadoc getJavadoc(IMethod method) { + return null; + } + + @Override + public IJavadoc getJavadoc(IAnnotation method) { + return null; + } + + }; + + private Supplier>> index; + + private JavadocProviderFactory javadocProviderFactory; + + private Cache javadocProvidersCache = CacheBuilder.newBuilder().build(); + + private JandexIndex[] baseIndex; + + public void setJvadocProviderFactory(JavadocProviderFactory sourceContainerProvider) { + this.javadocProviderFactory = sourceContainerProvider; } - public JandexIndex(Stream classpathEntries, Optional baseIndex) { - this(classpathEntries, jarFile -> null, baseIndex); + public JavadocProviderFactory getJavadocProviderFactory() { + return javadocProviderFactory; } - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, Optional baseIndex) { - index = Suppliers.memoize(() -> { - if (baseIndex.isPresent()) { - return CompositeIndex.create(baseIndex.get().index.get(), buildIndex(classpathEntries, indexFileFinder)); - } else { - return buildIndex(classpathEntries, indexFileFinder); - } - }); + public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, JavadocProviderFactory javadocProviderFactory, JandexIndex... baseIndex) { + this.baseIndex = baseIndex; + index = Suppliers.memoize(() -> buildIndex(classpathEntries, indexFileFinder).collect(Collectors.toList())); + this.javadocProviderFactory = javadocProviderFactory; } - private static CompositeIndex buildIndex(Stream classpathEntries, IndexFileFinder indexFileFinder) { - return CompositeIndex.create(classpathEntries + private Stream> buildIndex(Stream classpathEntries, IndexFileFinder indexFileFinder) { + return classpathEntries .map(entry -> entry.toFile()) .map(file -> { + Optional index = Optional.empty(); if (file.isFile() && file.getName().endsWith(".jar")) { - return indexJar(file, indexFileFinder); + index = indexJar(file, indexFileFinder); } else if (file.isDirectory()) { - return indexFolder(file); - } else { - return Optional.empty(); + index = indexFolder(file); } + return new Entry<>(file, index); }) - .filter(o -> o.isPresent()) - .map(o -> o.get()) - .collect(Collectors.toList())); + .filter(e -> e.value.isPresent()) + .map(e -> new Entry<>(e.key, e.value.get())); } private static Optional indexFolder(File folder) { @@ -134,8 +179,36 @@ public class JandexIndex { } public IType findType(String fqName) { - IndexView compositeIndex = index.get(); - return Wrappers.wrap(compositeIndex, compositeIndex.getClassByName(DotName.createSimple(fqName))); + return getClassByName(DotName.createSimple(fqName)); + } + + IType getClassByName(DotName fqName) { + // First look for type in the base index array + return (baseIndex == null ? Stream.empty() + : Arrays.stream(baseIndex) + .filter(jandexIndex -> jandexIndex != null) + .map(jandexIndex -> jandexIndex.getClassByName(fqName))) + .filter(type -> type != null) + .findFirst() + // If not found look at indices owned by this JandexIndex instance + .orElseGet(() -> index.get().stream() + .map(e -> new Entry<>(e.key, e.value.getClassByName(fqName))) + .filter(e -> e.value != null) + .map(e -> createType(e)) + .findFirst() + .orElse(null)); + } + private IType createType(Entry match) { + File classpathResource = match.key; + IJavadocProvider javadocProvider = null; + try { + javadocProvider = javadocProvidersCache.get(match.key, () -> javadocProviderFactory == null ? ABSENT_JAVADOC_PROVIDER : javadocProviderFactory.createJavadocProvider(classpathResource)); + } catch (ExecutionException e) { + Log.log(e); + } + return Wrappers.wrap(this, match.value, javadocProvider); + } + } 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 333d897ff..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; @@ -20,16 +19,19 @@ import org.springframework.ide.vscode.commons.java.Flags; import org.springframework.ide.vscode.commons.java.IAnnotation; import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IJavaType; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; import org.springframework.ide.vscode.commons.java.IMemberValuePair; import org.springframework.ide.vscode.commons.java.IMethod; import org.springframework.ide.vscode.commons.java.IPrimitiveType; import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IVoidType; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; public class Wrappers { - public static IType wrap(IndexView index, ClassInfo info) { + private static final String JANDEX_CONTRUCTOR_NAME = ""; + + public static IType wrap(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) { if (info == null) { return null; } @@ -43,17 +45,17 @@ public class Wrappers { @Override public IType getDeclaringType() { DotName enclosingClass = info.enclosingClass(); - return enclosingClass == null ? null : wrap(index, index.getClassByName(enclosingClass)); + return enclosingClass == null ? null : index.getClassByName(enclosingClass); } @Override public String getElementName() { - return info.simpleName(); + return info.simpleName() == null ? info.name().local() : info.simpleName(); } @Override - public HtmlSnippet getJavaDoc() { - throw new UnsupportedOperationException("Not yet implemented"); + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); } @Override @@ -64,7 +66,7 @@ public class Wrappers { @Override public Stream getAnnotations() { // TODO: check correctness! - return info.annotations().get(info.name()).stream().map(Wrappers::wrap); + return info.annotations().get(info.name()).stream().map(a -> wrap(a, javadocProvider)); } @Override @@ -79,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(); @@ -89,26 +96,26 @@ public class Wrappers { @Override public IField getField(String name) { - return wrap(index, info.field(name)); + return wrap(index, info.field(name), javadocProvider); } @Override public Stream getFields() { return info.fields().stream().map(f -> { - return wrap(index, f); + return wrap(index, f, javadocProvider); }); } @Override public IMethod getMethod(String name, Stream parameters) { List typeParameters = parameters.map(Wrappers::from).collect(Collectors.toList()); - return wrap(index, info.method(name, typeParameters.toArray(new Type[typeParameters.size()]))); + return wrap(index, info.method(name, typeParameters.toArray(new Type[typeParameters.size()])), javadocProvider); } @Override public Stream getMethods() { return info.methods().stream().map(m -> { - return wrap(index, m); + return wrap(index, m, javadocProvider); }); } @@ -116,11 +123,11 @@ public class Wrappers { public String toString() { return info.toString(); } - + }; } - public static IField wrap(IndexView index, FieldInfo field) { + public static IField wrap(JandexIndex index, FieldInfo field, IJavadocProvider javadocProvider) { if (field == null) { return null; } @@ -133,7 +140,7 @@ public class Wrappers { @Override public IType getDeclaringType() { - return wrap(index, field.declaringClass()); + return wrap(index, field.declaringClass(), javadocProvider); } @Override @@ -142,8 +149,8 @@ public class Wrappers { } @Override - public HtmlSnippet getJavaDoc() { - throw new UnsupportedOperationException("Not yet implemented"); + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); } @Override @@ -154,7 +161,7 @@ public class Wrappers { @Override public Stream getAnnotations() { return field.annotations().stream().map(a -> { - return wrap(a); + return wrap(a, javadocProvider); }); } @@ -170,7 +177,7 @@ public class Wrappers { }; } - public static IMethod wrap(IndexView index, MethodInfo method) { + public static IMethod wrap(JandexIndex index, MethodInfo method, IJavadocProvider javadocProvider) { isNotNull(index); isNotNull(method); return new IMethod() { @@ -179,20 +186,25 @@ public class Wrappers { public int getFlags() { return method.flags(); } + + @Override + public boolean isConstructor() { + return method.name().equals(JANDEX_CONTRUCTOR_NAME); + } @Override public IType getDeclaringType() { - return wrap(index, method.declaringClass()); + return wrap(index, method.declaringClass(), javadocProvider); } @Override public String getElementName() { - return method.name(); + return isConstructor() ? getDeclaringType().getElementName() : method.name(); } @Override - public HtmlSnippet getJavaDoc() { - throw new UnsupportedOperationException("Not yet implemented"); + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); } @Override @@ -202,7 +214,7 @@ public class Wrappers { @Override public Stream getAnnotations() { - return method.annotations().stream().map(Wrappers::wrap); + return method.annotations().stream().map(a -> wrap(a, javadocProvider)); } @Override @@ -229,10 +241,11 @@ public class Wrappers { public Stream parameters() { return method.parameters().stream().map(Wrappers::wrap); } + }; } - public static IAnnotation wrap(AnnotationInstance annotation) { + public static IAnnotation wrap(AnnotationInstance annotation, IJavadocProvider javadocProvider) { isNotNull(annotation); return new IAnnotation() { @@ -242,8 +255,8 @@ public class Wrappers { } @Override - public HtmlSnippet getJavaDoc() { - throw new UnsupportedOperationException("Not yet implemented"); + public IJavadoc getJavaDoc() { + return javadocProvider == null ? null : javadocProvider.getJavadoc(this); } @Override @@ -288,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/IJavaElement.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaElement.java index 387253e79..2738b7c18 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaElement.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaElement.java @@ -1,9 +1,9 @@ package org.springframework.ide.vscode.commons.java; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; public interface IJavaElement { String getElementName(); - HtmlSnippet getJavaDoc(); + IJavadoc getJavaDoc(); boolean exists(); } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java new file mode 100644 index 000000000..934535df2 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java @@ -0,0 +1,14 @@ +package org.springframework.ide.vscode.commons.java; + +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +public interface IJavadocProvider { + + IJavadoc getJavadoc(IType type); + + IJavadoc getJavadoc(IField field); + + IJavadoc getJavadoc(IMethod method); + + IJavadoc getJavadoc(IAnnotation annotation); +} 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/java/parser/CompilationUnitIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/CompilationUnitIndex.java new file mode 100644 index 000000000..c8533981c --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/CompilationUnitIndex.java @@ -0,0 +1,50 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.ExecutionException; + +import org.springframework.ide.vscode.commons.util.Log; + +import com.github.javaparser.JavaParser; +import com.github.javaparser.ParseException; +import com.github.javaparser.ast.CompilationUnit; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +public interface CompilationUnitIndex { + + static final CompilationUnitIndex DEFAULT = new CompilationUnitIndex() { + + private LoadingCache cache = CacheBuilder.newBuilder().build(new CacheLoader() { + + @Override + public CompilationUnit load(URL url) throws Exception { + InputStream in = url.openStream(); + try { + return JavaParser.parse(in); + } catch (ParseException e) { + in.close(); + Log.log("Failed to parse java source file: " + url, e); + } + return null; + } + + }); + + @Override + public CompilationUnit getCompilationUnit(URL url) { + try { + return cache.get(url); + } catch (ExecutionException e) { + Log.log(e); + } + return null; + } + + }; + + CompilationUnit getCompilationUnit(URL url); + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java new file mode 100644 index 000000000..6a9e887f3 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java @@ -0,0 +1,179 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.net.URL; +import java.util.Optional; + +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.IJavadoc; +import org.springframework.ide.vscode.commons.javadoc.RawJavadoc; +import org.springframework.ide.vscode.commons.javadoc.SourceUrlProvider; +import org.springframework.ide.vscode.commons.util.Log; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import com.github.javaparser.ast.body.EnumConstantDeclaration; +import com.github.javaparser.ast.body.EnumDeclaration; +import com.github.javaparser.ast.body.FieldDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.VariableDeclarator; +import com.github.javaparser.ast.visitor.GenericVisitorAdapter; + +public class ParserJavadocProvider implements IJavadocProvider { + + private SourceUrlProvider sourceUrlProvider; + + public ParserJavadocProvider(SourceUrlProvider sourceUrlProvider) { + this.sourceUrlProvider = sourceUrlProvider; + } + + public IJavadoc getJavadoc(IType type) { + if (type.isEnum()) { + EnumDeclaration declaration = getEnumDeclaration(type); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } else { + ClassOrInterfaceDeclaration declaration = getClassOrInterfaceDeclaration(type); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } + } + + public IJavadoc getJavadoc(IField field) { + IType declaringType = field.getDeclaringType(); + if (declaringType.isEnum()) { + EnumConstantDeclaration declaration = createVisitorToFindEnumConstant(field).visit(getEnumDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } else { + FieldDeclaration declaration = createVisitorToFindField(field).visit(getClassOrInterfaceDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } + } + + public IJavadoc getJavadoc(IMethod method) { + if (method.parameters().findFirst().isPresent()) { + throw new UnsupportedOperationException("Only methods with no parameters are supported"); + } + IType declaringType = method.getDeclaringType(); + if (declaringType.isEnum()) { + MethodDeclaration declaration = createVisitorToFindMethod(method).visit(getEnumDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } else { + MethodDeclaration declaration = createVisitorToFindMethod(method).visit(getClassOrInterfaceDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); + } + } + + public IJavadoc getJavadoc(IAnnotation annotation) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private CompilationUnit getCompilationUnit(IType type) { + try { + URL sourceUrl = sourceUrlProvider.sourceUrl(type); + return CompilationUnitIndex.DEFAULT.getCompilationUnit(sourceUrl); + } catch (Exception e) { + Log.log("Invalid source URL for type " + type, e); + return null; + } + } + + private EnumDeclaration getEnumDeclaration(IType type) { + IType parent = type.getDeclaringType(); + if (parent == null) { + CompilationUnit cu = getCompilationUnit(type); + return createVisitorToFindEnum(type).visit(cu, null); + } else { + if (parent.isEnum()) { + EnumDeclaration declaration = getEnumDeclaration(parent); + return createVisitorToFindEnum(type).visit(declaration, null); + } else { + ClassOrInterfaceDeclaration declaration = getClassOrInterfaceDeclaration(parent); + return createVisitorToFindEnum(type).visit(declaration, null); + } + } + } + + private ClassOrInterfaceDeclaration getClassOrInterfaceDeclaration(IType type) { + IType parent = type.getDeclaringType(); + if (parent == null) { + CompilationUnit cu = getCompilationUnit(type); + return createVisitorToFindClassOrInterface(type).visit(cu, null); + } else { + if (parent.isEnum()) { + EnumDeclaration declaration = getEnumDeclaration(parent); + return createVisitorToFindClassOrInterface(type).visit(declaration, null); + } else { + ClassOrInterfaceDeclaration declaration = getClassOrInterfaceDeclaration(parent); + return createVisitorToFindClassOrInterface(type).visit(declaration, null); + } + } + } + + private GenericVisitorAdapter createVisitorToFindClassOrInterface(IType type) { + return new GenericVisitorAdapter() { + + @Override + public ClassOrInterfaceDeclaration visit(ClassOrInterfaceDeclaration n, Object arg) { + if (n.getName().equals(type.getElementName())) { + return n; + } else { + return super.visit(n, arg); + } + } + + }; + } + + private GenericVisitorAdapter createVisitorToFindEnum(IType type) { + return new GenericVisitorAdapter() { + + @Override + public EnumDeclaration visit(EnumDeclaration n, Object arg) { + if (n.getName().equals(type.getElementName())) { + return n; + } else { + return super.visit(n, arg); + } + } + + }; + } + + private GenericVisitorAdapter createVisitorToFindMethod(IMethod method) { + return new GenericVisitorAdapter() { + @Override + public MethodDeclaration visit(MethodDeclaration n, Object arg) { + if (n.getParameters().isEmpty() && n.getName().equals(method.getElementName())) { + return n; + } + return null; + } + }; + } + + private GenericVisitorAdapter createVisitorToFindField(IField field) { + return new GenericVisitorAdapter() { + @Override + public FieldDeclaration visit(FieldDeclaration n, Object arg) { + Optional variable = n.getVariables().stream().filter(v -> v.getId().getName().equals(field.getElementName())).findFirst(); + return variable.isPresent() ? n : null; + } + }; + } + + private GenericVisitorAdapter createVisitorToFindEnumConstant(IField field) { + return new GenericVisitorAdapter() { + @Override + public EnumConstantDeclaration visit(EnumConstantDeclaration n, Object arg) { + if (n.getName().equals(field.getElementName())) { + return n; + } else { + return null; + } + } + }; + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/JavaUnitIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/JavaUnitIndex.java new file mode 100644 index 000000000..8d3244ded --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/JavaUnitIndex.java @@ -0,0 +1,47 @@ +package org.springframework.ide.vscode.commons.java.roaster; + +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.ExecutionException; + +import org.jboss.forge.roaster.Roaster; +import org.jboss.forge.roaster.model.JavaUnit; +import org.springframework.ide.vscode.commons.util.Log; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +public interface JavaUnitIndex { + + static final JavaUnitIndex DEFAULT = new JavaUnitIndex() { + + private LoadingCache cache = CacheBuilder.newBuilder().build(new CacheLoader() { + + @Override + public JavaUnit load(URL url) throws Exception { + InputStream in = url.openStream(); + try { + return Roaster.parseUnit(in); + } finally { + in.close(); + } + } + + }); + + @Override + public JavaUnit getJavaUnit(URL url) { + try { + return cache.get(url); + } catch (ExecutionException e) { + Log.log(e); + } + return null; + } + + }; + + JavaUnit getJavaUnit(URL url); + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadoc.java new file mode 100644 index 000000000..4420e2811 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadoc.java @@ -0,0 +1,26 @@ +package org.springframework.ide.vscode.commons.java.roaster; + +import org.jboss.forge.roaster.model.JavaDoc; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; +import org.springframework.ide.vscode.commons.util.Renderable; + +public class RoasterJavadoc implements IJavadoc { + + private JavaDoc javadoc; + + public RoasterJavadoc(JavaDoc javadoc) { + this.javadoc = javadoc; + } + + @Override + public String raw() { + return javadoc.getFullText(); + } + + @Override + public Renderable getRenderable() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadocProvider.java new file mode 100644 index 000000000..db051834b --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadocProvider.java @@ -0,0 +1,102 @@ +package org.springframework.ide.vscode.commons.java.roaster; + +import org.jboss.forge.roaster.model.Field; +import org.jboss.forge.roaster.model.FieldHolder; +import org.jboss.forge.roaster.model.JavaDocCapable; +import org.jboss.forge.roaster.model.JavaType; +import org.jboss.forge.roaster.model.JavaUnit; +import org.jboss.forge.roaster.model.Method; +import org.jboss.forge.roaster.model.MethodHolder; +import org.jboss.forge.roaster.model.TypeHolder; +import org.jboss.forge.roaster.model.source.JavaClassSource; +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.IJavadoc; +import org.springframework.ide.vscode.commons.javadoc.SourceUrlProvider; +import org.springframework.ide.vscode.commons.util.Log; + +public class RoasterJavadocProvider implements IJavadocProvider { + + private SourceUrlProvider sourceUrlProvider; + + public RoasterJavadocProvider(SourceUrlProvider sourceUrlProvider) { + this.sourceUrlProvider = sourceUrlProvider; + } + + @Override + public IJavadoc getJavadoc(IType type) { + try { + JavaClassSource declaration = (JavaClassSource) getDeclaration(type); + return new RoasterJavadoc(declaration.getJavaDoc()); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IField field) { + try { + JavaType typeDeclaration = getDeclaration(field.getDeclaringType()); + if (typeDeclaration instanceof FieldHolder) { + Field fieldDeclaration = ((FieldHolder)typeDeclaration).getField(field.getElementName()); + if (fieldDeclaration instanceof JavaDocCapable) { + return new RoasterJavadoc(((JavaDocCapable)fieldDeclaration).getJavaDoc()); + } + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + @Override + public IJavadoc getJavadoc(IMethod method) { + if (method.parameters().findFirst().isPresent()) { + throw new UnsupportedOperationException("Only methods with no parameters are supported"); + } + try { + JavaType typeDeclaration = getDeclaration(method.getDeclaringType()); + if (typeDeclaration instanceof MethodHolder) { + Method methodDeclaration = ((MethodHolder)typeDeclaration).getMethod(method.getElementName()); + if (methodDeclaration instanceof JavaDocCapable) { + return new RoasterJavadoc(((JavaDocCapable)methodDeclaration).getJavaDoc()); + } + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + @Override + public IJavadoc getJavadoc(IAnnotation annotation) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private JavaUnit getJavaUnit(IType type) throws Exception { + return JavaUnitIndex.DEFAULT.getJavaUnit(sourceUrlProvider.sourceUrl(type)); + } + + private JavaType getDeclaration(IType type) throws Exception { + if (type == null) { + return null; + } + IType declaringType = type.getDeclaringType(); + if (declaringType == null) { + JavaUnit ju = getJavaUnit(type); + return ju.getTopLevelTypes().stream().filter(jt -> jt.getName().equals(type.getElementName())).findFirst().orElse(null); + } else { + JavaType declaringTypeDeclaration = getDeclaration(declaringType); + if (declaringTypeDeclaration instanceof TypeHolder) { + return ((TypeHolder)declaringTypeDeclaration).getNestedType(type.getElementName()); + } else { + 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..f55618265 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java @@ -0,0 +1,25 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; + +public class HtmlJavadoc implements IJavadoc { + + private String html; + + public HtmlJavadoc(String html) { + this.html = html; + } + + @Override + public String raw() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public Renderable getRenderable() { + return Renderables.htmlBlob(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..87527bcaf --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java @@ -0,0 +1,55 @@ +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 org.springframework.ide.vscode.commons.util.Log; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + + +public interface HtmlJavadocIndex { + + static JavadocContents NO_HTML_CONTENT = new JavadocContents(null); + + public static final HtmlJavadocIndex DEFAULT = new HtmlJavadocIndex() { + + private Cache cache = CacheBuilder.newBuilder().build(); + + + @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) { + Log.log("Cannot load javadoc content from " + url, e); + return NO_HTML_CONTENT; + } finally { + if (stream != null) { + stream.close(); + } + } + }); + return content == NO_HTML_CONTENT ? null : content; + } catch (ExecutionException e) { + Log.log(e); + return null; + } + } + }; + + 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..2b5761742 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java @@ -0,0 +1,69 @@ +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); + String html = javadocContents == null ? null : javadocContents.getTypeDoc(type); + return html == null ? null : new HtmlJavadoc(html); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IField field) { + try { + IType declaringType = field.getDeclaringType(); + JavadocContents javadocContents = findHtml(declaringType); + String html = javadocContents == null ? null : javadocContents.getFieldDoc(field); + return html == null ? null : new HtmlJavadoc(html); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IMethod method) { + try { + IType declaringType = method.getDeclaringType(); + JavadocContents javadocContents = findHtml(declaringType); + String html = javadocContents == null ? null : javadocContents.getMethodDoc(method); + return html == null ? null : new HtmlJavadoc(html); + } 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/IJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/IJavadoc.java new file mode 100644 index 000000000..2ff132802 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/IJavadoc.java @@ -0,0 +1,11 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import org.springframework.ide.vscode.commons.util.Renderable; + +public interface IJavadoc { + + String raw(); + + Renderable getRenderable(); + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/RawJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/RawJavadoc.java new file mode 100644 index 000000000..c6bce88d9 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/RawJavadoc.java @@ -0,0 +1,23 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import org.springframework.ide.vscode.commons.util.Renderable; + +public class RawJavadoc implements IJavadoc { + + private String rawContent; + + public RawJavadoc(String rawContent) { + this.rawContent = rawContent; + } + + @Override + public String raw() { + return rawContent; + } + + @Override + public Renderable getRenderable() { + throw new UnsupportedOperationException("Not yet implemented"); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProvider.java new file mode 100644 index 000000000..a0b25ddfd --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProvider.java @@ -0,0 +1,12 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import java.net.URL; + +import org.springframework.ide.vscode.commons.java.IType; + +@FunctionalInterface +public interface SourceUrlProvider { + + URL sourceUrl(IType type) throws Exception; + +} 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 new file mode 100644 index 000000000..633a31ade --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java @@ -0,0 +1,53 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import java.net.URL; +import java.nio.file.Paths; + +import org.springframework.ide.vscode.commons.java.IType; + +@FunctionalInterface +public interface SourceUrlProviderFromSourceContainer { + + public static final SourceUrlProviderFromSourceContainer JAR_SOURCE_URL_PROVIDER = (sourceContainerUrl, type) -> { + StringBuilder sourceUrlStr = new StringBuilder(); + sourceUrlStr.append("jar:"); + sourceUrlStr.append(sourceContainerUrl); + sourceUrlStr.append("!"); + sourceUrlStr.append('/'); + sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/")); + sourceUrlStr.append(".java"); + return new URL(sourceUrlStr.toString()); + + }; + + public static final SourceUrlProviderFromSourceContainer SOURCE_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> { + 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..80296589e --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java @@ -0,0 +1,627 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +import java.util.Arrays; +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 = sanitizeRange(new int[]{javadocStart, javadocEnd}, "ul", "li"); + } 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 + final int indexOfStartOfClassData = CharOperation.indexOf(JavadocConstants.START_OF_CLASS_DATA, this.content, false); + this.indexOfEndOfClassData = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false, lastIndex); + int[] classDataRange = sanitizeRange(new int[] { indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA.length, indexOfEndOfClassData}, "ul", "li", "div"); + this.indexOfEndOfClassData = classDataRange[1]; + + // 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; + + // Get rid of possible
  • tag wrappers + int[] fieldsRange = sanitizeRange(new int[] {indexOfFieldDetails + JavadocConstants.FIELD_DETAIL.length, indexOfFieldsBottom}, "ul", "li", "div"); + indexOfFieldDetails = fieldsRange[0]; + indexOfFieldsBottom = fieldsRange[1]; + + int[] methodsRange = sanitizeRange(new int[] { + indexOfAllMethodsTop + (indexOfAllMethodsTop == indexOfConstructorDetails + ? JavadocConstants.CONSTRUCTOR_DETAIL.length : JavadocConstants.METHOD_DETAIL.length), + indexOfAllMethodsBottom }, "ul", "li", "div"); + indexOfAllMethodsTop = methodsRange[0]; + indexOfAllMethodsBottom = methodsRange[1]; + + // Remove trailing extra tag closings + String badEnding = "
  • \n
\n\n"; + indexOfAllMethodsBottom = trimBadEnding(badEnding, indexOfAllMethodsBottom, badEnding.length() / 2); + + this.hasComputedChildrenSections = true; + + } + + private int trimBadEnding(String badEnding, int end) { + return trimBadEnding(badEnding, end, badEnding.length()); + } + + private int trimBadEnding(String badEnding, int end, int numberOfCharsToTrim) { + if (Arrays.equals(CharOperation.subarray(content, end - badEnding.length(), end), badEnding.toCharArray())) { + return end - numberOfCharsToTrim; + } else { + return end; + } + } + + /* + * 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; + + int indexOfClassDescriptionEnd = trimBadEnding("
\n
    \n
  • \n", indexOfNextSummary); + indexOfClassDescriptionEnd = trimBadEnding("
  • \n
\n
\n", indexOfClassDescriptionEnd); + this.typeDocRange = new int[]{start, indexOfClassDescriptionEnd}; + this.typeDocRange = sanitizeRange(typeDocRange, "ul", "li"); + } else { + // No room left for class comment; + this.typeDocRange = null; + } + + + } + + private int[] trimRange(int[] range) { + int start = range[0]; + int end = range[1]; + + while (start < content.length && start < end && Character.isWhitespace(content[start])) { + start++; + } + + while (end > 0 && start < end && Character.isWhitespace(content[end-1])) { + end--; + } + + return new int[] { start, end }; + } + + private int[] trimTag(int[] range, CharSequence tag) { + int start = range[0]; + int end = range[1]; + + char[] startingTag = ("<" + tag).toCharArray(); + char[] closingTag = ("").toCharArray(); + char[] ending = CharOperation.subarray(content, end - closingTag.length, end); + char[] starting = CharOperation.subarray(content, start, start + startingTag.length); + if (Arrays.equals(closingTag, ending) && Arrays.equals(startingTag, starting)) { + return new int[] { CharOperation.indexOf('>', content, start, end) + 1, end - closingTag.length}; + } else { + return range; + } + } + + private int[] sanitizeRange(int[] range, CharSequence... removeWrapperTags) { + boolean changed = false; + do { + int[] newRange = trimRange(range); + for (CharSequence tag : removeWrapperTags) { + newRange = trimTag(newRange, tag); + } +// newRange = trimTag(newRange, "div"); +// newRange = trimTag(newRange, "ul"); +// newRange = trimTag(newRange, "li"); + changed = !Arrays.equals(newRange, range); + range = newRange; + } while (changed); + return range; + } + +} 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-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfoProvider.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfoProvider.java index a0e512692..43893c9b4 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfoProvider.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfoProvider.java @@ -12,11 +12,12 @@ package org.springframework.ide.vscode.commons.languageserver.hover; import org.springframework.ide.vscode.commons.languageserver.util.IDocument; import org.springframework.ide.vscode.commons.languageserver.util.IRegion; +import org.springframework.ide.vscode.commons.util.Renderable; import reactor.util.function.Tuple2; public interface HoverInfoProvider { - Tuple2 getHoverInfo(IDocument document, int offset) throws Exception; + Tuple2 getHoverInfo(IDocument document, int offset) throws Exception; } diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java index d0fd9c942..6a12177f4 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java +++ b/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/VscodeHoverEngineAdapter.java @@ -23,6 +23,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.TextDocument; import org.springframework.ide.vscode.commons.util.Futures; +import org.springframework.ide.vscode.commons.util.Renderable; import reactor.util.function.Tuple2; @@ -49,9 +50,9 @@ public class VscodeHoverEngineAdapter implements VscodeHoverEngine { if (doc!=null) { int offset = doc.toOffset(params.getPosition()); - Tuple2 hoverTuple = hoverInfoProvider.getHoverInfo(doc, offset); + Tuple2 hoverTuple = hoverInfoProvider.getHoverInfo(doc, offset); if (hoverTuple != null) { - HoverInfo hoverInfo = hoverTuple.getT1(); + Renderable hoverInfo = hoverTuple.getT1(); IRegion region = hoverTuple.getT2(); Range range = doc.toRange(region.getOffset(), region.getLength()); diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java new file mode 100644 index 000000000..19ca96331 --- /dev/null +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java @@ -0,0 +1,65 @@ +package org.springframework.ide.vscode.commons.maven; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.ExternalProcess; + +public class MavenBuilder { + + + private Path projectPath; + + private List targets; + + private List properties; + + public void execute() throws IOException, InterruptedException { + Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") ? projectPath.resolve("mvnw.cmd") + : projectPath.resolve("mvnw"); + mvnwPath.toFile().setExecutable(true); + List all = new ArrayList<>(1 + targets.size() + properties.size()); + all.add(mvnwPath.toAbsolutePath().toString()); + all.addAll(targets); + all.addAll(properties); + ExternalProcess process = new ExternalProcess(projectPath.toFile(), + new ExternalCommand(all.toArray(new String[all.size()])), true); + if (process.getExitValue() != 0) { + throw new RuntimeException("Failed to build test project"); + } + } + + public static MavenBuilder newBuilder(Path projectPath) { + return new MavenBuilder(projectPath); + } + + public MavenBuilder clean() { + targets.add("clean"); + return this; + } + + public MavenBuilder pack() { + targets.add("package"); + return this; + } + + public MavenBuilder skipTests() { + properties.add("-DskipTests"); + return this; + } + + public MavenBuilder javadoc() { + properties.add("javadoc:javadoc"); + return this; + } + + private MavenBuilder(Path projectPath) { + this.projectPath = projectPath; + this.targets = new ArrayList<>(); + this.properties = new ArrayList<>(); + } + +} 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 acf5d7ea1..1da37a51c 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; @@ -23,7 +25,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -50,8 +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.util.ExternalCommand; -import org.springframework.ide.vscode.commons.util.ExternalProcess; +import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider; +import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer; import org.springframework.ide.vscode.commons.util.Log; import com.google.common.base.Supplier; @@ -81,11 +82,26 @@ public class MavenCore { private MavenBridge maven = new MavenBridge(); - private Supplier> javaCoreIndex = Suppliers.memoize(() -> { + private Supplier javaCoreIndex = Suppliers.memoize(() -> { try { - return Optional.of(new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile))); + 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 Optional.empty(); + return null; } }); @@ -110,23 +126,6 @@ public class MavenCore { return Arrays.stream(text.split(File.pathSeparator)).map(dir::resolve); } - /** - * Builds maven project - * - * @param Path of the project - * @throws Exception - */ - public static void buildMavenProject(Path testProjectPath) throws Exception { - Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") - ? testProjectPath.resolve("mvnw.cmd") : testProjectPath.resolve("mvnw"); - mvnwPath.toFile().setExecutable(true); - ExternalProcess process = new ExternalProcess(testProjectPath.toFile(), - new ExternalCommand(mvnwPath.toAbsolutePath().toString(), "clean", "package", "-DskipTests"), true); - if (process.getExitValue() != 0) { - throw new RuntimeException("Failed to build test project"); - } - } - /** * Creates Maven Project descriptor based on the pom file. * @@ -281,7 +280,7 @@ public class MavenCore { return new File(getIndexFolder().toString(), jarFile.getName() + "-" + suffix + ".jdx"); } - public Optional getJavaIndexForJreLibs() { + public JandexIndex getJavaIndexForJreLibs() { return javaCoreIndex.get(); } diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java index 3d577cf5a..1acc33dd1 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java @@ -18,8 +18,8 @@ import org.apache.maven.project.MavenProject; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; import org.springframework.ide.vscode.commons.maven.MavenCore; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; /** * Wrapper for Maven Core project @@ -45,7 +45,7 @@ public class MavenJavaProject implements IJavaProject { } @Override - public HtmlSnippet getJavaDoc() { + public IJavadoc getJavaDoc() { return null; } 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 6d18e39d8..d2459f691 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 @@ -11,17 +11,26 @@ package org.springframework.ide.vscode.commons.maven.java; import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; +import java.util.Optional; import java.util.stream.Stream; +import org.apache.maven.artifact.Artifact; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.DirectoryScanner; import org.springframework.ide.vscode.commons.jandex.JandexIndex; import org.springframework.ide.vscode.commons.java.IClasspath; +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; import org.springframework.ide.vscode.commons.util.Log; import com.google.common.base.Supplier; @@ -34,11 +43,19 @@ import com.google.common.base.Suppliers; * */ public class MavenProjectClasspath implements IClasspath { - + + public static JavadocProviderTypes providerType = JavadocProviderTypes.HTML; + + public enum JavadocProviderTypes { + JAVA_PARSER, + ROASTER, + HTML + } + private MavenCore maven; private MavenProject project; private Supplier javaIndex; - + public MavenProjectClasspath(MavenProject project) { this(project, MavenCore.getInstance()); } @@ -53,16 +70,25 @@ public class MavenProjectClasspath implements IClasspath { } catch (Exception e) { Log.log(e); } - return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), maven.getJavaIndexForJreLibs()); + return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> { + switch (providerType) { + case JAVA_PARSER: + return createParserJavadocProvider(classpathResource); + case ROASTER: + return createRoasterJavadocProvider(classpathResource); + default: + return createHtmlJavdocProvider(classpathResource); + } + }, maven.getJavaIndexForJreLibs()); }); } @Override public Stream getClasspathEntries() throws Exception { return Stream.concat(maven.resolveDependencies(project, null).stream().map(artifact -> { - return Paths.get(artifact.getFile().toURI()); - }), Stream.of(Paths.get(new File(project.getBuild().getOutputDirectory()).toURI()), - Paths.get(new File(project.getBuild().getTestOutputDirectory()).toURI()))); + return artifact.getFile().toPath(); + }), Stream.of(new File(project.getBuild().getOutputDirectory()).toPath(), + new File(project.getBuild().getTestOutputDirectory()).toPath())); } public IType findType(String fqName) { @@ -72,7 +98,11 @@ public class MavenProjectClasspath implements IClasspath { private File findIndexFile(File jarFile) { return new File(maven.getIndexFolder().toString(), jarFile.getName() + "-" + jarFile.lastModified() + ".jdx"); } - + + private Optional getArtifactFromJarFile(File file) throws MavenException { + return maven.resolveDependencies(project, null).stream().filter(a -> file.equals(a.getFile())).findFirst(); + } + @Override public Stream getClasspathResources() { return project.getBuild().getResources().stream().flatMap(resource -> { @@ -89,5 +119,105 @@ 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())) { + return new RoasterJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getBuild().getSourceDirectory()).toURI().toURL(), type); + }); + } else if (classpathResource.toString().startsWith(project.getBuild().getTestOutputDirectory())) { + return new RoasterJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getBuild().getTestSourceDirectory()).toURI().toURL(), type); + }); + } else { + throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); + } + } else { + // Assume it's a JAR file + return new RoasterJavadocProvider(type -> { + try { + Artifact artifact = getArtifactFromJarFile(classpathResource).get(); + URL sourceContainer = maven.getSources(artifact).getFile().toURI().toURL(); + return SourceUrlProviderFromSourceContainer.JAR_SOURCE_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; + }); + } + } + private IJavadocProvider createParserJavadocProvider(File classpathResource) { + if (classpathResource.isDirectory()) { + if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { + return new ParserJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getBuild().getSourceDirectory()).toURI().toURL(), type); + }); + } else if (classpathResource.toString().startsWith(project.getBuild().getTestOutputDirectory())) { + return new ParserJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.SOURCE_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getBuild().getTestSourceDirectory()).toURI().toURL(), type); + }); + } else { + throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); + } + } else { + // Assume it's a JAR file + return new ParserJavadocProvider(type -> { + try { + Artifact artifact = getArtifactFromJarFile(classpathResource).get(); + URL sourceContainer = maven.getSources(artifact).getFile().toURI().toURL(); + return SourceUrlProviderFromSourceContainer.JAR_SOURCE_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; + }); + } + } + + 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/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFile.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFile.java index 0a3458f53..7df72c797 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFile.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/classpathfile/JavaProjectWithClasspathFile.java @@ -1,94 +1,94 @@ -/******************************************************************************* - * Copyright (c) 2016 Pivotal, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.commons.maven.java.classpathfile; - -import java.io.File; -import java.nio.file.Paths; - -import org.springframework.ide.vscode.commons.java.IClasspath; -import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.java.IType; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; - -/** - * Java project that contains classpath text file - * - * @author Alex Boyko - * - */ -public class JavaProjectWithClasspathFile implements IJavaProject { - - private File cpFile; - private FileClasspath classpath; - - public JavaProjectWithClasspathFile(File cpFile) { - this.cpFile = cpFile; - this.classpath = new FileClasspath(Paths.get(cpFile.toURI())); - } - - @Override - public String getElementName() { - return cpFile.getParentFile().getName(); - } - - @Override - public HtmlSnippet getJavaDoc() { - return null; - } - - @Override - public boolean exists() { - return cpFile.exists(); - } - - @Override - public IType findType(String fqName) { - //TODO: implement - return null; - } - - @Override - public IClasspath getClasspath() { - return classpath; - } - - @Override - public String toString() { - return "JavaProjectWithClasspathFile("+cpFile+")"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((cpFile == null) ? 0 : cpFile.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - JavaProjectWithClasspathFile other = (JavaProjectWithClasspathFile) obj; - if (cpFile == null) { - if (other.cpFile != null) - return false; - } else if (!cpFile.equals(other.cpFile)) - return false; - return true; - } - - +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.maven.java.classpathfile; + +import java.io.File; +import java.nio.file.Paths; + +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +/** + * Java project that contains classpath text file + * + * @author Alex Boyko + * + */ +public class JavaProjectWithClasspathFile implements IJavaProject { + + private File cpFile; + private FileClasspath classpath; + + public JavaProjectWithClasspathFile(File cpFile) { + this.cpFile = cpFile; + this.classpath = new FileClasspath(Paths.get(cpFile.toURI())); + } + + @Override + public String getElementName() { + return cpFile.getParentFile().getName(); + } + + @Override + public IJavadoc getJavaDoc() { + return null; + } + + @Override + public boolean exists() { + return cpFile.exists(); + } + + @Override + public IType findType(String fqName) { + //TODO: implement + return null; + } + + @Override + public IClasspath getClasspath() { + return classpath; + } + + @Override + public String toString() { + return "JavaProjectWithClasspathFile("+cpFile+")"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((cpFile == null) ? 0 : cpFile.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + JavaProjectWithClasspathFile other = (JavaProjectWithClasspathFile) obj; + if (cpFile == null) { + if (other.cpFile != null) + return false; + } else if (!cpFile.equals(other.cpFile)) + return false; + return true; + } + + } \ No newline at end of file diff --git a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/DependencyTreeTest.java b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/DependencyTreeTest.java index 3bc095cc8..9a4ff0e45 100644 --- a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/DependencyTreeTest.java +++ b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/DependencyTreeTest.java @@ -30,7 +30,7 @@ public class DependencyTreeTest { private void testMavenClasspath(String projectName) throws Exception { Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/" + projectName).toURI()); - MavenCore.buildMavenProject(testProjectPath); + MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute(); MavenProject project = MavenCore.getInstance().readProject(testProjectPath.resolve(MavenCore.POM_XML).toFile()); Set calculatedClassPath = MavenCore.getInstance().resolveDependencies(project, null).stream().map(artifact -> { 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 48383a330..bc8019026 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 @@ -11,11 +11,15 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; +import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IMethod; import org.springframework.ide.vscode.commons.java.IPrimitiveType; import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IVoidType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; 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; @@ -23,41 +27,55 @@ import com.google.common.cache.LoadingCache; public class JavaIndexTest { - private static LoadingCache projectsCache = CacheBuilder.newBuilder().build(new CacheLoader() { + private static LoadingCache projectsCache = CacheBuilder.newBuilder().build(new CacheLoader() { @Override - public MavenJavaProject load(String projectName) throws Exception { + public Path load(String projectName) throws Exception { Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/" + projectName).toURI()); - MavenCore.buildMavenProject(testProjectPath); - return new MavenJavaProject(testProjectPath.resolve(MavenCore.POM_XML).toFile()); + MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute(); + return testProjectPath; } }); + private static LoadingCache mavenProjectsCache = CacheBuilder.newBuilder().build(new CacheLoader() { + + @Override + public MavenJavaProject load(String projectName) throws Exception { + Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/" + projectName).toURI()); + return createMavenProject(testProjectPath); + } + + }); + + private static MavenJavaProject createMavenProject(Path projectPath) throws Exception { + return new MavenJavaProject(projectPath.resolve(MavenCore.POM_XML).toFile()); + } + @Test public void findClassInJar() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); IType type = project.findType("org.springframework.test.web.client.ExpectedCount"); assertNotNull(type); } @Test public void findClassInOutputFolder() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); IType type = project.findType("hello.Greeting"); assertNotNull(type); } @Test public void classNotFound() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); IType type = project.findType("hello.NonExistentClass"); assertNull(type); } @Test public void voidMethodNoParams() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); IType type = project.findType("java.util.ArrayList"); assertNotNull(type); IMethod m = type.getMethod("clear", Stream.empty()); @@ -68,24 +86,480 @@ public class JavaIndexTest { @Test public void voidConstructor() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); 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()); } @Test public void constructorMethodWithParams() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = mavenProjectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); 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())); + assertEquals(Collections.singletonList(IPrimitiveType.INT), m.parameters().collect(Collectors.toList())); + } + + @Test + public void parser_testClassJavadocForOutputFolder() throws Exception { + 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"); + + assertNotNull(type); + String expected = String.join("\n", + "/**", + " * Comment for Greeting class ", + " */" + ); + assertEquals(expected, type.getJavaDoc().raw().trim()); + + IField field = type.getField("id"); + assertNotNull(field); + expected = String.join("\n", + "/**", + " * Comment for id field", + " */" + ); + assertEquals(expected, field.getJavaDoc().raw().trim()); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "/**", + " * Comment for getId()", + " */" + ); + assertEquals(expected, method.getJavaDoc().raw().trim()); } + @Test + public void parser_testInnerClassJavadocForOutputFolder() throws Exception { + 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); + assertEquals("/**\n * Comment for inner class\n */", type.getJavaDoc().raw().trim()); + + IField field = type.getField("innerField"); + assertNotNull(field); + assertEquals("/**\n \t * Comment for inner field\n \t */", field.getJavaDoc().raw().trim()); + + IMethod method = type.getMethod("getInnerField", Stream.empty()); + assertNotNull(method); + assertEquals("/**\n \t * Comment for method inside nested class\n \t */", method.getJavaDoc().raw().trim()); + } + + @Test + public void parser_testClassJavadocForJar() throws Exception { + 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"); + assertNotNull(type); + String expected = String.join("\n", + "/**", + " * {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a" + ); + assertEquals(expected, type.getJavaDoc().raw().trim().substring(0, expected.length())); + + type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener$LiquibasePresent"); + assertNotNull(type); + expected = String.join("\n", + "/**", + " * Inner class to prevent class not found issues.", + " */" + ); + assertEquals(expected, type.getJavaDoc().raw().trim()); + } + + @Test + public void parser_testFieldAndMethodJavadocForJar() throws Exception { + 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"); + assertNotNull(type); + + IField field = type.getField("BANNER_LOCATION_PROPERTY_VALUE"); + assertNotNull(field); + String expected = String.join("\n", + "/**", + " * Default banner location.", + " */" + ); + assertEquals(expected, field.getJavaDoc().raw().trim()); + + IMethod method = type.getMethod("getListeners", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "/**", + " * Returns read-only ordered Set of the {@link ApplicationListener}s that will be" + ); + assertEquals(expected, method.getJavaDoc().raw().trim().substring(0, expected.length())); + } + + @Test + public void roaster_testClassJavadocForOutputFolder() throws Exception { + 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"); + + assertNotNull(type); + assertEquals("Comment for Greeting class", type.getJavaDoc().raw()); + + IField field = type.getField("id"); + assertNotNull(field); + assertEquals("Comment for id field", field.getJavaDoc().raw()); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + assertEquals("Comment for getId()", method.getJavaDoc().raw()); + } + + @Test + public void roaster_testInnerClassJavadocForOutputFolder() throws Exception { + 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); + assertEquals("Comment for inner class", type.getJavaDoc().raw()); + + IField field = type.getField("innerField"); + assertNotNull(field); + assertEquals("Comment for inner field", field.getJavaDoc().raw()); + + IMethod method = type.getMethod("getInnerField", Stream.empty()); + assertNotNull(method); + assertEquals("Comment for method inside nested class", method.getJavaDoc().raw()); + } + + @Test + public void roaster_testClassJavadocForJar() throws Exception { + 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"); + assertNotNull(type); + String expected = "{@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a"; + assertEquals(expected, type.getJavaDoc().raw().substring(0, expected.length())); + + type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener$LiquibasePresent"); + assertNotNull(type); + assertEquals("Inner class to prevent class not found issues.", type.getJavaDoc().raw()); + } + + @Test + public void roaster_testFieldAndMethodJavadocForJar() throws Exception { + 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"); + assertNotNull(type); + + IField field = type.getField("BANNER_LOCATION_PROPERTY_VALUE"); + assertNotNull(field); + assertEquals("Default banner location.", field.getJavaDoc().raw()); + + IMethod method = type.getMethod("getListeners", Stream.empty()); + assertNotNull(method); + String expected = "Returns read-only ordered Set of the {@link ApplicationListener} s that will be"; + assertEquals(expected, method.getJavaDoc().raw().substring(0, expected.length())); + } + + + @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." + ); + IJavadoc javaDoc = type.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml().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"); + IJavadoc javaDoc = type.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml().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.
" + ); + IJavadoc javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml().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

" + ); + IJavadoc javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml().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); + String expected = String.join("\n", + "

BANNER_LOCATION_PROPERTY_VALUE

", + "
public static final String BANNER_LOCATION_PROPERTY_VALUE
", + "
Default banner location.
", + "
", + "
See Also:
", + "
Constant Field Values
", + "
" + ); + IJavadoc javaDoc = field.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + + IMethod method = type.getMethod("getListeners", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "

getListeners

", + "
public Set<org.springframework.context.ApplicationListener<?>> getListeners()
", + "
Returns read-only ordered Set of the ApplicationListeners that will be", + " applied to the SpringApplication and registered with the ApplicationContext", + " .
", + "
", + "
Returns:
", + "
the listeners
", + "
" + ); + javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } + + @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"); + MavenJavaProject project = createMavenProject(projectPath); + IType type = project.findType("hello.Greeting"); + + assertNotNull(type); + String expected = "
Comment for Greeting class
"; + IJavadoc javaDoc = type.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + + IField field = type.getField("id"); + assertNotNull(field); + expected = String.join("\n", + "

id

", + "
protected final long id
", + "
Comment for id field
" + ); + javaDoc = field.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "

getId

", + "
public long getId()
", + "
Comment for getId()
" + ); + javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } + + @Test + public void html_testInnerClassJavadocForOutputFolder() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.Greeting$TestInnerClass"); + assertNotNull(type); + IJavadoc javaDoc = type.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals("
Comment for inner class
", javaDoc.getRenderable().toHtml()); + + IField field = type.getField("innerField"); + assertNotNull(field); + String expected = String.join("\n", + "

innerField

", + "
protected int innerField
", + "
Comment for inner field
" + ); + javaDoc = field.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + + IMethod method = type.getMethod("getInnerField", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "

getInnerField

", + "
public int getInnerField()
", + "
Comment for method inside nested class
" + ); + javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } + + @Test + public void html_testInnerClassLevel2_JavadocForOutputFolder() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.Greeting$TestInnerClass$TestInnerClassLevel2"); + assertNotNull(type); + IJavadoc javaDoc = type.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals("
Comment for level 2 nested class
", javaDoc.getRenderable().toHtml()); + + IField field = type.getField("innerLevel2Field"); + assertNotNull(field); + String expected = String.join("\n", + "

innerLevel2Field

", + "
protected int innerLevel2Field
", + "
Comment for level 2 inner field
" + ); + javaDoc = field.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + + IMethod method = type.getMethod("getInnerLevel2Field", Stream.empty()); + assertNotNull(method); + expected = String.join("\n", + "

getInnerLevel2Field

", + "
public int getInnerLevel2Field()
", + "
Comment for method inside level 2 nested class
" + ); + javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } + + @Test + public void html_testNoJavadocClass() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.GreetingController"); + assertNotNull(type); + assertNull(type.getJavaDoc()); + } + + @Test + public void html_testEmptyJavadocClass() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.Application"); + assertNotNull(type); + assertNull(type.getJavaDoc()); + } + + @Test + public void html_testNoJavadocMethod() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.Application"); + assertNotNull(type); + IMethod method = type.getMethod("corsConfigurer", Stream.empty()); + assertNotNull(method); + String expected = String.join("\n", + "

corsConfigurer

", + "
@Bean",
+				"public org.springframework.web.servlet.config.annotation.WebMvcConfigurer corsConfigurer()
" + ); + IJavadoc javaDoc = method.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } + + @Test + public void html_testNoJavadocField() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + Path projectPath = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + MavenJavaProject project = createMavenProject(projectPath); + + IType type = project.findType("hello.GreetingController"); + assertNotNull(type); + IField field = type.getField("template"); + assertNotNull(field); + String expected = String.join("\n", + "

template

", + "
public static final String template
", + "
", + "
See Also:
", + "
Constant Field Values
", + "
" + ); + IJavadoc javaDoc = field.getJavaDoc(); + assertNotNull(javaDoc); + assertEquals(expected, javaDoc.getRenderable().toHtml()); + } } 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/Application.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/Application.java index a70be84e4..a6ff0249b 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/Application.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/Application.java @@ -8,6 +8,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @SpringBootApplication +/** + */ public class Application { public static void main(String[] args) { 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 7e631d9ec..8d0394e21 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 @@ -1,8 +1,14 @@ package hello; +/** + * Comment for Greeting class + */ public class Greeting { - private final long id; + /** + * Comment for id field + */ + protected final long id; private final String content; public Greeting() { @@ -15,6 +21,9 @@ public class Greeting { this.content = content; } + /** + * Comment for getId() + */ public long getId() { return id; } @@ -22,4 +31,41 @@ public class Greeting { public String getContent() { return content; } + + /** + * Comment for inner class + */ + public class TestInnerClass { + + /** + * Comment for inner field + */ + protected int innerField; + + /** + * Comment for method inside nested class + */ + public int getInnerField() { + return innerField; + } + + /** + * Comment for level 2 nested class + */ + public class TestInnerClassLevel2 { + + /** + * Comment for level 2 inner field + */ + protected int innerLevel2Field; + + /** + * Comment for method inside level 2 nested class + */ + public int getInnerLevel2Field() { + return innerField; + } + } + + } } 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/GreetingController.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/GreetingController.java index 414eddb0c..196319f5c 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/GreetingController.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/GreetingController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { - private static final String template = "Hello, %s!"; + public static final String template = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @CrossOrigin(origins = "http://localhost:9000") diff --git a/vscode-extensions/commons/commons-util/pom.xml b/vscode-extensions/commons/commons-util/pom.xml index 0617ac842..91f40a713 100644 --- a/vscode-extensions/commons/commons-util/pom.xml +++ b/vscode-extensions/commons/commons-util/pom.xml @@ -21,6 +21,12 @@ javax.inject 1 + + + com.kotcrab.remark + remark + 1.0.0 + diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/HtmlBuffer.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/HtmlBuffer.java index fd15969a4..3e037e869 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/HtmlBuffer.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/HtmlBuffer.java @@ -113,10 +113,6 @@ public class HtmlBuffer { raw("

"); } - public void snippet(HtmlSnippet snippet) { - snippet.render(this); - } - public void bold(String string) { raw(""); text(string); diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/IOUtil.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/IOUtil.java similarity index 97% rename from vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/IOUtil.java rename to vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/IOUtil.java index bb0161cec..7dc09c5a9 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/IOUtil.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/IOUtil.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.yaml.util; +package org.springframework.ide.vscode.commons.util; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; diff --git a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfo.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderable.java similarity index 85% rename from vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfo.java rename to vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderable.java index f211d66a3..06195392f 100644 --- a/vscode-extensions/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/hover/HoverInfo.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderable.java @@ -8,15 +8,13 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.languageserver.hover; - -import org.springframework.ide.vscode.commons.util.HtmlBuffer; +package org.springframework.ide.vscode.commons.util; /** * Placeholder. Still need to figure out what exactly we should do with this in * vscode. TODO: rename to Renderable */ -public interface HoverInfo { +public interface Renderable { void renderAsHtml(HtmlBuffer buffer); diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/DescriptionProviders.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java similarity index 65% rename from vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/DescriptionProviders.java rename to vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java index a70d9ffe6..f34435c18 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/util/DescriptionProviders.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/Renderables.java @@ -8,20 +8,17 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.yaml.util; +package org.springframework.ide.vscode.commons.util; import java.io.InputStream; import java.util.List; -import javax.inject.Provider; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; -import org.springframework.ide.vscode.commons.util.HtmlBuffer; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; +import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; +import com.overzealous.remark.Remark; /** * Static methods and convenience constants for creating some 'description @@ -29,44 +26,50 @@ import com.google.common.collect.ImmutableList; * * @author Kris De Volder */ -public class DescriptionProviders { +public class Renderables { private static final String NO_DESCRIPTION_TEXT = "no description"; - final static Logger logger = LoggerFactory.getLogger(DescriptionProviders.class); + final static Logger logger = LoggerFactory.getLogger(Renderables.class); - public static final HoverInfo NO_DESCRIPTION = italic(text(NO_DESCRIPTION_TEXT)); - - public static Provider snippet(final HtmlSnippet snippet) { - return new Provider() { - @Override - public String toString() { - return snippet.toString(); - } - - @Override - public HtmlSnippet get() { - return snippet; - } - }; + public static final Renderable NO_DESCRIPTION = italic(text(NO_DESCRIPTION_TEXT)); + + private static Remark getHtmlToMarkdownConverter() { + return new Remark(); } - public static HoverInfo concat(HoverInfo... pieces) { + public static Renderable htmlBlob(String html) { + return new Renderable() { + + @Override + public void renderAsHtml(HtmlBuffer buffer) { + buffer.raw(html); + } + + @Override + public void renderAsMarkdown(StringBuilder buffer) { + buffer.append(getHtmlToMarkdownConverter().convert(html)); + } + + }; + } + + public static Renderable concat(Renderable... pieces) { return concat(ImmutableList.copyOf(pieces)); } - public static HoverInfo concat(List pieces) { + public static Renderable concat(List pieces) { if (pieces == null || pieces.size() == 0) { throw new IllegalArgumentException("At least one hover information is required for concat"); } else if (pieces.size() == 1) { return pieces.get(0); } else { - return new ConcatHoverInfo(pieces); + return new ConcatRenderables(pieces); } } - public static HoverInfo italic(HoverInfo text) { - return new HoverInfo() { + public static Renderable italic(Renderable text) { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { @@ -84,8 +87,8 @@ public class DescriptionProviders { }; } - public static HoverInfo link(String text, String url) { - return new HoverInfo() { + public static Renderable link(String text, String url) { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { @@ -110,8 +113,8 @@ public class DescriptionProviders { }; } - public static HoverInfo lineBreak() { - return new HoverInfo() { + public static Renderable lineBreak() { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { @@ -125,9 +128,9 @@ public class DescriptionProviders { }; } - public static HoverInfo bold(HoverInfo text) { + public static Renderable bold(Renderable text) { - return new HoverInfo() { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { @@ -145,8 +148,8 @@ public class DescriptionProviders { }; } - public static HoverInfo text(String text) { - return new HoverInfo() { + public static Renderable text(String text) { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { // TODO: handle escaping @@ -159,9 +162,24 @@ public class DescriptionProviders { } }; } + + public static Renderable lazy(Supplier supplier) { + return new Renderable() { + + @Override + public void renderAsMarkdown(StringBuilder buffer) { + supplier.get().renderAsMarkdown(buffer); + } + + @Override + public void renderAsHtml(HtmlBuffer buffer) { + supplier.get().renderAsHtml(buffer); + } + }; + } - public static HoverInfo fromClasspath(final Class klass, final String resourcePath) { - return new HoverInfo() { + public static Renderable fromClasspath(final Class klass, final String resourcePath) { + return new Renderable() { @Override public void renderAsMarkdown(StringBuilder buffer) { @@ -199,28 +217,28 @@ public class DescriptionProviders { }; } - private static class ConcatHoverInfo implements HoverInfo { + private static class ConcatRenderables implements Renderable { - private HoverInfo[] pieces; + private Renderable[] pieces; - ConcatHoverInfo(HoverInfo[] pieces) { + ConcatRenderables(Renderable[] pieces) { this.pieces = pieces; } - public ConcatHoverInfo(List pieces) { - this(pieces.toArray(new HoverInfo[pieces.size()])); + public ConcatRenderables(List pieces) { + this(pieces.toArray(new Renderable[pieces.size()])); } @Override public void renderAsHtml(HtmlBuffer buffer) { - for (HoverInfo hoverInfo : pieces) { + for (Renderable hoverInfo : pieces) { hoverInfo.renderAsHtml(buffer); } } @Override public void renderAsMarkdown(StringBuilder buffer) { - for (HoverInfo hoverInfo : pieces) { + for (Renderable hoverInfo : pieces) { hoverInfo.renderAsMarkdown(buffer); } } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TopLevelAssistContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TopLevelAssistContext.java index 3952e2e8e..1cb7f7daf 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TopLevelAssistContext.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/TopLevelAssistContext.java @@ -14,8 +14,8 @@ import java.util.Collection; import java.util.Collections; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode; @@ -52,17 +52,17 @@ public abstract class TopLevelAssistContext implements YamlAssistContext { } @Override - public HoverInfo getHoverInfo() { + public Renderable getHoverInfo() { return null; } @Override - public HoverInfo getHoverInfo(YamlPathSegment lastSegment) { + public Renderable getHoverInfo(YamlPathSegment lastSegment) { return null; } @Override - public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { + public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { return null; } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index 1506003fd..f732599ad 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -22,10 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.yaml.hover.YPropertyHoverInfo; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; @@ -219,7 +219,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { @Override - public HoverInfo getHoverInfo() { + public Renderable getHoverInfo() { if (parent!=null) { return parent.getHoverInfo(contextPath.getLastSegment()); } @@ -231,7 +231,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } @Override - public HoverInfo getHoverInfo(YamlPathSegment lastSegment) { + public Renderable getHoverInfo(YamlPathSegment lastSegment) { //Hoverinfo is only attached to YTypedProperties so... switch (lastSegment.getType()) { case VAL_AT_KEY: @@ -247,7 +247,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } @Override - public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { + public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { //By default we don't provide value-specific hover, so just show the same hover // as the assistContext the value is in. This is likely more interesting than showing nothing at all. return getHoverInfo(); diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlAssistContext.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlAssistContext.java index 23b831731..549684387 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlAssistContext.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YamlAssistContext.java @@ -13,8 +13,8 @@ package org.springframework.ide.vscode.commons.yaml.completion; import java.util.Collection; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.yaml.path.YamlNavigable; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.commons.yaml.structure.YamlDocument; @@ -28,8 +28,8 @@ public interface YamlAssistContext extends YamlNavigable { //TODO: conceptually... the right thing would be to only implement the second of these // two methods and get rid of the first one. - HoverInfo getHoverInfo(); - HoverInfo getHoverInfo(YamlPathSegment lastSegment); + Renderable getHoverInfo(); + Renderable getHoverInfo(YamlPathSegment lastSegment); - HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion); + Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion); } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YPropertyHoverInfo.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YPropertyHoverInfo.java index fa7a07f82..7eaae4287 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YPropertyHoverInfo.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YPropertyHoverInfo.java @@ -10,13 +10,13 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.hover; -import static org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders.bold; -import static org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders.concat; -import static org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders.lineBreak; -import static org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders.link; -import static org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders.text; +import static org.springframework.ide.vscode.commons.util.Renderables.bold; +import static org.springframework.ide.vscode.commons.util.Renderables.concat; +import static org.springframework.ide.vscode.commons.util.Renderables.lineBreak; +import static org.springframework.ide.vscode.commons.util.Renderables.link; +import static org.springframework.ide.vscode.commons.util.Renderables.text; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; @@ -31,9 +31,9 @@ import com.google.common.collect.ImmutableList.Builder; */ public class YPropertyHoverInfo { - public static HoverInfo create(String contextProperty, YType contextType, YTypedProperty prop) { + public static Renderable create(String contextProperty, YType contextType, YTypedProperty prop) { - Builder html = ImmutableList.builder(); + Builder html = ImmutableList.builder(); if (StringUtil.hasText(contextProperty)) { html.add(text(contextProperty)); html.add(text(".")); @@ -48,7 +48,7 @@ public class YPropertyHoverInfo { html.add(link(type.toString(), /* no URL */ null)); } - HoverInfo description = prop.getDescription(); + Renderable description = prop.getDescription(); if (description != null) { html.add(lineBreak()); html.add(description); diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YamlHoverInfoProvider.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YamlHoverInfoProvider.java index 11a8e7fcc..1ee8d0d22 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YamlHoverInfoProvider.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/hover/YamlHoverInfoProvider.java @@ -12,13 +12,13 @@ package org.springframework.ide.vscode.commons.yaml.hover; import java.util.List; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.languageserver.util.IDocument; import org.springframework.ide.vscode.commons.languageserver.util.IRegion; import org.springframework.ide.vscode.commons.languageserver.util.Region; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.yaml.ast.NodeRef; import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; @@ -59,7 +59,7 @@ public class YamlHoverInfoProvider implements HoverInfoProvider { } @Override - public Tuple2 getHoverInfo(IDocument doc, int offset) throws Exception { + public Tuple2 getHoverInfo(IDocument doc, int offset) throws Exception { YamlFileAST ast = getAst(doc); if (ast != null) { IRegion region = getHoverRegion(ast, offset); @@ -82,10 +82,10 @@ public class YamlHoverInfoProvider implements HoverInfoProvider { assistContext = assistPath.traverse(assistContext); if (assistContext != null) { if (path.pointsAtValue()) { - HoverInfo info = assistContext.getValueHoverInfo(ymlDoc, new DocumentRegion(doc, region)); + Renderable info = assistContext.getValueHoverInfo(ymlDoc, new DocumentRegion(doc, region)); return Tuples.of(info, region); } - HoverInfo info = assistContext.getHoverInfo(); + Renderable info = assistContext.getHoverInfo(); return Tuples.of(info, region); } } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index 5f517b395..ff74604d0 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -21,10 +21,10 @@ import java.util.Set; import javax.inject.Provider; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.util.EnumValueParser; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.ValueParser; -import org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders; /** * Static utility method for creating YType objects representing either @@ -193,7 +193,7 @@ public class YTypeFactory { propertyList.add(p); } - public void addProperty(String name, YType type, HoverInfo description) { + public void addProperty(String name, YType type, Renderable description) { YTypedPropertyImpl prop; addProperty(prop = new YTypedPropertyImpl(name, type)); prop.setDescriptionProvider(description); @@ -314,7 +314,7 @@ public class YTypeFactory { final private String name; final private YType type; - private HoverInfo description = DescriptionProviders.NO_DESCRIPTION; + private Renderable description = Renderables.NO_DESCRIPTION; private YTypedPropertyImpl(String name, YType type) { this.name = name; @@ -337,11 +337,11 @@ public class YTypeFactory { } @Override - public HoverInfo getDescription() { + public Renderable getDescription() { return description; } - public void setDescriptionProvider(HoverInfo description) { + public void setDescriptionProvider(Renderable description) { this.description = description; } } diff --git a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypedProperty.java b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypedProperty.java index 3945dd37c..a29908405 100644 --- a/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypedProperty.java +++ b/vscode-extensions/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypedProperty.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.schema; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; +import org.springframework.ide.vscode.commons.util.Renderable; /** * @author Kris De Volder @@ -18,5 +18,5 @@ import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; public interface YTypedProperty { String getName(); YType getType(); - HoverInfo getDescription(); + Renderable getDescription(); } diff --git a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index 81c7ba423..ac5b6d03d 100644 --- a/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/vscode-extensions/commons/project-test-harness/src/main/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -27,6 +27,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.maven.MavenBuilder; import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.maven.java.classpathfile.JavaProjectWithClasspathFile; @@ -59,10 +60,10 @@ public class ProjectsHarness { Path testProjectPath = getProjectPath(name); switch (type) { case MAVEN: - MavenCore.buildMavenProject(testProjectPath); + MavenBuilder.newBuilder(testProjectPath).clean().pack()./*javadoc().*/skipTests().execute(); return new MavenJavaProject(testProjectPath.resolve(MavenCore.POM_XML).toFile()); case CLASSPATH_TXT: - MavenCore.buildMavenProject(testProjectPath); + MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute(); return new JavaProjectWithClasspathFile(testProjectPath.resolve(MavenCore.CLASSPATH_TXT).toFile()); default: throw new IllegalStateException("Bug!!! Missing case"); diff --git a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.0-properties-live-metadta/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.0-properties-live-metadta/pom.xml index 92c921a66..386306310 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.0-properties-live-metadta/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.0-properties-live-metadta/pom.xml @@ -63,4 +63,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.1-app-properties-list-of-pojo/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.1-app-properties-list-of-pojo/pom.xml index d92c1a510..df788ff31 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.1-app-properties-list-of-pojo/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.2.1-app-properties-list-of-pojo/pom.xml @@ -86,5 +86,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.0-app-sts-4231/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.0-app-sts-4231/pom.xml index f3eca0fd4..da98d371d 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.0-app-sts-4231/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.0-app-sts-4231/pom.xml @@ -98,4 +98,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-app-with-resource-prop/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-app-with-resource-prop/pom.xml index 49ff0c4f8..d58e4efe3 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-app-with-resource-prop/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-app-with-resource-prop/pom.xml @@ -54,5 +54,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-sts-4335/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-sts-4335/pom.xml index e9da3684a..66e436813 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-sts-4335/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/boot-1.3.3-sts-4335/pom.xml @@ -54,5 +54,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/custom-properties-boot-project/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/custom-properties-boot-project/pom.xml index 4de5ad42b..50cc2b23a 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/custom-properties-boot-project/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/custom-properties-boot-project/pom.xml @@ -72,5 +72,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-app/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-app/pom.xml index 93f4858f5..dec476dfb 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-app/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-app/pom.xml @@ -49,5 +49,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-with-mongo/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-with-mongo/pom.xml index 91bed2db1..45edfea43 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-with-mongo/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/empty-boot-1.3.0-with-mongo/pom.xml @@ -53,5 +53,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/enums-boot-1.3.2-app/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/enums-boot-1.3.2-app/pom.xml index a10fb0a6b..4966e03e1 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/enums-boot-1.3.2-app/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/enums-boot-1.3.2-app/pom.xml @@ -52,4 +52,14 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + diff --git a/vscode-extensions/commons/project-test-harness/test-projects/tricky-getters-boot-1.3.1-app/pom.xml b/vscode-extensions/commons/project-test-harness/test-projects/tricky-getters-boot-1.3.1-app/pom.xml index 732a6acf6..55f4cd30f 100644 --- a/vscode-extensions/commons/project-test-harness/test-projects/tricky-getters-boot-1.3.1-app/pom.xml +++ b/vscode-extensions/commons/project-test-harness/test-projects/tricky-getters-boot-1.3.1-app/pom.xml @@ -50,5 +50,15 @@ + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + tricky-getters-boot-1.3.1-app diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java index 55814e5f1..8fa92b741 100644 --- a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/application/properties/ApplicationPropertiesLanguageServer.java @@ -54,7 +54,7 @@ public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { javaProjectFinder ); completionEngine = new VscodeCompletionEngineAdapter(this, propertiesCompletionEngine); - completionEngine.setMaxCompletionsNumber(-1); + completionEngine.setMaxCompletionsNumber(40); documents.onCompletion(completionEngine::getCompletions); documents.onCompletionResolve(completionEngine::resolveCompletion); diff --git a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java index 8f516333f..3d03e724e 100644 --- a/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java +++ b/vscode-extensions/vscode-application-yaml/src/main/java/org/springframework/ide/vscode/application/yaml/completions/ApplicationYamlAssistContext.java @@ -38,11 +38,11 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet import org.springframework.ide.vscode.commons.languageserver.completion.LazyProposalApplier; import org.springframework.ide.vscode.commons.languageserver.completion.ProposalApplier; import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion; import org.springframework.ide.vscode.commons.util.CollectionUtil; import org.springframework.ide.vscode.commons.util.FuzzyMatcher; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.yaml.completion.AbstractYamlAssistContext; import org.springframework.ide.vscode.commons.yaml.completion.TopLevelAssistContext; @@ -376,19 +376,19 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon @Override - public HoverInfo getHoverInfo(YamlPathSegment lastSegment) { + public Renderable getHoverInfo(YamlPathSegment lastSegment) { // TODO Auto-generated method stub return null; } @Override - public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { + public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { // TODO Auto-generated method stub return null; } @Override - public HoverInfo getHoverInfo() { + public Renderable getHoverInfo() { // TODO Auto-generated method stub return null; } @@ -504,19 +504,19 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon } @Override - public HoverInfo getHoverInfo() { + public Renderable getHoverInfo() { // TODO Auto-generated method stub return null; } @Override - public HoverInfo getHoverInfo(YamlPathSegment lastSegment) { + public Renderable getHoverInfo(YamlPathSegment lastSegment) { // TODO Auto-generated method stub return null; } @Override - public HoverInfo getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { + public Renderable getValueHoverInfo(YamlDocument doc, DocumentRegion documentRegion) { // TODO Auto-generated method stub return null; } diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index 6f826fa6e..8840d1873 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -15,7 +15,8 @@ import java.util.Set; import javax.inject.Provider; -import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfo; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType; @@ -24,7 +25,6 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPro import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; -import org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders; import com.google.common.collect.ImmutableSet; @@ -109,11 +109,11 @@ public class ManifestYmlSchema implements YamlSchema { } } - private HoverInfo descriptionFor(String propName) { - return DescriptionProviders.fromClasspath(this.getClass(), "/description-by-prop-name/"+propName); + private Renderable descriptionFor(String propName) { + return Renderables.fromClasspath(this.getClass(), "/description-by-prop-name/"+propName); } - private HoverInfo descriptionFor(YTypedPropertyImpl prop) { + private Renderable descriptionFor(YTypedPropertyImpl prop) { return descriptionFor(prop.getName()); } diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java index b2d59e0b6..02a5f4727 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java +++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java @@ -19,11 +19,11 @@ import java.util.List; import java.util.Map; import org.junit.Test; +import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YSeqType; -import org.springframework.ide.vscode.commons.yaml.util.DescriptionProviders; import org.springframework.ide.vscode.manifest.yaml.ManifestYmlSchema; import com.google.common.collect.ImmutableSet; @@ -115,14 +115,14 @@ public class ManifestYmlSchemaTest { private void assertHasRealDescription(YTypedProperty p) { { - String noDescriptionText = DescriptionProviders.NO_DESCRIPTION.toHtml(); + String noDescriptionText = Renderables.NO_DESCRIPTION.toHtml(); String actual = p.getDescription().toHtml(); String msg = "Description missing for '"+p.getName()+"'"; assertTrue(msg, StringUtil.hasText(actual)); assertFalse(msg, noDescriptionText.equals(actual)); } { - String noDescriptionText = DescriptionProviders.NO_DESCRIPTION.toMarkdown(); + String noDescriptionText = Renderables.NO_DESCRIPTION.toMarkdown(); String actual = p.getDescription().toMarkdown(); String msg = "Description missing for '"+p.getName()+"'"; assertTrue(msg, StringUtil.hasText(actual));