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