blob: e04be0b02c6b39b85f00900de55393e31ec4833c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 University of Illinois at Urbana-Champaign and others.
* 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:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.rephraserengine.core.vpg.db.cdt;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.rephraserengine.core.util.Pair;
import org.eclipse.rephraserengine.core.vpg.IVPGComponentFactory;
import org.eclipse.rephraserengine.core.vpg.IVPGNode;
import org.eclipse.rephraserengine.core.vpg.NodeRef;
import org.eclipse.rephraserengine.core.vpg.VPGDB;
import org.eclipse.rephraserengine.core.vpg.VPGDependency;
import org.eclipse.rephraserengine.core.vpg.VPGEdge;
import org.eclipse.rephraserengine.core.vpg.VPGLog;
import org.eclipse.rephraserengine.internal.core.vpg.db.cdt.InternalCDTDB;
import org.eclipse.rephraserengine.internal.core.vpg.db.cdt.InternalCDTDB.IntVector;
/**
* Base class for a VPG database maintained on disk using the B-tree infrastructure from the Eclipse
* C/C++ Development Tool's indexer database.
* <p>
* This class is intended to be subclassed directly.
* <p>
* N.B. You <i>must</i> call {@link #close()} to flush the database to disk.
*
* @author Jeff Overbey
*
* @param <A> AST type
* @param <T> token type
* @param <R> {@link IVPGNode}/{@link NodeRef} type
*
* @since 8.0
*/
public abstract class CDTDB<A, T, R extends IVPGNode<T>>
extends VPGDB<A, T, R>
{
private InternalCDTDB db;
private File lock;
private final VPGLog<T, R> log;
/**
* @since 3.0
*/
public CDTDB(String filename, IVPGComponentFactory<A, T, R> locator, VPGLog<T, R> log)
{
this(new File(filename), locator, log);
}
/**
* @since 3.0
*/
public CDTDB(File file, IVPGComponentFactory<A, T, R> locator, VPGLog<T, R> log)
{
super(locator);
Assert.isNotNull(file);
try
{
this.log = log;
this.db = new InternalCDTDB(file);
this.lock = new File(file.getPath() + ".lock"); //$NON-NLS-1$
clearDBIfPossiblyCorrupted();
}
catch (CoreException e)
{
throw new Error("Unable to create VPG database " + file.getName(), e); //$NON-NLS-1$
}
}
/**
* Clears the database if a lock file exists, indicating that it might be corrupted.
* <p>
* A lock file is created when the database is started. If Eclipse is shut down cleanly, the
* lock file is deleted. However, if it is terminated abnormally, the lock file will still
* exist. This checks if the lock file exists, and, if so, clears the database, which will
* force the VPG to re-index all the source files in the workspace.
* <p>
* Note that only one instance of Eclipse can be open on a particular workspace, so it is not
* possible that the lock exists because the database is open in a different instance of Eclipse.
*/
private void clearDBIfPossiblyCorrupted()
{
if (lock.exists())
{
this.clearDatabase();
}
else
{
try
{
lock.createNewFile();
}
catch (IOException e)
{
//ignore
}
}
}
////////////////////////////////////////////////////////////////////////////
// VPG DATABASE METHODS
////////////////////////////////////////////////////////////////////////////
@Override public void flush()
{
try
{
db.flush();
}
catch (CoreException e)
{
throw new Error("Unable to flush VPG database to disk", e); //$NON-NLS-1$
}
}
@Override public void close()
{
try
{
db.close();
lock.delete();
}
catch (CoreException e)
{
throw new Error("Unable to close VPG database", e); //$NON-NLS-1$
}
}
@Override public void clearDatabase()
{
log.clear();
try
{
db.clear();
}
catch (CoreException e)
{
log.logError(e);
}
}
// HYPOTHETICAL UPDATING ///////////////////////////////////////////////////
private InternalCDTDB origDB = null;
@Override public void enterHypotheticalMode() throws IOException
{
assert !isInHypotheticalMode();
try
{
db.flush();
File copy = copyFile(db.getFile());
origDB = db;
db = new InternalCDTDB(copy);
}
catch (CoreException e)
{
throw new Error(e);
}
}
private static File copyFile(File orig) throws IOException
{
File tempFile = File.createTempFile("rephraser-tmp", "db"); //$NON-NLS-1$ //$NON-NLS-2$
tempFile.deleteOnExit();
FileInputStream fromFile = new FileInputStream(orig);
FileOutputStream toFile = new FileOutputStream(tempFile);
FileChannel from = fromFile.getChannel();
FileChannel to = toFile.getChannel();
try
{
to.transferFrom(from, 0, from.size());
}
finally
{
to.close();
from.close();
toFile.close();
fromFile.close();
}
return tempFile;
}
@Override public void leaveHypotheticalMode()
{
assert isInHypotheticalMode();
db = origDB;
origDB = null;
}
@Override public boolean isInHypotheticalMode()
{
return origDB != null;
}
// FILES ///////////////////////////////////////////////////////////////////
private int ensureEntryForFile(String filename) throws CoreException
{
return db.files.ensure(filename);
}
@Override public void updateModificationStamp(String filename)
{
try
{
db.files.setModificationStamp(ensureEntryForFile(filename), getModificationStamp(filename));
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public boolean isOutOfDate(String filename)
{
try
{
long storedModificationStamp = db.files.getModificationStamp(ensureEntryForFile(filename));
//System.out.println((storedModificationStamp < getModificationStamp(filename)) + " - " + storedModificationStamp + " " + getModificationStamp(filename) + " - " + filename);
return storedModificationStamp < getModificationStamp(filename);
}
catch (CoreException e)
{
return true;
}
}
protected abstract long getModificationStamp(String filename);
/**
* Deletes all dependencies, edges, and token annotations that reference
* a token in the given file
*
* @param filename (non-null)
*/
@Override public void deleteAllEntriesFor(String filename)
{
try
{
db.dependencies.deleteAllIncomingDependenciesTo(filename);
db.dependencies.deleteAllOutgoingDependenciesFrom(filename);
db.edges.deleteAllIncomingEdgesTo(filename);
db.edges.deleteAllOutgoingEdgesFrom(filename);
db.annotations.deleteAllAnnotationsFor(filename);
db.files.delete(filename);
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public void deleteAllEdgesAndAnnotationsFor(String filename)
{
try
{
db.edges.deleteAllIncomingEdgesTo(filename);
db.edges.deleteAllOutgoingEdgesFrom(filename);
db.annotations.deleteAllAnnotationsFor(filename);
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public void deleteAllIncomingDependenciesFor(String filename)
{
try
{
db.dependencies.deleteAllIncomingDependenciesTo(filename);
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public void deleteAllOutgoingDependenciesFor(String filename)
{
try
{
db.dependencies.deleteAllOutgoingDependenciesFrom(filename);
}
catch (CoreException e)
{
log.logError(e);
}
}
private void cleanUp(String filename)
{
try
{
if (db.dependencies.hasIncomingDependencyRecords(filename)) return;
if (db.dependencies.hasOutgoingDependencyRecords(filename)) return;
if (db.edges.hasIncomingEdges(filename)) return;
if (db.edges.hasOutgoingEdges(filename)) return;
if (db.annotations.hasAnnotations(filename)) return;
db.files.delete(filename);
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public Iterable<String> listAllFilenames()
{
try
{
return db.files.getAllFilenames();
}
catch (CoreException e)
{
log.logError(e);
return Collections.<String>emptyList();
}
}
@Override public Iterable<String> listAllFilenamesWithDependents()
{
try
{
return db.dependencies.listAllFilenamesWithDependents();
}
catch (CoreException e)
{
log.logError(e);
return Collections.<String>emptyList();
}
}
@Override public Iterable<String> listAllDependentFilenames()
{
try
{
return db.dependencies.listAllDependentFilenames();
}
catch (CoreException e)
{
log.logError(e);
return Collections.<String>emptyList();
}
}
// DEPENDENCIES ////////////////////////////////////////////////////////////
@Override public void ensure(VPGDependency<A, T, R> dependency)
{
try
{
db.dependencies.ensure(dependency.getDependentFile(), dependency.getDependsOnFile());
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public void delete(VPGDependency<A, T, R> dependency)
{
try
{
String dependentFile = dependency.getDependentFile();
String dependsOnFile = dependency.getDependsOnFile();
db.dependencies.delete(dependentFile, dependsOnFile);
cleanUp(dependentFile);
cleanUp(dependsOnFile);
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public Iterable<String> getOutgoingDependenciesFrom(String filename)
{
try
{
final IntVector records = db.dependencies.findAllOutgoingDependencyRecordsFrom(filename);
return new Iterable<String>()
{
public Iterator<String> iterator()
{
return new Iterator<String>()
{
private int nextRecord = 0;
private int numRecords = records.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public String next()
{
try
{
return db.files.getFilename(db.dependencies.getDependsOnFileRecordPtr(records.get(nextRecord++))).getString();
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<String>();
}
}
@Override public Iterable<String> getIncomingDependenciesTo(String filename)
{
try
{
final IntVector records = db.dependencies.findAllIncomingDependencyRecordsTo(filename);
return new Iterable<String>()
{
public Iterator<String> iterator()
{
return new Iterator<String>()
{
private int nextRecord = 0;
private int numRecords = records.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public String next()
{
try
{
return db.files.getFilename(db.dependencies.getDependentFileRecordPtr(records.get(nextRecord++))).getString();
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<String>();
}
}
// EDGES ///////////////////////////////////////////////////////////////////
@Override public void ensure(VPGEdge<A, T, R> edge)
{
try
{
R source = edge.getSource();
R sink = edge.getSink();
db.edges.ensure(source.getFilename(), source.getOffset(), source.getLength(), sink.getFilename(), sink.getOffset(), sink.getLength(), edge.getType());
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public void delete(VPGEdge<A, T, R> edge)
{
try
{
R source = edge.getSource();
R sink = edge.getSink();
db.edges.delete(source.getFilename(), source.getOffset(), source.getLength(), sink.getFilename(), sink.getOffset(), sink.getLength(), edge.getType());
cleanUp(source.getFilename());
cleanUp(sink.getFilename());
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public Iterable<? extends VPGEdge<A, T, R>> getAllEdgesFor(String filename)
{
try
{
final IntVector inRecords = db.edges.findAllIncomingEdgeRecordsTo(filename);
final IntVector outRecords = db.edges.findAllOutgoingEdgeRecordsFrom(filename);
return new Iterable<VPGEdge<A, T, R>>()
{
public Iterator<VPGEdge<A, T, R>> iterator()
{
return new Iterator<VPGEdge<A, T, R>>()
{
private int nextRecord = 0;
private int numRecords = inRecords.size() + outRecords.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public VPGEdge<A, T, R> next()
{
try
{
IntVector records = (nextRecord < inRecords.size() ? inRecords : outRecords);
int recordNum = (nextRecord < inRecords.size() ? nextRecord : nextRecord-inRecords.size());
R fromRef = factory.getVPGNode(db.files.getFilename(db.edges.getFromFileRecordPtr(records.get(recordNum))).getString(), db.edges.getFromOffset(records.get(recordNum)), db.edges.getFromLength(records.get(recordNum)));
R toRef = factory.getVPGNode(db.files.getFilename(db.edges.getToFileRecordPtr(records.get(recordNum))).getString(), db.edges.getToOffset(records.get(recordNum)), db.edges.getToLength(records.get(recordNum)));
VPGEdge<A, T, R> result = new VPGEdge<A,T,R>(fromRef, toRef, db.edges.getEdgeType(records.get(recordNum)));
nextRecord++;
return result;
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<VPGEdge<A, T, R>>();
}
}
@Override public Iterable<VPGEdge<A, T, R>> getOutgoingEdgesFrom(R tokenRef, int edgeType)
{
try
{
final IntVector records =
edgeType == ALL_EDGES
? db.edges.findAllOutgoingEdgeRecordsFrom(tokenRef.getFilename(), tokenRef.getOffset(), tokenRef.getLength())
: db.edges.findAllOutgoingEdgeRecordsFrom(tokenRef.getFilename(), tokenRef.getOffset(), tokenRef.getLength(), edgeType);
return new Iterable<VPGEdge<A, T, R>>()
{
public Iterator<VPGEdge<A, T, R>> iterator()
{
return new Iterator<VPGEdge<A, T, R>>()
{
private int nextRecord = 0;
private int numRecords = records.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public VPGEdge<A, T, R> next()
{
try
{
R fromRef = factory.getVPGNode(db.files.getFilename(db.edges.getFromFileRecordPtr(records.get(nextRecord))).getString(), db.edges.getFromOffset(records.get(nextRecord)), db.edges.getFromLength(records.get(nextRecord)));
R toRef = factory.getVPGNode(db.files.getFilename(db.edges.getToFileRecordPtr(records.get(nextRecord))).getString(), db.edges.getToOffset(records.get(nextRecord)), db.edges.getToLength(records.get(nextRecord)));
return new VPGEdge<A,T,R>(fromRef, toRef, db.edges.getEdgeType(records.get(nextRecord++)));
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<VPGEdge<A, T, R>>();
}
}
@Override public Iterable<VPGEdge<A, T, R>> getIncomingEdgesTo(R tokenRef, int edgeType)
{
try
{
final IntVector records =
edgeType == ALL_EDGES
? db.edges.findAllIncomingEdgeRecordsTo(tokenRef.getFilename(), tokenRef.getOffset(), tokenRef.getLength())
: db.edges.findAllIncomingEdgeRecordsTo(tokenRef.getFilename(), tokenRef.getOffset(), tokenRef.getLength(), edgeType);
return new Iterable<VPGEdge<A, T, R>>()
{
public Iterator<VPGEdge<A, T, R>> iterator()
{
return new Iterator<VPGEdge<A, T, R>>()
{
private int nextRecord = 0;
private int numRecords = records.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public VPGEdge<A, T, R> next()
{
try
{
R fromRef = factory.getVPGNode(db.files.getFilename(db.edges.getFromFileRecordPtr(records.get(nextRecord))).getString(), db.edges.getFromOffset(records.get(nextRecord)), db.edges.getFromLength(records.get(nextRecord)));
R toRef = factory.getVPGNode(db.files.getFilename(db.edges.getToFileRecordPtr(records.get(nextRecord))).getString(), db.edges.getToOffset(records.get(nextRecord)), db.edges.getToLength(records.get(nextRecord)));
return new VPGEdge<A,T,R>(fromRef, toRef, db.edges.getEdgeType(records.get(nextRecord++)));
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<VPGEdge<A, T, R>>();
}
}
// ANNOTATIONS /////////////////////////////////////////////////////////////
@Override public void setAnnotation(R token, int annotationID, Serializable annotation)
{
try
{
db.annotations.set(token.getFilename(), token.getOffset(), token.getLength(), annotationID, serialize(annotation));
}
catch (Exception e)
{
log.logError(e);
}
}
@Override public void deleteAnnotation(R token, int annotationID)
{
try
{
db.annotations.delete(token.getFilename(), token.getOffset(), token.getLength(), annotationID);
cleanUp(token.getFilename());
}
catch (CoreException e)
{
log.logError(e);
}
}
@Override public Serializable getAnnotation(R token, int annotationID)
{
try
{
int record = db.annotations.findRecordFor(token.getFilename(), token.getOffset(), token.getLength(), annotationID);
if (record < 0)
return null;
else
return deserialize(db.annotations.getAnnotation(record));
}
catch (Exception e)
{
log.logError(e);
return null;
}
}
@Override public Iterable<Pair<R, Integer>> getAllAnnotationsFor(String filename)
{
try
{
final IntVector records = db.annotations.findAllAnnotationRecordsFor(filename);
return new Iterable<Pair<R, Integer>>()
{
public Iterator<Pair<R, Integer>> iterator()
{
return new Iterator<Pair<R, Integer>>()
{
private int nextRecord = 0;
private int numRecords = records.size();
public boolean hasNext()
{
return nextRecord < numRecords;
}
public Pair<R, Integer> next()
{
try
{
R tokenRef = factory.getVPGNode(
db.files.getFilename(db.annotations.getFileRecordPtr(records.get(nextRecord))).getString(),
db.annotations.getOffset(records.get(nextRecord)),
db.annotations.getLength(records.get(nextRecord)));
int type = db.annotations.getAnnotationType(records.get(nextRecord));
nextRecord++;
return new Pair<R, Integer>(tokenRef, type);
}
catch (CoreException e)
{
log.logError(e);
throw new Error(e);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
};
}
catch (CoreException e)
{
log.logError(e);
return new LinkedList<Pair<R, Integer>>();
}
}
/**
* Subclasses should implement this as
* <pre>
* ByteArrayOutputStream out = new ByteArrayOutputStream();
* new ObjectOutputStream(out).writeObject(annotation);
* return out.toByteArray();
* </pre>
* or a more efficient variant.
* <p>
* This is delegated to subclasses to complement <code>deserialize</code>.
*/
protected abstract byte[] serialize(Serializable annotation) throws IOException;
/**
* Subclasses should implement this as
* <pre>
* return (Serializable)new ObjectInputStream(binaryStream).readObject();
* </pre>
* <p>
* This must be implemented in the subclass, since the serialized (annotation) classes may not be in the
* classpath of the VPG plug-in, causing <code>ClassNotFoundException</code>s.
*/
protected abstract Serializable deserialize(InputStream binaryStream) throws IOException, ClassNotFoundException;
////////////////////////////////////////////////////////////////////////////
@Override public void printOn(PrintStream out)
{
try
{
out.println("MODIFICATION STAMPS:"); //$NON-NLS-1$
out.println();
out.println(db.files.toString());
out.println();
out.println();
out.println("DEPENDENCIES:"); //$NON-NLS-1$
out.println();
out.println(db.dependencies.toString());
out.println();
out.println();
out.println("EDGES:"); //$NON-NLS-1$
out.println();
out.println(db.edges.toString());
out.println();
out.println();
out.println("ANNOTATIONS:"); //$NON-NLS-1$
out.println();
try
{
out.println(db.annotations.toString());
}
catch (Exception e)
{
log.logError(e);
}
}
catch (Exception e)
{
out.print(e.getMessage());
e.printStackTrace(out);
}
}
@Override public void printStatisticsOn(PrintStream out)
{
out.println("(No statistics available)"); //$NON-NLS-1$
}
@Override public void resetStatistics()
{
// Nothing to do
}
}