/******************************************************************************* * Copyright (c) 2012 Google, Inc. * 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: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.eclipse.ui.convert; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; import java.util.Map.Entry; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaModelException; import com.windowtester.eclipse.ui.convert.preprocessor.BundleManifestReader; /** * Build a collection of all WindowTester API used by the selected project/package/class. * First {@link #scan(List, IProgressMonitor)} then {@link #getAPIUsageText()}. */ public class WTAPIUsage { private Collection<IJavaElement> visited = new HashSet<IJavaElement>(1000); private Map<String, Integer> apiUsed = new HashMap<String, Integer>(1000); private Collection<IJavaProject> projects = new HashSet<IJavaProject>(10); private int compUnitCount; private int wtCompUnitCount; private int exceptionCount; private Exception firstException; private String sourceWithException; //============================================================================== // Scanning /** * Recursively iterate over the specified java elements and their children to convert * each compilation to use the new WindowTester API. * * @param elements a collection of {@link IJavaElement}s to be converted (not * <code>null</code> and contains no <code>null</code>s) * @param monitor the progress monitor (not <code>null</code>) * @return a collection of API signatures */ public void scan(List<IJavaElement> elements, IProgressMonitor monitor) throws JavaModelException { compUnitCount = 0; wtCompUnitCount = 0; exceptionCount = 0; firstException = null; sourceWithException = null; monitor.beginTask("Scanning WindowTester tests", elements.size()); for (Iterator<IJavaElement> iter = elements.iterator(); iter.hasNext();) { scanElement(iter.next(), new SubProgressMonitor(monitor, 1)); monitor.worked(1); } monitor.done(); } /** * Recursively iterate over the specified java element and their children to convert * each compilation to use the new WindowTester API. * * @param elem the java element (not <code>null</code>) * @param monitor the progress monitor (not <code>null</code>) */ private void scanElement(IJavaElement elem, IProgressMonitor monitor) throws JavaModelException { projects.add(elem.getJavaProject()); switch (elem.getElementType()) { case IJavaElement.JAVA_PROJECT : scanProject((IJavaProject) elem, monitor); break; case IJavaElement.PACKAGE_FRAGMENT_ROOT : scanPackageRoot((IPackageFragmentRoot) elem, monitor); break; case IJavaElement.PACKAGE_FRAGMENT : scanPackage((IPackageFragment) elem, monitor); break; case IJavaElement.COMPILATION_UNIT : scanCompilationUnit((ICompilationUnit) elem); break; default : break; } } /** * Recursively iterate over the elements in the specified java element to convert each * compilation to use the new WindowTester API. * * @param proj the java project (not <code>null</code>) * @param monitor the progress monitor (not <code>null</code>) */ private void scanProject(IJavaProject proj, IProgressMonitor monitor) throws JavaModelException { if (visited.contains(proj)) return; visited.add(proj); IPackageFragmentRoot[] roots = proj.getPackageFragmentRoots(); monitor.beginTask("Scanning " + proj.getPath(), roots.length); for (int i = 0; i < roots.length; i++) { scanPackageRoot(roots[i], new SubProgressMonitor(monitor, 1)); monitor.worked(1); } monitor.done(); } /** * Recursively iterate over the elements in the specified java element to convert each * compilation to use the new WindowTester API. * * @param root the package fragment root (not <code>null</code>) * @param monitor the progress monitor (not <code>null</code>) */ private void scanPackageRoot(IPackageFragmentRoot root, IProgressMonitor monitor) throws JavaModelException { if (root.getKind() != IPackageFragmentRoot.K_SOURCE || visited.contains(root)) return; visited.add(root); IJavaElement[] children = root.getChildren(); monitor.beginTask("Scanning " + root.getPath(), children.length); for (int i = 0; i < children.length; i++) { scanElement(children[i], new SubProgressMonitor(monitor, 1)); monitor.worked(1); } monitor.done(); } /** * Recursively iterate over the elements in the specified java element to convert each * compilation to use the new WindowTester API. * * @param pkg the package fragment (not <code>null</code>) * @param monitor the progress monitor (not <code>null</code>) */ private void scanPackage(IPackageFragment pkg, IProgressMonitor monitor) throws JavaModelException { if (visited.contains(pkg)) return; visited.add(pkg); IJavaElement[] children = pkg.getChildren(); monitor.beginTask("Scanning " + pkg.getPath(), children.length); for (int i = 0; i < children.length; i++) { if (monitor.isCanceled()) throw new OperationCanceledException(); scanElement(children[i], new SubProgressMonitor(monitor, 1)); monitor.worked(1); } monitor.done(); } /** * Convert the specified compilation unit to use the new WindowTester API. * * @param compUnit the compilation unit (not <code>null</code>) */ private void scanCompilationUnit(ICompilationUnit compUnit) throws JavaModelException { if (visited.contains(compUnit)) return; visited.add(compUnit); compUnitCount++; try { parseScanResult(new WTConvertAPIContextBuilder().buildContext(compUnit)); } catch (Exception e) { exceptionCount++; if (firstException == null) { firstException = e; sourceWithException = compUnit.getSource(); } } } /** * Convert the specified compilation unit's source. This is used for testing purposes * and typically not called outside this class during the normal course of events * * @param source the compilation unit source * @return the context containing the converted source (not <code>null</code>) */ public void scanCompilationUnitSource(String source) { parseScanResult(new WTConvertAPIContextBuilder().buildContext(source)); } /** * Fold the results of the API scan into the report * * @param context the scan result (not <code>null</code>) */ private void parseScanResult(WTConvertAPIContext context) { if (context.getWTTypeNames().size() > 0) { wtCompUnitCount++; context.accept(new WTAPIUsageVisitor(context) { public void apiUsed(String signature) { Integer count = apiUsed.get(signature); apiUsed.put(signature, (count != null ? count : 0) + 1); } }); } } //============================================================================================== // Reporting /** * Return text describing what WindowTester APIs were detected * * @return a String (not <code>null</code>) */ public String getAPIUsageText() { StringWriter stringWriter = new StringWriter(2000); PrintWriter writer = new PrintWriter(stringWriter); printHeader(writer); printReferencedAPI(writer); printReferencedPlugins(writer); printExceptionIfAny(writer); return stringWriter.toString(); } private void printHeader(PrintWriter writer) { writer.println(); writer.println("WindowTester API Usage Report"); writer.println("=========================================================="); printInt(writer, projects.size()); writer.println("projects scanned"); printInt(writer, compUnitCount); writer.println("compilation units scanned"); printInt(writer, wtCompUnitCount); writer.println("compilation units scanned that referenced WindowTester types"); if (exceptionCount > 0) { printInt(writer, exceptionCount); writer.println("exceptions while scanning source"); } } private void printReferencedAPI(PrintWriter writer) { TreeSet<Map.Entry<String, Integer>> sorted = new TreeSet<Map.Entry<String, Integer>>( new Comparator<Map.Entry<String, Integer>>() { public int compare(Entry<String, Integer> entry1, Entry<String, Integer> entry2) { String line1 = entry1.getKey(); String line2 = entry2.getKey(); String type1 = line1.substring(0, line1.indexOf('#')); String type2 = line2.substring(0, line2.indexOf('#')); String package1 = type1.substring(0, type1.lastIndexOf('.')); String package2 = type2.substring(0, type2.lastIndexOf('.')); int delta = package1.compareToIgnoreCase(package2); if (delta == 0) { delta = type1.compareToIgnoreCase(type2); if (delta == 0) delta = line1.compareToIgnoreCase(line2); } return delta; } }); sorted.addAll(apiUsed.entrySet()); writer.println(); writer.println("API Usage:"); for (Map.Entry<String, Integer> entry : sorted) { Integer value = entry.getValue(); printInt(writer, value); writer.println(entry.getKey()); } } private void printReferencedPlugins(PrintWriter writer) { Collection<String> pluginIds = collectReferencedPlugins(writer); writer.println(); writer.println("Plugins:"); for (String id : new TreeSet<String>(pluginIds)) { writer.print(" "); writer.println(id); } } private Collection<String> collectReferencedPlugins(PrintWriter writer) { Collection<String> pluginIds = new HashSet<String>(); try { for (IJavaProject proj : projects) for (IClasspathEntry entry : proj.getRawClasspath()) collectPluginsReferencedByClasspathEntry(writer, pluginIds, proj, entry); } catch (JavaModelException e) { writer.println(); e.printStackTrace(writer); } catch (IOException e) { writer.println(); e.printStackTrace(writer); } return pluginIds; } private void collectPluginsReferencedByClasspathEntry(PrintWriter writer, Collection<String> pluginIds, IJavaProject proj, IClasspathEntry entry) throws IOException { IPath path = entry.getPath(); switch (entry.getEntryKind()) { case IClasspathEntry.CPE_LIBRARY : case IClasspathEntry.CPE_VARIABLE : for (int i = path.segmentCount() - 1; i >= 0; i--) { String segment = path.segment(i); if (segment.startsWith("com.windowtester.")) { String id = segment; i++; while (i < path.segmentCount()) id += "/" + path.segment(i++); pluginIds.add(id); break; } } break; case IClasspathEntry.CPE_CONTAINER : if (path.segmentCount() >= 1 && path.segment(0).equals("org.eclipse.pde.core.requiredPlugins")) collectPluginsReferencedInManifest(pluginIds, proj); break; case IClasspathEntry.CPE_SOURCE : case IClasspathEntry.CPE_PROJECT : // ignored break; default : pluginIds.add("unknown " + entry.getEntryKind() + " - " + entry); break; } } private void collectPluginsReferencedInManifest(Collection<String> pluginIds, IJavaProject proj) throws IOException { IPath location = proj.getResource().getLocation(); if (location != null) { File pluginXmlFile = null; for (File dir : location.toFile().listFiles()) { if (!dir.isDirectory() || !dir.getName().equalsIgnoreCase("META-INF")) continue; for (File file : dir.listFiles()) { String name = file.getName(); if (!name.equalsIgnoreCase("MANIFEST.MF")) { if (name.equalsIgnoreCase("plugin.xml")) pluginXmlFile = file; continue; } BundleManifestReader reader = new BundleManifestReader(); reader.process(file); for (String id : reader.getRequiredPlugins()) { if ( id.startsWith("com.windowtester.")) pluginIds.add(id); } return; } } if (pluginXmlFile != null) pluginIds.add("unknown plugins referenced in " + proj.getResource().getName() + "/" + pluginXmlFile.getName()); } } private void printExceptionIfAny(PrintWriter writer) { if (firstException != null) { writer.println(); writer.println("=========================================================="); firstException.printStackTrace(writer); writer.println("=========================================================="); writer.println(sourceWithException); } } private void printInt(PrintWriter writer, int value) { String count = Integer.toString(value); for (int i = 5 - count.length(); i > 0; i--) writer.print(" "); writer.print(count); writer.print(" "); } }