/* * Contributions to FindBugs * Copyright (C) 2011, Andrei Loskutov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package de.tobject.findbugs.builder; import java.io.IOException; import java.io.PrintWriter; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.ui.console.IOConsoleOutputStream; import de.tobject.findbugs.FindbugsPlugin; import de.tobject.findbugs.reporter.Reporter; import de.tobject.findbugs.view.FindBugsConsole; import edu.umd.cs.findbugs.FindBugs2; import edu.umd.cs.findbugs.Footprint; import edu.umd.cs.findbugs.ProjectStats; import edu.umd.cs.findbugs.SortedBugCollection; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.classfile.IAnalysisCache; import edu.umd.cs.findbugs.classfile.IClassPath; import edu.umd.cs.findbugs.classfile.ICodeBaseEntry; import edu.umd.cs.findbugs.classfile.analysis.ClassData; import edu.umd.cs.findbugs.classfile.engine.ClassDataAnalysisEngine; import edu.umd.cs.findbugs.classfile.impl.AnalysisCache; import edu.umd.cs.findbugs.log.Profiler; import edu.umd.cs.findbugs.log.Profiler.Profile; public class FindBugs2Eclipse extends FindBugs2 { private static WeakHashMap<IProject, SoftReference<List<String>>> auxClassPaths = new WeakHashMap<IProject, SoftReference<List<String>>>(); private static WeakHashMap<IProject, SoftReference<Map<ClassDescriptor, Object>>> classAnalysisCache = new WeakHashMap<IProject, SoftReference<Map<ClassDescriptor, Object>>>(); private AnalysisCache analysisCache; private final IProject project; private final boolean cacheClassData; private final Reporter reporter; private static IResourceChangeListener resourceListener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { if(event.getSource() instanceof IProject || event.getResource() instanceof IProject) { cleanBuild((IProject) event.getSource()); } else if(event.getDelta() != null) { final Set<IProject> affectedProjects = new HashSet<IProject>(); IResourceDelta delta = event.getDelta(); try { delta.accept(new IResourceDeltaVisitor() { public boolean visit(IResourceDelta d1) throws CoreException { IResource resource = d1.getResource(); if(resource instanceof IProject) { affectedProjects.add((IProject) resource); return false; } return true; } }); } catch (CoreException e) { FindbugsPlugin.getDefault().logException(e, "Error traversing resource delta"); } for (IProject iProject : affectedProjects) { cleanBuild(iProject); } } } }; public FindBugs2Eclipse(IProject project, boolean cacheClassData, Reporter bugReporter) { super(); this.project = project; this.cacheClassData = cacheClassData; if(cacheClassData) { int eventMask = IResourceChangeEvent.POST_BUILD | IResourceChangeEvent.PRE_CLOSE; ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceListener, eventMask); } reporter = bugReporter; } @Override protected IAnalysisCache createAnalysisCache() throws IOException { IAnalysisCache cache = super.createAnalysisCache(); if(cache instanceof AnalysisCache) { analysisCache = (AnalysisCache)cache; if(cacheClassData) { reuseClassCache(); } } return cache; } @Override protected void clearCaches() { if(analysisCache != null) { postProcessCaches(); } super.clearCaches(); } @Override public void dispose() { if(analysisCache != null) { analysisCache.dispose(); analysisCache = null; } super.dispose(); } private void reuseClassCache() { SoftReference<Map<ClassDescriptor, Object>> wr = classAnalysisCache.get(project); Map<ClassDescriptor, Object> classAnalysis = wr != null? wr.get() : null; if(classAnalysis != null) { analysisCache.reuseClassAnalysis(ClassData.class, classAnalysis); // TODO would be nice to reuse ClassInfoAnalysisEngine: XClass.class, // JavaClassAnalysisEngine: JavaClass.class // but unfortunately there are side effects for analysis during data generation // which we can't have if we would just re-use the data } } private void postProcessCaches() { IClassPath classPath = analysisCache.getClassPath(); Map<ClassDescriptor, Object> classAnalysis = analysisCache.getClassAnalysis(ClassData.class); if(classAnalysis == null) { return; } Set<Entry<ClassDescriptor,Object>> entrySet = classAnalysis.entrySet(); AnalysisData data = new AnalysisData(); for (Entry<ClassDescriptor, Object> entry : entrySet) { data.classCount ++; if(!(entry.getValue() instanceof ClassData)) { continue; } ClassData cd = (ClassData) entry.getValue(); data.byteSize += cd.getData().length; } Set<Entry<String, ICodeBaseEntry>> entrySet2 = classPath.getApplicationCodebaseEntries().entrySet(); DescriptorFactory descriptorFactory = DescriptorFactory.instance(); for (Entry<String, ICodeBaseEntry> entry : entrySet2) { String className = entry.getKey(); if(className.endsWith(".class")) { className = className.substring(0, className.length() - 6); } classAnalysis.remove(descriptorFactory.getClassDescriptor(className)); data.byteSizeApp += entry.getValue().getNumBytes(); } if(cacheClassData) { // create new reference not reachable to anyone except us classAnalysis = new HashMap<ClassDescriptor, Object>(classAnalysis); classAnalysisCache.put(project, new SoftReference<Map<ClassDescriptor, Object>>(classAnalysis)); } reportExtraData(data); } @SuppressWarnings("boxing") private void reportExtraData(AnalysisData data) { SortedBugCollection bugCollection = reporter.getBugCollection(); if(bugCollection == null) { return; } if (FindBugsConsole.getConsole() == null) { return; } IOConsoleOutputStream out = FindBugsConsole.getConsole().newOutputStream(); PrintWriter pw = new PrintWriter(out); ProjectStats stats = bugCollection.getProjectStats(); Footprint footprint = new Footprint(stats.getBaseFootprint()); Profiler profiler = stats.getProfiler(); Profile profile = profiler.getProfile(ClassDataAnalysisEngine.class); long totalClassReadTime = TimeUnit.MILLISECONDS.convert(profile.getTotalTime(), TimeUnit.NANOSECONDS); long totalTime = TimeUnit.MILLISECONDS.convert(footprint.getClockTime(), TimeUnit.MILLISECONDS); double classReadSpeed = totalClassReadTime > 0? data.byteSize * 1000 / totalClassReadTime : 0; double classCountSpeed = totalTime > 0? data.classCount * 1000 / totalTime : 0; double classPart = totalTime > 0? totalClassReadTime * 100 / totalTime : 0; double appPart = data.byteSize > 0? data.byteSizeApp * 100 / data.byteSize : 0; double bytesPerClass = data.classCount > 0? data.byteSize / data.classCount : 0; long peakMemory = footprint.getPeakMemory() / (1024 * 1024); pw.printf("\n"); pw.printf("Peak memory (MB) : %1$ 20d \n", peakMemory); pw.printf("Total classes : %1$ 20d \n", data.classCount); pw.printf("Total time (msec) : %1$ 20d \n", totalTime); pw.printf("Class read time (msec): %1$ 20d \n", totalClassReadTime); pw.printf("Class read time (%%) : %1$ 20.0f \n", classPart); pw.printf("Total bytes read : %1$ 20d \n", data.byteSize); pw.printf("Application bytes : %1$ 20d \n", data.byteSizeApp); pw.printf("Application bytes (%%) : %1$ 20.0f \n", appPart); pw.printf("Avg. bytes per class : %1$ 20.0f \n", bytesPerClass); pw.printf("Analysis class/sec : %1$ 20.0f \n", classCountSpeed); pw.printf("Read bytes/sec : %1$ 20.0f \n", classReadSpeed); pw.printf(" MB/sec : %1$ 20.1f \n", classReadSpeed / (1024 * 1024)); pw.flush(); try { out.close(); } catch (IOException e) { // ignore } } static class AnalysisData { long byteSize; long byteSizeApp; long classCount; } static void cleanBuild(IProject project) { auxClassPaths.remove(project); classAnalysisCache.remove(project); } static void checkClassPathChanges(List<String> auxClassPath, IProject project) { SoftReference<List<String>> wr = auxClassPaths.get(project); List<String> oldAuxCp = wr != null ? wr.get() : null; if(oldAuxCp != null && !oldAuxCp.equals(auxClassPath)) { auxClassPaths.put(project, new SoftReference<List<String>>(new ArrayList<String>(auxClassPath))); classAnalysisCache.remove(project); } else if(oldAuxCp == null){ auxClassPaths.put(project, new SoftReference<List<String>>(new ArrayList<String>(auxClassPath))); } } }