FSA_Validator
366 строк · 10.9 Кб
1import java.util.ArrayList;
2import java.util.HashMap;
3import java.util.HashSet;
4
5/**
6* Class that represents Finite State Machine.
7*/
8public class FSA {
9/**
10* Array of states.
11*/
12ArrayList<String> states = new ArrayList<>();
13/**
14* Array with alphabet.
15*/
16ArrayList<String> alphabet = new ArrayList<>();
17/**
18* Array with Initial states.
19*/
20ArrayList<String> initialStates = new ArrayList<>();
21/**
22* Array with Final states.
23*/
24ArrayList<String> finalStates = new ArrayList<>();
25/**
26* Array with Transitions.
27*/
28ArrayList<String[]> transitions = new ArrayList<>();
29/**
30* The Warnings.
31*/
32HashSet<String> warnings = new HashSet<>();
33/**
34* The Undirected graph.
35* <p>
36* key : state, value : child states
37*/
38HashMap<String, HashSet<String>> undirectedGraph = new HashMap<>();
39/**
40* The Directed graph.
41* <p>
42* key : state, value : {key : transition, value : child states}
43*/
44HashMap<String, HashMap<String, HashSet>> directedGraph = new HashMap<>();
45/**
46* Contains {@code true} if this {@link FSA} is deterministic, otherwise {@code false}.
47*/
48boolean deterministic = true;
49/**
50* Contains {@code true} if this {@link FSA} is complete, otherwise {@code false}.
51*/
52boolean complete = true;
53
54/**
55* Instantiates a new FSA.
56*
57* @param data input strings
58* @throws FSAException the FSA Exception
59*/
60FSA(ArrayList<String> data) throws FSAException {
61processData(data);
62
63allStatesReachable();
64isDisjoint();
65isDeterministic();
66isComplete();
67}
68
69/**
70* Handles all input strings.
71*
72* @param data input strings
73* @throws FSAException the FSA Exception
74*/
75private void processData(ArrayList<String> data) throws FSAException {
76processStates(data.get(0));
77processAlphas(data.get(1));
78processInitState(data.get(2));
79processFinalState(data.get(3));
80processTransitions(data.get(4));
81
82for (String state : this.states) {
83this.undirectedGraph.put(state, new HashSet<>());
84this.directedGraph.put(state, new HashMap<>());
85for (String a : this.alphabet) {
86this.directedGraph.get(state).put(a, new HashSet<>());
87}
88}
89
90for (String[] transition : this.transitions) {
91this.undirectedGraph.get(transition[0]).add(transition[2]);
92this.undirectedGraph.get(transition[2]).add(transition[0]);
93
94this.directedGraph.get(transition[0]).get(transition[1]).add(transition[2]);
95}
96
97
98}
99
100/**
101* Handles input string with {@code states}.
102*
103* @param s input strings
104* @throws FSAException the FSA Exception
105*/
106private void processStates(String s) throws FSAException {
107if (!s.startsWith("states=[") || !s.endsWith("]")) {
108throw new FSAException(Messages.E5.getValue());
109}
110
111String[] states = s.substring(8, s.length() - 1).split(",");
112for (String state : states) {
113if (state.length() == 0) {
114continue;
115}
116
117if (!FSA.isGoodStateName(state)) {
118throw new FSAException(Messages.E5.getValue());
119}
120this.states.add(state);
121}
122}
123
124/**
125* Handles input string with {@code alphabet}.
126*
127* @param s input strings
128* @throws FSAException the FSA Exception
129*/
130private void processAlphas(String s) throws FSAException {
131if (!s.startsWith("alpha=[") || !s.endsWith("]")) {
132throw new FSAException(Messages.E5.getValue());
133}
134
135String[] alphas = s.substring(7, s.length() - 1).split(",");
136for (String alpha : alphas) {
137if (alpha.length() == 0) {
138continue;
139}
140
141if (!FSA.isGoodAlphaName(alpha)) {
142throw new FSAException(Messages.E5.getValue());
143}
144this.alphabet.add(alpha);
145}
146}
147
148/**
149* Handles input string with {@code initialStates}.
150*
151* @param s input strings
152* @throws FSAException the FSA Exception
153*/
154private void processInitState(String s) throws FSAException {
155if (!s.startsWith("init.st=[") || !s.endsWith("]")) {
156throw new FSAException(Messages.E5.getValue());
157}
158
159String[] initStates = s.substring(9, s.length() - 1).split(",");
160
161for (String state : initStates) {
162if (state.length() == 0) {
163continue;
164}
165if (!this.states.contains(state)) {
166throw new FSAException(Messages.E1.getValue().formatted(state));
167}
168this.initialStates.add(state);
169}
170
171if (this.initialStates.size() < 1) {
172throw new FSAException(Messages.E4.getValue());
173}
174if (this.initialStates.size() > 1) {
175throw new FSAException(Messages.E5.getValue());
176}
177}
178
179/**
180* Handles input string with {@code finalStates}.
181*
182* @param s input strings
183* @throws FSAException the FSA Exception
184*/
185private void processFinalState(String s) throws FSAException {
186if (!s.startsWith("fin.st=[") || !s.endsWith("]")) {
187throw new FSAException(Messages.E5.getValue());
188}
189
190String[] finalStates = s.substring(8, s.length() - 1).split(",");
191for (String state : finalStates) {
192if (state.length() == 0) {
193continue;
194}
195if (!this.states.contains(state)) {
196throw new FSAException(Messages.E1.getValue().formatted(state));
197}
198this.finalStates.add(state);
199}
200
201if (this.finalStates.size() < 1) {
202this.warnings.add(Messages.W1.getValue());
203}
204}
205
206/**
207* Handles input string with {@code transitions}.
208*
209* @param s input strings
210* @throws FSAException the FSA Exception
211*/
212private void processTransitions(String s) throws FSAException {
213if (!s.startsWith("trans=[") || !s.endsWith("]")) {
214throw new FSAException(Messages.E5.getValue());
215}
216
217String[] transitions = s.substring(7, s.length() - 1).split(",");
218for (String trans : transitions) {
219if (trans.length() == 0) {
220continue;
221}
222String[] transition = trans.split(">");
223
224if (!this.states.contains(transition[0])) {
225throw new FSAException(Messages.E1.getValue().formatted(transition[0]));
226}
227if (!this.alphabet.contains(transition[1])) {
228throw new FSAException(Messages.E3.getValue().formatted(transition[1]));
229}
230if (!this.states.contains(transition[2])) {
231throw new FSAException(Messages.E1.getValue().formatted(transition[2]));
232}
233
234if (transition.length != 3) {
235throw new FSAException(Messages.E5.getValue());
236}
237this.transitions.add(transition);
238}
239}
240
241/**
242* Checks if name for {@code State} is good.
243*
244* @param s name
245* @return {@code true} if name is good, otherwise {@code false}
246*/
247public static boolean isGoodStateName(String s) {
248String alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
249
250for (char c : s.toCharArray()) {
251if (alphabet.indexOf(c) < 0) {
252return false;
253}
254}
255
256return true;
257}
258
259/**
260* Checks if name for {@code alphabet} is good.
261*
262* @param s name
263* @return {@code true} if name is good, otherwise {@code false}
264*/
265public static boolean isGoodAlphaName(String s) {
266String alphabet = "abcdefghijklmnopqrstuvwxyz0123456789_";
267
268for (char c : s.toCharArray()) {
269if (alphabet.indexOf(c) < 0) {
270return false;
271}
272}
273
274return true;
275}
276
277/**
278* Checks if all states of this {@link FSA} are reachable from initial state.
279*/
280private void allStatesReachable() {
281ArrayList<String> visited = recursiveSearchInDirectedGraph(this.initialStates.get(0), new ArrayList<>());
282if (visited.size() < this.states.size()) {
283warnings.add(Messages.W2.getValue());
284}
285}
286
287/**
288* Checks if this {@link FSA} has some disjoint states.
289*/
290private void isDisjoint() throws FSAException {
291ArrayList<String> visited = recursiveSearchInUndirectedGraph(this.initialStates.get(0), new ArrayList<>());
292if (visited.size() < this.states.size()) {
293throw new FSAException(Messages.E2.getValue());
294}
295}
296
297/**
298* Checks if this {@link FSA} is deterministic.
299*
300* @return {@code true} if this {@link FSA} is deterministic, otherwise {@code false}
301*/
302private boolean isDeterministic() {
303for (HashMap<String, HashSet> transition : this.directedGraph.values()) {
304for (HashSet<String> states : transition.values()) {
305if (states.size() > 1) {
306this.deterministic = false;
307this.warnings.add(Messages.W3.getValue());
308}
309}
310}
311return this.deterministic;
312}
313
314/**
315* Checks if this {@link FSA} is complete.
316*
317* @return {@code true} if this {@link FSA} is complete, otherwise {@code false}
318*/
319private boolean isComplete() {
320if (!isDeterministic()) {
321this.complete = false;
322}
323for (HashMap<String, HashSet> transition : this.directedGraph.values()) {
324for (HashSet<String> states : transition.values()) {
325if (states.size() == 0) {
326this.complete = false;
327}
328}
329}
330return this.complete;
331}
332
333
334/**
335* Checks which states are joint.
336*
337* @return array with visited states
338*/
339private ArrayList<String> recursiveSearchInUndirectedGraph(String start, ArrayList<String> visited) {
340for (String state : this.undirectedGraph.get(start)) {
341if (!visited.contains(state)) {
342visited.add(state);
343recursiveSearchInUndirectedGraph(state, visited);
344}
345}
346return visited;
347}
348
349/**
350* Checks which states are reachable from initial state.
351*
352* @return array with visited states
353*/
354private ArrayList<String> recursiveSearchInDirectedGraph(String start, ArrayList<String> visited) {
355for (HashSet<String> states : this.directedGraph.get(start).values()) {
356for (String state : states) {
357if (!visited.contains(state)) {
358visited.add(state);
359recursiveSearchInDirectedGraph(state, visited);
360}
361}
362}
363return visited;
364
365}
366}
367