2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.max.internal.message;
15 import java.util.ArrayList;
16 import java.util.List;
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;
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
36 * @author Christian Rockrohr - Initial contribution
39 public class MessageProcessor {
41 public static final String SEPARATOR = ":";
44 * The message that was created from last line received. (Null if no message
48 private @Nullable Message currentMessage;
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
58 private @Nullable Integer numberOfRequiredLines;
59 private List<String> receivedLines = new ArrayList<>();
60 private @Nullable MessageType currentMessageType;
63 * Resets the current status and processed lines. Should be used after
64 * processing a message
67 this.currentMessage = null;
68 receivedLines.clear();
69 currentMessageType = null;
70 numberOfRequiredLines = null;
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.
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
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
97 public Boolean addReceivedLine(String line) throws IncompleteMessageException, MessageIsWaitingException,
98 UnsupportedMessageTypeException, UnprocessableMessageException, IncorrectMultilineIndexException {
99 if (this.currentMessage != null) {
100 throw new MessageIsWaitingException();
103 MessageType messageType = getMessageType(line);
105 if (messageType == null) {
106 throw new UnsupportedMessageTypeException();
109 if ((this.currentMessageType != null) && (!messageType.equals(this.currentMessageType))) {
110 throw new IncompleteMessageException();
113 Boolean result = true;
115 switch (messageType) {
117 this.currentMessage = new HMessage(line);
120 this.currentMessage = new CMessage(line);
123 this.currentMessage = new LMessage(line);
126 this.currentMessage = new SMessage(line);
129 result = handleMMessageLine(line);
132 this.currentMessage = new NMessage(line);
135 this.currentMessage = new FMessage(line);
138 this.currentMessage = new AMessage(line);
146 private Boolean handleMMessageLine(String line)
147 throws UnprocessableMessageException, IncompleteMessageException, IncorrectMultilineIndexException {
148 Boolean result = false;
150 String[] tokens = line.split(Message.DELIMETER); // M:00,01,xyz.....
153 Integer index = Integer.valueOf(tokens[0].replaceFirst("M:", "")); // M:00
154 Integer counter = Integer.valueOf(tokens[1]); // 01
156 if (this.numberOfRequiredLines == null) {
159 throw new UnprocessableMessageException();
161 this.currentMessage = new MMessage(line);
165 this.numberOfRequiredLines = counter;
166 this.currentMessageType = MessageType.M;
168 this.receivedLines.add(line);
170 throw new IncorrectMultilineIndexException();
174 if (!counter.equals(this.numberOfRequiredLines) || index != this.receivedLines.size()) {
175 throw new IncorrectMultilineIndexException();
178 receivedLines.add(tokens[2]);
180 if (index + 1 == receivedLines.size()) {
182 for (String curLine : receivedLines) {
185 this.currentMessage = new MMessage(newLine);
189 } catch (IncorrectMultilineIndexException ex) {
191 } catch (Exception ex) {
192 throw new UnprocessableMessageException();
199 * @return true if there is a message waiting to be pulled
201 public boolean isMessageAvailable() {
202 return this.currentMessage != null;
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
212 * @throws NoMessageAvailableException
213 * when there was no message on the stack
216 public Message pull() throws NoMessageAvailableException {
217 final Message result = this.currentMessage;
218 if (this.currentMessage == null) {
219 throw new NoMessageAvailableException();
226 * Processes the raw TCP data read from the MAX protocol, returning the
227 * corresponding MessageType.
230 * the raw data provided read from the MAX protocol
231 * @return MessageType of the line added
234 private static MessageType getMessageType(String line) {
235 for (MessageType msgType : MessageType.values()) {
236 if (line.startsWith(msgType.name() + SEPARATOR)) {