blob: 02a56a9cff300b72ad2c94cdea890587d9d245db [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.api.tools.internal.provisional.scanner;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.pde.api.tools.internal.CompilationUnit;
import org.eclipse.pde.api.tools.internal.JavadocTagManager;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.Factory;
import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
import org.eclipse.pde.api.tools.internal.provisional.IApiDescription;
import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IPackageDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor;
import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
import org.eclipse.pde.api.tools.internal.util.Signatures;
import org.eclipse.pde.api.tools.internal.util.Util;
/**
* Scans the source of a *.java file for any API javadoc tags
*
* @since 1.0.0
*/
public class TagScanner {
/**
* Visitor to scan a compilation unit. We only care about Javadoc nodes that
* have either type or enum declarations as parents, so we have to override
* the ones we don't care about.
*/
static class Visitor extends ASTVisitor {
private IApiDescription fDescription = null;
/**
* Package descriptor. Initialized to default package, and overridden if
* a package declaration is visited.
*/
private IPackageDescriptor fPackage = Factory.packageDescriptor(""); //$NON-NLS-1$
/**
* Type descriptor for type currently being visited.
*/
private IReferenceTypeDescriptor fType = null;
/**
* Used to look up binaries when resolving method signatures, or
* <code>null</code> if not provided.
*/
private IApiTypeContainer fContainer = null;
/**
* Constructor
*
* @param description API description to annotate
* @param container class file container or <code>null</code>, used to
* resolve method signatures
*/
public Visitor(IApiDescription description, IApiTypeContainer container) {
fDescription = description;
fContainer = container;
}
/**
* A type has been entered - update the type being visited.
*
* @param name name from type node
*/
private void enterType(SimpleName name) {
if (fType == null) {
fType = fPackage.getType(name.getFullyQualifiedName());
} else {
fType = fType.getType(name.getFullyQualifiedName());
}
}
/**
* A type has been exited - update the type being visited.
*/
private void exitType() {
fType = fType.getEnclosingType();
}
@Override
public boolean visit(MarkerAnnotation node) {
String name = node.getTypeName().getFullyQualifiedName();
if (JavadocTagManager.ALL_ANNOTATIONS.contains(name)) {
ASTNode parent = node.getParent();
if (parent != null) {
switch (parent.getNodeType()) {
case ASTNode.TYPE_DECLARATION: {
scanTypeAnnotation(name, (TypeDeclaration) parent);
break;
}
case ASTNode.ANNOTATION_TYPE_DECLARATION:
case ASTNode.ENUM_DECLARATION: {
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
if (JavadocTagManager.ANNOTATION_NOREFERENCE.equals(name)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
fDescription.setRestrictions(fType, restrictions);
}
break;
}
case ASTNode.FIELD_DECLARATION: {
scanFieldAnnotation(name, (FieldDeclaration) parent);
break;
}
case ASTNode.METHOD_DECLARATION: {
scanMethodAnnotation(name, (MethodDeclaration) parent);
break;
}
default:
break;
}
}
}
return false;
}
/**
* Checks the annotation name found on the given {@link TypeDeclaration}
*
* @param name the name of the annotation
* @param node the parent {@link TypeDeclaration}
*
* @since 1.0.600
*/
void scanTypeAnnotation(String name, TypeDeclaration node) {
int flags = node.getModifiers();
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
if (JavadocTagManager.ANNOTATION_NOREFERENCE.equals(name)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
}
if (JavadocTagManager.ANNOTATION_NOEXTEND.equals(name) && !Flags.isFinal(flags)) {
restrictions |= RestrictionModifiers.NO_EXTEND;
}
if (node.isInterface()) {
if (JavadocTagManager.ANNOTATION_NOIMPLEMENT.equals(name)) {
restrictions |= RestrictionModifiers.NO_IMPLEMENT;
}
} else {
if (JavadocTagManager.ANNOTATION_NOINSTANTIATE.equals(name) && !Flags.isAbstract(flags)) {
restrictions |= RestrictionModifiers.NO_INSTANTIATE;
}
}
if (restrictions != RestrictionModifiers.NO_RESTRICTIONS) {
fDescription.setRestrictions(fType, restrictions);
}
}
/**
* Checks the annotation name found on the given
* {@link FieldDeclaration}
*
* @param name the annotation name
* @param node the parent {@link FieldDeclaration}
*/
void scanFieldAnnotation(String name, FieldDeclaration node) {
List<VariableDeclarationFragment> fields = node.fragments();
int flags = node.getModifiers();
if (!Flags.isFinal(flags) && JavadocTagManager.ANNOTATION_NOREFERENCE.equals(name)) {
for (VariableDeclarationFragment fragment : fields) {
IElementDescriptor descriptor = fType.getField(fragment.getName().getFullyQualifiedName());
fDescription.setRestrictions(descriptor, RestrictionModifiers.NO_REFERENCE);
}
}
}
/**
* Checks the annotation name found on the given
* {@link MethodDeclaration}
*
* @param name the name of the annotation
* @param node the parent {@link MethodDeclaration}
*
* @since 1.0.600
*/
void scanMethodAnnotation(String name, MethodDeclaration node) {
String signature = Signatures.getMethodSignatureFromNode(node, true);
if (signature != null) {
String methodname = node.getName().getFullyQualifiedName();
if (node.isConstructor()) {
methodname = "<init>"; //$NON-NLS-1$
}
IMethodDescriptor descriptor = fType.getMethod(methodname, signature);
try {
descriptor = Factory.resolveMethod(fContainer, descriptor);
} catch (CoreException e) {
if (ApiPlugin.DEBUG_TAG_SCANNER) {
System.err.println(e.getLocalizedMessage());
}
}
IApiAnnotations annots = fDescription.resolveAnnotations(descriptor);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
if (JavadocTagManager.ANNOTATION_NOREFERENCE.equals(name)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
}
if (JavadocTagManager.ANNOTATION_NOOVERRIDE.equals(name)) {
if (!Flags.isFinal(node.getModifiers()) && !Flags.isStatic(node.getModifiers())) {
ASTNode parent = node.getParent();
if (parent instanceof TypeDeclaration) {
TypeDeclaration type = (TypeDeclaration) parent;
if (type.isInterface()) {
if (Flags.isDefaultMethod(node.getModifiers())) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
} else if (!Flags.isFinal(type.getModifiers())) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
} else if (parent instanceof AnonymousClassDeclaration) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
}
}
if (restrictions != RestrictionModifiers.NO_RESTRICTIONS) {
fDescription.setRestrictions(descriptor, restrictions);
}
}
}
@Override
public boolean visit(TypeDeclaration node) {
if (isNotVisible(node.getModifiers())) {
return false;
}
enterType(node.getName());
scanTypeJavaDoc(node);
return true;
}
@Override
public boolean visit(RecordDeclaration node) {
if (isNotVisible(node.getModifiers())) {
return false;
}
enterType(node.getName());
scanTypeJavaDoc(node);
return true;
}
/**
* Scans the JavaDoc of a {@link TypeDeclaration}
*
* @param node the type
* @since 1.0.600
*/
void scanTypeJavaDoc(TypeDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
List<TagElement> tags = doc.tags();
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
}
if (node.isInterface()) {
if (JavadocTagManager.TAG_NOEXTEND.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_EXTEND;
} else if (JavadocTagManager.TAG_NOIMPLEMENT.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_IMPLEMENT;
}
} else {
int flags = node.getModifiers();
if (JavadocTagManager.TAG_NOEXTEND.equals(tagname)) {
if (!Flags.isFinal(flags)) {
restrictions |= RestrictionModifiers.NO_EXTEND;
continue;
}
}
if (JavadocTagManager.TAG_NOINSTANTIATE.equals(tagname)) {
if (!Flags.isAbstract(flags)) {
restrictions |= RestrictionModifiers.NO_INSTANTIATE;
continue;
}
}
}
}
if (restrictions != RestrictionModifiers.NO_RESTRICTIONS) {
fDescription.setRestrictions(fType, restrictions);
}
}
}
void scanTypeJavaDoc(RecordDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
List<TagElement> tags = doc.tags();
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
}
int flags = node.getModifiers();
if (JavadocTagManager.TAG_NOEXTEND.equals(tagname)) {
if (!Flags.isFinal(flags)) {
restrictions |= RestrictionModifiers.NO_EXTEND;
continue;
}
}
if (JavadocTagManager.TAG_NOINSTANTIATE.equals(tagname)) {
if (!Flags.isAbstract(flags)) {
restrictions |= RestrictionModifiers.NO_INSTANTIATE;
continue;
}
}
}
if (restrictions != RestrictionModifiers.NO_RESTRICTIONS) {
fDescription.setRestrictions(fType, restrictions);
}
}
}
@Override
public void endVisit(TypeDeclaration node) {
if (!isNotVisible(node.getModifiers())) {
exitType();
}
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
if (!isNotVisible(node.getModifiers())) {
exitType();
}
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
if (isNotVisible(node.getModifiers())) {
return false;
}
enterType(node.getName());
scanAnnotationJavaDoc(node);
return true;
}
/**
* Scans the JavaDoc of an {@link AnnotationTypeDeclaration}
*
* @param node the annotation
* @since 1.0.600
*/
void scanAnnotationJavaDoc(AnnotationTypeDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
List<TagElement> tags = doc.tags();
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
fDescription.setRestrictions(fType, restrictions);
}
}
}
}
@Override
public boolean visit(EnumDeclaration node) {
if (isNotVisible(node.getModifiers())) {
return false;
}
enterType(node.getName());
scanEnumJavaDoc(node);
return true;
}
/**
* Scans the JavaDoc of an {@link EnumDeclaration}
*
* @param node the enum
* @since 1.0.600
*/
void scanEnumJavaDoc(EnumDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
List<TagElement> tags = doc.tags();
IApiAnnotations annots = fDescription.resolveAnnotations(fType);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
fDescription.setRestrictions(fType, restrictions);
}
}
}
}
@Override
public void endVisit(EnumDeclaration node) {
if (!isNotVisible(node.getModifiers())) {
exitType();
}
}
@Override
public boolean visit(PackageDeclaration node) {
Name name = node.getName();
fPackage = Factory.packageDescriptor(name.getFullyQualifiedName());
return false;
}
@Override
public boolean visit(MethodDeclaration node) {
if (isNotVisible(node.getModifiers())) {
ASTNode parent = node.getParent();
if (parent instanceof TypeDeclaration) {
TypeDeclaration type = (TypeDeclaration) parent;
if (!type.isInterface()) {
return false;
}
} else {
return false;
}
}
scanMethodJavaDoc(node);
return true;
}
/**
* Scans the JavaDoc node of a {@link MethodDeclaration}
*
* @param node the method
* @since 1.0.600
*/
void scanMethodJavaDoc(MethodDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
String signature = Signatures.getMethodSignatureFromNode(node, true);
if (signature != null) {
String methodname = node.getName().getFullyQualifiedName();
if (node.isConstructor()) {
methodname = "<init>"; //$NON-NLS-1$
}
IMethodDescriptor descriptor = fType.getMethod(methodname, signature);
try {
descriptor = Factory.resolveMethod(fContainer, descriptor);
} catch (CoreException e) {
if (ApiPlugin.DEBUG_TAG_SCANNER) {
System.err.println(e.getLocalizedMessage());
}
}
List<TagElement> tags = doc.tags();
IApiAnnotations annots = fDescription.resolveAnnotations(descriptor);
int restrictions = annots != null ? annots.getRestrictions() : RestrictionModifiers.NO_RESTRICTIONS;
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
restrictions |= RestrictionModifiers.NO_REFERENCE;
}
if (JavadocTagManager.TAG_NOOVERRIDE.equals(tagname)) {
if (Flags.isFinal(node.getModifiers()) || Flags.isStatic(node.getModifiers())) {
continue;
}
ASTNode parent = node.getParent();
if (parent instanceof TypeDeclaration) {
TypeDeclaration type = (TypeDeclaration) parent;
if (type.isInterface()) {
if (Flags.isDefaultMethod(node.getModifiers())) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
} else if (!Flags.isFinal(type.getModifiers())) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
} else if (parent instanceof AnonymousClassDeclaration) {
restrictions |= RestrictionModifiers.NO_OVERRIDE;
}
}
}
if (restrictions != RestrictionModifiers.NO_RESTRICTIONS) {
fDescription.setRestrictions(descriptor, restrictions);
}
}
}
}
@Override
public boolean visit(FieldDeclaration node) {
if (isNotVisible(node.getModifiers())) {
return false;
}
scanFieldJavaDoc(node);
return true;
}
/**
* Scans the JavaDoc nodes of a {@link FieldDeclaration}
*
* @param node the field
* @since 1.0.600
*/
void scanFieldJavaDoc(FieldDeclaration node) {
Javadoc doc = node.getJavadoc();
if (doc != null) {
List<VariableDeclarationFragment> fields = node.fragments();
List<TagElement> tags = doc.tags();
int flags = node.getModifiers();
for (TagElement tag : tags) {
String tagname = tag.getTagName();
if (!JavadocTagManager.ALL_TAGS.contains(tagname)) {
continue;
}
if (!Flags.isFinal(flags) && JavadocTagManager.TAG_NOREFERENCE.equals(tagname)) {
for (VariableDeclarationFragment fragment : fields) {
IElementDescriptor descriptor = fType.getField(fragment.getName().getFullyQualifiedName());
fDescription.setRestrictions(descriptor, RestrictionModifiers.NO_REFERENCE);
}
}
}
}
}
/**
* Determine if the flags contain private or package default flags
*
* @param flags
* @return <code>true</code> if the flags are private or default,
* <code>false</code> otherwise
*/
private boolean isNotVisible(int flags) {
return Flags.isPrivate(flags) || Flags.isPackageDefault(flags);
}
}
/**
* The singleton instance of the scanner
*/
private static TagScanner fSingleton = null;
/**
* Delegate for getting the singleton instance of the scanner
*
* @return
*/
public static final TagScanner newScanner() {
if (fSingleton == null) {
fSingleton = new TagScanner();
}
return fSingleton;
}
/**
* Constructor Cannot be instantiated
*/
private TagScanner() {
}
/**
* Scans the specified {@link ICompilationUnit} for contributed API Javadoc
* tags. Tags on methods will have unresolved signatures.
*
* @param unit the compilation unit source
* @param description the API description to annotate with any new tag rules
* found
* @param container optional class file container containing the class file
* for the given source that can be used to resolve method
* signatures if required (for tags on methods). If not provided
* (<code>null</code>), method signatures will be unresolved.
* @param monitor
*
* @throws CoreException if problems were encountered while scanning tags,
* the description may still be modified
*/
public void scan(ICompilationUnit unit, IApiDescription description, IApiTypeContainer container, IProgressMonitor monitor) throws CoreException {
scan(new CompilationUnit(unit), description, container, unit.getJavaProject().getOptions(true), monitor);
}
/**
* Scans the specified source {@linkplain CompilationUnit} for contributed
* API javadoc tags. Tags on methods will have unresolved signatures.
*
* @param source the source file to scan for tags
* @param description the API description to annotate with any new tag rules
* found
* @param container optional class file container containing the class file
* for the given source that can be used to resolve method
* signatures if required (for tags on methods). If not provided
* (<code>null</code>), method signatures will be unresolved.
* @param options a map of Java compiler options to use when creating the
* AST to scan or <code>null</code> if default options should be
* used
* @param monitor
*
* @throws CoreException if problems were encountered while scanning tags,
* the description may still be modified
*/
public void scan(CompilationUnit source, IApiDescription description, IApiTypeContainer container, Map<String, String> options, IProgressMonitor monitor) throws CoreException {
SubMonitor localmonitor = SubMonitor.convert(monitor, 2);
ASTParser parser = ASTParser.newParser(AST.JLS_Latest);
InputStream inputStream = null;
try {
inputStream = source.getInputStream();
parser.setSource(Util.getInputStreamAsCharArray(inputStream, source.getEncoding()));
} catch (FileNotFoundException e) {
throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Compilation unit source not found: {0}", source.getName()), e)); //$NON-NLS-1$
} catch (IOException e) {
if (ApiPlugin.DEBUG_TAG_SCANNER) {
System.err.println(source.getName());
}
throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Error reading compilation unit: {0}", source.getName()), e)); //$NON-NLS-1$
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
ApiPlugin.log(e);
}
}
}
localmonitor.split(1);
Map<String, String> loptions = options;
if (loptions == null) {
loptions = JavaCore.getOptions();
}
loptions.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
parser.setCompilerOptions(loptions);
org.eclipse.jdt.core.dom.CompilationUnit cunit = (org.eclipse.jdt.core.dom.CompilationUnit) parser.createAST(localmonitor.split(1));
Visitor visitor = new Visitor(description, container);
cunit.accept(visitor);
}
}