package uk.ac.lkl.migen.system.expresser.model;
import java.util.ArrayList;
import java.util.List;
import org.openide.util.WeakListeners;
import uk.ac.lkl.common.util.event.UpdateEvent;
import uk.ac.lkl.common.util.event.UpdateListener;
import uk.ac.lkl.common.util.event.UpdateSupport;
import uk.ac.lkl.common.util.expression.Expression;
import uk.ac.lkl.common.util.expression.ModifiableOperation;
import uk.ac.lkl.common.util.value.IntegerValue;
import uk.ac.lkl.migen.system.expresser.model.event.AttributeChangeListener;
import uk.ac.lkl.migen.system.expresser.model.shape.block.BlockShape;
import uk.ac.lkl.migen.system.expresser.model.tiednumber.TiedNumberExpression;
public class ExpresserModelSlaveCopy extends ExpresserModel {
/**
* The slaved universe is currently implemented as a copy of a model. The
* copy differs in how boxed numbers are copied since they need different
* values in the master and slave models.
*
*/
// a mapping from master tied numbers to copies in the slave model
// private HashMap<TiedNumberExpression<?>, TiedNumberExpression<?>>
// tiedNumbersMasterToSlaveMapping =
// new HashMap<TiedNumberExpression<?>, TiedNumberExpression<?>>();
// offsets used to minimise the possibility of two offsets being the same
private ArrayList<Integer> offsetsInUse = new ArrayList<Integer>();
private ExpresserModel masterModel;
private boolean frozen = false;
// Need a strong reference for the weak listeners
private ArrayList<UpdateListener<TiedNumberExpression<IntegerValue>>> attributeChangeTiedNumberListeners =
new ArrayList<UpdateListener<TiedNumberExpression<IntegerValue>>>();
private ArrayList<UpdateListener<Expression<IntegerValue>>> attributeChangeExpressionListeners =
new ArrayList<UpdateListener<Expression<IntegerValue>>>();
// tied numbers in the slave universe differ from the master
// by one of randomOffsets chosen randomly
static private int randomOffsets[] = { 2, -3, -5, 5, 4, -2, 3, -4 };
// following used only if all the above are inappropriate
static private int emergencyRandomOffsets[] =
{ 6, 12, 8, -8, 10, 7, 11, -6, 9, -9, -12, -10, -7, -11 };
private UpdateSupport<ExpresserModelSlaveCopy> updateSupport =
new UpdateSupport<ExpresserModelSlaveCopy>();
// following is used to display all the unlocked tied numbers regardless of whether they are used in the model
private ArrayList<TiedNumberExpression<?>> unlockedTiedNumbers;
public ExpresserModelSlaveCopy() {
super();
}
@SuppressWarnings("unchecked")
// not clear how to avoid this warning -- named capture doesn't work without
// lots of other changes
public TiedNumberExpression<?> getSlaveCopy(TiedNumberExpression<?> masterTiedNumber) {
List<TiedNumberExpression<IntegerValue>> taskVariables = getMasterModel().getUnlockedNumbers();
int tiedNumberIndex = taskVariables.indexOf(masterTiedNumber);
if (tiedNumberIndex < 0) {
return masterTiedNumber;
} else {
List<TiedNumberExpression<IntegerValue>> slaveTaskVariables = getUnlockedNumbers();
if (tiedNumberIndex < slaveTaskVariables.size()) {
return slaveTaskVariables.get(tiedNumberIndex);
} else {
final TiedNumberExpression<IntegerValue> masterTiedInteger =
(TiedNumberExpression<IntegerValue>) masterTiedNumber;
final TiedNumberExpression<IntegerValue> copy =
masterTiedInteger.createFreshCopy(true);
// slave copies are really the same tied number (sort of)
// so they should share the same id (createFreshCopy(true) does this now)
// copy.setIdString(masterTiedInteger.getIdString());
// addSlaveToMasterListeners(masterTiedInteger, copy);
return copy;
}
}
}
public TiedNumberExpression<?> getOrCreateSlaveCopy(TiedNumberExpression<?> masterTiedNumber) {
TiedNumberExpression<?> slaveCopy = getSlaveCopy(masterTiedNumber);
if (slaveCopy == masterTiedNumber) {
return masterTiedNumber.createFreshCopy(true);
} else {
return slaveCopy;
}
}
/**
* @param masterTiedInteger
* @param copy
*
* ExpresserModelProxy overrides this
*/
protected void addSlaveToMasterListeners(
final TiedNumberExpression<IntegerValue> masterTiedInteger,
final TiedNumberExpression<IntegerValue> copy) {
addDisplayModeListener(masterTiedInteger, copy);
// copy could be updated from within the expression area (global colour resource panel)
// not any longer -- can only change things in the master panel now
// addDisplayModeListener(copy, masterTiedInteger);
addValueListener(masterTiedInteger, copy);
// addValueListener(copy, masterTiedInteger);
addNameListener(masterTiedInteger, copy);
// addNameListener(copy, masterTiedInteger);
}
protected void addNameListener(final TiedNumberExpression<IntegerValue> changed,
final TiedNumberExpression<IntegerValue> copy) {
UpdateListener<TiedNumberExpression<IntegerValue>> nameFieldUpdateListener =
new UpdateListener<TiedNumberExpression<IntegerValue>>() {
public void objectUpdated(UpdateEvent<TiedNumberExpression<IntegerValue>> e) {
copy.setName(changed.getName());
}
};
// changed.addNameFieldUpdateListener(nameFieldUpdateListener);
// don't know why the following didn't work
changed.addNameFieldUpdateListener(weakUpdateListenerTiedNumberExpression(nameFieldUpdateListener, changed));
}
protected void addValueListener(final TiedNumberExpression<IntegerValue> changed,
final TiedNumberExpression<IntegerValue> copy) {
UpdateListener<Expression<IntegerValue>> updateListener =
new UpdateListener<Expression<IntegerValue>>() {
public void objectUpdated(UpdateEvent<Expression<IntegerValue>> e) {
int intValue = changed.getValue().getInt();
int offset = computeValueOffsetForSlave(copy.hashCode(), intValue);
copy.setValue(new IntegerValue(intValue + offset));
}
};
// need a strong reference to the listener
attributeChangeExpressionListeners.add(updateListener);
changed.addUpdateListener(
weakUpdateListenerExpression(updateListener, changed));
}
protected void addDisplayModeListener(final TiedNumberExpression<IntegerValue> changed,
final TiedNumberExpression<IntegerValue> copy) {
UpdateListener<TiedNumberExpression<IntegerValue>> displayModeUpdateListener =
new UpdateListener<TiedNumberExpression<IntegerValue>>() {
public void objectUpdated(UpdateEvent<TiedNumberExpression<IntegerValue>> e) {
copy.setDisplayMode(changed.getDisplayMode());
}
};
// need a strong reference to the listener
attributeChangeTiedNumberListeners.add(displayModeUpdateListener);
// the following didn't work -- see Issue 564
// because there were no strong references to the listener
changed.addDisplayModeUpdateListener(
weakUpdateListenerTiedNumberExpression(displayModeUpdateListener, changed));
}
public TiedNumberExpression<?> getMasterTiedNumber(TiedNumberExpression<IntegerValue> slavedTiedNumber) {
List<TiedNumberExpression<IntegerValue>> taskVariables = getUnlockedNumbers();
int tiedNumberIndex = taskVariables.indexOf(slavedTiedNumber);
if (tiedNumberIndex < 0) {
return slavedTiedNumber;
} else if (tiedNumberIndex < getUnlockedNumbers().size()) {
return getMasterModel().getUnlockedNumbers().get(tiedNumberIndex);
} else {
// is not a variable that differs in the two models
return null;
}
}
/**
* @param expression
* @return a copy of the expression with each tied number replaced by the
* slave universe copy
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Expression<IntegerValue> createSlaveEquivalent(Expression<IntegerValue> expression) {
if (expression.isTiedNumber()) {
return (Expression<IntegerValue>)
getOrCreateSlaveCopy((TiedNumberExpression<IntegerValue>) expression);
} else if (expression instanceof ModifiableOperation) {
ModifiableOperation<IntegerValue, IntegerValue> operation =
(ModifiableOperation<IntegerValue, IntegerValue>) expression;
int operandCount = operation.getNumOperands();
if (operandCount == 0) {
return expression;
}
ArrayList<Expression<IntegerValue>> newOperands =
new ArrayList<Expression<IntegerValue>>(operandCount);
for (int i = 0; i < operandCount; i++) {
Expression operand = operation.getOperand(i);
newOperands.add(i, createSlaveEquivalent(operand));
}
return new ModifiableOperation(operation.getOperator(), newOperands);
} else {
return expression;
}
}
/**
* @param expression
* @return a copy of the expression with each tied number replaced by the
* master universe copy
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Expression<IntegerValue> createMasterEquivalent(Expression<IntegerValue> expression) {
if (expression.isTiedNumber()) {
return (Expression<IntegerValue>) getMasterTiedNumber((TiedNumberExpression<IntegerValue>) expression);
} else if (expression instanceof ModifiableOperation) {
ModifiableOperation<IntegerValue, IntegerValue> operation =
(ModifiableOperation<IntegerValue, IntegerValue>) expression;
int operandCount = operation.getNumOperands();
if (operandCount == 0) {
return expression;
}
ArrayList<Expression<IntegerValue>> newOperands =
new ArrayList<Expression<IntegerValue>>(operandCount);
for (int i = 0; i < operandCount; i++) {
Expression operand = operation.getOperand(i);
newOperands.add(i, createMasterEquivalent(operand));
}
return new ModifiableOperation(operation.getOperator(), newOperands);
} else {
return expression;
}
}
public int computeValueOffsetForSlave(int hashCode, int intValue) {
// by adding the hash code of the tied number and the value multiplied
// by a small prime
// we should get different offsets for different values
// see Issue 384
int hashCodeRemaining = hashCode;
int key = hashCode + intValue * 7;
int offset = randomOffsets[key % randomOffsets.length];
if (intValue + offset <= 1) {
// don't want a value less than 2
offset = Math.abs(offset);
}
int offsetThatMayBeInUse = offset;
// try to avoid having the same offsets for different tied numbers
while (isOffsetInUse(offset) && hashCodeRemaining > 0) {
hashCodeRemaining = hashCodeRemaining / randomOffsets.length;
offset = randomOffsets[hashCodeRemaining % randomOffsets.length];
if (intValue + offset <= 1) {
offset = Math.abs(offset);
}
}
if (hashCodeRemaining == 0) {
// try again with emergency numbers
hashCodeRemaining = hashCode;
key = hashCode + intValue * 7;
offset = emergencyRandomOffsets[key % emergencyRandomOffsets.length];
if (intValue + offset <= 1) {
// don't want a value less than 2
offset = Math.abs(offset);
}
// try to avoid having the same offsets for different tied numbers
while (isOffsetInUse(offset) && hashCodeRemaining > 0) {
hashCodeRemaining =
hashCodeRemaining / emergencyRandomOffsets.length;
offset = emergencyRandomOffsets[hashCodeRemaining % emergencyRandomOffsets.length];
if (intValue + offset <= 1) {
offset = Math.abs(offset);
}
}
}
if (hashCodeRemaining == 0) {
offset = firstUnusedOffset(2 - intValue);
if (offset == 0) {
return offsetThatMayBeInUse;
}
}
usingOffset(offset);
return offset;
}
//
// public void putSlaveCopy(TiedNumberExpression<?> masterTiedNumber,
// TiedNumberExpression<?> slaveTiedNumber) {
// tiedNumbersMasterToSlaveMapping.put(masterTiedNumber, slaveTiedNumber);
// }
@Override
public void removeAllObjects() {
super.removeAllObjects();
// tiedNumbersMasterToSlaveMapping.clear();
offsetsInUse.clear();
attributeChangeTiedNumberListeners.clear();
attributeChangeExpressionListeners.clear();
}
public boolean isOffsetInUse(Integer offset) {
return offsetsInUse.contains(offset);
}
public void usingOffset(Integer offset) {
offsetsInUse.add(offset);
}
public int firstUnusedOffset(int minimum) {
for (int offset : randomOffsets) {
if (offset >= minimum && !isOffsetInUse(offset)) {
return offset;
}
}
for (int offset : emergencyRandomOffsets) {
if (offset >= minimum && !isOffsetInUse(offset)) {
return offset;
}
}
return 0;
}
public boolean atLeastOneTaskVariable() {
return !getUnlockedNumbers().isEmpty();
}
public void beginCopy() {
// see comment associated with ModelCopier.getSlaveModelBeingConstructed
ModelCopier.setSlaveModelBeingConstructed(this);
}
public void endCopy() {
ModelCopier.setSlaveModelBeingConstructed(null);
}
public ExpresserModel getMasterModel() {
return masterModel;
}
public void setMasterModel(ExpresserModel masterModel) {
this.masterModel = masterModel;
}
@Override
public boolean isSlaved() {
return true;
}
@SuppressWarnings("unchecked")
public UpdateListener<TiedNumberExpression<IntegerValue>> weakUpdateListenerTiedNumberExpression(
UpdateListener<TiedNumberExpression<IntegerValue>> listener, Object source) {
return (UpdateListener<TiedNumberExpression<IntegerValue>>) WeakListeners.create(UpdateListener.class,
listener,
source);
}
@SuppressWarnings("unchecked")
public UpdateListener<Expression<IntegerValue>> weakUpdateListenerExpression(
UpdateListener<Expression<IntegerValue>> listener, Object source) {
return (UpdateListener<Expression<IntegerValue>>) WeakListeners.create(UpdateListener.class,
listener,
source);
}
public boolean isFrozen() {
return frozen;
}
public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
public void addUpdateListener(UpdateListener<ExpresserModelSlaveCopy> listener) {
updateSupport.addListener(listener);
}
public void removeUpdateListener(UpdateListener<ExpresserModelSlaveCopy> listener) {
updateSupport.removeListener(listener);
}
public void fireUpdateListeners() {
updateSupport.fireObjectUpdated(null);
}
@Override
public ArrayList<TiedNumberExpression<?>> getUnlockedTiedNumbers() {
return unlockedTiedNumbers;
}
public void setUnlockedTiedNumbers(ArrayList<TiedNumberExpression<?>> unlockedTiedNumbers) {
this.unlockedTiedNumbers = unlockedTiedNumbers;
}
@Override
public ArrayList<TiedNumberExpression<?>> getAnimationTiedNumbers() {
return getUnlockedTiedNumbers();
}
@Override
@SuppressWarnings("unchecked")
public AttributeChangeListener<BlockShape> weakListener(AttributeChangeListener<BlockShape> listener, Object source) {
// non-slaved models don't need to bother making these listeners weak
// moved from ExpresserModel to facilitate porting to Google Web Toolkit
return (AttributeChangeListener<BlockShape>) WeakListeners.create(AttributeChangeListener.class,
listener,
source);
}
}
|