]> git.basschouten.com Git - openhab-addons.git/blob
c3ea3e0eb062fddec6a4135943c4b4691ddfdac4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.max.internal.message;
14
15 import java.util.ArrayList;
16 import java.util.List;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.openhab.binding.max.internal.exceptions.IncompleteMessageException;
21 import org.openhab.binding.max.internal.exceptions.IncorrectMultilineIndexException;
22 import org.openhab.binding.max.internal.exceptions.MessageIsWaitingException;
23 import org.openhab.binding.max.internal.exceptions.NoMessageAvailableException;
24 import org.openhab.binding.max.internal.exceptions.UnprocessableMessageException;
25 import org.openhab.binding.max.internal.exceptions.UnsupportedMessageTypeException;
26
27 /**
28  * The message processor was introduced to combine multiple received lines to
29  * one single message. There are cases, when the MAX! Cube sends multiple
30  * messages (M-Message for example). The message processor acts as stack for
31  * received messages. Every received line should be added to the processor.
32  * After every added line, the message processor analyses the line. It is not
33  * possible to add additional lines when there is a message ready to be
34  * processed.
35  *
36  * @author Christian Rockrohr <christian@rockrohr.de> - Initial contribution
37  */
38 @NonNullByDefault
39 public class MessageProcessor {
40
41     public static final String SEPARATOR = ":";
42
43     /**
44      * The message that was created from last line received. (Null if no message
45      * available yet)
46      */
47
48     private @Nullable Message currentMessage;
49
50     /**
51      * <pre>
52      * If more that one single line is required to create a message
53      *    numberOfRequiredLines holds the number of required messages to complete
54      *    receivedLines holds the lines received so far
55      *    currentMessageType indicates which message type is currently on stack
56      * </pre>
57      */
58     private @Nullable Integer numberOfRequiredLines;
59     private List<String> receivedLines = new ArrayList<>();
60     private @Nullable MessageType currentMessageType;
61
62     /**
63      * Resets the current status and processed lines. Should be used after
64      * processing a message
65      */
66     public void reset() {
67         this.currentMessage = null;
68         receivedLines.clear();
69         currentMessageType = null;
70         numberOfRequiredLines = null;
71     }
72
73     /**
74      * Analyses the line and creates a message when possible. If the line
75      * indicates, that additional lines are required to create a complete
76      * message, the message processor keeps the line in memory and awaits
77      * additional lines. If the new line does not fit into current state
78      * (incomplete M: message on stack but L: message line received) a
79      * IncompleteMessageException is thrown.
80      *
81      * @param line
82      *            is the new line received
83      * @return true if a message could be created by this line, false in any
84      *         other cases (line was stacked, error, ...)
85      * @throws MessageIsWaitingException
86      *             when a line was added without pulling the previous message
87      * @throws IncompleteMessageException
88      *             when a line was added that does not belong to current message
89      *             stack
90      * @throws UnsupportedMessageTypeException
91      *             in case the line starts with an unknown message indicator
92      * @throws UnprocessableMessageException
93      *             is thrown when there was a known message indicator found, but
94      *             message could not be parsed correctly.
95      * @throws IncorrectMultilineIndexException
96      */
97     public Boolean addReceivedLine(String line) throws IncompleteMessageException, MessageIsWaitingException,
98             UnsupportedMessageTypeException, UnprocessableMessageException, IncorrectMultilineIndexException {
99         if (this.currentMessage != null) {
100             throw new MessageIsWaitingException();
101         }
102
103         MessageType messageType = getMessageType(line);
104
105         if (messageType == null) {
106             throw new UnsupportedMessageTypeException();
107         }
108
109         if ((this.currentMessageType != null) && (!messageType.equals(this.currentMessageType))) {
110             throw new IncompleteMessageException();
111         }
112
113         Boolean result = true;
114
115         switch (messageType) {
116             case H:
117                 this.currentMessage = new HMessage(line);
118                 break;
119             case C:
120                 this.currentMessage = new CMessage(line);
121                 break;
122             case L:
123                 this.currentMessage = new LMessage(line);
124                 break;
125             case S:
126                 this.currentMessage = new SMessage(line);
127                 break;
128             case M:
129                 result = handleMMessageLine(line);
130                 break;
131             case N:
132                 this.currentMessage = new NMessage(line);
133                 break;
134             case F:
135                 this.currentMessage = new FMessage(line);
136                 break;
137             case A:
138                 this.currentMessage = new AMessage(line);
139                 break;
140             default:
141         }
142
143         return result;
144     }
145
146     private Boolean handleMMessageLine(String line)
147             throws UnprocessableMessageException, IncompleteMessageException, IncorrectMultilineIndexException {
148         Boolean result = false;
149
150         String[] tokens = line.split(Message.DELIMETER); // M:00,01,xyz.....
151
152         try {
153             Integer index = Integer.valueOf(tokens[0].replaceFirst("M:", "")); // M:00
154             Integer counter = Integer.valueOf(tokens[1]); // 01
155
156             if (this.numberOfRequiredLines == null) {
157                 switch (counter) {
158                     case 0:
159                         throw new UnprocessableMessageException();
160                     case 1:
161                         this.currentMessage = new MMessage(line);
162                         result = true;
163                         break;
164                     default:
165                         this.numberOfRequiredLines = counter;
166                         this.currentMessageType = MessageType.M;
167                         if (index == 0) {
168                             this.receivedLines.add(line);
169                         } else {
170                             throw new IncorrectMultilineIndexException();
171                         }
172                 }
173             } else {
174                 if ((!counter.equals(this.numberOfRequiredLines)) || (!(index == this.receivedLines.size()))) {
175                     throw new IncorrectMultilineIndexException();
176                 }
177
178                 receivedLines.add(tokens[2]);
179
180                 if (index + 1 == receivedLines.size()) {
181                     String newLine = "";
182                     for (String curLine : receivedLines) {
183                         newLine += curLine;
184                     }
185                     this.currentMessage = new MMessage(newLine);
186                     result = true;
187                 }
188             }
189         } catch (IncorrectMultilineIndexException ex) {
190             throw ex;
191         } catch (Exception ex) {
192             throw new UnprocessableMessageException();
193         }
194
195         return result;
196     }
197
198     /**
199      * @return true if there is a message waiting to be pulled
200      */
201     public boolean isMessageAvailable() {
202         return this.currentMessage != null;
203     }
204
205     /**
206      * Pulls the message from the stack when there is one available. This needs
207      * to be done before next line can be added into message processor. When
208      * message is pulled, the message processor is reseted and ready to process
209      * next line.
210      *
211      * @return Message
212      * @throws NoMessageAvailableException
213      *             when there was no message on the stack
214      */
215     @Nullable
216     public Message pull() throws NoMessageAvailableException {
217         final Message result = this.currentMessage;
218         if (this.currentMessage == null) {
219             throw new NoMessageAvailableException();
220         }
221         reset();
222         return result;
223     }
224
225     /**
226      * Processes the raw TCP data read from the MAX protocol, returning the
227      * corresponding MessageType.
228      *
229      * @param line
230      *            the raw data provided read from the MAX protocol
231      * @return MessageType of the line added
232      */
233     @Nullable
234     private static MessageType getMessageType(String line) {
235         for (MessageType msgType : MessageType.values()) {
236             if (line.startsWith(msgType.name() + SEPARATOR)) {
237                 return msgType;
238             }
239         }
240         return null;
241     }
242 }