1 /*
2 * This file is part of Waarp Project (named also Waarp or GG).
3 *
4 * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5 * tags. See the COPYRIGHT.txt in the distribution for a full listing of
6 * individual contributors.
7 *
8 * All Waarp Project is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with
18 * Waarp . If not, see <http://www.gnu.org/licenses/>.
19 */
20 package org.waarp.common.state;
21
22 import org.waarp.common.exception.IllegalFiniteStateException;
23 import org.waarp.common.logging.WaarpLogger;
24 import org.waarp.common.logging.WaarpLoggerFactory;
25
26 import java.util.EnumSet;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap;
29
30 /**
31 * This is the base class for the basic support of Finite State Machine in
32 * GoldenGate. One need to implement
33 * an Enum class to use with it. <br>
34 * <br>
35 * Note: the type EnumSet< ? > is in fact of type EnumSet< EnumState >
36 *
37 * @param <E>
38 */
39 public class MachineState<E extends Enum<E>> {
40 /**
41 * Internal Logger
42 */
43 private static final WaarpLogger logger =
44 WaarpLoggerFactory.getLogger(MachineState.class);
45
46 private ConcurrentMap<E, EnumSet<E>> statemap;
47 private E currentState;
48
49 /**
50 * Initialize with an initialState
51 *
52 * @param initialState initial MachineState
53 * @param map the association of state and set of acceptable
54 * following
55 * states
56 */
57 public MachineState(final E initialState,
58 final ConcurrentMap<E, EnumSet<E>> map) {
59 statemap = map;
60 currentState = initialState;
61 }
62
63 /**
64 * Initialize with an initialState but no association (Machine State is
65 * empty)
66 *
67 * @param initialState initial MachineState
68 */
69 public MachineState(final E initialState) {
70 statemap = new ConcurrentHashMap<E, EnumSet<E>>();
71 currentState = initialState;
72 }
73
74 /**
75 * Add a new association from one state to a set of acceptable following
76 * states (can replace an existing
77 * association)
78 *
79 * @param state
80 * @param set the new association
81 *
82 * @return the previous association if any
83 */
84 public final EnumSet<E> addNewAssociation(final E state,
85 final EnumSet<E> set) {
86 return statemap.put(state, set);
87 }
88
89 /**
90 * Add a new association from one state to a set of acceptable following
91 * states (can replace an existing
92 * association)
93 *
94 * @param elt
95 *
96 * @return the previous association if any
97 */
98 public final EnumSet<E> addNewAssociation(final Transition<E> elt) {
99 return statemap.put(elt.getState(), elt.getSet());
100 }
101
102 /**
103 * Remove an association from one state to any acceptable following states
104 *
105 * @param state the state to remove any acceptable following states
106 *
107 * @return the previous association if any
108 */
109 public final EnumSet<E> removeAssociation(final E state) {
110 return statemap.remove(state);
111 }
112
113 /**
114 * Return the current application state.
115 *
116 * @return the current State
117 */
118 public final E getCurrent() {
119 return currentState;
120 }
121
122 /**
123 * Sets the current application state.
124 *
125 * @param desiredState
126 *
127 * @return the requested state, if it was reachable
128 *
129 * @throws IllegalFiniteStateException if the state is not allowed
130 */
131 public final E setCurrent(final E desiredState)
132 throws IllegalFiniteStateException {
133 if (!isReachable(desiredState)) {
134 logger.debug("State {} not reachable from: {}", desiredState,
135 currentState);
136 throw new IllegalFiniteStateException(
137 desiredState + " not allowed from " + currentState);
138 }
139 return setAsFinal(desiredState);
140 }
141
142 /**
143 * Sets the current application state, but no exception if not compatible.
144 *
145 * @param desiredState
146 *
147 * @return the requested state, even if it was not reachable
148 */
149 public final E setDryCurrent(final E desiredState) {
150 return setAsFinal(desiredState);
151 }
152
153 /**
154 * Determine if the given state is allowed to be next.
155 *
156 * @param desiredState desired MachineState
157 *
158 * @return True if the desiredState is valid from currentState
159 */
160 private boolean isReachable(final E desiredState) {
161 if (currentState == null || statemap == null) {
162 return false;
163 }
164 final EnumSet<?> set = statemap.get(currentState);
165 if (set != null) {
166 return set.contains(desiredState);
167 }
168 return false;
169 }
170
171 /**
172 * Finalizes the new requested state
173 *
174 * @param desiredState
175 *
176 * @return the requested state
177 */
178 private E setAsFinal(final E desiredState) {
179 logger.debug("New State: {} from {}", desiredState, currentState);
180 currentState = desiredState;
181 return currentState;
182 }
183
184 /**
185 * Release the Machine State
186 */
187 public final void release() {
188 currentState = null;
189 statemap = null;
190 }
191 }