/**
 * 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.io.transport.cul;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.openhab.io.transport.cul.internal.CULHandlerInternal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * This class handles all CULHandler. You can only obtain CULHandlers via this
 * manager.
 * 
 * @author Till Klocke
 * @since 1.4.0
 */
public class CULManager {

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

	private static Map<String, CULHandler> openDevices = new HashMap<String, CULHandler>();

	private static Map<String, Class<? extends CULHandler>> deviceTypeClasses = new HashMap<String, Class<? extends CULHandler>>();
	

	/**
	 * Get CULHandler for the given device in the given mode. The same
	 * CULHandler can be returned multiple times if you ask multiple times for
	 * the same device in the same mode. It is not possible to obtain a
	 * CULHandler of an already openend device for another RF mode.
	 * 
	 * @param deviceName
	 *            String representing the device. Currently only serial ports
	 *            are supported.
	 * @param mode
	 *            The RF mode for which the device will be configured.
	 * @return A CULHandler to communicate with the culfw based device.
	 * @throws CULDeviceException
	 */
	public static CULHandler getOpenCULHandler(String deviceName, CULMode mode) throws CULDeviceException {
		return getOpenCULHandler(deviceName, mode, null);
	}
	
	/**
	 * Get CULHandler for the given device in the given mode. The same
	 * CULHandler can be returned multiple times if you ask multiple times for
	 * the same device in the same mode. It is not possible to obtain a
	 * CULHandler of an already openend device for another RF mode.
	 * 
	 * @param deviceName
	 *            String representing the device. Currently only serial ports
	 *            are supported.
	 * @param mode
	 *            The RF mode for which the device will be configured.
	 * @param properties
	 *            Map of properties, do configure a specific CUL Handler Implementation. 
	 *            For details please reference the Handler it self.
	 * @return A CULHandler to communicate with the culfw based device.
	 * @throws CULDeviceException
	 */
	public static CULHandler getOpenCULHandler(String deviceName, CULMode mode, Map<String, ?> properties) throws CULDeviceException {
		logger.debug("Trying to open device " + deviceName + " in mode " + mode.toString());
		synchronized (openDevices) {

			if (openDevices.containsKey(deviceName)) {
				CULHandler handler = openDevices.get(deviceName);
				if ((handler.getCULMode() == mode)  && (handler.arePropertiesEqual(properties))){
					logger.debug("Device " + deviceName + " is already open in mode " + mode.toString()
							+ ", returning already openend handler");
					return handler;
				} else {
					throw new CULDeviceException("The device " + deviceName + " is already open in mode "
							+ mode.toString());
				}
			}
			CULHandler handler = createNewHandler(deviceName, mode, properties);
			openDevices.put(deviceName, handler);
			return handler;
		}
	}

	private static String getPrefix(String deviceName) {
		int index = deviceName.indexOf(':');
		return deviceName.substring(0, index);
	}

	private static String getRawDeviceName(String deviceName) {
		int index = deviceName.indexOf(':');
		return deviceName.substring(index + 1);
	}

	/**
	 * Return a CULHandler to the manager. The CULHandler will only be closed if
	 * there aren't any listeners registered with it. So it is save to call this
	 * methods as soon as you don't need the CULHandler any more.
	 * 
	 * @param handler
	 */
	public static void close(CULHandler handler) {
		synchronized (openDevices) {

			if (handler instanceof CULHandlerInternal) {
				CULHandlerInternal internalHandler = (CULHandlerInternal) handler;
				if (!internalHandler.hasListeners()) {
					openDevices.remove(handler);
					try {
						handler.send("X00");
					} catch (CULCommunicationException e) {
						logger.warn("Couldn't reset rf mode to X00");
					}
					internalHandler.close();
				} else {
					logger.warn("Can't close device because it still has listeners");
				}
			}
		}
	}

	public static void registerHandlerClass(String deviceType, Class<? extends CULHandler> clazz) {
		logger.debug("Registering class " + clazz.getCanonicalName() + " for device type " + deviceType);
		deviceTypeClasses.put(deviceType, clazz);
	}

	private static CULHandler createNewHandler(String deviceName, CULMode mode, Map<String, ?> properties) throws CULDeviceException {
		String deviceType = getPrefix(deviceName);
		String deviceAddress = getRawDeviceName(deviceName);
		logger.debug("Searching class for device type " + deviceType);
		Class<? extends CULHandler> culHandlerclass = deviceTypeClasses.get(deviceType);
		if (culHandlerclass == null) {
			throw new CULDeviceException("No class for the device type " + deviceType + " is registred");
		}
		
		Class<?>[] constructorParametersTypes = { String.class, CULMode.class };
		Object[] parameters = { deviceAddress, mode };
		
		if(properties != null){
			constructorParametersTypes = Arrays.copyOf(constructorParametersTypes, constructorParametersTypes.length + 1);
			constructorParametersTypes[constructorParametersTypes.length -1] = Map.class;
			
			parameters = Arrays.copyOf(parameters, parameters.length + 1);
			parameters[parameters.length -1] = properties;
		}
		
		try {
			Constructor<? extends CULHandler> culHanlderConstructor = culHandlerclass
					.getConstructor(constructorParametersTypes);
			
			CULHandler culHandler = culHanlderConstructor.newInstance(parameters);
			List<String> initCommands = mode.getCommands();
			if (!(culHandler instanceof CULHandlerInternal)) {
				logger.error("Class " + culHandlerclass.getCanonicalName()
						+ " does not implement the internal interface");
				throw new CULDeviceException("This CULHandler class does not implement the internal interface: "
						+ culHandlerclass.getCanonicalName());
			}
			CULHandlerInternal internalHandler = (CULHandlerInternal) culHandler;
			internalHandler.open();
			for (String command : initCommands) {
				internalHandler.sendWithoutCheck(command);
			}
			return culHandler;
		} catch (SecurityException e1) {
			throw new CULDeviceException("Not allowed to access the constructor ", e1);
		} catch (NoSuchMethodException e1) {
			throw new CULDeviceException("Can't find the constructor to build the CULHandler", e1);
		} catch (IllegalArgumentException e) {
			throw new CULDeviceException("Invalid arguments for constructor. Device name: " + deviceAddress
					+ " CULMode " + mode, e);
		} catch (InstantiationException e) {
			throw new CULDeviceException("Can't instantiate CULHandler object", e);
		} catch (IllegalAccessException e) {
			throw new CULDeviceException("Can't instantiate CULHandler object", e);
		} catch (InvocationTargetException e) {
			throw new CULDeviceException("Can't instantiate CULHandler object", e);
		} catch (CULCommunicationException e) {
			throw new CULDeviceException("Can't initialise RF mode", e);
		}
	}
}

@@ -1880,19 +1880,44 @@
  mode, n
-ull
+ew HashMap%3CString, Object%3E()
 );%0A%09%7D%0A%09%0A

Fix for issue #2671: Added empty property list to getCulHandler method