<commit_before>/**
 * Copyright (c) 2010-2015, openHAB.org and others.
 *
 * 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.tcp.protocol.internal;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Dictionary;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.tcp.AbstractSocketChannelBinding;
import org.openhab.binding.tcp.Direction;
import org.openhab.binding.tcp.internal.TCPActivator;
import org.openhab.binding.tcp.protocol.ProtocolBindingProvider;
import org.openhab.binding.tcp.protocol.TCPBindingProvider;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TCPBinding is most "simple" implementation of a TCP based ASCII protocol. It sends and received 
 * data as ASCII strings. Data sent out is padded with a CR/LF. This should be sufficient for a lot
 * of home automation devices that take simple ASCII based control commands, or that send back
 * text based status messages
 * 
 * 
 * @author <NAME>
 * @since 1.1.0
 *
 */
public class TCPBinding extends AbstractSocketChannelBinding<TCPBindingProvider> implements ManagedService {

	static private final Logger logger = LoggerFactory.getLogger(TCPBinding.class);

	/** RegEx to extract a parse a function String <code>'(.*?)\((.*)\)'</code> */
	private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)");

	// time to wait for a reply, in milliseconds
	private static int timeOut = 3000;
	// flag to use only blocking write/read operations
	private static boolean blocking = false;
	// string to prepend to data being sent
	private static String preAmble = "";
	// string to append to data being sent
	private static String postAmble = "";
	// flag to use the reply of the remote end to update the status of the Item receving the data
	private static boolean updateWithResponse = true;
	// used character set
	private static String charset = "ASCII";

	@Override
	protected boolean internalReceiveChanneledCommand(String itemName,
			Command command, Channel sChannel, String commandAsString) {

		ProtocolBindingProvider provider = findFirstMatchingBindingProvider(itemName);

		if(command != null ){		
			
			String transformedMessage = transformResponse(provider.getProtocolCommand(itemName, command),commandAsString);
			String tcpCommandName = preAmble + transformedMessage + postAmble ;

			ByteBuffer outputBuffer = null;
			try {
				outputBuffer = ByteBuffer.allocate(tcpCommandName.getBytes(charset).length);
				outputBuffer.put(tcpCommandName.getBytes(charset));
			} catch (UnsupportedEncodingException e) {
				logger.warn("Exception while attempting an unsupported encoding scheme");
			}

			// send the buffer in an asynchronous way
			ByteBuffer result = null;
			try {
				result = writeBuffer(outputBuffer,sChannel,blocking,timeOut);
			} catch (Exception e) {
				logger.error("An exception occurred while writing a buffer to a channel: {}",e.getMessage());
			}

			if(result!=null && blocking) {
				String resultString = "";
				try {
					resultString = new String(result.array(), charset).split("\0")[0];
				} catch (UnsupportedEncodingException e) {
					logger.warn("Exception while attempting an unsupported encoding scheme");
				}
				
				logger.info("Received {} from the remote end {}", resultString, sChannel.toString());
				String transformedResponse = transformResponse(provider.getProtocolCommand(itemName, command), resultString);

				// if the remote-end does not send a reply in response to the string we just sent, then the abstract superclass will update
				// the openhab status of the item for us. If it does reply, then an additional update is done via parseBuffer.
				// since this TCP binding does not know about the specific protocol, there might be two state updates (the command, and if
				// the case, the reply from the remote-end)

				if(updateWithResponse) {

					List<Class<? extends State>> stateTypeList = provider.getAcceptedDataTypes(itemName,command);
					State newState = createStateFromString(stateTypeList,transformedResponse);

					if(newState != null) {
						eventPublisher.postUpdate(itemName, newState);						        						
					} else {
						logger.warn("Can not parse transformed output "+transformedResponse+" to match command {} on item {}  ",command,itemName);
					}

					return false;
				} else {
					return true;
				}
			} else {
				return true;
			}
		}
		return false;
	}

	/**
	 * 
	 * Main function to parse ASCII string received 
	 * @return 
	 * 
	 */
	@Override
	protected void parseBuffer(String itemName, Command aCommand, Direction theDirection,ByteBuffer byteBuffer){

		String theUpdate = "";
		try {
			theUpdate = new String(byteBuffer.array(), charset).split("\0")[0];
		} catch (UnsupportedEncodingException e) {
			logger.warn("Exception while attempting an unsupported encoding scheme");
		}

		ProtocolBindingProvider provider = findFirstMatchingBindingProvider(itemName);

		List<Class<? extends State>> stateTypeList = provider.getAcceptedDataTypes(itemName,aCommand);
		
		String transformedResponse = transformResponse(provider.getProtocolCommand(itemName, aCommand),theUpdate);
		State newState = createStateFromString(stateTypeList,transformedResponse);

		if(newState != null) {
			eventPublisher.postUpdate(itemName, newState);							        						
		} else {
			logger.warn("Can not parse input "+theUpdate+" to match command {} on item {}  ",aCommand,itemName);
		}
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void updated(Dictionary config) throws ConfigurationException {

		super.updated(config);

		if (config != null) {

			String timeOutString = (String) config.get("timeout");
			if (StringUtils.isNotBlank(timeOutString)) {
				timeOut = Integer.parseInt((timeOutString));
			} else {
				logger.info("The maximum time out for blocking write operations will be set to the default vaulue of {}",timeOut);
			}

			String blockingString = (String) config.get("blocking");
			if (StringUtils.isNotBlank(blockingString)) {
				blocking = Boolean.parseBoolean((blockingString));
			} else {
				logger.info("The blocking nature of read/write operations will be set to the default vaulue of {}",blocking);
			}

			String preambleString = (String) config.get("preamble");
			if (StringUtils.isNotBlank(preambleString)) {
				try {
					preAmble = preambleString.replaceAll("\\\\", "\\");
				}
				catch(Exception e) {
					preAmble = preambleString;
				}
			} else {
				logger.info("The preamble for all write operations will be set to the default vaulue of {}",preAmble);
			}

			String postambleString = (String) config.get("postamble");
			if (StringUtils.isNotBlank(postambleString)) {
				try {
					postAmble = postambleString.replaceAll("\\\\", "\\");
				}
				catch(Exception e) {
					postAmble = postambleString;
				}
			} else {
				logger.info("The postamble for all write operations will be set to the default vaulue of {}",postAmble);
			}
			
			String updatewithresponseString = (String) config.get("updatewithresponse");
			if (StringUtils.isNotBlank(updatewithresponseString)) {
				updateWithResponse = Boolean.parseBoolean((updatewithresponseString));
			} else {
				logger.info("Updating states with returned values will be set to the default vaulue of {}",updateWithResponse);
			}

			String charsetString = (String) config.get("charset");
			if (StringUtils.isNotBlank(charsetString)) {
				charset = charsetString;
			} else {
				logger.info("The characterset will be set to the default vaulue of {}",charset);
			}

		}

	}<commit_msg>[TCP-Binding] correctly parse postamble and preamble strings<commit_after>/**
 * Copyright (c) 2010-2015, openHAB.org and others.
 *
 * 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.tcp.protocol.internal;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Dictionary;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.tcp.AbstractSocketChannelBinding;
import org.openhab.binding.tcp.Direction;
import org.openhab.binding.tcp.internal.TCPActivator;
import org.openhab.binding.tcp.protocol.ProtocolBindingProvider;
import org.openhab.binding.tcp.protocol.TCPBindingProvider;
import org.openhab.core.transform.TransformationException;
import org.openhab.core.transform.TransformationHelper;
import org.openhab.core.transform.TransformationService;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TCPBinding is most "simple" implementation of a TCP based ASCII protocol. It sends and received 
 * data as ASCII strings. Data sent out is padded with a CR/LF. This should be sufficient for a lot
 * of home automation devices that take simple ASCII based control commands, or that send back
 * text based status messages
 * 
 * 
 * @author <NAME>
 * @since 1.1.0
 *
 */
public class TCPBinding extends AbstractSocketChannelBinding<TCPBindingProvider> implements ManagedService {

	static private final Logger logger = LoggerFactory.getLogger(TCPBinding.class);

	/** RegEx to extract a parse a function String <code>'(.*?)\((.*)\)'</code> */
	private static final Pattern EXTRACT_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\)");

	// time to wait for a reply, in milliseconds
	private static int timeOut = 3000;
	// flag to use only blocking write/read operations
	private static boolean blocking = false;
	// string to prepend to data being sent
	private static String preAmble = "";
	// string to append to data being sent
	private static String postAmble = "";
	// flag to use the reply of the remote end to update the status of the Item receving the data
	private static boolean updateWithResponse = true;
	// used character set
	private static String charset = "ASCII";

	@Override
	protected boolean internalReceiveChanneledCommand(String itemName,
			Command command, Channel sChannel, String commandAsString) {

		ProtocolBindingProvider provider = findFirstMatchingBindingProvider(itemName);

		if(command != null ){		
			
			String transformedMessage = transformResponse(provider.getProtocolCommand(itemName, command),commandAsString);
			String tcpCommandName = preAmble + transformedMessage + postAmble ;

			ByteBuffer outputBuffer = null;
			try {
				outputBuffer = ByteBuffer.allocate(tcpCommandName.getBytes(charset).length);
				outputBuffer.put(tcpCommandName.getBytes(charset));
			} catch (UnsupportedEncodingException e) {
				logger.warn("Exception while attempting an unsupported encoding scheme");
			}

			// send the buffer in an asynchronous way
			ByteBuffer result = null;
			try {
				result = writeBuffer(outputBuffer,sChannel,blocking,timeOut);
			} catch (Exception e) {
				logger.error("An exception occurred while writing a buffer to a channel: {}",e.getMessage());
			}

			if(result!=null && blocking) {
				String resultString = "";
				try {
					resultString = new String(result.array(), charset).split("\0")[0];
				} catch (UnsupportedEncodingException e) {
					logger.warn("Exception while attempting an unsupported encoding scheme");
				}
				
				logger.info("Received {} from the remote end {}", resultString, sChannel.toString());
				String transformedResponse = transformResponse(provider.getProtocolCommand(itemName, command), resultString);

				// if the remote-end does not send a reply in response to the string we just sent, then the abstract superclass will update
				// the openhab status of the item for us. If it does reply, then an additional update is done via parseBuffer.
				// since this TCP binding does not know about the specific protocol, there might be two state updates (the command, and if
				// the case, the reply from the remote-end)

				if(updateWithResponse) {

					List<Class<? extends State>> stateTypeList = provider.getAcceptedDataTypes(itemName,command);
					State newState = createStateFromString(stateTypeList,transformedResponse);

					if(newState != null) {
						eventPublisher.postUpdate(itemName, newState);						        						
					} else {
						logger.warn("Can not parse transformed output "+transformedResponse+" to match command {} on item {}  ",command,itemName);
					}

					return false;
				} else {
					return true;
				}
			} else {
				return true;
			}
		}
		return false;
	}

	/**
	 * 
	 * Main function to parse ASCII string received 
	 * @return 
	 * 
	 */
	@Override
	protected void parseBuffer(String itemName, Command aCommand, Direction theDirection,ByteBuffer byteBuffer){

		String theUpdate = "";
		try {
			theUpdate = new String(byteBuffer.array(), charset).split("\0")[0];
		} catch (UnsupportedEncodingException e) {
			logger.warn("Exception while attempting an unsupported encoding scheme");
		}

		ProtocolBindingProvider provider = findFirstMatchingBindingProvider(itemName);

		List<Class<? extends State>> stateTypeList = provider.getAcceptedDataTypes(itemName,aCommand);
		
		String transformedResponse = transformResponse(provider.getProtocolCommand(itemName, aCommand),theUpdate);
		State newState = createStateFromString(stateTypeList,transformedResponse);

		if(newState != null) {
			eventPublisher.postUpdate(itemName, newState);							        						
		} else {
			logger.warn("Can not parse input "+theUpdate+" to match command {} on item {}  ",aCommand,itemName);
		}
	}

	@SuppressWarnings("rawtypes")
	@Override
	public void updated(Dictionary config) throws ConfigurationException {

		super.updated(config);

		if (config != null) {

			String timeOutString = (String) config.get("timeout");
			if (StringUtils.isNotBlank(timeOutString)) {
				timeOut = Integer.parseInt((timeOutString));
			} else {
				logger.info("The maximum time out for blocking write operations will be set to the default vaulue of {}",timeOut);
			}

			String blockingString = (String) config.get("blocking");
			if (StringUtils.isNotBlank(blockingString)) {
				blocking = Boolean.parseBoolean((blockingString));
			} else {
				logger.info("The blocking nature of read/write operations will be set to the default vaulue of {}",blocking);
			}

			String preambleString = (String) config.get("preamble");
			if (StringUtils.isNotBlank(preambleString)) {
				preAmble = StringEscapeUtils.unescapeJava(preambleString);
			} else {
				logger.info("The preamble for all write operations will be set to the default vaulue of \"{}\"",preAmble);
			}

			String postambleString = (String) config.get("postamble");
			if (StringUtils.isNotBlank(postambleString)) {
				postAmble = StringEscapeUtils.unescapeJava(postambleString);
			} else {
				logger.info("The postamble for all write operations will be set to the default vaulue of \"{}\"",postAmble);
			}

			String updatewithresponseString = (String) config.get("updatewithresponse");
			if (StringUtils.isNotBlank(updatewithresponseString)) {
				updateWithResponse = Boolean.parseBoolean((updatewithresponseString));
			} else {
				logger.info("Updating states with returned values will be set to the default vaulue of {}",updateWithResponse);
			}

			String charsetString = (String) config.get("charset");
			if (StringUtils.isNotBlank(charsetString)) {
				charset = charsetString;
			} else {
				logger.info("The characterset will be set to the default vaulue of {}",charset);
			}

		}

	}
{'fraction_non_alphanumeric': 0.10098057128935989, 'fraction_numerical': 0.0030452524514282234, 'mean_word_length': 4.225970719287078, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5018064', 'n_tokens_mistral': 5190, 'n_tokens_neox': 4408, 'n_words': 1358}