/* * Created on 14 avr. 2005 * * Copyright (c) 2005, PMD for Eclipse Development Team * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The end-user documentation included with the redistribution, if * any, must include the following acknowledgement: * "This product includes software developed in part by support from * the Defense Advanced Research Project Agency (DARPA)" * * Neither the name of "PMD for Eclipse Development Team" nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.pmd.eclipse.runtime.cmd; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import name.herlin.command.CommandException; import net.sourceforge.pmd.cpd.CPD; import net.sourceforge.pmd.cpd.CPDConfiguration; import net.sourceforge.pmd.cpd.Language; import net.sourceforge.pmd.cpd.LanguageFactory; import net.sourceforge.pmd.cpd.Match; import net.sourceforge.pmd.cpd.Renderer; import net.sourceforge.pmd.eclipse.plugin.PMDPlugin; import net.sourceforge.pmd.eclipse.runtime.PMDRuntimeConstants; import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties; import net.sourceforge.pmd.eclipse.runtime.properties.PropertiesException; import net.sourceforge.pmd.eclipse.util.IOUtil; import net.sourceforge.pmd.util.StringUtil; import org.apache.log4j.Logger; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IPropertyListener; /** * This command produces a report of the Cut And Paste detector * * @author Philippe Herlin, Sven Jacob * */ public class DetectCutAndPasteCmd extends AbstractProjectCommand { private Language language; private int minTileSize; private Renderer renderer; private String reportName; private boolean createReport; private List<IPropertyListener> listeners; private static final long serialVersionUID = 1L; private static final Logger log = Logger.getLogger(DetectCutAndPasteCmd.class); /** * Default Constructor */ public DetectCutAndPasteCmd() { super("DetectCutAndPaste", "Detect Cut & paste for a project"); setOutputProperties(true); setReadOnly(false); setTerminated(false); listeners = new ArrayList<IPropertyListener>(); } private void notifyListeners(final CPD cpd) { // trigger event propertyChanged for all listeners Display.getDefault().asyncExec(new Runnable() { public void run() { for (IPropertyListener listener : listeners) { listener.propertyChanged(cpd.getMatches(), PMDRuntimeConstants.PROPERTY_CPD); } } }); } /** * @see name.herlin.command.AbstractProcessableCommand#execute() */ @Override public void execute() throws CommandException { try { List<File> files = findCandidateFiles(); if (files.isEmpty()) { logInfo("No files found for specified language."); } else { logInfo("Found " + files.size() + " files for the specified language. Performing CPD."); } setStepCount(files.size()); beginTask("Finding suspect Cut And Paste", getStepCount()*2); if (!isCanceled()) { final CPD cpd = detectCutAndPaste(files); if (!isCanceled()) { if (createReport) { renderReport(cpd.getMatches()); } notifyListeners(cpd); } } } catch (CoreException e) { log.debug("Core Exception: " + e.getMessage(), e); throw new CommandException(e); } catch (PropertiesException e) { log.debug("Properties Exception: " + e.getMessage(), e); throw new CommandException(e); } finally { setTerminated(true); } } /** * @see name.herlin.command.Command#reset() */ @Override public void reset() { super.reset(); setTerminated(false); setReportName(null); setRenderer(null); setLanguage("java"); setMinTileSize(PMDPlugin.getDefault().loadPreferences().getMinTileSize()); setCreateReport(false); addPropertyListener(null); listeners = new ArrayList<IPropertyListener>(); } /** * @param language The language to set. */ public void setLanguage(String theLanguage) { language = LanguageFactory.createLanguage(theLanguage); } /** * @param tilesize The tilesize to set. */ public void setMinTileSize(final int tilesize) { minTileSize = tilesize; } /** * @param renderer The renderer to set. */ public void setRenderer(final Renderer renderer) { this.renderer = renderer; } /** * @param reportName The reportName to set. */ public void setReportName(final String reportName) { this.reportName = reportName; } /** * @param render render a report or not. */ public void setCreateReport(final boolean render) { createReport = render; } /** * Adds an object that wants to get an event after the command is finished. * @param listener the property listener to set. */ public void addPropertyListener(IPropertyListener listener) { listeners.add(listener); } private boolean canRenderReport() { return renderer != null && StringUtil.isNotEmpty(reportName); } /** * @see name.herlin.command.Command#isReadyToExecute() */ @Override public boolean isReadyToExecute() { return super.isReadyToExecute() && language != null && (!createReport // need a renderer and reportName if a report should be created || canRenderReport()); } /** * Finds all files in a project based on a language. * Uses internally the CPDVisitor. * @return List of files * @throws PropertiesException * @throws CoreException */ private List<File> findCandidateFiles() throws PropertiesException, CoreException { final IProjectProperties properties = projectProperties(); final CPDVisitor visitor = new CPDVisitor(); visitor.setWorkingSet(properties.getProjectWorkingSet()); visitor.setIncludeDerivedFiles(properties.isIncludeDerivedFiles()); visitor.setLanguage(language); visitor.setFiles(new ArrayList<File>()); visitProjectResourcesWith(visitor); return visitor.getFiles(); } /** * Run the cut and paste detector. At first all files have to be added * to the cpd. Then the CPD can be executed. * @param files List of files to be checked. * @return the CPD itself for retrieving the matches. * @throws CoreException */ private CPD detectCutAndPaste(final List<File> files) { log.debug("Searching for project files"); final CPD cpd = newCPD(); subTask("Collecting files for CPD"); final Iterator<File> fileIterator = files.iterator(); while (fileIterator.hasNext() && !isCanceled()) { final File file = fileIterator.next(); try { cpd.add(file); worked(1); } catch (IOException e) { log.warn("IOException when adding file " + file.getName() + " to CPD. Continuing.", e); } } if (!isCanceled()) { subTask("Performing CPD"); log.debug("Performing CPD"); cpd.go(); worked(getStepCount()); } return cpd; } private CPD newCPD() { CPDConfiguration config = new CPDConfiguration(); config.setMinimumTileSize(minTileSize); config.setLanguage(language); config.setEncoding(System.getProperty("file.encoding")); return new CPD(config); } /** * Renders a report using the matches of the CPD. Creates a report folder * and report file. * @param matches matches of the CPD * @throws CommandException */ private void renderReport(Iterator<Match> matches) throws CommandException { InputStream contentsStream = null; try { log.debug("Rendering CPD report"); subTask("Rendering CPD report"); final String reportString = renderer.render(matches); // Create the report folder if not already existing log.debug("Create the report folder"); final IFolder folder = getProjectFolder(PMDRuntimeConstants.REPORT_FOLDER); if (!folder.exists()) { folder.create(true, true, getMonitor()); } // Create the report file log.debug("Create the report file"); final IFile reportFile = folder.getFile(reportName); contentsStream = new ByteArrayInputStream(reportString.getBytes()); if (reportFile.exists()) { log.debug(" Overwriting report file"); reportFile.setContents(contentsStream, true, false, getMonitor()); } else { log.debug(" Creating report file"); reportFile.create(contentsStream, true, getMonitor()); } reportFile.refreshLocal(IResource.DEPTH_INFINITE, getMonitor()); } catch (CoreException e) { log.debug("Core Exception: " + e.getMessage(), e); throw new CommandException(e); } finally { IOUtil.closeQuietly(contentsStream); } } }