1 /******************************************************************************* 2 * Copyright (c) 2009, 2017 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.BufferedWriter; 17 import java.io.File; 18 import java.io.FileInputStream; 19 import java.io.FileNotFoundException; 20 import java.io.FileOutputStream; 21 import java.io.IOException; 22 import java.io.OutputStreamWriter; 23 import java.nio.charset.StandardCharsets; 24 import java.util.HashMap; 25 import java.util.HashSet; 26 import java.util.Iterator; 27 import java.util.Map.Entry; 28 29 import javax.xml.parsers.DocumentBuilder; 30 import javax.xml.parsers.DocumentBuilderFactory; 31 import javax.xml.parsers.FactoryConfigurationError; 32 import javax.xml.parsers.ParserConfigurationException; 33 34 import org.eclipse.core.runtime.CoreException; 35 import org.eclipse.pde.api.tools.internal.IApiXmlConstants; 36 import org.eclipse.pde.api.tools.internal.builder.Reference; 37 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 38 import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; 39 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; 40 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor; 41 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; 42 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IFieldDescriptor; 43 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; 44 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; 45 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; 46 import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember; 47 import org.eclipse.pde.api.tools.internal.util.Signatures; 48 import org.eclipse.pde.api.tools.internal.util.Util; 49 import org.w3c.dom.Document; 50 import org.w3c.dom.Element; 51 import org.w3c.dom.NodeList; 52 import org.xml.sax.SAXException; 53 import org.xml.sax.helpers.DefaultHandler; 54 55 /** 56 * Writes reference descriptions to XML files. 57 * 58 * @since 1.0.1 59 */ 60 public class XmlReferenceDescriptorWriter { 61 62 /** 63 * file names for the output reference files 64 */ 65 public static final String TYPE_REFERENCES = "type_references"; //$NON-NLS-1$ 66 public static final String METHOD_REFERENCES = "method_references"; //$NON-NLS-1$ 67 public static final String FIELD_REFERENCES = "field_references"; //$NON-NLS-1$ 68 private static final Integer V_ILLEGAL = Integer.valueOf(VisibilityModifiers.ILLEGAL_API); 69 private String fLocation = null; 70 private HashMap<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> fReferenceMap = null; 71 private DocumentBuilder parser = null; 72 73 /** 74 * Alternate API component where references were unresolved, or 75 * <code>null</code> if not to be reported. 76 */ 77 private IComponentDescriptor alternate; 78 79 /** 80 * Constructor 81 * 82 * @param location the absolute path in the local file system to the folder 83 * to write the reports to 84 * @param debug if debugging infos should be written out to the console 85 */ XmlReferenceDescriptorWriter(String location)86 public XmlReferenceDescriptorWriter(String location) { 87 fLocation = location; 88 try { 89 parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 90 parser.setErrorHandler(new DefaultHandler()); 91 } catch (FactoryConfigurationError | ParserConfigurationException pce) { 92 ApiPlugin.log(pce); 93 } 94 } 95 96 /** 97 * Writes the given references to XML files. 98 * 99 * @param references 100 */ writeReferences(IReferenceDescriptor[] references)101 public void writeReferences(IReferenceDescriptor[] references) { 102 if (fLocation != null) { 103 try { 104 File parent = new File(fLocation); 105 if (!parent.exists()) { 106 parent.mkdirs(); 107 } 108 collateResults(references); 109 writeXML(parent); 110 } catch (Exception e) { 111 ApiPlugin.log(e); 112 } finally { 113 if (fReferenceMap != null) { 114 fReferenceMap.clear(); 115 fReferenceMap = null; 116 } 117 } 118 } 119 } 120 121 /** 122 * Collates the results into like reference kinds. If two references have 123 * the same reference, referencer, type, visibility, and member, one will be 124 * removed (even if the line numbers differ). Updates {@link #fReferenceMap} 125 * with a map based tree structure as follows: 126 * 127 * <pre> 128 * Returned Map (Referenced Component ID -> rmap) 129 * rmap (Referencing Component ID -> mmap) 130 * mmap (Visibility -> vmap) 131 * vmap (Reference Type -> tmap) 132 * tmap (Referenced Member -> Reference Descriptor) 133 * </pre> 134 * 135 * @param references 136 */ collateResults(IReferenceDescriptor[] references)137 private void collateResults(IReferenceDescriptor[] references) throws CoreException { 138 if (fReferenceMap == null) { 139 fReferenceMap = new HashMap<>(); 140 } 141 Integer type = null; 142 Integer visibility = null; 143 String id = null; 144 String tname = null; 145 HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> rmap = null; 146 HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> mmap = null; 147 HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> vmap = null; 148 HashMap<String, HashSet<IReferenceDescriptor>> tmap = null; 149 HashSet<IReferenceDescriptor> reflist = null; 150 IComponentDescriptor rcomponent = null; 151 IComponentDescriptor mcomponent = null; 152 for (int i = 0; i < references.length; i++) { 153 rcomponent = references[i].getReferencedComponent(); 154 id = getId(rcomponent); 155 rmap = fReferenceMap.get(id); 156 if (rmap == null) { 157 rmap = new HashMap<>(); 158 fReferenceMap.put(id, rmap); 159 } 160 mcomponent = references[i].getComponent(); 161 id = getId(mcomponent); 162 mmap = rmap.get(id); 163 if (mmap == null) { 164 mmap = new HashMap<>(); 165 rmap.put(id, mmap); 166 } 167 if ((references[i].getReferenceFlags() & IReference.F_ILLEGAL) > 0) { 168 visibility = V_ILLEGAL; 169 } else { 170 visibility = Integer.valueOf(references[i].getVisibility()); 171 } 172 vmap = mmap.get(visibility); 173 if (vmap == null) { 174 vmap = new HashMap<>(); 175 mmap.put(visibility, vmap); 176 } 177 type = Integer.valueOf(references[i].getReferenceType()); 178 tmap = vmap.get(type); 179 if (tmap == null) { 180 tmap = new HashMap<>(); 181 vmap.put(type, tmap); 182 } 183 tname = getText(references[i].getReferencedMember()); 184 reflist = tmap.get(tname); 185 if (reflist == null) { 186 reflist = new HashSet<>(); 187 tmap.put(tname, reflist); 188 } 189 reflist.add(references[i]); 190 } 191 } 192 193 /** 194 * Resolves the id to use for the component in the mapping 195 * 196 * @param component 197 * @return the id to use for the component in the mapping, includes the 198 * version information as well 199 * @throws CoreException 200 */ getId(IComponentDescriptor component)201 String getId(IComponentDescriptor component) throws CoreException { 202 StringBuilder buffer = new StringBuilder(); 203 buffer.append(component.getId()).append(" ").append('(').append(component.getVersion()).append(')'); //$NON-NLS-1$ 204 return buffer.toString(); 205 } 206 207 /** 208 * Returns a formatted version of the references xml file name for use 209 * during conversion via the default XSLT file 210 * 211 * @param groupname 212 * @return a formatted version of the references file name 213 */ getFormattedTypeName(String groupname)214 private String getFormattedTypeName(String groupname) { 215 if (TYPE_REFERENCES.equals(groupname)) { 216 return "Types"; //$NON-NLS-1$ 217 } 218 if (METHOD_REFERENCES.equals(groupname)) { 219 return "Methods"; //$NON-NLS-1$ 220 } 221 if (FIELD_REFERENCES.equals(groupname)) { 222 return "Fields"; //$NON-NLS-1$ 223 } 224 return "unknown references"; //$NON-NLS-1$ 225 } 226 227 /** 228 * Returns the name for the file of references base on the given type 229 * 230 * @param type 231 * @return 232 */ getRefTypeName(int type)233 private String getRefTypeName(int type) { 234 switch (type) { 235 case IReference.T_TYPE_REFERENCE: 236 return TYPE_REFERENCES; 237 case IReference.T_METHOD_REFERENCE: 238 return METHOD_REFERENCES; 239 case IReference.T_FIELD_REFERENCE: 240 return FIELD_REFERENCES; 241 default: 242 break; 243 } 244 return "unknown_reference_kinds"; //$NON-NLS-1$ 245 } 246 247 /** 248 * Writes out the XML for the given api element using the collated 249 * {@link IReference}s 250 * 251 * @param parent 252 * @throws CoreException 253 * @throws FileNotFoundException 254 * @throws IOException 255 */ writeXML(File parent)256 private void writeXML(File parent) throws CoreException, FileNotFoundException, IOException { 257 HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> vismap = null; 258 HashMap<String, HashSet<IReferenceDescriptor>> typemap = null; 259 HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> rmap = null; 260 HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> mmap = null; 261 Integer type = null; 262 Integer vis = null; 263 String id = null; 264 String referee = null; 265 File root = null; 266 File location = null; 267 File base = null; 268 for (Iterator<Entry<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>>> iter = fReferenceMap.entrySet().iterator(); iter.hasNext();) { 269 Entry<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> entry = iter.next(); 270 id = entry.getKey(); 271 referee = id; 272 base = new File(parent, id); 273 if (!base.exists()) { 274 base.mkdir(); 275 } 276 rmap = entry.getValue(); 277 for (Iterator<Entry<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> iter2 = rmap.entrySet().iterator(); iter2.hasNext();) { 278 Entry<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> entry2 = iter2.next(); 279 id = entry2.getKey(); 280 root = new File(base, id); 281 if (!root.exists()) { 282 root.mkdir(); 283 } 284 mmap = entry2.getValue(); 285 for (Iterator<Entry<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> iter4 = mmap.entrySet().iterator(); iter4.hasNext();) { 286 Entry<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> entry3 = iter4.next(); 287 vis = entry3.getKey(); 288 location = new File(root, VisibilityModifiers.getVisibilityName(vis.intValue())); 289 if (!location.exists()) { 290 location.mkdir(); 291 } 292 vismap = entry3.getValue(); 293 for (Iterator<Entry<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> iter3 = vismap.entrySet().iterator(); iter3.hasNext();) { 294 Entry<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> entry4 = iter3.next(); 295 type = entry4.getKey(); 296 typemap = entry4.getValue(); 297 writeGroup(id, referee, location, getRefTypeName(type.intValue()), typemap, vis.intValue()); 298 } 299 } 300 } 301 } 302 } 303 304 /** 305 * Writes out a group of references under the newly created element with the 306 * given name 307 * 308 * @param origin the name of the bundle that has the references in it 309 * @param referee the name of the bundle that is referenced 310 * @param parent 311 * @param name 312 * @param map 313 * @param visibility 314 */ writeGroup(String origin, String referee, File parent, String name, HashMap<String, HashSet<IReferenceDescriptor>> map, int visibility)315 private void writeGroup(String origin, String referee, File parent, String name, HashMap<String, HashSet<IReferenceDescriptor>> map, int visibility) throws CoreException, FileNotFoundException, IOException { 316 if (parent.exists()) { 317 BufferedWriter writer = null; 318 try { 319 Document doc = null; 320 Element root = null; 321 int count = 0; 322 File out = new File(parent, name + ".xml"); //$NON-NLS-1$ 323 if (out.exists()) { 324 try { 325 FileInputStream inputStream = null; 326 try { 327 inputStream = new FileInputStream(out); 328 doc = this.parser.parse(inputStream); 329 } catch (IOException e) { 330 e.printStackTrace(); 331 } finally { 332 if (inputStream != null) { 333 inputStream.close(); 334 } 335 } 336 if (doc == null) { 337 return; 338 } 339 root = doc.getDocumentElement(); 340 String value = root.getAttribute(IApiXmlConstants.ATTR_REFERENCE_COUNT); 341 count = Integer.parseInt(value); 342 } catch (SAXException se) { 343 se.printStackTrace(); 344 } 345 } else { 346 doc = Util.newDocument(); 347 root = doc.createElement(IApiXmlConstants.REFERENCES); 348 doc.appendChild(root); 349 root.setAttribute(IApiXmlConstants.ATTR_REFERENCE_VISIBILITY, Integer.toString(visibility)); 350 root.setAttribute(IApiXmlConstants.ATTR_ORIGIN, origin); 351 root.setAttribute(IApiXmlConstants.ATTR_REFEREE, referee); 352 root.setAttribute(IApiXmlConstants.ATTR_NAME, getFormattedTypeName(name)); 353 if (alternate != null) { 354 root.setAttribute(IApiXmlConstants.ATTR_ALTERNATE, getId(alternate)); 355 } 356 } 357 if (doc == null || root == null) { 358 return; 359 } 360 String tname = null; 361 HashSet<IReferenceDescriptor> refs = null; 362 Element telement = null; 363 for (Iterator<Entry<String, HashSet<IReferenceDescriptor>>> iter = map.entrySet().iterator(); iter.hasNext();) { 364 Entry<String, HashSet<IReferenceDescriptor>> entry = iter.next(); 365 tname = entry.getKey(); 366 telement = findTypeElement(root, tname); 367 if (telement == null) { 368 telement = doc.createElement(IApiXmlConstants.ELEMENT_TARGET); 369 telement.setAttribute(IApiXmlConstants.ATTR_NAME, tname); 370 root.appendChild(telement); 371 } 372 refs = entry.getValue(); 373 if (refs != null) { 374 for (Iterator<IReferenceDescriptor> iter2 = refs.iterator(); iter2.hasNext();) { 375 count++; 376 IReferenceDescriptor ref = iter2.next(); 377 writeReference(doc, telement, ref); 378 if (!iter2.hasNext()) { 379 // set qualified referenced attributes 380 IMemberDescriptor resolved = ref.getReferencedMember(); 381 if (resolved != null) { 382 addMemberDetails(telement, resolved); 383 } 384 } 385 } 386 } 387 } 388 root.setAttribute(IApiXmlConstants.ATTR_REFERENCE_COUNT, Integer.toString(count)); 389 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), StandardCharsets.UTF_8)); 390 writer.write(Util.serializeDocument(doc)); 391 writer.flush(); 392 } finally { 393 if (writer != null) { 394 writer.close(); 395 } 396 } 397 } 398 } 399 400 /** 401 * Add member descriptor details to the given element. 402 * 403 * @param element XML element 404 * @param member member to add details for 405 */ addMemberDetails(Element element, IMemberDescriptor member)406 private void addMemberDetails(Element element, IMemberDescriptor member) { 407 switch (member.getElementType()) { 408 case IElementDescriptor.TYPE: 409 element.setAttribute(IApiXmlConstants.ATTR_TYPE, ((IReferenceTypeDescriptor) member).getQualifiedName()); 410 break; 411 case IElementDescriptor.FIELD: 412 IReferenceTypeDescriptor encl = member.getEnclosingType(); 413 element.setAttribute(IApiXmlConstants.ATTR_TYPE, encl.getQualifiedName()); 414 element.setAttribute(IApiXmlConstants.ATTR_MEMBER_NAME, member.getName()); 415 break; 416 case IElementDescriptor.METHOD: 417 encl = member.getEnclosingType(); 418 element.setAttribute(IApiXmlConstants.ATTR_TYPE, encl.getQualifiedName()); 419 element.setAttribute(IApiXmlConstants.ATTR_MEMBER_NAME, member.getName()); 420 element.setAttribute(IApiXmlConstants.ATTR_SIGNATURE, ((IMethodDescriptor) member).getSignature()); 421 break; 422 default: 423 break; 424 } 425 } 426 427 /** 428 * gets the root kind element 429 * 430 * @param root 431 * @param kind 432 * @return 433 */ findTypeElement(Element root, String tname)434 private Element findTypeElement(Element root, String tname) { 435 if (tname == null) { 436 return null; 437 } 438 Element kelement = null; 439 NodeList nodes = root.getElementsByTagName(IApiXmlConstants.ELEMENT_TARGET); 440 for (int i = 0; i < nodes.getLength(); i++) { 441 kelement = (Element) nodes.item(i); 442 if (tname.equals(kelement.getAttribute(IApiXmlConstants.ATTR_NAME))) { 443 return kelement; 444 } 445 } 446 return null; 447 } 448 449 /** 450 * gets the root kind element 451 * 452 * @param root 453 * @param kind 454 * @return 455 */ findKindElement(Element root, Integer kind)456 private Element findKindElement(Element root, Integer kind) { 457 Element kelement = null; 458 NodeList nodes = root.getElementsByTagName(IApiXmlConstants.REFERENCE_KIND); 459 for (int i = 0; i < nodes.getLength(); i++) { 460 kelement = (Element) nodes.item(i); 461 if (kind.toString().equals(kelement.getAttribute(IApiXmlConstants.ATTR_KIND))) { 462 return kelement; 463 } 464 } 465 return null; 466 } 467 468 /** 469 * Writes the attributes from the given {@link IReference} into a new 470 * {@link Element} that is added to the given parent. 471 * 472 * @param document 473 * @param parent 474 * @param reference 475 */ writeReference(Document document, Element parent, IReferenceDescriptor reference)476 private void writeReference(Document document, Element parent, IReferenceDescriptor reference) throws CoreException { 477 Element kelement = null; 478 Integer kind = Integer.valueOf(reference.getReferenceKind()); 479 kelement = findKindElement(parent, kind); 480 if (kelement == null) { 481 kelement = document.createElement(IApiXmlConstants.REFERENCE_KIND); 482 kelement.setAttribute(IApiXmlConstants.ATTR_REFERENCE_KIND_NAME, Reference.getReferenceText(kind.intValue())); 483 kelement.setAttribute(IApiXmlConstants.ATTR_KIND, kind.toString()); 484 kelement.setAttribute(IApiXmlConstants.ATTR_FLAGS, Integer.toString(reference.getReferenceFlags())); 485 parent.appendChild(kelement); 486 } 487 Element relement = document.createElement(IApiXmlConstants.ATTR_REFERENCE); 488 IMemberDescriptor member = reference.getMember(); 489 relement.setAttribute(IApiXmlConstants.ATTR_ORIGIN, getText(member)); 490 String[] messages = reference.getProblemMessages(); 491 if (messages != null) { 492 relement.setAttribute(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS, getText(messages)); 493 } 494 // add detailed information about origin 495 addMemberDetails(relement, member); 496 member = reference.getReferencedMember(); 497 if (member != null) { 498 relement.setAttribute(IApiXmlConstants.ATTR_LINE_NUMBER, Integer.toString(reference.getLineNumber())); 499 kelement.appendChild(relement); 500 } 501 } 502 503 /** 504 * Gets the {@link String} value of the given array by calling 505 * {@link #toString()} on each of the elements in the array. 506 * 507 * @param array the array to convert to a string 508 * @return the {@link String} or an empty {@link String} never 509 * <code>null</code> 510 * @since 1.1 511 */ getText(Object[] array)512 String getText(Object[] array) { 513 StringBuilder buffer = new StringBuilder(); 514 for (int i = 0; i < array.length; i++) { 515 buffer.append(array[i].toString()); 516 if (i < array.length - 1) { 517 buffer.append(","); //$NON-NLS-1$ 518 } 519 } 520 return buffer.toString(); 521 } 522 523 /** 524 * Returns the text to set in the attribute for the given {@link IApiMember} 525 * 526 * @param member 527 * @return 528 * @throws CoreException 529 */ getText(IMemberDescriptor member)530 private String getText(IMemberDescriptor member) throws CoreException { 531 switch (member.getElementType()) { 532 case IElementDescriptor.TYPE: 533 return Signatures.getQualifiedTypeSignature((IReferenceTypeDescriptor) member); 534 case IElementDescriptor.METHOD: 535 return Signatures.getQualifiedMethodSignature((IMethodDescriptor) member); 536 case IElementDescriptor.FIELD: 537 return Signatures.getQualifiedFieldSignature((IFieldDescriptor) member); 538 default: 539 break; 540 } 541 return null; 542 } 543 544 /** 545 * Sets the alternate component where references were unresolved, or 546 * <code>null</code> if none. 547 * 548 * @param other component descriptor or <code>null</code> 549 */ setAlternate(IComponentDescriptor other)550 public void setAlternate(IComponentDescriptor other) { 551 alternate = other; 552 } 553 } 554