| /******************************************************************************* |
| * Copyright (c) 2009, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.pde.api.tools.internal.search; |
| |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map.Entry; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.FactoryConfigurationError; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.pde.api.tools.internal.IApiCoreConstants; |
| import org.eclipse.pde.api.tools.internal.IApiXmlConstants; |
| import org.eclipse.pde.api.tools.internal.builder.Reference; |
| import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
| import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; |
| import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IFieldDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; |
| import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember; |
| import org.eclipse.pde.api.tools.internal.util.Signatures; |
| import org.eclipse.pde.api.tools.internal.util.Util; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * Writes reference descriptions to XML files. |
| * |
| * @since 1.0.1 |
| */ |
| public class XmlReferenceDescriptorWriter { |
| |
| /** |
| * file names for the output reference files |
| */ |
| public static final String TYPE_REFERENCES = "type_references"; //$NON-NLS-1$ |
| public static final String METHOD_REFERENCES = "method_references"; //$NON-NLS-1$ |
| public static final String FIELD_REFERENCES = "field_references"; //$NON-NLS-1$ |
| private static final Integer V_ILLEGAL = Integer.valueOf(VisibilityModifiers.ILLEGAL_API); |
| private String fLocation = null; |
| private HashMap<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> fReferenceMap = null; |
| private DocumentBuilder parser = null; |
| |
| /** |
| * Alternate API component where references were unresolved, or |
| * <code>null</code> if not to be reported. |
| */ |
| private IComponentDescriptor alternate; |
| |
| /** |
| * Constructor |
| * |
| * @param location the absolute path in the local file system to the folder |
| * to write the reports to |
| * @param debug if debugging infos should be written out to the console |
| */ |
| public XmlReferenceDescriptorWriter(String location) { |
| fLocation = location; |
| try { |
| parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| parser.setErrorHandler(new DefaultHandler()); |
| } catch (FactoryConfigurationError fce) { |
| ApiPlugin.log(fce); |
| } catch (ParserConfigurationException pce) { |
| ApiPlugin.log(pce); |
| } |
| } |
| |
| /** |
| * Writes the given references to XML files. |
| * |
| * @param references |
| */ |
| public void writeReferences(IReferenceDescriptor[] references) { |
| if (fLocation != null) { |
| try { |
| File parent = new File(fLocation); |
| if (!parent.exists()) { |
| parent.mkdirs(); |
| } |
| collateResults(references); |
| writeXML(parent); |
| } catch (Exception e) { |
| ApiPlugin.log(e); |
| } finally { |
| if (fReferenceMap != null) { |
| fReferenceMap.clear(); |
| fReferenceMap = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Collates the results into like reference kinds. If two references have |
| * the same reference, referencer, type, visibility, and member, one will be |
| * removed (even if the line numbers differ). Updates {@link #fReferenceMap} |
| * with a map based tree structure as follows: |
| * |
| * <pre> |
| * Returned Map (Referenced Component ID -> rmap) |
| * rmap (Referencing Component ID -> mmap) |
| * mmap (Visibility -> vmap) |
| * vmap (Reference Type -> tmap) |
| * tmap (Referenced Member -> Reference Descriptor) |
| * </pre> |
| * |
| * @param references |
| */ |
| private void collateResults(IReferenceDescriptor[] references) throws CoreException { |
| if (fReferenceMap == null) { |
| fReferenceMap = new HashMap<>(); |
| } |
| Integer type = null; |
| Integer visibility = null; |
| String id = null; |
| String tname = null; |
| HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> rmap = null; |
| HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> mmap = null; |
| HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> vmap = null; |
| HashMap<String, HashSet<IReferenceDescriptor>> tmap = null; |
| HashSet<IReferenceDescriptor> reflist = null; |
| IComponentDescriptor rcomponent = null; |
| IComponentDescriptor mcomponent = null; |
| for (int i = 0; i < references.length; i++) { |
| rcomponent = references[i].getReferencedComponent(); |
| id = getId(rcomponent); |
| rmap = fReferenceMap.get(id); |
| if (rmap == null) { |
| rmap = new HashMap<>(); |
| fReferenceMap.put(id, rmap); |
| } |
| mcomponent = references[i].getComponent(); |
| id = getId(mcomponent); |
| mmap = rmap.get(id); |
| if (mmap == null) { |
| mmap = new HashMap<>(); |
| rmap.put(id, mmap); |
| } |
| if ((references[i].getReferenceFlags() & IReference.F_ILLEGAL) > 0) { |
| visibility = V_ILLEGAL; |
| } else { |
| visibility = Integer.valueOf(references[i].getVisibility()); |
| } |
| vmap = mmap.get(visibility); |
| if (vmap == null) { |
| vmap = new HashMap<>(); |
| mmap.put(visibility, vmap); |
| } |
| type = Integer.valueOf(references[i].getReferenceType()); |
| tmap = vmap.get(type); |
| if (tmap == null) { |
| tmap = new HashMap<>(); |
| vmap.put(type, tmap); |
| } |
| tname = getText(references[i].getReferencedMember()); |
| reflist = tmap.get(tname); |
| if (reflist == null) { |
| reflist = new HashSet<>(); |
| tmap.put(tname, reflist); |
| } |
| reflist.add(references[i]); |
| } |
| } |
| |
| /** |
| * Resolves the id to use for the component in the mapping |
| * |
| * @param component |
| * @return the id to use for the component in the mapping, includes the |
| * version information as well |
| * @throws CoreException |
| */ |
| String getId(IComponentDescriptor component) throws CoreException { |
| StringBuilder buffer = new StringBuilder(); |
| buffer.append(component.getId()).append(" ").append('(').append(component.getVersion()).append(')'); //$NON-NLS-1$ |
| return buffer.toString(); |
| } |
| |
| /** |
| * Returns a formatted version of the references xml file name for use |
| * during conversion via the default XSLT file |
| * |
| * @param groupname |
| * @return a formatted version of the references file name |
| */ |
| private String getFormattedTypeName(String groupname) { |
| if (TYPE_REFERENCES.equals(groupname)) { |
| return "Types"; //$NON-NLS-1$ |
| } |
| if (METHOD_REFERENCES.equals(groupname)) { |
| return "Methods"; //$NON-NLS-1$ |
| } |
| if (FIELD_REFERENCES.equals(groupname)) { |
| return "Fields"; //$NON-NLS-1$ |
| } |
| return "unknown references"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns the name for the file of references base on the given type |
| * |
| * @param type |
| * @return |
| */ |
| private String getRefTypeName(int type) { |
| switch (type) { |
| case IReference.T_TYPE_REFERENCE: |
| return TYPE_REFERENCES; |
| case IReference.T_METHOD_REFERENCE: |
| return METHOD_REFERENCES; |
| case IReference.T_FIELD_REFERENCE: |
| return FIELD_REFERENCES; |
| default: |
| break; |
| } |
| return "unknown_reference_kinds"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Writes out the XML for the given api element using the collated |
| * {@link IReference}s |
| * |
| * @param parent |
| * @throws CoreException |
| * @throws FileNotFoundException |
| * @throws IOException |
| */ |
| private void writeXML(File parent) throws CoreException, FileNotFoundException, IOException { |
| HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> vismap = null; |
| HashMap<String, HashSet<IReferenceDescriptor>> typemap = null; |
| HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> rmap = null; |
| HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> mmap = null; |
| Integer type = null; |
| Integer vis = null; |
| String id = null; |
| String referee = null; |
| File root = null; |
| File location = null; |
| File base = null; |
| for (Iterator<Entry<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>>> iter = fReferenceMap.entrySet().iterator(); iter.hasNext();) { |
| Entry<String, HashMap<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> entry = iter.next(); |
| id = entry.getKey(); |
| referee = id; |
| base = new File(parent, id); |
| if (!base.exists()) { |
| base.mkdir(); |
| } |
| rmap = entry.getValue(); |
| for (Iterator<Entry<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>>> iter2 = rmap.entrySet().iterator(); iter2.hasNext();) { |
| Entry<String, HashMap<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> entry2 = iter2.next(); |
| id = entry2.getKey(); |
| root = new File(base, id); |
| if (!root.exists()) { |
| root.mkdir(); |
| } |
| mmap = entry2.getValue(); |
| for (Iterator<Entry<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>>> iter4 = mmap.entrySet().iterator(); iter4.hasNext();) { |
| Entry<Integer, HashMap<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> entry3 = iter4.next(); |
| vis = entry3.getKey(); |
| location = new File(root, VisibilityModifiers.getVisibilityName(vis.intValue())); |
| if (!location.exists()) { |
| location.mkdir(); |
| } |
| vismap = entry3.getValue(); |
| for (Iterator<Entry<Integer, HashMap<String, HashSet<IReferenceDescriptor>>>> iter3 = vismap.entrySet().iterator(); iter3.hasNext();) { |
| Entry<Integer, HashMap<String, HashSet<IReferenceDescriptor>>> entry4 = iter3.next(); |
| type = entry4.getKey(); |
| typemap = entry4.getValue(); |
| writeGroup(id, referee, location, getRefTypeName(type.intValue()), typemap, vis.intValue()); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Writes out a group of references under the newly created element with the |
| * given name |
| * |
| * @param origin the name of the bundle that has the references in it |
| * @param referee the name of the bundle that is referenced |
| * @param parent |
| * @param name |
| * @param map |
| * @param visibility |
| */ |
| private void writeGroup(String origin, String referee, File parent, String name, HashMap<String, HashSet<IReferenceDescriptor>> map, int visibility) throws CoreException, FileNotFoundException, IOException { |
| if (parent.exists()) { |
| BufferedWriter writer = null; |
| try { |
| Document doc = null; |
| Element root = null; |
| int count = 0; |
| File out = new File(parent, name + ".xml"); //$NON-NLS-1$ |
| if (out.exists()) { |
| try { |
| FileInputStream inputStream = null; |
| try { |
| inputStream = new FileInputStream(out); |
| doc = this.parser.parse(inputStream); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } finally { |
| if (inputStream != null) { |
| inputStream.close(); |
| } |
| } |
| if (doc == null) { |
| return; |
| } |
| root = doc.getDocumentElement(); |
| String value = root.getAttribute(IApiXmlConstants.ATTR_REFERENCE_COUNT); |
| count = Integer.parseInt(value); |
| } catch (SAXException se) { |
| se.printStackTrace(); |
| } |
| } else { |
| doc = Util.newDocument(); |
| root = doc.createElement(IApiXmlConstants.REFERENCES); |
| doc.appendChild(root); |
| root.setAttribute(IApiXmlConstants.ATTR_REFERENCE_VISIBILITY, Integer.toString(visibility)); |
| root.setAttribute(IApiXmlConstants.ATTR_ORIGIN, origin); |
| root.setAttribute(IApiXmlConstants.ATTR_REFEREE, referee); |
| root.setAttribute(IApiXmlConstants.ATTR_NAME, getFormattedTypeName(name)); |
| if (alternate != null) { |
| root.setAttribute(IApiXmlConstants.ATTR_ALTERNATE, getId(alternate)); |
| } |
| } |
| if (doc == null || root == null) { |
| return; |
| } |
| String tname = null; |
| HashSet<IReferenceDescriptor> refs = null; |
| Element telement = null; |
| for (Iterator<Entry<String, HashSet<IReferenceDescriptor>>> iter = map.entrySet().iterator(); iter.hasNext();) { |
| Entry<String, HashSet<IReferenceDescriptor>> entry = iter.next(); |
| tname = entry.getKey(); |
| telement = findTypeElement(root, tname); |
| if (telement == null) { |
| telement = doc.createElement(IApiXmlConstants.ELEMENT_TARGET); |
| telement.setAttribute(IApiXmlConstants.ATTR_NAME, tname); |
| root.appendChild(telement); |
| } |
| refs = entry.getValue(); |
| if (refs != null) { |
| for (Iterator<IReferenceDescriptor> iter2 = refs.iterator(); iter2.hasNext();) { |
| count++; |
| IReferenceDescriptor ref = iter2.next(); |
| writeReference(doc, telement, ref); |
| if (!iter2.hasNext()) { |
| // set qualified referenced attributes |
| IMemberDescriptor resolved = ref.getReferencedMember(); |
| if (resolved != null) { |
| addMemberDetails(telement, resolved); |
| } |
| } |
| } |
| } |
| } |
| root.setAttribute(IApiXmlConstants.ATTR_REFERENCE_COUNT, Integer.toString(count)); |
| writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out), IApiCoreConstants.UTF_8)); |
| writer.write(Util.serializeDocument(doc)); |
| writer.flush(); |
| } finally { |
| if (writer != null) { |
| writer.close(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add member descriptor details to the given element. |
| * |
| * @param element XML element |
| * @param member member to add details for |
| */ |
| private void addMemberDetails(Element element, IMemberDescriptor member) { |
| switch (member.getElementType()) { |
| case IElementDescriptor.TYPE: |
| element.setAttribute(IApiXmlConstants.ATTR_TYPE, ((IReferenceTypeDescriptor) member).getQualifiedName()); |
| break; |
| case IElementDescriptor.FIELD: |
| IReferenceTypeDescriptor encl = member.getEnclosingType(); |
| element.setAttribute(IApiXmlConstants.ATTR_TYPE, encl.getQualifiedName()); |
| element.setAttribute(IApiXmlConstants.ATTR_MEMBER_NAME, member.getName()); |
| break; |
| case IElementDescriptor.METHOD: |
| encl = member.getEnclosingType(); |
| element.setAttribute(IApiXmlConstants.ATTR_TYPE, encl.getQualifiedName()); |
| element.setAttribute(IApiXmlConstants.ATTR_MEMBER_NAME, member.getName()); |
| element.setAttribute(IApiXmlConstants.ATTR_SIGNATURE, ((IMethodDescriptor) member).getSignature()); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * gets the root kind element |
| * |
| * @param root |
| * @param kind |
| * @return |
| */ |
| private Element findTypeElement(Element root, String tname) { |
| if (tname == null) { |
| return null; |
| } |
| Element kelement = null; |
| NodeList nodes = root.getElementsByTagName(IApiXmlConstants.ELEMENT_TARGET); |
| for (int i = 0; i < nodes.getLength(); i++) { |
| kelement = (Element) nodes.item(i); |
| if (tname.equals(kelement.getAttribute(IApiXmlConstants.ATTR_NAME))) { |
| return kelement; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * gets the root kind element |
| * |
| * @param root |
| * @param kind |
| * @return |
| */ |
| private Element findKindElement(Element root, Integer kind) { |
| Element kelement = null; |
| NodeList nodes = root.getElementsByTagName(IApiXmlConstants.REFERENCE_KIND); |
| for (int i = 0; i < nodes.getLength(); i++) { |
| kelement = (Element) nodes.item(i); |
| if (kind.toString().equals(kelement.getAttribute(IApiXmlConstants.ATTR_KIND))) { |
| return kelement; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Writes the attributes from the given {@link IReference} into a new |
| * {@link Element} that is added to the given parent. |
| * |
| * @param document |
| * @param parent |
| * @param reference |
| */ |
| private void writeReference(Document document, Element parent, IReferenceDescriptor reference) throws CoreException { |
| Element kelement = null; |
| Integer kind = Integer.valueOf(reference.getReferenceKind()); |
| kelement = findKindElement(parent, kind); |
| if (kelement == null) { |
| kelement = document.createElement(IApiXmlConstants.REFERENCE_KIND); |
| kelement.setAttribute(IApiXmlConstants.ATTR_REFERENCE_KIND_NAME, Reference.getReferenceText(kind.intValue())); |
| kelement.setAttribute(IApiXmlConstants.ATTR_KIND, kind.toString()); |
| kelement.setAttribute(IApiXmlConstants.ATTR_FLAGS, Integer.toString(reference.getReferenceFlags())); |
| parent.appendChild(kelement); |
| } |
| Element relement = document.createElement(IApiXmlConstants.ATTR_REFERENCE); |
| IMemberDescriptor member = reference.getMember(); |
| relement.setAttribute(IApiXmlConstants.ATTR_ORIGIN, getText(member)); |
| String[] messages = reference.getProblemMessages(); |
| if (messages != null) { |
| relement.setAttribute(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS, getText(messages)); |
| } |
| // add detailed information about origin |
| addMemberDetails(relement, member); |
| member = reference.getReferencedMember(); |
| if (member != null) { |
| relement.setAttribute(IApiXmlConstants.ATTR_LINE_NUMBER, Integer.toString(reference.getLineNumber())); |
| kelement.appendChild(relement); |
| } |
| } |
| |
| /** |
| * Gets the {@link String} value of the given array by calling |
| * {@link #toString()} on each of the elements in the array. |
| * |
| * @param array the array to convert to a string |
| * @return the {@link String} or an empty {@link String} never |
| * <code>null</code> |
| * @since 1.1 |
| */ |
| String getText(Object[] array) { |
| StringBuilder buffer = new StringBuilder(); |
| for (int i = 0; i < array.length; i++) { |
| buffer.append(array[i].toString()); |
| if (i < array.length - 1) { |
| buffer.append(","); //$NON-NLS-1$ |
| } |
| } |
| return buffer.toString(); |
| } |
| |
| /** |
| * Returns the text to set in the attribute for the given {@link IApiMember} |
| * |
| * @param member |
| * @return |
| * @throws CoreException |
| */ |
| private String getText(IMemberDescriptor member) throws CoreException { |
| switch (member.getElementType()) { |
| case IElementDescriptor.TYPE: |
| return Signatures.getQualifiedTypeSignature((IReferenceTypeDescriptor) member); |
| case IElementDescriptor.METHOD: |
| return Signatures.getQualifiedMethodSignature((IMethodDescriptor) member); |
| case IElementDescriptor.FIELD: |
| return Signatures.getQualifiedFieldSignature((IFieldDescriptor) member); |
| default: |
| break; |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the alternate component where references were unresolved, or |
| * <code>null</code> if none. |
| * |
| * @param other component descriptor or <code>null</code> |
| */ |
| public void setAlternate(IComponentDescriptor other) { |
| alternate = other; |
| } |
| } |