/*******************************************************************************
 * Copyright (c) 2003, 2012 IBM Corporation 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.osgi.internal.serviceregistry;

import java.lang.reflect.Array;
import java.util.*;
import org.eclipse.osgi.framework.util.Headers;
import org.osgi.framework.Constants;

/**
 * Hashtable for service properties.
 * 
 * Supports case-insensitive key lookup.
 */
class ServiceProperties extends Headers<String, Object> {
	/**
	 * Create a properties object for the service.
	 *
	 * @param props The properties for this service.
	 */
	private ServiceProperties(int size, Dictionary<String, ?> props) {
		super(size);

		if (props == null) {
			return;
		}
		synchronized (props) {
			Enumeration<?> keysEnum = props.keys();

			while (keysEnum.hasMoreElements()) {
				Object key = keysEnum.nextElement();

				if (key instanceof String) {
					String header = (String) key;

					setProperty(header, props.get(header));
				}
			}
		}
	}

	/**
	 * Create a properties object for the service.
	 *
	 * @param props The properties for this service.
	 */
	ServiceProperties(Dictionary<String, ?> props) {
		this((props == null) ? 2 : props.size() + 2, props);
	}

	/**
	 * Get a clone of the value of a service's property.
	 *
	 * @param key header name.
	 * @return Clone of the value of the property or <code>null</code> if there is
	 * no property by that name.
	 */
	Object getProperty(String key) {
		return cloneValue(get(key));
	}

	/**
	 * Get the list of key names for the service's properties.
	 *
	 * @return The list of property key names.
	 */
	synchronized String[] getPropertyKeys() {
		int size = size();

		String[] keynames = new String[size];

		Enumeration<String> keysEnum = keys();

		for (int i = 0; i < size; i++) {
			keynames[i] = keysEnum.nextElement();
		}

		return keynames;
	}

	/**
	 * Put a clone of the property value into this property object.
	 *
	 * @param key Name of property.
	 * @param value Value of property.
	 * @return previous property value.
	 */
	synchronized Object setProperty(String key, Object value) {
		return set(key, cloneValue(value));
	}

	/**
	 * Attempt to clone the value if necessary and possible.
	 *
	 * For some strange reason, you can test to see of an Object is
	 * Cloneable but you can't call the clone method since it is
	 * protected on Object!
	 *
	 * @param value object to be cloned.
	 * @return cloned object or original object if we didn't clone it.
	 */
	private static Object cloneValue(Object value) {
		if (value == null)
			return null;
		if (value instanceof String) /* shortcut String */
			return value;
		if (value instanceof Number) /* shortcut Number */
			return value;
		if (value instanceof Character) /* shortcut Character */
			return value;
		if (value instanceof Boolean) /* shortcut Boolean */
			return value;

		Class<?> clazz = value.getClass();
		if (clazz.isArray()) {
			// Do an array copy
			Class<?> type = clazz.getComponentType();
			int len = Array.getLength(value);
			Object clonedArray = Array.newInstance(type, len);
			System.arraycopy(value, 0, clonedArray, 0, len);
			return clonedArray;
		}
		// must use reflection because Object clone method is protected!!
		try {
			return clazz.getMethod("clone", (Class<?>[]) null).invoke(value, (Object[]) null); //$NON-NLS-1$
		} catch (Exception e) {
			/* clone is not a public method on value's class */
		} catch (Error e) {
			/* JCL does not support reflection; try some well known types */
			if (value instanceof Vector<?>)
				return ((Vector<?>) value).clone();
			if (value instanceof Hashtable<?, ?>)
				return ((Hashtable<?, ?>) value).clone();
		}
		return value;
	}

	public synchronized String toString() {
		String keys[] = getPropertyKeys();

		int size = keys.length;

		StringBuffer sb = new StringBuffer(20 * size);

		sb.append('{');

		int n = 0;
		for (int i = 0; i < size; i++) {
			String key = keys[i];
			if (!key.equals(Constants.OBJECTCLASS)) {
				if (n > 0)
					sb.append(", "); //$NON-NLS-1$

				sb.append(key);
				sb.append('=');
				Object value = get(key);
				if (value.getClass().isArray()) {
					sb.append('[');
					int length = Array.getLength(value);
					for (int j = 0; j < length; j++) {
						if (j > 0)
							sb.append(',');
						sb.append(Array.get(value, j));
					}
					sb.append(']');
				} else {
					sb.append(value);
				}
				n++;
			}
		}

		sb.append('}');

		return sb.toString();
	}
}