| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.api.java.source.ui; |
| |
| import com.sun.source.doctree.AttributeTree; |
| import com.sun.source.doctree.DeprecatedTree; |
| import com.sun.source.doctree.DocCommentTree; |
| import com.sun.source.doctree.DocTree; |
| import com.sun.source.doctree.EndElementTree; |
| import com.sun.source.doctree.EntityTree; |
| import com.sun.source.doctree.InheritDocTree; |
| import com.sun.source.doctree.LinkTree; |
| import com.sun.source.doctree.LiteralTree; |
| import com.sun.source.doctree.ParamTree; |
| import com.sun.source.doctree.ReferenceTree; |
| import com.sun.source.doctree.ReturnTree; |
| import com.sun.source.doctree.SeeTree; |
| import com.sun.source.doctree.SinceTree; |
| import com.sun.source.doctree.StartElementTree; |
| import com.sun.source.doctree.TextTree; |
| import com.sun.source.doctree.ThrowsTree; |
| import com.sun.source.doctree.UnknownBlockTagTree; |
| import com.sun.source.doctree.ValueTree; |
| import com.sun.source.util.DocTreePath; |
| import com.sun.source.util.DocTreeScanner; |
| import com.sun.source.util.DocTrees; |
| import com.sun.source.util.TreePath; |
| import java.awt.event.ActionEvent; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.PackageElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.ArrayType; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.swing.AbstractAction; |
| import javax.swing.Action; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.function.Function; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.AnnotationValue; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.ModuleElement; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeParameterElement; |
| import javax.lang.model.type.IntersectionType; |
| import javax.lang.model.type.TypeVariable; |
| import javax.lang.model.type.WildcardType; |
| import javax.lang.model.util.ElementFilter; |
| import javax.lang.model.util.Elements; |
| import javax.swing.SwingUtilities; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.queries.JavadocForBinaryQuery; |
| import org.netbeans.api.java.queries.SourceJavadocAttacher; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.ElementUtilities; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.modules.java.preprocessorbridge.api.JavaSourceUtil; |
| import org.netbeans.modules.java.source.JavadocHelper; |
| import org.netbeans.modules.java.source.parsing.FileObjects; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileStateInvalidException; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| import org.openide.xml.XMLUtil; |
| |
| /** Utility class for viewing Javadoc comments as HTML. |
| * |
| * @author Dusan Balek, Petr Hrebejk, Tomas Zezula |
| */ |
| public class ElementJavadoc { |
| |
| private static final String ASSOCIATE_JDOC = "associate-javadoc:"; //NOI18N |
| private static final String API = "/api"; //NOI18N |
| private static final Set<String> LANGS; |
| |
| private static final RequestProcessor RP = new RequestProcessor(ElementJavadoc.class); |
| |
| static { |
| Locale[] availableLocales = Locale.getAvailableLocales(); |
| Set<String> locNames = new HashSet<>((int) (availableLocales.length/.75f) + 1); |
| for (Locale locale : availableLocales) { |
| locNames.add(locale.toString()); |
| } |
| LANGS = Collections.unmodifiableSet(locNames); |
| } |
| |
| private final ClasspathInfo cpInfo; |
| private final FileObject fileObject; |
| private final ElementHandle<? extends Element> handle; |
| //private Doc doc; |
| private volatile Future<String> content; |
| private final Callable<Boolean> cancel; |
| private Map<String, ElementHandle<? extends Element>> links = new HashMap<>(); |
| private int linkCounter = 0; |
| private volatile URL docURL = null; |
| private volatile URL docRoot = null; |
| private volatile AbstractAction goToSource = null; |
| |
| /** Non-normative notes about the API. Usually used for examples. */ |
| private static final String APINOTE_TAG = "apiNote"; //NOI18N |
| /** Describes required behaviour of conforming implementations. Key is that the description is not inherited. */ |
| private static final String IMPLSPEC_TAG = "implSpec"; //NOI18N |
| /** Non-normative notes about the implementation. Typically used for descriptions of the behaviour. Also not inherited. */ |
| private static final String IMPLNOTE_TAG = "implNote"; //NOI18N |
| |
| /** Creates an object describing the Javadoc of given element. The object |
| * is capable of getting the text formated into HTML, resolve the links, |
| * jump to external javadoc. |
| * |
| * @param compilationInfo CompilationInfo |
| * @param element Element the javadoc is required for |
| * @return ElementJavadoc describing the javadoc |
| */ |
| public static final ElementJavadoc create(CompilationInfo compilationInfo, Element element) { |
| return create (compilationInfo, element, null); |
| } |
| |
| /** Creates an object describing the Javadoc of given element. The object |
| * is capable of getting the text formated into HTML, resolve the links, |
| * jump to external javadoc. |
| * |
| * @param compilationInfo CompilationInfo |
| * @param element Element the javadoc is required for |
| * @param cancel a {@link Callable} to signal the cancel request |
| * @return ElementJavadoc describing the javadoc |
| * @since 1.15 |
| */ |
| public static final ElementJavadoc create(CompilationInfo compilationInfo, Element element, final Callable<Boolean> cancel) { |
| return new ElementJavadoc(compilationInfo, element, null, cancel); |
| } |
| |
| /** Gets the javadoc comment formated as HTML. |
| * @return HTML text of the javadoc |
| */ |
| public String getText() { |
| try { |
| final Future<String> tmp = content; |
| return tmp != null ? tmp.get() : null; |
| } catch (InterruptedException | ExecutionException ex) { |
| return null; |
| } |
| } |
| |
| /** Gets the javadoc comment formated as HTML. |
| * @return {@link Future} of HTML text of the javadoc |
| * @since 1.20 |
| */ |
| public Future<String> getTextAsync() { |
| return content; |
| } |
| |
| /** Gets URL of the external javadoc. |
| * @return Text of the Javadoc comment formated as HTML |
| */ |
| public URL getURL() { |
| return docURL; |
| } |
| |
| /** Resolves a link contained in the Javadoc comment to an object |
| * describing the linked javadoc |
| * @param link Link which has to be resolved |
| * @return ElementJavadoc describing the javadoc of liked element |
| */ |
| public ElementJavadoc resolveLink(final String link) { |
| if (link.startsWith(ASSOCIATE_JDOC)) { |
| final String root = link.substring(ASSOCIATE_JDOC.length()); |
| try { |
| final CountDownLatch latch = new CountDownLatch(1); |
| final AtomicBoolean success = new AtomicBoolean(); |
| SourceJavadocAttacher.attachJavadoc( |
| new URL(root), |
| new SourceJavadocAttacher.AttachmentListener() { |
| @Override |
| public void attachmentSucceeded() { |
| success.set(true); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void attachmentFailed() { |
| latch.countDown(); |
| } |
| }); |
| if (!SwingUtilities.isEventDispatchThread()) { |
| latch.await(); |
| return success.get() ? |
| resolveElement(handle, link): |
| new ElementJavadoc(NbBundle.getMessage(ElementJavadoc.class, "javadoc_attaching_failed"), cancel); |
| } |
| } catch (MalformedURLException | InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return null; |
| } |
| final ElementHandle<? extends Element> linkDoc = links.get(link); |
| return resolveElement(linkDoc, link); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("ElementJavadoc[url=%s, handle=%s]", getURL(), handle); //NOI18N |
| } |
| |
| private ElementJavadoc resolveElement( |
| final ElementHandle<?> handle, |
| final String link) { |
| final ElementJavadoc[] ret = new ElementJavadoc[1]; |
| try { |
| FileObject fo = handle != null ? SourceUtils.getFile(handle, cpInfo) : null; |
| if (fo != null && fo.isFolder() && handle.getKind() == ElementKind.PACKAGE) { |
| fo = fo.getFileObject("package-info", "java"); //NOI18N |
| } |
| if (cpInfo == null && fo == null) { |
| //link cannot be resolved by this element |
| try { |
| URL u = getURL() != null ? new URL(getURL(), link) : new URL(link); |
| ret[0] = new ElementJavadoc(u, cancel); |
| } catch (MalformedURLException ex) { |
| // ignore |
| } |
| return ret[0]; |
| } |
| JavaSource js = fo != null ? JavaSource.forFileObject(fo) |
| : fileObject != null ? JavaSource.forFileObject(fileObject) |
| : JavaSource.create(cpInfo); |
| if (js != null) { |
| js.runUserActionTask((controller) -> { |
| controller.toPhase(Phase.ELEMENTS_RESOLVED); |
| if (handle != null) { |
| ret[0] = new ElementJavadoc(controller, handle.resolve(controller), null, cancel); |
| } else { |
| int idx = link.indexOf('#'); //NOI18N |
| URI uri = null; |
| try { |
| uri = URI.create(idx < 0 ? link : link.substring(0, idx)); |
| if (!uri.isAbsolute() && ElementJavadoc.this.handle != null) { |
| Element e = ElementJavadoc.this.handle.resolve(controller); |
| if (e != null) { |
| PackageElement pe = controller.getElements().getPackageOf(e); |
| uri = URI.create(FileObjects.resolveRelativePath(pe.getQualifiedName().toString(), uri.getPath())); |
| } |
| } |
| } catch (IllegalArgumentException iae) {} |
| if (uri != null) { |
| if (!uri.isAbsolute()) { |
| uri = uri.normalize(); |
| } |
| String path = uri.toString(); |
| int startIdx = path.lastIndexOf(".."); //NOI18N |
| startIdx = startIdx < 0 ? 0 : startIdx + 3; |
| int endIdx = path.lastIndexOf('.'); //NOI18N |
| if (endIdx >= 0) |
| path = path.substring(startIdx, endIdx); |
| String clsName = path.replace('/', '.'); //NOI18N |
| Element e = controller.getElements().getTypeElement(clsName); |
| if (e != null) { |
| if (idx >= 0) { |
| String fragment = link.substring(idx + 1); |
| idx = fragment.indexOf('('); //NOI18N |
| String name = idx < 0 ? fragment : fragment.substring(0, idx); |
| for (Element member : e.getEnclosedElements()) { |
| if (member.getSimpleName().contentEquals(name) && fragment.contentEquals(getFragment(member))) { |
| e = member; |
| break; |
| } |
| } |
| } |
| URL u; |
| if (uri.isAbsolute()) { |
| u = new URL(link); |
| } else if (getURL() != null) { |
| u = new URL(getURL(), link); |
| } else { |
| return; |
| } |
| ret[0] = new ElementJavadoc(controller, e, u, cancel); |
| } else { |
| //external URL |
| if( uri.isAbsolute() ) { |
| ret[0] = new ElementJavadoc( uri.toURL(), cancel ); |
| } else if (getURL() != null) { |
| try { |
| ret[0] = new ElementJavadoc(new URL(getURL(), link), cancel); |
| } catch (MalformedURLException ex) { |
| // ignore |
| } |
| } |
| } |
| } |
| } |
| }, true); |
| } |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } |
| if (ret[0] != null) { |
| try { |
| while (cancel != null && !cancel.call()) { |
| try { |
| ret[0].getTextAsync().get(250, TimeUnit.MILLISECONDS); |
| break; |
| } catch (TimeoutException timeOut) {/*retry*/} |
| } |
| } catch (Exception ex) {} |
| } |
| return ret[0]; |
| } |
| |
| |
| /** Gets action capable of jumping to source of the Element this Javadoc |
| * belongs to. |
| * @return Action going to the source of the Element described by this javadoc. |
| */ |
| public Action getGotoSourceAction() { |
| return goToSource; |
| } |
| |
| private ElementJavadoc(CompilationInfo compilationInfo, Element element, final URL url, final Callable<Boolean> cancel) { |
| this.cpInfo = compilationInfo.getClasspathInfo(); |
| this.fileObject = compilationInfo.getFileObject(); |
| this.handle = element == null ? null : ElementHandle.create(element); |
| this.cancel = cancel; |
| final StringBuilder header = getElementHeader(element, compilationInfo); |
| try { |
| //Optimisitic no http |
| CharSequence doc = getElementDoc(element, compilationInfo, header, url, true); |
| if (doc == null) { |
| computeDocURL(Collections.emptyList(), true, cancel); |
| doc = header.append(noJavadocFound()); |
| } |
| this.content = new Now(doc.toString()); |
| } catch (JavadocHelper.RemoteJavadocException re) { |
| if (fileObject == null || JavaSource.forFileObject(fileObject) == null) { |
| header.append(noJavadocFound()); |
| this.content = new Now(header.toString()); |
| return; |
| } |
| this.content = new FutureTask<>(() -> { |
| final JavaSourceUtil.Handle ch = JavaSourceUtil.createControllerHandle(fileObject, null); |
| final CompilationController c = (CompilationController) ch.getCompilationController(); |
| c.toPhase(Phase.RESOLVED); |
| final Element el = handle.resolve(c); |
| CharSequence doc = getElementDoc(el, c, header, url, false); |
| if (doc == null) { |
| computeDocURL(Collections.emptyList(), false, cancel); |
| doc = header.append(noJavadocFound()); |
| } |
| return doc.toString(); |
| }); |
| RP.post((Runnable)this.content); |
| } |
| } |
| |
| private ElementJavadoc(URL url, final Callable<Boolean> cancel) { |
| assert url != null; |
| this.content = null; |
| this.docURL = url; |
| this.handle = null; |
| this.cpInfo = null; |
| this.fileObject = null; |
| this.cancel = cancel; |
| } |
| |
| private ElementJavadoc(final String message, final Callable<Boolean> cancel) { |
| assert message != null; |
| this.content = new Now(message); |
| this.docURL = null; |
| this.handle = null; |
| this.cpInfo = null; |
| this.fileObject = null; |
| this.cancel = cancel; |
| } |
| |
| // Private section --------------------------------------------------------- |
| |
| private StringBuilder getElementHeader(final Element element, final CompilationInfo info) { |
| final StringBuilder sb = new StringBuilder(); |
| if (element != null) { |
| sb.append(getContainingClassOrPackageHeader(element, info.getElements(), info.getElementUtilities())); |
| switch(element.getKind()) { |
| case METHOD: |
| case CONSTRUCTOR: |
| sb.append(getMethodHeader((ExecutableElement)element)); |
| break; |
| case FIELD: |
| case ENUM_CONSTANT: |
| sb.append(getFieldHeader((VariableElement)element)); |
| break; |
| case CLASS: |
| case INTERFACE: |
| case ENUM: |
| case ANNOTATION_TYPE: |
| sb.append(getClassHeader((TypeElement)element)); |
| break; |
| case PACKAGE: |
| sb.append(getPackageHeader((PackageElement)element)); |
| break; |
| case MODULE: |
| sb.append(getModuleHeader((ModuleElement)element)); |
| break; |
| } |
| } |
| return sb; |
| } |
| |
| private StringBuilder getContainingClassOrPackageHeader(Element el, Elements elements, ElementUtilities eu) { |
| StringBuilder sb = new StringBuilder(); |
| if (el.getKind() != ElementKind.PACKAGE && el.getKind() != ElementKind.MODULE) { |
| TypeElement cls = eu.enclosingTypeElement(el); |
| if (cls != null) { |
| switch(cls.getEnclosingElement().getKind()) { |
| case ANNOTATION_TYPE: |
| case CLASS: |
| case ENUM: |
| case INTERFACE: |
| case PACKAGE: |
| sb.append("<font size='+0'><b>"); //NOI18N |
| createLink(sb, cls, makeNameLineBreakable(cls.getQualifiedName().toString())); |
| sb.append("</b></font>"); //NOI18N) |
| } |
| } else { |
| PackageElement pkg = elements.getPackageOf(el); |
| if (pkg != null) { |
| sb.append("<font size='+0'><b>"); //NOI18N |
| createLink(sb, pkg, makeNameLineBreakable(pkg.getQualifiedName().toString())); |
| sb.append("</b></font>"); //NOI18N) |
| } |
| } |
| } |
| return sb; |
| } |
| |
| private String makeNameLineBreakable(String name) { |
| return name.replace(".", /* ZERO WIDTH SPACE */".​"); |
| } |
| |
| private StringBuilder getMethodHeader(ExecutableElement mdoc) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("<pre>"); //NOI18N |
| mdoc.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| int len = sb.length(); |
| mdoc.getModifiers().stream().filter((modifier) -> (modifier != Modifier.NATIVE)).forEachOrdered((modifier) -> { |
| sb.append(modifier).append(' '); //NOI18N |
| }); |
| len = sb.length() - len; |
| List<? extends TypeParameterElement> typeParameters = mdoc.getTypeParameters(); |
| if (!typeParameters.isEmpty()) { |
| sb.append("<"); //NOI18N |
| for (Iterator<? extends TypeParameterElement> it = typeParameters.iterator(); it.hasNext();) { |
| TypeParameterElement typeParam = it.next(); |
| len += appendType(sb, typeParam.asType(), false, true, false); |
| if (it.hasNext()) { |
| sb.append(','); //NOI18N |
| len++; |
| } |
| } |
| sb.append("> "); //NOI18N |
| len += 3; |
| } |
| if (mdoc.getKind() == ElementKind.CONSTRUCTOR) { |
| CharSequence name = mdoc.getEnclosingElement().getSimpleName(); |
| len += name.length(); |
| sb.append("<b>").append(name).append("</b>"); //NOI18N |
| } else { |
| len += appendType(sb, mdoc.getReturnType(), false, false, false); |
| CharSequence name = mdoc.getSimpleName(); |
| len += name.length(); |
| sb.append(" <b>").append(name).append("</b>"); //NOI18N |
| } |
| if (mdoc.getEnclosingElement().getKind() != ElementKind.ANNOTATION_TYPE) { |
| sb.append('('); //NOI18N |
| len++; |
| for (Iterator<? extends VariableElement> it = mdoc.getParameters().iterator(); it.hasNext();) { |
| VariableElement param = it.next(); |
| param.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| boolean varArg = mdoc.isVarArgs() && !it.hasNext(); |
| appendType(sb, param.asType(), varArg, false, false); |
| sb.append(' ').append(param.getSimpleName()); //NOI18N |
| if (it.hasNext()) { |
| sb.append(",<br>"); //NOI18N |
| appendSpace(sb, len); |
| } |
| } |
| sb.append(')'); //NOI18N |
| } |
| List<? extends TypeMirror> exs = mdoc.getThrownTypes(); |
| if (!exs.isEmpty()) { |
| sb.append("<br>"); //NOI18N |
| len = Math.max(0, len - 7); |
| appendSpace(sb, len); |
| sb.append("throws "); //NOI18N |
| for (Iterator<? extends TypeMirror> it = exs.iterator(); it.hasNext();) { |
| TypeMirror ex = it.next(); |
| appendType(sb, ex, false, false, false); |
| if (it.hasNext()) { |
| sb.append(",<br>"); //NOI18N |
| appendSpace(sb, len); |
| } |
| } |
| } |
| sb.append("</pre>"); //NOI18N |
| return sb; |
| } |
| |
| private StringBuilder getFieldHeader(VariableElement fdoc) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<pre>"); //NOI18N |
| fdoc.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| fdoc.getModifiers().forEach((modifier) -> { |
| sb.append(modifier).append(' '); //NOI18N |
| }); |
| appendType(sb, fdoc.asType(), false, false, false); |
| sb.append(" <b>").append(fdoc.getSimpleName()).append("</b>"); //NOI18N |
| String val = null; |
| try { |
| val = XMLUtil.toAttributeValue(fdoc.getConstantValue().toString()); |
| } catch (Exception ex) {} |
| if (val != null && val.length() > 0) |
| sb.append(" = ").append(val); //NOI18N |
| sb.append("</pre>"); //NOI18N |
| return sb; |
| } |
| |
| private StringBuilder getClassHeader(TypeElement cdoc) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<pre>"); //NOI18N |
| cdoc.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| for (Modifier modifier : cdoc.getModifiers()) { |
| switch(cdoc.getKind()) { |
| case ENUM: |
| if (modifier == Modifier.FINAL) |
| continue; |
| break; |
| case INTERFACE: |
| case ANNOTATION_TYPE: |
| if (modifier == Modifier.ABSTRACT) |
| continue; |
| break; |
| } |
| sb.append(modifier).append(' '); //NOI18N |
| } |
| switch (cdoc.getKind()) { |
| case ANNOTATION_TYPE: |
| sb.append("@interface "); //NOI18N |
| break; |
| case ENUM: |
| sb.append("enum "); //NOI18N |
| break; |
| case INTERFACE: |
| sb.append("interface "); //NOI18N |
| break; |
| default: |
| sb.append("class "); //NOI18N |
| } |
| sb.append("<b>").append(cdoc.getSimpleName()); //NOI18N |
| List<? extends TypeParameterElement> typeParams = cdoc.getTypeParameters(); |
| if (!typeParams.isEmpty()) { |
| sb.append("<"); //NOI18N |
| for (Iterator<? extends TypeParameterElement> it = typeParams.iterator(); it.hasNext();) { |
| TypeParameterElement typeParam = it.next(); |
| appendType(sb, typeParam.asType(), false, true, false); |
| if (it.hasNext()) |
| sb.append(","); //NOI18N |
| } |
| sb.append(">"); //NOI18N |
| } |
| sb.append("</b>"); //NOi18N |
| if (cdoc.getKind() != ElementKind.ANNOTATION_TYPE) { |
| TypeMirror supercls = cdoc.getSuperclass(); |
| if (supercls != null && supercls.getKind() != TypeKind.NONE) { |
| sb.append("<br>extends "); //NOI18N |
| appendType(sb, supercls, false, false, false); |
| } |
| List<? extends TypeMirror> ifaces = cdoc.getInterfaces(); |
| if (!ifaces.isEmpty()) { |
| sb.append(cdoc.getKind().isInterface() ? "<br>extends " : "<br>implements "); //NOI18N |
| for (Iterator<? extends TypeMirror> it = ifaces.iterator(); it.hasNext();) { |
| TypeMirror iface = it.next(); |
| appendType(sb, iface, false, false, false); |
| if (it.hasNext()) |
| sb.append(", "); //NOI18N |
| } |
| } |
| } |
| sb.append("</pre>"); //NOI18N |
| return sb; |
| } |
| |
| private StringBuilder getPackageHeader(PackageElement pdoc) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<pre>"); //NOI18N |
| pdoc.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| sb.append("package <b>").append(pdoc.getQualifiedName()).append("</b>"); //NOI18N |
| sb.append("</pre>"); //NOI18N |
| return sb; |
| } |
| |
| private StringBuilder getModuleHeader(ModuleElement mdoc) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<pre>"); //NOI18N |
| mdoc.getAnnotationMirrors().forEach((annotationDesc) -> { |
| appendAnnotation(sb, annotationDesc, true); |
| }); |
| sb.append("module <b>").append(mdoc.getQualifiedName()).append("</b>"); //NOI18N |
| sb.append("</pre>"); //NOI18N |
| return sb; |
| } |
| |
| private void appendAnnotation(StringBuilder sb, AnnotationMirror annotationDesc, boolean topLevel) { |
| DeclaredType annotationType = annotationDesc.getAnnotationType(); |
| if (annotationType != null && (!topLevel || isDocumented(annotationType))) { |
| appendType(sb, annotationType, false, false, true); |
| Map<? extends ExecutableElement, ? extends AnnotationValue> values = annotationDesc.getElementValues(); |
| if (!values.isEmpty()) { |
| sb.append('('); //NOI18N |
| for (Iterator<? extends Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>> it = values.entrySet().iterator(); it.hasNext();) { |
| Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> value = it.next(); |
| createLink(sb, value.getKey(), value.getKey().getSimpleName()); |
| sb.append('='); //NOI18N |
| appendAnnotationValue(sb, value.getValue()); |
| if (it.hasNext()) |
| sb.append(","); //NOI18N |
| } |
| sb.append(')'); //NOI18N |
| } |
| if (topLevel) |
| sb.append("<br>"); //NOI18N |
| } |
| } |
| |
| private boolean isDocumented(DeclaredType annotationType) { |
| return annotationType.asElement().getAnnotationMirrors().stream().anyMatch(annotationDesc |
| -> "java.lang.annotation.Documented".contentEquals(((TypeElement)annotationDesc.getAnnotationType().asElement()).getQualifiedName())); //NOI18N |
| } |
| |
| private void appendAnnotationValue(StringBuilder sb, AnnotationValue av) { |
| Object value = av.getValue(); |
| if (value instanceof List) { |
| List<? extends AnnotationValue> list = (List<? extends AnnotationValue>)value; |
| if (list.size() > 1) |
| sb.append('{'); //NOI18N |
| for (Iterator<? extends AnnotationValue> it = list.iterator(); it.hasNext();) { |
| AnnotationValue val = it.next(); |
| appendAnnotationValue(sb, val); |
| if (it.hasNext()) |
| sb.append(","); //NOI18N |
| } |
| if (list.size() > 1) |
| sb.append('}'); //NOI18N |
| } else if (value instanceof String) { |
| sb.append('"').append(value).append('"'); //NOI18N |
| } else if (value instanceof TypeMirror) { |
| appendType(sb, (TypeMirror)value, false, false, false); |
| } else if (value instanceof VariableElement) { |
| createLink(sb, (VariableElement)value, ((VariableElement)value).getSimpleName()); |
| } else if (value instanceof AnnotationMirror) { |
| appendAnnotation(sb, (AnnotationMirror)value, false); |
| } else { |
| sb.append(value.toString()); |
| } |
| } |
| |
| private StringBuilder getElementDoc(final Element element, final CompilationInfo info, final StringBuilder header, final URL url, final boolean sync) throws JavadocHelper.RemoteJavadocException { |
| final StringBuilder sb = new StringBuilder(); |
| if (element != null) { |
| final List<JavadocHelper.TextStream> pages = JavadocHelper.getJavadoc( |
| element, |
| sync ? JavadocHelper.RemoteJavadocPolicy.SPECULATIVE |
| : JavadocHelper.RemoteJavadocPolicy.USE, |
| cancel); |
| try { |
| boolean localized = false; |
| boolean remote = false; |
| for (JavadocHelper.TextStream ts : pages) { |
| if (docURL == null && !ts.getLocations().isEmpty()) { |
| docURL = ts.getLocations().get(0); |
| } |
| if (docRoot == null) { |
| docRoot = ts.getDocRoot(); |
| } |
| localized |= isLocalized(ts.getLocations(), element); |
| remote |= isRemote(ts, docURL); |
| } |
| if (!localized) { |
| assignSource(element, info, url, header); |
| } |
| sb.append(header); |
| if (!localized) { |
| Map<Object, StringBuilder> doc = getElementDocFromSource(element, info); |
| if (!doc.isEmpty()) { |
| final StringBuilder params = new StringBuilder(); |
| final StringBuilder thrs = new StringBuilder(); |
| doc.entrySet().forEach((entry) -> { |
| if (entry.getKey() instanceof Element) { |
| thrs.append("<code>"); //NOI18N |
| if (((Element)entry.getKey()).getKind() == ElementKind.TYPE_PARAMETER) { |
| thrs.append(((Element)entry.getKey()).getSimpleName()); |
| } else { |
| createLink(thrs, (Element)entry.getKey(), ((Element)entry.getKey()).getSimpleName()); |
| } |
| thrs.append("</code> - "); //NOI18N |
| thrs.append(entry.getValue()); |
| thrs.append("<br>"); //NOI18N |
| } else if (entry.getKey() instanceof Integer) { |
| params.append("<code>").append(getName(element, (int)entry.getKey())).append("</code> - "); //NOI18N |
| params.append(entry.getValue()); |
| params.append("<br>"); //NOI18N |
| } |
| }); |
| sb.append("<p>"); //NOI18N |
| if (doc.containsKey(DocTree.Kind.DEPRECATED)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-deprecated")).append("</b> <i>").append(doc.get(DocTree.Kind.DEPRECATED)).append("</i><p>"); //NOI18N |
| } |
| if (doc.containsKey(null)) { |
| sb.append(doc.get(null)); |
| } |
| sb.append("<p>"); //NOI18N |
| if (doc.containsKey(APINOTE_TAG)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-apinote")).append("</b><blockquote>").append(doc.get(APINOTE_TAG)).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(IMPLSPEC_TAG)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-implspec")).append("</b><blockquote>").append(doc.get(IMPLSPEC_TAG)).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(IMPLNOTE_TAG)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-implnote")).append("</b><blockquote>").append(doc.get(IMPLNOTE_TAG)).append("</blockquote>"); //NOI18N |
| } |
| if (params.length() > 0) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-params")).append("</b><blockquote>").append(params).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(DocTree.Kind.PARAM)) { //NOI18N |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-typeparams")).append("</b><blockquote>").append(doc.get(DocTree.Kind.PARAM)).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(DocTree.Kind.RETURN)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-returns")).append("</b><blockquote>").append(doc.get(DocTree.Kind.RETURN)).append("</blockquote>"); //NOI18N |
| } |
| if (thrs.length() > 0) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-throws")).append("</b><blockquote>").append(thrs).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(DocTree.Kind.SINCE)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-since")).append("</b><blockquote>").append(doc.get(DocTree.Kind.SINCE)).append("</blockquote>"); //NOI18N |
| } |
| if (doc.containsKey(DocTree.Kind.SEE)) { |
| sb.append("<b>").append(NbBundle.getMessage(ElementJavadoc.class, "JCD-see")).append("</b><blockquote>").append(doc.get(DocTree.Kind.SEE)).append("</blockquote>"); //NOI18N |
| } |
| computeDocURL(pages, sync, cancel); |
| return sb; |
| } |
| } |
| if (sync && remote) { |
| throw new JavadocHelper.RemoteJavadocException(null); |
| } |
| for (JavadocHelper.TextStream page : pages) { |
| String jdText = page != null ? HTMLJavadocParser.getJavadocText(page, false) : getURL() != null ? HTMLJavadocParser.getJavadocText(getURL(), false) : null; |
| if (jdText != null) { |
| sb.append("<p>"); //NOI18N |
| sb.append(jdText); |
| docURL = page.getLocation(); |
| return sb; |
| } |
| } |
| } finally { |
| pages.forEach((page) -> { |
| page.close(); |
| }); |
| } |
| if (element.getKind() == ElementKind.METHOD) { |
| return getInherited((ExecutableElement)element, (TypeElement)element.getEnclosingElement(), info, method -> { |
| try { |
| return getElementDoc(method, info, header, url, sync); |
| } catch (JavadocHelper.RemoteJavadocException rje) { |
| ElementJavadoc.<Void, RuntimeException>sthrow(rje); |
| } |
| return null; |
| }); |
| } |
| } |
| return null; |
| } |
| |
| private Map<Object, StringBuilder> getElementDocFromSource(final Element element, final CompilationInfo info) { |
| final Map<Object, StringBuilder> ret = new LinkedHashMap<>(); |
| final ElementHandle<? extends Element> eh = ElementHandle.create(element); |
| FileObject fo = SourceUtils.getFile(eh, cpInfo); |
| if (fo != null && fo.isFolder() && element.getKind() == ElementKind.PACKAGE) { |
| fo = fo.getFileObject("package-info.java"); // NOI18N |
| } |
| if (fo != null && fo != info.getFileObject()) { |
| try { |
| JavaSource js = JavaSource.forFileObject(fo); |
| if (js != null) { |
| final Map<Object, StringBuilder> out = new HashMap<>(); |
| js.runUserActionTask(controller -> { |
| controller.toPhase(Phase.ELEMENTS_RESOLVED); |
| Element e = eh.resolve(controller); |
| if (e != null) { |
| getElementDocFromSource(e, controller).entrySet().forEach((entry) -> { |
| Object key = entry.getKey(); |
| if (key instanceof Element) { |
| key = ElementHandle.create((Element)key); |
| } |
| out.put(key, entry.getValue()); |
| }); |
| } |
| },true); |
| out.entrySet().forEach((entry) -> { |
| Object key = entry.getKey(); |
| if (key instanceof ElementHandle) { |
| Element e = ((ElementHandle)key).resolve(info); |
| if (e != null) { |
| ret.put(e, entry.getValue()); |
| } |
| } else { |
| ret.put(key, entry.getValue()); |
| } |
| }); |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return ret; |
| } |
| final DocCommentTree doc = info.getDocTrees().getDocCommentTree(element); |
| if (doc != null) { |
| final TreePath path = info.getDocTrees().getPath(element); |
| List<? extends DocTree> body = doc.getFullBody(); |
| List<? extends DocTree> tags = doc.getBlockTags(); |
| AtomicBoolean inheritedDocNeeded = new AtomicBoolean(false); |
| Map<Object, StringBuilder> inheritedDoc = null; |
| Function<ExecutableElement, Map<Object, StringBuilder>> funct = method -> { |
| return getElementDocFromSource(method, info); |
| }; |
| if (element.getKind() == ElementKind.METHOD) { |
| if (body.isEmpty()) { |
| inheritedDocNeeded.set(true); |
| } else { |
| new DocTreeScanner<Void, Void>() { |
| @Override |
| public Void visitInheritDoc(InheritDocTree node, Void p) { |
| inheritedDocNeeded.set(true); |
| return null; |
| } |
| }.scan(doc, null); |
| } |
| if (inheritedDocNeeded.get()) { |
| inheritedDoc = getInherited((ExecutableElement)element, (TypeElement)element.getEnclosingElement(), info, funct); |
| } |
| } |
| if (body.isEmpty()) { |
| StringBuilder inhBody = inheritedDoc != null ? inheritedDoc.get(null) : null; |
| if (inhBody != null) { |
| ret.put(null, inhBody); |
| } |
| } else { |
| ret.put(null, inlineTags(body, path, doc, info.getDocTrees(), inheritedDoc != null ? inheritedDoc.get(null) : null)); |
| } |
| StringBuilder sb; |
| for (DocTree tag : tags) { |
| switch(tag.getKind()) { |
| case PARAM: |
| ParamTree paramTag = (ParamTree)tag; |
| if (paramTag.isTypeParameter()) { |
| sb = ret.get(DocTree.Kind.PARAM); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(DocTree.Kind.PARAM, sb); |
| } |
| sb.append("<code>").append(paramTag.getName().getName()).append("</code>"); //NOI18N |
| List<? extends DocTree> desc = paramTag.getDescription(); |
| if (!desc.isEmpty()) { |
| sb.append(" - "); //NOI18N |
| sb.append(inlineTags(desc, path, doc, info.getDocTrees(), null)); |
| } |
| sb.append("<br>"); //NOI18N |
| } else { |
| List<? extends DocTree> desc = paramTag.getDescription(); |
| if (!desc.isEmpty()) { |
| Integer idx = getIdx(element, paramTag.getName().getName()); |
| if (idx != null) { |
| sb = ret.get(idx); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(idx, sb); |
| } |
| sb.append(inlineTags(desc, path, doc, info.getDocTrees(), inheritedDoc != null ? inheritedDoc.get(idx) : null)); |
| } |
| } |
| } |
| break; |
| case THROWS: |
| ThrowsTree throwsTag = (ThrowsTree)tag; |
| Element e = info.getDocTrees().getElement(DocTreePath.getPath(path, doc, throwsTag.getExceptionName())); |
| if (e != null) { |
| List<? extends DocTree> desc = throwsTag.getDescription(); |
| if (!desc.isEmpty()) { |
| sb = ret.get(e); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(e, sb); |
| } |
| sb.append(inlineTags(desc, path, doc, info.getDocTrees(), inheritedDoc != null ? inheritedDoc.get(e) : null)); |
| } |
| } |
| break; |
| case RETURN: |
| sb = ret.get(DocTree.Kind.RETURN); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(DocTree.Kind.RETURN, sb); |
| } |
| sb.append(inlineTags(((ReturnTree)tag).getDescription(), path, doc, info.getDocTrees(), inheritedDoc != null ? inheritedDoc.get(DocTree.Kind.RETURN) : null)); |
| break; |
| case SEE: |
| sb = ret.get(DocTree.Kind.SEE); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(DocTree.Kind.SEE, sb); |
| } |
| SeeTree seeTag = (SeeTree)tag; |
| if (sb.length() > 0) { |
| sb.append(", "); //NOI18N |
| } |
| sb.append(inlineTags(seeTag.getReference(), path, doc, info.getDocTrees(), null)); |
| break; |
| case SINCE: |
| sb = ret.get(DocTree.Kind.SINCE); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(DocTree.Kind.SINCE, sb); |
| } |
| sb.append(inlineTags(((SinceTree)tag).getBody(), path, doc, info.getDocTrees(), null)); |
| break; |
| case DEPRECATED: |
| sb = ret.get(DocTree.Kind.DEPRECATED); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(DocTree.Kind.DEPRECATED, sb); |
| } |
| sb.append(inlineTags(((DeprecatedTree)tag).getBody(), path, doc, info.getDocTrees(), null)); |
| break; |
| case UNKNOWN_BLOCK_TAG: |
| UnknownBlockTagTree unTag = (UnknownBlockTagTree)tag; |
| switch (unTag.getTagName()) { |
| case APINOTE_TAG: |
| sb = ret.get(APINOTE_TAG); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(APINOTE_TAG, sb); |
| } |
| sb.append(inlineTags(unTag.getContent(), path, doc, info.getDocTrees(), null)); |
| break; |
| case IMPLSPEC_TAG: |
| sb = ret.get(IMPLSPEC_TAG); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(IMPLSPEC_TAG, sb); |
| } |
| sb.append(inlineTags(unTag.getContent(), path, doc, info.getDocTrees(), null)); |
| break; |
| case IMPLNOTE_TAG: |
| sb = ret.get(IMPLNOTE_TAG); |
| if (sb == null) { |
| sb = new StringBuilder(); |
| ret.put(IMPLNOTE_TAG, sb); |
| } |
| sb.append(inlineTags(unTag.getContent(), path, doc, info.getDocTrees(), null)); |
| break; |
| } |
| break; |
| } |
| } |
| |
| if (element.getKind() == ElementKind.METHOD) { |
| if (!ret.containsKey(DocTree.Kind.RETURN)) { |
| //If the method does not have a return javadoc then we need to get javadoc from parent. |
| if (inheritedDoc == null && !inheritedDocNeeded.getAndSet(true)) { |
| inheritedDoc = getInherited((ExecutableElement)element, (TypeElement)element.getEnclosingElement(), info, funct); |
| } |
| if (inheritedDoc != null && inheritedDoc.containsKey(DocTree.Kind.RETURN)) { |
| ret.put(DocTree.Kind.RETURN, new StringBuilder(inheritedDoc.get(DocTree.Kind.RETURN))); |
| } |
| } |
| for (int i = 0; i < ((ExecutableElement)element).getParameters().size(); i++) { |
| //Check every parameters in the method. If the current parameter has javadoc we do nothing. |
| //If the current parameter does not have javadoc we try to get javadoc from the parent. |
| if (!ret.containsKey(i)) { |
| if (inheritedDoc == null && !inheritedDocNeeded.getAndSet(true)) { |
| inheritedDoc = getInherited((ExecutableElement)element, (TypeElement)element.getEnclosingElement(), info, funct); |
| } |
| if (inheritedDoc != null && inheritedDoc.containsKey(i)) { |
| ret.put(i, new StringBuilder(inheritedDoc.get(i))); |
| } |
| } |
| } |
| for (TypeMirror thrownType : ((ExecutableElement)element).getThrownTypes()) { |
| //Check every throwables listed in the method. If for the current throwable there is not a javadoc try to get it from the parent. |
| //If for the current throwable there is a javadoc we show the current javadoc. |
| Element e = thrownType.getKind() == TypeKind.TYPEVAR ? ((TypeVariable)thrownType).asElement() : ((DeclaredType)thrownType).asElement(); |
| if (!ret.containsKey(e)) { |
| if (inheritedDoc == null && !inheritedDocNeeded.getAndSet(true)) { |
| inheritedDoc = getInherited((ExecutableElement)element, (TypeElement)element.getEnclosingElement(), info, funct); |
| } |
| if (inheritedDoc != null && inheritedDoc.containsKey(e)) { |
| ret.put(e, new StringBuilder(inheritedDoc.get(e))); |
| } |
| } |
| } |
| } |
| } |
| return ret; |
| } |
| |
| private <T> T getInherited(final ExecutableElement element, final TypeElement type, final CompilationInfo info, final Function<ExecutableElement, T> funct) { |
| final Elements elements = info.getElements(); |
| for (TypeMirror iface : type.getInterfaces()) { |
| for (ExecutableElement method : ElementFilter.methodsIn(((DeclaredType)iface).asElement().getEnclosedElements())) { |
| if (elements.overrides(element, method, (TypeElement)element.getEnclosingElement())) { |
| T ret = funct.apply(method); |
| if (ret != null) { |
| return ret; |
| } |
| } |
| } |
| } |
| for (TypeMirror iface : type.getInterfaces()) { |
| T ret = getInherited(element, (TypeElement)((DeclaredType)iface).asElement(), info, funct); |
| if (ret != null) { |
| return ret; |
| } |
| } |
| TypeMirror superclass = type.getSuperclass(); |
| if (superclass.getKind() == TypeKind.DECLARED) { |
| for (ExecutableElement method : ElementFilter.methodsIn(((DeclaredType)superclass).asElement().getEnclosedElements())) { |
| if (elements.overrides(element, method, (TypeElement)element.getEnclosingElement())) { |
| T ret = funct.apply(method); |
| if (ret != null) { |
| return ret; |
| } |
| } |
| } |
| return getInherited(element, (TypeElement)((DeclaredType)superclass).asElement(), info, funct); |
| } |
| return null; |
| } |
| |
| private void computeDocURL( |
| final List<? extends JavadocHelper.TextStream> pages, |
| final boolean sync, |
| final Callable<Boolean> cancel) { |
| class ComputeURL implements Callable<Void> { |
| |
| private final boolean remote; |
| |
| private ComputeURL(@NonNull final boolean remote) { |
| this.remote = remote; |
| } |
| |
| @Override |
| public Void call() throws Exception { |
| if (cancel == null || cancel.call() != Boolean.TRUE) { |
| if (!remote && pages.size() > 1) { |
| throw new JavadocHelper.RemoteJavadocException(null); |
| } |
| for (JavadocHelper.TextStream page : pages) { |
| final URL loc = page.getLocation( |
| remote ? |
| JavadocHelper.RemoteJavadocPolicy.USE : |
| JavadocHelper.RemoteJavadocPolicy.EXCEPTION); |
| if (loc != null) { |
| docURL = loc; |
| break; |
| } |
| } |
| } |
| return null; |
| } |
| } |
| if (docURL == null) { |
| if (sync) { |
| try { |
| new ComputeURL(false).call(); |
| } catch (JavadocHelper.RemoteJavadocException e) { |
| RP.submit(new ComputeURL(true)); |
| } catch (Exception e) { |
| Exceptions.printStackTrace(e); |
| } |
| } else { |
| try { |
| new ComputeURL(true).call(); |
| } catch (Exception e) { |
| Exceptions.printStackTrace(e); |
| } |
| } |
| } |
| } |
| |
| private String noJavadocFound() { |
| if (handle != null) { |
| final List<ClassPath> cps = new ArrayList<>(2); |
| ClassPath cp = cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT); |
| if (cp != null) { |
| cps.add(cp); |
| } |
| cp = cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE); |
| if (cp != null) { |
| cps.add(cp); |
| } |
| cp = ClassPathSupport.createProxyClassPath(cps.toArray(new ClassPath[cps.size()])); |
| String toSearch = SourceUtils.getJVMSignature(handle)[0].replace('.', '/'); |
| if (handle.getKind() != ElementKind.PACKAGE) { |
| toSearch += ".class"; //NOI18N |
| } |
| final FileObject resource = cp.findResource(toSearch); |
| if (resource != null) { |
| final FileObject root = cp.findOwnerRoot(resource); |
| try { |
| final URL rootURL = root.getURL(); |
| if (JavadocForBinaryQuery.findJavadoc(rootURL).getRoots().length == 0) { |
| FileObject userRoot = FileUtil.getArchiveFile(root); |
| if (userRoot == null) { |
| userRoot = root; |
| } |
| return NbBundle.getMessage( |
| ElementJavadoc.class, |
| "javadoc_content_not_found_attach", |
| rootURL.toExternalForm(), |
| FileUtil.getFileDisplayName(userRoot)); |
| } |
| } catch (FileStateInvalidException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| return NbBundle.getMessage(ElementJavadoc.class, "javadoc_content_not_found"); //NOI18N |
| } |
| |
| private StringBuilder inlineTags(List<? extends DocTree> tags, TreePath docPath, DocCommentTree doc, DocTrees trees, CharSequence inherited) { |
| StringBuilder sb = new StringBuilder(); |
| for (DocTree tag : tags) { |
| switch (tag.getKind()) { |
| case REFERENCE: |
| ReferenceTree refTag = (ReferenceTree)tag; |
| appendReference(sb, refTag, null, docPath, doc, trees); |
| break; |
| case LINK_PLAIN: |
| LinkTree linkTag = (LinkTree)tag; |
| appendReference(sb, linkTag.getReference(), linkTag.getLabel(), docPath, doc, trees); |
| break; |
| case LINK: |
| linkTag = (LinkTree)tag; |
| sb.append("<code>"); //NOI18N |
| appendReference(sb, linkTag.getReference(), linkTag.getLabel(), docPath, doc, trees); |
| sb.append("</code>"); //NOI18N |
| break; |
| case CODE: |
| LiteralTree codeTag = (LiteralTree)tag; |
| sb.append("<code>"); //NOI18N |
| try { |
| sb.append(XMLUtil.toElementContent(codeTag.getBody().getBody())); |
| } catch (IOException ioe) {} |
| sb.append("</code>"); //NOI18N |
| break; |
| case LITERAL: |
| LiteralTree literalTag = (LiteralTree)tag; |
| try { |
| sb.append(XMLUtil.toElementContent(literalTag.getBody().getBody())); |
| } catch (IOException ioe) {} |
| break; |
| case VALUE: |
| ValueTree valueTag = (ValueTree)tag; |
| ReferenceTree ref = valueTag.getReference(); |
| Element element = ref == null |
| ? trees.getElement(docPath) |
| : trees.getElement(DocTreePath.getPath(docPath, doc, ref)); |
| if (element != null && element.getKind().isField()) { |
| try { |
| sb.append(XMLUtil.toElementContent(((VariableElement)element).getConstantValue().toString())); |
| } catch (IOException ioe) {} |
| } |
| break; |
| case INHERIT_DOC: |
| if (inherited != null) { |
| sb.append(inherited); |
| } |
| break; |
| case START_ELEMENT: |
| StartElementTree startTag = (StartElementTree)tag; |
| List<? extends DocTree> attrs = startTag.getAttributes(); |
| sb.append('<').append(startTag.getName()).append(attrs.isEmpty() ? "" : " ").append(inlineTags(attrs, docPath, doc, trees, null)).append(startTag.isSelfClosing() ? "/>" : ">"); //NOI18N |
| break; |
| case END_ELEMENT: |
| EndElementTree endTag = (EndElementTree)tag; |
| sb.append("</").append(endTag.getName()).append('>'); //NOI18N |
| break; |
| case ATTRIBUTE: |
| AttributeTree attrTag = (AttributeTree)tag; |
| sb.append(attrTag.getName()); |
| String quote; |
| switch (attrTag.getValueKind()) { |
| case EMPTY: |
| quote = null; |
| break; |
| case UNQUOTED: |
| quote = ""; //NOI18N |
| break; |
| case SINGLE: |
| quote = "'"; //NOI18N |
| break; |
| case DOUBLE: |
| quote = "\""; //NOI18N |
| break; |
| default: |
| throw new AssertionError(); |
| } |
| if (quote != null) { |
| sb.append("=").append(quote).append(inlineTags(attrTag.getValue(), docPath, doc, trees, null)).append(quote); //NOI18N |
| } |
| break; |
| case DOC_ROOT: |
| if (docRoot != null) { |
| sb.append(docRoot); |
| } |
| break; |
| case ENTITY: |
| EntityTree entityTag = (EntityTree)tag; |
| sb.append('&').append(entityTag.getName()).append(';'); |
| break; |
| case TEXT: |
| TextTree ttag = (TextTree)tag; |
| sb.append(ttag.getBody()); |
| } |
| } |
| return sb; |
| } |
| |
| private void appendReference(StringBuilder sb, ReferenceTree ref, List<? extends DocTree> label, TreePath docPath, DocCommentTree doc, DocTrees trees) { |
| String sig = ref.getSignature(); |
| if (sig != null && sig.length() > 0) { |
| if (sig.charAt(0) == '#') { //NOI18N |
| sig = sig.substring(1); |
| } |
| sig = sig.replace('#', '.'); //NOI18N |
| } |
| Element element = trees.getElement(DocTreePath.getPath(docPath, doc, ref)); |
| if (element != null) { |
| createLink(sb, element, label == null || label.isEmpty() ? sig : inlineTags(label, docPath, doc, trees, null)); //NOI18N |
| } else { |
| sb.append(label == null || label.isEmpty() ? sig : inlineTags(label, docPath, doc, trees, null)); |
| } |
| } |
| |
| private void appendType(StringBuilder sb, TypeMirror type, boolean varArg) { |
| switch (type.getKind()) { |
| case ARRAY: |
| appendType(sb, ((ArrayType)type).getComponentType(), false); |
| sb.append(varArg ? "..." : "[]"); //NOI18N |
| break; |
| case DECLARED: |
| sb.append(((TypeElement)((DeclaredType)type).asElement()).getQualifiedName()); |
| break; |
| default: |
| sb.append(type); |
| } |
| } |
| |
| private void appendSpace(StringBuilder sb, int length) { |
| while (length-- >= 0) { |
| sb.append(' '); //NOI18N |
| } |
| } |
| |
| private int appendType(StringBuilder sb, TypeMirror type, boolean varArg, boolean typeVar, boolean annotation) { |
| int len = 0; |
| switch(type.getKind()) { |
| case WILDCARD: |
| WildcardType wt = (WildcardType)type; |
| sb.append('?'); //NOI18N |
| len++; |
| TypeMirror bound = wt.getExtendsBound(); |
| if (bound != null) { |
| sb.append(" extends "); //NOI18N |
| len += 9; |
| len += appendType(sb, bound, false, false, false); |
| } |
| bound = wt.getSuperBound(); |
| if (bound != null) { |
| sb.append(" super "); //NOI18N |
| len += 7; |
| len += appendType(sb, bound, false, false, false); |
| } |
| break; |
| case TYPEVAR: |
| TypeVariable tv = (TypeVariable)type; |
| len += createLink(sb, null, tv.asElement().getSimpleName()); |
| bound = tv.getUpperBound(); |
| if (typeVar && bound != null && |
| (bound.getKind() != TypeKind.DECLARED || !bound.getAnnotationMirrors().isEmpty() |
| || !"java.lang.Object".contentEquals(((TypeElement)((DeclaredType)bound).asElement()).getQualifiedName()))) { |
| sb.append(" extends "); //NOI18N |
| len += 9; |
| len += appendType(sb, bound, false, false, false); |
| } |
| break; |
| case INTERSECTION: |
| IntersectionType it = (IntersectionType)type; |
| for (Iterator<? extends TypeMirror> iter = it.getBounds().iterator(); iter.hasNext();) { |
| bound = iter.next(); |
| len += appendType(sb, bound, false, false, false); |
| if (iter.hasNext()) { |
| sb.append(" & "); //NOI18N |
| len += 3; |
| } |
| } |
| break; |
| case DECLARED: |
| DeclaredType dt = (DeclaredType)type; |
| Element el = dt.asElement(); |
| len += createLink(sb, el, annotation && el.getKind() == ElementKind.ANNOTATION_TYPE ? "@" + el.getSimpleName() : el.getSimpleName()); //NOI18N |
| List<? extends TypeMirror> typeArgs = dt.getTypeArguments(); |
| if (!typeArgs.isEmpty()) { |
| sb.append("<"); //NOI18N |
| for (Iterator<? extends TypeMirror> iter = typeArgs.iterator(); iter.hasNext();) { |
| TypeMirror typeArg = iter.next(); |
| len += appendType(sb, typeArg, false, false, false); |
| if (iter.hasNext()) { |
| sb.append(","); //NOI18N |
| len++; |
| } |
| } |
| sb.append(">"); //NOI18N |
| len += 2; |
| } |
| break; |
| case ARRAY: |
| ArrayType at = (ArrayType)type; |
| len += appendType(sb, at.getComponentType(), false, false, false); |
| if (varArg) { |
| sb.append("..."); //NOI18N |
| len += 3; |
| } else { |
| sb.append("[]"); //NOI18N |
| len += 2; |
| } |
| break; |
| case BOOLEAN: |
| sb.append("boolean"); |
| len += 7; |
| break; |
| case CHAR: |
| sb.append("char"); |
| len += 4; |
| break; |
| case BYTE: |
| sb.append("byte"); |
| len += 4; |
| break; |
| case DOUBLE: |
| sb.append("double"); |
| len += 6; |
| break; |
| case FLOAT: |
| sb.append("float"); |
| len += 5; |
| break; |
| case INT: |
| sb.append("int"); |
| len += 3; |
| break; |
| case LONG: |
| sb.append("long"); |
| len += 4; |
| break; |
| case SHORT: |
| sb.append("short"); |
| len += 5; |
| break; |
| case VOID: |
| sb.append("void"); |
| len += 4; |
| break; |
| } |
| return len; |
| } |
| |
| private Integer getIdx(Element e, Name n) { |
| if (e instanceof ExecutableElement && n != null) { |
| int idx = 0; |
| for (VariableElement par : ((ExecutableElement)e).getParameters()) { |
| if (n.contentEquals(par.getSimpleName())) { |
| return idx; |
| } |
| idx++; |
| } |
| } |
| return null; |
| } |
| |
| private Name getName(Element e, int idx) { |
| return e instanceof ExecutableElement ? ((ExecutableElement)e).getParameters().get(idx).getSimpleName() : null; |
| } |
| |
| private int createLink(StringBuilder sb, Element e, CharSequence text) { |
| if (e != null && e.asType().getKind() != TypeKind.ERROR) { |
| String link = "*" + linkCounter++; //NOI18N |
| links.put(link, ElementHandle.create(e)); |
| sb.append("<a href='").append(link).append("'>"); //NOI18N |
| } |
| sb.append(text); |
| if (e != null) |
| sb.append("</a>"); //NOI18N |
| return text.length(); |
| } |
| |
| private CharSequence getFragment(Element e) { |
| StringBuilder sb = new StringBuilder(); |
| if (!e.getKind().isClass() && !e.getKind().isInterface()) { |
| if (e.getKind() == ElementKind.CONSTRUCTOR) { |
| sb.append(e.getEnclosingElement().getSimpleName()); |
| } else { |
| sb.append(e.getSimpleName()); |
| } |
| if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) { |
| ExecutableElement ee = (ExecutableElement)e; |
| sb.append('('); //NOI18N |
| for (Iterator<? extends VariableElement> it = ee.getParameters().iterator(); it.hasNext();) { |
| VariableElement param = it.next(); |
| appendType(sb, param.asType(), ee.isVarArgs() && !it.hasNext()); |
| if (it.hasNext()) |
| sb.append(", "); |
| } |
| sb.append(')'); //NOI18N |
| } |
| } |
| return sb; |
| } |
| |
| private static boolean isRemote(final JavadocHelper.TextStream page, final URL url) { |
| if (page != null) { |
| if (page.getLocations().stream().anyMatch((loc) -> (loc.toString().startsWith("http")))) { //NOI18N |
| return true; |
| } |
| } |
| return url != null ? url.toString().startsWith("http") : false; //NOI18N |
| } |
| |
| private static boolean isLocalized(final List<? extends URL> docUrls, final Element element) { |
| return docUrls.stream().anyMatch((docUrl) -> (isLocalized(docUrl, element))); |
| } |
| |
| private static boolean isLocalized(final URL docURL, final Element element) { |
| if (docURL == null) { |
| return false; |
| } |
| Element pkg = element; |
| while (pkg.getKind() != ElementKind.PACKAGE) { |
| pkg = pkg.getEnclosingElement(); |
| if (pkg == null) { |
| return false; |
| } |
| } |
| String pkgBinName = ((PackageElement)pkg).getQualifiedName().toString(); |
| String surl = docURL.toString(); |
| int index = surl.lastIndexOf('/'); //NOI18N |
| if (index < 0) { |
| return false; |
| } |
| index-=(pkgBinName.length()+1); |
| if (index < 0) { |
| return false; |
| } |
| index-=API.length(); |
| if (index < 0 || !surl.regionMatches(index,API,0,API.length())) { |
| return false; |
| } |
| int index2 = surl.lastIndexOf('/', index-1); //NOI18N |
| if (index2 < 0) { |
| return false; |
| } |
| String lang = surl.substring(index2+1, index); |
| return LANGS.contains(lang); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static <R, T extends Throwable> R sthrow (@NonNull final Throwable t) throws T { |
| throw (T) t; |
| } |
| |
| private static final class Now implements Future<String> { |
| |
| private final String value; |
| |
| Now(final String value) { |
| this.value = value; |
| } |
| |
| @Override |
| public boolean cancel(boolean mayInterruptIfRunning) { |
| return false; |
| } |
| |
| @Override |
| public boolean isCancelled() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDone() { |
| return true; |
| } |
| |
| @Override |
| public String get() throws InterruptedException, ExecutionException { |
| return value; |
| } |
| |
| @Override |
| public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { |
| return value; |
| } |
| } |
| |
| private void assignSource( |
| @NonNull final Element element, |
| @NonNull final CompilationInfo compilationInfo, |
| @NullAllowed final URL url, |
| @NonNull final StringBuilder content) { |
| final FileObject fo = SourceUtils.getFile(element, compilationInfo.getClasspathInfo()); |
| if (fo != null) { |
| if (docURL == null && goToSource == null) { |
| content.insert(0, "<base href=\"" + fo.toURL() + "\"></base>"); //NOI18N |
| } |
| goToSource = new AbstractAction() { |
| @Override |
| public void actionPerformed(ActionEvent evt) { |
| ElementOpen.open(fo, handle); |
| } |
| }; |
| } |
| if (url != null) { |
| docURL = url; |
| } |
| } |
| } |