/* * Contributions to FindBugs * Copyright (C) 2010, 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; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; import java.util.SortedSet; import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipOutputStream; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IContributor; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.osgi.framework.Bundle; import de.tobject.findbugs.io.IO; /** * Helper class to read contributions for the "detectorPlugins" extension point * * @author andrei */ public class DetectorsExtensionHelper { private static final String DEFAULT_USE_PLUGIN_JAR = "."; private static final String EXTENSION_POINT_ID = FindbugsPlugin.PLUGIN_ID + ".detectorPlugins"; private static final String LIBRARY_PATH = "libraryPath"; private static SortedSet<String> contributedDetectors; public static synchronized SortedSet<String> getContributedDetectors() { if (contributedDetectors != null) { return contributedDetectors; } TreeSet<String> set = new TreeSet<String>(); IExtensionRegistry registry = Platform.getExtensionRegistry(); IExtensionPoint point = registry.getExtensionPoint(EXTENSION_POINT_ID); if (point == null) { return set; } IExtension[] extensions = point.getExtensions(); for (IExtension extension : extensions) { IConfigurationElement[] elements = extension.getConfigurationElements(); for (IConfigurationElement configElt : elements) { String libPathAsString; IContributor contributor = null; try { contributor = configElt.getContributor(); libPathAsString = configElt.getAttribute(LIBRARY_PATH); if (libPathAsString == null || contributor == null) { throw new IllegalArgumentException("Null argument"); } libPathAsString = resolveRelativePath(contributor, libPathAsString); if (libPathAsString == null) { throw new IllegalArgumentException("Failed to resolve library path: " + libPathAsString); } set.add(libPathAsString); } catch (Throwable e) { String cName = contributor != null ? contributor.getName() : "unknown contributor"; String message = "Failed to read '" + LIBRARY_PATH + "' attribute for '" + EXTENSION_POINT_ID + "' extension point from " + cName; FindbugsPlugin.getDefault().logException(e, message); continue; } } } contributedDetectors = set; return contributedDetectors; } /** * * @param contributor * non null * @param libPathAsString * non null * @return resolved absolute path for the detector package */ @CheckForNull private static String resolveRelativePath(IContributor contributor, String libPathAsString) { String bundleName = contributor.getName(); Bundle bundle = Platform.getBundle(bundleName); if (bundle == null) { return null; } File bundleFile; try { bundleFile = FileLocator.getBundleFile(bundle); } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Failed to resolve detector library for " + bundle.getSymbolicName()); return null; } boolean runningInDebugger = Boolean.getBoolean("eclipse.pde.launch"); if (!DEFAULT_USE_PLUGIN_JAR.equals(libPathAsString)) { return new Path(bundleFile.getAbsolutePath()).append(libPathAsString).toOSString(); } if (!bundleFile.isDirectory()) { return bundleFile.getAbsolutePath(); } if (runningInDebugger) { // in case we are inside debugger & see bundle as directory return createTemporaryJar(bundleName, bundleFile); } // it's a directory, and we are in the production environment. IllegalArgumentException e = new IllegalArgumentException("Failed to resolve detector library for " + bundle.getSymbolicName()); String message = "Failed to resolve detector library. '" + bundleFile + "' is a directory and can't be used as FindBugs detector package." + " Please specify '" + LIBRARY_PATH + "' argument as a relative path to the detectors jar file."; FindbugsPlugin.getDefault().logException(e, message); return null; } /** * Used for Eclipse instances running inside debugger. FindBugs expects to * see *.jar files, but during development Eclipse plugins are just * directories. The code below makes a temporary jar file from the plugin's * "bin" directory. It doesn't work if the plugin build.properties are not * existing or contain invalid content. * * * @param bundleName * @param sourceDir * @return */ @CheckForNull private static String createTemporaryJar(String bundleName, File sourceDir) { if (sourceDir.listFiles() == null) { FindbugsPlugin.getDefault().logException(new IllegalStateException("No files in the bundle!"), "Failed to create temporary detector package for bundle " + sourceDir); return null; } String outputDir = getBuildDirectory(bundleName, sourceDir); if (outputDir.length() == 0) { FindbugsPlugin.getDefault().logException(new IllegalStateException("No output directory in build.properties"), "No output directory in build.properties " + sourceDir); return null; } File classDir = new File(sourceDir, outputDir); if (classDir.listFiles() == null) { FindbugsPlugin.getDefault().logException(new IllegalStateException("No files in the bundle output dir!"), "Failed to create temporary detector package for bundle " + sourceDir); return null; } File etcDir = new File(sourceDir, "etc"); if (etcDir.listFiles() == null) { FindbugsPlugin.getDefault().logException(new IllegalStateException("No files in the bundle etc dir!"), "Failed to create temporary detector package for bundle " + sourceDir); return null; } File jarFile; try { jarFile = File.createTempFile(bundleName + "_", ".jar"); jarFile.deleteOnExit(); } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Failed to create temporary detector package for bundle " + bundleName); return null; } ZipOutputStream jar = null; try { jar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile))); addFiles(bundleName, classDir, classDir, jar); try { addFiles(bundleName, etcDir, etcDir, jar); } catch (ZipException e) { // duplicated entries as files from /etc are on classpath already } } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Failed to create temporary detector package for bundle " + bundleName); return null; } finally { IO.closeQuietly(jar); } return jarFile.getAbsolutePath(); } /** * @return possible deployment root directory of a plugin project */ @Nonnull private static String getBuildDirectory(String bundleName, File sourceDir) { Properties props = new Properties(); File buildProps = new File(sourceDir, "build.properties"); if (buildProps.isFile()) { FileInputStream inStream = null; try { inStream = new FileInputStream(buildProps); props.load(inStream); } catch (IOException e) { FindbugsPlugin.getDefault().logException(e, "Failed to read build.properties for bundle " + bundleName); } finally { IO.closeQuietly(inStream); } } // this works only for plugins which are self-contained and do not // include // any external libraries return props.getProperty("output..", ""); } private static void addFiles(String name, File sourceDir, File root, ZipOutputStream zip) throws IOException { File[] files = sourceDir.listFiles(); if (files == null) { FindbugsPlugin.getDefault().logException(new IllegalStateException("No files for bundle in " + sourceDir), "Failed to create temporary detector package for bundle " + name); return; } byte[] buffer = new byte[1024]; int bytesRead; int prefixLength = root.getAbsolutePath().length() + 1; for (File file : files) { boolean directory = file.isDirectory(); if (directory) { String dirName = file.getName(); if (dirName.equalsIgnoreCase(".svn") || dirName.equalsIgnoreCase(".cvs") || dirName.equalsIgnoreCase(".hg")) { continue; } addFiles(name, file, root, zip); } else { String fileName = file.getAbsolutePath().substring(prefixLength); ZipEntry entry = new ZipEntry(fileName); entry.setMethod(ZipEntry.DEFLATED); zip.putNextEntry(entry); BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(file)); entry.setSize(file.length()); while ((bytesRead = bis.read(buffer)) != -1) { zip.write(buffer, 0, bytesRead); } } finally { IO.closeQuietly(bis); } } } } }