/*
* Created on Nov 30, 2006 Copyright (C) 2001-6, Anthony Harrison anh23@pitt.edu
* (jactr.org) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version. This library is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU Lesser General Public License for more details. You should have
* received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.core.runtime.controller.debug;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.concurrent.ExecutorServices;
import org.jactr.core.event.ACTREventDispatcher;
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.procedural.IProceduralModule;
import org.jactr.core.module.procedural.IProductionInstantiator;
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.CannotInstantiateException;
import org.jactr.core.production.IInstantiation;
import org.jactr.core.production.IProduction;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.runtime.controller.DefaultController;
import org.jactr.core.runtime.controller.debug.event.BreakpointEvent;
import org.jactr.core.runtime.controller.debug.event.IBreakpointListener;
import org.jactr.core.runtime.event.ACTRRuntimeEvent;
import org.jactr.core.runtime.event.IACTRRuntimeListener;
/**
* support production break points that block the model when the marked
* productions are instantiated and selected for firing. it also supports a few
* other break point types
*
* @author developer
*/
public class DebugController extends DefaultController implements
IDebugController
{
/**
* logger definition
*/
static private final Log LOGGER = LogFactory
.getLog(DebugController.class);
private Map<IModel, Map<BreakpointType, Collection<Object>>> _breakpoints;
private Set<IProduction> _disabledProductions;
private ACTREventDispatcher<IModel, IBreakpointListener> _breakpointListeners;
private IProceduralModuleListener _proceduralListener;
private IProductionInstantiator _instantiator;
private IModelListener _modelListener;
private IACTRRuntimeListener _runtimeListener;
public DebugController()
{
super();
_disabledProductions = new HashSet<IProduction>();
_breakpoints = new HashMap<IModel, Map<BreakpointType, Collection<Object>>>();
_instantiator = new IProductionInstantiator() {
public Collection<IInstantiation> instantiate(IProduction production,
Collection<Map<String, Object>> provisionalBindings)
throws CannotInstantiateException
{
if (_disabledProductions.contains(production))
throw new CannotInstantiateException(production
.getSymbolicProduction().getName()
+ " has been disabled");
try
{
return production.instantiateAll(provisionalBindings);
}
catch (Exception e)
{
throw new CannotInstantiateException("Could not instantiate "
+ production + " : " + e.getMessage(), e);
}
}
};
_proceduralListener = createProceduralListener();
_breakpointListeners = new ACTREventDispatcher<IModel, IBreakpointListener>();
_runtimeListener = new IACTRRuntimeListener() {
/**
* normally we would attach at start, but since break points can be added
* before the model runs, we need to do it here.
*
* @param event
*/
public void modelAdded(ACTRRuntimeEvent event)
{
IModel model = event.getModel();
model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
IProceduralModule procMod = model.getProceduralModule();
procMod.addListener(getProceduralListener(),
ExecutorServices.INLINE_EXECUTOR);
procMod.setProductionInstantiator(_instantiator);
try
{
_lock.lock();
_breakpoints.put(event.getModel(),
new HashMap<BreakpointType, Collection<Object>>());
}
finally
{
_lock.unlock();
}
}
public void modelRemoved(ACTRRuntimeEvent event)
{
IModel model = event.getModel();
model.removeListener(_modelListener);
model.getProceduralModule().removeListener(getProceduralListener());
/*
* remove the breakpoint info
*/
try
{
_lock.lock();
_breakpoints.remove(event.getModel());
}
finally
{
_lock.unlock();
}
}
public void runtimeResumed(ACTRRuntimeEvent event)
{
}
public void runtimeStarted(ACTRRuntimeEvent event)
{
}
public void runtimeStopped(ACTRRuntimeEvent event)
{
}
public void runtimeSuspended(ACTRRuntimeEvent event)
{
}
};
_modelListener = new ModelListenerAdaptor() {
@Override
public void cycleStarted(ModelEvent event)
{
super.cycleStarted(event);
IModel model = event.getSource();
long cycle = model.getCycle();
checkForBreakpoint(event.getSource(), BreakpointType.CYCLE, cycle);
}
};
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#addListener(org.jactr.core.runtime.controller.debug.event.IBreakpointListener,
* java.util.concurrent.Executor)
*/
public void addListener(IBreakpointListener listener, Executor executor)
{
_breakpointListeners.addListener(listener, executor);
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#removeListener(org.jactr.core.runtime.controller.debug.event.IBreakpointListener)
*/
public void removeListener(IBreakpointListener listener)
{
_breakpointListeners.removeListener(listener);
}
protected IProceduralModuleListener getProceduralListener()
{
return _proceduralListener;
}
protected IProceduralModuleListener createProceduralListener()
{
return new ProceduralModuleListenerAdaptor() {
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#conflictSetAssembled(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void conflictSetAssembled(ProceduralModuleEvent pme)
{
}
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#productionAdded(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void productionAdded(ProceduralModuleEvent pme)
{
}
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#productionWillFire(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void productionWillFire(ProceduralModuleEvent pme)
{
IModel model = pme.getSource().getModel();
IProduction production = pme.getProduction();
checkForBreakpoint(model, BreakpointType.PRODUCTION, production);
}
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#productionCreated(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void productionCreated(ProceduralModuleEvent pme)
{
}
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#productionFired(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void productionFired(ProceduralModuleEvent pme)
{
}
/**
* @see org.jactr.core.module.procedural.event.IProceduralModuleListener#productionsMerged(org.jactr.core.module.procedural.event.ProceduralModuleEvent)
*/
@Override
public void productionsMerged(ProceduralModuleEvent pme)
{
}
};
}
@Override
public void attach()
{
super.attach();
ACTRRuntime.getRuntime().addListener(_runtimeListener,
ExecutorServices.INLINE_EXECUTOR);
}
@Override
public void detach()
{
super.detach();
/*
* we also need to make sure we remove our listeners
*/
for (IModel model : _breakpoints.keySet())
model.getProceduralModule().removeListener(getProceduralListener());
clearBreakpoints();
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#clearBreakpoints()
*/
public void clearBreakpoints()
{
clearBreakpoints(null, null);
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#clearBreakpoints(org.jactr.core.model.IModel,
* org.jactr.core.runtime.controller.debug.BreakpointType)
*/
public void clearBreakpoints(IModel model, BreakpointType type)
{
try
{
_lock.lock();
Collection<IModel> forModels = _breakpoints.keySet();
if (model != null) forModels = Collections.singleton(model);
for (IModel m : forModels)
{
Map<BreakpointType, Collection<Object>> maps = _breakpoints.get(model);
Collection<BreakpointType> types = Arrays.asList(BreakpointType
.values());
if (type != null) types = Collections.singleton(type);
if (maps != null)
{
for (BreakpointType t : types)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Clearing break points of " + t + " for " + m);
Collection<Object> points = maps.get(t);
if (points != null) points.clear();
}
maps.clear();
}
}
_breakpoints.clear();
}
finally
{
_lock.unlock();
}
}
public void setEnabled(IProduction production, boolean enabled)
{
if (enabled)
_disabledProductions.remove(production);
else
_disabledProductions.add(production);
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#addBreakpoint(org.jactr.core.model.IModel,
* org.jactr.core.runtime.controller.debug.BreakpointType,
* java.lang.Object)
*/
public void addBreakpoint(IModel model, BreakpointType type, Object value)
{
try
{
_lock.lock();
Map<BreakpointType, Collection<Object>> breakpoints = _breakpoints
.get(model);
/*
* breakpoints wont be null..
*/
Collection<Object> values = breakpoints.get(type);
if (values == null)
{
values = new ArrayList<Object>();
breakpoints.put(type, values);
}
values.add(value);
}
finally
{
_lock.unlock();
}
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#removeBreakpoint(org.jactr.core.model.IModel,
* org.jactr.core.runtime.controller.debug.BreakpointType,
* java.lang.Object)
*/
public void removeBreakpoint(IModel model, BreakpointType type, Object value)
{
try
{
_lock.lock();
Map<BreakpointType, Collection<Object>> breakpoints = _breakpoints
.get(model);
Collection<Object> values = breakpoints.get(type);
if (values != null) values.remove(value);
}
finally
{
_lock.unlock();
}
}
/**
* @see org.jactr.core.runtime.controller.debug.IDebugController#isBreakpoint(org.jactr.core.model.IModel,
* org.jactr.core.runtime.controller.debug.BreakpointType,
* java.lang.Object)
*/
public boolean isBreakpoint(IModel model, BreakpointType type, Object value)
{
try
{
_lock.lock();
/*
* the break object is an instantiation, we need to get the production it
* was derived from
*/
if (type == BreakpointType.PRODUCTION)
if (value instanceof IInstantiation)
value = ((IInstantiation) value).getProduction();
/*
* never break on exception this is temporary until a more optimal
* solution can be found - basically, the issue is that if an exception
* occurs during model execution, there is no way to recover gracefully,
* so using it as a break point is silly.
*/
if (type == BreakpointType.EXCEPTION) return false;
Map<BreakpointType, Collection<Object>> breakpoints = _breakpoints
.get(model);
if (breakpoints == null) return false;
Collection<Object> values = breakpoints.get(type);
if (values == null) return false;
boolean isBreakpoint = values.contains(value);
/*
* time and cycle behave differently. if value is >= any value they will
* trigger..
*/
if (!isBreakpoint) if (type == BreakpointType.CYCLE)
{
// values are numbers..
for (Object obj : values)
if (((Number) obj).intValue() < ((Number) value).intValue())
{
isBreakpoint = true;
continue;
}
}
else if (type == BreakpointType.TIME) for (Object obj : values)
if (((Number) obj).doubleValue() < ((Number) value).doubleValue())
{
isBreakpoint = true;
continue;
}
return isBreakpoint;
}
finally
{
_lock.unlock();
}
}
/**
* will check to see if value represents a valid break point of type within
* model and block after calling breakpointReached
*
* @param model
* @param type
* @param value
*/
protected void checkForBreakpoint(IModel model, BreakpointType type,
Object value)
{
if (isBreakpoint(model, type, value))
breakpointReached(model, type, value);
}
/**
* actually suspend the model. this should only be called on the model thread
* via an event listener that is attached as inline. we can independently
* suspend or resume models, however, that doesn't make a whole lot of sense.
* so, when we reach this point, we suspend EVERYONE. meaning, the model that
* tripped this breakpoint will suspend right here, but everyone else will
* suspend at the top of the next cycle, unless they also trip a breakpoint.
*
* @param model
* @param type
* @param value
*/
protected void breakpointReached(IModel model, BreakpointType type,
Object value)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Breakpoint." + type.name() + " reached at " + value);
if (value instanceof Throwable)
LOGGER.error("Specifically, an exception was thrown ",
(Throwable) value);
}
fireBreakpointReachedEvent(model, type, value);
// signal to suspend everyone
suspend();
suspendLocally(model);
}
protected void fireBreakpointReachedEvent(IModel model, BreakpointType type,
Object details)
{
_breakpointListeners.fire(new BreakpointEvent(model, type, details));
}
}