/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.core.build; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.text.Format; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.helpers.AccessibleByteArrayOutputStream; import org.eclipse.buckminster.core.helpers.BMProperties; import org.eclipse.buckminster.core.helpers.FileUtils; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.IOUtils; import org.eclipse.buckminster.runtime.MonitorUtils; import org.eclipse.core.resources.IFile; 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.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; /** * An Abstract Builder that emits the properties to some location. * * @author Thomas Hallgren */ public abstract class PropertiesEmitter extends AbstractBuckminsterBuilder implements IResourceChangeListener { /** * The path, relative to the project root, of the properties file. */ public static final String ARG_FILE = "file"; //$NON-NLS-1$ /** * File separator. Defaults to setting of system property * "file.separator" */ public static final String ARG_FILE_SEPARATOR = "file.separator"; //$NON-NLS-1$ /** * A boolean argument denoting wether or not the already present properties * should be retained (truncate == false) or discarded (truncate == true). */ public static final String ARG_TRUNCATE = "truncate"; //$NON-NLS-1$ public static final String DEFAULT_PROPERTY_FILE = "buckminster.properties"; //$NON-NLS-1$ private Map<String, String> arguments; private HashMap<String, Format> formatters; private Map<String, String> properties; private IFile propertyFile; public void doStartupOnIntialize() throws CoreException { ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); } @Override public void resourceChanged(IResourceChangeEvent event) { if (event.getType() != IResourceChangeEvent.POST_CHANGE || propertyFile == null) return; IResourceDelta propFileDelta = event.getDelta().findMember(propertyFile.getFullPath()); if (propFileDelta != null && (propFileDelta.getKind() & IResourceDelta.REMOVED) != 0) // // Someone removed our property file. Let's make sure it's rebuilt // this.forgetLastBuiltState(); } /** * Add a format that can be used when creating the keys in the emitted * properties. Before adding the <code>defaultFormat</code>, a check is made * if an alternative format has been provided in the <code>args</code> map * that was supplied to the <b>build</b> method. * * @param argKey * The key to use when obtaining the formatter. * @param defaultFormat * The default formatter. */ protected void addFormat(String argKey, Format defaultFormat) { String arg = this.getArgument(argKey); formatters.put(argKey, (arg == null) ? defaultFormat : new MessageFormat(arg)); } /** * Subclasses should implement this method to supply formatters for the key * of each of the properties that it intend to emit. * * @see #addFormat */ protected abstract void addFormatters(); /** * Add a property to the set of properties that will be emitted. The * formatKey must correspond to a previously added formatter. * * @param formatKey * @param keyArgs * @param value * @see #addFormat(String, Format) * @see #addFormatters() */ protected void addProperty(String formatKey, String[] keyArgs, String value) { properties.put(formatters.get(formatKey).format(keyArgs), value); } /** * Subclasses should implement this method to add the properties that it * wants to emit. * * @throws CoreException */ protected abstract void appendProperties() throws CoreException; @Override protected IProject[] doAutoBuild(Map<String, String> args, IProgressMonitor monitor) throws CoreException { return this.doFullBuild(args, monitor); } @Override protected IProject[] doCleanBuild(Map<String, String> args, IProgressMonitor monitor) throws CoreException { if (propertyFile != null) { monitor.beginTask(Messages.Deleting + propertyFile, 100); propertyFile.refreshLocal(IResource.DEPTH_ZERO, MonitorUtils.subMonitor(monitor, 50)); if (propertyFile.exists()) propertyFile.delete(false, false, MonitorUtils.subMonitor(monitor, 50)); monitor.done(); } return null; } @Override protected IProject[] doFullBuild(Map<String, String> args, IProgressMonitor monitor) throws CoreException { arguments = args; formatters = new HashMap<String, Format>(); String propertyFileName = this.getArgument(ARG_FILE); if (propertyFileName == null) propertyFileName = DEFAULT_PROPERTY_FILE; propertyFile = this.getProject().getFile(propertyFileName); boolean truncate = Boolean.parseBoolean(this.getArgument(ARG_TRUNCATE)); int ticks = 5; if (!truncate) ticks += 2; monitor.beginTask(null, ticks); monitor.subTask(Messages.Emitting_properties); propertyFile.refreshLocal(IResource.DEPTH_ZERO, MonitorUtils.subMonitor(monitor, 1)); Map<String, String> oldProps = null; if (propertyFile.exists()) { InputStream recentProperties = null; try { recentProperties = new BufferedInputStream(propertyFile.getContents()); oldProps = new BMProperties(recentProperties); MonitorUtils.worked(monitor, 2); } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { IOUtils.close(recentProperties); } } properties = new HashMap<String, String>(); if (!truncate && oldProps != null) properties.putAll(oldProps); this.addFormatters(); try { this.appendProperties(); MonitorUtils.worked(monitor, 2); AccessibleByteArrayOutputStream output = new AccessibleByteArrayOutputStream(); BMProperties.store(properties, output, Messages.Generated_by_Buckminster_Do_not_edit); if (oldProps != null) { if (!properties.equals(oldProps)) { propertyFile.setContents(output.getInputStream(), false, false, MonitorUtils.subMonitor(monitor, 1)); // Might stem from a project created on existing folders. // propertyFile.setDerived(true, MonitorUtils.subMonitor(monitor, 1)); } else MonitorUtils.worked(monitor, 2); } else { FileUtils.createFolder(propertyFile.getParent()); propertyFile.create(output.getInputStream(), false, MonitorUtils.subMonitor(monitor, 1)); propertyFile.setDerived(true, MonitorUtils.subMonitor(monitor, 1)); } } catch (IOException e) { throw BuckminsterException.wrap(e); } finally { monitor.done(); arguments = null; formatters = null; properties = null; // We retain propertyFile for the sake of cleaning. } return null; } @Override protected IProject[] doIncrementalBuild(Map<String, String> args, IProgressMonitor monitor) throws CoreException { return this.doFullBuild(args, monitor); } /** * Format the path according to value of <code>ARG_FILE_SEPARATOR</code>. * * @param path * path to format. * @return The formatted path. */ protected final String formatPath(IPath path) { String filesep = this.getArgument(ARG_FILE_SEPARATOR); if (filesep == null || filesep.length() == 0) return path.toOSString(); String portable = path.toPortableString(); if (filesep.equals("/")) //$NON-NLS-1$ return portable; return portable.replace('/', filesep.charAt(0)); } /** * Returns a value supplied in the <code>args</code> argument of the * <code>build</code> method or <code>null</code> if the given * <code>key</code> was not found. * * @param key * The key used when obtaining the value. * @return The value or <code>null</code>. */ protected final String getArgument(String key) { return arguments == null ? null : (String) arguments.get(key); } }