| /******************************************************************************* |
| * 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; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| |
| import org.eclipse.core.filebuffers.FileBuffers; |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.filebuffers.ITextFileBufferManager; |
| import org.eclipse.core.filebuffers.LocationKind; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.dom.AST; |
| import org.eclipse.jdt.core.dom.ASTParser; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.Javadoc; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.TagElement; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.jdt.core.dom.rewrite.ListRewrite; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.pde.api.tools.internal.provisional.ApiDescriptionVisitor; |
| 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.IApiJavadocTag; |
| import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers; |
| import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IFieldDescriptor; |
| 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.scanner.ScannerMessages; |
| import org.eclipse.pde.api.tools.internal.util.Signatures; |
| import org.eclipse.pde.api.tools.internal.util.Util; |
| import org.eclipse.text.edits.TextEdit; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * Provides tools for scanning/loading/parsing component.xml files. |
| * |
| * @since 1.0.0 |
| */ |
| public class ApiDescriptionProcessor { |
| |
| /** |
| * Visits each type, collecting all members before processing the type. |
| */ |
| static class DescriptionVisitor extends ApiDescriptionVisitor { |
| |
| /** |
| * The API description associated with the project. |
| */ |
| private IApiDescription apiDescription = null; |
| |
| /** |
| * Java project to resolve types in |
| */ |
| private IJavaProject project = null; |
| |
| /** |
| * List to collect text edits |
| */ |
| private Map<IFile, Set<TextEdit>> fCollector = null; |
| |
| /** |
| * Members collected from current type. |
| */ |
| private List<IElementDescriptor> members = new ArrayList<>(); |
| |
| /** |
| * List of exception statuses that occurred, or <code>null</code> if |
| * none. |
| */ |
| private List<IStatus> exceptions = null; |
| |
| /** |
| * Constructs a new visitor to collect tag updates in a java project. |
| * |
| * @param jp project to update |
| * @param cd project's API description |
| * @param collector collection to place text edits into |
| */ |
| DescriptionVisitor(IJavaProject jp, IApiDescription cd, Map<IFile, Set<TextEdit>> collector) { |
| project = jp; |
| apiDescription = cd; |
| fCollector = collector; |
| } |
| |
| @Override |
| public boolean visitElement(IElementDescriptor element, IApiAnnotations description) { |
| switch (element.getElementType()) { |
| case IElementDescriptor.PACKAGE: { |
| return true; |
| } |
| case IElementDescriptor.TYPE: { |
| members.clear(); |
| members.add(element); |
| return true; |
| } |
| default: { |
| members.add(element); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void endVisitElement(IElementDescriptor element, IApiAnnotations description) { |
| if (element.getElementType() == IElementDescriptor.TYPE) { |
| IReferenceTypeDescriptor refType = (IReferenceTypeDescriptor) element; |
| try { |
| IReferenceTypeDescriptor topLevelType = refType.getEnclosingType(); |
| while (topLevelType != null) { |
| refType = topLevelType; |
| topLevelType = refType.getEnclosingType(); |
| } |
| IType type = project.findType(refType.getQualifiedName(), new NullProgressMonitor()); |
| IType typeInProject = Util.getTypeInSameJavaProject(type, refType.getQualifiedName(), project); |
| if (typeInProject != null) { |
| type = typeInProject; |
| } |
| if (type != null) { |
| processTagUpdates(type, refType, apiDescription, members, fCollector); |
| } |
| } catch (CoreException e) { |
| addStatus(e.getStatus()); |
| } catch (BadLocationException e) { |
| addStatus(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, ScannerMessages.ComponentXMLScanner_0 + element.toString(), e)); |
| } |
| members.clear(); |
| } |
| } |
| |
| /** |
| * Adds a status to the current listing of messages |
| * |
| * @param status |
| */ |
| private void addStatus(IStatus status) { |
| if (exceptions == null) { |
| exceptions = new ArrayList<>(); |
| } |
| exceptions.add(status); |
| } |
| |
| /** |
| * Returns the status of processing the project. Status is OK if no |
| * errors occurred. |
| * |
| * @return status |
| */ |
| public IStatus getStatus() { |
| if (exceptions == null) { |
| return Status.OK_STATUS; |
| } |
| return new MultiStatus(ApiPlugin.PLUGIN_ID, 0, exceptions.toArray(new IStatus[exceptions.size()]), ScannerMessages.ComponentXMLScanner_1, null); |
| } |
| |
| } |
| |
| /** |
| * Visitor used for finding the nodes to update the javadoc tags for, if |
| * needed |
| */ |
| static class ASTTagVisitor extends ASTVisitor { |
| private List<IElementDescriptor> apis = null; |
| private IApiDescription description = null; |
| private ASTRewrite rewrite = null; |
| private Stack<Integer> typeStack; |
| |
| /** |
| * Constructor |
| * |
| * @param APIs a listing of {@link IElementDescriptor}s that we care |
| * about for this visit |
| */ |
| public ASTTagVisitor(List<IElementDescriptor> apis, IApiDescription description, ASTRewrite rewrite) { |
| this.apis = apis; |
| this.description = description; |
| this.rewrite = rewrite; |
| typeStack = new Stack<>(); |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration node) { |
| int type = IApiJavadocTag.TYPE_CLASS; |
| if (node.isInterface()) { |
| type = IApiJavadocTag.TYPE_INTERFACE; |
| } |
| typeStack.push(Integer.valueOf(type)); |
| updateDocNode(findDescriptorByName(node.getName().getFullyQualifiedName(), null), node, getType(), IApiJavadocTag.MEMBER_NONE); |
| return true; |
| } |
| |
| @Override |
| public void endVisit(TypeDeclaration node) { |
| typeStack.pop(); |
| } |
| |
| /** |
| * Returns the kind of type being visited. |
| * |
| * @return <code>TYPE_CLASS</code> or <code>TYPE_INTERFACE</code> |
| */ |
| private int getType() { |
| return (typeStack.peek()).intValue(); |
| } |
| |
| @Override |
| public boolean visit(FieldDeclaration node) { |
| List<VariableDeclarationFragment> fields = node.fragments(); |
| VariableDeclarationFragment fragment = null; |
| for (Iterator<VariableDeclarationFragment> iter = fields.iterator(); iter.hasNext();) { |
| fragment = iter.next(); |
| updateDocNode(findDescriptorByName(fragment.getName().getFullyQualifiedName(), null), node, getType(), IApiJavadocTag.MEMBER_FIELD); |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(MethodDeclaration node) { |
| String signature = Signatures.getMethodSignatureFromNode(node, true); |
| if (signature != null) { |
| updateDocNode(findDescriptorByName(node.getName().getFullyQualifiedName(), signature), node, getType(), IApiJavadocTag.MEMBER_METHOD); |
| } |
| return false; |
| } |
| |
| /** |
| * Updates the specified javadoc node if needed, creates a new doc node |
| * if one is not present |
| * |
| * @param element the element to get API information from |
| * @param docnode the doc node to update |
| * @param type one of <code>CLASS</code> or <code>INTERFACE</code> |
| * @param member one of <code>METHOD</code> or <code>FIELD</code> or |
| * <code>NONE</code> |
| */ |
| private void updateDocNode(IElementDescriptor element, BodyDeclaration body, int type, int member) { |
| if (element != null) { |
| // check for missing tags first, might not need to do any work |
| IApiAnnotations api = description.resolveAnnotations(element); |
| if (api != null) { |
| Javadoc docnode = body.getJavadoc(); |
| AST ast = body.getAST(); |
| boolean newnode = docnode == null; |
| if (docnode == null) { |
| docnode = ast.newJavadoc(); |
| } |
| String[] missingtags = collectMissingTags(api, docnode.tags(), type, member); |
| if (missingtags.length == 0) { |
| return; |
| } else if (newnode) { |
| // we do not want to create a new empty Javadoc node in |
| // the AST if there are no missing tags |
| rewrite.set(body, body.getJavadocProperty(), docnode, null); |
| } |
| ListRewrite lrewrite = rewrite.getListRewrite(docnode, Javadoc.TAGS_PROPERTY); |
| TagElement newtag = null; |
| for (String missingtag : missingtags) { |
| newtag = createNewTagElement(ast, missingtag); |
| lrewrite.insertLast(newtag, null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates a new {@link TagElement} against the specified {@link AST} |
| * and returns it |
| * |
| * @param ast the {@link AST} to create the {@link TagElement} against |
| * @param tagname the name of the new tag |
| * @return a new {@link TagElement} with the given name |
| */ |
| private TagElement createNewTagElement(AST ast, String tagname) { |
| TagElement newtag = ast.newTagElement(); |
| newtag.setTagName(tagname); |
| return newtag; |
| } |
| |
| /** |
| * Collects the missing javadoc tags from based on the given listing of |
| * {@link TagElement}s |
| * |
| * @param api |
| * @param tags |
| * @param type one of <code>CLASS</code> or <code>INTERFACE</code> |
| * @param member one of <code>METHOD</code> or <code>FIELD</code> or |
| * <code>NONE</code> |
| * @return an array of the missing {@link TagElement}s or an empty |
| * array, never <code>null</code> |
| */ |
| private String[] collectMissingTags(IApiAnnotations api, List<TagElement> tags, int type, int member) { |
| int res = api.getRestrictions(); |
| ArrayList<String> missing = new ArrayList<>(); |
| JavadocTagManager jtm = ApiPlugin.getJavadocTagManager(); |
| switch (member) { |
| case IApiJavadocTag.MEMBER_FIELD: |
| if (RestrictionModifiers.isReferenceRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@noreference")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_REFERENCE_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| break; |
| case IApiJavadocTag.MEMBER_METHOD: |
| if (RestrictionModifiers.isReferenceRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@noreference")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_REFERENCE_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| if (RestrictionModifiers.isOverrideRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@nooverride")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_OVERRIDE_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| break; |
| case IApiJavadocTag.MEMBER_NONE: |
| if (RestrictionModifiers.isImplementRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@noimplement")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_IMPLEMENT_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| if (RestrictionModifiers.isInstantiateRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@noinstantiate")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_INSTANTIATE_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| if (RestrictionModifiers.isExtendRestriction(res)) { |
| if (!containsRestrictionTag(tags, "@noextend")) { //$NON-NLS-1$ |
| IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_EXTEND_TAG_ID); |
| missing.add(tag.getCompleteTag(type, member)); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| return missing.toArray(new String[missing.size()]); |
| } |
| |
| /** |
| * Determines if the specified tag appears in the {@link TagElement} |
| * listing given |
| * |
| * @param tags |
| * @param tag |
| * @return true if the listing of {@link TagElement}s contains the given |
| * tag |
| */ |
| private boolean containsRestrictionTag(List<TagElement> tags, String tag) { |
| TagElement tagelement = null; |
| for (int i = 0; i < tags.size(); i++) { |
| tagelement = tags.get(i); |
| if (tag.equals(tagelement.getTagName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Finds the {@link IElementDescriptor} that matches the specified name |
| * and signature |
| * |
| * @param name |
| * @param signature |
| * @return the matching {@link IElementDescriptor} or <code>null</code> |
| */ |
| private IElementDescriptor findDescriptorByName(String name, String signature) { |
| IElementDescriptor desc = null; |
| for (int i = 0; i < apis.size(); i++) { |
| desc = apis.get(i); |
| switch (desc.getElementType()) { |
| case IElementDescriptor.TYPE: { |
| if (((IReferenceTypeDescriptor) desc).getName().equals(name)) { |
| return desc; |
| } |
| break; |
| } |
| case IElementDescriptor.METHOD: { |
| IMethodDescriptor method = (IMethodDescriptor) desc; |
| if (method.getName().equals(name) && method.getSignature().equals(signature)) { |
| return desc; |
| } |
| break; |
| } |
| case IElementDescriptor.FIELD: { |
| if (((IFieldDescriptor) desc).getName().equals(name)) { |
| return desc; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Constructor can not be instantiated directly |
| */ |
| private ApiDescriptionProcessor() { |
| } |
| |
| /** |
| * Parses a component XML into a string. The location may be a jar, |
| * directory containing the component.xml file, or the component.xml file |
| * itself |
| * |
| * @param location root location of the component.xml file, or the |
| * component.xml file itself |
| * @return component XML as a string or <code>null</code> if none |
| * @throws IOException if unable to parse |
| */ |
| public static String serializeComponentXml(File location) { |
| if (location.exists()) { |
| ZipFile jarFile = null; |
| InputStream stream = null; |
| try { |
| String extension = new Path(location.getName()).getFileExtension(); |
| if (extension != null && extension.equals("jar") && location.isFile()) { //$NON-NLS-1$ |
| jarFile = new ZipFile(location, ZipFile.OPEN_READ); |
| ZipEntry manifestEntry = jarFile.getEntry(IApiCoreConstants.COMPONENT_XML_NAME); |
| if (manifestEntry != null) { |
| stream = jarFile.getInputStream(manifestEntry); |
| } |
| } else if (location.isDirectory()) { |
| File file = new File(location, IApiCoreConstants.COMPONENT_XML_NAME); |
| if (file.exists()) { |
| stream = new FileInputStream(file); |
| } |
| } else if (location.isFile()) { |
| if (location.getName().equals(IApiCoreConstants.COMPONENT_XML_NAME)) { |
| stream = new FileInputStream(location); |
| } |
| } |
| if (stream != null) { |
| return new String(Util.getInputStreamAsCharArray(stream, StandardCharsets.UTF_8)); |
| } |
| } catch (IOException e) { |
| ApiPlugin.log(e); |
| } finally { |
| try { |
| if (stream != null) { |
| stream.close(); |
| } |
| } catch (IOException e) { |
| ApiPlugin.log(e); |
| } |
| try { |
| if (jarFile != null) { |
| jarFile.close(); |
| } |
| } catch (IOException e) { |
| ApiPlugin.log(e); |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * This method updates the javadoc for members of the specified java source |
| * files with information retrieved from the the specified component.xml |
| * file. |
| * |
| * @param project the java project to update |
| * @param componentxml the component.xml file to update from |
| * @param collector |
| * @throws CoreException |
| * @throws IOException |
| */ |
| public static void collectTagUpdates(IJavaProject project, File componentxml, Map<IFile, Set<TextEdit>> collector) throws CoreException, IOException { |
| IApiDescription description = new ApiDescription(null); |
| annotateApiSettings(project, description, serializeComponentXml(componentxml)); |
| // visit the types |
| DescriptionVisitor visitor = new DescriptionVisitor(project, description, collector); |
| description.accept(visitor, null); |
| IStatus status = visitor.getStatus(); |
| if (!status.isOK()) { |
| throw new CoreException(status); |
| } |
| } |
| |
| /** |
| * Given the type, the parent type descriptor and an annotated description, |
| * update the javadoc comments for the type and all members of the type |
| * found in the description. |
| * |
| * @param type |
| * @param desc |
| * @param description |
| * @param members members with API annotations |
| * @param collector |
| * @throws CoreException |
| * @throws BadLocationException |
| */ |
| static void processTagUpdates(IType type, IReferenceTypeDescriptor desc, IApiDescription description, List<IElementDescriptor> members, Map<IFile, Set<TextEdit>> collector) throws CoreException, BadLocationException { |
| ASTParser parser = ASTParser.newParser(AST.JLS_Latest); |
| ICompilationUnit cunit = type.getCompilationUnit(); |
| if (cunit != null) { |
| parser.setSource(cunit); |
| Map<String, String> options = cunit.getJavaProject().getOptions(true); |
| options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); |
| parser.setCompilerOptions(options); |
| CompilationUnit cast = (CompilationUnit) parser.createAST(new NullProgressMonitor()); |
| cast.recordModifications(); |
| ASTRewrite rewrite = ASTRewrite.create(cast.getAST()); |
| ASTTagVisitor visitor = new ASTTagVisitor(members, description, rewrite); |
| cast.accept(visitor); |
| ITextFileBufferManager bm = FileBuffers.getTextFileBufferManager(); |
| IPath path = cast.getJavaElement().getPath(); |
| try { |
| bm.connect(path, LocationKind.IFILE, null); |
| ITextFileBuffer tfb = bm.getTextFileBuffer(path, LocationKind.IFILE); |
| IDocument document = tfb.getDocument(); |
| TextEdit edit = rewrite.rewriteAST(document, null); |
| if (edit.getChildrenSize() > 0 || edit.getLength() != 0) { |
| IFile file = (IFile) cunit.getUnderlyingResource(); |
| Set<TextEdit> edits = collector.get(file); |
| if (edits == null) { |
| edits = new HashSet<>(3); |
| collector.put(file, edits); |
| } |
| edits.add(edit); |
| } |
| } finally { |
| bm.disconnect(path, LocationKind.IFILE, null); |
| } |
| } |
| } |
| |
| /** |
| * Throws an exception with the given message and underlying exception. |
| * |
| * @param message error message |
| * @param exception underlying exception, or <code>null</code> |
| * @throws CoreException |
| */ |
| private static void abort(String message, Throwable exception) throws CoreException { |
| IStatus status = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, message, exception); |
| throw new CoreException(status); |
| } |
| |
| /** |
| * Parses the given xml document (in string format), and annotates the |
| * specified {@link IApiDescription} with {@link IPackageDescriptor}s, |
| * {@link IReferenceTypeDescriptor}s, {@link IMethodDescriptor}s and |
| * {@link IFieldDescriptor}s. |
| * |
| * @param settings API settings to annotate |
| * @param xml XML used to generate settings |
| * @throws CoreException |
| */ |
| public static void annotateApiSettings(IJavaProject project, IApiDescription settings, String xml) throws CoreException { |
| Element root = null; |
| try { |
| root = Util.parseDocument(xml); |
| } catch (CoreException ce) { |
| abort("Failed to parse API description xml file", ce); //$NON-NLS-1$ |
| } |
| if (!root.getNodeName().equals(IApiXmlConstants.ELEMENT_COMPONENT)) { |
| abort(ScannerMessages.ComponentXMLScanner_0, null); |
| } |
| String version = root.getAttribute(IApiXmlConstants.ATTR_VERSION); |
| ApiDescription desc = (ApiDescription) settings; |
| desc.setEmbeddedVersion(version); |
| // TODO for now this compares to 1.2, since the change from 1.1 -> 1.2 |
| // denotes the |
| // @noextend change, not 1.1 -> current version |
| boolean earlierversion = desc.compareEmbeddedVersionTo("1.2") == 1; //$NON-NLS-1$ |
| NodeList packages = root.getElementsByTagName(IApiXmlConstants.ELEMENT_PACKAGE); |
| NodeList types = null; |
| IPackageDescriptor packdesc = null; |
| Element type = null; |
| for (int i = 0; i < packages.getLength(); i++) { |
| Element pkg = (Element) packages.item(i); |
| // package visibility comes from the MANIFEST.MF |
| String pkgName = pkg.getAttribute(IApiXmlConstants.ATTR_NAME); |
| packdesc = Factory.packageDescriptor(pkgName); |
| types = pkg.getElementsByTagName(IApiXmlConstants.ELEMENT_TYPE); |
| for (int j = 0; j < types.getLength(); j++) { |
| type = (Element) types.item(j); |
| String name = type.getAttribute(IApiXmlConstants.ATTR_NAME); |
| if (name.length() == 0) { |
| abort("Missing type name", null); //$NON-NLS-1$ |
| } |
| IReferenceTypeDescriptor typedesc = packdesc.getType(name); |
| annotateDescriptor(project, settings, typedesc, type, earlierversion); |
| annotateMethodSettings(project, settings, typedesc, type, earlierversion); |
| annotateFieldSettings(project, settings, typedesc, type, earlierversion); |
| } |
| } |
| } |
| |
| /** |
| * Annotates the backing {@link IApiDescription} from the given |
| * {@link Element}, by adding the visibility and restriction attributes to |
| * the specified {@link IElementDescriptor} |
| * |
| * @param settings the settings to annotate |
| * @param descriptor the current descriptor context |
| * @param element the current element to annotate from |
| * @param earlierversion if the version read from XML is older than the |
| * current tooling version |
| */ |
| private static void annotateDescriptor(IJavaProject project, IApiDescription settings, IElementDescriptor descriptor, Element element, boolean earlierversion) { |
| int typeVis = getVisibility(element); |
| if (typeVis != -1) { |
| settings.setVisibility(descriptor, typeVis); |
| } |
| settings.setRestrictions(descriptor, getRestrictions(project, element, descriptor, earlierversion)); |
| } |
| |
| /** |
| * Returns restriction settings described in the given element. |
| * |
| * @param project the {@link IJavaProject} context |
| * @param element XML element |
| * @param descriptor the {@link IElementDescriptor} to get the restrictions |
| * for |
| * @param earlierversion if the version read from XML is older than the |
| * current tooling version |
| * @return restriction settings |
| */ |
| private static int getRestrictions(final IJavaProject project, final Element element, final IElementDescriptor descriptor, boolean earlierversion) { |
| int res = RestrictionModifiers.NO_RESTRICTIONS; |
| if (element.hasAttribute(IApiXmlConstants.ATTR_RESTRICTIONS)) { |
| res = Integer.parseInt(element.getAttribute(IApiXmlConstants.ATTR_RESTRICTIONS)); |
| } else { |
| switch (descriptor.getElementType()) { |
| case IElementDescriptor.FIELD: { |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_REFERENCE, RestrictionModifiers.NO_REFERENCE, res); |
| break; |
| } |
| case IElementDescriptor.METHOD: { |
| IMethodDescriptor method = (IMethodDescriptor) descriptor; |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_REFERENCE, RestrictionModifiers.NO_REFERENCE, res); |
| if (!method.isConstructor()) { |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_OVERRIDE, RestrictionModifiers.NO_OVERRIDE, res); |
| } |
| break; |
| } |
| case IElementDescriptor.TYPE: { |
| IReferenceTypeDescriptor rtype = (IReferenceTypeDescriptor) descriptor; |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_IMPLEMENT, RestrictionModifiers.NO_IMPLEMENT, res); |
| if (earlierversion && RestrictionModifiers.isImplementRestriction(res)) { |
| res |= RestrictionModifiers.NO_EXTEND; |
| } |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_EXTEND, RestrictionModifiers.NO_EXTEND, res); |
| if (!RestrictionModifiers.isExtendRestriction(res)) { |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_SUBCLASS, RestrictionModifiers.NO_EXTEND, res); |
| } |
| res = annotateRestriction(element, IApiXmlConstants.ATTR_INSTANTIATE, RestrictionModifiers.NO_INSTANTIATE, res); |
| IType type = null; |
| if (project != null) { |
| try { |
| type = project.findType(rtype.getQualifiedName()); |
| IType typeInProject = Util.getTypeInSameJavaProject(type, rtype.getQualifiedName(), project); |
| if (typeInProject != null) { |
| type = typeInProject; |
| } |
| } catch (JavaModelException e) { |
| ApiPlugin.log("Failed to find type for " + rtype.getQualifiedName(), e); //$NON-NLS-1$ |
| } |
| if (type != null) { |
| try { |
| if (Flags.isInterface(type.getFlags())) { |
| res &= ~RestrictionModifiers.NO_INSTANTIATE; |
| } else { |
| res &= ~RestrictionModifiers.NO_IMPLEMENT; |
| if (Flags.isFinal(type.getFlags())) { |
| res &= ~RestrictionModifiers.NO_EXTEND; |
| } |
| if (Flags.isAbstract(type.getFlags())) { |
| res &= ~RestrictionModifiers.NO_INSTANTIATE; |
| } |
| } |
| } catch (JavaModelException e) { |
| ApiPlugin.log("Failed to read type flags for " + type, e); //$NON-NLS-1$ |
| } |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * Tests if the given restriction exists for the given element and returns |
| * an updated restrictions flag. |
| * |
| * @param element XML element |
| * @param name attribute to test |
| * @param flag bit mask for attribute |
| * @param res flag to combine with |
| * @return updated flags |
| */ |
| private static int annotateRestriction(Element element, String name, int flag, int res) { |
| String value = element.getAttribute(name); |
| int lres = res; |
| if (value.length() > 0) { |
| if (!Boolean.parseBoolean(value)) { |
| lres = res | flag; |
| } |
| } |
| return lres; |
| } |
| |
| /** |
| * Returns visibility settings described in the given element or -1 if none. |
| * |
| * @param element XML element |
| * @return visibility settings or -1 if none |
| */ |
| private static int getVisibility(Element element) { |
| String attribute = element.getAttribute(IApiXmlConstants.ATTR_VISIBILITY); |
| if (attribute != null && attribute.isEmpty()) { |
| return -1; |
| } |
| try { |
| return Integer.parseInt(attribute); |
| } catch (NumberFormatException nfe) { |
| if ("API".equals(attribute)) { //$NON-NLS-1$ |
| return VisibilityModifiers.API; |
| } |
| if ("PRIVATE".equals(attribute)) { //$NON-NLS-1$ |
| return VisibilityModifiers.PRIVATE; |
| } |
| if ("PRIVATE_PERMISSABLE".equals(attribute)) { //$NON-NLS-1$ |
| return VisibilityModifiers.PRIVATE_PERMISSIBLE; |
| } |
| if ("SPI".equals(attribute)) { //$NON-NLS-1$ |
| return VisibilityModifiers.SPI; |
| } |
| return -1; |
| } |
| } |
| |
| /** |
| * Annotates the supplied {@link IApiDescription} from all of the field |
| * elements that are direct children of the specified {@link Element}. |
| * {@link IFieldDescriptor}s are created as needed and added as children of |
| * the specified {@link IReferenceTypeDescriptor}. |
| * |
| * @param settings the {@link IApiDescription} to add the new |
| * {@link IFieldDescriptor} to |
| * @param typedesc the containing type descriptor for this field |
| * @param type the parent {@link Element} |
| * @param earlierversion if the version read from XML is older than the |
| * current tooling version |
| * @throws CoreException |
| */ |
| private static void annotateFieldSettings(IJavaProject project, IApiDescription settings, IReferenceTypeDescriptor typedesc, Element type, boolean earlierversion) throws CoreException { |
| NodeList fields = type.getElementsByTagName(IApiXmlConstants.ELEMENT_FIELD); |
| Element field = null; |
| IFieldDescriptor fielddesc = null; |
| String name = null; |
| for (int i = 0; i < fields.getLength(); i++) { |
| field = (Element) fields.item(i); |
| name = field.getAttribute(IApiXmlConstants.ATTR_NAME); |
| if (name == null) { |
| abort(ScannerMessages.ComponentXMLScanner_1, null); |
| } |
| fielddesc = typedesc.getField(name); |
| annotateDescriptor(project, settings, fielddesc, field, earlierversion); |
| } |
| } |
| |
| /** |
| * Annotates the supplied {@link IApiDescription} from all of the method |
| * elements that are direct children of the specified {@link Element}. |
| * {@link IMethodDescriptor}s are created as needed and added as children of |
| * the specified {@link IReferenceTypeDescriptor}. |
| * |
| * @param settings the {@link IApiDescription} to add the new |
| * {@link IMethodDescriptor} to |
| * @param typedesc the containing type descriptor for this method |
| * @param type the parent {@link Element} |
| * @param earlierversion if the version read from XML is older than the |
| * current tooling version |
| * @throws CoreException |
| */ |
| private static void annotateMethodSettings(IJavaProject project, IApiDescription settings, IReferenceTypeDescriptor typedesc, Element type, boolean earlierversion) throws CoreException { |
| NodeList methods = type.getElementsByTagName(IApiXmlConstants.ELEMENT_METHOD); |
| Element method = null; |
| IMethodDescriptor methoddesc = null; |
| String name, signature; |
| for (int i = 0; i < methods.getLength(); i++) { |
| method = (Element) methods.item(i); |
| name = method.getAttribute(IApiXmlConstants.ATTR_NAME); |
| if (name == null) { |
| abort(ScannerMessages.ComponentXMLScanner_2, null); |
| } |
| signature = method.getAttribute(IApiXmlConstants.ATTR_SIGNATURE); |
| if (signature == null) { |
| abort(ScannerMessages.ComponentXMLScanner_3, null); |
| } |
| // old files might use '.' instead of '/' |
| signature = signature.replace('.', '/'); |
| methoddesc = typedesc.getMethod(name, signature); |
| annotateDescriptor(project, settings, methoddesc, method, earlierversion); |
| } |
| } |
| } |