//============================================================================ // // Copyright (C) 2002-2016 David Schneider, Lars Ködderitzsch // // 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 net.sf.eclipsecs.core.config; import com.google.common.io.Closeables; import com.google.common.io.Files; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import net.sf.eclipsecs.core.CheckstylePlugin; import net.sf.eclipsecs.core.Messages; import net.sf.eclipsecs.core.config.configtypes.IConfigurationType; import net.sf.eclipsecs.core.util.CheckstylePluginException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.URIUtil; import org.eclipse.osgi.util.NLS; import org.xml.sax.InputSource; /** * This class acts as wrapper around check configurations to add editing aspects. Check * configurations by themself are not editable. * * @author Lars Ködderitzsch */ public class CheckConfigurationWorkingCopy implements ICheckConfiguration, Cloneable { /** The source check configuration of the working copy. */ private final ICheckConfiguration mCheckConfiguration; /** The working set this working copy belongs to. */ private final ICheckConfigurationWorkingSet mWorkingSet; /** The edited name of the configuration. */ private String mEditedName; /** The edited location of the configuration. */ private String mEditedLocation; /** The edited description of the configuration. */ private String mEditedDescription; /** The list of resolvable properties. */ private List<ResolvableProperty> mProperties = new ArrayList<>(); /** The map of additional data for this configuration. */ private Map<String, String> mAdditionalData = new HashMap<>(); /** flags if the configuration is dirty. */ private boolean mHasConfigChanged; /** * Creates a new working copy from an existing check configuration. * * @param checkConfigToEdit * the existing check configuration * @param workingSet * the working set this working copy belongs to */ public CheckConfigurationWorkingCopy(ICheckConfiguration checkConfigToEdit, ICheckConfigurationWorkingSet workingSet) { mCheckConfiguration = checkConfigToEdit; mWorkingSet = workingSet; mAdditionalData.putAll(checkConfigToEdit.getAdditionalData()); List<ResolvableProperty> props = checkConfigToEdit.getResolvableProperties(); for (ResolvableProperty prop : props) { mProperties.add(prop.clone()); } } /** * Creates a working copy for a new check configuration. * * @param configType * the type of the new configuration * @param workingSet * the working set this working copy belongs to * @param global * <code>true</code> if the new configuration is a global configuration */ public CheckConfigurationWorkingCopy(IConfigurationType configType, ICheckConfigurationWorkingSet workingSet, boolean global) { mWorkingSet = workingSet; mCheckConfiguration = new CheckConfiguration(null, null, null, configType, global, null, null); } /** * Returns the source check configuration of this working copy. * * @return the source check configuration */ public ICheckConfiguration getSourceCheckConfiguration() { return mCheckConfiguration; } /** * Changes the name of the check configuration. * * @param name * the new name * @throws CheckstylePluginException * if name is <code>null</code> or empty or a name collision with an existing check * configuration exists */ public void setName(String name) throws CheckstylePluginException { if (name == null || name.trim().length() == 0) { throw new CheckstylePluginException(Messages.errorConfigNameEmpty); } String oldName = getName(); if (!name.equals(oldName)) { mEditedName = name; // Check if the new name is in use if (mWorkingSet.isNameCollision(this)) { mEditedName = oldName; throw new CheckstylePluginException(NLS.bind(Messages.errorConfigNameInUse, name)); } } } /** * Changes the location of the Checkstyle configuration file. * * @param location * the new location of Checkstyle configuration file * @throws CheckstylePluginException * if location is <code>null</code> or empty or the Checkstyle configuration file cannot * be resolved */ public void setLocation(String location) throws CheckstylePluginException { if (location == null || location.trim().length() == 0) { throw new CheckstylePluginException(Messages.errorLocationEmpty); } String oldLocation = getLocation(); if (!location.equals(oldLocation)) { try { mEditedLocation = location; // test if configuration file exists getCheckstyleConfiguration(); } catch (Exception e) { mEditedLocation = oldLocation; CheckstylePluginException.rethrow(e, NLS.bind(Messages.errorResolveConfigLocation, location, e.getLocalizedMessage())); } } } /** * Sets a new description for the check configuration. * * @param description * the new description */ public void setDescription(String description) { String oldDescription = getDescription(); if (description == null || !description.equals(oldDescription)) { mEditedDescription = description; } } /** * Flags if the working copy changed compared to the original check configuration and needs to be * saved. * * @return <code>true</code> if the working copy has changes over the original check configuration */ public boolean isDirty() { return !this.equals(mCheckConfiguration); } /** * Determines if the checkstyle configuration of this working copy changed. This is used to * determine if specific projects need to rebuild afterwards. * * @return <code>true</code> if the checkstyle configuration changed. */ public boolean hasConfigurationChanged() { return mHasConfigChanged || !(Objects.equals(getLocation(), mCheckConfiguration.getLocation()) && Objects.equals(getResolvableProperties(), mCheckConfiguration.getResolvableProperties()) && Objects.equals(getAdditionalData(), mCheckConfiguration.getAdditionalData())); } /** * Reads the Checkstyle configuration file and builds the list of configured modules. Elements are * of type <code>net.sf.eclipsecs.core.config.Module</code>. * * @return the list of configured modules in this Checkstyle configuration * @throws CheckstylePluginException * error when reading the Checkstyle configuration file */ public List<Module> getModules() throws CheckstylePluginException { List<Module> result = null; InputSource in = null; try { in = getCheckstyleConfiguration().getCheckConfigFileInputSource(); result = ConfigurationReader.read(in); } finally { Closeables.closeQuietly(in.getByteStream()); } return result; } /** * Stores the (edited) list of modules to the Checkstyle configuration file. * * @param modules * the list of modules to store into the Checkstyle configuration file * @throws CheckstylePluginException * error storing the Checkstyle configuration */ public void setModules(List<Module> modules) throws CheckstylePluginException { try (ByteArrayOutputStream byteOut = new ByteArrayOutputStream();) { // First write to a byte array outputstream // because otherwise in an error case the original // file would be destroyed ConfigurationWriter.write(byteOut, modules, this); // all went ok, write to the file File configFile = URIUtil.toFile(getResolvedConfigurationFileURL().toURI()); Files.write(byteOut.toByteArray(), configFile); // refresh the files if within the workspace // Bug 1251194 - Resource out of sync after performing changes to // config IPath path = new Path(configFile.toString()); IFile[] files = CheckstylePlugin.getWorkspace().getRoot().findFilesForLocation(path); for (int i = 0; i < files.length; i++) { try { files[i].refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); } catch (CoreException e) { // NOOP - just ignore } } mHasConfigChanged = true; // throw away the cached Checkstyle configurations CheckConfigurationFactory.refresh(); } catch (IOException | URISyntaxException e) { CheckstylePluginException.rethrow(e); } } @Override public String getName() { return mEditedName != null ? mEditedName : getSourceCheckConfiguration().getName(); } @Override public String getDescription() { return mEditedDescription != null ? mEditedDescription : getSourceCheckConfiguration().getDescription(); } @Override public String getLocation() { return mEditedLocation != null ? mEditedLocation : getSourceCheckConfiguration().getLocation(); } @Override public IConfigurationType getType() { return getSourceCheckConfiguration().getType(); } @Override public Map<String, String> getAdditionalData() { return mAdditionalData; } @Override public List<ResolvableProperty> getResolvableProperties() { return mProperties; } @Override public URL getResolvedConfigurationFileURL() throws CheckstylePluginException { return getType().getResolvedConfigurationFileURL(this); } @Override public CheckstyleConfigurationFile getCheckstyleConfiguration() throws CheckstylePluginException { return getType().getCheckstyleConfiguration(this); } @Override public boolean isEditable() { return getType().isEditable(); } @Override public boolean isConfigurable() { return getType().isConfigurable(this); } @Override public boolean isGlobal() { return mCheckConfiguration.isGlobal(); } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof ICheckConfiguration)) { return false; } if (this == obj) { return true; } ICheckConfiguration rhs = (ICheckConfiguration) obj; return Objects.equals(getName(), rhs.getName()) && Objects.equals(getLocation(), rhs.getLocation()) && Objects.equals(getDescription(), rhs.getDescription()) && Objects.equals(getType(), rhs.getType()) && isGlobal() == rhs.isGlobal() && Objects.equals(getResolvableProperties(), rhs.getResolvableProperties()) && Objects.equals(getAdditionalData(), rhs.getAdditionalData()); } @Override public int hashCode() { return Objects.hash(getName(), getLocation(), getDescription(), getType(), isGlobal(), getResolvableProperties(), getAdditionalData()); } @Override public CheckConfigurationWorkingCopy clone() { CheckConfigurationWorkingCopy clone = null; try { clone = (CheckConfigurationWorkingCopy) super.clone(); clone.mAdditionalData = new HashMap<>(); clone.mAdditionalData.putAll(this.mAdditionalData); clone.mProperties = new ArrayList<>(); for (ResolvableProperty prop : mProperties) { clone.mProperties.add(prop.clone()); } } catch (CloneNotSupportedException e) { throw new InternalError(); // this should never happen } return clone; } }