| 1 | /******************************************************************************* | 
| 2 |  * Copyright (c) 2007, 2009 IBM Corporation and others. | 
| 3 |  * All rights reserved. This program and the accompanying materials | 
| 4 |  * are made available under the terms of the Eclipse Public License v1.0 | 
| 5 |  * which accompanies this distribution, and is available at | 
| 6 |  * http://www.eclipse.org/legal/epl-v10.html | 
| 7 |  * | 
| 8 |  * Contributors: | 
| 9 |  *     IBM Corporation - initial API and implementation | 
| 10 |  *******************************************************************************/ | 
| 11 | package org.eclipse.pde.api.tools.internal; | 
| 12 |   | 
| 13 | import java.io.File; | 
| 14 | import java.io.FileInputStream; | 
| 15 | import java.io.IOException; | 
| 16 | import java.io.InputStream; | 
| 17 | import java.util.ArrayList; | 
| 18 | import java.util.HashSet; | 
| 19 | import java.util.Iterator; | 
| 20 | import java.util.List; | 
| 21 | import java.util.Map; | 
| 22 | import java.util.Stack; | 
| 23 | import java.util.zip.ZipEntry; | 
| 24 | import java.util.zip.ZipFile; | 
| 25 |   | 
| 26 | import org.eclipse.core.filebuffers.FileBuffers; | 
| 27 | import org.eclipse.core.filebuffers.ITextFileBuffer; | 
| 28 | import org.eclipse.core.filebuffers.ITextFileBufferManager; | 
| 29 | import org.eclipse.core.filebuffers.LocationKind; | 
| 30 | import org.eclipse.core.resources.IFile; | 
| 31 | import org.eclipse.core.runtime.CoreException; | 
| 32 | import org.eclipse.core.runtime.IPath; | 
| 33 | import org.eclipse.core.runtime.IStatus; | 
| 34 | import org.eclipse.core.runtime.MultiStatus; | 
| 35 | import org.eclipse.core.runtime.NullProgressMonitor; | 
| 36 | import org.eclipse.core.runtime.Path; | 
| 37 | import org.eclipse.core.runtime.Status; | 
| 38 | import org.eclipse.jdt.core.Flags; | 
| 39 | import org.eclipse.jdt.core.ICompilationUnit; | 
| 40 | import org.eclipse.jdt.core.IJavaProject; | 
| 41 | import org.eclipse.jdt.core.IType; | 
| 42 | import org.eclipse.jdt.core.JavaCore; | 
| 43 | import org.eclipse.jdt.core.JavaModelException; | 
| 44 | import org.eclipse.jdt.core.dom.AST; | 
| 45 | import org.eclipse.jdt.core.dom.ASTParser; | 
| 46 | import org.eclipse.jdt.core.dom.ASTVisitor; | 
| 47 | import org.eclipse.jdt.core.dom.BodyDeclaration; | 
| 48 | import org.eclipse.jdt.core.dom.CompilationUnit; | 
| 49 | import org.eclipse.jdt.core.dom.FieldDeclaration; | 
| 50 | import org.eclipse.jdt.core.dom.Javadoc; | 
| 51 | import org.eclipse.jdt.core.dom.MethodDeclaration; | 
| 52 | import org.eclipse.jdt.core.dom.TagElement; | 
| 53 | import org.eclipse.jdt.core.dom.TypeDeclaration; | 
| 54 | import org.eclipse.jdt.core.dom.VariableDeclarationFragment; | 
| 55 | import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; | 
| 56 | import org.eclipse.jdt.core.dom.rewrite.ListRewrite; | 
| 57 | import org.eclipse.jface.text.BadLocationException; | 
| 58 | import org.eclipse.jface.text.IDocument; | 
| 59 | import org.eclipse.pde.api.tools.internal.provisional.ApiDescriptionVisitor; | 
| 60 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; | 
| 61 | import org.eclipse.pde.api.tools.internal.provisional.Factory; | 
| 62 | import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations; | 
| 63 | import org.eclipse.pde.api.tools.internal.provisional.IApiDescription; | 
| 64 | import org.eclipse.pde.api.tools.internal.provisional.IApiJavadocTag; | 
| 65 | import org.eclipse.pde.api.tools.internal.provisional.RestrictionModifiers; | 
| 66 | import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; | 
| 67 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; | 
| 68 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IFieldDescriptor; | 
| 69 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; | 
| 70 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IPackageDescriptor; | 
| 71 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; | 
| 72 | import org.eclipse.pde.api.tools.internal.provisional.scanner.ScannerMessages; | 
| 73 | import org.eclipse.pde.api.tools.internal.util.Signatures; | 
| 74 | import org.eclipse.pde.api.tools.internal.util.Util; | 
| 75 | import org.eclipse.text.edits.TextEdit; | 
| 76 | import org.w3c.dom.Element; | 
| 77 | import org.w3c.dom.NodeList; | 
| 78 |   | 
| 79 | /** | 
| 80 |  * Provides tools for scanning/loading/parsing component.xml files. | 
| 81 |  *  | 
| 82 |  * @since 1.0.0 | 
| 83 |  */ | 
| 84 | public class ApiDescriptionProcessor { | 
| 85 |   | 
| 86 |         /** | 
| 87 |          * Visits each type, collecting all members before processing the type. | 
| 88 |          */ | 
| 89 |         static class DescriptionVisitor extends ApiDescriptionVisitor { | 
| 90 |                  | 
| 91 |                 /** | 
| 92 |                  * The API description associated with the project.  | 
| 93 |                  */ | 
| 94 |                 private IApiDescription apiDescription = null; | 
| 95 |                  | 
| 96 |                 /** | 
| 97 |                  * Java project to resolve types in | 
| 98 |                  */ | 
| 99 |                 private IJavaProject project = null; | 
| 100 |                  | 
| 101 |                 /** | 
| 102 |                  * List to collect text edits | 
| 103 |                  */ | 
| 104 |                 private Map fCollector = null; | 
| 105 |                  | 
| 106 |                 /** | 
| 107 |                  * Members collected from current type. | 
| 108 |                  */ | 
| 109 |                 private List members = new ArrayList(); | 
| 110 |                  | 
| 111 |                 /** | 
| 112 |                  * List of exception statuses that occurred, or <code>null</code> if none. | 
| 113 |                  */ | 
| 114 |                 private List exceptions = null; | 
| 115 |                  | 
| 116 |                 /** | 
| 117 |                  * Constructs a new visitor to collect tag updates in a java project. | 
| 118 |                  *  | 
| 119 |                  * @param jp project to update | 
| 120 |                  * @param cd project's API description | 
| 121 |                  * @param collector collection to place text edits into | 
| 122 |                  */ | 
| 123 |                 DescriptionVisitor(IJavaProject jp, IApiDescription cd, Map collector) { | 
| 124 |                         project = jp; | 
| 125 |                         apiDescription = cd; | 
| 126 |                         fCollector = collector; | 
| 127 |                 } | 
| 128 |                  | 
| 129 |                 /* (non-Javadoc) | 
| 130 |                  * @see org.eclipse.pde.api.tools.model.component.ApiDescriptionVisitor#visitElement(org.eclipse.pde.api.tools.model.component.IElementDescriptor, java.lang.String, org.eclipse.pde.api.tools.model.IApiAnnotations) | 
| 131 |                  */ | 
| 132 |                 public boolean visitElement(IElementDescriptor element, IApiAnnotations description) { | 
| 133 |                         switch(element.getElementType()) { | 
| 134 |                                 case IElementDescriptor.PACKAGE: { | 
| 135 |                                         return true; | 
| 136 |                                 } | 
| 137 |                                 case IElementDescriptor.TYPE: { | 
| 138 |                                         members.clear();  | 
| 139 |                                         members.add(element); | 
| 140 |                                         return true; | 
| 141 |                                 } | 
| 142 |                                 default: { | 
| 143 |                                         members.add(element); | 
| 144 |                                 } | 
| 145 |                         } | 
| 146 |                         return false; | 
| 147 |                 } | 
| 148 |   | 
| 149 |                 /* (non-Javadoc) | 
| 150 |                  * @see org.eclipse.pde.api.tools.model.component.ApiDescriptionVisitor#endVisitElement(org.eclipse.pde.api.tools.model.component.IElementDescriptor, java.lang.String, org.eclipse.pde.api.tools.model.IApiAnnotations) | 
| 151 |                  */ | 
| 152 |                 public void endVisitElement(IElementDescriptor element, IApiAnnotations description) { | 
| 153 |                         if (element.getElementType() == IElementDescriptor.TYPE) { | 
| 154 |                                 IReferenceTypeDescriptor refType = (IReferenceTypeDescriptor) element; | 
| 155 |                                 try { | 
| 156 |                                         IReferenceTypeDescriptor topLevelType = refType.getEnclosingType(); | 
| 157 |                                         while (topLevelType != null) { | 
| 158 |                                                 refType = topLevelType; | 
| 159 |                                                 topLevelType = refType.getEnclosingType(); | 
| 160 |                                         } | 
| 161 |                                         IType type = project.findType(refType.getQualifiedName(), new NullProgressMonitor()); | 
| 162 |                                         if(type != null) { | 
| 163 |                                                 processTagUpdates(type, refType, apiDescription, members, fCollector); | 
| 164 |                                         } | 
| 165 |                                 } catch (CoreException e) { | 
| 166 |                                         addStatus(e.getStatus()); | 
| 167 |                                 } catch (BadLocationException e) { | 
| 168 |                                         addStatus(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID,  | 
| 169 |                                                         ScannerMessages.ComponentXMLScanner_0 + element.toString(),e)); | 
| 170 |                                 } | 
| 171 |                                 members.clear(); | 
| 172 |                         } | 
| 173 |                 } | 
| 174 |                  | 
| 175 |                 /** | 
| 176 |                  * Adds a status to the current listing of messages | 
| 177 |                  * @param status | 
| 178 |                  */ | 
| 179 |                 private void addStatus(IStatus status) { | 
| 180 |                         if (exceptions == null) { | 
| 181 |                                 exceptions = new ArrayList(); | 
| 182 |                         } | 
| 183 |                         exceptions.add(status); | 
| 184 |                 } | 
| 185 |                  | 
| 186 |                 /** | 
| 187 |                  * Returns the status of processing the project. Status is OK | 
| 188 |                  * if no errors occurred. | 
| 189 |                  *  | 
| 190 |                  * @return status | 
| 191 |                  */ | 
| 192 |                 public IStatus getStatus() { | 
| 193 |                         if (exceptions == null) { | 
| 194 |                                 return Status.OK_STATUS; | 
| 195 |                         } | 
| 196 |                         return new MultiStatus(ApiPlugin.PLUGIN_ID, 0,  | 
| 197 |                                         (IStatus[]) exceptions.toArray(new IStatus[exceptions.size()]), | 
| 198 |                                         ScannerMessages.ComponentXMLScanner_1, null); | 
| 199 |                 } | 
| 200 |                  | 
| 201 |         } | 
| 202 |          | 
| 203 |         /** | 
| 204 |          * Visitor used for finding the nodes to update the javadoc tags for, if needed | 
| 205 |          */ | 
| 206 |         static class ASTTagVisitor extends ASTVisitor { | 
| 207 |                 private List apis = null; | 
| 208 |                 private IApiDescription description = null; | 
| 209 |                 private ASTRewrite rewrite = null; | 
| 210 |                 private Stack typeStack; | 
| 211 |                 /** | 
| 212 |                  * Constructor | 
| 213 |                  * @param APIs a listing of {@link IElementDescriptor}s that we care about for this visit | 
| 214 |                  */ | 
| 215 |                 public ASTTagVisitor(List apis, IApiDescription description, ASTRewrite rewrite) { | 
| 216 |                         this.apis = apis; | 
| 217 |                         this.description = description; | 
| 218 |                         this.rewrite = rewrite; | 
| 219 |                         typeStack = new Stack(); | 
| 220 |                 } | 
| 221 |   | 
| 222 |                 /* (non-Javadoc) | 
| 223 |                  * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.TypeDeclaration) | 
| 224 |                  */ | 
| 225 |                 public boolean visit(TypeDeclaration node) { | 
| 226 |                         int type = IApiJavadocTag.TYPE_CLASS; | 
| 227 |                         if (node.isInterface()) { | 
| 228 |                                 type = IApiJavadocTag.TYPE_INTERFACE; | 
| 229 |                         } | 
| 230 |                         typeStack.push(new Integer(type)); | 
| 231 |                         updateDocNode(findDescriptorByName(node.getName().getFullyQualifiedName(), null), node, getType(), IApiJavadocTag.MEMBER_NONE); | 
| 232 |                         return true; | 
| 233 |                 } | 
| 234 |                  | 
| 235 |                 /* (non-Javadoc) | 
| 236 |                  * @see org.eclipse.jdt.core.dom.ASTVisitor#endVisit(org.eclipse.jdt.core.dom.TypeDeclaration) | 
| 237 |                  */ | 
| 238 |                 public void endVisit(TypeDeclaration node) { | 
| 239 |                         typeStack.pop(); | 
| 240 |                 } | 
| 241 |                  | 
| 242 |                 /** | 
| 243 |                  * Returns the kind of type being visited. | 
| 244 |                  *  | 
| 245 |                  * @return <code>TYPE_CLASS</code> or <code>TYPE_INTERFACE</code> | 
| 246 |                  */ | 
| 247 |                 private int getType() { | 
| 248 |                         return ((Integer)(typeStack.peek())).intValue(); | 
| 249 |                 } | 
| 250 |   | 
| 251 |                 /* (non-Javadoc) | 
| 252 |                  * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.FieldDeclaration) | 
| 253 |                  */ | 
| 254 |                 public boolean visit(FieldDeclaration node) { | 
| 255 |                         List fields = node.fragments(); | 
| 256 |                         VariableDeclarationFragment fragment = null; | 
| 257 |                         for(Iterator iter = fields.iterator(); iter.hasNext();) { | 
| 258 |                                 fragment = (VariableDeclarationFragment) iter.next(); | 
| 259 |                                 updateDocNode(findDescriptorByName(fragment.getName().getFullyQualifiedName(), null), node, getType(), IApiJavadocTag.MEMBER_FIELD); | 
| 260 |                         } | 
| 261 |                         return false; | 
| 262 |                 } | 
| 263 |                 /* (non-Javadoc) | 
| 264 |                  * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.MethodDeclaration) | 
| 265 |                  */ | 
| 266 |                 public boolean visit(MethodDeclaration node) { | 
| 267 |                         String signature = Signatures.getMethodSignatureFromNode(node); | 
| 268 |                         if(signature != null) { | 
| 269 |                                 updateDocNode(findDescriptorByName(node.getName().getFullyQualifiedName(), signature), node, getType(), IApiJavadocTag.MEMBER_METHOD); | 
| 270 |                         } | 
| 271 |                         return false; | 
| 272 |                 } | 
| 273 |                 /** | 
| 274 |                  * Updates the specified javadoc node if needed, creates a new doc node if one is not present | 
| 275 |                  * @param element the element to get API information from  | 
| 276 |                  * @param docnode the doc node to update | 
| 277 |                  * @param type one of <code>CLASS</code> or <code>INTERFACE</code> | 
| 278 |               * @param member one of <code>METHOD</code> or <code>FIELD</code> or <code>NONE</code> | 
| 279 |                  */ | 
| 280 |                 private void updateDocNode(IElementDescriptor element, BodyDeclaration body, int type, int member) { | 
| 281 |                         if(element != null) { | 
| 282 |                                 //check for missing tags first, might not need to do any work | 
| 283 |                                 IApiAnnotations api = description.resolveAnnotations(element); | 
| 284 |                                 if(api != null) { | 
| 285 |                                         Javadoc docnode = body.getJavadoc(); | 
| 286 |                                         AST ast = body.getAST(); | 
| 287 |                                         boolean newnode = docnode == null; | 
| 288 |                                         if(docnode == null) { | 
| 289 |                                                 docnode = ast.newJavadoc(); | 
| 290 |                                         } | 
| 291 |                                         String[] missingtags = collectMissingTags(api, docnode.tags(), type, member); | 
| 292 |                                         if(missingtags.length == 0) { | 
| 293 |                                                 return; | 
| 294 |                                         } | 
| 295 |                                         else if(newnode) { | 
| 296 |                                                 //we do not want to create a new empty Javadoc node in | 
| 297 |                                                 //the AST if there are no missing tags | 
| 298 |                                                 rewrite.set(body, body.getJavadocProperty(), docnode, null); | 
| 299 |                                         } | 
| 300 |                                         ListRewrite lrewrite = rewrite.getListRewrite(docnode, Javadoc.TAGS_PROPERTY); | 
| 301 |                                         TagElement newtag = null; | 
| 302 |                                         for(int i = 0; i < missingtags.length; i++) { | 
| 303 |                                                 newtag = createNewTagElement(ast, missingtags[i]); | 
| 304 |                                                 lrewrite.insertLast(newtag, null); | 
| 305 |                                         } | 
| 306 |                                 } | 
| 307 |                         } | 
| 308 |                 } | 
| 309 |                 /** | 
| 310 |                  * Creates a new {@link TagElement} against the specified {@link AST} and returns it | 
| 311 |                  * @param ast the {@link AST} to create the {@link TagElement} against | 
| 312 |                  * @param tagname the name of the new tag | 
| 313 |                  * @return a new {@link TagElement} with the given name | 
| 314 |                  */ | 
| 315 |                 private TagElement createNewTagElement(AST ast, String tagname) { | 
| 316 |                         TagElement newtag = ast.newTagElement(); | 
| 317 |                         newtag.setTagName(tagname); | 
| 318 |                         return newtag; | 
| 319 |                 } | 
| 320 |                 /** | 
| 321 |                  * Collects the missing javadoc tags from based on the given listing of {@link TagElement}s | 
| 322 |                  * @param api | 
| 323 |                  * @param tags | 
| 324 |                  * @param type one of <code>CLASS</code> or <code>INTERFACE</code> | 
| 325 |                  * @param member one of <code>METHOD</code> or <code>FIELD</code> or <code>NONE</code> | 
| 326 |                  * @return an array of the missing {@link TagElement}s or an empty array, never <code>null</code> | 
| 327 |                  */ | 
| 328 |                 private String[] collectMissingTags(IApiAnnotations api, List tags, int type, int member) { | 
| 329 |                         int res = api.getRestrictions(); | 
| 330 |                         ArrayList missing = new ArrayList(); | 
| 331 |                         JavadocTagManager jtm = ApiPlugin.getJavadocTagManager(); | 
| 332 |                         switch(member) { | 
| 333 |                                 case IApiJavadocTag.MEMBER_FIELD : | 
| 334 |                                         if(RestrictionModifiers.isReferenceRestriction(res)) { | 
| 335 |                                                 if(!containsRestrictionTag(tags, "@noreference")) { //$NON-NLS-1$ | 
| 336 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_REFERENCE_TAG_ID); | 
| 337 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 338 |                                                 } | 
| 339 |                                         } | 
| 340 |                                         break; | 
| 341 |                                 case IApiJavadocTag.MEMBER_METHOD : | 
| 342 |                                         if(RestrictionModifiers.isReferenceRestriction(res)) { | 
| 343 |                                                 if(!containsRestrictionTag(tags, "@noreference")) { //$NON-NLS-1$ | 
| 344 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_REFERENCE_TAG_ID); | 
| 345 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 346 |                                                 } | 
| 347 |                                         } | 
| 348 |                                         if(RestrictionModifiers.isOverrideRestriction(res)) { | 
| 349 |                                                 if(!containsRestrictionTag(tags, "@nooverride")) { //$NON-NLS-1$ | 
| 350 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_OVERRIDE_TAG_ID); | 
| 351 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 352 |                                                 } | 
| 353 |                                         } | 
| 354 |                                         break; | 
| 355 |                                 case IApiJavadocTag.MEMBER_NONE : | 
| 356 |                                         if(RestrictionModifiers.isImplementRestriction(res)) { | 
| 357 |                                                 if(!containsRestrictionTag(tags, "@noimplement")) { //$NON-NLS-1$ | 
| 358 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_IMPLEMENT_TAG_ID); | 
| 359 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 360 |                                                 } | 
| 361 |                                         } | 
| 362 |                                         if(RestrictionModifiers.isInstantiateRestriction(res)) { | 
| 363 |                                                 if(!containsRestrictionTag(tags, "@noinstantiate")) { //$NON-NLS-1$ | 
| 364 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_INSTANTIATE_TAG_ID); | 
| 365 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 366 |                                                 } | 
| 367 |                                         } | 
| 368 |                                         if(RestrictionModifiers.isExtendRestriction(res)) { | 
| 369 |                                                 if(!containsRestrictionTag(tags, "@noextend")) { //$NON-NLS-1$ | 
| 370 |                                                         IApiJavadocTag tag = jtm.getTag(IApiJavadocTag.NO_EXTEND_TAG_ID); | 
| 371 |                                                         missing.add(tag.getCompleteTag(type, member)); | 
| 372 |                                                 } | 
| 373 |                                         } | 
| 374 |                         } | 
| 375 |                         return (String[]) missing.toArray(new String[missing.size()]); | 
| 376 |                 } | 
| 377 |                 /** | 
| 378 |                  * Determines if the specified tag appears in the {@link TagElement} listing given | 
| 379 |                  * @param tags | 
| 380 |                  * @param tag | 
| 381 |                  * @return true if the listing of {@link TagElement}s contains the given tag | 
| 382 |                  */ | 
| 383 |                 private boolean containsRestrictionTag(List tags, String tag) { | 
| 384 |                         TagElement tagelement = null; | 
| 385 |                         for(int i = 0; i < tags.size(); i++) { | 
| 386 |                                 tagelement = (TagElement) tags.get(i); | 
| 387 |                                 if(tag.equals(tagelement.getTagName())) { | 
| 388 |                                         return true; | 
| 389 |                                 } | 
| 390 |                         } | 
| 391 |                         return false; | 
| 392 |                 } | 
| 393 |                 /** | 
| 394 |                  * Finds the {@link IElementDescriptor} that matches the specified name and signature | 
| 395 |                  * @param name | 
| 396 |                  * @param signature | 
| 397 |                  * @return the matching {@link IElementDescriptor} or <code>null</code>  | 
| 398 |                  */ | 
| 399 |                 private IElementDescriptor findDescriptorByName(String name, String signature) { | 
| 400 |                         IElementDescriptor desc = null; | 
| 401 |                         for(int i = 0; i < apis.size(); i++) { | 
| 402 |                                 desc = (IElementDescriptor) apis.get(i); | 
| 403 |                                 switch(desc.getElementType()) { | 
| 404 |                                         case IElementDescriptor.TYPE: { | 
| 405 |                                                 if(((IReferenceTypeDescriptor)desc).getName().equals(name)) { | 
| 406 |                                                         return desc; | 
| 407 |                                                 } | 
| 408 |                                                 break; | 
| 409 |                                         } | 
| 410 |                                         case IElementDescriptor.METHOD: { | 
| 411 |                                                 IMethodDescriptor method = (IMethodDescriptor) desc; | 
| 412 |                                                 if(method.getName().equals(name) && method.getSignature().equals(signature)) { | 
| 413 |                                                         return desc; | 
| 414 |                                                 } | 
| 415 |                                                 break; | 
| 416 |                                         } | 
| 417 |                                         case IElementDescriptor.FIELD: { | 
| 418 |                                                 if(((IFieldDescriptor)desc).getName().equals(name)) { | 
| 419 |                                                         return desc; | 
| 420 |                                                 } | 
| 421 |                                                 break; | 
| 422 |                                         } | 
| 423 |                                 } | 
| 424 |                         } | 
| 425 |                         return null; | 
| 426 |                 } | 
| 427 |         } | 
| 428 |          | 
| 429 |         /** | 
| 430 |          * Constructor | 
| 431 |          * can not be instantiated directly | 
| 432 |          */ | 
| 433 |         private ApiDescriptionProcessor() {} | 
| 434 |          | 
| 435 |         /** | 
| 436 |          * Parses a component XML into a string. The location may be a jar, directory containing the component.xml file, or  | 
| 437 |          * the component.xml file itself | 
| 438 |          *  | 
| 439 |          * @param location root location of the component.xml file, or the component.xml file itself | 
| 440 |          * @return component XML as a string or <code>null</code> if none | 
| 441 |          * @throws IOException if unable to parse | 
| 442 |          */ | 
| 443 |         public static String serializeComponentXml(File location) { | 
| 444 |                 if(location.exists()) { | 
| 445 |                         ZipFile jarFile = null; | 
| 446 |                         InputStream stream = null; | 
| 447 |                         try { | 
| 448 |                                 String extension = new Path(location.getName()).getFileExtension(); | 
| 449 |                                 if (extension != null && extension.equals("jar") && location.isFile()) { //$NON-NLS-1$ | 
| 450 |                                         jarFile = new ZipFile(location, ZipFile.OPEN_READ); | 
| 451 |                                         ZipEntry manifestEntry = jarFile.getEntry(IApiCoreConstants.COMPONENT_XML_NAME); | 
| 452 |                                         if (manifestEntry != null) { | 
| 453 |                                                 stream = jarFile.getInputStream(manifestEntry); | 
| 454 |                                         } | 
| 455 |                                 } else if(location.isDirectory()) { | 
| 456 |                                         File file = new File(location, IApiCoreConstants.COMPONENT_XML_NAME); | 
| 457 |                                         if (file.exists()) { | 
| 458 |                                                 stream = new FileInputStream(file); | 
| 459 |                                         } | 
| 460 |                                 } | 
| 461 |                                 else if(location.isFile()) { | 
| 462 |                                         if(location.getName().equals(IApiCoreConstants.COMPONENT_XML_NAME)) { | 
| 463 |                                                 stream = new FileInputStream(location); | 
| 464 |                                         } | 
| 465 |                                 } | 
| 466 |                                 if(stream != null) { | 
| 467 |                                                 return new String(Util.getInputStreamAsCharArray(stream, -1, IApiCoreConstants.UTF_8)); | 
| 468 |                                 } | 
| 469 |                         } catch(IOException e) { | 
| 470 |                                 ApiPlugin.log(e); | 
| 471 |                         } finally { | 
| 472 |                                 try { | 
| 473 |                                         if (stream != null) { | 
| 474 |                                                 stream.close(); | 
| 475 |                                         } | 
| 476 |                                 } catch (IOException e) { | 
| 477 |                                         ApiPlugin.log(e); | 
| 478 |                                 } | 
| 479 |                                 try { | 
| 480 |                                         if (jarFile != null) { | 
| 481 |                                                 jarFile.close(); | 
| 482 |                                         } | 
| 483 |                                 } catch (IOException e) { | 
| 484 |                                         ApiPlugin.log(e); | 
| 485 |                                 } | 
| 486 |                         } | 
| 487 |                 } | 
| 488 |                 return null; | 
| 489 |         } | 
| 490 |          | 
| 491 |         /** | 
| 492 |          * This method updates the javadoc for members of the specified java source files with information | 
| 493 |          * retrieved from the the specified component.xml file. | 
| 494 |          * @param project the java project to update | 
| 495 |          * @param componentxml the component.xml file to update from | 
| 496 |          * @param collector | 
| 497 |          * @throws CoreException | 
| 498 |          * @throws IOException | 
| 499 |          */ | 
| 500 |         public static void collectTagUpdates(IJavaProject project, File componentxml, Map collector) throws CoreException, IOException { | 
| 501 |                 IApiDescription description = new ApiDescription(null); | 
| 502 |                 annotateApiSettings(project, description, serializeComponentXml(componentxml)); | 
| 503 |                 //visit the types | 
| 504 |                 DescriptionVisitor visitor = new DescriptionVisitor(project, description, collector); | 
| 505 |                 description.accept(visitor, null); | 
| 506 |                 IStatus status = visitor.getStatus(); | 
| 507 |                 if (!status.isOK()) { | 
| 508 |                         throw new CoreException(status); | 
| 509 |                 } | 
| 510 |         } | 
| 511 |          | 
| 512 |         /** | 
| 513 |          * Given the type, the parent type descriptor and an annotated description, update | 
| 514 |          * the javadoc comments for the type and all members of the type found in the description. | 
| 515 |          * @param type | 
| 516 |          * @param desc | 
| 517 |          * @param description | 
| 518 |          * @param members members with API annotations | 
| 519 |          * @param collector | 
| 520 |          * @throws CoreException | 
| 521 |          * @throws BadLocationException | 
| 522 |          */ | 
| 523 |         static void processTagUpdates(IType type, IReferenceTypeDescriptor desc, IApiDescription description, List members, Map collector) throws CoreException, BadLocationException { | 
| 524 |                 ASTParser parser = ASTParser.newParser(AST.JLS3); | 
| 525 |                 ICompilationUnit cunit = type.getCompilationUnit(); | 
| 526 |                 if(cunit != null) { | 
| 527 |                         parser.setSource(cunit); | 
| 528 |                         Map options = cunit.getJavaProject().getOptions(true); | 
| 529 |                         options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); | 
| 530 |                         parser.setCompilerOptions(options); | 
| 531 |                         CompilationUnit cast = (CompilationUnit) parser.createAST(new NullProgressMonitor()); | 
| 532 |                         cast.recordModifications(); | 
| 533 |                         ASTRewrite rewrite = ASTRewrite.create(cast.getAST()); | 
| 534 |                         ASTTagVisitor visitor = new ASTTagVisitor(members, description, rewrite); | 
| 535 |                         cast.accept(visitor); | 
| 536 |                         ITextFileBufferManager bm = FileBuffers.getTextFileBufferManager();  | 
| 537 |                         IPath path = cast.getJavaElement().getPath(); | 
| 538 |                         try { | 
| 539 |                                 bm.connect(path, LocationKind.IFILE, null); | 
| 540 |                                 ITextFileBuffer tfb = bm.getTextFileBuffer(path, LocationKind.IFILE); | 
| 541 |                                 IDocument document = tfb.getDocument(); | 
| 542 |                                 TextEdit edit = rewrite.rewriteAST(document, null); | 
| 543 |                                 if(edit.getChildrenSize() > 0 || edit.getLength() != 0) { | 
| 544 |                                         IFile file = (IFile) cunit.getUnderlyingResource(); | 
| 545 |                                         HashSet edits = (HashSet) collector.get(file); | 
| 546 |                                         if(edits == null) { | 
| 547 |                                                 edits = new HashSet(3); | 
| 548 |                                                 collector.put(file, edits); | 
| 549 |                                         } | 
| 550 |                                         edits.add(edit); | 
| 551 |                                 } | 
| 552 |                         } finally { | 
| 553 |                                 bm.disconnect(path, LocationKind.IFILE, null); | 
| 554 |                         } | 
| 555 |                 } | 
| 556 |         } | 
| 557 |          | 
| 558 |         /** | 
| 559 |          * Throws an exception with the given message and underlying exception. | 
| 560 |          *  | 
| 561 |          * @param message error message | 
| 562 |          * @param exception underlying exception, or <code>null</code> | 
| 563 |          * @throws CoreException | 
| 564 |          */ | 
| 565 |         private static void abort(String message, Throwable exception) throws CoreException { | 
| 566 |                 IStatus status = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, message, exception); | 
| 567 |                 throw new CoreException(status); | 
| 568 |         } | 
| 569 |   | 
| 570 |         /** | 
| 571 |          * Parses the given xml document (in string format), and annotates the specified  | 
| 572 |          * {@link IApiDescription} with {@link IPackageDescriptor}s, {@link IReferenceTypeDescriptor}s, {@link IMethodDescriptor}s | 
| 573 |          * and {@link IFieldDescriptor}s. | 
| 574 |          *  | 
| 575 |          * @param settings API settings to annotate | 
| 576 |          * @param xml XML used to generate settings | 
| 577 |          * @throws CoreException  | 
| 578 |          */ | 
| 579 |         public static void annotateApiSettings(IJavaProject project, IApiDescription settings, String xml) throws CoreException { | 
| 580 |                 Element root = null; | 
| 581 |                 try { | 
| 582 |                         root = Util.parseDocument(xml); | 
| 583 |                 } | 
| 584 |                 catch(CoreException ce) { | 
| 585 |                         abort("Failed to parse API description xml file", ce); //$NON-NLS-1$ | 
| 586 |                 } | 
| 587 |                 if (!root.getNodeName().equals(IApiXmlConstants.ELEMENT_COMPONENT)) { | 
| 588 |                         abort(ScannerMessages.ComponentXMLScanner_0, null);  | 
| 589 |                 } | 
| 590 |                 String version = root.getAttribute(IApiXmlConstants.ATTR_VERSION); | 
| 591 |                 ApiDescription desc = (ApiDescription) settings; | 
| 592 |                 desc.setEmbeddedVersion(version); | 
| 593 |                 //TODO for now this compares to 1.2, since the change from 1.1 -> 1.2 denotes the  | 
| 594 |                 //@noextend change, not 1.1 -> current version | 
| 595 |                 boolean earlierversion = desc.compareEmbeddedVersionTo("1.2") == 1; //$NON-NLS-1$ | 
| 596 |                 NodeList packages = root.getElementsByTagName(IApiXmlConstants.ELEMENT_PACKAGE); | 
| 597 |                 NodeList types = null; | 
| 598 |                 IPackageDescriptor packdesc = null; | 
| 599 |                 Element type = null; | 
| 600 |                 for (int i = 0; i < packages.getLength(); i++) { | 
| 601 |                         Element pkg = (Element) packages.item(i); | 
| 602 |                         // package visibility comes from the MANIFEST.MF | 
| 603 |                         String pkgName = pkg.getAttribute(IApiXmlConstants.ATTR_NAME); | 
| 604 |                         packdesc = Factory.packageDescriptor(pkgName); | 
| 605 |                         types = pkg.getElementsByTagName(IApiXmlConstants.ELEMENT_TYPE); | 
| 606 |                         for (int j = 0; j < types.getLength(); j++) { | 
| 607 |                                 type = (Element) types.item(j); | 
| 608 |                                 String name = type.getAttribute(IApiXmlConstants.ATTR_NAME); | 
| 609 |                                 if (name.length() == 0) { | 
| 610 |                                         abort("Missing type name", null); //$NON-NLS-1$ | 
| 611 |                                 } | 
| 612 |                                 IReferenceTypeDescriptor typedesc = packdesc.getType(name);  | 
| 613 |                                 annotateDescriptor(project, settings, typedesc, type, earlierversion); | 
| 614 |                                 annotateMethodSettings(project, settings, typedesc, type, earlierversion); | 
| 615 |                                 annotateFieldSettings(project, settings, typedesc, type, earlierversion); | 
| 616 |                         } | 
| 617 |                 } | 
| 618 |         } | 
| 619 |          | 
| 620 |         /** | 
| 621 |          * Annotates the backing {@link IApiDescription} from the given {@link Element}, by adding the visibility | 
| 622 |          * and restriction attributes to the specified {@link IElementDescriptor} | 
| 623 |          *  | 
| 624 |          * @param settings the settings to annotate | 
| 625 |          * @param descriptor the current descriptor context | 
| 626 |          * @param element the current element to annotate from | 
| 627 |          * @param earlierversion if the version read from XML is older than the current tooling version | 
| 628 |          */ | 
| 629 |         private static void annotateDescriptor(IJavaProject project, IApiDescription settings, IElementDescriptor descriptor, Element element, boolean earlierversion) { | 
| 630 |                 int typeVis = getVisibility(element); | 
| 631 |                 if (typeVis != -1) { | 
| 632 |                         settings.setVisibility(descriptor, typeVis); | 
| 633 |                 } | 
| 634 |                 settings.setRestrictions(descriptor, getRestrictions(project, element, descriptor, earlierversion)); | 
| 635 |         } | 
| 636 |          | 
| 637 |         /** | 
| 638 |          * Returns restriction settings described in the given element. | 
| 639 |          *  | 
| 640 |          * @param project the {@link IJavaProject} context | 
| 641 |          * @param element XML element | 
| 642 |          * @param descriptor the {@link IElementDescriptor} to get the restrictions for | 
| 643 |          * @param earlierversion if the version read from XML is older than the current tooling version | 
| 644 |          * @return restriction settings | 
| 645 |          */ | 
| 646 |         private static int getRestrictions(final IJavaProject project, final Element element, final IElementDescriptor descriptor, boolean earlierversion) { | 
| 647 |                 int res = RestrictionModifiers.NO_RESTRICTIONS; | 
| 648 |                 if(element.hasAttribute(IApiXmlConstants.ATTR_RESTRICTIONS)) { | 
| 649 |                         res = Integer.parseInt(element.getAttribute(IApiXmlConstants.ATTR_RESTRICTIONS)); | 
| 650 |                 } | 
| 651 |                 else { | 
| 652 |                         switch(descriptor.getElementType()) { | 
| 653 |                                 case IElementDescriptor.FIELD: { | 
| 654 |                                         res = annotateRestriction(element, IApiXmlConstants.ATTR_REFERENCE, RestrictionModifiers.NO_REFERENCE, res); | 
| 655 |                                         break; | 
| 656 |                                 } | 
| 657 |                                 case IElementDescriptor.METHOD: { | 
| 658 |                                         IMethodDescriptor method  = (IMethodDescriptor) descriptor; | 
| 659 |                                         res = annotateRestriction(element, IApiXmlConstants.ATTR_REFERENCE, RestrictionModifiers.NO_REFERENCE, res); | 
| 660 |                                         if(!method.isConstructor()) { | 
| 661 |                                                 res = annotateRestriction(element, IApiXmlConstants.ATTR_OVERRIDE, RestrictionModifiers.NO_OVERRIDE, res); | 
| 662 |                                         } | 
| 663 |                                         break; | 
| 664 |                                 } | 
| 665 |                                 case IElementDescriptor.TYPE: { | 
| 666 |                                         IReferenceTypeDescriptor rtype = (IReferenceTypeDescriptor) descriptor; | 
| 667 |                                         res = annotateRestriction(element, IApiXmlConstants.ATTR_IMPLEMENT, RestrictionModifiers.NO_IMPLEMENT, res); | 
| 668 |                                         if(earlierversion && RestrictionModifiers.isImplementRestriction(res)) { | 
| 669 |                                                 res |= RestrictionModifiers.NO_EXTEND; | 
| 670 |                                         } | 
| 671 |                                         res = annotateRestriction(element, IApiXmlConstants.ATTR_EXTEND, RestrictionModifiers.NO_EXTEND, res); | 
| 672 |                                         if(!RestrictionModifiers.isExtendRestriction(res)) { | 
| 673 |                                                 res = annotateRestriction(element, IApiXmlConstants.ATTR_SUBCLASS, RestrictionModifiers.NO_EXTEND, res); | 
| 674 |                                         } | 
| 675 |                                         res = annotateRestriction(element, IApiXmlConstants.ATTR_INSTANTIATE, RestrictionModifiers.NO_INSTANTIATE, res); | 
| 676 |                                         IType type = null; | 
| 677 |                                         if (project != null) { | 
| 678 |                                                 try { | 
| 679 |                                                         type = project.findType(rtype.getQualifiedName()); | 
| 680 |                                                         if (type != null) { | 
| 681 |                                                                 if(Flags.isInterface(type.getFlags())) { | 
| 682 |                                                                         res &= ~RestrictionModifiers.NO_INSTANTIATE; | 
| 683 |                                                                 } | 
| 684 |                                                                 else { | 
| 685 |                                                                         res &= ~RestrictionModifiers.NO_IMPLEMENT; | 
| 686 |                                                                         if(Flags.isFinal(type.getFlags())) { | 
| 687 |                                                                                 res &= ~RestrictionModifiers.NO_EXTEND; | 
| 688 |                                                                         } | 
| 689 |                                                                         if(Flags.isAbstract(type.getFlags())) { | 
| 690 |                                                                                 res &= ~RestrictionModifiers.NO_INSTANTIATE; | 
| 691 |                                                                         } | 
| 692 |                                                                 } | 
| 693 |                                                         } | 
| 694 |                                                 }  | 
| 695 |                                                 catch (JavaModelException e) {} | 
| 696 |                                         } | 
| 697 |                                         break; | 
| 698 |                                 } | 
| 699 |                         } | 
| 700 |                 } | 
| 701 |                 return res; | 
| 702 |         } | 
| 703 |          | 
| 704 |         /** | 
| 705 |          * Tests if the given restriction exists for the given element | 
| 706 |          * and returns an updated restrictions flag. | 
| 707 |          *  | 
| 708 |          * @param element XML element | 
| 709 |          * @param name attribute to test | 
| 710 |          * @param flag bit mask for attribute | 
| 711 |          * @param res flag to combine with  | 
| 712 |          * @return updated flags | 
| 713 |          */ | 
| 714 |         private static int annotateRestriction(Element element, String name, int flag, int res) { | 
| 715 |                 String value = element.getAttribute(name); | 
| 716 |                 int lres = res; | 
| 717 |                 if (value.length() > 0) { | 
| 718 |                         if (!Boolean.valueOf(value).booleanValue()) { | 
| 719 |                                 lres = res | flag; | 
| 720 |                         } | 
| 721 |                 } | 
| 722 |                 return lres; | 
| 723 |         } | 
| 724 |          | 
| 725 |         /** | 
| 726 |          * Returns visibility settings described in the given element or | 
| 727 |          * -1 if none. | 
| 728 |          *  | 
| 729 |          * @param element XML element | 
| 730 |          * @return visibility settings or -1 if none | 
| 731 |          */ | 
| 732 |         private static int getVisibility(Element element) { | 
| 733 |                 String attribute = element.getAttribute(IApiXmlConstants.ATTR_VISIBILITY); | 
| 734 |                 try { | 
| 735 |                         return Integer.parseInt(attribute); | 
| 736 |                 } | 
| 737 |                 catch(NumberFormatException nfe) { | 
| 738 |                         if ("API".equals(attribute)) { //$NON-NLS-1$ | 
| 739 |                                 return VisibilityModifiers.API; | 
| 740 |                         } | 
| 741 |                         if ("PRIVATE".equals(attribute)) { //$NON-NLS-1$ | 
| 742 |                                 return VisibilityModifiers.PRIVATE; | 
| 743 |                         } | 
| 744 |                         if ("PRIVATE_PERMISSABLE".equals(attribute)) { //$NON-NLS-1$ | 
| 745 |                                 return VisibilityModifiers.PRIVATE_PERMISSIBLE; | 
| 746 |                         } | 
| 747 |                         if ("SPI".equals(attribute)) { //$NON-NLS-1$ | 
| 748 |                                 return VisibilityModifiers.SPI; | 
| 749 |                         } | 
| 750 |                         return -1; | 
| 751 |                 } | 
| 752 |         } | 
| 753 |          | 
| 754 |         /** | 
| 755 |          * Annotates the supplied {@link IApiDescription} from all of the field elements | 
| 756 |          * that are direct children of the specified {@link Element}. {@link IFieldDescriptor}s are created | 
| 757 |          * as needed and added as children of the specified {@link IReferenceTypeDescriptor}. | 
| 758 |          *  | 
| 759 |          * @param settings the {@link IApiDescription} to add the new {@link IFieldDescriptor} to | 
| 760 |          * @param typedesc the containing type descriptor for this field | 
| 761 |          * @param type the parent {@link Element} | 
| 762 |          * @param earlierversion if the version read from XML is older than the current tooling version | 
| 763 |          * @throws CoreException | 
| 764 |          */ | 
| 765 |         private static void annotateFieldSettings(IJavaProject project, IApiDescription settings, IReferenceTypeDescriptor typedesc, Element type, boolean earlierversion) throws CoreException { | 
| 766 |                 NodeList fields = type.getElementsByTagName(IApiXmlConstants.ELEMENT_FIELD); | 
| 767 |                 Element field = null; | 
| 768 |                 IFieldDescriptor fielddesc = null; | 
| 769 |                 String name = null; | 
| 770 |                 for(int i = 0; i < fields.getLength(); i++) { | 
| 771 |                         field = (Element) fields.item(i); | 
| 772 |                         name = field.getAttribute(IApiXmlConstants.ATTR_NAME); | 
| 773 |                         if(name == null) { | 
| 774 |                                 abort(ScannerMessages.ComponentXMLScanner_1, null);  | 
| 775 |                         } | 
| 776 |                         fielddesc = typedesc.getField(name); | 
| 777 |                         annotateDescriptor(project, settings, fielddesc, field, earlierversion); | 
| 778 |                 } | 
| 779 |         } | 
| 780 |          | 
| 781 |         /** | 
| 782 |          * Annotates the supplied {@link IApiDescription} from all of the method elements | 
| 783 |          * that are direct children of the specified {@link Element}. {@link IMethodDescriptor}s are created | 
| 784 |          * as needed and added as children of the specified {@link IReferenceTypeDescriptor}. | 
| 785 |          *  | 
| 786 |          * @param settings the {@link IApiDescription} to add the new {@link IMethodDescriptor} to  | 
| 787 |          * @param typedesc the containing type descriptor for this method | 
| 788 |          * @param type the parent {@link Element} | 
| 789 |          * @param earlierversion if the version read from XML is older than the current tooling version | 
| 790 |          * @throws CoreException | 
| 791 |          */ | 
| 792 |         private static void annotateMethodSettings(IJavaProject project, IApiDescription settings, IReferenceTypeDescriptor typedesc, Element type, boolean earlierversion) throws CoreException { | 
| 793 |                 NodeList methods = type.getElementsByTagName(IApiXmlConstants.ELEMENT_METHOD); | 
| 794 |                 Element method = null; | 
| 795 |                 IMethodDescriptor methoddesc = null; | 
| 796 |                 String name, signature; | 
| 797 |                 for(int i = 0; i < methods.getLength(); i++) { | 
| 798 |                         method = (Element) methods.item(i); | 
| 799 |                         name = method.getAttribute(IApiXmlConstants.ATTR_NAME); | 
| 800 |                         if(name == null) { | 
| 801 |                                 abort(ScannerMessages.ComponentXMLScanner_2, null);  | 
| 802 |                         } | 
| 803 |                         signature = method.getAttribute(IApiXmlConstants.ATTR_SIGNATURE); | 
| 804 |                         if(signature == null) { | 
| 805 |                                 abort(ScannerMessages.ComponentXMLScanner_3, null);  | 
| 806 |                         } | 
| 807 |                         // old files might use '.' instead of '/' | 
| 808 |                         signature = signature.replace('.', '/'); | 
| 809 |                         methoddesc = typedesc.getMethod(name, signature); | 
| 810 |                         annotateDescriptor(project, settings, methoddesc, method, earlierversion); | 
| 811 |                 } | 
| 812 |         } | 
| 813 | } |