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 | } |