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