/* * Created on 29 mai 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.properties.impl; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.bind.DataBindingException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; import net.sourceforge.pmd.eclipse.plugin.PMDPlugin; import net.sourceforge.pmd.eclipse.runtime.builder.PMDNature; import net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties; import net.sourceforge.pmd.eclipse.runtime.properties.IProjectPropertiesManager; import net.sourceforge.pmd.eclipse.runtime.properties.PropertiesException; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.ui.IWorkingSetManager; import org.eclipse.ui.PlatformUI; /** * This class manages the persistence of the ProjectProperies information structure * * @author Philippe Herlin * */ public class ProjectPropertiesManagerImpl implements IProjectPropertiesManager { private static final Logger log = Logger.getLogger(ProjectPropertiesManagerImpl.class); private static final String PROPERTIES_FILE = ".pmd"; private final Map<IProject, IProjectProperties> projectsProperties = new HashMap<IProject, IProjectProperties>(); private static final JAXBContext JAXB_CONTEXT = initJaxbContext(); private static JAXBContext initJaxbContext() { try { return JAXBContext.newInstance(ProjectPropertiesTO.class); } catch (JAXBException e) { throw new RuntimeException(e); } } /** * Load a project properties * * @param project a project */ public IProjectProperties loadProjectProperties(final IProject project) throws PropertiesException { log.debug("Loading project properties for project " + project.getName()); try { IProjectProperties projectProperties = this.projectsProperties.get(project); if (projectProperties == null) { log.debug("Creating new poject properties for " + project.getName()); projectProperties = new PropertiesFactoryImpl().newProjectProperties(project, this); final ProjectPropertiesTO to = readProjectProperties(project); fillProjectProperties(projectProperties, to); this.projectsProperties.put(project, projectProperties); } // if the ruleset is stored in the project always reload it if (projectProperties.isRuleSetStoredInProject()) { loadRuleSetFromProject(projectProperties); } // else resynchronize the ruleset else { final boolean needRebuild = synchronizeRuleSet(projectProperties); projectProperties.setNeedRebuild(projectProperties.isNeedRebuild() || needRebuild); } return projectProperties; } catch (CoreException e) { throw new PropertiesException("Core Exception when loading project properties for project " + project.getName(), e); } } /** * @see net.sourceforge.pmd.eclipse.runtime.properties.IProjectPropertiesManager#storeProjectProperties(net.sourceforge.pmd.eclipse.runtime.properties.IProjectProperties) */ public void storeProjectProperties(IProjectProperties projectProperties) throws PropertiesException { log.debug("Storing project properties for project " + projectProperties.getProject().getName()); try { if (projectProperties.isPmdEnabled()) { PMDNature.addPMDNature(projectProperties.getProject(), null); } else { PMDNature.removePMDNature(projectProperties.getProject(), null); } writeProjectProperties(projectProperties.getProject(), fillTransferObject(projectProperties)); projectsProperties.put(projectProperties.getProject(), projectProperties); } catch (CoreException e) { throw new PropertiesException("Core Exception when storing project properties for project " + projectProperties.getProject().getName(), e); } } @Override public void removeProjectProperties(IProject project) { this.projectsProperties.remove(project); } /** * Load the project rule set from the project ruleset * */ private void loadRuleSetFromProject(IProjectProperties projectProperties) throws PropertiesException { if (projectProperties.isRuleSetFileExist() && projectProperties.isNeedRebuild()) { log.debug("Loading ruleset from project ruleset file: " + projectProperties.getRuleSetFile()); try { final RuleSetFactory factory = new RuleSetFactory(); final File ruleSetFile = projectProperties.getResolvedRuleSetFile(); projectProperties.setProjectRuleSet(factory.createRuleSets(ruleSetFile.getPath()).getAllRuleSets()[0]); projectProperties.setNeedRebuild(false); } catch (RuleSetNotFoundException e) { PMDPlugin.getDefault().logError( "Project RuleSet cannot be loaded for project " + projectProperties.getProject().getName() + " using RuleSet file name " + projectProperties.getRuleSetFile() + ". Using the rules from properties.", e); } } } public ProjectPropertiesTO convertProjectPropertiesFromString(String properties) { try { Source source = new StreamSource(new StringReader(properties)); JAXBElement<ProjectPropertiesTO> element = JAXB_CONTEXT.createUnmarshaller().unmarshal(source, ProjectPropertiesTO.class); return element.getValue(); } catch (JAXBException e) { throw new DataBindingException(e); } } /** * Read a project properties from properties file * * @param project a project */ private ProjectPropertiesTO readProjectProperties(final IProject project) throws PropertiesException { ProjectPropertiesTO projectProperties = null; try { final IFile propertiesFile = project.getFile(PROPERTIES_FILE); if (propertiesFile.exists() && propertiesFile.isAccessible()) { String properties = IOUtils.toString(propertiesFile.getContents()); projectProperties = convertProjectPropertiesFromString(properties); } return projectProperties; } catch (IOException e) { throw new PropertiesException(e); } catch (CoreException e) { throw new PropertiesException(e); } } /** * Fill a properties information structure from a transfer object * * @param projectProperties a project properties data structure * @param to a project properties transfer object */ private void fillProjectProperties(IProjectProperties projectProperties, ProjectPropertiesTO to) throws PropertiesException, CoreException { if (to == null) { log.info("Project properties not found. Use default."); } else { final IWorkingSetManager workingSetManager = PlatformUI.getWorkbench().getWorkingSetManager(); projectProperties.setProjectWorkingSet(workingSetManager.getWorkingSet(to.getWorkingSetName())); projectProperties.setRuleSetFile(to.getRuleSetFile()); projectProperties.setRuleSetStoredInProject(to.isRuleSetStoredInProject()); projectProperties.setPmdEnabled(projectProperties.getProject().hasNature(PMDNature.PMD_NATURE)); projectProperties.setIncludeDerivedFiles(to.isIncludeDerivedFiles()); projectProperties.setViolationsAsErrors(to.isViolationsAsErrors()); projectProperties.setFullBuildEnabled(to.isFullBuildEnabled()); if (to.isRuleSetStoredInProject()) { loadRuleSetFromProject(projectProperties); } else { setRuleSetFromProperties(projectProperties, to.getRules()); } log.debug("Project properties loaded"); } } /** * Set the rule set from rule specs found in properties file * * @param rules array of selected rules */ private void setRuleSetFromProperties(IProjectProperties projectProperties, RuleSpecTO[] rules) throws PropertiesException { final RuleSet ruleSet = new RuleSet(); final RuleSet pluginRuleSet = PMDPlugin.getDefault().getPreferencesManager().getRuleSet(); int n = rules==null?0:rules.length; for (int i = 0; i < n; i++) { try { final Rule rule = pluginRuleSet.getRuleByName(rules[i].getName()); ruleSet.addRule(rule); } catch (RuntimeException e) { log.debug("The rule " + rules[i].getName() + " cannot be found. ignore."); } } ruleSet.addExcludePatterns(pluginRuleSet.getExcludePatterns()); ruleSet.addIncludePatterns(pluginRuleSet.getIncludePatterns()); projectProperties.setProjectRuleSet(ruleSet); } public String convertProjectPropertiesToString(ProjectPropertiesTO projectProperties) { try { Marshaller marshaller = JAXB_CONTEXT.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); StringWriter writer = new StringWriter(); writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); marshaller.marshal(projectProperties, writer); writer.write("\n"); return writer.toString(); } catch (JAXBException e) { throw new DataBindingException(e); } } /** * Save project properties * * @param project a project * @param projectProperties the project properties to save * @param monitor a progress monitor * @throws DAOException */ private void writeProjectProperties(final IProject project, final ProjectPropertiesTO projectProperties) throws PropertiesException { try { String writer = convertProjectPropertiesToString(projectProperties); final IFile propertiesFile = project.getFile(PROPERTIES_FILE); if (propertiesFile.exists() && propertiesFile.isAccessible()) { propertiesFile.setContents(new ByteArrayInputStream(writer.getBytes()), false, false, null); } else { propertiesFile.create(new ByteArrayInputStream(writer.getBytes()), false, null); } } catch (CoreException e) { throw new PropertiesException(e); } } /** * Fill in a transfer object from a project properties information structure * * @throws DAOException */ private ProjectPropertiesTO fillTransferObject(IProjectProperties projectProperties) throws PropertiesException { final ProjectPropertiesTO bean = new ProjectPropertiesTO(); bean.setRuleSetStoredInProject(projectProperties.isRuleSetStoredInProject()); bean.setRuleSetFile(projectProperties.getRuleSetFile()); bean.setWorkingSetName(projectProperties.getProjectWorkingSet() == null ? null : projectProperties.getProjectWorkingSet().getName()); bean.setIncludeDerivedFiles(projectProperties.isIncludeDerivedFiles()); bean.setViolationsAsErrors(projectProperties.violationsAsErrors()); bean.setFullBuildEnabled(projectProperties.isFullBuildEnabled()); if (!projectProperties.isRuleSetStoredInProject()) { final RuleSet ruleSet = projectProperties.getProjectRuleSet(); final List<RuleSpecTO> rules = new ArrayList<RuleSpecTO>(); for (Rule rule: ruleSet.getRules()) { rules.add(new RuleSpecTO(rule.getName(), rule.getRuleSetName())); // NOPMD:AvoidInstantiatingObjectInLoop } bean.setRules(rules.toArray(new RuleSpecTO[rules.size()])); bean.setExcludePatterns(ruleSet.getExcludePatterns().toArray(new String[ruleSet.getExcludePatterns().size()])); bean.setIncludePatterns(ruleSet.getIncludePatterns().toArray(new String[ruleSet.getIncludePatterns().size()])); } return bean; } /** * Check the project ruleset against the plugin ruleset and synchronize if * necessary * * @return true if the project ruleset has changed. * */ private boolean synchronizeRuleSet(IProjectProperties projectProperties) throws PropertiesException { log.debug("Synchronizing the project ruleset with the plugin ruleset"); final RuleSet pluginRuleSet = PMDPlugin.getDefault().getPreferencesManager().getRuleSet(); final RuleSet projectRuleSet = projectProperties.getProjectRuleSet(); boolean flChanged = false; if (!projectRuleSet.getRules().equals(pluginRuleSet.getRules())) { log.debug("The project ruleset is different from the plugin ruleset; synchronizing."); // 1-If rules have been deleted from preferences // delete them also from the project ruleset final Iterator<Rule> i = projectRuleSet.getRules().iterator(); while (i.hasNext()) { final Rule projectRule = i.next(); final Rule pluginRule = pluginRuleSet.getRuleByName(projectRule.getName()); if (pluginRule == null) { log.debug("The rule " + projectRule.getName() + " is not defined in the plugin ruleset. Remove it."); i.remove(); } } // 2-For all other rules, replace the current one by the plugin one final RuleSet ruleSet = new RuleSet(); ruleSet.setDescription(projectRuleSet.getDescription()); ruleSet.setName(projectRuleSet.getName()); for (Rule projectRule: projectRuleSet.getRules()) { final Rule pluginRule = pluginRuleSet.getRuleByName(projectRule.getName()); if (pluginRule != null) { // log.debug("Keeping rule " + projectRule.getName()); ruleSet.addRule(pluginRule); } } if (!ruleSet.getRules().equals(projectRuleSet.getRules())) { log.info("Set the project ruleset according to preferences."); projectProperties.setProjectRuleSet(ruleSet); flChanged = true; } log.debug("Ruleset for project " + projectProperties.getProject().getName() + " is now synchronized. " + (flChanged ? "Ruleset has changed" : "Ruleset has not changed")); } return flChanged; } }