blob: 42700454abe1a3ebf3c12dba9ee2d645dc9c3e3b [file] [log] [blame]
/*******************************************************************************
* 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
}
}
}
}
}