/**
 * ***************************************************************
 * JADE - Java Agent DEvelopment Framework is a framework to develop
 * multi-agent systems in compliance with the FIPA specifications.
 * Copyright (C) 2000 CSELT S.p.A.
 * GNU Lesser General Public License
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */
package jade.core;

import java.util.Enumeration;
import java.util.Vector;

/**
 * This class represent a specifier and collects a name, a className,
 * and an array of arguments. Profile specifiers are used to describe
 * several kinds of complex information when configuring JADE
 * (e.g. MTPs to install, agents to start up, kernel services to
 * activate).
 *
 * The general string format for a specifier is
 * <p><code>name<b>:</b>className<b>(</b><i>separated arglist</i><b>)</b></code></p>
 * The separated argument list uses a separator character that can be configured when
 * invoking parsing-related utility methods. 
 *
 * @author LEAP
 */
public class Specifier {
	public static final char SPECIFIER_SEPARATOR = ';';
	public static final String NULL_SPECIFIER_LIST = "null";

	private String   name = null;
	private String   className = null;
	private Object[] args = null;

	/**
       Set the name for this specifier object.
       @param n The name to give to this specifier.
	 */
	public void setName(String n) {
		name = n;
	} 

	/**
       Retrieve the name for this specifier object.
       @return The name of the specifier, if one was set, or
       <code>null</code> otherwise.
	 */
	public String getName() {
		return name;
	} 

	/**
       Set the name of the class of this specifier.
       @param cn The class name to assign to the specifier object.
	 */
	public void setClassName(String cn) {
		className = cn;
	} 

	/**
       Retrieve the class name of this specifier.
       @return The class name of the specifier, if one was set, or
       <code>null</code> otherwise.
	 */
	public String getClassName() {
		return className;
	} 

	/**
       Set the argument list for this specifier object.
       @param a An object array containing the argument list for this
       specifier.
	 */
	public void setArgs(Object[] a) {
		args = a;
	} 

	/**
       Retrieve the argument list for this specifier.
       @return An object array containing the argument list, if one
       was set, or <code>null</code> otherwise.
	 */
	public Object[] getArgs() {
		return args;
	} 

	/**
	 * This method is used by Boot, ProfileImpl, and RMA in order
	 * to have a String representation of this Specifier according to the
	 * format <code>name:className(arg1 arg2 argn)</code>
	 *
	 * @return A string representation of this specifier, according to
	 * the format above.
	 **/
	public String toString() {
		// TAKE CARE: do not change this method otherwise Boot might fail
		StringBuffer tmp = new StringBuffer();
		if (name != null) {
			tmp.append(name);
			tmp.append(":");
		}
		if (className != null) {
			tmp.append(className);
		}
		if (args != null) {
			tmp.append("(");
			for (int i=0; i<args.length; i++) {
				tmp.append(args[i]);
				if (i<args.length-1) {
					//#ALL_EXCLUDE_BEGIN
					tmp.append(" ");
					//#ALL_EXCLUDE_END
					/*#ALL_INCLUDE_BEGIN
					tmp.append(",");
					//#ALL_INCLUDE_END*/
				}
			}
			tmp.append(")");
		}
		return tmp.toString();
	}

	/**
     This static utility method can parse the string representation of
     a list of specifiers. The general format of a specifier is used,
     with a comma as argument separator, i.e.:
     <p><code>name<b>:</b>className<b>(</b><i>comma-separated arglist</i><b>)</b></code></p>
     While comma is the separator character within a specifier
     arguments, the semicolon is used to separate the different
     specifiers in the list.

     @param specsLine The string containing the representation of the
     specifier list, according to the format above.
     @return A vector containing the parsed specifiers.
	 */
	public static Vector parseSpecifierList(String specsLine) throws Exception {
		Vector specs = parseList(specsLine, SPECIFIER_SEPARATOR);
		for (int i = 0; i < specs.size(); ++i) {
			String s = (String) specs.elementAt(i);
			if (s.length() > 0) {
				specs.setElementAt(parseSpecifier(s, ','), i);
			}
			else {
				specs.removeElementAt(i--);
			}
		}
		return specs;
	} 

	/**
	 * This static utility method produces a string representation of a list of Specifier objects.
	 */
	public static String encodeSpecifierList(Vector v){
		return encodeList(v, SPECIFIER_SEPARATOR);
	}

	public static final Vector parseList(String list, char delimiter) {
		Vector v = new Vector();

		if (list != null && !list.equals("") && !list.equals(NULL_SPECIFIER_LIST)) {
			// Copy the string with the specifiers into an array of char
			char[] specsChars = new char[list.length()];

			list.getChars(0, list.length(), specsChars, 0);

			// Create the StringBuffer to hold the first element
			StringBuffer sbElement = new StringBuffer();
			int          i = 0;

			while (i < specsChars.length) {
				char c = specsChars[i];

				if (c != delimiter) {
					sbElement.append(c);
				} 
				else {
					// The element is terminated.
					v.addElement(sbElement.toString().trim());
					// Create the StringBuffer to hold the next element
					sbElement = new StringBuffer();
				} 
				++i;
			} 

			// Append the last element
			v.addElement(sbElement.toString().trim());
		}
		return v;
	}

	public static String encodeList(Vector v, char delimiter){
		StringBuffer sb = new StringBuffer();
		Enumeration elements = v.elements();
		while(elements.hasMoreElements()){
			sb.append(elements.nextElement());
			if(elements.hasMoreElements()){
				sb.append(delimiter);
			}
		}
		return sb.toString();
	}

	/**
	 * Utility method that parses a stringified object specifier in the form
	 * <p><code>name<b>:</b>className<b>(</b><i>separated arglist</i><b>)</b></code></p>
	 * a Specifier object.
	 * Both the name and the list of arguments are optional.
	 * 
	 * @param specString A string containing the representation of the
	 * specifier, according to the format above.
	 * @param argsDelimiter The character to use as a delimiter within the argument list.
	 * @return A specifier object, built according to the parsed information.
	 */
	public static Specifier parseSpecifier(String specString, char argsDelimiter) throws Exception {
		Specifier s = new Specifier();

		// NAME
		int       index1 = specString.indexOf(':');
		int       index2 = specString.indexOf('(');

		if (index2 < 0) {
			index2 = 99999;
		} 

		if (index1 > 0 && index1 < index2) {

			// The name exists, colon exists, and is followed by the class name
			s.setName(specString.substring(0, index1));

			// Skip colon
			index1++;
		} 
		else {

			// No name specified
			index1 = 0;
		} 

		// CLASS
		index2 = specString.indexOf('(', index1);

		if (index2 < 0) {

			// No arguments --> just add the class name
			s.setClassName(specString.substring(index1));
		} 
		else {

			// There are arguments --> add the class name and then parse the args
			s.setClassName(specString.substring(index1, index2));

			// ARGUMENTS
			if (!specString.endsWith(")")) {
				throw new Exception("Incorrect specifier \""+specString+"\". Missing final parenthesis");
			} 

			// Get everything is in between '(' and ')'
			String args = specString.substring(index2+1, specString.length()-1);

			s.setArgs(parseArguments(args, argsDelimiter));
		} 

		return s;
	} 

	/**
	 */
	private static String[] parseArguments(String args, char argsDelimiter) {
		Vector argList = new Vector();
		int  argStart = 0;
		int  argEnd = args.indexOf(argsDelimiter);

		while (argEnd >= 0) {
			String arg = args.substring(argStart, argEnd);

			argList.addElement(arg.trim());

			argStart = argEnd+1;
			argEnd = args.indexOf(argsDelimiter, argStart);
		} 

		// Last argument
		String arg = args.substring(argStart, args.length());

		argList.addElement(arg.trim());

		// Convert the List into an Array
		String arguments[] = new String[argList.size()];
		int    i = 0;

		for (Enumeration e = argList.elements(); e.hasMoreElements(); arguments[i++] = (String) e.nextElement());

		return arguments;
	} 


}

