@StateMachine
public static interface InvoiceStateMachineMeta {
@StateSet
static interface States {
@Initial
@Function(transition = InvoiceStateMachineMeta.Transitions.Post.class, value = { InvoiceStateMachineMeta.States.Posted.class })
static interface Draft {}
@Functions({ @Function(transition = InvoiceStateMachineMeta.Transitions.Pay.class, value = { States.PartialPaid.class,
InvoiceStateMachineMeta.States.PaidOff.class }) })
static interface Posted {}
@Function(transition = InvoiceStateMachineMeta.Transitions.Pay.class, value = { States.PartialPaid.class,
InvoiceStateMachineMeta.States.PaidOff.class })
static interface PartialPaid {}
@End
static interface PaidOff {}
}
@TransitionSet
static interface Transitions {
static interface Post {}
@Conditional(condition = Payable.class, judger = PayableJudger.class, postEval = true)
static interface Pay {}
}
@ConditionSet
static interface Conditions {
static interface Payable {
BigDecimal getTotalAmount();
BigDecimal getPayedAmount();
}
}
public static class Utilities {
public static class PayableJudger implements ConditionalTransition<Payable> {
@Override
public Class<?> doConditionJudge(Payable t) {
if ( 0 < t.getPayedAmount().compareTo(BigDecimal.ZERO) && 0 < t.getTotalAmount().compareTo(t.getPayedAmount()) ) {
return PartialPaid.class;
} else if ( 0 >= t.getTotalAmount().compareTo(t.getPayedAmount()) ) {
return PaidOff.class;
} else {
throw new IllegalStateException();
}
}
}
}
}
@StateMachine
public static interface InvoiceItemStateMachineMeta {
@StateSet
static interface States {
@Initial
@Function(transition = InvoiceItemStateMachineMeta.Transitions.Pay.class, value = { InvoiceItemStateMachineMeta.States.Paid.class })
static interface Unpaid {}
@End
@InboundWhile(on = { InvoiceStateMachineMeta.States.Posted.class, InvoiceStateMachineMeta.States.PartialPaid.class },
relation = InvoiceItemStateMachineMeta.Relations.ParentInvoice.class)
static interface Paid {}
}
@TransitionSet
static interface Transitions {
static interface Pay {}
}
@RelationSet
static interface Relations {
@RelateTo(InvoiceStateMachineMeta.class)
static interface ParentInvoice {}
}
}
@LifecycleMeta(InvoiceStateMachineMeta.class)
public static class Invoice extends ReactiveObject implements InvoiceStateMachineMeta.Conditions.Payable {
private final BigDecimal totalAmount;;
private BigDecimal payedAmount = new BigDecimal(0D);
private final List<InvoiceItem> items = new ArrayList<>();
public Invoice(final BigDecimal totalAmount) {
initialState(InvoiceStateMachineMeta.States.Draft.class.getSimpleName());
this.totalAmount = totalAmount;
}
@Condition(InvoiceStateMachineMeta.Conditions.Payable.class)
public InvoiceStateMachineMeta.Conditions.Payable getPayable() {
return this;
}
@Override
public BigDecimal getTotalAmount() {
return totalAmount;
}
@Override
public synchronized BigDecimal getPayedAmount() {
return payedAmount;
}
@Transition
public void post() {}
@Transition(InvoiceStateMachineMeta.Transitions.Pay.class)
@PostStateChange(to = InvoiceItemStateMachineMeta.States.Paid.class, observableName = "items", mappedBy = "parent")
public synchronized void onItemPaied(LifecycleContext<InvoiceItem, String> context) {
payedAmount = payedAmount.add(context.getTarget().getPayedAmount());
}
public void addItem(InvoiceItem invoiceItem) {
if ( !items.contains(invoiceItem) ) {
items.add(invoiceItem);
}
}
public List<InvoiceItem> getItems() {
return Collections.unmodifiableList(items);
}
}
@LifecycleMeta(InvoiceItemStateMachineMeta.class)
public static class InvoiceItem extends ReactiveObject {
private int seq;
private BigDecimal amount;
private BigDecimal payedAmount;
private final Invoice parent;
public InvoiceItem(Invoice parent, BigDecimal amount) {
initialState(InvoiceItemStateMachineMeta.States.Unpaid.class.getSimpleName());
this.amount = amount;
this.parent = parent;
this.seq = this.parent.getItems().size() + 1;
this.parent.addItem(this);
}
@Transition
public void pay(final BigDecimal amount) {
if ( 0 < this.amount.compareTo(amount) ) {
throw new IllegalArgumentException("paying amount is not enough to pay this item.");
}
this.payedAmount = amount;
}
public BigDecimal getPayedAmount() {
return payedAmount;
}
@Relation(InvoiceItemStateMachineMeta.Relations.ParentInvoice.class)
public Invoice getParent() {
return this.parent;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ( ( parent == null ) ? 0 : parent.hashCode() );
result = prime * result + seq;
return result;
}
@Override
public boolean equals(Object obj) {
if ( this == obj ) return true;
if ( obj == null ) return false;
if ( getClass() != obj.getClass() ) return false;
InvoiceItem other = (InvoiceItem) obj;
if ( parent == null ) {
if ( other.parent != null ) return false;
} else if ( !parent.equals(other.parent) ) return false;
if ( seq != other.seq ) return false;
return true;
}
}
@Test
public void test_relational_callback_post_state_change() {
final Invoice invoice = new Invoice(new BigDecimal(10000.0D));
final InvoiceItem itemOne = new InvoiceItem(invoice, new BigDecimal(4000.0D));
final InvoiceItem itemTwo = new InvoiceItem(invoice, new BigDecimal(4000.0D));
final InvoiceItem itemThree = new InvoiceItem(invoice, new BigDecimal(2000.0D));
invoice.post();
assertState(InvoiceStateMachineMeta.States.Posted.class, invoice);
assertState(InvoiceItemStateMachineMeta.States.Unpaid.class, itemOne);
itemOne.pay(new BigDecimal(4000.0D));
assertState(InvoiceItemStateMachineMeta.States.Paid.class, itemOne);
assertState(InvoiceStateMachineMeta.States.PartialPaid.class, invoice);
itemTwo.pay(new BigDecimal(4000.0D));
assertState(InvoiceItemStateMachineMeta.States.Paid.class, itemTwo);
assertState(InvoiceStateMachineMeta.States.PartialPaid.class, invoice);
itemThree.pay(new BigDecimal(2000.0D));
assertState(InvoiceItemStateMachineMeta.States.Paid.class, itemThree);
assertState(InvoiceStateMachineMeta.States.PaidOff.class, invoice);
}