From 1e2af95da5201465835a1f0a5e6d6168f8ca1c7f Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Tue, 15 Nov 2016 19:32:31 -0500 Subject: [PATCH 01/17] Javadoc support initial commit --- .../commons/commons-java/pom.xml | 5 ++ .../vscode/commons/jandex/JandexIndex.java | 64 ++++++++++++++----- .../ide/vscode/commons/jandex/Wrappers.java | 21 +++--- .../java/parser/CompilationUnitIndex.java | 13 ++++ .../parser/DefaultCompilationUnitIndex.java | 44 +++++++++++++ 5 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/CompilationUnitIndex.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/DefaultCompilationUnitIndex.java diff --git a/vscode-extensions/commons/commons-java/pom.xml b/vscode-extensions/commons/commons-java/pom.xml index abb8bdd51..b8cc94fc6 100644 --- a/vscode-extensions/commons/commons-java/pom.xml +++ b/vscode-extensions/commons/commons-java/pom.xml @@ -23,5 +23,10 @@ jandex 2.0.3.Final + + com.github.javaparser + javaparser-core + 2.5.1 + \ 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..871a6e7f0 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 @@ -6,11 +6,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.Iterator; +import java.util.List; import java.util.Optional; 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; @@ -24,12 +25,28 @@ import com.google.common.base.Suppliers; 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; + @FunctionalInterface + public static interface SourceContainerProvider { + File getSourceContainer(File container); + } + + private Supplier>> index; + + private SourceContainerProvider sourceContainerProvider; public JandexIndex(Stream classpathEntries) { this(classpathEntries, jarFile -> null, Optional.empty()); @@ -43,31 +60,38 @@ public class JandexIndex { this(classpathEntries, jarFile -> null, baseIndex); } + public void setSourceContainerProvider(SourceContainerProvider sourceContainerProvider) { + this.sourceContainerProvider = sourceContainerProvider; + } + + public SourceContainerProvider getSourceContainerProvider() { + return sourceContainerProvider; + } + public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, Optional baseIndex) { index = Suppliers.memoize(() -> { + List> indices = buildIndex(classpathEntries, indexFileFinder).collect(Collectors.toList()); if (baseIndex.isPresent()) { - return CompositeIndex.create(baseIndex.get().index.get(), buildIndex(classpathEntries, indexFileFinder)); - } else { - return buildIndex(classpathEntries, indexFileFinder); + indices.addAll(baseIndex.get().index.get()); } + return indices; }); } - 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 +158,16 @@ 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 className) { + Optional> pair = index.get().stream().map(e -> new Entry<>(e.key, e.value.getClassByName(className))).filter(e -> e.value != null).findFirst(); + if (pair.isPresent()) { + return Wrappers.wrap(this, pair.get().value, pair.get().key); + } else { + return null; + } } } 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..0d9e65476 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 @@ -2,6 +2,7 @@ package org.springframework.ide.vscode.commons.jandex; import static org.springframework.ide.vscode.commons.util.Assert.isNotNull; +import java.io.File; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,7 +30,7 @@ import org.springframework.ide.vscode.commons.util.HtmlSnippet; public class Wrappers { - public static IType wrap(IndexView index, ClassInfo info) { + public static IType wrap(JandexIndex index, ClassInfo info, File container) { if (info == null) { return null; } @@ -43,7 +44,7 @@ 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 @@ -89,26 +90,26 @@ public class Wrappers { @Override public IField getField(String name) { - return wrap(index, info.field(name)); + return wrap(index, info.field(name), container); } @Override public Stream getFields() { return info.fields().stream().map(f -> { - return wrap(index, f); + return wrap(index, f, container); }); } @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()])), container); } @Override public Stream getMethods() { return info.methods().stream().map(m -> { - return wrap(index, m); + return wrap(index, m, container); }); } @@ -120,7 +121,7 @@ public class Wrappers { }; } - public static IField wrap(IndexView index, FieldInfo field) { + public static IField wrap(JandexIndex index, FieldInfo field, File container) { if (field == null) { return null; } @@ -133,7 +134,7 @@ public class Wrappers { @Override public IType getDeclaringType() { - return wrap(index, field.declaringClass()); + return wrap(index, field.declaringClass(), container); } @Override @@ -170,7 +171,7 @@ public class Wrappers { }; } - public static IMethod wrap(IndexView index, MethodInfo method) { + public static IMethod wrap(JandexIndex index, MethodInfo method, File container) { isNotNull(index); isNotNull(method); return new IMethod() { @@ -182,7 +183,7 @@ public class Wrappers { @Override public IType getDeclaringType() { - return wrap(index, method.declaringClass()); + return wrap(index, method.declaringClass(), container); } @Override 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..ce8b26028 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/CompilationUnitIndex.java @@ -0,0 +1,13 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.net.URL; + +import com.github.javaparser.ast.CompilationUnit; + +public interface CompilationUnitIndex { + + static final CompilationUnitIndex DEFAULT = new DefaultCompilationUnitIndex(); + + CompilationUnit getCompilationUnit(URL url); + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/DefaultCompilationUnitIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/DefaultCompilationUnitIndex.java new file mode 100644 index 000000000..7a74b6fdd --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/DefaultCompilationUnitIndex.java @@ -0,0 +1,44 @@ +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; + +class DefaultCompilationUnitIndex implements 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; + } + +} From afd3762fe41462a24d1d7a1d8b2a3d1303b764b7 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Wed, 16 Nov 2016 19:26:00 -0500 Subject: [PATCH 02/17] Javadoc for source folders and maven atrifact jars --- .../metadata/hints/StsValueHint.java | 2 +- .../vscode/commons/jandex/JandexIndex.java | 44 +++-- .../ide/vscode/commons/jandex/Wrappers.java | 48 ++--- .../ide/vscode/commons/java/IJavaElement.java | 4 +- .../ide/vscode/commons/java/IJavadoc.java | 13 ++ .../vscode/commons/java/IJavadocProvider.java | 12 ++ .../java/parser/AbstractJavadocProvider.java | 160 +++++++++++++++ .../parser/JarSourcesJavadocProvider.java | 31 +++ .../vscode/commons/java/parser/Javadoc.java | 35 ++++ .../parser/SourceFolderJavadocProvider.java | 23 +++ .../ide/vscode/commons/maven/MavenCore.java | 2 +- .../commons/maven/java/MavenJavaProject.java | 4 +- .../maven/java/MavenProjectClasspath.java | 39 +++- .../JavaProjectWithClasspathFile.java | 186 +++++++++--------- .../vscode/commons/maven/JavaIndexTest.java | 67 ++++++- .../src/main/java/hello/Greeting.java | 28 +++ 16 files changed, 553 insertions(+), 145 deletions(-) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java 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 7d5909824..8fa72ba9c 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 @@ -117,7 +117,7 @@ public class StsValueHint { public static Provider javaDocSnippet(IJavaElement je) { return () -> { try { - HtmlSnippet jdoc = je.getJavaDoc(); + HtmlSnippet jdoc = HtmlSnippet.raw(je.getJavaDoc().html()); if (jdoc!=null) { return jdoc; } 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 871a6e7f0..c4143aaaf 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 @@ -8,6 +8,7 @@ import java.nio.file.Path; 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; @@ -17,11 +18,14 @@ 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.IJavadocProvider; import org.springframework.ide.vscode.commons.java.IType; 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 { @@ -40,35 +44,29 @@ public class JandexIndex { } @FunctionalInterface - public static interface SourceContainerProvider { - File getSourceContainer(File container); + public static interface JavadocProviderFactory { + IJavadocProvider createJavadocProvider(File jarContainer); } private Supplier>> index; - private SourceContainerProvider sourceContainerProvider; + private JavadocProviderFactory javadocProviderFactory; - public JandexIndex(Stream classpathEntries) { - this(classpathEntries, jarFile -> null, Optional.empty()); + private Cache javadocProvidersCache = CacheBuilder.newBuilder().build(); + + public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, JavadocProviderFactory javadocProviderFactory) { + this(classpathEntries, indexFileFinder, Optional.empty(), javadocProviderFactory); } - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder) { - this(classpathEntries, indexFileFinder, Optional.empty()); + 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 void setSourceContainerProvider(SourceContainerProvider sourceContainerProvider) { - this.sourceContainerProvider = sourceContainerProvider; - } - - public SourceContainerProvider getSourceContainerProvider() { - return sourceContainerProvider; - } - - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, Optional baseIndex) { + public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, Optional baseIndex, JavadocProviderFactory javadocProviderFactory) { index = Suppliers.memoize(() -> { List> indices = buildIndex(classpathEntries, indexFileFinder).collect(Collectors.toList()); if (baseIndex.isPresent()) { @@ -76,6 +74,7 @@ public class JandexIndex { } return indices; }); + this.javadocProviderFactory = javadocProviderFactory; } private Stream> buildIndex(Stream classpathEntries, IndexFileFinder indexFileFinder) { @@ -164,7 +163,14 @@ public class JandexIndex { IType getClassByName(DotName className) { Optional> pair = index.get().stream().map(e -> new Entry<>(e.key, e.value.getClassByName(className))).filter(e -> e.value != null).findFirst(); if (pair.isPresent()) { - return Wrappers.wrap(this, pair.get().value, pair.get().key); + File classpathResource = pair.get().key; + IJavadocProvider javadocProvider = null; + try { + javadocProvider = javadocProvidersCache.get(pair.get().key, () -> javadocProviderFactory == null ? null : javadocProviderFactory.createJavadocProvider(classpathResource)); + } catch (ExecutionException e) { + Log.log(e); + } + return Wrappers.wrap(this, pair.get().value, javadocProvider); } else { return null; } 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 0d9e65476..89baaf7e0 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 @@ -2,7 +2,6 @@ package org.springframework.ide.vscode.commons.jandex; import static org.springframework.ide.vscode.commons.util.Assert.isNotNull; -import java.io.File; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -21,16 +20,17 @@ 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.IJavadoc; +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; public class Wrappers { - public static IType wrap(JandexIndex index, ClassInfo info, File container) { + public static IType wrap(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) { if (info == null) { return null; } @@ -49,12 +49,12 @@ public class Wrappers { @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 @@ -65,7 +65,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 @@ -90,26 +90,26 @@ public class Wrappers { @Override public IField getField(String name) { - return wrap(index, info.field(name), container); + return wrap(index, info.field(name), javadocProvider); } @Override public Stream getFields() { return info.fields().stream().map(f -> { - return wrap(index, f, container); + 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()])), container); + 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, container); + return wrap(index, m, javadocProvider); }); } @@ -121,7 +121,7 @@ public class Wrappers { }; } - public static IField wrap(JandexIndex index, FieldInfo field, File container) { + public static IField wrap(JandexIndex index, FieldInfo field, IJavadocProvider javadocProvider) { if (field == null) { return null; } @@ -134,7 +134,7 @@ public class Wrappers { @Override public IType getDeclaringType() { - return wrap(index, field.declaringClass(), container); + return wrap(index, field.declaringClass(), javadocProvider); } @Override @@ -143,8 +143,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 @@ -155,7 +155,7 @@ public class Wrappers { @Override public Stream getAnnotations() { return field.annotations().stream().map(a -> { - return wrap(a); + return wrap(a, javadocProvider); }); } @@ -171,7 +171,7 @@ public class Wrappers { }; } - public static IMethod wrap(JandexIndex index, MethodInfo method, File container) { + public static IMethod wrap(JandexIndex index, MethodInfo method, IJavadocProvider javadocProvider) { isNotNull(index); isNotNull(method); return new IMethod() { @@ -183,7 +183,7 @@ public class Wrappers { @Override public IType getDeclaringType() { - return wrap(index, method.declaringClass(), container); + return wrap(index, method.declaringClass(), javadocProvider); } @Override @@ -192,8 +192,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 @@ -203,7 +203,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 @@ -233,7 +233,7 @@ public class Wrappers { }; } - public static IAnnotation wrap(AnnotationInstance annotation) { + public static IAnnotation wrap(AnnotationInstance annotation, IJavadocProvider javadocProvider) { isNotNull(annotation); return new IAnnotation() { @@ -243,8 +243,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 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..5be8993a0 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,7 @@ package org.springframework.ide.vscode.commons.java; -import org.springframework.ide.vscode.commons.util.HtmlSnippet; - 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/IJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java new file mode 100644 index 000000000..51bb2d893 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java @@ -0,0 +1,13 @@ +package org.springframework.ide.vscode.commons.java; + +public interface IJavadoc { + + String raw(); + + String plainText(); + + String html(); + + String markdown(); + +} 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..3eb42027a --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java @@ -0,0 +1,12 @@ +package org.springframework.ide.vscode.commons.java; + +public interface IJavadocProvider { + + IJavadoc getJavadoc(IType type); + + IJavadoc getJavadoc(IField field); + + IJavadoc getJavadoc(IMethod method); + + IJavadoc getJavadoc(IAnnotation method); +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java new file mode 100644 index 000000000..749b06bc8 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java @@ -0,0 +1,160 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.net.MalformedURLException; +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.IJavadoc; +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.util.Log; + +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +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 abstract class AbstractJavadocProvider implements IJavadocProvider { + + public IJavadoc getJavadoc(IType type) { + if (type.isEnum()) { + EnumDeclaration declaration = getEnumDeclaration(type); + return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + } else { + ClassOrInterfaceDeclaration declaration = getClassOrInterfaceDeclaration(type); + return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + } + } + + public IJavadoc getJavadoc(IField field) { + IType declaringType = field.getDeclaringType(); + if (declaringType.isEnum()) { + FieldDeclaration declaration = createVisitorToFindField(field).visit(getEnumDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + } else { + FieldDeclaration declaration = createVisitorToFindField(field).visit(getClassOrInterfaceDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + } + } + + 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 Javadoc(declaration.getJavaDoc()); + } else { + MethodDeclaration declaration = createVisitorToFindMethod(method).visit(getClassOrInterfaceDeclaration(declaringType), null); + return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + } + } + + public IJavadoc getJavadoc(IAnnotation annotation) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private CompilationUnit getCompilationUnit(IType type) { + try { + URL sourceUrl = createSourceUrl(type); + return CompilationUnitIndex.DEFAULT.getCompilationUnit(sourceUrl); + } catch (MalformedURLException 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; + } + }; + } + + abstract protected URL createSourceUrl(IType type) throws MalformedURLException; + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java new file mode 100644 index 000000000..5a2396ee7 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java @@ -0,0 +1,31 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.springframework.ide.vscode.commons.java.IType; + +import com.google.common.base.Supplier; + +public class JarSourcesJavadocProvider extends AbstractJavadocProvider { + + private Supplier sourcesJarUrl; + + public JarSourcesJavadocProvider(Supplier sourcesJarUrl) { + super(); + this.sourcesJarUrl = sourcesJarUrl; + } + + @Override + protected URL createSourceUrl(IType type) throws MalformedURLException { + StringBuilder sourceUrlStr = new StringBuilder(); + sourceUrlStr.append("jar:"); + sourceUrlStr.append(sourcesJarUrl.get()); + sourceUrlStr.append("!"); + sourceUrlStr.append('/'); + sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/")); + sourceUrlStr.append(".java"); + return new URL(sourceUrlStr.toString()); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java new file mode 100644 index 000000000..cbc1315a4 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java @@ -0,0 +1,35 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import org.springframework.ide.vscode.commons.java.IJavadoc; + +import com.github.javaparser.ast.comments.JavadocComment; + +final class Javadoc implements IJavadoc { + + private JavadocComment javadocComment; + + Javadoc(JavadocComment javadocComment) { + this.javadocComment = javadocComment; + } + + @Override + public String raw() { + return javadocComment.getContent(); + } + + @Override + public String plainText() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String html() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String markdown() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java new file mode 100644 index 000000000..0f2ce7983 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java @@ -0,0 +1,23 @@ +package org.springframework.ide.vscode.commons.java.parser; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.springframework.ide.vscode.commons.java.IType; + +public class SourceFolderJavadocProvider extends AbstractJavadocProvider { + + private File sourceFolder; + + public SourceFolderJavadocProvider(File sourceFolder) { + super(); + this.sourceFolder = sourceFolder; + } + + @Override + protected URL createSourceUrl(IType type) throws MalformedURLException { + return new File(sourceFolder, type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toURI().toURL(); + } + +} 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..cb1d1d8c6 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 @@ -83,7 +83,7 @@ public class MavenCore { private Supplier> javaCoreIndex = Suppliers.memoize(() -> { try { - return Optional.of(new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile))); + return Optional.of(new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), null)); } catch (MavenException e) { return Optional.empty(); } 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..6e951cc32 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 @@ -17,9 +17,9 @@ import java.nio.file.Paths; 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.IJavadoc; import org.springframework.ide.vscode.commons.java.IType; 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..d32b770c2 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,24 @@ package org.springframework.ide.vscode.commons.maven.java; import java.io.File; +import java.net.MalformedURLException; 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.JarSourcesJavadocProvider; +import org.springframework.ide.vscode.commons.java.parser.SourceFolderJavadocProvider; 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; @@ -53,7 +60,7 @@ 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), maven.getJavaIndexForJreLibs(), classpathResource -> createJavadocProvider(classpathResource)); }); } @@ -72,6 +79,36 @@ 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(); + } + + private IJavadocProvider createJavadocProvider(File classpathResource) { + System.out.println("--------> creating javadoc provider for " + classpathResource); + if (classpathResource.isDirectory()) { + if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { + return new SourceFolderJavadocProvider(new File(project.getBuild().getSourceDirectory())); + } else if (classpathResource.toString().startsWith(project.getBuild().getTestOutputDirectory())) { + return new SourceFolderJavadocProvider(new File(project.getBuild().getTestSourceDirectory())); + } else { + throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); + } + } else { + // Assume it's a JAR file + return new JarSourcesJavadocProvider(Suppliers.memoize(() -> { + try { + Artifact artifact = getArtifactFromJarFile(classpathResource).get(); + return maven.getSources(artifact).getFile().toURI().toURL(); + } 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; + })); + } + } @Override public Stream getClasspathResources() { 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..3e24c8c1e 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.IJavadoc; +import org.springframework.ide.vscode.commons.java.IType; + +/** + * 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/JavaIndexTest.java b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java index 48383a330..6bd927e9c 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,6 +11,7 @@ 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; @@ -85,7 +86,71 @@ public class JavaIndexTest { IMethod m = type.getMethod("", Stream.of(IPrimitiveType.INT)); assertEquals("", 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 testClassJavadocForOutputFolder() throws Exception { + MavenJavaProject project = 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().trim()); + + IField field = type.getField("id"); + assertNotNull(field); + assertEquals("* Comment for id field", field.getJavaDoc().raw().trim()); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + assertEquals("* Comment for getId()", method.getJavaDoc().raw().trim()); } + @Test + public void testInnerClassJavadocForOutputFolder() throws Exception { + MavenJavaProject project = 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().trim()); + + IField field = type.getField("innerField"); + assertNotNull(field); + assertEquals("* Comment for inner field", field.getJavaDoc().raw().trim()); + + IMethod method = type.getMethod("getInnerField", Stream.empty()); + assertNotNull(method); + assertEquals("* Comment for method inside nested class", method.getJavaDoc().raw().trim()); + } + + @Test + public void testClassJavadocForJar() throws Exception { + MavenJavaProject project = 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 expectedPrefix = "* {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a"; + assertEquals(expectedPrefix, type.getJavaDoc().raw().trim().substring(0, expectedPrefix.length())); + + type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener$LiquibasePresent"); + assertNotNull(type); + assertEquals("* Inner class to prevent class not found issues.", type.getJavaDoc().raw().trim()); + } + + @Test + public void testFieldAndMethodJavadocForJar() throws Exception { + MavenJavaProject project = 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().trim()); + + IMethod method = type.getMethod("getListeners", Stream.empty()); + assertNotNull(method); + String expectedPrefix = "* Returns read-only ordered Set of the {@link ApplicationListener}s that will be"; + assertEquals(expectedPrefix, method.getJavaDoc().raw().trim().substring(0, expectedPrefix.length())); + } + } 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..7baa70971 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,7 +1,13 @@ package hello; +/** + * Comment for Greeting class + */ public class Greeting { + /** + * Comment for id field + */ private final long id; private final String content; @@ -15,6 +21,9 @@ public class Greeting { this.content = content; } + /** + * Comment for getId() + */ public long getId() { return id; } @@ -22,4 +31,23 @@ public class Greeting { public String getContent() { return content; } + + /** + * Comment for inner class + */ + private static class TestInnerClass { + + /** + * Comment for inner field + */ + int innerField; + + /** + * Comment for method inside nested class + */ + public int getInnerField() { + return innerField; + } + + } } From c50cf8b88317b31b65e25001806d6b992008f439 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Wed, 16 Nov 2016 22:03:57 -0500 Subject: [PATCH 03/17] Ensure JavadocProviderFactory is used for the index instance only --- .../vscode/commons/jandex/JandexIndex.java | 89 +++++++++++++------ .../ide/vscode/commons/maven/MavenCore.java | 9 +- .../maven/java/MavenProjectClasspath.java | 3 +- 3 files changed, 67 insertions(+), 34 deletions(-) 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 c4143aaaf..198e281ae 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,6 +5,7 @@ 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; @@ -18,7 +19,11 @@ 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.IJavadoc; 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.util.Log; @@ -48,15 +53,37 @@ public class JandexIndex { IJavadocProvider createJavadocProvider(File jarContainer); } + 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(); - - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, JavadocProviderFactory javadocProviderFactory) { - this(classpathEntries, indexFileFinder, Optional.empty(), javadocProviderFactory); - } + + private JandexIndex[] baseIndex; public void setJvadocProviderFactory(JavadocProviderFactory sourceContainerProvider) { this.javadocProviderFactory = sourceContainerProvider; @@ -66,14 +93,9 @@ public class JandexIndex { return javadocProviderFactory; } - public JandexIndex(Stream classpathEntries, IndexFileFinder indexFileFinder, Optional baseIndex, JavadocProviderFactory javadocProviderFactory) { - index = Suppliers.memoize(() -> { - List> indices = buildIndex(classpathEntries, indexFileFinder).collect(Collectors.toList()); - if (baseIndex.isPresent()) { - indices.addAll(baseIndex.get().index.get()); - } - return indices; - }); + 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; } @@ -159,21 +181,34 @@ public class JandexIndex { public IType findType(String fqName) { return getClassByName(DotName.createSimple(fqName)); } - - IType getClassByName(DotName className) { - Optional> pair = index.get().stream().map(e -> new Entry<>(e.key, e.value.getClassByName(className))).filter(e -> e.value != null).findFirst(); - if (pair.isPresent()) { - File classpathResource = pair.get().key; - IJavadocProvider javadocProvider = null; - try { - javadocProvider = javadocProvidersCache.get(pair.get().key, () -> javadocProviderFactory == null ? null : javadocProviderFactory.createJavadocProvider(classpathResource)); - } catch (ExecutionException e) { - Log.log(e); - } - return Wrappers.wrap(this, pair.get().value, javadocProvider); - } else { - return null; - } + + 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-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 cb1d1d8c6..54aebce25 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 @@ -23,7 +23,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; @@ -81,11 +80,11 @@ 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), null)); + return new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), null); } catch (MavenException e) { - return Optional.empty(); + return null; } }); @@ -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/MavenProjectClasspath.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java index d32b770c2..ee7a8d576 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 @@ -60,7 +60,7 @@ public class MavenProjectClasspath implements IClasspath { } catch (Exception e) { Log.log(e); } - return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), maven.getJavaIndexForJreLibs(), classpathResource -> createJavadocProvider(classpathResource)); + return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> createJavadocProvider(classpathResource), maven.getJavaIndexForJreLibs()); }); } @@ -85,7 +85,6 @@ public class MavenProjectClasspath implements IClasspath { } private IJavadocProvider createJavadocProvider(File classpathResource) { - System.out.println("--------> creating javadoc provider for " + classpathResource); if (classpathResource.isDirectory()) { if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { return new SourceFolderJavadocProvider(new File(project.getBuild().getSourceDirectory())); From ff8c5ce491a29af6bb6847ac7dab1a6681efa53f Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 17 Nov 2016 11:11:18 -0500 Subject: [PATCH 04/17] Factor staff out in javadoc package --- .../vscode/commons/jandex/JandexIndex.java | 2 +- .../ide/vscode/commons/jandex/Wrappers.java | 2 +- .../ide/vscode/commons/java/IJavaElement.java | 2 + .../vscode/commons/java/IJavadocProvider.java | 2 + .../java/parser/AbstractJavadocProvider.java | 15 ++--- .../vscode/commons/java/parser/Javadoc.java | 35 ----------- .../commons/{java => javadoc}/IJavadoc.java | 2 +- .../vscode/commons/javadoc/RawJavadoc.java | 31 ++++++++++ .../commons/maven/java/MavenJavaProject.java | 2 +- .../JavaProjectWithClasspathFile.java | 2 +- .../vscode/commons/maven/JavaIndexTest.java | 61 ++++++++++++++----- 11 files changed, 94 insertions(+), 62 deletions(-) delete mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java rename vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/{java => javadoc}/IJavadoc.java (66%) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/RawJavadoc.java 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 198e281ae..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 @@ -21,10 +21,10 @@ 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.IJavadoc; 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; 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 89baaf7e0..e7128ed0e 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 @@ -20,13 +20,13 @@ 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.IJavadoc; 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.javadoc.IJavadoc; public class Wrappers { 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 5be8993a0..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,5 +1,7 @@ package org.springframework.ide.vscode.commons.java; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + public interface IJavaElement { String getElementName(); IJavadoc getJavaDoc(); 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 index 3eb42027a..2766706bc 100644 --- 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 @@ -1,5 +1,7 @@ package org.springframework.ide.vscode.commons.java; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + public interface IJavadocProvider { IJavadoc getJavadoc(IType type); diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java index 749b06bc8..d9ca22434 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java @@ -6,10 +6,11 @@ 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.IJavadoc; 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.util.Log; import com.github.javaparser.ast.CompilationUnit; @@ -25,10 +26,10 @@ public abstract class AbstractJavadocProvider implements IJavadocProvider { public IJavadoc getJavadoc(IType type) { if (type.isEnum()) { EnumDeclaration declaration = getEnumDeclaration(type); - return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); } else { ClassOrInterfaceDeclaration declaration = getClassOrInterfaceDeclaration(type); - return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); } } @@ -36,10 +37,10 @@ public abstract class AbstractJavadocProvider implements IJavadocProvider { IType declaringType = field.getDeclaringType(); if (declaringType.isEnum()) { FieldDeclaration declaration = createVisitorToFindField(field).visit(getEnumDeclaration(declaringType), null); - return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + 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 Javadoc(declaration.getJavaDoc()); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); } } @@ -50,10 +51,10 @@ public abstract class AbstractJavadocProvider implements IJavadocProvider { IType declaringType = method.getDeclaringType(); if (declaringType.isEnum()) { MethodDeclaration declaration = createVisitorToFindMethod(method).visit(getEnumDeclaration(declaringType), null); - return declaration.getJavaDoc() == null ? null : new Javadoc(declaration.getJavaDoc()); + 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 Javadoc(declaration.getJavaDoc()); + return declaration.getJavaDoc() == null ? null : new RawJavadoc(declaration.getJavaDoc().toString()); } } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java deleted file mode 100644 index cbc1315a4..000000000 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/Javadoc.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.springframework.ide.vscode.commons.java.parser; - -import org.springframework.ide.vscode.commons.java.IJavadoc; - -import com.github.javaparser.ast.comments.JavadocComment; - -final class Javadoc implements IJavadoc { - - private JavadocComment javadocComment; - - Javadoc(JavadocComment javadocComment) { - this.javadocComment = javadocComment; - } - - @Override - public String raw() { - return javadocComment.getContent(); - } - - @Override - public String plainText() { - throw new UnsupportedOperationException("Not yet implemnted"); - } - - @Override - public String html() { - throw new UnsupportedOperationException("Not yet implemnted"); - } - - @Override - public String markdown() { - throw new UnsupportedOperationException("Not yet implemnted"); - } - -} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/IJavadoc.java similarity index 66% rename from vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java rename to vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/IJavadoc.java index 51bb2d893..f50e5c4f1 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadoc.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/IJavadoc.java @@ -1,4 +1,4 @@ -package org.springframework.ide.vscode.commons.java; +package org.springframework.ide.vscode.commons.javadoc; public interface IJavadoc { 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..1cee09aaf --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/RawJavadoc.java @@ -0,0 +1,31 @@ +package org.springframework.ide.vscode.commons.javadoc; + +public class RawJavadoc implements IJavadoc { + + private String rawContent; + + public RawJavadoc(String rawContent) { + this.rawContent = rawContent; + } + + @Override + public String raw() { + return rawContent; + } + + @Override + public String plainText() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String html() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String markdown() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + +} 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 6e951cc32..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 @@ -17,8 +17,8 @@ import java.nio.file.Paths; 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.IJavadoc; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; import org.springframework.ide.vscode.commons.maven.MavenCore; /** 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 3e24c8c1e..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 @@ -15,8 +15,8 @@ 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.IJavadoc; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; /** * Java project that contains classpath text file 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 6bd927e9c..742b51fcb 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 @@ -94,16 +94,31 @@ public class JavaIndexTest { MavenJavaProject project = 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().trim()); + assertNotNull(type); + String expected = String.join("\n", + "/**", + " * Comment for Greeting class ", + " */" + ); + assertEquals(expected, type.getJavaDoc().raw().trim()); IField field = type.getField("id"); assertNotNull(field); - assertEquals("* Comment for id field", field.getJavaDoc().raw().trim()); + expected = String.join("\n", + "/**", + " * Comment for id field", + " */" + ); + assertEquals(expected, field.getJavaDoc().raw().trim()); IMethod method = type.getMethod("getId", Stream.empty()); assertNotNull(method); - assertEquals("* Comment for getId()", method.getJavaDoc().raw().trim()); + expected = String.join("\n", + "/**", + " * Comment for getId()", + " */" + ); + assertEquals(expected, method.getJavaDoc().raw().trim()); } @Test @@ -111,15 +126,15 @@ public class JavaIndexTest { MavenJavaProject project = 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().trim()); + assertEquals("/**\n * Comment for inner class\n */", type.getJavaDoc().raw().trim()); IField field = type.getField("innerField"); assertNotNull(field); - assertEquals("* Comment for inner field", field.getJavaDoc().raw().trim()); + assertEquals("/**\n \t * Comment for inner field\n \t */", field.getJavaDoc().raw().trim()); IMethod method = type.getMethod("getInnerField", Stream.empty()); assertNotNull(method); - assertEquals("* Comment for method inside nested class", method.getJavaDoc().raw().trim()); + assertEquals("/**\n \t * Comment for method inside nested class\n \t */", method.getJavaDoc().raw().trim()); } @Test @@ -127,13 +142,21 @@ public class JavaIndexTest { MavenJavaProject project = 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 expectedPrefix = "* {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a"; - assertEquals(expectedPrefix, type.getJavaDoc().raw().trim().substring(0, expectedPrefix.length())); + 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); - assertEquals("* Inner class to prevent class not found issues.", type.getJavaDoc().raw().trim()); + assertNotNull(type); + expected = String.join("\n", + "/**", + " * Inner class to prevent class not found issues.", + " */" + ); + assertEquals(expected, type.getJavaDoc().raw().trim()); } @Test @@ -145,12 +168,20 @@ public class JavaIndexTest { IField field = type.getField("BANNER_LOCATION_PROPERTY_VALUE"); assertNotNull(field); - assertEquals("* Default banner location.", field.getJavaDoc().raw().trim()); + String expected = String.join("\n", + "/**", + " * Default banner location.", + " */" + ); + assertEquals(expected, field.getJavaDoc().raw().trim()); IMethod method = type.getMethod("getListeners", Stream.empty()); assertNotNull(method); - String expectedPrefix = "* Returns read-only ordered Set of the {@link ApplicationListener}s that will be"; - assertEquals(expectedPrefix, method.getJavaDoc().raw().trim().substring(0, expectedPrefix.length())); + 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())); } } From f2abf2d21db988c1593717cdd1a56a3d3ce066fd Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 17 Nov 2016 14:34:07 -0500 Subject: [PATCH 05/17] Factor common logic for javadoc in javadoc package --- .../parser/JarSourcesJavadocProvider.java | 31 ----------------- ...adocProvider.java => JavadocProvider.java} | 16 +++++---- .../parser/SourceFolderJavadocProvider.java | 23 ------------- .../commons/javadoc/SourceUrlProvider.java | 12 +++++++ .../SourceUrlProviderFromSourceContainer.java | 29 ++++++++++++++++ .../maven/java/MavenProjectClasspath.java | 33 ++++++++++++------- 6 files changed, 72 insertions(+), 72 deletions(-) delete mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java rename vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/{AbstractJavadocProvider.java => JavadocProvider.java} (94%) delete mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java deleted file mode 100644 index 5a2396ee7..000000000 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JarSourcesJavadocProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springframework.ide.vscode.commons.java.parser; - -import java.net.MalformedURLException; -import java.net.URL; - -import org.springframework.ide.vscode.commons.java.IType; - -import com.google.common.base.Supplier; - -public class JarSourcesJavadocProvider extends AbstractJavadocProvider { - - private Supplier sourcesJarUrl; - - public JarSourcesJavadocProvider(Supplier sourcesJarUrl) { - super(); - this.sourcesJarUrl = sourcesJarUrl; - } - - @Override - protected URL createSourceUrl(IType type) throws MalformedURLException { - StringBuilder sourceUrlStr = new StringBuilder(); - sourceUrlStr.append("jar:"); - sourceUrlStr.append(sourcesJarUrl.get()); - sourceUrlStr.append("!"); - sourceUrlStr.append('/'); - sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/")); - sourceUrlStr.append(".java"); - return new URL(sourceUrlStr.toString()); - } - -} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java similarity index 94% rename from vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java rename to vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java index d9ca22434..110e5a0e3 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/AbstractJavadocProvider.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java @@ -1,6 +1,5 @@ package org.springframework.ide.vscode.commons.java.parser; -import java.net.MalformedURLException; import java.net.URL; import java.util.Optional; @@ -11,6 +10,7 @@ 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; @@ -21,7 +21,13 @@ import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.visitor.GenericVisitorAdapter; -public abstract class AbstractJavadocProvider implements IJavadocProvider { +public class JavadocProvider implements IJavadocProvider { + + private SourceUrlProvider sourceUrlProvider; + + public JavadocProvider(SourceUrlProvider sourceUrlProvider) { + this.sourceUrlProvider = sourceUrlProvider; + } public IJavadoc getJavadoc(IType type) { if (type.isEnum()) { @@ -64,9 +70,9 @@ public abstract class AbstractJavadocProvider implements IJavadocProvider { private CompilationUnit getCompilationUnit(IType type) { try { - URL sourceUrl = createSourceUrl(type); + URL sourceUrl = sourceUrlProvider.sourceUrl(type); return CompilationUnitIndex.DEFAULT.getCompilationUnit(sourceUrl); - } catch (MalformedURLException e) { + } catch (Exception e) { Log.log("Invalid source URL for type " + type, e); return null; } @@ -156,6 +162,4 @@ public abstract class AbstractJavadocProvider implements IJavadocProvider { }; } - abstract protected URL createSourceUrl(IType type) throws MalformedURLException; - } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java deleted file mode 100644 index 0f2ce7983..000000000 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/SourceFolderJavadocProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.springframework.ide.vscode.commons.java.parser; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; - -import org.springframework.ide.vscode.commons.java.IType; - -public class SourceFolderJavadocProvider extends AbstractJavadocProvider { - - private File sourceFolder; - - public SourceFolderJavadocProvider(File sourceFolder) { - super(); - this.sourceFolder = sourceFolder; - } - - @Override - protected URL createSourceUrl(IType type) throws MalformedURLException { - return new File(sourceFolder, type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toURI().toURL(); - } - -} 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..c92f3e5ef --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java @@ -0,0 +1,29 @@ +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(); + }; + + URL sourceUrl(URL sourceContainerUrl, IType type) throws Exception; + +} 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 ee7a8d576..cdbf82b2a 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 @@ -12,8 +12,8 @@ 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; @@ -25,8 +25,8 @@ 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.JarSourcesJavadocProvider; -import org.springframework.ide.vscode.commons.java.parser.SourceFolderJavadocProvider; +import org.springframework.ide.vscode.commons.java.parser.JavadocProvider; +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; @@ -45,7 +45,7 @@ public class MavenProjectClasspath implements IClasspath { private MavenCore maven; private MavenProject project; private Supplier javaIndex; - + public MavenProjectClasspath(MavenProject project) { this(project, MavenCore.getInstance()); } @@ -67,9 +67,9 @@ public class MavenProjectClasspath implements IClasspath { @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) { @@ -87,25 +87,34 @@ public class MavenProjectClasspath implements IClasspath { private IJavadocProvider createJavadocProvider(File classpathResource) { if (classpathResource.isDirectory()) { if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { - return new SourceFolderJavadocProvider(new File(project.getBuild().getSourceDirectory())); + return new JavadocProvider(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 SourceFolderJavadocProvider(new File(project.getBuild().getTestSourceDirectory())); + return new JavadocProvider(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 JarSourcesJavadocProvider(Suppliers.memoize(() -> { + return new JavadocProvider(type -> { try { Artifact artifact = getArtifactFromJarFile(classpathResource).get(); - return maven.getSources(artifact).getFile().toURI().toURL(); + URL sourceContainer = maven.getSources(artifact).getFile().toURI().toURL(); + System.out.println("----> SOURCE " + sourceContainer); + 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; - })); + }); } } From 2c9a250ec5174c146c63a4d3cf0f2ef12bb04bee Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Thu, 17 Nov 2016 17:14:51 -0500 Subject: [PATCH 06/17] Roaster parser and javadoc support --- .../commons/commons-java/pom.xml | 17 +++ .../vscode/commons/java/IJavadocProvider.java | 2 +- ...ovider.java => ParserJavadocProvider.java} | 4 +- .../java/roaster/DefaultJavaUnitIndex.java | 41 ++++++ .../commons/java/roaster/JavaUnitIndex.java | 13 ++ .../commons/java/roaster/RoasterJavadoc.java | 34 +++++ .../java/roaster/RoasterJavadocProvider.java | 102 +++++++++++++++ .../maven/java/MavenProjectClasspath.java | 111 ++++++++++------ .../vscode/commons/maven/JavaIndexTest.java | 121 +++++++++++++++--- 9 files changed, 390 insertions(+), 55 deletions(-) rename vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/{JavadocProvider.java => ParserJavadocProvider.java} (97%) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/DefaultJavaUnitIndex.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/JavaUnitIndex.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadoc.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadocProvider.java diff --git a/vscode-extensions/commons/commons-java/pom.xml b/vscode-extensions/commons/commons-java/pom.xml index b8cc94fc6..ca5b0540f 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 @@ -28,5 +34,16 @@ 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/java/IJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavadocProvider.java index 2766706bc..934535df2 100644 --- 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 @@ -10,5 +10,5 @@ public interface IJavadocProvider { IJavadoc getJavadoc(IMethod method); - IJavadoc getJavadoc(IAnnotation method); + IJavadoc getJavadoc(IAnnotation annotation); } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java similarity index 97% rename from vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java rename to vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java index 110e5a0e3..f4a215da3 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JavadocProvider.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/ParserJavadocProvider.java @@ -21,11 +21,11 @@ import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.visitor.GenericVisitorAdapter; -public class JavadocProvider implements IJavadocProvider { +public class ParserJavadocProvider implements IJavadocProvider { private SourceUrlProvider sourceUrlProvider; - public JavadocProvider(SourceUrlProvider sourceUrlProvider) { + public ParserJavadocProvider(SourceUrlProvider sourceUrlProvider) { this.sourceUrlProvider = sourceUrlProvider; } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/DefaultJavaUnitIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/DefaultJavaUnitIndex.java new file mode 100644 index 000000000..bff5cbb26 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/DefaultJavaUnitIndex.java @@ -0,0 +1,41 @@ +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; + +class DefaultJavaUnitIndex implements 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; + } + +} 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..c975d68e9 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/JavaUnitIndex.java @@ -0,0 +1,13 @@ +package org.springframework.ide.vscode.commons.java.roaster; + +import java.net.URL; + +import org.jboss.forge.roaster.model.JavaUnit; + +public interface JavaUnitIndex { + + static final JavaUnitIndex DEFAULT = new DefaultJavaUnitIndex(); + + 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..420c14620 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/roaster/RoasterJavadoc.java @@ -0,0 +1,34 @@ +package org.springframework.ide.vscode.commons.java.roaster; + +import org.jboss.forge.roaster.model.JavaDoc; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; + +public class RoasterJavadoc implements IJavadoc { + + private JavaDoc javadoc; + + public RoasterJavadoc(JavaDoc javadoc) { + this.javadoc = javadoc; + } + + @Override + public String raw() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public String plainText() { + return javadoc.getFullText(); + } + + @Override + public String html() { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public String markdown() { + 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-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 cdbf82b2a..2d2e2e9a1 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 @@ -25,7 +25,8 @@ 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.JavadocProvider; +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.SourceUrlProviderFromSourceContainer; import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.MavenException; @@ -41,6 +42,8 @@ import com.google.common.base.Suppliers; * */ public class MavenProjectClasspath implements IClasspath { + + public static boolean USE_JAVA_PARSER = false; private MavenCore maven; private MavenProject project; @@ -60,7 +63,9 @@ public class MavenProjectClasspath implements IClasspath { } catch (Exception e) { Log.log(e); } - return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> createJavadocProvider(classpathResource), maven.getJavaIndexForJreLibs()); + return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> { + return USE_JAVA_PARSER ? createParserJavadocProvider(classpathResource) : createRoasterJavadocProvider(classpathResource); + }, maven.getJavaIndexForJreLibs()); }); } @@ -84,40 +89,6 @@ public class MavenProjectClasspath implements IClasspath { return maven.resolveDependencies(project, null).stream().filter(a -> file.equals(a.getFile())).findFirst(); } - private IJavadocProvider createJavadocProvider(File classpathResource) { - if (classpathResource.isDirectory()) { - if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { - return new JavadocProvider(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 JavadocProvider(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 JavadocProvider(type -> { - try { - Artifact artifact = getArtifactFromJarFile(classpathResource).get(); - URL sourceContainer = maven.getSources(artifact).getFile().toURI().toURL(); - System.out.println("----> SOURCE " + sourceContainer); - 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; - }); - } - } - @Override public Stream getClasspathResources() { return project.getBuild().getResources().stream().flatMap(resource -> { @@ -135,4 +106,72 @@ public class MavenProjectClasspath implements IClasspath { }); } + 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(); + System.out.println("----> SOURCE " + sourceContainer); + 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(); + System.out.println("----> SOURCE " + sourceContainer); + 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; + }); + } + } + } 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 742b51fcb..b2aaa00e7 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 @@ -17,6 +17,7 @@ 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.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.commons.maven.java.MavenProjectClasspath; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -24,41 +25,56 @@ 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 Path load(String projectName) throws Exception { + Path testProjectPath = Paths.get(DependencyTreeTest.class.getResource("/" + projectName).toURI()); + MavenCore.buildMavenProject(testProjectPath); + 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()); MavenCore.buildMavenProject(testProjectPath); - return new MavenJavaProject(testProjectPath.resolve(MavenCore.POM_XML).toFile()); + 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()); @@ -69,7 +85,7 @@ 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()); @@ -80,7 +96,7 @@ public class JavaIndexTest { @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)); @@ -90,8 +106,9 @@ public class JavaIndexTest { } @Test - public void testClassJavadocForOutputFolder() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + public void parser_testClassJavadocForOutputFolder() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = true; + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("hello.Greeting"); assertNotNull(type); @@ -122,8 +139,9 @@ public class JavaIndexTest { } @Test - public void testInnerClassJavadocForOutputFolder() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + public void parser_testInnerClassJavadocForOutputFolder() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = true; + 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()); @@ -138,8 +156,9 @@ public class JavaIndexTest { } @Test - public void testClassJavadocForJar() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + public void parser_testClassJavadocForJar() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = true; + 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); @@ -160,8 +179,9 @@ public class JavaIndexTest { } @Test - public void testFieldAndMethodJavadocForJar() throws Exception { - MavenJavaProject project = projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file"); + public void parser_testFieldAndMethodJavadocForJar() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = true; + 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); @@ -184,4 +204,73 @@ public class JavaIndexTest { assertEquals(expected, method.getJavaDoc().raw().trim().substring(0, expected.length())); } + @Test + public void roaster_testClassJavadocForOutputFolder() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = false; + 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().plainText()); + + IField field = type.getField("id"); + assertNotNull(field); + assertEquals("Comment for id field", field.getJavaDoc().plainText()); + + IMethod method = type.getMethod("getId", Stream.empty()); + assertNotNull(method); + assertEquals("Comment for getId()", method.getJavaDoc().plainText()); + } + + @Test + public void roaster_testInnerClassJavadocForOutputFolder() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = false; + 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().plainText()); + + IField field = type.getField("innerField"); + assertNotNull(field); + assertEquals("Comment for inner field", field.getJavaDoc().plainText()); + + IMethod method = type.getMethod("getInnerField", Stream.empty()); + assertNotNull(method); + assertEquals("Comment for method inside nested class", method.getJavaDoc().plainText()); + } + + @Test + public void roaster_testClassJavadocForJar() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = false; + 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().plainText().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().plainText()); + } + + @Test + public void roaster_testFieldAndMethodJavadocForJar() throws Exception { + MavenProjectClasspath.USE_JAVA_PARSER = false; + 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().plainText()); + + 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().plainText().substring(0, expected.length())); + } + + } From 26b95f32636e1786d1d1e201e4eb8bfa52265e15 Mon Sep 17 00:00:00 2001 From: BoykoAlex Date: Fri, 18 Nov 2016 20:01:26 -0500 Subject: [PATCH 07/17] Javadoc from Javadoc jars --- .../commons/commons-java/pom.xml | 6 + .../ide/vscode/commons/jandex/Wrappers.java | 27 +- .../ide/vscode/commons/java/IMethod.java | 2 + .../ide/vscode/commons/java/IType.java | 1 + .../javadoc/DefaultHtmlJavadocIndex.java | 45 + .../vscode/commons/javadoc/HtmlJavadoc.java | 35 + .../commons/javadoc/HtmlJavadocIndex.java | 14 + .../commons/javadoc/HtmlJavadocProvider.java | 66 + .../SourceUrlProviderFromSourceContainer.java | 24 + .../javadoc/internal/CharOperation.java | 4190 +++++++++++++++++ .../internal/HashtableOfObjectToIntArray.java | 158 + .../javadoc/internal/JavadocConstants.java | 35 + .../javadoc/internal/JavadocContents.java | 539 +++ .../javadoc/internal/ScannerHelper.java | 569 +++ .../ide/vscode/commons/maven/MavenCore.java | 44 +- .../maven/java/MavenProjectClasspath.java | 56 +- .../vscode/commons/maven/JavaIndexTest.java | 154 +- .../pom.xml | 10 + .../src/main/java/hello/Greeting.java | 4 +- 19 files changed, 5948 insertions(+), 31 deletions(-) create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java create mode 100644 vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java diff --git a/vscode-extensions/commons/commons-java/pom.xml b/vscode-extensions/commons/commons-java/pom.xml index ca5b0540f..558fca823 100644 --- a/vscode-extensions/commons/commons-java/pom.xml +++ b/vscode-extensions/commons/commons-java/pom.xml @@ -29,6 +29,12 @@ jandex 2.0.3.Final + + + com.kotcrab.remark + remark + 1.0.0 + com.github.javaparser javaparser-core diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java index e7128ed0e..7b78b36a0 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/Wrappers.java @@ -11,7 +11,6 @@ import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; -import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.Type; @@ -30,6 +29,8 @@ import org.springframework.ide.vscode.commons.javadoc.IJavadoc; public class Wrappers { + private static final String JANDEX_CONTRUCTOR_NAME = ""; + public static IType wrap(JandexIndex index, ClassInfo info, IJavadocProvider javadocProvider) { if (info == null) { return null; @@ -80,9 +81,14 @@ public class Wrappers { @Override public boolean isInterface() { - return false; + return Flags.isInterface(info.flags()); } + @Override + public boolean isAnnotation() { + return Flags.isAnnotation(info.flags()); + } + @Override public String getFullyQualifiedName() { return info.name().toString(); @@ -117,7 +123,7 @@ public class Wrappers { public String toString() { return info.toString(); } - + }; } @@ -180,6 +186,11 @@ public class Wrappers { public int getFlags() { return method.flags(); } + + @Override + public boolean isConstructor() { + return method.name().equals(JANDEX_CONTRUCTOR_NAME); + } @Override public IType getDeclaringType() { @@ -188,7 +199,7 @@ public class Wrappers { @Override public String getElementName() { - return method.name(); + return isConstructor() ? getDeclaringType().getElementName() : method.name(); } @Override @@ -230,6 +241,7 @@ public class Wrappers { public Stream parameters() { return method.parameters().stream().map(Wrappers::wrap); } + }; } @@ -289,13 +301,6 @@ public class Wrappers { }; } - public static Type createParameterTypeFromSignature(IndexView index, String signature) { - if (signature == null) { - return null; - } - throw new UnsupportedOperationException("Not yet implemented"); - } - public static IPrimitiveType wrap(PrimitiveType type) { switch (type.primitive()) { case SHORT: diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java index eed8b3953..06440c551 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IMethod.java @@ -51,5 +51,7 @@ public interface IMethod extends IMember { * @return */ Stream parameters(); + + boolean isConstructor(); } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java index dae58510e..d5e1e22cf 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java @@ -23,6 +23,7 @@ public interface IType extends IMember { boolean isClass(); boolean isEnum(); boolean isInterface(); + boolean isAnnotation(); /** * Returns the fully qualified name of this type, diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java new file mode 100644 index 000000000..9bf2d56e5 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/DefaultHtmlJavadocIndex.java @@ -0,0 +1,45 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +public class DefaultHtmlJavadocIndex implements HtmlJavadocIndex { + + private Cache cache = CacheBuilder.newBuilder().build(); + + private static JavadocContents NO_HTML_CONTENT = new JavadocContents(null); + + @Override + public JavadocContents getHtmlJavadoc(URL url) { + try { + JavadocContents content = cache.get(url, () -> { + InputStream stream = null; + try { + stream = url.openStream(); + BufferedReader buffer = new BufferedReader(new InputStreamReader(stream)); + return new JavadocContents(buffer.lines().collect(Collectors.joining("\n"))); + } catch (IOException e) { + return NO_HTML_CONTENT; + } finally { + if (stream != null) { + stream.close(); + } + } + }); + return content == NO_HTML_CONTENT ? null : content; + } catch (ExecutionException e) { + return null; + } + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java new file mode 100644 index 000000000..3e95c61b9 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadoc.java @@ -0,0 +1,35 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import com.overzealous.remark.Remark; + +public class HtmlJavadoc implements IJavadoc { + + private String html; + private Remark remark; + + public HtmlJavadoc(String html) { + this.html = html; + this.remark = new Remark(); + } + + @Override + public String raw() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String plainText() { + throw new UnsupportedOperationException("Not yet implemnted"); + } + + @Override + public String html() { + return html; + } + + @Override + public String markdown() { + return remark.convertFragment(html); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java new file mode 100644 index 000000000..434d48ad5 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocIndex.java @@ -0,0 +1,14 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import java.net.URL; + +import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents; + + +public interface HtmlJavadocIndex { + + public static final HtmlJavadocIndex DEFAULT = new DefaultHtmlJavadocIndex(); + + JavadocContents getHtmlJavadoc(URL url); + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java new file mode 100644 index 000000000..4adb55985 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/HtmlJavadocProvider.java @@ -0,0 +1,66 @@ +package org.springframework.ide.vscode.commons.javadoc; + +import java.net.URL; + +import org.springframework.ide.vscode.commons.java.IAnnotation; +import org.springframework.ide.vscode.commons.java.IField; +import org.springframework.ide.vscode.commons.java.IJavadocProvider; +import org.springframework.ide.vscode.commons.java.IMethod; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.internal.JavadocContents; +import org.springframework.ide.vscode.commons.util.Log; + +public class HtmlJavadocProvider implements IJavadocProvider { + + private SourceUrlProvider htmlUrlProvider; + + public HtmlJavadocProvider(SourceUrlProvider htmlUrlProvider) { + this.htmlUrlProvider = htmlUrlProvider; + } + + @Override + public IJavadoc getJavadoc(IType type) { + try { + JavadocContents javadocContents = findHtml(type); + return new HtmlJavadoc(javadocContents.getTypeDoc(type)); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IField field) { + try { + IType declaringType = field.getDeclaringType(); + JavadocContents javadocContents = findHtml(declaringType); + return new HtmlJavadoc(javadocContents.getFieldDoc(field)); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IMethod method) { + try { + IType declaringType = method.getDeclaringType(); + JavadocContents javadocContents = findHtml(declaringType); + return new HtmlJavadoc(javadocContents.getMethodDoc(method)); + } catch (Exception e) { + Log.log(e); + return null; + } + } + + @Override + public IJavadoc getJavadoc(IAnnotation annotation) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + private JavadocContents findHtml(IType type) throws Exception { + URL url = htmlUrlProvider.sourceUrl(type); + return HtmlJavadocIndex.DEFAULT.getHtmlJavadoc(url); + } + +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java index c92f3e5ef..633a31ade 100644 --- a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/SourceUrlProviderFromSourceContainer.java @@ -24,6 +24,30 @@ public interface SourceUrlProviderFromSourceContainer { return Paths.get(sourceContainerUrl.toURI()).resolve(type.getFullyQualifiedName().replaceAll("\\.", "/") + ".java").toUri().toURL(); }; + public static final SourceUrlProviderFromSourceContainer JAR_JAVADOC_URL_PROVIDER = (javadocContainerUrl, type) -> { + StringBuilder sourceUrlStr = new StringBuilder(); + sourceUrlStr.append("jar:"); + sourceUrlStr.append(javadocContainerUrl); + sourceUrlStr.append("!"); + sourceUrlStr.append('/'); + // Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files + sourceUrlStr.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", ".")); + sourceUrlStr.append(".html"); + return new URL(sourceUrlStr.toString()); + + }; + + public static final SourceUrlProviderFromSourceContainer JAVADOC_FOLDER_URL_SUPPLIER = (sourceContainerUrl, type) -> { + String urlStr = sourceContainerUrl.toString(); + StringBuilder sb = new StringBuilder(urlStr); + if (!urlStr.endsWith("/")) { + sb.append('/'); + } + // Inner classes are in separate Top.Nesting1.Nesting2.Nesting3.MyType.html files + sb.append(type.getFullyQualifiedName().replaceAll("\\.", "/").replaceAll("\\$", ".") + ".html"); + return new URL(sb.toString()); + }; + URL sourceUrl(URL sourceContainerUrl, IType type) throws Exception; } diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java new file mode 100644 index 000000000..b3a554f52 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/CharOperation.java @@ -0,0 +1,4190 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +/** + * This class is a collection of helper methods to manipulate char arrays. + * + * @since 2.1 + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class CharOperation { + + /** + * Constant for an empty char array + */ + public static final char[] NO_CHAR = new char[0]; + + /** + * Constant for an empty char array with two dimensions. + */ + public static final char[][] NO_CHAR_CHAR = new char[0][]; + + /** + * Constant for an empty String array. + * @since 3.1 + */ + public static final String[] NO_STRINGS = new String[0]; + +/** + * Answers a new array with appending the suffix character at the end of the array. + *
+ *
+ * For example:
+ *
    + *
  1. + *    array = { 'a', 'b' }
    + *    suffix = 'c'
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    suffix = 'c'
    + *    => result = { 'c' }
    + * 
  4. + *
+ * + * @param array the array that is concatenated with the suffix character + * @param suffix the suffix character + * @return the new array + */ +public static final char[] append(char[] array, char suffix) { + if (array == null) + return new char[] { suffix }; + int length = array.length; + System.arraycopy(array, 0, array = new char[length + 1], 0, length); + array[length] = suffix; + return array; +} + +/** + * Answers a new array with appending the sub-array at the end of the array. + *
+ *
+ * For example:
+ *
    + *
  1. + *    array = { 'a', 'b' }
    + *    suffix = { 'c', 'd' }
    + *    => result = { 'a', 'b' , 'c' , d' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    suffix = { 'c' }
    + *    => result = { 'c' }
    + * 
  4. + *
+ * + * @param target the array that is concatenated with the suffix array. + * @param suffix the array that will be concatenated to the target + * @return the new array + * @throws NullPointerException if the target array is null + * @since 3.11 + */ +public static final char[] append(char[] target, char[] suffix) { + if(suffix == null || suffix.length == 0) + return target; + int targetLength = target.length; + int subLength = suffix.length; + int newTargetLength = targetLength + subLength; + if (newTargetLength > targetLength) { + System.arraycopy(target, 0, target = new char[newTargetLength], 0, targetLength); + } + System.arraycopy(suffix, 0, target, targetLength, subLength); + return target; +} + +/** + * Append the given sub-array to the target array starting at the given index in the target array. + * The start of the sub-array is inclusive, the end is exclusive. + * Answers a new target array if it needs to grow, otherwise answers the same target array. + *
+ * For example:
+ *
    + *
  1. + *    target = { 'a', 'b', '0' }
    + *    index = 2
    + *    array = { 'c', 'd' }
    + *    start = 0
    + *    end = 1
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    target = { 'a', 'b' }
    + *    index = 2
    + *    array = { 'c', 'd' }
    + *    start = 0
    + *    end = 1
    + *    => result = { 'a', 'b' , 'c', '0', '0' , '0' } (new array)
    + * 
  4. + *
  5. + *    target = { 'a', 'b', 'c' }
    + *    index = 1
    + *    array = { 'c', 'd', 'e', 'f' }
    + *    start = 1
    + *    end = 4
    + *    => result = { 'a', 'd' , 'e', 'f', '0', '0', '0', '0' } (new array)
    + * 
  6. + *
+ * + * @param target the given target + * @param index the given index + * @param array the given array + * @param start the given start index + * @param end the given end index + * + * @return the new array + * @throws NullPointerException if the target array is null + */ +public static final char[] append(char[] target, int index, char[] array, int start, int end) { + int targetLength = target.length; + int subLength = end-start; + int newTargetLength = subLength+index; + if (newTargetLength > targetLength) { + System.arraycopy(target, 0, target = new char[newTargetLength*2], 0, index); + } + System.arraycopy(array, start, target, index, subLength); + return target; +} + +/** + * Answers the concatenation of the two arrays. It answers null if the two arrays are null. + * If the first array is null, then the second array is returned. + * If the second array is null, then the first array is returned. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    => result = null
    + * 
    + *
  2. + *
  3. + *    first = { { ' a' } }
    + *    second = null
    + *    => result = { { ' a' } }
    + * 
    + *
  4. + *
  5. + *    first = null
    + *    second = { { ' a' } }
    + *    => result = { { ' a' } }
    + * 
    + *
  6. + *
  7. + *    first = { { ' b' } }
    + *    second = { { ' a' } }
    + *    => result = { { ' b' }, { ' a' } }
    + * 
    + *
  8. + *
+ * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @return the concatenation of the two arrays, or null if the two arrays are null. + */ +public static final char[][] arrayConcat(char[][] first, char[][] second) { + if (first == null) + return second; + if (second == null) + return first; + + int length1 = first.length; + int length2 = second.length; + char[][] result = new char[length1 + length2][]; + System.arraycopy(first, 0, result, 0, length1); + System.arraycopy(second, 0, result, length1, length2); + return result; +} + +/** + * Answers true if the pattern matches the given name using CamelCase rules, or + * false otherwise. char[] CamelCase matching does NOT accept explicit wild-cards + * '*' and '?' and is inherently case sensitive. + *

+ * CamelCase denotes the convention of writing compound names without spaces, + * and capitalizing every term. This function recognizes both upper and lower + * CamelCase, depending whether the leading character is capitalized or not. + * The leading part of an upper CamelCase pattern is assumed to contain a + * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will + * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern + * uses a lowercase first character. In Java, type names follow the upper + * CamelCase convention, whereas method or field names follow the lower + * CamelCase convention. + *

+ * The pattern may contain lowercase characters, which will be matched in a case + * sensitive way. These characters must appear in sequence in the name. + * For instance, 'NPExcep' will match 'NullPointerException', but not + * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not + * 'NoPointerException'. + *

+ * Digit characters are treated in a special way. They can be used in the pattern + * but are not always considered as leading character. For instance, both + * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

+ * Using this method allows matching names to have more parts than the specified + * pattern (see {@link #camelCaseMatch(char[], char[], boolean)}).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap', + * 'HatMapper' and also 'HashMapEntry'. + *

+ *

+ * Examples:
    + *
  1. pattern = "NPE".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * name = "NullPointerException".toCharArray() + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * name = "IPerspectiveListener3".toCharArray() + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * name = "HashMapEntry".toCharArray() + * result => true
  14. + *
+ * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * @since 3.2 + */ +public static final boolean camelCaseMatch(char[] pattern, char[] name) { + if (pattern == null) + return true; // null pattern is equivalent to '*' + if (name == null) + return false; // null name cannot match + + return camelCaseMatch(pattern, 0, pattern.length, name, 0, name.length, false/*not the same count of parts*/); +} + +/** + * Answers true if the pattern matches the given name using CamelCase rules, or + * false otherwise. char[] CamelCase matching does NOT accept explicit wild-cards + * '*' and '?' and is inherently case sensitive. + *

+ * CamelCase denotes the convention of writing compound names without spaces, + * and capitalizing every term. This function recognizes both upper and lower + * CamelCase, depending whether the leading character is capitalized or not. + * The leading part of an upper CamelCase pattern is assumed to contain a + * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will + * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern + * uses a lowercase first character. In Java, type names follow the upper + * CamelCase convention, whereas method or field names follow the lower + * CamelCase convention. + *

+ * The pattern may contain lowercase characters, which will be matched in a case + * sensitive way. These characters must appear in sequence in the name. + * For instance, 'NPExcep' will match 'NullPointerException', but not + * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not + * 'NoPointerException'. + *

+ * Digit characters are treated in a special way. They can be used in the pattern + * but are not always considered as leading character. For instance, both + * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

+ * CamelCase can be restricted to match only the same count of parts. When this + * restriction is specified the given pattern and the given name must have exactly + * the same number of parts (i.e. the same number of uppercase characters).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap' and + * 'HatMapper' but not 'HashMapEntry'. + *

+ *

+ * Examples:
    + *
  1. pattern = "NPE".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * name = "NullPointerException".toCharArray() + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * name = "NoPermissionException".toCharArray() + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * name = "NullPointerException".toCharArray() + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * name = "IPerspectiveListener3".toCharArray() + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * name = "HashMapEntry".toCharArray() + * result => (samePartCount == false)
  14. + *
+ * + * @param pattern the given pattern + * @param name the given name + * @param samePartCount flag telling whether the pattern and the name should + * have the same count of parts or not.
+ *   For example: + *
    + *
  • 'HM' type string pattern will match 'HashMap' and 'HtmlMapper' types, + * but not 'HashMapEntry'
  • + *
  • 'HMap' type string pattern will still match previous 'HashMap' and + * 'HtmlMapper' types, but not 'HighMagnitude'
  • + *
+ * @return true if the pattern matches the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(char[] pattern, char[] name, boolean samePartCount) { + if (pattern == null) + return true; // null pattern is equivalent to '*' + if (name == null) + return false; // null name cannot match + + return camelCaseMatch(pattern, 0, pattern.length, name, 0, name.length, samePartCount); +} + +/** + * Answers true if a sub-pattern matches the sub-part of the given name using + * CamelCase rules, or false otherwise. char[] CamelCase matching does NOT + * accept explicit wild-cards '*' and '?' and is inherently case sensitive. + * Can match only subset of name/pattern, considering end positions as non-inclusive. + * The sub-pattern is defined by the patternStart and patternEnd positions. + *

+ * CamelCase denotes the convention of writing compound names without spaces, + * and capitalizing every term. This function recognizes both upper and lower + * CamelCase, depending whether the leading character is capitalized or not. + * The leading part of an upper CamelCase pattern is assumed to contain a + * sequence of capitals which are appearing in the matching name; e.g. 'NPE' will + * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern + * uses a lowercase first character. In Java, type names follow the upper + * CamelCase convention, whereas method or field names follow the lower + * CamelCase convention. + *

+ * The pattern may contain lowercase characters, which will be matched in a case + * sensitive way. These characters must appear in sequence in the name. + * For instance, 'NPExcep' will match 'NullPointerException', but not + * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not + * 'NoPointerException'. + *

+ * Digit characters are treated in a special way. They can be used in the pattern + * but are not always considered as leading character. For instance, both + * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

+ * Digit characters are treated in a special way. They can be used in the pattern + * but are not always considered as leading character. For instance, both + * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

+ * Using this method allows matching names to have more parts than the specified + * pattern (see {@link #camelCaseMatch(char[], int, int, char[], int, int, boolean)}).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap', + * 'HatMapper' and also 'HashMapEntry'. + *

+ * Examples: + *

    + *
  1. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * patternStart = 0 + * patternEnd = 4 + * name = "IPerspectiveListener3".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * patternStart = 0 + * patternEnd = 2 + * name = "HashMapEntry".toCharArray() + * nameStart = 0 + * nameEnd = 12 + * result => true
  14. + *
+ * + * @param pattern the given pattern + * @param patternStart the start index of the pattern, inclusive + * @param patternEnd the end index of the pattern, exclusive + * @param name the given name + * @param nameStart the start index of the name, inclusive + * @param nameEnd the end index of the name, exclusive + * @return true if a sub-pattern matches the sub-part of the given name, false otherwise + * @since 3.2 + */ +public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd) { + return camelCaseMatch(pattern, patternStart, patternEnd, name, nameStart, nameEnd, false/*not the same count of parts*/); +} + +/** + * Answers true if a sub-pattern matches the sub-part of the given name using + * CamelCase rules, or false otherwise. char[] CamelCase matching does NOT + * accept explicit wild-cards '*' and '?' and is inherently case sensitive. + * Can match only subset of name/pattern, considering end positions as + * non-inclusive. The sub-pattern is defined by the patternStart and patternEnd + * positions. + *

+ * CamelCase denotes the convention of writing compound names without spaces, + * and capitalizing every term. This function recognizes both upper and lower + * CamelCase, depending whether the leading character is capitalized or not. + * The leading part of an upper CamelCase pattern is assumed to contain + * a sequence of capitals which are appearing in the matching name; e.g. 'NPE' will + * match 'NullPointerException', but not 'NewPerfData'. A lower CamelCase pattern + * uses a lowercase first character. In Java, type names follow the upper + * CamelCase convention, whereas method or field names follow the lower + * CamelCase convention. + *

+ * The pattern may contain lowercase characters, which will be matched in a case + * sensitive way. These characters must appear in sequence in the name. + * For instance, 'NPExcep' will match 'NullPointerException', but not + * 'NullPointerExCEPTION' or 'NuPoEx' will match 'NullPointerException', but not + * 'NoPointerException'. + *

+ * Digit characters are treated in a special way. They can be used in the pattern + * but are not always considered as leading character. For instance, both + * 'UTF16DSS' and 'UTFDSS' patterns will match 'UTF16DocumentScannerSupport'. + *

+ * CamelCase can be restricted to match only the same count of parts. When this + * restriction is specified the given pattern and the given name must have exactly + * the same number of parts (i.e. the same number of uppercase characters).
+ * For instance, 'HM' , 'HaMa' and 'HMap' patterns will match 'HashMap' and + * 'HatMapper' but not 'HashMapEntry'. + *

+ *

+ * Examples:
+ * 
    + *
  1. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  2. + *
  3. pattern = "NPE".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  4. + *
  5. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => true
  6. + *
  7. pattern = "NuPoEx".toCharArray() + * patternStart = 0 + * patternEnd = 6 + * name = "NoPermissionException".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => false
  8. + *
  9. pattern = "npe".toCharArray() + * patternStart = 0 + * patternEnd = 3 + * name = "NullPointerException".toCharArray() + * nameStart = 0 + * nameEnd = 20 + * result => false
  10. + *
  11. pattern = "IPL3".toCharArray() + * patternStart = 0 + * patternEnd = 4 + * name = "IPerspectiveListener3".toCharArray() + * nameStart = 0 + * nameEnd = 21 + * result => true
  12. + *
  13. pattern = "HM".toCharArray() + * patternStart = 0 + * patternEnd = 2 + * name = "HashMapEntry".toCharArray() + * nameStart = 0 + * nameEnd = 12 + * result => (samePartCount == false)
  14. + *
+ *
+ * + * @param pattern the given pattern + * @param patternStart the start index of the pattern, inclusive + * @param patternEnd the end index of the pattern, exclusive + * @param name the given name + * @param nameStart the start index of the name, inclusive + * @param nameEnd the end index of the name, exclusive + * @param samePartCount flag telling whether the pattern and the name should + * have the same count of parts or not.
+ *   For example: + *
    + *
  • 'HM' type string pattern will match 'HashMap' and 'HtmlMapper' types, + * but not 'HashMapEntry'
  • + *
  • 'HMap' type string pattern will still match previous 'HashMap' and + * 'HtmlMapper' types, but not 'HighMagnitude'
  • + *
+ * @return true if a sub-pattern matches the sub-part of the given name, false otherwise + * @since 3.4 + */ +public static final boolean camelCaseMatch(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, boolean samePartCount) { + + /* !!!!!!!!!! WARNING !!!!!!!!!! + * The algorithm implemented in this method has been heavily used in + * StringOperation#getCamelCaseMatchingRegions(String, int, int, String, int, int, boolean) + * method. + * + * So, if any change needs to be applied in the current algorithm, + * do NOT forget to also apply the same change in the StringOperation method! + */ + + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + if (patternEnd < 0) patternEnd = pattern.length; + if (nameEnd < 0) nameEnd = name.length; + + if (patternEnd <= patternStart) return nameEnd <= nameStart; + if (nameEnd <= nameStart) return false; + // check first pattern char + if (name[nameStart] != pattern[patternStart]) { + // first char must strictly match (upper/lower) + return false; + } + + char patternChar, nameChar; + int iPattern = patternStart; + int iName = nameStart; + + // Main loop is on pattern characters + while (true) { + + iPattern++; + iName++; + + if (iPattern == patternEnd) { // we have exhausted pattern... + // it's a match if the name can have additional parts (i.e. uppercase characters) or is also exhausted + if (!samePartCount || iName == nameEnd) return true; + + // otherwise it's a match only if the name has no more uppercase characters + while (true) { + if (iName == nameEnd) { + // we have exhausted the name, so it's a match + return true; + } + nameChar = name[iName]; + // test if the name character is uppercase + if (nameChar < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar] & ScannerHelper.C_UPPER_LETTER) != 0) { + return false; + } + } + else if (!Character.isJavaIdentifierPart(nameChar) || Character.isUpperCase(nameChar)) { + return false; + } + iName++; + } + } + + if (iName == nameEnd){ + // We have exhausted the name (and not the pattern), so it's not a match + return false; + } + + // For as long as we're exactly matching, bring it on (even if it's a lower case character) + if ((patternChar = pattern[iPattern]) == name[iName]) { + continue; + } + + // If characters are not equals, then it's not a match if patternChar is lowercase + if (patternChar < ScannerHelper.MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[patternChar] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_DIGIT)) == 0) { + return false; + } + } + else if (Character.isJavaIdentifierPart(patternChar) && !Character.isUpperCase(patternChar) && !Character.isDigit(patternChar)) { + return false; + } + + // patternChar is uppercase, so let's find the next uppercase in name + while (true) { + if (iName == nameEnd){ + // We have exhausted name (and not pattern), so it's not a match + return false; + } + + nameChar = name[iName]; + if (nameChar < ScannerHelper.MAX_OBVIOUS) { + int charNature = ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[nameChar]; + if ((charNature & (ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_SPECIAL)) != 0) { + // nameChar is lowercase + iName++; + } else if ((charNature & ScannerHelper.C_DIGIT) != 0) { + // nameChar is digit => break if the digit is current pattern character otherwise consume it + if (patternChar == nameChar) break; + iName++; + // nameChar is uppercase... + } else if (patternChar != nameChar) { + //.. and it does not match patternChar, so it's not a match + return false; + } else { + //.. and it matched patternChar. Back to the big loop + break; + } + } + // Same tests for non-obvious characters + else if (Character.isJavaIdentifierPart(nameChar) && !Character.isUpperCase(nameChar)) { + iName++; + } else if (Character.isDigit(nameChar)) { + if (patternChar == nameChar) break; + iName++; + } else if (patternChar != nameChar) { + return false; + } else { + break; + } + } + // At this point, either name has been exhausted, or it is at an uppercase letter. + // Since pattern is also at an uppercase letter + } +} + +/** + * Answers true if the characters of the pattern are contained in the + * name as a substring, in a case-insensitive way. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * @since 3.12 + */ +public static final boolean substringMatch(String pattern, String name) { + if (pattern == null || pattern.length() == 0) { + return true; + } + if (name == null) { + return false; + } + return checkSubstringMatch(pattern.toCharArray(), name.toCharArray()); +} + +/** + * Answers true if the characters of the pattern are contained in the + * name as a substring, in a case-insensitive way. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * @since 3.12 + */ +public static final boolean substringMatch(char[] pattern, char[] name) { + if (pattern == null || pattern.length == 0) { + return true; + } + if (name == null) { + return false; + } + return checkSubstringMatch(pattern, name); +} + +/** + * Internal substring matching method; called after the null and length + * checks are performed. + * + * @param pattern the given pattern + * @param name the given name + * @return true if the pattern matches the given name, false otherwise + * + * @see CharOperation#substringMatch(char[], char[]) + */ +private static final boolean checkSubstringMatch(char[] pattern, char[] name) { + +/* XXX: to be revised/enabled + + // allow non-consecutive occurrence of pattern characters + if (pattern.length >= 3) { + int pidx = 0; + + for (int nidx = 0; nidx < name.length; nidx++) { + if (Character.toLowerCase(name[nidx]) == + Character.toLowerCase(pattern[pidx])) + pidx++; + if (pidx == pattern.length) + return true; + } + + // for short patterns only allow consecutive occurrence + } else { +*/ + // outer loop iterates on the characters of the name; trying to + // match at any possible position + outer: for (int nidx = 0; nidx < name.length - pattern.length + 1; nidx++) { + // inner loop iterates on pattern characters + for (int pidx = 0; pidx < pattern.length; pidx++) { + if (Character.toLowerCase(name[nidx + pidx]) != + Character.toLowerCase(pattern[pidx])) { + // no match until parameter list; do not match parameter list + if ((name[nidx + pidx] == '(') || (name[nidx + pidx] == ':')) + return false; + continue outer; + } + if (pidx == pattern.length - 1) + return true; + } + } + // XXX: } + + return false; +} + +/** + * Returns the char arrays as an array of Strings + * + * @param charArrays the char array to convert + * @return the char arrays as an array of Strings or null if the given char arrays is null. + * @since 3.0 + */ +public static String[] charArrayToStringArray(char[][] charArrays) { + if (charArrays == null) + return null; + int length = charArrays.length; + if (length == 0) + return NO_STRINGS; + String[] strings= new String[length]; + for (int i= 0; i < length; i++) + strings[i]= new String(charArrays[i]); + return strings; +} + +/** + * Returns the char array as a String + + * @param charArray the char array to convert + * @return the char array as a String or null if the given char array is null. + * @since 3.0 + */ +public static String charToString(char[] charArray) { + if (charArray == null) return null; + return new String(charArray); +} + +/** + * Answers a new array adding the second array at the end of first array. + * It answers null if the first and second are null. + * If the first array is null, then a new array char[][] is created with second. + * If the second array is null, then the first array is returned. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    => result = { { ' a' } }
    + * 
    + *
  2. + *    first = { { ' a' } }
    + *    second = null
    + *    => result = { { ' a' } }
    + * 
    + *
  3. + *
  4. + *    first = { { ' a' } }
    + *    second = { ' b' }
    + *    => result = { { ' a' } , { ' b' } }
    + * 
    + *
  5. + *
+ * + * @param first the first array to concatenate + * @param second the array to add at the end of the first array + * @return a new array adding the second array at the end of first array, or null if the two arrays are null. + */ +public static final char[][] arrayConcat(char[][] first, char[] second) { + if (second == null) + return first; + if (first == null) + return new char[][] { second }; + + int length = first.length; + char[][] result = new char[length + 1][]; + System.arraycopy(first, 0, result, 0, length); + result[length] = second; + return result; +} +/** + * Compares the two char arrays lexicographically. + * + * Returns a negative integer if array1 lexicographically precedes the array2, + * a positive integer if this array1 lexicographically follows the array2, or + * zero if both arrays are equal. + * + * @param array1 the first given array + * @param array2 the second given array + * @return the returned value of the comparison between array1 and array2 + * @throws NullPointerException if one of the arrays is null + * @since 3.3 + */ +public static final int compareTo(char[] array1, char[] array2) { + int length1 = array1.length; + int length2 = array2.length; + int min = Math.min(length1, length2); + for (int i = 0; i < min; i++) { + if (array1[i] != array2[i]) { + return array1[i] - array2[i]; + } + } + return length1 - length2; +} +/** + * Compares the two char arrays lexicographically between the given start and end positions. + * + * Returns a negative integer if array1 lexicographically precedes the array2, + * a positive integer if this array1 lexicographically follows the array2, or + * zero if both arrays are equal. + *

The comparison is done between start and end positions.

+ * + * @param array1 the first given array + * @param array2 the second given array + * @param start the starting position to compare (inclusive) + * @param end the ending position to compare (exclusive) + * + * @return the returned value of the comparison between array1 and array2 + * @throws NullPointerException if one of the arrays is null + * @since 3.7.1 + */ +public static final int compareTo(char[] array1, char[] array2, int start, int end) { + int length1 = array1.length; + int length2 = array2.length; + int min = Math.min(length1, length2); + min = Math.min(min, end); + for (int i = start; i < min; i++) { + if (array1[i] != array2[i]) { + return array1[i] - array2[i]; + } + } + return length1 - length2; +} +/** + * Compares the contents of the two arrays array and prefix. Returns + *
    + *
  • zero if the array starts with the prefix contents
  • + *
  • the difference between the first two characters that are not equal
  • + *
  • one if array length is lower than the prefix length and that the prefix starts with the + * array contents.
  • + *
+ *

+ * For example: + *

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

+ * + * @param array the given array + * @param prefix the given prefix + * @return the result of the comparison (>=0 if array>prefix) + * @throws NullPointerException if either array or prefix is null + */ +public static final int compareWith(char[] array, char[] prefix) { + int arrayLength = array.length; + int prefixLength = prefix.length; + int min = Math.min(arrayLength, prefixLength); + int i = 0; + while (min-- != 0) { + char c1 = array[i]; + char c2 = prefix[i++]; + if (c1 != c2) + return c1 - c2; + } + if (prefixLength == i) + return 0; + return -1; // array is shorter than prefix (e.g. array:'ab' < prefix:'abc'). +} + +/** + * Answers the concatenation of the two arrays. It answers null if the two arrays are null. + * If the first array is null, then the second array is returned. + * If the second array is null, then the first array is returned. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    => result = { ' a' , ' b' }
    + * 
    + *
  6. + *
+ * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @return the concatenation of the two arrays, or null if the two arrays are null. + */ +public static final char[] concat(char[] first, char[] second) { + if (first == null) + return second; + if (second == null) + return first; + + int length1 = first.length; + int length2 = second.length; + char[] result = new char[length1 + length2]; + System.arraycopy(first, 0, result, 0, length1); + System.arraycopy(second, 0, result, length1, length2); + return result; +} + +/** + * Answers the concatenation of the three arrays. It answers null if the three arrays are null. + * If first is null, it answers the concatenation of second and third. + * If second is null, it answers the concatenation of first and third. + * If third is null, it answers the concatenation of first and second. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    third = { 'b' }
    + *    => result = { ' a', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    second = null
    + *    third = { 'b' }
    + *    => result = { ' a', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'b' }
    + *    third = null
    + *    => result = { ' a', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = null
    + *    second = null
    + *    third = null
    + *    => result = null
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    second = { 'b' }
    + *    third = { 'c' }
    + *    => result = { 'a', 'b', 'c' }
    + * 
    + *
  10. + *
+ * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @param third the third array to concatenate + * + * @return the concatenation of the three arrays, or null if the three arrays are null. + */ +public static final char[] concat( + char[] first, + char[] second, + char[] third) { + if (first == null) + return concat(second, third); + if (second == null) + return concat(first, third); + if (third == null) + return concat(first, second); + + int length1 = first.length; + int length2 = second.length; + int length3 = third.length; + char[] result = new char[length1 + length2 + length3]; + System.arraycopy(first, 0, result, 0, length1); + System.arraycopy(second, 0, result, length1, length2); + System.arraycopy(third, 0, result, length1 + length2, length3); + return result; +} + +/** + * Answers the concatenation of the two arrays inserting the separator character between the two arrays. + * It answers null if the two arrays are null. + * If the first array is null, then the second array is returned. + * If the second array is null, then the first array is returned. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    separator = '/'
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
+ * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @param separator the character to insert + * @return the concatenation of the two arrays inserting the separator character + * between the two arrays , or null if the two arrays are null. + */ +public static final char[] concat( + char[] first, + char[] second, + char separator) { + if (first == null) + return second; + if (second == null) + return first; + + int length1 = first.length; + if (length1 == 0) + return second; + int length2 = second.length; + if (length2 == 0) + return first; + + char[] result = new char[length1 + length2 + 1]; + System.arraycopy(first, 0, result, 0, length1); + result[length1] = separator; + System.arraycopy(second, 0, result, length1 + 1, length2); + return result; +} + +/** + * Answers the concatenation of the three arrays inserting the sep1 character between the + * first two arrays and sep2 between the last two. + * It answers null if the three arrays are null. + * If the first array is null, then it answers the concatenation of second and third inserting + * the sep2 character between them. + * If the second array is null, then it answers the concatenation of first and third inserting + * the sep1 character between them. + * If the third array is null, then it answers the concatenation of first and second inserting + * the sep1 character between them. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    sep1 = '/'
    + *    second = { 'a' }
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , ':', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = null
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = null
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a' , '/', 'b' , ':', 'c' }
    + * 
    + *
  8. + *
+ * + * @param first the first array to concatenate + * @param sep1 the character to insert + * @param second the second array to concatenate + * @param sep2 the character to insert + * @param third the second array to concatenate + * @return the concatenation of the three arrays inserting the sep1 character between the + * two arrays and sep2 between the last two. + */ +public static final char[] concat( + char[] first, + char sep1, + char[] second, + char sep2, + char[] third) { + if (first == null) + return concat(second, third, sep2); + if (second == null) + return concat(first, third, sep1); + if (third == null) + return concat(first, second, sep1); + + int length1 = first.length; + int length2 = second.length; + int length3 = third.length; + char[] result = new char[length1 + length2 + length3 + 2]; + System.arraycopy(first, 0, result, 0, length1); + result[length1] = sep1; + System.arraycopy(second, 0, result, length1 + 1, length2); + result[length1 + length2 + 1] = sep2; + System.arraycopy(third, 0, result, length1 + length2 + 2, length3); + return result; +} +/** + * Answers the concatenation of the two arrays inserting the separator character between the two arrays. + * It answers null if the two arrays are null. + * If the first array is null or is empty, then the second array is returned. + * If the second array is null or is empty, then the first array is returned. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = { 'a' }
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  2. + *
  3. + *    first = { ' a' }
    + *    second = null
    + *    separator = '/'
    + *    => result = { ' a' }
    + * 
    + *
  4. + *
  5. + *    first = { ' a' }
    + *    second = { ' b' }
    + *    separator = '/'
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + * *
  7. + *    first = { ' a' }
    + *    second = {  }
    + *    separator = '/'
    + *    => result = { ' a'}
    + * 
    + *
  8. + + *
+ * + * @param first the first array to concatenate + * @param second the second array to concatenate + * @param separator the character to insert + * @return the concatenation of the two arrays inserting the separator character + * between the two arrays , or null if the two arrays are null. + * @since 3.12 + */ +public static final char[] concatNonEmpty( + char[] first, + char[] second, + char separator) { + if (first == null || first.length == 0) + return second; + if (second == null || second.length == 0) + return first; + return concat(first, second, separator); +} +/** + * Answers the concatenation of the three arrays inserting the sep1 character between the + * first two arrays and sep2 between the last two. + * It answers null if the three arrays are null. + * If the first array is null or empty, then it answers the concatenation of second and third inserting + * the sep2 character between them. + * If the second array is null or empty, then it answers the concatenation of first and third inserting + * the sep1 character between them. + * If the third array is null or empty, then it answers the concatenation of first and second inserting + * the sep1 character between them. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    sep1 = '/'
    + *    second = { 'a' }
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , ':', 'b' }
    + * 
    + *
  2. + *
  3. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = null
    + *    sep2 = ':'
    + *    third = { 'b' }
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = null
    + *    => result = { ' a' , '/', 'b' }
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { 'b' }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a' , '/', 'b' , ':', 'c' }
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    sep1 = '/'
    + *    second = { }
    + *    sep2 = ':'
    + *    third = { 'c' }
    + *    => result = { ' a', ':', 'c' }
    + * 
    + *
  10. + *
+ * + * @param first the first array to concatenate + * @param sep1 the character to insert + * @param second the second array to concatenate + * @param sep2 the character to insert + * @param third the second array to concatenate + * @return the concatenation of the three arrays inserting the sep1 character between the + * two arrays and sep2 between the last two. + * @since 3.12 + */ +public static final char[] concatNonEmpty( + char[] first, + char sep1, + char[] second, + char sep2, + char[] third) { + if (first == null || first.length == 0) + return concatNonEmpty(second, third, sep2); + if (second == null || second.length == 0) + return concatNonEmpty(first, third, sep1); + if (third == null || third.length == 0) + return concatNonEmpty(first, second, sep1); + + return concat(first, sep1, second, sep2, third); +} + +/** + * Answers a new array with prepending the prefix character and appending the suffix + * character at the end of the array. If array is null, it answers a new array containing the + * prefix and the suffix characters. + *
+ *
+ * For example:
+ *
    + *
  1. + *    prefix = 'a'
    + *    array = { 'b' }
    + *    suffix = 'c'
    + *    => result = { 'a', 'b' , 'c' }
    + * 
    + *
  2. + *
  3. + *    prefix = 'a'
    + *    array = null
    + *    suffix = 'c'
    + *    => result = { 'a', 'c' }
    + * 
  4. + *
+ * + * @param prefix the prefix character + * @param array the array that is concatenated with the prefix and suffix characters + * @param suffix the suffix character + * @return the new array + */ +public static final char[] concat(char prefix, char[] array, char suffix) { + if (array == null) + return new char[] { prefix, suffix }; + + int length = array.length; + char[] result = new char[length + 2]; + result[0] = prefix; + System.arraycopy(array, 0, result, 1, length); + result[length + 1] = suffix; + return result; +} + +/** + * Answers the concatenation of the given array parts using the given separator between each + * part and prepending the given name at the beginning. + *
+ *
+ * For example:
+ *
    + *
  1. + *    name = { 'c' }
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' , '.', 'c' }
    + * 
    + *
  2. + *
  3. + *    name = null
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
  4. + *
  5. + *    name = { ' c' }
    + *    array = null
    + *    separator = '.'
    + *    => result = { 'c' }
    + * 
  6. + *
+ * + * @param name the given name + * @param array the given array + * @param separator the given separator + * @return the concatenation of the given array parts using the given separator between each + * part and prepending the given name at the beginning + */ +public static final char[] concatWith( + char[] name, + char[][] array, + char separator) { + int nameLength = name == null ? 0 : name.length; + if (nameLength == 0) + return concatWith(array, separator); + + int length = array == null ? 0 : array.length; + if (length == 0) + return name; + + int size = nameLength; + int index = length; + while (--index >= 0) + if (array[index].length > 0) + size += array[index].length + 1; + char[] result = new char[size]; + index = size; + for (int i = length - 1; i >= 0; i--) { + int subLength = array[i].length; + if (subLength > 0) { + index -= subLength; + System.arraycopy(array[i], 0, result, index, subLength); + result[--index] = separator; + } + } + System.arraycopy(name, 0, result, 0, nameLength); + return result; +} + +/** + * Answers the concatenation of the given array parts using the given separator between each + * part and appending the given name at the end. + *
+ *
+ * For example:
+ *
    + *
  1. + *    name = { 'c' }
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' , '.', 'c' }
    + * 
    + *
  2. + *
  3. + *    name = null
    + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
  4. + *
  5. + *    name = { ' c' }
    + *    array = null
    + *    separator = '.'
    + *    => result = { 'c' }
    + * 
  6. + *
+ * + * @param array the given array + * @param name the given name + * @param separator the given separator + * @return the concatenation of the given array parts using the given separator between each + * part and appending the given name at the end + */ +public static final char[] concatWith( + char[][] array, + char[] name, + char separator) { + int nameLength = name == null ? 0 : name.length; + if (nameLength == 0) + return concatWith(array, separator); + + int length = array == null ? 0 : array.length; + if (length == 0) + return name; + + int size = nameLength; + int index = length; + while (--index >= 0) + if (array[index].length > 0) + size += array[index].length + 1; + char[] result = new char[size]; + index = 0; + for (int i = 0; i < length; i++) { + int subLength = array[i].length; + if (subLength > 0) { + System.arraycopy(array[i], 0, result, index, subLength); + index += subLength; + result[index++] = separator; + } + } + System.arraycopy(name, 0, result, index, nameLength); + return result; +} + +/** + * Answers the concatenation of the given array parts using the given separator between each part. + *
+ *
+ * For example:
+ *
    + *
  1. + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
    + *
  2. + *
  3. + *    array = null
    + *    separator = '.'
    + *    => result = { }
    + * 
  4. + *
+ * + * @param array the given array + * @param separator the given separator + * @return the concatenation of the given array parts using the given separator between each part + */ +public static final char[] concatWith(char[][] array, char separator) { + int length = array == null ? 0 : array.length; + if (length == 0) + return CharOperation.NO_CHAR; + + int size = length - 1; + int index = length; + while (--index >= 0) { + if (array[index].length == 0) + size--; + else + size += array[index].length; + } + if (size <= 0) + return CharOperation.NO_CHAR; + char[] result = new char[size]; + index = length; + while (--index >= 0) { + length = array[index].length; + if (length > 0) { + System.arraycopy( + array[index], + 0, + result, + (size -= length), + length); + if (--size >= 0) + result[size] = separator; + } + } + return result; +} + +/** + * Answers the concatenation of the given array parts using the given separator between each part + * irrespective of whether an element is a zero length array or not. + *
+ *
+ * For example:
+ *
    + *
  1. + *    array = { { 'a' }, {}, { 'b' } }
    + *    separator = ''
    + *    => result = { 'a', '/', '/', 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { { 'a' }, { 'b' } }
    + *    separator = '.'
    + *    => result = { 'a', '.', 'b' }
    + * 
    + *
  4. + *
  5. + *    array = null
    + *    separator = '.'
    + *    => result = { }
    + * 
  6. + *
+ * + * @param array the given array + * @param separator the given separator + * @return the concatenation of the given array parts using the given separator between each part + * @since 3.12 + */ +public static final char[] concatWithAll(char[][] array, char separator) { + int length = array == null ? 0 : array.length; + if (length == 0) + return CharOperation.NO_CHAR; + + int size = length - 1; + int index = length; + while (--index >= 0) { + size += array[index].length; + } + char[] result = new char[size]; + index = length; + while (--index >= 0) { + length = array[index].length; + if (length > 0) { + System.arraycopy( + array[index], + 0, + result, + (size -= length), + length); + } + if (--size >= 0) + result[size] = separator; + } + return result; +} + +/** + * Answers true if the array contains an occurrence of character, false otherwise. + * + *
+ *
+ * For example: + *
    + *
  1. + *    character = 'c'
    + *    array = { { ' a' }, { ' b' } }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    character = 'a'
    + *    array = { { ' a' }, { ' b' } }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @param character the character to search + * @param array the array in which the search is done + * @return true if the array contains an occurrence of character, false otherwise. + * @throws NullPointerException if array is null. + */ +public static final boolean contains(char character, char[][] array) { + for (int i = array.length; --i >= 0;) { + char[] subarray = array[i]; + for (int j = subarray.length; --j >= 0;) + if (subarray[j] == character) + return true; + } + return false; +} + +/** + * Answers true if the array contains an occurrence of character, false otherwise. + * + *
+ *
+ * For example: + *
    + *
  1. + *    character = 'c'
    + *    array = { ' b'  }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    character = 'a'
    + *    array = { ' a' , ' b' }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @param character the character to search + * @param array the array in which the search is done + * @return true if the array contains an occurrence of character, false otherwise. + * @throws NullPointerException if array is null. + */ +public static final boolean contains(char character, char[] array) { + for (int i = array.length; --i >= 0;) + if (array[i] == character) + return true; + return false; +} + +/** + * Answers true if the array contains an occurrence of one of the characters, false otherwise. + * + *
+ *
+ * For example: + *
    + *
  1. + *    characters = { 'c', 'd' }
    + *    array = { 'a', ' b'  }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    characters = { 'c', 'd' }
    + *    array = { 'a', ' b', 'c'  }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @param characters the characters to search + * @param array the array in which the search is done + * @return true if the array contains an occurrence of one of the characters, false otherwise. + * @throws NullPointerException if array is null. + * @since 3.1 + */ +public static final boolean contains(char[] characters, char[] array) { + for (int i = array.length; --i >= 0;) + for (int j = characters.length; --j >= 0;) + if (array[i] == characters[j]) + return true; + return false; +} + +/** + * Answers a deep copy of the toCopy array. + * + * @param toCopy the array to copy + * @return a deep copy of the toCopy array. + */ + +public static final char[][] deepCopy(char[][] toCopy) { + int toCopyLength = toCopy.length; + char[][] result = new char[toCopyLength][]; + for (int i = 0; i < toCopyLength; i++) { + char[] toElement = toCopy[i]; + int toElementLength = toElement.length; + char[] resultElement = new char[toElementLength]; + System.arraycopy(toElement, 0, resultElement, 0, toElementLength); + result[i] = resultElement; + } + return result; +} + +/** + * Return true if array ends with the sequence of characters contained in toBeFound, + * otherwise false. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a', 'b', 'c', 'd' }
    + *    toBeFound = { 'b', 'c' }
    + *    result => false
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'b', 'c' }
    + *    toBeFound = { 'b', 'c' }
    + *    result => true
    + * 
    + *
  4. + *
+ * + * @param array the array to check + * @param toBeFound the array to find + * @return true if array ends with the sequence of characters contained in toBeFound, + * otherwise false. + * @throws NullPointerException if array is null or toBeFound is null + */ +public static final boolean endsWith(char[] array, char[] toBeFound) { + int i = toBeFound.length; + int j = array.length - i; + + if (j < 0) + return false; + while (--i >= 0) + if (toBeFound[i] != array[i + j]) + return false; + return true; +} + +/** + * Answers true if the two arrays are identical character by character, otherwise false. + * The equality is case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { { } }
    + *    second = null
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { { 'a' } }
    + *    second = { { 'a' } }
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    result => false
    + * 
    + *
  8. + *
+ * @param first the first array + * @param second the second array + * @return true if the two arrays are identical character by character, otherwise false + */ +public static final boolean equals(char[][] first, char[][] second) { + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (!equals(first[i], second[i])) + return false; + return true; +} + +/** + * If isCaseSensite is true, answers true if the two arrays are identical character + * by character, otherwise false. + * If it is false, answers true if the two arrays are identical character by + * character without checking the case, otherwise false. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { { } }
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    first = { { 'A' } }
    + *    second = { { 'a' } }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  8. + *
+ * + * @param first the first array + * @param second the second array + * @param isCaseSensitive check whether or not the equality should be case sensitive + * @return true if the two arrays are identical character by character according to the value + * of isCaseSensitive, otherwise false + */ +public static final boolean equals( + char[][] first, + char[][] second, + boolean isCaseSensitive) { + + if (isCaseSensitive) { + return equals(first, second); + } + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (!equals(first[i], second[i], false)) + return false; + return true; +} + +/** + * Answers true if the two arrays are identical character by character, otherwise false. + * The equality is case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    result => false
    + * 
    + *
  8. + *
+ * @param first the first array + * @param second the second array + * @return true if the two arrays are identical character by character, otherwise false + */ +public static final boolean equals(char[] first, char[] second) { + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (first[i] != second[i]) + return false; + return true; +} + +/** + * Answers true if the first array is identical character by character to a portion of the second array + * delimited from position secondStart (inclusive) to secondEnd(exclusive), otherwise false. + * The equality is case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    result => false
    + * 
    + *
  8. + *
+ * @param first the first array + * @param second the second array + * @param secondStart inclusive start position in the second array to compare + * @param secondEnd exclusive end position in the second array to compare + * @return true if the first array is identical character by character to fragment of second array ranging from secondStart to secondEnd-1, otherwise false + * @since 3.0 + */ +public static final boolean equals(char[] first, char[] second, int secondStart, int secondEnd) { + return equals(first, second, secondStart, secondEnd, true); +} +/** + *

Answers true if the first array is identical character by character to a portion of the second array + * delimited from position secondStart (inclusive) to secondEnd(exclusive), otherwise false. The equality could be either + * case sensitive or case insensitive according to the value of the isCaseSensitive parameter. + *

+ *

For example:

+ *
    + *
  1. + *    first = null
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    secondStart = 0
    + *    secondEnd = 0
    + *    isCaseSensitive = false
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'a' }
    + *    second = { 'a' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  6. + *
  7. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  8. + *
  9. + *    first = { 'a' }
    + *    second = { 'A' }
    + *    secondStart = 0
    + *    secondEnd = 1
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  10. + *
+ * @param first the first array + * @param second the second array + * @param secondStart inclusive start position in the second array to compare + * @param secondEnd exclusive end position in the second array to compare + * @param isCaseSensitive check whether or not the equality should be case sensitive + * @return true if the first array is identical character by character to fragment of second array ranging from secondStart to secondEnd-1, otherwise false + * @since 3.2 + */ +public static final boolean equals(char[] first, char[] second, int secondStart, int secondEnd, boolean isCaseSensitive) { + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != secondEnd - secondStart) + return false; + if (isCaseSensitive) { + for (int i = first.length; --i >= 0;) + if (first[i] != second[i+secondStart]) + return false; + } else { + for (int i = first.length; --i >= 0;) + if (ScannerHelper.toLowerCase(first[i]) != ScannerHelper.toLowerCase(second[i+secondStart])) + return false; + } + return true; +} + +/** + * If isCaseSensite is true, answers true if the two arrays are identical character + * by character, otherwise false. + * If it is false, answers true if the two arrays are identical character by + * character without checking the case, otherwise false. + *
+ *
+ * For example: + *
    + *
  1. + *    first = null
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    first = { }
    + *    second = null
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    first = { 'A' }
    + *    second = { 'a' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    first = { 'A' }
    + *    second = { 'a' }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  8. + *
+ * + * @param first the first array + * @param second the second array + * @param isCaseSensitive check whether or not the equality should be case sensitive + * @return true if the two arrays are identical character by character according to the value + * of isCaseSensitive, otherwise false + */ +public static final boolean equals( + char[] first, + char[] second, + boolean isCaseSensitive) { + + if (isCaseSensitive) { + return equals(first, second); + } + if (first == second) + return true; + if (first == null || second == null) + return false; + if (first.length != second.length) + return false; + + for (int i = first.length; --i >= 0;) + if (ScannerHelper.toLowerCase(first[i]) + != ScannerHelper.toLowerCase(second[i])) + return false; + return true; +} + +/** + * If isCaseSensite is true, the equality is case sensitive, otherwise it is case insensitive. + * + * Answers true if the name contains the fragment at the starting index startIndex, otherwise false. + *
+ *
+ * For example: + *
    + *
  1. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    startIndex = 1
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'C' , 'd' }
    + *    startIndex = 1
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b', 'C' , 'd' }
    + *    startIndex = 0
    + *    isCaseSensitive = false
    + *    result => false
    + * 
    + *
  6. + *
  7. + *    fragment = { 'b', 'c' , 'd' }
    + *    name = { 'a', 'b'}
    + *    startIndex = 0
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  8. + *
+ * + * @param fragment the fragment to check + * @param name the array to check + * @param startIndex the starting index + * @param isCaseSensitive check whether or not the equality should be case sensitive + * @return true if the name contains the fragment at the starting index startIndex according to the + * value of isCaseSensitive, otherwise false. + * @throws NullPointerException if fragment or name is null. + */ +public static final boolean fragmentEquals( + char[] fragment, + char[] name, + int startIndex, + boolean isCaseSensitive) { + + int max = fragment.length; + if (name.length < max + startIndex) + return false; + if (isCaseSensitive) { + for (int i = max; + --i >= 0; + ) // assumes the prefix is not larger than the name + if (fragment[i] != name[i + startIndex]) + return false; + return true; + } + for (int i = max; + --i >= 0; + ) // assumes the prefix is not larger than the name + if (ScannerHelper.toLowerCase(fragment[i]) + != ScannerHelper.toLowerCase(name[i + startIndex])) + return false; + return true; +} + +/** + * Answers a hashcode for the array + * + * @param array the array for which a hashcode is required + * @return the hashcode + * @throws NullPointerException if array is null + */ +public static final int hashCode(char[] array) { + int length = array.length; + int hash = length == 0 ? 31 : array[0]; + if (length < 8) { + for (int i = length; --i > 0;) + hash = (hash * 31) + array[i]; + } else { + // 8 characters is enough to compute a decent hash code, don't waste time examining every character + for (int i = length - 1, last = i > 16 ? i - 16 : 0; i > last; i -= 2) + hash = (hash * 31) + array[i]; + } + return hash & 0x7FFFFFFF; +} + +/** + * Answers true if c is a whitespace according to the JLS (\u0009, \u000a, \u000c, \u000d, \u0020), otherwise false. + *
+ *
+ * For example: + *
    + *
  1. + *    c = ' '
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    c = '\u3000'
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @param c the character to check + * @return true if c is a whitespace according to the JLS, otherwise false. + */ +public static boolean isWhitespace(char c) { + return c < ScannerHelper.MAX_OBVIOUS && ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_JLS_SPACE) != 0); +} + +/** + * Answers the first index in the array for which the corresponding character is + * equal to toBeFound. Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @return the first index in the array for which the corresponding character is + * equal to toBeFound, -1 otherwise + * @throws NullPointerException if array is null + */ +public static final int indexOf(char toBeFound, char[] array) { + return indexOf(toBeFound, array, 0); +} + +/** + * Answers the first index in the array for which the toBeFound array is a matching + * subarray following the case rule. Answers -1 if no match is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @param toBeFound the subarray to search + * @param array the array to be searched + * @param isCaseSensitive flag to know if the matching should be case sensitive + * @return the first index in the array for which the toBeFound array is a matching + * subarray following the case rule, -1 otherwise + * @throws NullPointerException if array is null or toBeFound is null + * @since 3.2 + */ +public static final int indexOf(char[] toBeFound, char[] array, boolean isCaseSensitive) { + return indexOf(toBeFound, array, isCaseSensitive, 0); +} + +/** + * Answers the first index in the array for which the toBeFound array is a matching + * subarray following the case rule starting at the index start. Answers -1 if no match is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @param toBeFound the subarray to search + * @param array the array to be searched + * @param isCaseSensitive flag to know if the matching should be case sensitive + * @param start the starting index + * @return the first index in the array for which the toBeFound array is a matching + * subarray following the case rule starting at the index start, -1 otherwise + * @throws NullPointerException if array is null or toBeFound is null + * @since 3.2 + */ +public static final int indexOf(final char[] toBeFound, final char[] array, final boolean isCaseSensitive, final int start) { + return indexOf(toBeFound, array, isCaseSensitive, start, array.length); +} + +/** + * Answers the first index in the array for which the toBeFound array is a matching + * subarray following the case rule starting at the index start. Answers -1 if no match is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = { 'c' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = { 'e' }
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @param toBeFound the subarray to search + * @param array the array to be searched + * @param isCaseSensitive flag to know if the matching should be case sensitive + * @param start the starting index (inclusive) + * @param end the end index (exclusive) + * @return the first index in the array for which the toBeFound array is a matching + * subarray following the case rule starting at the index start, -1 otherwise + * @throws NullPointerException if array is null or toBeFound is null + * @since 3.2 + */ +public static final int indexOf(final char[] toBeFound, final char[] array, final boolean isCaseSensitive, final int start, final int end) { + final int arrayLength = end; + final int toBeFoundLength = toBeFound.length; + if (toBeFoundLength > arrayLength || start < 0) return -1; + if (toBeFoundLength == 0) return 0; + if (toBeFoundLength == arrayLength) { + if (isCaseSensitive) { + for (int i = start; i < arrayLength; i++) { + if (array[i] != toBeFound[i]) return -1; + } + return 0; + } else { + for (int i = start; i < arrayLength; i++) { + if (ScannerHelper.toLowerCase(array[i]) != ScannerHelper.toLowerCase(toBeFound[i])) return -1; + } + return 0; + } + } + if (isCaseSensitive) { + arrayLoop: for (int i = start, max = arrayLength - toBeFoundLength + 1; i < max; i++) { + if (array[i] == toBeFound[0]) { + for (int j = 1; j < toBeFoundLength; j++) { + if (array[i + j] != toBeFound[j]) continue arrayLoop; + } + return i; + } + } + } else { + arrayLoop: for (int i = start, max = arrayLength - toBeFoundLength + 1; i < max; i++) { + if (ScannerHelper.toLowerCase(array[i]) == ScannerHelper.toLowerCase(toBeFound[0])) { + for (int j = 1; j < toBeFoundLength; j++) { + if (ScannerHelper.toLowerCase(array[i + j]) != ScannerHelper.toLowerCase(toBeFound[j])) continue arrayLoop; + } + return i; + } + } + } + return -1; +} + +/** + * Answers the first index in the array for which the corresponding character is + * equal to toBeFound starting the search at index start. + * Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 1
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @param start the starting index + * @return the first index in the array for which the corresponding character is + * equal to toBeFound, -1 otherwise + * @throws NullPointerException if array is null + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 + */ +public static final int indexOf(char toBeFound, char[] array, int start) { + for (int i = start; i < array.length; i++) + if (toBeFound == array[i]) + return i; + return -1; +} + +/** + * Answers the first index in the array for which the corresponding character is + * equal to toBeFound starting the search at index start and before the ending index. + * Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    start = 1
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @param start the starting index (inclusive) + * @param end the ending index (exclusive) + * @return the first index in the array for which the corresponding character is + * equal to toBeFound, -1 otherwise + * @throws NullPointerException if array is null + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or ending greater than array length + * @since 3.2 + */ +public static final int indexOf(char toBeFound, char[] array, int start, int end) { + for (int i = start; i < end; i++) + if (toBeFound == array[i]) + return i; + return -1; +} + +/** + * Answers the last index in the array for which the corresponding character is + * equal to toBeFound starting from the end of the array. + * Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' , 'c', 'e' }
    + *    result => 4
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    result => -1
    + * 
    + *
  4. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @return the last index in the array for which the corresponding character is + * equal to toBeFound starting from the end of the array, -1 otherwise + * @throws NullPointerException if array is null + */ +public static final int lastIndexOf(char toBeFound, char[] array) { + for (int i = array.length; --i >= 0;) + if (toBeFound == array[i]) + return i; + return -1; +} + +/** + * Answers the last index in the array for which the corresponding character is + * equal to toBeFound stopping at the index startIndex. + * Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd', 'e' }
    + *    startIndex = 3
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 0
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @param startIndex the stopping index + * @return the last index in the array for which the corresponding character is + * equal to toBeFound stopping at the index startIndex, -1 otherwise + * @throws NullPointerException if array is null + * @throws ArrayIndexOutOfBoundsException if startIndex is lower than 0 + */ +public static final int lastIndexOf( + char toBeFound, + char[] array, + int startIndex) { + for (int i = array.length; --i >= startIndex;) + if (toBeFound == array[i]) + return i; + return -1; +} + +/** + * Answers the last index in the array for which the corresponding character is + * equal to toBeFound starting from endIndex to startIndex. + * Answers -1 if no occurrence of this character is found. + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 2
    + *    endIndex = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { ' a', 'b', 'c', 'd', 'e' }
    + *    startIndex = 3
    + *    endIndex = 4
    + *    result => -1
    + * 
    + *
  4. + *
  5. + *    toBeFound = 'e'
    + *    array = { ' a', 'b', 'c', 'd' }
    + *    startIndex = 0
    + *    endIndex = 3
    + *    result => -1
    + * 
    + *
  6. + *
+ * + * @param toBeFound the character to search + * @param array the array to be searched + * @param startIndex the stopping index + * @param endIndex the starting index + * @return the last index in the array for which the corresponding character is + * equal to toBeFound starting from endIndex to startIndex, -1 otherwise + * @throws NullPointerException if array is null + * @throws ArrayIndexOutOfBoundsException if endIndex is greater or equals to array length or starting is lower than 0 + */ +public static final int lastIndexOf( + char toBeFound, + char[] array, + int startIndex, + int endIndex) { + for (int i = endIndex; --i >= startIndex;) + if (toBeFound == array[i]) + return i; + return -1; +} + +/** + * Answers the last portion of a name given a separator. + *
+ *
+ * For example, + *
+ * 	lastSegment("java.lang.Object".toCharArray(),'.') --> Object
+ * 
+ * + * @param array the array + * @param separator the given separator + * @return the last portion of a name given a separator + * @throws NullPointerException if array is null + */ +final static public char[] lastSegment(char[] array, char separator) { + int pos = lastIndexOf(separator, array); + if (pos < 0) + return array; + return subarray(array, pos + 1, array.length); +} + +/** + *

Answers true if the pattern matches the given name, false otherwise. This char[] pattern matching + * accepts wild-cards '*' and '?'.

+ * + *

When not case sensitive, the pattern is assumed to already be lowercased, the + * name will be lowercased character per character as comparing.
+ * If name is null, the answer is false.
+ * If pattern is null, the answer is true if name is not null. + *

+ * For example: + *
    + *
  1. + *    pattern = { '?', 'b', '*' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    pattern = { '?', 'b', '?' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
  5. + *    pattern = { 'b', '*' }
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  6. + *
+ * + * @param pattern the given pattern + * @param name the given name + * @param isCaseSensitive flag to know whether or not the matching should be case sensitive + * @return true if the pattern matches the given name, false otherwise + */ +public static final boolean match( + char[] pattern, + char[] name, + boolean isCaseSensitive) { + + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + + return match( + pattern, + 0, + pattern.length, + name, + 0, + name.length, + isCaseSensitive); +} + +/** + * Answers true if a sub-pattern matches the subpart of the given name, false otherwise. + * char[] pattern matching, accepting wild-cards '*' and '?'. Can match only subset of name/pattern. + * end positions are non-inclusive. + * The subpattern is defined by the patternStart and pattternEnd positions. + * When not case sensitive, the pattern is assumed to already be lowercased, the + * name will be lowercased character per character as comparing. + *
+ *
+ * For example: + *
    + *
  1. + *    pattern = { '?', 'b', '*' }
    + *    patternStart = 1
    + *    patternEnd = 3
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    nameStart = 1
    + *    nameEnd = 4
    + *    isCaseSensitive = true
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    pattern = { '?', 'b', '*' }
    + *    patternStart = 1
    + *    patternEnd = 2
    + *    name = { 'a', 'b', 'c' , 'd' }
    + *    nameStart = 1
    + *    nameEnd = 4
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @param pattern the given pattern + * @param patternStart the given pattern start + * @param patternEnd the given pattern end + * @param name the given name + * @param nameStart the given name start + * @param nameEnd the given name end + * @param isCaseSensitive flag to know if the matching should be case sensitive + * @return true if a sub-pattern matches the subpart of the given name, false otherwise + */ +public static final boolean match( + char[] pattern, + int patternStart, + int patternEnd, + char[] name, + int nameStart, + int nameEnd, + boolean isCaseSensitive) { + + if (name == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + int iPattern = patternStart; + int iName = nameStart; + + if (patternEnd < 0) + patternEnd = pattern.length; + if (nameEnd < 0) + nameEnd = name.length; + + /* check first segment */ + char patternChar = 0; + while (true) { + if (iPattern == patternEnd) { + if (iName == nameEnd) return true; // the chars match + return false; // pattern has ended but not the name, no match + } + if ((patternChar = pattern[iPattern]) == '*') { + break; + } + if (iName == nameEnd) { + return false; // name has ended but not the pattern + } + if (patternChar + != (isCaseSensitive + ? name[iName] + : ScannerHelper.toLowerCase(name[iName])) + && patternChar != '?') { + return false; + } + iName++; + iPattern++; + } + /* check sequence of star+segment */ + int segmentStart; + if (patternChar == '*') { + segmentStart = ++iPattern; // skip star + } else { + segmentStart = 0; // force iName check + } + int prefixStart = iName; + checkSegment : while (iName < nameEnd) { + if (iPattern == patternEnd) { + iPattern = segmentStart; // mismatch - restart current segment + iName = ++prefixStart; + continue checkSegment; + } + /* segment is ending */ + if ((patternChar = pattern[iPattern]) == '*') { + segmentStart = ++iPattern; // skip start + if (segmentStart == patternEnd) { + return true; + } + prefixStart = iName; + continue checkSegment; + } + /* check current name character */ + if ((isCaseSensitive ? name[iName] : ScannerHelper.toLowerCase(name[iName])) + != patternChar + && patternChar != '?') { + iPattern = segmentStart; // mismatch - restart current segment + iName = ++prefixStart; + continue checkSegment; + } + iName++; + iPattern++; + } + + return (segmentStart == patternEnd) + || (iName == nameEnd && iPattern == patternEnd) + || (iPattern == patternEnd - 1 && pattern[iPattern] == '*'); +} + +/** + * Answers true if the pattern matches the filepath using the pathSepatator, false otherwise. + * + * Path char[] pattern matching, accepting wild-cards '**', '*' and '?' (using Ant directory tasks + * conventions, also see "http://jakarta.apache.org/ant/manual/dirtasks.html#defaultexcludes"). + * Path pattern matching is enhancing regular pattern matching in supporting extra rule where '**' represent + * any folder combination. + * Special rule: + * - foo\ is equivalent to foo\** + * When not case sensitive, the pattern is assumed to already be lowercased, the + * name will be lowercased character per character as comparing. + * + * @param pattern the given pattern + * @param filepath the given path + * @param isCaseSensitive to find out whether or not the matching should be case sensitive + * @param pathSeparator the given path separator + * @return true if the pattern matches the filepath using the pathSepatator, false otherwise + */ +public static final boolean pathMatch( + char[] pattern, + char[] filepath, + boolean isCaseSensitive, + char pathSeparator) { + + if (filepath == null) + return false; // null name cannot match + if (pattern == null) + return true; // null pattern is equivalent to '*' + + // offsets inside pattern + int pSegmentStart = pattern[0] == pathSeparator ? 1 : 0; + int pLength = pattern.length; + int pSegmentEnd = CharOperation.indexOf(pathSeparator, pattern, pSegmentStart+1); + if (pSegmentEnd < 0) pSegmentEnd = pLength; + + // special case: pattern foo\ is equivalent to foo\** + boolean freeTrailingDoubleStar = pattern[pLength - 1] == pathSeparator; + + // offsets inside filepath + int fSegmentStart, fLength = filepath.length; + if (filepath[0] != pathSeparator){ + fSegmentStart = 0; + } else { + fSegmentStart = 1; + } + if (fSegmentStart != pSegmentStart) { + return false; // both must start with a separator or none. + } + int fSegmentEnd = CharOperation.indexOf(pathSeparator, filepath, fSegmentStart+1); + if (fSegmentEnd < 0) fSegmentEnd = fLength; + + // first segments + while (pSegmentStart < pLength + && !(pSegmentEnd == pLength && freeTrailingDoubleStar + || (pSegmentEnd == pSegmentStart + 2 + && pattern[pSegmentStart] == '*' + && pattern[pSegmentStart + 1] == '*'))) { + + if (fSegmentStart >= fLength) + return false; + if (!CharOperation + .match( + pattern, + pSegmentStart, + pSegmentEnd, + filepath, + fSegmentStart, + fSegmentEnd, + isCaseSensitive)) { + return false; + } + + // jump to next segment + pSegmentEnd = + CharOperation.indexOf( + pathSeparator, + pattern, + pSegmentStart = pSegmentEnd + 1); + // skip separator + if (pSegmentEnd < 0) + pSegmentEnd = pLength; + + fSegmentEnd = + CharOperation.indexOf( + pathSeparator, + filepath, + fSegmentStart = fSegmentEnd + 1); + // skip separator + if (fSegmentEnd < 0) fSegmentEnd = fLength; + } + + /* check sequence of doubleStar+segment */ + int pSegmentRestart; + if ((pSegmentStart >= pLength && freeTrailingDoubleStar) + || (pSegmentEnd == pSegmentStart + 2 + && pattern[pSegmentStart] == '*' + && pattern[pSegmentStart + 1] == '*')) { + pSegmentEnd = + CharOperation.indexOf( + pathSeparator, + pattern, + pSegmentStart = pSegmentEnd + 1); + // skip separator + if (pSegmentEnd < 0) pSegmentEnd = pLength; + pSegmentRestart = pSegmentStart; + } else { + if (pSegmentStart >= pLength) return fSegmentStart >= fLength; // true if filepath is done too. + pSegmentRestart = 0; // force fSegmentStart check + } + int fSegmentRestart = fSegmentStart; + checkSegment : while (fSegmentStart < fLength) { + + if (pSegmentStart >= pLength) { + if (freeTrailingDoubleStar) return true; + // mismatch - restart current path segment + pSegmentEnd = + CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentRestart); + if (pSegmentEnd < 0) pSegmentEnd = pLength; + + fSegmentRestart = + CharOperation.indexOf(pathSeparator, filepath, fSegmentRestart + 1); + // skip separator + if (fSegmentRestart < 0) { + fSegmentRestart = fLength; + } else { + fSegmentRestart++; + } + fSegmentEnd = + CharOperation.indexOf(pathSeparator, filepath, fSegmentStart = fSegmentRestart); + if (fSegmentEnd < 0) fSegmentEnd = fLength; + continue checkSegment; + } + + /* path segment is ending */ + if (pSegmentEnd == pSegmentStart + 2 + && pattern[pSegmentStart] == '*' + && pattern[pSegmentStart + 1] == '*') { + pSegmentEnd = + CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentEnd + 1); + // skip separator + if (pSegmentEnd < 0) pSegmentEnd = pLength; + pSegmentRestart = pSegmentStart; + fSegmentRestart = fSegmentStart; + if (pSegmentStart >= pLength) return true; + continue checkSegment; + } + /* chech current path segment */ + if (!CharOperation.match( + pattern, + pSegmentStart, + pSegmentEnd, + filepath, + fSegmentStart, + fSegmentEnd, + isCaseSensitive)) { + // mismatch - restart current path segment + pSegmentEnd = + CharOperation.indexOf(pathSeparator, pattern, pSegmentStart = pSegmentRestart); + if (pSegmentEnd < 0) pSegmentEnd = pLength; + + fSegmentRestart = + CharOperation.indexOf(pathSeparator, filepath, fSegmentRestart + 1); + // skip separator + if (fSegmentRestart < 0) { + fSegmentRestart = fLength; + } else { + fSegmentRestart++; + } + fSegmentEnd = + CharOperation.indexOf(pathSeparator, filepath, fSegmentStart = fSegmentRestart); + if (fSegmentEnd < 0) fSegmentEnd = fLength; + continue checkSegment; + } + // jump to next segment + pSegmentEnd = + CharOperation.indexOf( + pathSeparator, + pattern, + pSegmentStart = pSegmentEnd + 1); + // skip separator + if (pSegmentEnd < 0) + pSegmentEnd = pLength; + + fSegmentEnd = + CharOperation.indexOf( + pathSeparator, + filepath, + fSegmentStart = fSegmentEnd + 1); + // skip separator + if (fSegmentEnd < 0) + fSegmentEnd = fLength; + } + + return (pSegmentRestart >= pSegmentEnd) + || (fSegmentStart >= fLength && pSegmentStart >= pLength) + || (pSegmentStart == pLength - 2 + && pattern[pSegmentStart] == '*' + && pattern[pSegmentStart + 1] == '*') + || (pSegmentStart == pLength && freeTrailingDoubleStar); +} + +/** + * Answers the number of occurrences of the given character in the given array, 0 if any. + * + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => 3
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => 0
    + * 
    + *
  4. + *
+ * + * @param toBeFound the given character + * @param array the given array + * @return the number of occurrences of the given character in the given array, 0 if any + * @throws NullPointerException if array is null + */ +public static final int occurencesOf(char toBeFound, char[] array) { + int count = 0; + for (int i = 0; i < array.length; i++) + if (toBeFound == array[i]) + count++; + return count; +} + +/** + * Answers the number of occurrences of the given character in the given array starting + * at the given index, 0 if any. + * + *
+ *
+ * For example: + *
    + *
  1. + *    toBeFound = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 2
    + *    result => 2
    + * 
    + *
  2. + *
  3. + *    toBeFound = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 0
    + *    result => 0
    + * 
    + *
  4. + *
+ * + * @param toBeFound the given character + * @param array the given array + * @param start the given index + * @return the number of occurrences of the given character in the given array, 0 if any + * @throws NullPointerException if array is null + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 + */ +public static final int occurencesOf( + char toBeFound, + char[] array, + int start) { + int count = 0; + for (int i = start; i < array.length; i++) + if (toBeFound == array[i]) + count++; + return count; +} +/** + * Return the int value represented by the designated subpart of array. The + * calculation of the result for single-digit positive integers is optimized in + * time. + * @param array the array within which the int value is to be parsed + * @param start first character of the int value in array + * @param length length of the int value in array + * @return the int value of a subpart of array + * @throws NumberFormatException if the designated subpart of array does not + * parse to an int + * @since 3.4 + */ +public static final int parseInt(char[] array, int start, int length) throws NumberFormatException { + if (length == 1) { + int result = array[start] - '0'; + if (result < 0 || result > 9) { + throw new NumberFormatException("invalid digit"); //$NON-NLS-1$ + } + return result; + } else { + return Integer.parseInt(new String(array, start, length)); + } +} +/** + * Answers true if the given name starts with the given prefix, false otherwise. + * The comparison is case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    prefix = { 'a' , 'b' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'c' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @param prefix the given prefix + * @param name the given name + * @return true if the given name starts with the given prefix, false otherwise + * @throws NullPointerException if the given name is null or if the given prefix is null + */ +public static final boolean prefixEquals(char[] prefix, char[] name) { + + int max = prefix.length; + if (name.length < max) + return false; + for (int i = max; + --i >= 0; + ) // assumes the prefix is not larger than the name + if (prefix[i] != name[i]) + return false; + return true; +} + +/** + * Answers true if the given name starts with the given prefix, false otherwise. + * isCaseSensitive is used to find out whether or not the comparison should be case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    prefix = { 'a' , 'B' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'B' }
    + *    name = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @param prefix the given prefix + * @param name the given name + * @param isCaseSensitive to find out whether or not the comparison should be case sensitive + * @return true if the given name starts with the given prefix, false otherwise + * @throws NullPointerException if the given name is null or if the given prefix is null + */ +public static final boolean prefixEquals( + char[] prefix, + char[] name, + boolean isCaseSensitive) { + return prefixEquals(prefix, name, isCaseSensitive, 0); +} + +/** + * Answers true if the given name, starting from the given index, starts with the given prefix, + * false otherwise. isCaseSensitive is used to find out whether or not the comparison should be + * case sensitive. + *
+ *
+ * For example: + *
    + *
  1. + *    prefix = { 'a' , 'B' }
    + *    name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    startIndex = 2
    + *    isCaseSensitive = false
    + *    result => true
    + * 
    + *
  2. + *
  3. + *    prefix = { 'a' , 'B' }
    + *    name = { 'c', 'd', 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    startIndex = 2
    + *    isCaseSensitive = true
    + *    result => false
    + * 
    + *
  4. + *
+ * + * @param prefix the given prefix + * @param name the given name + * @param isCaseSensitive to find out whether or not the comparison should be case sensitive + * @param startIndex index from which the prefix should be searched in the name + * @return true if the given name starts with the given prefix, false otherwise + * @throws NullPointerException if the given name is null or if the given prefix is null + * @since 3.7 + */ +public static final boolean prefixEquals( + char[] prefix, + char[] name, + boolean isCaseSensitive, + int startIndex) { + + int max = prefix.length; + if (name.length - startIndex < max) + return false; + if (isCaseSensitive) { + for (int i = max; --i >= 0;) // assumes the prefix is not larger than the name + if (prefix[i] != name[startIndex + i]) + return false; + return true; + } + + for (int i = max; --i >= 0;) // assumes the prefix is not larger than the name + if (ScannerHelper.toLowerCase(prefix[i]) + != ScannerHelper.toLowerCase(name[startIndex + i])) + return false; + return true; +} + +/** + * Answers a new array removing a given character. Answers the given array if there is + * no occurrence of the character to remove. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'c', 'b', 'a' }
    + *    toBeRemoved = 'b'
    + *    return { 'a' , 'c', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeRemoved = 'c'
    + *    return array
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param toBeRemoved the character to be removed + * @return a new array removing given character + * @since 3.2 + */ +public static final char[] remove(char[] array, char toBeRemoved) { + + if (array == null) return null; + int length = array.length; + if (length == 0) return array; + char[] result = null; + int count = 0; + for (int i = 0; i < length; i++) { + char c = array[i]; + if (c == toBeRemoved) { + if (result == null) { + result = new char[length]; + System.arraycopy(array, 0, result, 0, i); + count = i; + } + } else if (result != null) { + result[count++] = c; + } + } + if (result == null) return array; + System.arraycopy(result, 0, result = new char[count], 0, count); + return result; +} + +/** + * Replace all occurrence of the character to be replaced with the replacement character in the + * given array. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = 'b'
    + *    replacementChar = 'a'
    + *    result => No returned value, but array is now equals to { 'a' , 'a', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = 'c'
    + *    replacementChar = 'a'
    + *    result => No returned value, but array is now equals to { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param toBeReplaced the character to be replaced + * @param replacementChar the replacement character + * @throws NullPointerException if the given array is null + */ +public static final void replace( + char[] array, + char toBeReplaced, + char replacementChar) { + if (toBeReplaced != replacementChar) { + for (int i = 0, max = array.length; i < max; i++) { + if (array[i] == toBeReplaced) + array[i] = replacementChar; + } + } +} + +/** + * Replace all occurrences of characters to be replaced with the replacement character in the + * given array. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'c', 'a', 'b', 'c', 'a' }
    + *    toBeReplaced = { 'b', 'c' }
    + *    replacementChar = 'a'
    + *    result => No returned value, but array is now equals to { 'a' , 'a', 'a', 'a', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
+ * + * @param array the given array + * @param toBeReplaced characters to be replaced + * @param replacementChar the replacement character + * @throws NullPointerException if arrays are null. + * @since 3.1 + */ +public static final void replace(char[] array, char[] toBeReplaced, char replacementChar) { + replace(array, toBeReplaced, replacementChar, 0, array.length); +} + +/** + * Replace all occurrences of characters to be replaced with the replacement character in the + * given array from the start position (inclusive) to the end position (exclusive). + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'c', 'a', 'b', 'c', 'a' }
    + *    toBeReplaced = { 'b', 'c' }
    + *    replacementChar = 'a'
    + *    start = 4
    + *    end = 8
    + *    result => No returned value, but array is now equals to { 'a' , 'b', 'b', 'c', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
+ * + * @param array the given array + * @param toBeReplaced characters to be replaced + * @param replacementChar the replacement character + * @param start the given start position (inclusive) + * @param end the given end position (exclusive) + * @throws NullPointerException if arrays are null. + * @since 3.2 + */ +public static final void replace(char[] array, char[] toBeReplaced, char replacementChar, int start, int end) { + for (int i = end; --i >= start;) + for (int j = toBeReplaced.length; --j >= 0;) + if (array[i] == toBeReplaced[j]) + array[i] = replacementChar; +} +/** + * Answers a new array of characters with substitutions. No side-effect is operated on the original + * array, in case no substitution happened, then the result is the same as the + * original one. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = { 'b' }
    + *    replacementChar = { 'a', 'a' }
    + *    result => { 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = { 'c' }
    + *    replacementChar = { 'a' }
    + *    result => { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param toBeReplaced characters to be replaced + * @param replacementChars the replacement characters + * @return a new array of characters with substitutions or the given array if none + * @throws NullPointerException if the given array is null + */ +public static final char[] replace( + char[] array, + char[] toBeReplaced, + char[] replacementChars) { + + int max = array.length; + int replacedLength = toBeReplaced.length; + int replacementLength = replacementChars.length; + + int[] starts = new int[5]; + int occurrenceCount = 0; + + if (!equals(toBeReplaced, replacementChars)) { + + next : for (int i = 0; i < max;) { + int index = indexOf(toBeReplaced, array, true, i); + if (index == -1) { + i++; + continue next; + } + if (occurrenceCount == starts.length) { + System.arraycopy( + starts, + 0, + starts = new int[occurrenceCount * 2], + 0, + occurrenceCount); + } + starts[occurrenceCount++] = index; + i = index + replacedLength; + } + } + if (occurrenceCount == 0) + return array; + char[] result = + new char[max + + occurrenceCount * (replacementLength - replacedLength)]; + int inStart = 0, outStart = 0; + for (int i = 0; i < occurrenceCount; i++) { + int offset = starts[i] - inStart; + System.arraycopy(array, inStart, result, outStart, offset); + inStart += offset; + outStart += offset; + System.arraycopy( + replacementChars, + 0, + result, + outStart, + replacementLength); + inStart += replacedLength; + outStart += replacementLength; + } + System.arraycopy(array, inStart, result, outStart, max - inStart); + return result; +} + +/** + * Replace all occurrence of the character to be replaced with the replacement character + * in a copy of the given array. Returns the given array if no occurrences of the character + * to be replaced are found. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = 'b'
    + *    replacementChar = 'a'
    + *    result => A new array that is equals to { 'a' , 'a', 'a', 'a', 'a', 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    toBeReplaced = 'c'
    + *    replacementChar = 'a'
    + *    result => The original array that remains unchanged.
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param toBeReplaced the character to be replaced + * @param replacementChar the replacement character + * @throws NullPointerException if the given array is null + * @since 3.1 + */ +public static final char[] replaceOnCopy( + char[] array, + char toBeReplaced, + char replacementChar) { + + char[] result = null; + for (int i = 0, length = array.length; i < length; i++) { + char c = array[i]; + if (c == toBeReplaced) { + if (result == null) { + result = new char[length]; + System.arraycopy(array, 0, result, 0, i); + } + result[i] = replacementChar; + } else if (result != null) { + result[i] = c; + } + } + if (result == null) return array; + return result; +} + +/** + * Return a new array which is the split of the given array using the given divider and trimming each subarray to remove + * whitespaces equals to ' '. + *
+ *
+ * For example: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  2. + *
  3. + *    divider = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  4. + *
  5. + *    divider = 'b'
    + *    array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  6. + *
  7. + *    divider = 'c'
    + *    array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  8. + *
+ * + * @param divider the given divider + * @param array the given array + * @return a new array which is the split of the given array using the given divider and trimming each subarray to remove + * whitespaces equals to ' ' + */ +public static final char[][] splitAndTrimOn(char divider, char[] array) { + int length = array == null ? 0 : array.length; + if (length == 0) + return NO_CHAR_CHAR; + + int wordCount = 1; + for (int i = 0; i < length; i++) + if (array[i] == divider) + wordCount++; + char[][] split = new char[wordCount][]; + int last = 0, currentWord = 0; + for (int i = 0; i < length; i++) { + if (array[i] == divider) { + int start = last, end = i - 1; + while (start < i && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord] = new char[end - start + 1]; + System.arraycopy( + array, + start, + split[currentWord++], + 0, + end - start + 1); + last = i + 1; + } + } + int start = last, end = length - 1; + while (start < length && array[start] == ' ') + start++; + while (end > start && array[end] == ' ') + end--; + split[currentWord] = new char[end - start + 1]; + System.arraycopy( + array, + start, + split[currentWord++], + 0, + end - start + 1); + return split; +} + +/** + * Return a new array which is the split of the given array using the given divider. + *
+ *
+ * For example: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a' }, {  }, { 'a' }, { 'a' } }
    + * 
    + *
  2. + *
  3. + *    divider = 'c'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    result => { { 'a', 'b', 'b', 'a', 'b', 'a' } }
    + * 
    + *
  4. + *
  5. + *    divider = 'c'
    + *    array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' }
    + *    result => { { ' ', 'a', 'b', 'b', 'a', 'b', 'a', ' ' } }
    + * 
    + *
  6. + *
+ * + * @param divider the given divider + * @param array the given array + * @return a new array which is the split of the given array using the given divider + */ +public static final char[][] splitOn(char divider, char[] array) { + int length = array == null ? 0 : array.length; + if (length == 0) + return NO_CHAR_CHAR; + + int wordCount = 1; + for (int i = 0; i < length; i++) + if (array[i] == divider) + wordCount++; + char[][] split = new char[wordCount][]; + int last = 0, currentWord = 0; + for (int i = 0; i < length; i++) { + if (array[i] == divider) { + split[currentWord] = new char[i - last]; + System.arraycopy( + array, + last, + split[currentWord++], + 0, + i - last); + last = i + 1; + } + } + split[currentWord] = new char[length - last]; + System.arraycopy(array, last, split[currentWord], 0, length - last); + return split; +} + +/** + * Return a new array which is the split of the given array using the given divider. The given end + * is exclusive and the given start is inclusive. + *
+ *
+ * For example: + *
    + *
  1. + *    divider = 'b'
    + *    array = { 'a' , 'b', 'b', 'a', 'b', 'a' }
    + *    start = 2
    + *    end = 5
    + *    result => { {  }, { 'a' }, {  } }
    + * 
    + *
  2. + *
+ * + * @param divider the given divider + * @param array the given array + * @param start the given starting index + * @param end the given ending index + * @return a new array which is the split of the given array using the given divider + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length + */ +public static final char[][] splitOn( + char divider, + char[] array, + int start, + int end) { + int length = array == null ? 0 : array.length; + if (length == 0 || start > end) + return NO_CHAR_CHAR; + + int wordCount = 1; + for (int i = start; i < end; i++) + if (array[i] == divider) + wordCount++; + char[][] split = new char[wordCount][]; + int last = start, currentWord = 0; + for (int i = start; i < end; i++) { + if (array[i] == divider) { + split[currentWord] = new char[i - last]; + System.arraycopy( + array, + last, + split[currentWord++], + 0, + i - last); + last = i + 1; + } + } + split[currentWord] = new char[end - last]; + System.arraycopy(array, last, split[currentWord], 0, end - last); + return split; +} + +/** + * Return a new array which is the split of the given array using the given divider ignoring the + * text between (possibly nested) openEncl and closingEncl. If there are no openEncl in the code + * this is identical to {@link CharOperation#splitOn(char, char[], int, int)}. The given end + * is exclusive and the given start is inclusive. + *
+ *
+ * For example: + *
    + *
  1. + *    divider = ','
    + *    array = { 'A' , '<', 'B', ',', 'C', '>', ',', 'D' }
    + *    start = 0
    + *    end = 8
    + *    result => { {  'A' , '<', 'B', ',', 'C', '>'}, { 'D' }}
    + * 
    + *
  2. + *
+ * + * @param divider the given divider + * @param openEncl the opening enclosure + * @param closeEncl the closing enclosure + * @param array the given array + * @param start the given starting index + * @param end the given ending index + * @return a new array which is the split of the given array using the given divider + * @throws ArrayIndexOutOfBoundsException if start is lower than 0 or end is greater than the array length + * @since 3.12 + */ +public static final char[][] splitOnWithEnclosures( + char divider, + char openEncl, + char closeEncl, + char[] array, + int start, + int end) { + int length = array == null ? 0 : array.length; + if (length == 0 || start > end) + return NO_CHAR_CHAR; + + int wordCount = 1; + int enclCount = 0; + for (int i = start; i < end; i++) { + if (array[i] == openEncl) + enclCount++; + else if (array[i] == divider) + wordCount++; + } + if (enclCount == 0) + return CharOperation.splitOn(divider, array, start, end); + + int nesting = 0; + if (openEncl == divider || closeEncl == divider) // divider should be distinct + return CharOperation.NO_CHAR_CHAR; + + int[][] splitOffsets = new int[wordCount][2]; //maximum + int last = start, currentWord = 0, prevOffset = start; + for (int i = start; i < end; i++) { + if (array[i] == openEncl) { + ++nesting; + continue; + } + if (array[i] == closeEncl) { + if (nesting > 0) + --nesting; + continue; + } + if (array[i] == divider && nesting == 0) { + splitOffsets[currentWord][0] = prevOffset; + last = splitOffsets[currentWord++][1] = i; + prevOffset = last + 1; + } + } + if (last < end - 1) { + splitOffsets[currentWord][0] = prevOffset; + splitOffsets[currentWord++][1] = end; + } + char[][] split = new char[currentWord][]; + for (int i = 0; i < currentWord; ++i) { + int sStart = splitOffsets[i][0]; + int sEnd = splitOffsets[i][1]; + int size = sEnd - sStart; + split[i] = new char[size]; + System.arraycopy(array, sStart, split[i], 0, size); + } + return split; + } + +/** + * Answers a new array which is a copy of the given array starting at the given start and + * ending at the given end. The given start is inclusive and the given end is exclusive. + * Answers null if start is greater than end, if start is lower than 0 or if end is greater + * than the length of the given array. If end equals -1, it is converted to the array length. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { { 'a' } , { 'b' } }
    + *    start = 0
    + *    end = 1
    + *    result => { { 'a' } }
    + * 
    + *
  2. + *
  3. + *    array = { { 'a' } , { 'b' } }
    + *    start = 0
    + *    end = -1
    + *    result => { { 'a' }, { 'b' } }
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param start the given starting index + * @param end the given ending index + * @return a new array which is a copy of the given array starting at the given start and + * ending at the given end + * @throws NullPointerException if the given array is null + */ +public static final char[][] subarray(char[][] array, int start, int end) { + if (end == -1) + end = array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + + char[][] result = new char[end - start][]; + System.arraycopy(array, start, result, 0, end - start); + return result; +} + +/** + * Answers a new array which is a copy of the given array starting at the given start and + * ending at the given end. The given start is inclusive and the given end is exclusive. + * Answers null if start is greater than end, if start is lower than 0 or if end is greater + * than the length of the given array. If end equals -1, it is converted to the array length. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { 'a' , 'b' }
    + *    start = 0
    + *    end = 1
    + *    result => { 'a' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'b' }
    + *    start = 0
    + *    end = -1
    + *    result => { 'a' , 'b' }
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @param start the given starting index + * @param end the given ending index + * @return a new array which is a copy of the given array starting at the given start and + * ending at the given end + * @throws NullPointerException if the given array is null + */ +public static final char[] subarray(char[] array, int start, int end) { + if (end == -1) + end = array.length; + if (start > end) + return null; + if (start < 0) + return null; + if (end > array.length) + return null; + + char[] result = new char[end - start]; + System.arraycopy(array, start, result, 0, end - start); + return result; +} + +/** + * Answers the result of a char[] conversion to lowercase. Answers null if the given chars array is null. + *
+ * NOTE: If no conversion was necessary, then answers back the argument one. + *
+ *
+ * For example: + *
    + *
  1. + *    chars = { 'a' , 'b' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { 'A', 'b' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  4. + *
+ * + * @param chars the chars to convert + * @return the result of a char[] conversion to lowercase + */ +final static public char[] toLowerCase(char[] chars) { + if (chars == null) + return null; + int length = chars.length; + char[] lowerChars = null; + for (int i = 0; i < length; i++) { + char c = chars[i]; + char lc = ScannerHelper.toLowerCase(c); + if ((c != lc) || (lowerChars != null)) { + if (lowerChars == null) { + System.arraycopy( + chars, + 0, + lowerChars = new char[length], + 0, + i); + } + lowerChars[i] = lc; + } + } + return lowerChars == null ? chars : lowerChars; +} + +/** + * Answers the result of a char[] conversion to uppercase. Answers null if the given chars array is null. + *
+ * NOTE: If no conversion was necessary, then answers back the argument one. + *
+ *
+ * For example: + *
    + *
  1. + *    chars = { 'A' , 'B' }
    + *    result => { 'A' , 'B' }
    + * 
    + *
  2. + *
  3. + *    array = { 'a', 'B' }
    + *    result => { 'A' , 'B' }
    + * 
    + *
  4. + *
+ * + * @param chars the chars to convert + * @return the result of a char[] conversion to uppercase + * + * @since 3.5 + */ +final static public char[] toUpperCase(char[] chars) { + if (chars == null) + return null; + int length = chars.length; + char[] upperChars = null; + for (int i = 0; i < length; i++) { + char c = chars[i]; + char lc = ScannerHelper.toUpperCase(c); + if ((c != lc) || (upperChars != null)) { + if (upperChars == null) { + System.arraycopy( + chars, + 0, + upperChars = new char[length], + 0, + i); + } + upperChars[i] = lc; + } + } + return upperChars == null ? chars : upperChars; +} + +/** + * Answers a new array removing leading and trailing spaces (' '). Answers the given array if there is no + * space characters to remove. + *
+ *
+ * For example: + *
    + *
  1. + *    chars = { ' ', 'a' , 'b', ' ',  ' ' }
    + *    result => { 'a' , 'b' }
    + * 
    + *
  2. + *
  3. + *    array = { 'A', 'b' }
    + *    result => { 'A' , 'b' }
    + * 
    + *
  4. + *
+ * + * @param chars the given array + * @return a new array removing leading and trailing spaces (' ') + */ +final static public char[] trim(char[] chars) { + + if (chars == null) + return null; + + int start = 0, length = chars.length, end = length - 1; + while (start < length && chars[start] == ' ') { + start++; + } + while (end > start && chars[end] == ' ') { + end--; + } + if (start != 0 || end != length - 1) { + return subarray(chars, start, end + 1); + } + return chars; +} + +/** + * Answers a string which is the concatenation of the given array using the '.' as a separator. + *
+ *
+ * For example: + *
    + *
  1. + *    array = { { 'a' } , { 'b' } }
    + *    result => "a.b"
    + * 
    + *
  2. + *
  3. + *    array = { { ' ',  'a' } , { 'b' } }
    + *    result => " a.b"
    + * 
    + *
  4. + *
+ * + * @param array the given array + * @return a string which is the concatenation of the given array using the '.' as a separator + */ +final static public String toString(char[][] array) { + char[] result = concatWith(array, '.'); + return new String(result); +} + +/** + * Answers an array of strings from the given array of char array. + * + * @param array the given array + * @return an array of strings + * @since 3.0 + */ +final static public String[] toStrings(char[][] array) { + if (array == null) return NO_STRINGS; + int length = array.length; + if (length == 0) return NO_STRINGS; + String[] result = new String[length]; + for (int i = 0; i < length; i++) + result[i] = new String(array[i]); + return result; +} +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java new file mode 100644 index 000000000..c51fc3672 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/HashtableOfObjectToIntArray.java @@ -0,0 +1,158 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +/** + * Hashtable of {Object --> int[] } + */ +public final class HashtableOfObjectToIntArray implements Cloneable { + + // to avoid using Enumerations, walk the individual tables skipping nulls + public Object[] keyTable; + public int[][] valueTable; + + public int elementSize; // number of elements in the table + int threshold; + + public HashtableOfObjectToIntArray() { + this(13); + } + + public HashtableOfObjectToIntArray(int size) { + + this.elementSize = 0; + this.threshold = size; // size represents the expected number of elements + int extraRoom = (int) (size * 1.75f); + if (this.threshold == extraRoom) + extraRoom++; + this.keyTable = new Object[extraRoom]; + this.valueTable = new int[extraRoom][]; + } + + public Object clone() throws CloneNotSupportedException { + HashtableOfObjectToIntArray result = (HashtableOfObjectToIntArray) super.clone(); + result.elementSize = this.elementSize; + result.threshold = this.threshold; + + int length = this.keyTable.length; + result.keyTable = new Object[length]; + System.arraycopy(this.keyTable, 0, result.keyTable, 0, length); + + length = this.valueTable.length; + result.valueTable = new int[length][]; + System.arraycopy(this.valueTable, 0, result.valueTable, 0, length); + return result; + } + + public boolean containsKey(Object key) { + int length = this.keyTable.length, + index = (key.hashCode()& 0x7FFFFFFF) % length; + Object currentKey; + while ((currentKey = this.keyTable[index]) != null) { + if (currentKey.equals(key)) + return true; + if (++index == length) { + index = 0; + } + } + return false; + } + + public int[] get(Object key) { + int length = this.keyTable.length, + index = (key.hashCode()& 0x7FFFFFFF) % length; + Object currentKey; + while ((currentKey = this.keyTable[index]) != null) { + if (currentKey.equals(key)) + return this.valueTable[index]; + if (++index == length) { + index = 0; + } + } + return null; + } + + public void keysToArray(Object[] array) { + int index = 0; + for (int i=0, length=this.keyTable.length; i this.threshold) + rehash(); + return value; + } + + public int[] removeKey(Object key) { + int length = this.keyTable.length, + index = (key.hashCode()& 0x7FFFFFFF) % length; + Object currentKey; + while ((currentKey = this.keyTable[index]) != null) { + if (currentKey.equals(key)) { + int[] value = this.valueTable[index]; + this.elementSize--; + this.keyTable[index] = null; + rehash(); + return value; + } + if (++index == length) { + index = 0; + } + } + return null; + } + + private void rehash() { + + HashtableOfObjectToIntArray newHashtable = new HashtableOfObjectToIntArray(this.elementSize * 2); // double the number of expected elements + Object currentKey; + for (int i = this.keyTable.length; --i >= 0;) + if ((currentKey = this.keyTable[i]) != null) + newHashtable.put(currentKey, this.valueTable[i]); + + this.keyTable = newHashtable.keyTable; + this.valueTable = newHashtable.valueTable; + this.threshold = newHashtable.threshold; + } + + public int size() { + return this.elementSize; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + Object key; + for (int i = 0, length = this.keyTable.length; i < length; i++) { + if ((key = this.keyTable[i]) != null) { + buffer.append(key).append(" -> "); //$NON-NLS-1$ + int[] ints = this.valueTable[i]; + buffer.append('['); + if (ints != null) { + for (int j = 0, max = ints.length; j < max; j++) { + if (j > 0) { + buffer.append(','); + } + buffer.append(ints[j]); + } + } + buffer.append("]\n"); //$NON-NLS-1$ + } + } + return String.valueOf(buffer); + } +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java new file mode 100644 index 000000000..01ca87833 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocConstants.java @@ -0,0 +1,35 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +public interface JavadocConstants { + + String ANCHOR_PREFIX_END = "\""; //$NON-NLS-1$ + char[] ANCHOR_PREFIX_START = ""; //$NON-NLS-1$ +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java new file mode 100644 index 000000000..8c4d920f9 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/JavadocContents.java @@ -0,0 +1,539 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +import java.util.stream.Collectors; + +import org.springframework.ide.vscode.commons.java.IField; +import org.springframework.ide.vscode.commons.java.IMethod; +import org.springframework.ide.vscode.commons.java.IType; + +public class JavadocContents { + private static final int[] UNKNOWN_FORMAT = new int[0]; + +// private IType type; + private char[] content; + + private int childrenStart; + + private boolean hasComputedChildrenSections = false; + private int indexOfFieldDetails; + private int indexOfConstructorDetails; + private int indexOfMethodDetails; + private int indexOfEndOfClassData; + + private int indexOfFieldsBottom; + private int indexOfAllMethodsTop; + private int indexOfAllMethodsBottom; + + private int[] typeDocRange; + private HashtableOfObjectToIntArray fieldDocRanges; + private HashtableOfObjectToIntArray methodDocRanges; + + private int[] fieldAnchorIndexes; + private int fieldAnchorIndexesCount; + private int fieldLastAnchorFoundIndex; + private int[] methodAnchorIndexes; + private int methodAnchorIndexesCount; + private int methodLastAnchorFoundIndex; + private int[] unknownFormatAnchorIndexes; + private int unknownFormatAnchorIndexesCount; + private int unknownFormatLastAnchorFoundIndex; + private int[] tempAnchorIndexes; + private int tempAnchorIndexesCount; + private int tempLastAnchorFoundIndex; + +// public JavadocContents(IType type, String content) { +// this(content); +// this.type = type; +// } + + public JavadocContents(String content) { + this.content = content != null ? content.toCharArray() : null; + } + /* + * Returns the part of the javadoc that describe the type + */ + public String getTypeDoc(IType type) throws Exception { + if (this.content == null) return null; + + synchronized (this) { + if (this.typeDocRange == null) { + computeTypeRange(type); + } + } + + if (this.typeDocRange != null) { + if (this.typeDocRange == UNKNOWN_FORMAT) throw new Exception("Unknown javadoc format " + type); + return String.valueOf(CharOperation.subarray(this.content, this.typeDocRange[0], this.typeDocRange[1])); + } + return null; + } + +// public String getPackageDoc() throws JavaModelException { +// if (this.content == null) return null; +// int[] range = null; +// int index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START2, this.content, false, 0); +// if (index == -1) { +// index = CharOperation.indexOf(JavadocConstants.PACKAGE_DESCRIPTION_START, this.content, false, 0); +// } +// if (index == -1) return null; +// index = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, index); +// if (index == -1) return null; +// +// int start = CharOperation.indexOf(JavadocConstants.H2_PREFIX, this.content, false, index); +// if (start != -1) { +// start = CharOperation.indexOf(JavadocConstants.H2_SUFFIX, this.content, false, start); +// if (start != -1) index = start + JavadocConstants.H2_SUFFIX_LENGTH; +// } +// if (index != -1) { +// int end = CharOperation.indexOf(JavadocConstants.BOTTOM_NAVBAR, this.content, false, index); +// if (end == -1) end = this.content.length -1; +// range = new int[]{index, end}; +// return String.valueOf(CharOperation.subarray(this.content, range[0], range[1])); +// } +// return null; +// } + + /* + * Returns the part of the javadoc that describe a field of the type + */ + public String getFieldDoc(IField child) throws Exception { + if (this.content == null) return null; + + int[] range = null; + synchronized (this) { + if (this.fieldDocRanges == null) { + this.fieldDocRanges = new HashtableOfObjectToIntArray(); + } else { + range = this.fieldDocRanges.get(child); + } + + if (range == null) { + range = computeFieldRange(child); + this.fieldDocRanges.put(child, range); + } + } + + if (range != null) { + if (range == UNKNOWN_FORMAT) throw new Exception("Unknown javadoc format " + child); + return String.valueOf(CharOperation.subarray(this.content, range[0], range[1])); + } + return null; + } + + /* + * Returns the part of the javadoc that describe a method of the type + */ + public String getMethodDoc(IMethod method) throws Exception { + if (this.content == null) return null; + + int[] range = null; + synchronized (this) { + if (this.methodDocRanges == null) { + this.methodDocRanges = new HashtableOfObjectToIntArray(); + } else { + range = this.methodDocRanges.get(method); + } + + if (range == null) { + range = computeMethodRange(method); + this.methodDocRanges.put(method, range); + } + } + + if (range != null) { + if (range == UNKNOWN_FORMAT) { + throw new Exception("Unknown javadoc format " + method); + } + return String.valueOf(CharOperation.subarray(this.content, range[0], range[1])); + } + return null; + } + + /* + * Compute the ranges of the parts of the javadoc that describe each method of the type + */ + private int[] computeChildRange(char[] anchor, int indexOfSectionBottom) throws Exception { + + // checks each known anchor locations + if (this.tempAnchorIndexesCount > 0) { + for (int i = 0; i < this.tempAnchorIndexesCount; i++) { + int anchorEndStart = this.tempAnchorIndexes[i]; + + if (anchorEndStart != -1 && CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) { + + this.tempAnchorIndexes[i] = -1; + + return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom); + } + } + } + + int fromIndex = this.tempLastAnchorFoundIndex; + int index; + + // check each next unknown anchor locations + while ((index = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, fromIndex)) != -1 && (index < indexOfSectionBottom || indexOfSectionBottom == -1)) { + fromIndex = index + 1; + + int anchorEndStart = index + JavadocConstants.ANCHOR_PREFIX_START_LENGHT; + + this.tempLastAnchorFoundIndex = anchorEndStart; + + if (CharOperation.prefixEquals(anchor, this.content, false, anchorEndStart)) { + return computeChildRange(anchorEndStart, anchor, indexOfSectionBottom); + } else { + if (this.tempAnchorIndexes.length == this.tempAnchorIndexesCount) { + System.arraycopy(this.tempAnchorIndexes, 0, this.tempAnchorIndexes = new int[this.tempAnchorIndexesCount + 20], 0, this.tempAnchorIndexesCount); + } + + this.tempAnchorIndexes[this.tempAnchorIndexesCount++] = anchorEndStart; + } + } + + return null; + } + + private int[] computeChildRange(int anchorEndStart, char[] anchor, int indexOfBottom) { + int[] range = null; + + // try to find the bottom of the section + if (indexOfBottom != -1) { + // try to find the end of the anchor + int indexOfEndLink = CharOperation.indexOf(JavadocConstants.ANCHOR_SUFFIX, this.content, false, anchorEndStart + anchor.length); + if (indexOfEndLink != -1) { + // try to find the next anchor + int indexOfNextElement = CharOperation.indexOf(JavadocConstants.ANCHOR_PREFIX_START, this.content, false, indexOfEndLink); + + int javadocStart = indexOfEndLink + JavadocConstants.ANCHOR_SUFFIX_LENGTH; + int javadocEnd = indexOfNextElement == -1 ? indexOfBottom : Math.min(indexOfNextElement, indexOfBottom); + range = new int[]{javadocStart, javadocEnd}; + } else { + // the anchor has no suffix + range = UNKNOWN_FORMAT; + } + } else { + // the detail section has no bottom + range = UNKNOWN_FORMAT; + } + + return range; + } + + private void computeChildrenSections() { + // try to find the next separator part + int lastIndex = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false, this.childrenStart); + lastIndex = lastIndex == -1 ? this.childrenStart : lastIndex; + + // try to find field detail start + this.indexOfFieldDetails = CharOperation.indexOf(JavadocConstants.FIELD_DETAIL, this.content, false, lastIndex); + lastIndex = this.indexOfFieldDetails == -1 ? lastIndex : this.indexOfFieldDetails; + + // try to find constructor detail start + this.indexOfConstructorDetails = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_DETAIL, this.content, false, lastIndex); + lastIndex = this.indexOfConstructorDetails == -1 ? lastIndex : this.indexOfConstructorDetails; + + // try to find method detail start + this.indexOfMethodDetails = CharOperation.indexOf(JavadocConstants.METHOD_DETAIL, this.content, false, lastIndex); + lastIndex = this.indexOfMethodDetails == -1 ? lastIndex : this.indexOfMethodDetails; + + // we take the end of class data + this.indexOfEndOfClassData = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false, lastIndex); + + // try to find the field detail end + this.indexOfFieldsBottom = + this.indexOfConstructorDetails != -1 ? this.indexOfConstructorDetails : + this.indexOfMethodDetails != -1 ? this.indexOfMethodDetails: + this.indexOfEndOfClassData; + + this.indexOfAllMethodsTop = + this.indexOfConstructorDetails != -1 ? + this.indexOfConstructorDetails : + this.indexOfMethodDetails; + + this.indexOfAllMethodsBottom = this.indexOfEndOfClassData; + + this.hasComputedChildrenSections = true; + } + + /* + * Compute the ranges of the parts of the javadoc that describe each child of the type (fields, methods) + */ + private int[] computeFieldRange(IField field) throws Exception { + IType type = field.getDeclaringType(); + + if (!this.hasComputedChildrenSections) { + computeChildrenSections(); + } + + StringBuffer buffer = new StringBuffer(field.getElementName()); + buffer.append(JavadocConstants.ANCHOR_PREFIX_END); + char[] anchor = String.valueOf(buffer).toCharArray(); + + int[] range = null; + + if (this.indexOfFieldDetails == -1 || this.indexOfFieldsBottom == -1) { + // the detail section has no top or bottom, so the doc has an unknown format + if (this.unknownFormatAnchorIndexes == null) { + this.unknownFormatAnchorIndexes = new int[(int)type.getFields().count()]; + this.unknownFormatAnchorIndexesCount = 0; + this.unknownFormatLastAnchorFoundIndex = this.childrenStart; + } + + this.tempAnchorIndexes = this.unknownFormatAnchorIndexes; + this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount; + this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex; + + range = computeChildRange(anchor, this.indexOfFieldsBottom); + + this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; + this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount; + this.unknownFormatAnchorIndexes = this.tempAnchorIndexes; + } else { + if (this.fieldAnchorIndexes == null) { + this.fieldAnchorIndexes = new int[(int)type.getFields().count()]; + this.fieldAnchorIndexesCount = 0; + this.fieldLastAnchorFoundIndex = this.indexOfFieldDetails; + } + + this.tempAnchorIndexes = this.fieldAnchorIndexes; + this.tempAnchorIndexesCount = this.fieldAnchorIndexesCount; + this.tempLastAnchorFoundIndex = this.fieldLastAnchorFoundIndex; + + range = computeChildRange(anchor, this.indexOfFieldsBottom); + + this.fieldLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; + this.fieldAnchorIndexesCount = this.tempAnchorIndexesCount; + this.fieldAnchorIndexes = this.tempAnchorIndexes; + } + + return range; + } + + /* + * Compute the ranges of the parts of the javadoc that describe each method of the type + */ + private int[] computeMethodRange(IMethod method) throws Exception { + IType type = method.getDeclaringType(); + + if (!this.hasComputedChildrenSections) { + computeChildrenSections(); + } + + char[] anchor = computeMethodAnchorPrefixEnd(method).toCharArray(); + + int[] range = null; + + + if (this.indexOfAllMethodsTop == -1 || this.indexOfAllMethodsBottom == -1) { + // the detail section has no top or bottom, so the doc has an unknown format + if (this.unknownFormatAnchorIndexes == null) { + int childernCount = (int)(type.getMethods().count() + type.getFields().count()); + this.unknownFormatAnchorIndexes = new int[childernCount]; + this.unknownFormatAnchorIndexesCount = 0; + this.unknownFormatLastAnchorFoundIndex = this.childrenStart; + } + + this.tempAnchorIndexes = this.unknownFormatAnchorIndexes; + this.tempAnchorIndexesCount = this.unknownFormatAnchorIndexesCount; + this.tempLastAnchorFoundIndex = this.unknownFormatLastAnchorFoundIndex; + + range = computeChildRange(anchor, this.indexOfFieldsBottom); + if (range == null) { + range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom); + } + + this.unknownFormatLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; + this.unknownFormatAnchorIndexesCount = this.tempAnchorIndexesCount; + this.unknownFormatAnchorIndexes = this.tempAnchorIndexes; + } else { + if (this.methodAnchorIndexes == null) { + this.methodAnchorIndexes = new int[(int)type.getMethods().count()]; + this.methodAnchorIndexesCount = 0; + this.methodLastAnchorFoundIndex = this.indexOfAllMethodsTop; + } + + this.tempAnchorIndexes = this.methodAnchorIndexes; + this.tempAnchorIndexesCount = this.methodAnchorIndexesCount; + this.tempLastAnchorFoundIndex = this.methodLastAnchorFoundIndex; + + range = computeChildRange(anchor, this.indexOfAllMethodsBottom); + if (range == null) { + range = computeChildRange(getJavadoc8Anchor(anchor), this.indexOfAllMethodsBottom); + } + + this.methodLastAnchorFoundIndex = this.tempLastAnchorFoundIndex; + this.methodAnchorIndexesCount = this.tempAnchorIndexesCount; + this.methodAnchorIndexes = this.tempAnchorIndexes; + } + + return range; + } + + private static char[] getJavadoc8Anchor(char[] anchor) { + // fix for bug 432284: [1.8] Javadoc-8-style anchors not found by IMethod#getAttachedJavadoc(..) + char[] anchor8 = new char[anchor.length]; + int i8 = 0; + for (int i = 0; i < anchor.length; i++) { + char ch = anchor[i]; + switch (ch) { + case '(': + case ')': + case ',': + anchor8[i8++] = '-'; + break; + case '[': + anchor8[i8++] = ':'; + anchor8[i8++] = 'A'; + break; + case ' ': // handled by preceding ',' + case ']': // handled by preceding '[' + break; + default: + anchor8[i8++] = ch; + } + } + if (i8 != anchor.length) { + anchor8 = CharOperation.subarray(anchor8, 0, i8); + } + return anchor8; + } + + private String computeMethodAnchorPrefixEnd(IMethod method) throws Exception { +// String typeQualifiedName = null; +// if (this.type.isMember()) { +// IType currentType = this.type; +// StringBuffer buffer = new StringBuffer(); +// while (currentType != null) { +// buffer.insert(0, currentType.getElementName()); +// currentType = currentType.getDeclaringType(); +// if (currentType != null) { +// buffer.insert(0, '.'); +// } +// } +// typeQualifiedName = new String(buffer.toString()); +// } else { +// typeQualifiedName = this.type.getElementName(); +// } + +// String methodName = method.getElementName(); +// if (method.getElementName().equals(method.getDeclaringType().getElementName())) { +// methodName = typeQualifiedName; +// } + + String anchor = createMethodAnchor(method); + +// char[] genericSignature = info.getGenericSignature(); +// if (genericSignature != null) { +// genericSignature = CharOperation.replaceOnCopy(genericSignature, '/', '.'); +// anchor = Util.toAnchor(0, genericSignature, methodName, Flags.isVarargs(method.getFlags())); +// if (anchor == null) throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.UNKNOWN_JAVADOC_FORMAT, method)); +// } else { +// anchor = Signature.toString(method.getSignature().replace('/', '.'), methodName, null, true, false, Flags.isVarargs(method.getFlags())); +// } + +// IType declaringType = /*this.type*/method.getDeclaringType(); +// if (declaringType.isMember()) { +// // might need to remove a part of the signature corresponding to the synthetic argument (only for constructor) +// if (method.getElementName().equals(method.getDeclaringType().getElementName()) && !Flags.isStatic(declaringType.getFlags())) { +// int indexOfOpeningParen = anchor.indexOf('('); +// if (indexOfOpeningParen == -1) { +// // should not happen as this is a method signature +// return null; +// } +// int index = indexOfOpeningParen; +// indexOfOpeningParen++; +// int indexOfComma = anchor.indexOf(',', index); +// if (indexOfComma != -1) { +// index = indexOfComma + 2; +// } else { +// // no argument, but a synthetic argument +// index = anchor.indexOf(')', index); +// } +// anchor = anchor.substring(0, indexOfOpeningParen) + anchor.substring(index); +// } +// } + return anchor + JavadocConstants.ANCHOR_PREFIX_END; + } + + private String createMethodAnchor(IMethod method) { + StringBuilder sb = new StringBuilder(method.getElementName()); + sb.append('('); + sb.append(String.join(",", method.parameters().map(p -> p.toString()).collect(Collectors.toList()))); + sb.append(')'); + return sb.toString(); + } + + /* + * Compute the range of the part of the javadoc that describe the type + */ + private void computeTypeRange(IType type) throws Exception { + final int indexOfStartOfClassData = CharOperation.indexOf(JavadocConstants.START_OF_CLASS_DATA, this.content, false); + if (indexOfStartOfClassData == -1) { + this.typeDocRange = UNKNOWN_FORMAT; + return; + } + int indexOfNextSeparator = CharOperation.indexOf(JavadocConstants.SEPARATOR_START, this.content, false, indexOfStartOfClassData); + if (indexOfNextSeparator == -1) { + this.typeDocRange = UNKNOWN_FORMAT; + return; + } + int indexOfNextSummary = CharOperation.indexOf(JavadocConstants.NESTED_CLASS_SUMMARY, this.content, false, indexOfNextSeparator); + if (indexOfNextSummary == -1 && type.isEnum()) { + // try to find enum constant summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ENUM_CONSTANT_SUMMARY, this.content, false, indexOfNextSeparator); + } + if (indexOfNextSummary == -1 && type.isAnnotation()) { + // try to find required enum constant summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_REQUIRED_MEMBER_SUMMARY, this.content, false, indexOfNextSeparator); + if (indexOfNextSummary == -1) { + // try to find optional enum constant summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.ANNOTATION_TYPE_OPTIONAL_MEMBER_SUMMARY, this.content, false, indexOfNextSeparator); + } + } + if (indexOfNextSummary == -1) { + // try to find field summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.FIELD_SUMMARY, this.content, false, indexOfNextSeparator); + } + if (indexOfNextSummary == -1) { + // try to find constructor summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.CONSTRUCTOR_SUMMARY, this.content, false, indexOfNextSeparator); + } + if (indexOfNextSummary == -1) { + // try to find method summary start + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.METHOD_SUMMARY, this.content, false, indexOfNextSeparator); + } + + if (indexOfNextSummary == -1) { + // we take the end of class data + indexOfNextSummary = CharOperation.indexOf(JavadocConstants.END_OF_CLASS_DATA, this.content, false, indexOfNextSeparator); + } else { + // improve performance of computation of children ranges + this.childrenStart = indexOfNextSummary + 1; + } + + if (indexOfNextSummary == -1) { + this.typeDocRange = UNKNOWN_FORMAT; + return; + } + /* + * Cut off the type hierarchy, see bug 119844. + * We remove the contents between the start of class data and where + * we guess the actual class comment starts. + */ + int start = indexOfStartOfClassData + JavadocConstants.START_OF_CLASS_DATA_LENGTH; + int indexOfFirstParagraph = CharOperation.indexOf(JavadocConstants.P.toCharArray(), this.content, false, start, indexOfNextSummary); + int indexOfFirstDiv = CharOperation.indexOf(JavadocConstants.DIV_CLASS_BLOCK.toCharArray(), this.content, false, start, indexOfNextSummary); + int afterHierarchy = indexOfNextSummary; + if (indexOfFirstParagraph != -1 && indexOfFirstParagraph < afterHierarchy) { + afterHierarchy = indexOfFirstParagraph; + } + if (indexOfFirstDiv != -1 && indexOfFirstDiv < afterHierarchy) { + afterHierarchy = indexOfFirstDiv; + } + if (afterHierarchy != indexOfNextSummary) { + start = afterHierarchy; + } + + this.typeDocRange = new int[]{start, indexOfNextSummary}; + } +} diff --git a/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java new file mode 100644 index 000000000..c1cb3cc11 --- /dev/null +++ b/vscode-extensions/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/javadoc/internal/ScannerHelper.java @@ -0,0 +1,569 @@ +package org.springframework.ide.vscode.commons.javadoc.internal; + +public class ScannerHelper { + + // storage for internal flags (32 bits) BIT USAGE + public final static int Bit1 = 0x1; // return type (operator) | name reference kind (name ref) | add assertion (type decl) | useful empty statement (empty statement) + public final static int Bit2 = 0x2; // return type (operator) | name reference kind (name ref) | has local type (type, method, field decl) | if type elided (local) + public final static int Bit3 = 0x4; // return type (operator) | name reference kind (name ref) | implicit this (this ref) | is argument(local) + public final static int Bit4 = 0x8; // return type (operator) | first assignment to local (name ref,local decl) | undocumented empty block (block, type and method decl) + public final static int Bit5 = 0x10; // value for return (expression) | has all method bodies (unit) | supertype ref (type ref) | resolved (field decl) + public final static int Bit6 = 0x20; // depth (name ref, msg) | ignore need cast check (cast expression) | error in signature (method declaration/ initializer) | is recovered (annotation reference) + public final static int Bit7 = 0x40; // depth (name ref, msg) | operator (operator) | need runtime checkcast (cast expression) | label used (labelStatement) | needFreeReturn (AbstractMethodDeclaration) + public final static int Bit8 = 0x80; // depth (name ref, msg) | operator (operator) | unsafe cast (cast expression) | is default constructor (constructor declaration) | isElseStatementUnreachable (if statement) + public final static int Bit9 = 0x100; // depth (name ref, msg) | operator (operator) | is local type (type decl) | isThenStatementUnreachable (if statement) | can be static + public final static int Bit10= 0x200; // depth (name ref, msg) | operator (operator) | is anonymous type (type decl) + public final static int Bit11 = 0x400; // depth (name ref, msg) | operator (operator) | is member type (type decl) + public final static int Bit12 = 0x800; // depth (name ref, msg) | operator (operator) | has abstract methods (type decl) + public final static int Bit13 = 0x1000; // depth (name ref, msg) | is secondary type (type decl) + public final static int Bit14 = 0x2000; // strictly assigned (reference lhs) | discard enclosing instance (explicit constr call) | hasBeenGenerated (type decl) + public final static int Bit15 = 0x4000; // is unnecessary cast (expression) | is varargs (type ref) | isSubRoutineEscaping (try statement) | superAccess (javadoc allocation expression/javadoc message send/javadoc return statement) + public final static int Bit16 = 0x8000; // in javadoc comment (name ref, type ref, msg) + public final static int Bit17 = 0x10000; // compound assigned (reference lhs) | unchecked (msg, alloc, explicit constr call) + public final static int Bit18 = 0x20000; // non null (expression) | onDemand (import reference) + public final static int Bit19 = 0x40000; // didResolve (parameterized qualified type ref/parameterized single type ref) | empty (javadoc return statement) | needReceiverGenericCast (msg/fieldref) + public final static int Bit20 = 0x80000; // contains syntax errors (method declaration, type declaration, field declarations, initializer), typeref: <> name ref: lambda capture) + public final static int Bit21 = 0x100000; + public final static int Bit22 = 0x200000; // parenthesis count (expression) | used (import reference) shadows outer local (local declarations) + public final static int Bit23 = 0x400000; // parenthesis count (expression) + public final static int Bit24 = 0x800000; // parenthesis count (expression) + public final static int Bit25 = 0x1000000; // parenthesis count (expression) + public final static int Bit26 = 0x2000000; // parenthesis count (expression) + public final static int Bit27 = 0x4000000; // parenthesis count (expression) + public final static int Bit28 = 0x8000000; // parenthesis count (expression) + public final static int Bit29 = 0x10000000; // parenthesis count (expression) + public final static int Bit30 = 0x20000000; // elseif (if statement) | try block exit (try statement) | fall-through (case statement) | ignore no effect assign (expression ref) | needScope (for statement) | isAnySubRoutineEscaping (return statement) | blockExit (synchronized statement) + public final static int Bit31 = 0x40000000; // local declaration reachable (local decl) | ignore raw type check (type ref) | discard entire assignment (assignment) | isSynchronized (return statement) | thenExit (if statement) + public final static int Bit32 = 0x80000000; // reachable (statement) + +// public final static long[] Bits = { +// ASTNode.Bit1, ASTNode.Bit2, ASTNode.Bit3, ASTNode.Bit4, ASTNode.Bit5, ASTNode.Bit6, +// ASTNode.Bit7, ASTNode.Bit8, ASTNode.Bit9, ASTNode.Bit10, ASTNode.Bit11, ASTNode.Bit12, +// ASTNode.Bit13, ASTNode.Bit14, ASTNode.Bit15, ASTNode.Bit16, ASTNode.Bit17, ASTNode.Bit18, +// ASTNode.Bit19, ASTNode.Bit20, ASTNode.Bit21, ASTNode.Bit22, ASTNode.Bit23, ASTNode.Bit24, +// ASTNode.Bit25, ASTNode.Bit26, ASTNode.Bit27, ASTNode.Bit28, ASTNode.Bit29, ASTNode.Bit30, +// ASTNode.Bit31, ASTNode.Bit32L, ASTNode.Bit33L, ASTNode.Bit34L, ASTNode.Bit35L, ASTNode.Bit36L, +// ASTNode.Bit37L, ASTNode.Bit38L, ASTNode.Bit39L, ASTNode.Bit40L, ASTNode.Bit41L, ASTNode.Bit42L, +// ASTNode.Bit43L, ASTNode.Bit44L, ASTNode.Bit45L, ASTNode.Bit46L, ASTNode.Bit47L, ASTNode.Bit48L, +// ASTNode.Bit49L, ASTNode.Bit50L, ASTNode.Bit51L, ASTNode.Bit52L, ASTNode.Bit53L, ASTNode.Bit54L, +// ASTNode.Bit55L, ASTNode.Bit56L, ASTNode.Bit57L, ASTNode.Bit58L, ASTNode.Bit59L, ASTNode.Bit60L, +// ASTNode.Bit61L, ASTNode.Bit62L, ASTNode.Bit63L, ASTNode.Bit64L, +// }; +// +// private static final int START_INDEX = 0; +// private static final int PART_INDEX = 1; +// +// private static long[][][] Tables; +// private static long[][][] Tables7; +// private static long[][][] Tables8; + + public final static int MAX_OBVIOUS = 128; + public final static int[] OBVIOUS_IDENT_CHAR_NATURES = new int[MAX_OBVIOUS]; + + public final static int C_JLS_SPACE = Bit9; + public final static int C_SPECIAL = Bit8; + public final static int C_IDENT_START = Bit7; + public final static int C_UPPER_LETTER = Bit6; + public final static int C_LOWER_LETTER = Bit5; + public final static int C_IDENT_PART = Bit4; + public final static int C_DIGIT = Bit3; + public final static int C_SEPARATOR = Bit2; + public final static int C_SPACE = Bit1; + + static { + OBVIOUS_IDENT_CHAR_NATURES[0] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[1] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[2] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[3] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[4] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[5] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[6] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[7] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[8] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[14] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[15] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[16] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[17] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[18] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[19] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[20] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[21] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[22] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[23] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[24] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[25] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[26] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[27] = C_IDENT_PART; + OBVIOUS_IDENT_CHAR_NATURES[127] = C_IDENT_PART; + + for (int i = '0'; i <= '9'; i++) + OBVIOUS_IDENT_CHAR_NATURES[i] = C_DIGIT | C_IDENT_PART; + + for (int i = 'a'; i <= 'z'; i++) + OBVIOUS_IDENT_CHAR_NATURES[i] = C_LOWER_LETTER | C_IDENT_PART | C_IDENT_START; + for (int i = 'A'; i <= 'Z'; i++) + OBVIOUS_IDENT_CHAR_NATURES[i] = C_UPPER_LETTER | C_IDENT_PART | C_IDENT_START; + + OBVIOUS_IDENT_CHAR_NATURES['_'] = C_SPECIAL | C_IDENT_PART | C_IDENT_START; + OBVIOUS_IDENT_CHAR_NATURES['$'] = C_SPECIAL | C_IDENT_PART | C_IDENT_START; + + OBVIOUS_IDENT_CHAR_NATURES[9] = C_SPACE | C_JLS_SPACE; // \ u0009: HORIZONTAL TABULATION + OBVIOUS_IDENT_CHAR_NATURES[10] = C_SPACE | C_JLS_SPACE; // \ u000a: LINE FEED + OBVIOUS_IDENT_CHAR_NATURES[11] = C_SPACE; + OBVIOUS_IDENT_CHAR_NATURES[12] = C_SPACE | C_JLS_SPACE; // \ u000c: FORM FEED + OBVIOUS_IDENT_CHAR_NATURES[13] = C_SPACE | C_JLS_SPACE; // \ u000d: CARRIAGE RETURN + OBVIOUS_IDENT_CHAR_NATURES[28] = C_SPACE; + OBVIOUS_IDENT_CHAR_NATURES[29] = C_SPACE; + OBVIOUS_IDENT_CHAR_NATURES[30] = C_SPACE; + OBVIOUS_IDENT_CHAR_NATURES[31] = C_SPACE; + OBVIOUS_IDENT_CHAR_NATURES[32] = C_SPACE | C_JLS_SPACE; // \ u0020: SPACE + + OBVIOUS_IDENT_CHAR_NATURES['.'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES[':'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES[';'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES[','] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['['] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES[']'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['('] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES[')'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['{'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['}'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['+'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['-'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['*'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['/'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['='] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['&'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['|'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['?'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['<'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['>'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['!'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['%'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['^'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['~'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['"'] = C_SEPARATOR; + OBVIOUS_IDENT_CHAR_NATURES['\''] = C_SEPARATOR; + } +//static void initializeTable() { +// Tables = initializeTables("unicode"); //$NON-NLS-1$ +//} +//static void initializeTable17() { +// Tables7 = initializeTables("unicode6"); //$NON-NLS-1$ +//} +//static void initializeTable18() { +// Tables8 = initializeTables("unicode6_2"); //$NON-NLS-1$ +//} +//static long[][][] initializeTables(String unicode_path) { +// long[][][] tempTable = new long[2][][]; +// tempTable[START_INDEX] = new long[3][]; +// tempTable[PART_INDEX] = new long[4][]; +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start0.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[START_INDEX][0] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start1.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[START_INDEX][1] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/start2.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[START_INDEX][2] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part0.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[PART_INDEX][0] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part1.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[PART_INDEX][1] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part2.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[PART_INDEX][2] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// try { +// DataInputStream inputStream = new DataInputStream(new BufferedInputStream(ScannerHelper.class.getResourceAsStream(unicode_path + "/part14.rsc"))); //$NON-NLS-1$ +// long[] readValues = new long[1024]; +// for (int i = 0; i < 1024; i++) { +// readValues[i] = inputStream.readLong(); +// } +// inputStream.close(); +// tempTable[PART_INDEX][3] = readValues; +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// return tempTable; +//} +//private final static boolean isBitSet(long[] values, int i) { +// try { +// return (values[i / 64] & Bits[i % 64]) != 0; +// } catch (NullPointerException e) { +// return false; +// } +//} +//public static boolean isJavaIdentifierPart(char c) { +// if (c < MAX_OBVIOUS) { +// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_PART) != 0; +// } +// return Character.isJavaIdentifierPart(c); +//} +///** +// * @param complianceLevel +// * @param c +// * @return +// */ +//public static boolean isJavaIdentifierPart(long complianceLevel, char c) { +// if (c < MAX_OBVIOUS) { +// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_PART) != 0; +// } +// return isJavaIdentifierPart(complianceLevel, (int) c); +//} +///** +// * @param complianceLevel +// * @param codePoint +// * @return +// */ +//public static boolean isJavaIdentifierPart(long complianceLevel, int codePoint) { +// if (complianceLevel <= ClassFileConstants.JDK1_6) { +// if (Tables == null) { +// initializeTable(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables[PART_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables[PART_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables[PART_INDEX][2], codePoint & 0xFFFF); +// case 14 : +// return isBitSet(Tables[PART_INDEX][3], codePoint & 0xFFFF); +// } +// } else if (complianceLevel <= ClassFileConstants.JDK1_7) { +// // java 7 supports Unicode 6 +// if (Tables7 == null) { +// initializeTable17(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables7[PART_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables7[PART_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables7[PART_INDEX][2], codePoint & 0xFFFF); +// case 14 : +// return isBitSet(Tables7[PART_INDEX][3], codePoint & 0xFFFF); +// } +// } else { +// // java 7 supports Unicode 6.2 +// if (Tables8 == null) { +// initializeTable18(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables8[PART_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables8[PART_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables8[PART_INDEX][2], codePoint & 0xFFFF); +// case 14 : +// return isBitSet(Tables8[PART_INDEX][3], codePoint & 0xFFFF); +// } +// } +// return false; +//} +///** +// * @param complianceLevel +// * @param high +// * @param low +// * @return +// */ +//public static boolean isJavaIdentifierPart(long complianceLevel, char high, char low) { +// return isJavaIdentifierPart(complianceLevel, toCodePoint(high, low)); +//} +///** +// * @param c +// * @return +// */ +//public static boolean isJavaIdentifierStart(char c) { +// if (c < MAX_OBVIOUS) { +// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_START) != 0; +// } +// return Character.isJavaIdentifierStart(c); +//} +///** +// * @param complianceLevel +// * @param c +// * @return +// */ +//public static boolean isJavaIdentifierStart(long complianceLevel, char c) { +// if (c < MAX_OBVIOUS) { +// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_IDENT_START) != 0; +// } +// return ScannerHelper.isJavaIdentifierStart(complianceLevel, (int) c); +//} +///** +// * @param complianceLevel +// * @param high +// * @param low +// * @return +// */ +//public static boolean isJavaIdentifierStart(long complianceLevel, char high, char low) { +// return isJavaIdentifierStart(complianceLevel, toCodePoint(high, low)); +//} +///** +// * @param complianceLevel +// * @param codePoint +// * @return +// */ +//public static boolean isJavaIdentifierStart(long complianceLevel, int codePoint) { +// if (complianceLevel <= ClassFileConstants.JDK1_6) { +// if (Tables == null) { +// initializeTable(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables[START_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables[START_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables[START_INDEX][2], codePoint & 0xFFFF); +// } +// } else if (complianceLevel <= ClassFileConstants.JDK1_7) { +// // java 7 supports Unicode 6 +// if (Tables7 == null) { +// initializeTable17(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables7[START_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables7[START_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables7[START_INDEX][2], codePoint & 0xFFFF); +// } +// } else { +// // java 7 supports Unicode 6 +// if (Tables8 == null) { +// initializeTable18(); +// } +// switch((codePoint & 0x1F0000) >> 16) { +// case 0 : +// return isBitSet(Tables8[START_INDEX][0], codePoint & 0xFFFF); +// case 1 : +// return isBitSet(Tables8[START_INDEX][1], codePoint & 0xFFFF); +// case 2 : +// return isBitSet(Tables8[START_INDEX][2], codePoint & 0xFFFF); +// } +// } +// return false; +//} +//private static int toCodePoint(char high, char low) { +// return (high - Scanner.HIGH_SURROGATE_MIN_VALUE) * 0x400 + (low - Scanner.LOW_SURROGATE_MIN_VALUE) + 0x10000; +//} +//public static boolean isDigit(char c) throws InvalidInputException { +// if(c < ScannerHelper.MAX_OBVIOUS) { +// return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_DIGIT) != 0; +// } +// if (Character.isDigit(c)) { +// throw new InvalidStateException(Scanner.INVALID_DIGIT); +// } +// return false; +//} +public static int digit(char c, int radix) { + if (c < ScannerHelper.MAX_OBVIOUS) { + switch(radix) { + case 8 : + if (c >= 48 && c <= 55) { + return c - 48; + } + return -1; + case 10 : + if (c >= 48 && c <= 57) { + return c - 48; + } + return -1; + case 16 : + if (c >= 48 && c <= 57) { + return c - 48; + } + if (c >= 65 && c <= 70) { + return c - 65 + 10; + } + if (c >= 97 && c <= 102) { + return c - 97 + 10; + } + return -1; + } + } + return Character.digit(c, radix); +} +public static int getNumericValue(char c) { + if (c < ScannerHelper.MAX_OBVIOUS) { + switch(ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c]) { + case C_DIGIT : + return c - '0'; + case C_LOWER_LETTER : + return 10 + c - 'a'; + case C_UPPER_LETTER : + return 10 + c - 'A'; + } + } + return Character.getNumericValue(c); +} +public static int getHexadecimalValue(char c) { + switch(c) { + case '0' : + return 0; + case '1' : + return 1; + case '2' : + return 2; + case '3' : + return 3; + case '4' : + return 4; + case '5' : + return 5; + case '6' : + return 6; + case '7' : + return 7; + case '8' : + return 8; + case '9' : + return 9; + case 'A' : + case 'a' : + return 10; + case 'B' : + case 'b' : + return 11; + case 'C' : + case 'c' : + return 12; + case 'D' : + case 'd' : + return 13; + case 'E' : + case 'e' : + return 14; + case 'F' : + case 'f' : + return 15; + default: + return -1; + } +} +public static char toUpperCase(char c) { + if (c < MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0) { + return c; + } else if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0) { + return (char) (c - 32); + } + } + return Character.toUpperCase(c); +} +public static char toLowerCase(char c) { + if (c < MAX_OBVIOUS) { + if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0) { + return c; + } else if ((ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0) { + return (char) (32 + c); + } + } + return Character.toLowerCase(c); +} +public static boolean isLowerCase(char c) { + if (c < MAX_OBVIOUS) { + return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_LOWER_LETTER) != 0; + } + return Character.isLowerCase(c); +} +public static boolean isUpperCase(char c) { + if (c < MAX_OBVIOUS) { + return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_UPPER_LETTER) != 0; + } + return Character.isUpperCase(c); +} +/** + * Include also non JLS whitespaces. + * + * return true if Character.isWhitespace(c) would return true + */ +public static boolean isWhitespace(char c) { + if (c < MAX_OBVIOUS) { + return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & ScannerHelper.C_SPACE) != 0; + } + return Character.isWhitespace(c); +} +public static boolean isLetter(char c) { + if (c < MAX_OBVIOUS) { + return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_LOWER_LETTER)) != 0; + } + return Character.isLetter(c); +} +public static boolean isLetterOrDigit(char c) { + if (c < MAX_OBVIOUS) { + return (ScannerHelper.OBVIOUS_IDENT_CHAR_NATURES[c] & (ScannerHelper.C_UPPER_LETTER | ScannerHelper.C_LOWER_LETTER | ScannerHelper.C_DIGIT)) != 0; + } + return Character.isLetterOrDigit(c); +} +} diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java index 54aebce25..e55ed3966 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenCore.java @@ -15,6 +15,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -49,6 +51,8 @@ import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector; import org.eclipse.aether.util.graph.visitor.CloningDependencyVisitor; import org.eclipse.aether.util.graph.visitor.FilteringDependencyVisitor; import org.springframework.ide.vscode.commons.jandex.JandexIndex; +import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider; +import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.ExternalProcess; import org.springframework.ide.vscode.commons.util.Log; @@ -82,7 +86,22 @@ public class MavenCore { private Supplier javaCoreIndex = Suppliers.memoize(() -> { try { - return new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), null); + return new JandexIndex(getJreLibs(), jarFile -> findIndexFile(jarFile), (classpathResource) -> { + try { + String javaVersion = "8"; + try { + String fullVersion = getJavaRuntimeVersion(); + javaVersion = fullVersion.substring(fullVersion.indexOf('.') + 1, fullVersion.lastIndexOf('.')); + } catch (MavenException e) { + Log.log("Cannot determine Java runtime version. Defaulting to version 8", e); + } + URL javadocUrl = new URL("http://docs.oracle.com/javase/" + javaVersion + "/docs/api/"); + return new HtmlJavadocProvider((type) -> SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER.sourceUrl(javadocUrl, type)); + } catch (MalformedURLException e) { + Log.log(e); + return null; + } + }); } catch (MavenException e) { return null; } @@ -115,17 +134,34 @@ public class MavenCore { * @param Path of the project * @throws Exception */ - public static void buildMavenProject(Path testProjectPath) throws Exception { + public static void buildMavenProject(Path projectPath) throws Exception { Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") - ? testProjectPath.resolve("mvnw.cmd") : testProjectPath.resolve("mvnw"); + ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw"); mvnwPath.toFile().setExecutable(true); - ExternalProcess process = new ExternalProcess(testProjectPath.toFile(), + ExternalProcess process = new ExternalProcess(projectPath.toFile(), new ExternalCommand(mvnwPath.toAbsolutePath().toString(), "clean", "package", "-DskipTests"), true); if (process.getExitValue() != 0) { throw new RuntimeException("Failed to build test project"); } } + /** + * Generate javadoc jar for maven project + * + * @param Path of the project + * @throws Exception + */ + public static void generateJavadocFolderForMavenProject(Path projectPath) throws Exception { + Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") + ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw"); + mvnwPath.toFile().setExecutable(true); + ExternalProcess process = new ExternalProcess(projectPath.toFile(), + new ExternalCommand(mvnwPath.toAbsolutePath().toString(), "javadoc:javadoc"), true); + if (process.getExitValue() != 0) { + throw new RuntimeException("Failed to build test project"); + } + } + /** * Creates Maven Project descriptor based on the pom file. * diff --git a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java index 2d2e2e9a1..92a7259bc 100644 --- a/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java +++ b/vscode-extensions/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenProjectClasspath.java @@ -27,6 +27,7 @@ import org.springframework.ide.vscode.commons.java.IJavadocProvider; import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.parser.ParserJavadocProvider; import org.springframework.ide.vscode.commons.java.roaster.RoasterJavadocProvider; +import org.springframework.ide.vscode.commons.javadoc.HtmlJavadocProvider; import org.springframework.ide.vscode.commons.javadoc.SourceUrlProviderFromSourceContainer; import org.springframework.ide.vscode.commons.maven.MavenCore; import org.springframework.ide.vscode.commons.maven.MavenException; @@ -43,8 +44,14 @@ import com.google.common.base.Suppliers; */ public class MavenProjectClasspath implements IClasspath { - public static boolean USE_JAVA_PARSER = false; - + public static JavadocProviderTypes providerType = JavadocProviderTypes.JAVA_PARSER; + + public enum JavadocProviderTypes { + JAVA_PARSER, + ROASTER, + HTML + } + private MavenCore maven; private MavenProject project; private Supplier javaIndex; @@ -64,7 +71,14 @@ public class MavenProjectClasspath implements IClasspath { Log.log(e); } return new JandexIndex(classpathEntries, jarFile -> findIndexFile(jarFile), classpathResource -> { - return USE_JAVA_PARSER ? createParserJavadocProvider(classpathResource) : createRoasterJavadocProvider(classpathResource); + switch (providerType) { + case JAVA_PARSER: + return createParserJavadocProvider(classpathResource); + case ROASTER: + return createRoasterJavadocProvider(classpathResource); + default: + return createHtmlJavdocProvider(classpathResource); + } }, maven.getJavaIndexForJreLibs()); }); } @@ -105,7 +119,7 @@ public class MavenProjectClasspath implements IClasspath { return Arrays.stream(scanner.getIncludedFiles()); }); } - + private IJavadocProvider createRoasterJavadocProvider(File classpathResource) { if (classpathResource.isDirectory()) { if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { @@ -174,4 +188,38 @@ public class MavenProjectClasspath implements IClasspath { } } + private IJavadocProvider createHtmlJavdocProvider(File classpathResource) { + if (classpathResource.isDirectory()) { + if (classpathResource.toString().startsWith(project.getBuild().getOutputDirectory())) { + return new HtmlJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getModel().getReporting().getOutputDirectory(), "apidocs").toURI().toURL(), type); + }); + } else if (classpathResource.toString().startsWith(project.getBuild().getTestOutputDirectory())) { + return new ParserJavadocProvider(type -> { + return SourceUrlProviderFromSourceContainer.JAVADOC_FOLDER_URL_SUPPLIER + .sourceUrl(new File(project.getModel().getReporting().getOutputDirectory(), "apidocs").toURI().toURL(), type); + }); + } else { + throw new IllegalArgumentException("Cannot find source folder for " + classpathResource); + } + } else { + // Assume it's a JAR file + return new HtmlJavadocProvider(type -> { + try { + Artifact artifact = getArtifactFromJarFile(classpathResource).get(); + URL sourceContainer = maven.getJavadoc(artifact).getFile().toURI().toURL(); + return SourceUrlProviderFromSourceContainer.JAR_JAVADOC_URL_PROVIDER.sourceUrl(sourceContainer, + type); + } catch (MavenException e) { + Log.log("Failed to find sources JAR for " + classpathResource, e); + } catch (MalformedURLException e) { + Log.log("Invalid URL for sources JAR for " + classpathResource, e); + } + return null; + }); + } + } + + } diff --git a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java index b2aaa00e7..7bd17c687 100644 --- a/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java +++ b/vscode-extensions/commons/commons-maven/src/test/java/org/springframework/ide/vscode/commons/maven/JavaIndexTest.java @@ -3,6 +3,7 @@ package org.springframework.ide.vscode.commons.maven; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import java.nio.file.Path; import java.nio.file.Paths; @@ -18,6 +19,7 @@ import org.springframework.ide.vscode.commons.java.IType; import org.springframework.ide.vscode.commons.java.IVoidType; import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; import org.springframework.ide.vscode.commons.maven.java.MavenProjectClasspath; +import org.springframework.ide.vscode.commons.maven.java.MavenProjectClasspath.JavadocProviderTypes; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -89,7 +91,7 @@ public class JavaIndexTest { IType type = project.findType("java.util.ArrayList"); assertNotNull(type); IMethod m = type.getMethod("", Stream.empty()); - assertEquals("", m.getElementName()); + assertEquals(type.getElementName(), m.getElementName()); assertEquals(IVoidType.DEFAULT, m.getReturnType()); assertEquals(0, m.parameters().count()); } @@ -100,14 +102,14 @@ public class JavaIndexTest { IType type = project.findType("java.util.ArrayList"); assertNotNull(type); IMethod m = type.getMethod("", Stream.of(IPrimitiveType.INT)); - assertEquals("", m.getElementName()); + assertEquals(m.getDeclaringType().getElementName(), m.getElementName()); assertEquals(IVoidType.DEFAULT, m.getReturnType()); assertEquals(Collections.singletonList(IPrimitiveType.INT), m.parameters().collect(Collectors.toList())); } @Test public void parser_testClassJavadocForOutputFolder() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = true; + MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("hello.Greeting"); @@ -140,7 +142,7 @@ public class JavaIndexTest { @Test public void parser_testInnerClassJavadocForOutputFolder() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = true; + MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("hello.Greeting$TestInnerClass"); assertNotNull(type); @@ -157,7 +159,7 @@ public class JavaIndexTest { @Test public void parser_testClassJavadocForJar() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = true; + MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"); @@ -180,7 +182,7 @@ public class JavaIndexTest { @Test public void parser_testFieldAndMethodJavadocForJar() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = true; + MavenProjectClasspath.providerType = JavadocProviderTypes.JAVA_PARSER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("org.springframework.boot.SpringApplication"); @@ -206,7 +208,7 @@ public class JavaIndexTest { @Test public void roaster_testClassJavadocForOutputFolder() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = false; + MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("hello.Greeting"); @@ -224,7 +226,7 @@ public class JavaIndexTest { @Test public void roaster_testInnerClassJavadocForOutputFolder() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = false; + MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER; MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("hello.Greeting$TestInnerClass"); assertNotNull(type); @@ -241,7 +243,8 @@ public class JavaIndexTest { @Test public void roaster_testClassJavadocForJar() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = false; + MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER; + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener"); @@ -256,7 +259,8 @@ public class JavaIndexTest { @Test public void roaster_testFieldAndMethodJavadocForJar() throws Exception { - MavenProjectClasspath.USE_JAVA_PARSER = false; + MavenProjectClasspath.providerType = JavadocProviderTypes.ROASTER; + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); IType type = project.findType("org.springframework.boot.SpringApplication"); @@ -273,4 +277,134 @@ public class JavaIndexTest { } + @Test + public void html_testClassJavadoc() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); + + IType type = project.findType("java.util.Map"); + assertNotNull(type); + String expected = String.join("\n", + "
An object that maps keys to values. A map cannot contain duplicate keys;", + " each key can map to at most one value." + ); + assertEquals(expected, type.getJavaDoc().html().substring(0, expected.length())); + } + + @Test + public void html_testNestedClassJavadoc() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); + + IType type = project.findType("java.util.Map$Entry"); + assertNotNull(type); + String expected = String.join("\n", + "
A map entry (key-value pair). The Map.entrySet method returns", + " a collection-view of the map, whose elements are of this class. The"); + assertEquals(expected, type.getJavaDoc().html().substring(0, expected.length())); + } + + @Test + public void html_testMethodJavadoc() throws Exception { + MavenProjectClasspath.providerType = JavadocProviderTypes.HTML; + + MavenJavaProject project = createMavenProject(projectsCache.get("gs-rest-service-cors-boot-1.4.1-with-classpath-file")); + + IType type = project.findType("java.util.ArrayList"); + assertNotNull(type); + IMethod method = type.getMethod("size", Stream.empty()); + assertNotNull(method); + + String expected = String.join("\n", + "", + "