/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. All rights reserved. * * 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 com.noctarius.tengi.spi.statemachine; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; class BuilderImpl<E extends Enum<E>> implements StateMachine.Builder<E> { private final List<Transition<E>> transitions = new ArrayList<>(); @Override public StateMachine.Builder<E> addTransition(E oldState, E newState, StateMachine.Evaluator<E> evaluator) { transitions.add(new Transition<E>(oldState, newState, evaluator)); return this; } @Override public StateMachine<E> build(E startState, boolean contextSupport) { return new StateMachineImpl<>(transitions, startState, contextSupport); } private static class Transition<E extends Enum<E>> { private final E oldState; private final E newState; private final StateMachine.Evaluator<E> evaluator; Transition(E oldState, E newState, StateMachine.Evaluator<E> evaluator) { this.oldState = oldState; this.newState = newState; this.evaluator = evaluator; } } private static class StateMachineImpl<E extends Enum<E>> implements StateMachine<E> { private static final AtomicReferenceFieldUpdater<StateMachineImpl, Enum> STATE_UPDATER = AtomicReferenceFieldUpdater .newUpdater(StateMachineImpl.class, Enum.class, "currentState"); private final Context context; private final Map<E, List<Transition<E>>> transitions; private volatile E currentState; StateMachineImpl(List<Transition<E>> transitions, E startState, boolean contextSupport) { this.transitions = createTransitionLookupTable(transitions); this.context = contextSupport ? new ContextImpl() : null; this.currentState = startState; } @Override public E currentState() { return currentState; } @Override public Context getContext() { return context; } @Override public boolean transit(E newState) { List<Transition<E>> transitions = this.transitions.get(newState); if (transitions == null || transitions.size() == 0) { return false; } // Sanity system, max of 100 retries for (int i = 0; i < 100; i++) { E currentState = this.currentState; Transition<E> transition = findTransition(currentState, transitions); if (transition == null) { return false; } if (transition.evaluator != null) { if (!transition.evaluator.evaluate(currentState, newState, context)) { return false; } } if (STATE_UPDATER.compareAndSet(this, currentState, newState)) { return true; } } return false; } private Transition<E> findTransition(E currentState, List<Transition<E>> transitions) { for (Transition<E> transition : transitions) { if (transition.oldState == currentState) { return transition; } } return null; } private Map<E, List<Transition<E>>> createTransitionLookupTable(List<Transition<E>> transitions) { Map<E, List<Transition<E>>> mapping = new HashMap<>(); for (Transition<E> transition : transitions) { List<Transition<E>> temp = mapping.get(transition.newState); if (temp == null) { temp = new ArrayList<>(); mapping.put(transition.newState, temp); } temp.add(transition); } return mapping; } } private static class ContextImpl implements StateMachine.Context { private final ConcurrentMap<String, Object> attributes = new ConcurrentHashMap<>(); @Override public <T> T getAttribute(String name) { return (T) attributes.get(name); } @Override public <T> T setAttribute(String name, T value) { return (T) attributes.put(name, value); } @Override public Map<String, Object> getAttributes() { return Collections.unmodifiableMap(attributes); } } }