| 1 | /******************************************************************************* | 
| 2 |  * Copyright (c) 2008, 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.builder; | 
| 12 |   | 
| 13 | import java.util.ArrayList; | 
| 14 | import java.util.HashSet; | 
| 15 | import java.util.Iterator; | 
| 16 | import java.util.List; | 
| 17 | import java.util.Set; | 
| 18 |   | 
| 19 | import org.eclipse.core.runtime.CoreException; | 
| 20 | import org.eclipse.jdt.core.Flags; | 
| 21 | import org.eclipse.jdt.core.ICompilationUnit; | 
| 22 | import org.eclipse.jdt.core.JavaModelException; | 
| 23 | import org.eclipse.jdt.core.dom.ASTNode; | 
| 24 | import org.eclipse.jdt.core.dom.ASTVisitor; | 
| 25 | import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; | 
| 26 | import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; | 
| 27 | import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; | 
| 28 | import org.eclipse.jdt.core.dom.CompilationUnit; | 
| 29 | import org.eclipse.jdt.core.dom.EnumConstantDeclaration; | 
| 30 | import org.eclipse.jdt.core.dom.EnumDeclaration; | 
| 31 | import org.eclipse.jdt.core.dom.FieldDeclaration; | 
| 32 | import org.eclipse.jdt.core.dom.Javadoc; | 
| 33 | import org.eclipse.jdt.core.dom.MethodDeclaration; | 
| 34 | import org.eclipse.jdt.core.dom.PackageDeclaration; | 
| 35 | import org.eclipse.jdt.core.dom.TagElement; | 
| 36 | import org.eclipse.jdt.core.dom.TypeDeclaration; | 
| 37 | import org.eclipse.jface.text.BadLocationException; | 
| 38 | import org.eclipse.jface.text.IDocument; | 
| 39 | import org.eclipse.pde.api.tools.internal.JavadocTagManager; | 
| 40 | import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; | 
| 41 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; | 
| 42 | import org.eclipse.pde.api.tools.internal.provisional.IApiJavadocTag; | 
| 43 | import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; | 
| 44 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; | 
| 45 | import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; | 
| 46 | import org.eclipse.pde.api.tools.internal.util.Util; | 
| 47 |   | 
| 48 | /** | 
| 49 |  * Visit Javadoc comments of types and member to find API javadoc tags that are being misused | 
| 50 |  *  | 
| 51 |  * @since 1.0.0 | 
| 52 |  */ | 
| 53 | public class TagValidator extends ASTVisitor { | 
| 54 |   | 
| 55 |         /** | 
| 56 |          * backing collection of tag problems, if any | 
| 57 |          */ | 
| 58 |         private ArrayList fTagProblems = null; | 
| 59 |          | 
| 60 |         private ICompilationUnit fCompilationUnit = null; | 
| 61 |          | 
| 62 |         private static final IApiJavadocTag[] NO_TAGS = new IApiJavadocTag[0]; | 
| 63 |          | 
| 64 |         /** | 
| 65 |          * Constructor | 
| 66 |          * @param parent | 
| 67 |          */ | 
| 68 |         public TagValidator(ICompilationUnit parent) { | 
| 69 |                 fCompilationUnit = parent; | 
| 70 |         } | 
| 71 |          | 
| 72 |         /* (non-Javadoc) | 
| 73 |          * @see org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom.Javadoc) | 
| 74 |          */ | 
| 75 |         public boolean visit(Javadoc node) { | 
| 76 |                 ASTNode parent = node.getParent(); | 
| 77 |                 if(parent != null) { | 
| 78 |                         List tags = node.tags(); | 
| 79 |                         validateTags(parent, tags); | 
| 80 |                 } | 
| 81 |                 return false; | 
| 82 |         } | 
| 83 |   | 
| 84 |         /** | 
| 85 |          * Validates the set of tags for the given parent node and the given listing of {@link TagElement}s | 
| 86 |          * @param node | 
| 87 |          * @param tags | 
| 88 |          */ | 
| 89 |         private void validateTags(ASTNode node, List tags) { | 
| 90 |                 if(tags.size() == 0) { | 
| 91 |                         return; | 
| 92 |                 } | 
| 93 |                 JavadocTagManager jtm = ApiPlugin.getJavadocTagManager(); | 
| 94 |                 switch(node.getNodeType()) { | 
| 95 |                         case ASTNode.TYPE_DECLARATION: { | 
| 96 |                                 TypeDeclaration type = (TypeDeclaration) node; | 
| 97 |                                 IApiJavadocTag[] validtags = jtm.getTagsForType(type.isInterface() ? IApiJavadocTag.TYPE_INTERFACE : IApiJavadocTag.TYPE_CLASS, IApiJavadocTag.MEMBER_NONE); | 
| 98 |                                 HashSet invalidtags = new HashSet(validtags.length); | 
| 99 |                                 String context = BuilderMessages.TagValidator_an_interface; | 
| 100 |                                 if(!type.isInterface()) { | 
| 101 |                                         context = BuilderMessages.TagValidator_a_class; | 
| 102 |                                         if(Flags.isAbstract(type.getModifiers())) { | 
| 103 |                                                 context = BuilderMessages.TagValidator_an_abstract_class; | 
| 104 |                                                 invalidtags.add("@noinstantiate"); //$NON-NLS-1$ | 
| 105 |                                         } | 
| 106 |                                         if(Flags.isFinal(type.getModifiers())) { | 
| 107 |                                                 context = BuilderMessages.TagValidator_a_final_class; | 
| 108 |                                                 invalidtags.add("@noextend"); //$NON-NLS-1$ | 
| 109 |                                         } | 
| 110 |                                 } | 
| 111 |                                 if(invalidtags.size() > 0) { | 
| 112 |                                         ArrayList vtags = new ArrayList(validtags.length); | 
| 113 |                                         for(int i = 0; i < validtags.length; i++) { | 
| 114 |                                                 if(invalidtags.contains(validtags[i].getTagName())) { | 
| 115 |                                                         continue; | 
| 116 |                                                 } | 
| 117 |                                                 vtags.add(validtags[i]); | 
| 118 |                                         } | 
| 119 |                                         validtags = (IApiJavadocTag[]) vtags.toArray(new IApiJavadocTag[vtags.size()]); | 
| 120 |                                 } | 
| 121 |                                 processTags(getTypeName(type), tags, validtags, IElementDescriptor.TYPE, context); | 
| 122 |                                 break; | 
| 123 |                         } | 
| 124 |                         case ASTNode.ENUM_DECLARATION: { | 
| 125 |                                 EnumDeclaration enumm = (EnumDeclaration) node; | 
| 126 |                                 IApiJavadocTag[] validtags = jtm.getTagsForType(IApiJavadocTag.TYPE_ENUM, IApiJavadocTag.MEMBER_NONE); | 
| 127 |                                 processTags(getTypeName(enumm), tags, validtags, IElementDescriptor.TYPE, BuilderMessages.TagValidator_an_enum); | 
| 128 |                                 break; | 
| 129 |                         } | 
| 130 |                         case ASTNode.ENUM_CONSTANT_DECLARATION: { | 
| 131 |                                 EnumConstantDeclaration decl = (EnumConstantDeclaration) node; | 
| 132 |                                 processTags(getTypeName(decl), tags, new IApiJavadocTag[0], IElementDescriptor.FIELD, BuilderMessages.TagValidator_an_enum_constant); | 
| 133 |                                 break; | 
| 134 |                         } | 
| 135 |                         case ASTNode.ANNOTATION_TYPE_DECLARATION: { | 
| 136 |                                 AnnotationTypeDeclaration annot = (AnnotationTypeDeclaration) node; | 
| 137 |                                 IApiJavadocTag[] validtags = jtm.getTagsForType(IApiJavadocTag.TYPE_ANNOTATION, IApiJavadocTag.MEMBER_NONE); | 
| 138 |                                 processTags(getTypeName(annot), tags, validtags, IElementDescriptor.TYPE, BuilderMessages.TagValidator_an_annotation); | 
| 139 |                                 break; | 
| 140 |                         } | 
| 141 |                         case ASTNode.METHOD_DECLARATION: { | 
| 142 |                                 MethodDeclaration method = (MethodDeclaration) node; | 
| 143 |                                 int pkind = getParentKind(node); | 
| 144 |                                 String context = null; | 
| 145 |                                 int mods = method.getModifiers(); | 
| 146 |                                 boolean isprivate = Flags.isPrivate(mods); | 
| 147 |                                 boolean isconstructor = method.isConstructor(); | 
| 148 |                                 boolean isfinal = Flags.isFinal(mods); | 
| 149 |                                 boolean isstatic = Flags.isStatic(mods); | 
| 150 |                                 boolean pfinal = false; | 
| 151 |                                 switch(pkind) { | 
| 152 |                                         case IApiJavadocTag.TYPE_ENUM: { | 
| 153 |                                                 context = isprivate ? BuilderMessages.TagValidator_private_enum_method : BuilderMessages.TagValidator_an_enum_method; | 
| 154 |                                                 break; | 
| 155 |                                         } | 
| 156 |                                         case IApiJavadocTag.TYPE_INTERFACE: { | 
| 157 |                                                 context = BuilderMessages.TagValidator_an_interface_method; | 
| 158 |                                                 break; | 
| 159 |                                         } | 
| 160 |                                         default: { | 
| 161 |                                                 pfinal = Flags.isFinal(getParentModifiers(method)); | 
| 162 |                                                 if(isprivate) { | 
| 163 |                                                         context = isconstructor ? BuilderMessages.TagValidator_private_constructor : BuilderMessages.TagValidator_private_method; | 
| 164 |                                                 } | 
| 165 |                                                 else if(isstatic && isfinal) { | 
| 166 |                                                         context = BuilderMessages.TagValidator_a_static_final_method; | 
| 167 |                                                 } | 
| 168 |                                                 else if (isfinal) { | 
| 169 |                                                         context = BuilderMessages.TagValidator_a_final_method; | 
| 170 |                                                 } | 
| 171 |                                                 else if(isstatic) { | 
| 172 |                                                         context = BuilderMessages.TagValidator_a_static_method; | 
| 173 |                                                 } | 
| 174 |                                                 else if(pfinal) { | 
| 175 |                                                         context = BuilderMessages.TagValidator_a_method_in_a_final_class; | 
| 176 |                                                 } | 
| 177 |                                                 else { | 
| 178 |                                                         context = isconstructor ? BuilderMessages.TagValidator_a_constructor : BuilderMessages.TagValidator_a_method; | 
| 179 |                                                 } | 
| 180 |                                                 break; | 
| 181 |                                         } | 
| 182 |                                 } | 
| 183 |                                 IApiJavadocTag[] validtags = NO_TAGS; | 
| 184 |                                 if(!isprivate) { | 
| 185 |                                         validtags = jtm.getTagsForType(pkind, isconstructor ? IApiJavadocTag.MEMBER_CONSTRUCTOR : IApiJavadocTag.MEMBER_METHOD); | 
| 186 |                                 } | 
| 187 |                                 if(isfinal || isstatic || pfinal) { | 
| 188 |                                         ArrayList ttags = new ArrayList(validtags.length); | 
| 189 |                                         for(int i = 0; i < validtags.length; i++) { | 
| 190 |                                                 if(!validtags[i].getTagName().equals("@nooverride")) { //$NON-NLS-1$ | 
| 191 |                                                         ttags.add(validtags[i]); | 
| 192 |                                                 } | 
| 193 |                                         } | 
| 194 |                                         validtags = (IApiJavadocTag[]) ttags.toArray(new IApiJavadocTag[ttags.size()]); | 
| 195 |                                 } | 
| 196 |                                 processTags(getTypeName(method), tags, validtags, IElementDescriptor.METHOD, context); | 
| 197 |                                 break; | 
| 198 |                         } | 
| 199 |                         case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION: { | 
| 200 |                                 AnnotationTypeMemberDeclaration decl = (AnnotationTypeMemberDeclaration) node; | 
| 201 |                                 IApiJavadocTag[] validtags = jtm.getTagsForType(IApiJavadocTag.TYPE_ANNOTATION, IApiJavadocTag.MEMBER_METHOD); | 
| 202 |                                 processTags(getTypeName(decl), tags, validtags, IElementDescriptor.METHOD, BuilderMessages.TagValidator_an_annotation_method); | 
| 203 |                                 break; | 
| 204 |                         } | 
| 205 |                         case ASTNode.FIELD_DECLARATION: { | 
| 206 |                                 FieldDeclaration field = (FieldDeclaration) node; | 
| 207 |                                 int pkind = getParentKind(node); | 
| 208 |                                 String context = null; | 
| 209 |                                 boolean isfinal = Flags.isFinal(field.getModifiers()); | 
| 210 |                                 boolean isprivate = Flags.isPrivate(field.getModifiers()); | 
| 211 |                                 switch(pkind) { | 
| 212 |                                         case IApiJavadocTag.TYPE_ANNOTATION: { | 
| 213 |                                                 context = BuilderMessages.TagValidator_annotation_field; | 
| 214 |                                                 if(isfinal) { | 
| 215 |                                                         context = BuilderMessages.TagValidator_a_final_annotation_field; | 
| 216 |                                                 } | 
| 217 |                                                 break; | 
| 218 |                                         } | 
| 219 |                                         case IApiJavadocTag.TYPE_ENUM: { | 
| 220 |                                                 context = isprivate ? BuilderMessages.TagValidator_private_enum_field : BuilderMessages.TagValidator_enum_field; | 
| 221 |                                                 break; | 
| 222 |                                         } | 
| 223 |                                         default: { | 
| 224 |                                                 if(isprivate) { | 
| 225 |                                                         context = BuilderMessages.TagValidator_private_field; | 
| 226 |                                                 } | 
| 227 |                                                 else { | 
| 228 |                                                         context = isfinal ? BuilderMessages.TagValidator_a_final_field : BuilderMessages.TagValidator_a_field; | 
| 229 |                                                 } | 
| 230 |                                                 break; | 
| 231 |                                         } | 
| 232 |                                 } | 
| 233 |                                 IApiJavadocTag[] validtags = NO_TAGS; | 
| 234 |                                 if(!isprivate && !isfinal) { | 
| 235 |                                         validtags = jtm.getTagsForType(pkind, IApiJavadocTag.MEMBER_FIELD); | 
| 236 |                                 } | 
| 237 |                                 processTags(getTypeName(field), tags, validtags, IElementDescriptor.FIELD, context); | 
| 238 |                                 break; | 
| 239 |                         } | 
| 240 |                 } | 
| 241 |         } | 
| 242 |          | 
| 243 |         /** | 
| 244 |          * Returns the modifiers from the smallest enclosing type containing the given node | 
| 245 |          * @param node | 
| 246 |          * @return the modifiers for the smallest enclosing type or 0 | 
| 247 |          */ | 
| 248 |         private int getParentModifiers(ASTNode node) { | 
| 249 |                 if(node == null) { | 
| 250 |                         return 0; | 
| 251 |                 } | 
| 252 |                 if(node instanceof AbstractTypeDeclaration) { | 
| 253 |                         AbstractTypeDeclaration type = (AbstractTypeDeclaration) node; | 
| 254 |                         return type.getModifiers(); | 
| 255 |                 } | 
| 256 |                 return getParentModifiers(node.getParent()); | 
| 257 |         } | 
| 258 |          | 
| 259 |         /** | 
| 260 |          * Returns the fully qualified name of the enclosing type for the given node | 
| 261 |          * @param node | 
| 262 |          * @return the fully qualified name of the enclosing type | 
| 263 |          */ | 
| 264 |         private String getTypeName(ASTNode node) { | 
| 265 |                 return getTypeName(node, new StringBuffer()); | 
| 266 |         } | 
| 267 |          | 
| 268 |         /** | 
| 269 |          * Constructs the qualified name of the enclosing parent type | 
| 270 |          * @param node the node to get the parent name for | 
| 271 |          * @param buffer the buffer to write the name into | 
| 272 |          * @return the fully qualified name of the parent  | 
| 273 |          */ | 
| 274 |         private String getTypeName(ASTNode node, StringBuffer buffer) { | 
| 275 |                 switch(node.getNodeType()) { | 
| 276 |                         case ASTNode.COMPILATION_UNIT : { | 
| 277 |                                 CompilationUnit unit = (CompilationUnit) node; | 
| 278 |                                 PackageDeclaration packageDeclaration = unit.getPackage(); | 
| 279 |                                 if (packageDeclaration != null) { | 
| 280 |                                         buffer.insert(0, '.'); | 
| 281 |                                         buffer.insert(0, packageDeclaration.getName().getFullyQualifiedName()); | 
| 282 |                                 } | 
| 283 |                                 return String.valueOf(buffer); | 
| 284 |                         } | 
| 285 |                         default : { | 
| 286 |                                 if (node instanceof AbstractTypeDeclaration) { | 
| 287 |                                         AbstractTypeDeclaration typeDeclaration = (AbstractTypeDeclaration) node; | 
| 288 |                                         if (typeDeclaration.isPackageMemberTypeDeclaration()) { | 
| 289 |                                                 buffer.insert(0, typeDeclaration.getName().getIdentifier()); | 
| 290 |                                         } | 
| 291 |                                         else { | 
| 292 |                                                 buffer.insert(0, typeDeclaration.getName().getFullyQualifiedName()); | 
| 293 |                                                 buffer.insert(0, '$'); | 
| 294 |                                         } | 
| 295 |                                 } | 
| 296 |                         } | 
| 297 |                 } | 
| 298 |                 return getTypeName(node.getParent(), buffer); | 
| 299 |         } | 
| 300 |   | 
| 301 |         /** | 
| 302 |          * Processes the listing of valid tags against the listing of existing tags on the node, and | 
| 303 |          * creates errors if disallowed tags are found. | 
| 304 |          * @param tags | 
| 305 |          * @param validtags | 
| 306 |          * @param element | 
| 307 |          * @param context | 
| 308 |          */ | 
| 309 |         private void processTags(String typeName, List tags, IApiJavadocTag[] validtags, int element, String context) { | 
| 310 |                 IApiJavadocTag[] alltags = ApiPlugin.getJavadocTagManager().getAllTags(); | 
| 311 |                 Set tagnames = ApiPlugin.getJavadocTagManager().getAllTagNames(); | 
| 312 |                 HashSet invalidtags = new HashSet(alltags.length); | 
| 313 |                 for(int i = 0; i < alltags.length; i++) { | 
| 314 |                         invalidtags.add(alltags[i].getTagName()); | 
| 315 |                 } | 
| 316 |                 for(int i = 0; i < validtags.length; i++) { | 
| 317 |                         invalidtags.remove(validtags[i]); | 
| 318 |                 } | 
| 319 |                 if(invalidtags.size() == 0) { | 
| 320 |                         return; | 
| 321 |                 } | 
| 322 |                 TagElement tag = null; | 
| 323 |                 HashSet tagz = new HashSet(tags.size()); | 
| 324 |                 String tagname = null; | 
| 325 |                 for(Iterator iter = tags.iterator(); iter.hasNext();) { | 
| 326 |                         tag = (TagElement) iter.next(); | 
| 327 |                         tagname = tag.getTagName(); | 
| 328 |                         if(invalidtags.contains(tag.getTagName())) { | 
| 329 |                                 processTagProblem(typeName, tag, element, IApiProblem.UNSUPPORTED_TAG_USE, IApiMarkerConstants.UNSUPPORTED_TAG_MARKER_ID, context); | 
| 330 |                         } | 
| 331 |                         if(tagnames.contains(tag.getTagName()) && !tagz.add(tagname)) { | 
| 332 |                                 processTagProblem(typeName, tag, element, IApiProblem.DUPLICATE_TAG_USE, IApiMarkerConstants.DUPLICATE_TAG_MARKER_ID, null); | 
| 333 |                         } | 
| 334 |                 } | 
| 335 |         } | 
| 336 |          | 
| 337 |         /** | 
| 338 |          * Creates a new {@link IApiProblem} for the given tag and adds it to the cache | 
| 339 |          * @param tag | 
| 340 |          * @param element | 
| 341 |          * @param context | 
| 342 |          */ | 
| 343 |         private void processTagProblem(String typeName, TagElement tag, int element, int kind, int markerid, String context) { | 
| 344 |                 if(fTagProblems == null) { | 
| 345 |                         fTagProblems = new ArrayList(10); | 
| 346 |                 } | 
| 347 |                 int charstart = tag.getStartPosition(); | 
| 348 |                 int charend = charstart + tag.getTagName().length(); | 
| 349 |                 int linenumber = -1; | 
| 350 |                 try { | 
| 351 |                         // unit cannot be null | 
| 352 |                         IDocument document = Util.getDocument(fCompilationUnit); | 
| 353 |                         linenumber = document.getLineOfOffset(charstart); | 
| 354 |                 }  | 
| 355 |                 catch (BadLocationException e) {}  | 
| 356 |                 catch (CoreException e) {} | 
| 357 |                 try { | 
| 358 |                         IApiProblem problem = ApiProblemFactory.newApiProblem(fCompilationUnit.getCorrespondingResource().getProjectRelativePath().toPortableString(), | 
| 359 |                                         typeName, | 
| 360 |                                         new String[] {tag.getTagName(), context}, | 
| 361 |                                         new String[] {IApiMarkerConstants.API_MARKER_ATTR_ID, IApiMarkerConstants.MARKER_ATTR_HANDLE_ID},  | 
| 362 |                                         new Object[] {new Integer(markerid), fCompilationUnit.getHandleIdentifier()},  | 
| 363 |                                         linenumber, | 
| 364 |                                         charstart, | 
| 365 |                                         charend, | 
| 366 |                                         IApiProblem.CATEGORY_USAGE, | 
| 367 |                                         element, | 
| 368 |                                         kind, | 
| 369 |                                         IApiProblem.NO_FLAGS); | 
| 370 |                          | 
| 371 |                         fTagProblems.add(problem); | 
| 372 |                 }  | 
| 373 |                 catch (JavaModelException e) {} | 
| 374 |         } | 
| 375 |          | 
| 376 |         /** | 
| 377 |          * Returns the {@link IApiJavadocTag} kind of the parent {@link ASTNode} for the given  | 
| 378 |          * node or -1 if the parent is not found or not a {@link TypeDeclaration} | 
| 379 |          * @param node | 
| 380 |          * @return the {@link IApiJavadocTag} kind of the parent or -1 | 
| 381 |          */ | 
| 382 |         private int getParentKind(ASTNode node) { | 
| 383 |                 if(node == null) { | 
| 384 |                         return -1; | 
| 385 |                 } | 
| 386 |                 if(node instanceof TypeDeclaration) { | 
| 387 |                         return ((TypeDeclaration)node).isInterface() ? IApiJavadocTag.TYPE_INTERFACE : IApiJavadocTag.TYPE_CLASS; | 
| 388 |                 } | 
| 389 |                 else if(node instanceof AnnotationTypeDeclaration) { | 
| 390 |                         return IApiJavadocTag.TYPE_ANNOTATION; | 
| 391 |                 } | 
| 392 |                 else if(node instanceof EnumDeclaration) { | 
| 393 |                         return IApiJavadocTag.TYPE_ENUM; | 
| 394 |                 } | 
| 395 |                 return getParentKind(node.getParent()); | 
| 396 |         } | 
| 397 |          | 
| 398 |         /** | 
| 399 |          * Returns the complete listing of API tag problems found during the scan or  | 
| 400 |          * an empty array, never <code>null</code> | 
| 401 |          * @return the complete listing of API tag problems found | 
| 402 |          */ | 
| 403 |         public IApiProblem[] getTagProblems() { | 
| 404 |                 if(fTagProblems == null) { | 
| 405 |                         return new IApiProblem[0]; | 
| 406 |                 } | 
| 407 |                 return (IApiProblem[]) fTagProblems.toArray(new IApiProblem[fTagProblems.size()]); | 
| 408 |         } | 
| 409 | } |