package org.jactr.tools.change;
/*
* default logging
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javolution.util.FastList;
import org.antlr.runtime.tree.CommonTree;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.buffer.IActivationBuffer;
import org.jactr.core.buffer.event.ActivationBufferEvent;
import org.jactr.core.buffer.event.ActivationBufferListenerAdaptor;
import org.jactr.core.buffer.event.IActivationBufferListener;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.chunk.event.ChunkEvent;
import org.jactr.core.chunk.event.ChunkListenerAdaptor;
import org.jactr.core.chunk.event.IChunkListener;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.model.event.IModelListener;
import org.jactr.core.model.event.ModelEvent;
import org.jactr.core.model.event.ModelListenerAdaptor;
import org.jactr.core.module.declarative.event.DeclarativeModuleEvent;
import org.jactr.core.module.declarative.event.DeclarativeModuleListenerAdaptor;
import org.jactr.core.module.declarative.event.IDeclarativeModuleListener;
import org.jactr.core.module.procedural.event.IProceduralModuleListener;
import org.jactr.core.module.procedural.event.ProceduralModuleEvent;
import org.jactr.core.module.procedural.event.ProceduralModuleListenerAdaptor;
import org.jactr.core.production.IProduction;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.instrument.IInstrument;
import org.jactr.io.generator.CodeGeneratorFactory;
import org.jactr.io.generator.ICodeGenerator;
import org.jactr.io.resolver.ASTResolver;
/**
* general change tracker that logs chunk changes and production instantiations.
* It outputs them to the {@link Logger}, using a custom stream label "CHANGE"
*
* @author harrison
*/
public class ChangeTracker implements IInstrument, IParameterized
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(ChangeTracker.class);
static public final String CHANGE_STREAM = "CHANGE";
static public final String FORMAT_PARAM = "Format";
static public final String TRACK_NEW_CHUNKS = "TrackNewChunks";
static public final String TRACK_ACTIVE_CHUNKS = "TrackActiveChunks";
static public final String TRACK_INSTANTIATIONS = "TrackInstantiations";
/**
* listen to the model for cycle start/stop
*/
private IModelListener _modelListener;
/**
* listen to the procedural module for the production to fire
*/
private IProceduralModuleListener _proceduralListener;
/**
* for new chunks
*/
private IDeclarativeModuleListener _declarativeListener;
/**
* to catch the add/remove of chunks to buffers
*/
private IActivationBufferListener _bufferListener;
/**
* so we know when chunks in buffers change
*/
private IChunkListener _chunkListener;
/**
* keep track of all the chunks that have changed. We will use these at the
* start/end of the cycle to generate ASTs
*/
private Map<IModel, Set<IChunk>> _changedChunks;
/**
* all the asts that need to be output
*/
private Map<IModel, Collection<CommonTree>> _astsToWrite;
private ICodeGenerator _codeGenerator;
private String _format;
private boolean _trackNewChunks = false;
private boolean _trackActiveChunks = false;
private boolean _trackInstantitions = false;
public ChangeTracker()
{
_changedChunks = new HashMap<IModel, Set<IChunk>>();
_astsToWrite = new HashMap<IModel, Collection<CommonTree>>();
_modelListener = new ModelListenerAdaptor() {
public void cycleStarted(ModelEvent me)
{
if (LOGGER.isDebugEnabled()) LOGGER.debug("Cycle started, generate");
generateASTs(me.getSource());
}
public void cycleStopped(ModelEvent me)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Cycle stopped, generate and flush");
generateASTs(me.getSource());
flush(me.getSource(), false);
}
};
_declarativeListener = new DeclarativeModuleListenerAdaptor() {
public void chunkAdded(DeclarativeModuleEvent dme)
{
IChunk chunk = dme.getChunk();
if (LOGGER.isDebugEnabled())
LOGGER.debug("Chunk added to DM " + chunk);
synchronized (_changedChunks)
{
_changedChunks.get(dme.getSource().getModel()).add(chunk);
}
}
public void chunksMerged(DeclarativeModuleEvent event)
{
/*
* the original
*/
IChunk chunk = event.getChunk();
if (LOGGER.isDebugEnabled())
LOGGER.debug("Merged into " + chunk);
synchronized (_changedChunks)
{
_changedChunks.get(event.getSource().getModel()).add(chunk);
}
}
};
_proceduralListener = new ProceduralModuleListenerAdaptor() {
public void productionWillFire(ProceduralModuleEvent pme)
{
/*
* we have to generate this now since it is an instantiation
*/
generateAST(pme.getSource().getModel(), pme.getProduction());
}
};
_bufferListener = new ActivationBufferListenerAdaptor() {
public void sourceChunkAdded(ActivationBufferEvent abe)
{
for (IChunk chunk : abe.getSourceChunks())
chunk.addListener(_chunkListener, ExecutorServices.INLINE_EXECUTOR);
}
public void sourceChunkRemoved(ActivationBufferEvent abe)
{
sourceChunksCleared(abe);
}
public void sourceChunksCleared(ActivationBufferEvent abe)
{
for (IChunk chunk : abe.getSourceChunks())
chunk.removeListener(_chunkListener);
}
};
_chunkListener = new ChunkListenerAdaptor() {
public void slotChanged(ChunkEvent ce)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(ce.getSource() + " has changed");
synchronized (_changedChunks)
{
_changedChunks.get(ce.getSource().getModel()).add(ce.getSource());
}
}
};
}
public void initialize()
{
// TODO Auto-generated method stub
}
public void install(IModel model)
{
if (_codeGenerator == null)
{
if (LOGGER.isWarnEnabled())
LOGGER.warn("No code generator was defined. must set " + FORMAT_PARAM
+ " parameter");
return;
}
if (LOGGER.isDebugEnabled()) LOGGER.debug("Attaching to " + model);
Set<IChunk> changedChunks = new HashSet<IChunk>();
Collection<CommonTree> asts = new ArrayList<CommonTree>();
synchronized (_astsToWrite)
{
_astsToWrite.put(model, asts);
}
synchronized (_changedChunks)
{
_changedChunks.put(model, changedChunks);
}
model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
if (_trackInstantitions)
model.getProceduralModule().addListener(_proceduralListener,
ExecutorServices.INLINE_EXECUTOR);
if (_trackNewChunks)
model.getDeclarativeModule().addListener(_declarativeListener,
ExecutorServices.INLINE_EXECUTOR);
if (_trackActiveChunks)
for (IActivationBuffer buffer : model.getActivationBuffers())
buffer.addListener(_bufferListener, ExecutorServices.INLINE_EXECUTOR);
}
public void uninstall(IModel model)
{
if (_codeGenerator == null) return;
model.removeListener(_modelListener);
model.getProceduralModule().removeListener(_proceduralListener);
for (IActivationBuffer buffer : model.getActivationBuffers())
buffer.removeListener(_bufferListener);
/**
* queue up a dump request..
*/
flush(model, true);
}
/**
* generate the ast of the to-be-fire instantaition
*
* @param model
* @param production
*/
protected void generateAST(IModel model, IProduction production)
{
Collection<CommonTree> asts = null;
synchronized (_astsToWrite)
{
asts = _astsToWrite.get(model);
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("Generating ast for " + production);
asts.add(ASTResolver.toAST(production));
}
/**
* use the list of changed chunks to generate asts for them
*
* @param model
*/
protected void generateASTs(IModel model)
{
Collection<CommonTree> asts = null;
synchronized (_astsToWrite)
{
asts = _astsToWrite.get(model);
}
FastList<IChunk> changedChunks = FastList.newInstance();
synchronized (_changedChunks)
{
changedChunks.addAll(_changedChunks.get(model));
_changedChunks.get(model).clear();
}
for (FastList.Node<IChunk> node = changedChunks.head(), end = changedChunks
.tail(); (node = node.getNext()) != end;)
{
IChunk chunk = node.getValue();
if (LOGGER.isDebugEnabled())
LOGGER.debug("generating ast for " + chunk);
asts.add(ASTResolver.toAST(chunk, false));
}
FastList.recycle(changedChunks);
}
/**
* generate the code for the asts and then send them to the logger on the
* background thread
*
* @param model
* @param cleanUp
*/
protected void flush( IModel model, boolean cleanUp)
{
FastList<CommonTree> list = FastList.newInstance();
synchronized (_astsToWrite)
{
list.addAll(_astsToWrite.get(model));
_astsToWrite.get(model).clear();
}
StringBuilder sb = new StringBuilder();
//fast, destructive iterator where processing order does not matter
for (CommonTree ast = null; !list.isEmpty()
&& (ast = list.removeLast()) != null;)
{
sb.delete(0, sb.length());
for (StringBuilder line : _codeGenerator.generate(ast, true))
sb.append(line).append("\n");
sb.append("\n");
Logger.log(model, CHANGE_STREAM, sb.toString());
}
FastList.recycle(list);
if (cleanUp)
{
synchronized (_astsToWrite)
{
_astsToWrite.remove(model);
}
synchronized (_changedChunks)
{
_changedChunks.remove(model);
}
}
}
public String getParameter(String key)
{
if (FORMAT_PARAM.equalsIgnoreCase(key)) return _format;
if (TRACK_ACTIVE_CHUNKS.equalsIgnoreCase(key))
return "" + _trackActiveChunks;
if (TRACK_NEW_CHUNKS.equalsIgnoreCase(key)) return "" + _trackNewChunks;
if (TRACK_INSTANTIATIONS.equalsIgnoreCase(key))
return "" + _trackInstantitions;
return null;
}
public Collection<String> getPossibleParameters()
{
return getSetableParameters();
}
public Collection<String> getSetableParameters()
{
return Arrays.asList(FORMAT_PARAM, TRACK_ACTIVE_CHUNKS, TRACK_NEW_CHUNKS,
TRACK_INSTANTIATIONS);
}
public void setParameter(String key, String value)
{
if (FORMAT_PARAM.equalsIgnoreCase(key))
{
_codeGenerator = CodeGeneratorFactory.getCodeGenerator(value);
_format = value;
}
else if (TRACK_ACTIVE_CHUNKS.equalsIgnoreCase(key))
_trackActiveChunks = Boolean.parseBoolean(value);
else if (TRACK_NEW_CHUNKS.equalsIgnoreCase(key))
_trackNewChunks = Boolean.parseBoolean(value);
else if (TRACK_INSTANTIATIONS.equalsIgnoreCase(key))
_trackInstantitions = Boolean.parseBoolean(value);
else if (LOGGER.isWarnEnabled())
LOGGER.warn("No clue how to set " + key + " = " + value);
}
}