Skip to content

Commit

Permalink
Should not define initial state as state
Browse files Browse the repository at this point in the history
- Tweak DefaultStateConfigurer so that we don't need
  to define initial state as additional state. Also
  fixed some other issue which caused a bit of a mess
  when i.e multiple states were introduces twice.
- Fixes spring-projects#29
  • Loading branch information
jvalkeal committed May 6, 2015
1 parent 1186ae4 commit 8e42ca0
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,26 @@ public S getState() {
public Collection<E> getDeferred() {
return deferred;
}

public void setDeferred(Collection<E> deferred) {
this.deferred = deferred;
}

public Collection<? extends Action<S, E>> getEntryActions() {
return entryActions;
}

public void setEntryActions(Collection<? extends Action<S, E>> entryActions) {
this.entryActions = entryActions;
}

public Collection<? extends Action<S, E>> getExitActions() {
return exitActions;
}

public void setExitActions(Collection<? extends Action<S, E>> exitActions) {
this.exitActions = exitActions;
}

public Object getParent() {
return parent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

Expand Down Expand Up @@ -45,7 +47,7 @@ public class DefaultStateConfigurer<S, E>

private final Object region = UUID.randomUUID().toString();

private final Collection<StateData<S, E>> incomplete = new ArrayList<StateData<S, E>>();
private final Map<S, StateData<S, E>> incomplete = new HashMap<S, StateData<S, E>>();

private S initialState;

Expand All @@ -68,7 +70,7 @@ public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
// before passing state datas to builder, update structure
// for missing parent, initial and end state infos.
Collection<StateData<S, E>> stateDatas = new ArrayList<StateData<S, E>>();
for (StateData<S, E> s : incomplete) {
for (StateData<S, E> s : incomplete.values()) {
s.setParent(parent);
stateDatas.add(s);
if (s.getState() == initialState) {
Expand Down Expand Up @@ -99,6 +101,7 @@ public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
@Override
public StateConfigurer<S, E> initial(S initial) {
this.initialState = initial;
state(initial);
return this;
}

Expand Down Expand Up @@ -195,7 +198,26 @@ public StateConfigurer<S, E> history(S history, History type) {

private void addIncomplete(Object parent, S state, Collection<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
incomplete.add(new StateData<S, E>(parent, region, state, deferred, entryActions, exitActions));
StateData<S, E> stateData = incomplete.get(state);
if (stateData == null) {
stateData = new StateData<S, E>(parent, region, state, deferred, entryActions, exitActions);
incomplete.put(state, stateData);
}
if (stateData.getParent() == null) {
stateData.setParent(parent);
}
if (stateData.getRegion() == null) {
stateData.setRegion(region);
}
if (stateData.getDeferred() == null) {
stateData.setDeferred(deferred);
}
if (stateData.getEntryActions() == null) {
stateData.setEntryActions(entryActions);
}
if (stateData.getExitActions() == null) {
stateData.setExitActions(exitActions);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.statemachine.config.configurers;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.springframework.statemachine.AbstractStateMachineTests.TestEntryAction;
import org.springframework.statemachine.AbstractStateMachineTests.TestEvents;
import org.springframework.statemachine.AbstractStateMachineTests.TestExitAction;
import org.springframework.statemachine.AbstractStateMachineTests.TestStates;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.StateData;
import org.springframework.statemachine.config.builders.StateMachineStateBuilder;

public class DefaultStateConfigurerTests {

@Test
public void testInitialWithoutState() throws Exception {
DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.initial(TestStates.SI);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));
assertThat(builder.data.iterator().next().getState(), is(TestStates.SI));
}

@Test
public void testInitialWithState() throws Exception {
DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.initial(TestStates.SI);
configurer.state(TestStates.SI);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));
assertThat(builder.data.iterator().next().getState(), is(TestStates.SI));
}

@Test
public void testSameStateShouldResultOneState() throws Exception {
DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.state(TestStates.SI);
configurer.state(TestStates.SI);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));
assertThat(builder.data.iterator().next().getState(), is(TestStates.SI));
}

@Test
public void testParentSet() throws Exception {
DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.parent(TestStates.SI);
configurer.state(TestStates.S1);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));
assertThat(builder.data.iterator().next().getState(), is(TestStates.S1));
assertThat((TestStates)builder.data.iterator().next().getParent(), is(TestStates.SI));
}

@Test
public void testActionsInitialFirst() throws Exception {
@SuppressWarnings("unchecked")
Collection<Action<TestStates, TestEvents>> exitActions = Arrays.asList(testExitAction());

DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.initial(TestStates.S1);
configurer.state(TestStates.S1, null, exitActions);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));

assertThat(builder.data.iterator().next().getState(), is(TestStates.S1));
assertThat(builder.data.iterator().next().getEntryActions(), nullValue());
assertThat(builder.data.iterator().next().getExitActions(), notNullValue());
}

@Test
public void testActionsJustState() throws Exception {
@SuppressWarnings("unchecked")
Collection<Action<TestStates, TestEvents>> entryActions = Arrays.asList(testEntryAction());

DefaultStateConfigurer<TestStates, TestEvents> configurer = new DefaultStateConfigurer<TestStates, TestEvents>();
TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder();
configurer.state(TestStates.S2, entryActions, null);
configurer.configure(builder);
assertThat(builder.data, notNullValue());
assertThat(builder.data.size(), is(1));

assertThat(builder.data.iterator().next().getState(), is(TestStates.S2));
assertThat(builder.data.iterator().next().getExitActions(), nullValue());
assertThat(builder.data.iterator().next().getEntryActions(), notNullValue());
}

private static class TestStateMachineStateBuilder extends StateMachineStateBuilder<TestStates, TestEvents> {

Collection<StateData<TestStates, TestEvents>> data;

@Override
public void addStateData(Collection<StateData<TestStates, TestEvents>> stateDatas) {
this.data = stateDatas;
}
}

private Action<TestStates, TestEvents> testEntryAction() {
return new TestEntryAction();
}

private Action<TestStates, TestEvents> testExitAction() {
return new TestExitAction();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ public void testInitialStateMissingFailure() throws Exception {
context.refresh();
}

@SuppressWarnings({ "unchecked" })
@Test
public void testInitialNoNeedAsState() throws Exception {
context.register(BaseConfig.class, Config3.class);
context.refresh();
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
EnumStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
machine.start();
assertThat(machine.getState().getIds(), contains(TestStates.SI));
machine.sendEvent(TestEvents.E1);
assertThat(machine.getState().getIds(), contains(TestStates.S1));
}

@Configuration
@EnableStateMachine
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
Expand Down Expand Up @@ -111,6 +125,29 @@ public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> t

}

@Configuration
@EnableStateMachine
public static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {

@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.state(TestStates.S1);
}

@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.SI)
.target(TestStates.S1)
.event(TestEvents.E1);
}

}

@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
Expand Down

0 comments on commit 8e42ca0

Please sign in to comment.