/*
* $Id$
*
* Copyright (c) 2008-2009 by Brent Easton
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.counters;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import VASSAL.build.BadDataReport;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.command.Command;
import VASSAL.configure.BeanShellExpressionConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.script.expression.BeanShellExpression;
import VASSAL.script.expression.Expression;
import VASSAL.script.expression.ExpressionException;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.RecursionLimitException;
import VASSAL.tools.RecursionLimiter;
import VASSAL.tools.RecursionLimiter.Loopable;
import VASSAL.tools.SequenceEncoder;
/**
* Conditional Marker
* A marker with a variable value depending on conditions.
* */
public class CalculatedProperty extends Decorator implements EditablePiece, Loopable {
public static final String ID = "calcProp;";
protected static int counter = 0;
protected String name = "";
protected Expression expression;
public CalculatedProperty() {
this(ID, null);
}
public CalculatedProperty(String type, GamePiece inner) {
mySetType(type);
setInner(inner);
}
public Rectangle boundingBox() {
return piece.boundingBox();
}
public void draw(Graphics g, int x, int y, Component obs, double zoom) {
piece.draw(g, x, y, obs, zoom);
}
public String getName() {
return piece.getName();
}
protected KeyCommand[] myGetKeyCommands() {
return new KeyCommand[0];
}
public String myGetState() {
return "";
}
public String myGetType() {
SequenceEncoder se = new SequenceEncoder(';');
se.append(name)
.append(getExpression());
return ID + se.getValue();
}
public Command myKeyEvent(KeyStroke stroke) {
return null;
}
public void mySetState(String newState) {
}
public Shape getShape() {
return piece.getShape();
}
public String getDescription() {
String desc = "Calculated Property";
if (name != null && name.length() > 0) {
desc += " - " + name;
}
return desc;
}
public HelpFile getHelpFile() {
return HelpFile.getReferenceManualPage("CalculatedProperty.htm");
}
public void mySetType(String type) {
SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(type, ';');
st.nextToken();
name = st.nextToken("");
expression = BeanShellExpression.createExpression(st.nextToken(""));
}
protected String getExpression() {
return expression.getExpression();
}
/**
* Return the value of this trait's property.
* Evaluating Expressions can lead to infinite loops and
* eventually a Stack Overflow. Trap and report this
*/
public Object getProperty(Object key) {
Object result = "";
if (name.length() > 0 && name.equals(key)) {
try {
RecursionLimiter.startExecution(this);
result = evaluate();
return result;
}
catch (RecursionLimitException e) {
RecursionLimiter.infiniteLoop(e);
}
finally {
RecursionLimiter.endExecution();
}
return result;
}
return super.getProperty(key);
}
public Object getLocalizedProperty(Object key) {
if (name.length() > 0 && name.equals(key)) {
return getProperty(key);
}
return super.getLocalizedProperty(key);
}
/**
* Evaluate the calculated property. Do not call Decorator.reportDataError as this will probably
* cause an infinite reporting loop.
*
* @return value
*/
protected String evaluate() {
try {
return expression.evaluate(Decorator.getOutermost(this));
}
catch (ExpressionException e) {
ErrorDialog.dataError(new BadDataReport(Resources.getString("Error.expression_error"), piece.getProperty(BasicPiece.BASIC_NAME)+"-Calculated Property["+name+"]="+getExpression()+", Error="+e.getError(), e));
return "";
}
}
public PieceEditor getEditor() {
return new Ed(this);
}
/**
* Trait Editor implementation
*
*/
public static class Ed implements PieceEditor {
protected StringConfigurer nameConfig;
protected BeanShellExpressionConfigurer expressionConfig;
protected StringConfigurer defaultValueConfig;
protected JPanel box;
public Ed(CalculatedProperty piece) {
box = new JPanel();
box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));
nameConfig = new StringConfigurer(null, "Property Name: ", piece.name);
box.add(nameConfig.getControls());
expressionConfig = new BeanShellExpressionConfigurer(null, "Expression: ", piece.getExpression(), Decorator.getOutermost(piece));
box.add(expressionConfig.getControls());
}
public Component getControls() {
return box;
}
public String getState() {
return "";
}
public String getType() {
SequenceEncoder se = new SequenceEncoder(';');
se.append(nameConfig.getValueString())
.append(expressionConfig.getValueString());
return ID + se.getValue();
}
}
// Implement Loopable
public String getComponentName() {
// Use inner name to prevent recursive looping when reporting errors.
return piece.getName();
}
public String getComponentTypeName() {
return getDescription();
}
/**
* Return Property names exposed by this trait
*/
public List<String> getPropertyNames() {
ArrayList<String> l = new ArrayList<String>();
l.add(name);
return l;
}
}