blob: a3549e4f6cb60bbb0536f58503dbd4fbea148edd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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.ram;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.channels.FileChannel;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import org.eclipse.rephraserengine.core.util.Pair;
import org.eclipse.rephraserengine.core.util.TwoKeyHashMap;
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.VPG;
import org.eclipse.rephraserengine.core.vpg.VPGDB;
import org.eclipse.rephraserengine.core.vpg.VPGDependency;
import org.eclipse.rephraserengine.core.vpg.VPGEdge;
/**
* VPG database that maintains files, dependencies, edges, and annotations in memory, then flushes
* the in-memory database to disk when the database is closed (i.e., the Eclipse workbench is
* closed).
*
* @author Esfar Huq
* @author Rui Wang
* @author Jeff Overbey
*
* @param <A> AST type
* @param <T> token type
* @param <R> {@link IVPGNode}/{@link NodeRef} type
*
* @since 8.0
*/
public abstract class RAMDB<A, T, R extends IVPGNode<T>>
extends VPGDB<A, T, R>
{
protected final File file; //file to which database information will be written to/read from
protected HashMap<String, Long> files;
protected HashSet<VPGDependency<A, T, R>> dependencies;
protected HashMap<R, Set<VPGEdge<A, T, R>>> outgoingEdges;
protected HashMap<R, Set<VPGEdge<A, T, R>>> incomingEdges;
protected TwoKeyHashMap<R, Integer, Serializable> annotations;
/** Constructor that initializes the private fields */
public RAMDB(IVPGComponentFactory<A, T, R> locator, File file)
{
super(locator);
this.file = file;
if (file.exists() && file.canRead())
readFrom(file);
else
createEmptyDatabase();
}
protected void createEmptyDatabase()
{
files = new HashMap<String, Long>();
dependencies = new HashSet<VPGDependency<A, T, R>>();
outgoingEdges = new HashMap<R, Set<VPGEdge<A, T, R>>>();
incomingEdges = new HashMap<R, Set<VPGEdge<A, T, R>>>();
annotations = new TwoKeyHashMap<R, Integer, Serializable>();
}
@SuppressWarnings("unchecked")
protected void readFrom(File file)
{
//check if we can load in fields from disk
try
{
ObjectInputStream in =
new ObjectInputStream(
new InflaterInputStream(
new BufferedInputStream(
new FileInputStream(file))));
files = (HashMap<String, Long>)readObject(in);
dependencies = (HashSet<VPGDependency<A, T, R>>)readObject(in);
outgoingEdges = (HashMap<R, Set<VPGEdge<A, T, R>>>)readObject(in);
incomingEdges = (HashMap<R, Set<VPGEdge<A, T, R>>>)readObject(in);
annotations = (TwoKeyHashMap<R, Integer, Serializable>)readObject(in);
in.close();
}
catch (EOFException e)
{
// Database file is probably empty; don't worry
createEmptyDatabase();
}
catch (Exception e)
{
//Activator.log(e);
e.printStackTrace();
createEmptyDatabase();
}
}
protected abstract Object readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
protected void writeTo(File file)
{
//serialize private fields and store them
try
{
ObjectOutputStream out =
new ObjectOutputStream(
new DeflaterOutputStream(
new BufferedOutputStream(
new FileOutputStream(file))));
out.writeObject(files);
out.writeObject(dependencies);
out.writeObject(outgoingEdges);
out.writeObject(incomingEdges);
out.writeObject(annotations);
out.close();
}
catch (IOException e)
{
//Activator.log(e);
e.printStackTrace();
file.delete();
}
}
///////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////
/** Forces any in-memory data to be flushed to disk */
@Override
public void flush()
{
writeTo(file);
}
/** Called when the database is no longer needed. Typically ensures that
* any data in memory is flushed to disk and any locks are released.
*/
@Override
public void close()
{
flush();
files = null;
dependencies = null;
outgoingEdges = null;
incomingEdges = null;
annotations = null;
}
/** Removes ALL data from the database; also clears the error/warning log. */
@Override
public void clearDatabase()
{
files.clear();
dependencies.clear();
outgoingEdges.clear();
incomingEdges.clear();
annotations.clear();
}
// HYPOTHETICAL UPDATING ///////////////////////////////////////////////////
private File originalContents = null;
@Override
public void enterHypotheticalMode() throws IOException
{
if (isInHypotheticalMode()) return;
flush();
originalContents = copyFile(file);
clearDatabase();
readFrom(file);
}
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() throws IOException
{
if (!isInHypotheticalMode()) return;
clearDatabase();
readFrom(originalContents);
originalContents.delete();
originalContents = null;
}
@Override
public boolean isInHypotheticalMode()
{
return originalContents != null;
}
// FILES ///////////////////////////////////////////////////////////////////
/** Marks the VPG database entries for the given file as being up-to-date. */
@Override
public void updateModificationStamp(String filename)
{
files.put(filename, getModificationStamp(filename));
}
public abstract long getModificationStamp(String filename);
/** @return true iff the VPG entries for the given file are not up-to-date */
@Override
public boolean isOutOfDate(String filename)
{
checkIfFileInDatabase(filename);
long storedModificationStamp = files.get(filename);
return storedModificationStamp < getModificationStamp(filename);
}
/** Removes all dependencies, edges, and annotations for the given file. */
@Override
public void deleteAllEntriesFor(String filename)
{
this.deleteAllIncomingDependenciesFor(filename);
this.deleteAllOutgoingDependenciesFor(filename);
this.deleteAllEdgesAndAnnotationsFor(filename);
}
/** Removes all edges and annotations (but not dependencies) for the given file. */
@Override
public void deleteAllEdgesAndAnnotationsFor(String filename)
{
//edges
Iterator<R> it = outgoingEdges.keySet().iterator();
while (it.hasNext())
if (it.next().getFilename().equals(filename))
it.remove();
it = incomingEdges.keySet().iterator();
while (it.hasNext())
if (it.next().getFilename().equals(filename))
it.remove();
// annotations
it = annotations.keySet().iterator();
while (it.hasNext())
if (it.next().getFilename().equals(filename))
it.remove();
}
/** Removes all edges pointing inward to any token in the given file. */
@Override
public void deleteAllIncomingDependenciesFor(String filename)
{
Iterator<VPGDependency<A, T, R>> dItr = dependencies.iterator();
while (dItr.hasNext())
if (dItr.next().getDependsOnFile().equals(filename))
dItr.remove();
}
/** Removes all edges pointing outward from any token in the given file. */
@Override
public void deleteAllOutgoingDependenciesFor(String filename)
{
Iterator<VPGDependency<A, T, R>> dItr = dependencies.iterator();
while (dItr.hasNext())
if (dItr.next().getDependentFile().equals(filename))
dItr.remove();
}
/** Returns all filenames present in the VPG database. */
@Override
public Set<String> listAllFilenames()
{
return files.keySet();
}
/** Returns the name of every file on which at least one other file is
* dependent. */
@Override
public Set<String> listAllFilenamesWithDependents()
{
Set<String> toReturn = new HashSet<String>();
for (VPGDependency<A, T, R> d : dependencies)
toReturn.add(d.getDependsOnFile());
return toReturn;
}
/** Returns the name of every file which depends on at least one other
* file. */
@Override
public Set<String> listAllDependentFilenames()
{
Set<String> toReturn = new HashSet<String>();
for (VPGDependency<A, T, R> d : dependencies)
toReturn.add(d.getDependentFile());
return toReturn;
}
// DEPENDENCIES ////////////////////////////////////////////////////////////
/** Adds the given dependency to the VPG database if a dependency between
* its files does not already exist. */
@Override
public void ensure(VPGDependency<A, T, R> dependency)
{
checkIfFileInDatabase(dependency.getDependentFile());
checkIfFileInDatabase(dependency.getDependsOnFile());
dependencies.add(dependency);
}
/** Deletes the given dependency from the VPG database. */
@Override
public void delete(VPGDependency<A, T, R> dependency)
{
dependencies.remove(dependency);
}
/** @return all of the files on which the given file depends */
@Override
public Iterable<String> getOutgoingDependenciesFrom(String filename)
{
checkIfFileInDatabase(filename);
Set<String> toReturn = new TreeSet<String>();
for (VPGDependency<A, T, R> d : dependencies)
if (d.getDependentFile().equals(filename))
toReturn.add(d.getDependsOnFile());
return toReturn;
}
/** @return all of the files dependent on the given file */
@Override
public Iterable<String> getIncomingDependenciesTo(String filename)
{
checkIfFileInDatabase(filename);
Set<String> toReturn = new TreeSet<String>();
for (VPGDependency<A, T, R> d : dependencies)
if (d.getDependsOnFile().equals(filename))
toReturn.add(d.getDependentFile());
return toReturn;
}
// EDGES ///////////////////////////////////////////////////////////////////
/** Adds the given edge to the VPG database if an edge of the given type
* between its tokens does not already exist. */
@Override
public void ensure(VPGEdge<A, T, R> edge)
{
checkIfFileInDatabase(edge.getSource().getFilename());
checkIfFileInDatabase(edge.getSink().getFilename());
R source = edge.getSource();
R sink = edge.getSink();
Set<VPGEdge<A, T, R>> eSource = outgoingEdges.get(source);
Set<VPGEdge<A, T, R>> eSink = incomingEdges.get(sink);
if (eSource == null)
outgoingEdges.put(source, new TreeSet<VPGEdge<A, T, R>>());
outgoingEdges.get(source).add(edge);
if (eSink == null)
incomingEdges.put(sink, new TreeSet<VPGEdge<A, T, R>>());
incomingEdges.get(sink).add(edge);
}
/** Deletes the given edge from the VPG database. */
@Override
public void delete(VPGEdge<A, T, R> edge)
{
Set<VPGEdge<A, T, R>> e1 = outgoingEdges.get(edge.getSource());
if (e1 != null && e1.contains(edge))
{
if (e1.size() == 1)
outgoingEdges.remove(edge.getSource());
else
outgoingEdges.get(edge.getSource()).remove(edge);
}
Set<VPGEdge<A, T, R>> e2 = incomingEdges.get(edge.getSink());
if (e2 != null && e2.contains(edge))
{
if (e2.size() == 1)
incomingEdges.remove(edge.getSink());
else
incomingEdges.get(edge.getSink()).remove(edge);
}
}
/**
* Returns a list of all of the edges with at least one endpoint in the given file.
* <p>
* Due to implementation details, some edges may be listed more than once.
*
*/
@Override
public Collection<? extends VPGEdge<A, T, R>> getAllEdgesFor(String filename)
{
checkIfFileInDatabase(filename);
Set<VPGEdge<A, T, R>> toReturn = new TreeSet<VPGEdge<A, T, R>>();
for (R r : outgoingEdges.keySet())
if (r.getFilename().equals(filename))
toReturn.addAll(this.getOutgoingEdgesFrom(r, ALL_EDGES));
for (R r : incomingEdges.keySet())
if (r.getFilename().equals(filename))
toReturn.addAll(this.getIncomingEdgesTo(r, ALL_EDGES));
return toReturn;
}
/**
* Returns a list of the edges extending from the given token.
* <p>
* To only return edges of a particular type, set the <code>edgeType</code>
* parameter to that type.
*
* @param edgeType the type of edge (an arbitrary non-negative integer), or
* {@link VPG#ALL_EDGES_ALLOWED} to return all edges, regardless
* of type THIS IS INDICATED BY Integer.MIN_VALUE
*/
@Override
public Collection<? extends VPGEdge<A, T, R>> getOutgoingEdgesFrom(R source, int edgeType)
{
if (outgoingEdges.get(source) == null) return Collections.emptySet();
checkIfFileInDatabase(source.getFilename());
if (edgeType == ALL_EDGES)
{
return outgoingEdges.get(source);
}
else
{
Set<VPGEdge<A, T, R>> toReturn = new TreeSet<VPGEdge<A, T, R>>();
for (VPGEdge<A, T, R> e : outgoingEdges.get(source))
if (e.getType() == edgeType)
toReturn.add(e);
return toReturn;
}
}
/**
* Returns a list of the edges pointing at the given token.
* <p>
* To only return edges of a particular type, set the <code>edgeType</code>
* parameter to that type.
*
* @param edgeType the type of edge (an arbitrary non-negative integer), or
* {@link VPG#ALL_EDGES} to return all edges, regardless
* of type THIS IS INDICATED BY Integer.MIN_VALUE
*/
@Override
public Collection<? extends VPGEdge<A, T, R>> getIncomingEdgesTo(R sink, int edgeType)
{
if (incomingEdges.get(sink) == null) return new TreeSet<VPGEdge<A, T, R>>();
checkIfFileInDatabase(sink.getFilename());
if (edgeType == ALL_EDGES)
{
return incomingEdges.get(sink);
}
else
{
Set<VPGEdge<A, T, R>> toReturn = new TreeSet<VPGEdge<A, T, R>>();
for (VPGEdge<A, T, R> e : incomingEdges.get(sink))
if (e.getType() == edgeType)
toReturn.add(e);
return toReturn;
}
}
// ANNOTATIONS /////////////////////////////////////////////////////////////
/**
* Annotates the given token with the given (serializable) object
* (which may be <code>null</code>).
* If an annotation for the given token with the given ID already exists,
* it will be replaced.
* <p>
* A token can have several annotations, but each annotation must be given
* a unique ID. For example, annotation 0 might describe
* the type of an identifier, while annotation 1 might hold documentation
* for that identifier.
*/
@Override
public void setAnnotation(R token, int annotationID, Serializable annotation)
{
checkIfFileInDatabase(token.getFilename());
annotations.put(token, annotationID, annotation);
}
/** Deletes the annotation with the given ID for the given token, if it exists. */
@Override
public void deleteAnnotation(R token, int annotationID)
{
annotations.remove(token, annotationID);
}
/** @return the annotation with the given ID for the given token, or <code>null</code>
* if it does not exist */
@Override
public Serializable getAnnotation(R token, int annotationID)
{
checkIfFileInDatabase(token.getFilename());
return annotations.getEntry(token, annotationID);
}
/**
* Returns a list of all of the annotations in the given file.
* <p>
* The first entry of each pair is a {@link IVPGNode}, and the second is an annotation type.
* The annotation can be retrieved using {@link VPGDB#getAnnotation(IVPGNode, int)}.
* <p>
* Due to implementation details, some annotations may be listed more than once.
*
* @since 3.0
*/
@Override
public Iterable<Pair<R, Integer>> getAllAnnotationsFor(String filename)
{
checkIfFileInDatabase(filename);
Set<Pair<R, Integer>> toReturn = new HashSet<Pair<R, Integer>>();
for (R r : annotations.keySet())
if (r.getFilename().equals(filename))
for (Integer i : annotations.getAllEntriesFor(r).keySet())
toReturn.add(new Pair<R, Integer>((R)r, i));
return toReturn;
}
// UTILITY METHODS /////////////////////////////////////////////////////////
@Override
public void printOn(PrintStream out)
{
out.println("MODIFICATION STAMPS:"); //$NON-NLS-1$
out.println();
out.println(this.files.toString());
out.println();
out.println();
out.println("DEPENDENCIES:"); //$NON-NLS-1$
out.println();
out.println(this.dependencies.toString());
out.println();
out.println();
out.println("EDGES:"); //$NON-NLS-1$
out.println();
out.println(this.outgoingEdges.toString());
out.println();
out.println();
out.println("ANNOTATIONS:"); //$NON-NLS-1$
out.println();
out.println(this.annotations.toString());
}
@Override
public void printStatisticsOn(PrintStream out)
{
//BLANK
}
@Override
public void resetStatistics()
{
//BLANK
}
/** Checks to see if a file is in the database, if not, bring the file in **/
private void checkIfFileInDatabase(String filename)
{
if (!files.containsKey(filename))
files.put(filename, Long.MIN_VALUE);
}
}