| /******************************************************************************* |
| * Copyright (c) 2006 BEA Systems, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * wharley@bea.com - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.apt.core.internal.generatedfile; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.DataInputStream; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Map.Entry; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.jdt.apt.core.internal.AptPlugin; |
| import org.eclipse.jdt.apt.core.internal.util.ManyToMany; |
| |
| /** |
| * A bidirectional many-to-many map from parent files to generated files. |
| * This extends the functionality of ManyToMany by adding serialization. |
| * The object also tracks attributes of the generated files. |
| */ |
| public class GeneratedFileMap extends ManyToMany<IFile, IFile> { |
| |
| public enum Flags { |
| /** Non-source files, e.g., text or xml. */ |
| NONSOURCE; |
| } |
| |
| // Version 2 since Eclipse 3.3.1: add ability to track attributes of generated files |
| private static final int SERIALIZATION_VERSION = 2; |
| |
| private final IProject _proj; |
| |
| private final Map<IFile, Set<Flags>> _flags = new HashMap<IFile, Set<Flags>>(); |
| |
| public GeneratedFileMap(IProject proj) { |
| _proj = proj; |
| readState(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.apt.core.internal.util.ManyToMany#clear() |
| */ |
| @Override |
| public synchronized boolean clear() { |
| _flags.clear(); |
| return super.clear(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.apt.core.internal.util.ManyToMany#remove(java.lang.Object, java.lang.Object) |
| */ |
| @Override |
| public synchronized boolean remove(IFile key, IFile value) { |
| boolean removed = super.remove(key, value); |
| if (removed) { |
| if (!containsValue(value)) { |
| _flags.remove(value); |
| } |
| } |
| return removed; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.apt.core.internal.util.ManyToMany#removeKey(java.lang.Object) |
| */ |
| @Override |
| public synchronized boolean removeKey(IFile key) { |
| Set<IFile> values = getValues(key); |
| boolean removed = super.removeKey(key); |
| if (removed) { |
| for (IFile value : values) { |
| if (!containsValue(value)) { |
| _flags.remove(value); |
| } |
| } |
| } |
| return removed; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.apt.core.internal.util.ManyToMany#removeValue(java.lang.Object) |
| */ |
| @Override |
| public synchronized boolean removeValue(IFile value) { |
| boolean removed = super.removeValue(value); |
| if (removed) { |
| _flags.remove(value); |
| } |
| return removed; |
| } |
| |
| /** |
| * Clear the file dependencies and delete the serialized state. |
| * This will take effect even if the dirty bit is not set. |
| */ |
| public synchronized void clearState() { |
| clear(); |
| File state = getStateFile(_proj); |
| if (state != null) { |
| boolean successfullyDeleted = state.delete(); |
| if (!successfullyDeleted && state.exists()) { |
| AptPlugin.log(new IOException("Could not delete apt dependency state file"), //$NON-NLS-1$ |
| state.getPath()); |
| } |
| } |
| clearDirtyBit(); |
| } |
| |
| /** |
| * Convenience method, equivalent to put(key, value, [no flags]) |
| */ |
| @Override |
| public synchronized boolean put(IFile parent, IFile generated) { |
| return put(parent, generated, Collections.<Flags>emptySet()); |
| } |
| |
| /** |
| * Convenience method, equivalent to put(key, value, isSource ? [no flags] : [NONSOURCE]) |
| */ |
| public boolean put(IFile parent, IFile generated, boolean isSource) { |
| return put(parent, generated, isSource ? Collections.<Flags>emptySet() : EnumSet.of(Flags.NONSOURCE)); |
| } |
| |
| /** |
| * Add a parent-to-generated association and specify attributes for the generated file. |
| * The attributes are associated with the file, not the link: that is, a given generated |
| * file can only have one set of attributes, not a different set per parent. The attributes |
| * set in the most recent call will override those set in previous calls. |
| */ |
| public synchronized boolean put(IFile parent, IFile generated, Set<Flags> flags) { |
| if (flags.isEmpty()) { |
| _flags.remove(generated); |
| } |
| else { |
| _flags.put(generated, flags); |
| } |
| return super.put(parent, generated); |
| } |
| |
| public Set<Flags> getFlags(IFile generated) { |
| Set<Flags> flags = _flags.get(generated); |
| return flags == null ? Collections.<Flags>emptySet() : flags; |
| } |
| |
| /** |
| * Convenience method, equivalent to !getFlags(generated).contains(Flags.NONSOURCE) |
| * @return true if the generated file is a source (Java) file rather than text, xml, etc. |
| */ |
| public boolean isSource(IFile generated) { |
| return !getFlags(generated).contains(Flags.NONSOURCE); |
| } |
| |
| /** |
| * Utility method for serialization |
| */ |
| private String convertIFileToPath(IFile file) { |
| IPath path = file.getProjectRelativePath(); |
| return path.toOSString(); |
| } |
| |
| /** |
| * Utility method for deserialization |
| */ |
| private IFile convertPathToIFile(String projectRelativeString) { |
| IPath path = new Path(projectRelativeString); |
| return _proj.getFile(path); |
| } |
| |
| /** |
| * Returns the File to use for saving and restoring the last built state for the given project. |
| * Returns null if the project does not exists (e.g. has been deleted) |
| */ |
| private File getStateFile(IProject project) { |
| if (!project.exists()) return null; |
| IPath workingLocation = project.getWorkingLocation(AptPlugin.PLUGIN_ID); |
| return workingLocation.append("state.dat").toFile(); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Reads the last serialized build state into memory. This includes dependency |
| * information so that we do not need to do a clean build in order to recreate |
| * our dependencies. |
| * |
| * File format: |
| * |
| * int version |
| * int sizeOfMap |
| * String parentIFilePath |
| * int numberOfChildren |
| * String childIFilePath |
| * |
| * This method is not synchronized because it is called only from this object's constructor. |
| */ |
| private void readState() { |
| File file = getStateFile(_proj); |
| if (file == null || !file.exists()) { |
| // We'll just start with no dependencies |
| return; |
| } |
| DataInputStream in = null; |
| try { |
| in= new DataInputStream(new BufferedInputStream(new FileInputStream(file))); |
| int version = in.readInt(); |
| if (version != SERIALIZATION_VERSION) { |
| throw new IOException("Dependency map file version does not match. Expected " //$NON-NLS-1$ |
| + SERIALIZATION_VERSION + ", but found " + version); //$NON-NLS-1$ |
| } |
| int sizeOfMap = in.readInt(); |
| |
| // For each entry, we'll have a parent and a set of children, |
| // which we can drop into the parent -> child map. |
| for (int parentIndex=0; parentIndex<sizeOfMap; parentIndex++) { |
| String parentPath = in.readUTF(); |
| IFile parent = convertPathToIFile(parentPath); |
| int numChildren = in.readInt(); |
| for (int childIndex = 0; childIndex<numChildren; childIndex++) { |
| String childPath = in.readUTF(); |
| IFile child = convertPathToIFile(childPath); |
| // add the child to the parent->child map |
| put(parent, child); |
| } |
| } |
| |
| // Now the _flags map: |
| int sizeOfFlags = in.readInt(); |
| for (int i = 0; i < sizeOfFlags; ++i) { |
| String childPath = in.readUTF(); |
| IFile child = convertPathToIFile(childPath); |
| if (!containsValue(child)) { |
| throw new IOException("Error in generated file attributes: did not expect file " + childPath); //$NON-NLS-1$ |
| } |
| |
| int attributeCount = in.readInt(); |
| EnumSet<Flags> flags = EnumSet.noneOf(Flags.class); |
| for (int j = 0; j < attributeCount; ++j) { |
| String attr = in.readUTF(); |
| Flags f = Flags.valueOf(attr); |
| flags.add(f); |
| } |
| _flags.put(child, flags); |
| } |
| |
| // our serialized and in-memory states are now identical |
| clearDirtyBit(); |
| } |
| catch (IOException ioe) { |
| // Avoid partial initialization |
| clear(); |
| // We can safely continue without having read our dependencies. |
| AptPlugin.logWarning(ioe, "Could not read APT dependencies: generated files may not be deleted until the next clean"); //$NON-NLS-1$ |
| } |
| catch (IllegalArgumentException iae) { |
| // Avoid partial initialization |
| clear(); |
| // We can safely continue without having read our dependencies. |
| AptPlugin.logWarning(iae, "Could not read APT dependencies: generated files may not be deleted until the next clean"); //$NON-NLS-1$ |
| } |
| finally { |
| if (in != null) { |
| try {in.close();} catch (IOException ioe) {} |
| } |
| } |
| } |
| |
| /** |
| * Write our dependencies to disk. If not dirty, nothing is written. |
| */ |
| public synchronized void writeState() { |
| if (!isDirty()) { |
| return; |
| } |
| File file = getStateFile(_proj); |
| if (file == null) { |
| // Cannot write state, as project has been deleted |
| return; |
| } |
| file.delete(); |
| |
| DataOutputStream out = null; |
| try { |
| out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); |
| |
| out.writeInt(SERIALIZATION_VERSION); |
| |
| // Number of parent files |
| Set<IFile> parents = getKeySet(); |
| out.writeInt(parents.size()); |
| |
| // for each parent... |
| for (IFile parent : parents) { |
| |
| // ...parent name |
| out.writeUTF(convertIFileToPath(parent)); |
| |
| Set<IFile> children = getValues(parent); |
| |
| // ...number of children |
| out.writeInt(children.size()); |
| |
| // for each child... |
| for (IFile child : children) { |
| // ...child name. |
| out.writeUTF(convertIFileToPath(child)); |
| } |
| } |
| |
| // Number of generated files with attributes |
| out.writeInt(_flags.size()); |
| |
| // for each generated file that has attributes... |
| for (Entry<IFile, Set<Flags>> entry : _flags.entrySet()) { |
| // ...generated file name |
| out.writeUTF(convertIFileToPath(entry.getKey())); |
| |
| Set<Flags> flags = entry.getValue(); |
| // ...number of attributes |
| out.writeInt(flags.size()); |
| for (Flags f : flags) { |
| // ...attribute name |
| out.writeUTF(f.name()); |
| } |
| } |
| |
| // our serialized and in-memory states are now identical |
| clearDirtyBit(); |
| out.flush(); |
| } |
| catch (IOException ioe) { |
| // We can safely continue without having written our dependencies. |
| AptPlugin.logWarning(ioe, "Could not serialize APT dependencies"); //$NON-NLS-1$ |
| } |
| finally { |
| if (out != null) { |
| try { |
| out.close(); |
| } |
| catch (IOException ioe) { |
| // Do nothing |
| } |
| } |
| } |
| } |
| |
| |
| } |