1 /******************************************************************************* 2 * Copyright (c) 2009, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.pde.api.tools.internal.search; 15 16 import java.io.File; 17 import java.io.FileFilter; 18 import java.io.FileInputStream; 19 import java.io.IOException; 20 import java.io.InputStream; 21 import java.util.ArrayList; 22 import java.util.Collections; 23 import java.util.List; 24 25 import javax.xml.parsers.ParserConfigurationException; 26 import javax.xml.parsers.SAXParser; 27 import javax.xml.parsers.SAXParserFactory; 28 29 import org.eclipse.core.runtime.IProgressMonitor; 30 import org.eclipse.core.runtime.SubMonitor; 31 import org.eclipse.osgi.util.NLS; 32 import org.eclipse.pde.api.tools.internal.IApiXmlConstants; 33 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 34 import org.eclipse.pde.api.tools.internal.provisional.Factory; 35 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; 36 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor; 37 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; 38 import org.eclipse.pde.api.tools.internal.util.Util; 39 import org.xml.sax.Attributes; 40 import org.xml.sax.SAXException; 41 import org.xml.sax.helpers.DefaultHandler; 42 43 /** 44 * Parses a use scan (XML) to visit a {@link UseScanVisitor} 45 */ 46 public class UseScanParser { 47 48 private UseScanVisitor visitor; 49 50 private IComponentDescriptor targetComponent; 51 private IComponentDescriptor referencingComponent; 52 private IMemberDescriptor targetMember; 53 private int referenceKind; 54 private int visibility; 55 56 private boolean visitReferencingComponent = true; 57 private boolean visitMembers = true; 58 private boolean visitReferences = true; 59 60 /** 61 * Handler to resolve a reference 62 */ 63 class ReferenceHandler extends DefaultHandler { 64 65 // type of file being analyzed - type reference, method reference, field 66 // reference 67 private int type = 0; 68 69 /** 70 * Constructor 71 * 72 * @param type one of IReference.T_TYPE_REFERENCE, 73 * IReference.T_METHOD_REFERENCE, 74 * IReference.T_FIELD_REFERENCE 75 */ ReferenceHandler(int type)76 public ReferenceHandler(int type) { 77 this.type = type; 78 } 79 80 @Override startElement(String uri, String localName, String name, Attributes attributes)81 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { 82 processElement(uri, localName, name, attributes, type); 83 } 84 85 } 86 getIdVersion(String value)87 protected String[] getIdVersion(String value) { 88 int index = value.indexOf(' '); 89 if (index > 0) { 90 String id = value.substring(0, index); 91 String version = value.substring(index + 1); 92 if (version.startsWith("(")) { //$NON-NLS-1$ 93 version = version.substring(1); 94 if (version.endsWith(")")) { //$NON-NLS-1$ 95 version = version.substring(0, version.length() - 1); 96 } 97 } 98 return new String[] { id, version }; 99 } 100 return new String[] { value, null }; 101 } 102 103 /** 104 * Process the XML element described by the URI, local name, name and 105 * attributes 106 * 107 * @param uri the URI of the XML element 108 * @param localName the local name of the XML element 109 * @param name the name of the XML element 110 * @param attributes the attribute listing for the XML element 111 * @param type the type of the XML file. One of: 112 * {@link IReference#T_TYPE_REFERENCE}, 113 * {@link IReference#T_METHOD_REFERENCE} or 114 * {@link IReference#T_FIELD_REFERENCE} 115 * @throws SAXException 116 */ processElement(String uri, String localName, String name, Attributes attributes, int type)117 protected void processElement(String uri, String localName, String name, Attributes attributes, int type) throws SAXException { 118 if (IApiXmlConstants.REFERENCES.equals(name)) { 119 // Check that the current target component and referencing component 120 // match what is in the file 121 String target = attributes.getValue(IApiXmlConstants.ATTR_REFEREE); 122 String[] idv = getIdVersion(target); 123 IComponentDescriptor tcomp = Factory.componentDescriptor(idv[0], idv[1]); 124 if (!tcomp.equals(this.targetComponent)) { 125 System.out.println("WARNING: The referee in the xml file (" + tcomp + ") does not match the directory name (" + this.targetComponent + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 126 } 127 128 String source = attributes.getValue(IApiXmlConstants.ATTR_ORIGIN); 129 idv = getIdVersion(source); 130 IComponentDescriptor sourceComponent = Factory.componentDescriptor(idv[0], idv[1]); 131 if (!sourceComponent.equals(this.referencingComponent)) { 132 System.out.println("WARNING: The origin in the xml file (" + sourceComponent + ") does not match the directory name (" + this.referencingComponent + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 133 } 134 135 // Track the current reference visibility 136 String visString = attributes.getValue(IApiXmlConstants.ATTR_REFERENCE_VISIBILITY); 137 try { 138 int vis = Integer.parseInt(visString); 139 enterVisibility(vis); 140 } catch (NumberFormatException e) { 141 // TODO: 142 enterVisibility(-1); 143 System.out.println("Internal error: invalid visibility: " + visString); //$NON-NLS-1$ 144 } 145 } else if (IApiXmlConstants.ELEMENT_TARGET.equals(name)) { 146 String qName = attributes.getValue(IApiXmlConstants.ATTR_TYPE); 147 String memberName = attributes.getValue(IApiXmlConstants.ATTR_MEMBER_NAME); 148 String signature = attributes.getValue(IApiXmlConstants.ATTR_SIGNATURE); 149 IMemberDescriptor member = null; 150 switch (type) { 151 case IReference.T_TYPE_REFERENCE: 152 member = Factory.typeDescriptor(qName); 153 break; 154 case IReference.T_METHOD_REFERENCE: 155 member = Factory.methodDescriptor(qName, memberName, signature); 156 break; 157 case IReference.T_FIELD_REFERENCE: 158 member = Factory.fieldDescriptor(qName, memberName); 159 break; 160 default: 161 break; 162 } 163 enterTargetMember(member); 164 } else if (IApiXmlConstants.REFERENCE_KIND.equals(name)) { 165 String value = attributes.getValue(IApiXmlConstants.ATTR_KIND); 166 if (value != null) { 167 try { 168 enterReferenceKind(Integer.parseInt(value)); 169 } catch (NumberFormatException e) { 170 // ERROR 171 System.out.println(NLS.bind("Internal error: invalid reference kind: {0}", value)); //$NON-NLS-1$ 172 } 173 } 174 } else if (IApiXmlConstants.ATTR_REFERENCE.equals(name)) { 175 String qName = attributes.getValue(IApiXmlConstants.ATTR_TYPE); 176 177 if (qName != null) { 178 179 String memberName = attributes.getValue(IApiXmlConstants.ATTR_MEMBER_NAME); 180 String signature = attributes.getValue(IApiXmlConstants.ATTR_SIGNATURE); 181 IMemberDescriptor origin = null; 182 if (signature != null) { 183 origin = Factory.methodDescriptor(qName, memberName, signature); 184 } else if (memberName != null) { 185 origin = Factory.fieldDescriptor(qName, memberName); 186 } else { 187 origin = Factory.typeDescriptor(qName); 188 } 189 String line = attributes.getValue(IApiXmlConstants.ATTR_LINE_NUMBER); 190 String flags = attributes.getValue(IApiXmlConstants.ATTR_FLAGS); 191 try { 192 int num = Integer.parseInt(line); 193 int flgs = 0; 194 if (flags != null) { 195 flgs = Integer.parseInt(flags); 196 } 197 setReference(Factory.referenceDescriptor(referencingComponent, origin, num, targetComponent, targetMember, referenceKind, flgs, visibility, parseMessages(attributes))); 198 } catch (NumberFormatException e) { 199 // TODO: 200 System.out.println("Internal error: invalid line number: " + line); //$NON-NLS-1$ 201 } 202 } else { 203 System.out.println(NLS.bind("Element {0} is missing type attribute and will be skipped", targetMember.getName())); //$NON-NLS-1$ 204 } 205 } 206 } 207 208 /** 209 * Parses the problem messages from the attributes 210 * 211 * @param attribs 212 * @return the messages or an empty array never <code>null</code> 213 * @since 1.1 214 */ parseMessages(Attributes attribs)215 protected String[] parseMessages(Attributes attribs) { 216 String msgs = attribs.getValue(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS); 217 String[] messages = null; 218 if (msgs != null) { 219 messages = msgs.split("\\,"); //$NON-NLS-1$ 220 } 221 return messages; 222 } 223 224 /** 225 * Resolves references from an API use scan rooted at the specified location 226 * in the file system in the given baseline. 227 * 228 * @param xmlLocation root of API use scan (XML directory). 229 * @param monitor progress monitor 230 * @param baseline API baseline to resolve references in 231 */ parse(String xmlLocation, IProgressMonitor monitor, UseScanVisitor usv)232 public void parse(String xmlLocation, IProgressMonitor monitor, UseScanVisitor usv) throws Exception { 233 if (xmlLocation == null) { 234 throw new Exception(SearchMessages.missing_xml_files_location); 235 } 236 visitor = usv; 237 File reportsRoot = new File(xmlLocation); 238 if (!reportsRoot.exists() || !reportsRoot.isDirectory()) { 239 throw new Exception(NLS.bind(SearchMessages.invalid_directory_name, xmlLocation)); 240 } 241 SubMonitor localmonitor = SubMonitor.convert(monitor, SearchMessages.UseScanParser_parsing, 8); 242 localmonitor.subTask(SearchMessages.UseReportConverter_collecting_dir_info); 243 File[] referees = getDirectories(reportsRoot); 244 localmonitor.split(1); 245 File[] origins = null; 246 File[] xmlfiles = null; 247 localmonitor.setWorkRemaining(referees.length); 248 visitor.visitScan(); 249 try { 250 SAXParser parser = getParser(); 251 // Treat each top level directory as a producer component 252 for (File referee : referees) { 253 if (referee.isDirectory()) { 254 String[] idv = getIdVersion(referee.getName()); 255 IComponentDescriptor tcomp = Factory.componentDescriptor(idv[0], idv[1]); 256 enterTargetComponent(tcomp); 257 if (visitReferencingComponent) { 258 259 // If the visitor returned true, treat sub-directories 260 // as consumer components 261 origins = getDirectories(referee); 262 origins = sort(origins); // sort to visit in determined 263 // order 264 for (File origin : origins) { 265 if (origin.isDirectory()) { 266 idv = getIdVersion(origin.getName()); 267 IComponentDescriptor rcomp = Factory.componentDescriptor(idv[0], idv[1]); 268 enterReferencingComponent(rcomp); 269 if (visitMembers) { 270 271 // If the visitor returned true, open all 272 // xml files in the directory and process 273 // them to find members 274 localmonitor.subTask(NLS.bind(SearchMessages.UseScanParser_analyzing_references, new String[] { origin.getName() })); 275 xmlfiles = Util.getAllFiles(origin, pathname -> pathname.isDirectory() || pathname.getName().endsWith(".xml")); //$NON-NLS-1$ 276 if (xmlfiles != null && xmlfiles.length > 0) { 277 xmlfiles = sort(xmlfiles); // sort to 278 // visit in 279 // determined 280 // order 281 for (File xmlfile : xmlfiles) { 282 ReferenceHandler handler = new ReferenceHandler(getTypeFromFileName(xmlfile)); 283 try (InputStream inputFile = new FileInputStream(xmlfile.getAbsoluteFile());) { 284 parser.parse(inputFile, handler); 285 } catch (SAXException | IOException e) { 286 ApiPlugin.log(e); 287 } 288 } 289 } 290 endMember(); 291 } 292 endReferencingComponent(); 293 } 294 } 295 } 296 localmonitor.split(1); 297 endComponent(); 298 } 299 } 300 } finally { 301 visitor.endVisitScan(); 302 localmonitor.done(); 303 } 304 } 305 306 /** 307 * Returns a parser 308 * 309 * @return default parser 310 * @throws Exception forwarded general exception that can be trapped in Ant 311 * builds 312 */ getParser()313 SAXParser getParser() throws Exception { 314 SAXParserFactory factory = SAXParserFactory.newInstance(); 315 try { 316 return factory.newSAXParser(); 317 } catch (ParserConfigurationException pce) { 318 throw new Exception(SearchMessages.UseReportConverter_pce_error_getting_parser, pce); 319 } catch (SAXException se) { 320 throw new Exception(SearchMessages.UseReportConverter_se_error_parser_handle, se); 321 } 322 } 323 324 /** 325 * @return the referencingComponent or <code>null</code> 326 */ getReferencingComponent()327 protected IComponentDescriptor getReferencingComponent() { 328 return referencingComponent; 329 } 330 331 /** 332 * @return the targetComponent or <code>null</code> 333 */ getTargetComponent()334 protected IComponentDescriptor getTargetComponent() { 335 return targetComponent; 336 } 337 338 /** 339 * @return the targetMember or <code>null</code> 340 */ getTargetMember()341 protected IMemberDescriptor getTargetMember() { 342 return targetMember; 343 } 344 345 /** 346 * @return the referenceKind 347 */ getReferenceKind()348 protected int getReferenceKind() { 349 return referenceKind; 350 } 351 352 /** 353 * @return the visibility 354 */ getVisibility()355 protected int getVisibility() { 356 return visibility; 357 } 358 359 /** 360 * Returns all the child directories from the given directory 361 * 362 * @param file 363 * @return 364 */ getDirectories(File file)365 File[] getDirectories(File file) { 366 File[] directories = file.listFiles((FileFilter) pathname -> pathname.isDirectory() && !pathname.isHidden()); 367 return directories; 368 } 369 370 /** 371 * Returns the {@link IReference} type from the file name 372 * 373 * @param xmlfile 374 * @return the type from the file name 375 */ getTypeFromFileName(File xmlfile)376 private int getTypeFromFileName(File xmlfile) { 377 if (xmlfile.getName().indexOf(XmlReferenceDescriptorWriter.TYPE_REFERENCES) > -1) { 378 return IReference.T_TYPE_REFERENCE; 379 } 380 if (xmlfile.getName().indexOf(XmlReferenceDescriptorWriter.METHOD_REFERENCES) > -1) { 381 return IReference.T_METHOD_REFERENCE; 382 } 383 return IReference.T_FIELD_REFERENCE; 384 } 385 enterTargetComponent(IComponentDescriptor component)386 public void enterTargetComponent(IComponentDescriptor component) { 387 boolean different = false; 388 if (targetComponent == null) { 389 different = true; 390 } else { 391 if (!targetComponent.equals(component)) { 392 different = true; 393 } 394 } 395 if (different) { 396 // end visit 397 endMember(); 398 endReferencingComponent(); 399 endComponent(); 400 401 // start next 402 targetComponent = component; 403 visitReferencingComponent = visitor.visitComponent(targetComponent); 404 } 405 } 406 enterReferencingComponent(IComponentDescriptor component)407 public void enterReferencingComponent(IComponentDescriptor component) { 408 boolean different = false; 409 if (referencingComponent == null) { 410 different = true; 411 } else { 412 if (!referencingComponent.equals(component)) { 413 different = true; 414 } 415 } 416 if (different) { 417 // end visit 418 endMember(); 419 endReferencingComponent(); 420 421 // start next 422 referencingComponent = component; 423 if (visitReferencingComponent) { 424 visitMembers = visitor.visitReferencingComponent(referencingComponent); 425 } 426 } 427 } 428 enterVisibility(int vis)429 public void enterVisibility(int vis) { 430 visibility = vis; 431 } 432 enterTargetMember(IMemberDescriptor member)433 public void enterTargetMember(IMemberDescriptor member) { 434 if (targetMember == null || !targetMember.equals(member)) { 435 endMember(); 436 targetMember = member; 437 if (visitReferencingComponent && visitMembers) { 438 visitReferences = visitor.visitMember(targetMember); 439 } 440 } 441 } 442 enterReferenceKind(int refKind)443 public void enterReferenceKind(int refKind) { 444 referenceKind = refKind; 445 } 446 setReference(IReferenceDescriptor reference)447 public void setReference(IReferenceDescriptor reference) { 448 if (visitReferencingComponent && visitMembers && visitReferences) { 449 visitor.visitReference(reference); 450 } 451 } 452 endMember()453 private void endMember() { 454 if (targetMember != null) { 455 if (visitReferencingComponent && visitMembers) { 456 visitor.endVisitMember(targetMember); 457 } 458 targetMember = null; 459 } 460 } 461 endReferencingComponent()462 private void endReferencingComponent() { 463 if (referencingComponent != null) { 464 if (visitReferencingComponent) { 465 visitor.endVisitReferencingComponent(referencingComponent); 466 } 467 referencingComponent = null; 468 } 469 } 470 endComponent()471 private void endComponent() { 472 if (targetComponent != null) { 473 visitor.endVisitComponent(targetComponent); 474 targetComponent = null; 475 } 476 } 477 478 /** 479 * Sorts the given files by name (not path). 480 * 481 * @param files 482 * @return sorted files 483 */ sort(File[] files)484 File[] sort(File[] files) { 485 List<File> sorted = new ArrayList<>(files.length + 2); 486 Collections.addAll(sorted, files); 487 488 Collections.sort(sorted, Util.filesorter); 489 return sorted.toArray(new File[sorted.size()]); 490 } 491 } 492