/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.maxcube.internal.message; import java.util.ArrayList; import java.util.List; import org.openhab.binding.maxcube.internal.exceptions.IncompleteMessageException; import org.openhab.binding.maxcube.internal.exceptions.IncorrectMultilineIndexException; import org.openhab.binding.maxcube.internal.exceptions.MessageIsWaitingException; import org.openhab.binding.maxcube.internal.exceptions.NoMessageAvailableException; import org.openhab.binding.maxcube.internal.exceptions.UnprocessableMessageException; import org.openhab.binding.maxcube.internal.exceptions.UnsupportedMessageTypeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The message processor was introduced to combine multiple received lines to * one single message. There are cases, when the MAX!Cube sends multiple * messages (M-Message for example). The message processor acts as stack for * received messages. Every received line should be added to the processor. * After every added line, the message processor analyses the line. It is not * possible to add additional lines when there is a message ready to be * processed. * * @author Christian Rockrohr <christian@rockrohr.de> * @since 1.7.0 */ public class MessageProcessor { private static final Logger logger = LoggerFactory.getLogger(MessageProcessor.class); public static final String SEPARATOR = ":"; /** * The message that was created from last line received. (Null if no message * available yet) */ private Message currentMessage = null; /** * <pre> * If more that one single line is required to create a message * numberOfRequiredLines holds the number of required messages to complete * receivedLines holds the lines received so far * currentMessageType indicates which message type is currently on stack * </pre> */ private Integer numberOfRequiredLines = null; private List<String> receivedLines = new ArrayList<String>(); private MessageType currentMessageType = null; /** * Resets the current status and processed lines. Should be used after * processing a message */ public void reset() { this.currentMessage = null; receivedLines.clear(); currentMessageType = null; numberOfRequiredLines = null; } /** * Analyses the line and creates a message when possible. If the line * indicates, that additional lines are required to create a complete * message, the message processor keeps the line in memory and awaits * additional lines. If the new line does not fit into current state * (incomplete M: message on stack but L: message line received) a * IncompleteMessageException is thrown. * * @param line * is the new line received * @return true if a message could be created by this line, false in any * other cases (line was stacked, error, ...) * @throws MessageIsWaitingException * when a line was added without pulling the previous message * @throws IncompleteMessageException * when a line was added that does not belong to current message * stack * @throws UnsupportedMessageTypeException * in case the line starts with an unknown message indicator * @throws UnprocessableMessageException * is thrown when there was a known message indicator found, but * message could not be parsed correctly. * @throws IncorrectMultilineIndexException */ public Boolean addReceivedLine(String line) throws IncompleteMessageException, MessageIsWaitingException, UnsupportedMessageTypeException, UnprocessableMessageException, IncorrectMultilineIndexException { if (this.currentMessage != null) { throw new MessageIsWaitingException(); } MessageType messageType = getMessageType(line); if (messageType == null) { throw new UnsupportedMessageTypeException(); } if ((this.currentMessageType != null) && (!messageType.equals(this.currentMessageType))) { throw new IncompleteMessageException(); } Boolean result = true; switch (messageType) { case H: this.currentMessage = new H_Message(line); break; case C: this.currentMessage = new C_Message(line); break; case L: this.currentMessage = new L_Message(line); break; case S: this.currentMessage = new S_Message(line); break; case M: result = handle_M_MessageLine(line); break; default: } return result; } private Boolean handle_M_MessageLine(String line) throws UnprocessableMessageException, IncompleteMessageException, IncorrectMultilineIndexException { Boolean result = false; String[] tokens = line.split(Message.DELIMETER); // M:00,01,xyz..... try { Integer index = Integer.valueOf(tokens[0].replaceFirst("M:", "")); // M:00 Integer counter = Integer.valueOf(tokens[1]); // 01 if (this.numberOfRequiredLines == null) { switch (counter) { case 0: throw new UnprocessableMessageException(); case 1: this.currentMessage = new M_Message(line); result = true; break; default: this.numberOfRequiredLines = counter; this.currentMessageType = MessageType.M; if (index == 0) { this.receivedLines.add(line); } else { throw new IncorrectMultilineIndexException(); } } } else { if ((!counter.equals(this.numberOfRequiredLines)) || (!(index == this.receivedLines.size()))) { throw new IncorrectMultilineIndexException(); } receivedLines.add(tokens[2]); if (index + 1 == receivedLines.size()) { String newLine = ""; for (String curLine : receivedLines) { newLine += curLine; } this.currentMessage = new M_Message(newLine); result = true; } } } catch (IncorrectMultilineIndexException ex) { throw ex; } catch (Exception ex) { throw new UnprocessableMessageException(); } return result; } /** * @return true if there is a message waiting to be pulled */ public boolean isMessageAvailable() { return this.currentMessage != null; } /** * Pulls the message from the stack when there is one available. This needs * to be done before next line can be added into message processor. When * message is pulled, the message processor is reseted and ready to process * next line. * * @return Message * @throws NoMessageAvailableException * when there was no message on the stack */ public Message pull() throws NoMessageAvailableException { Message result = null; if (this.currentMessage == null) { throw new NoMessageAvailableException(); } else { result = this.currentMessage; reset(); } return result; } /** * Processes the raw TCP data read from the MAX protocol, returning the * corresponding MessageType. * * @param line * the raw data provided read from the MAX protocol * @return MessageType of the line added */ private static MessageType getMessageType(String line) { for (MessageType msgType : MessageType.values()) { if (line.startsWith(msgType.name() + SEPARATOR)) { return msgType; } } return null; } }