/*
 * Copyright (c) 2003, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.knopflerfish.bundle.cm.commands.impl;

import org.knopflerfish.service.console.*;
import org.knopflerfish.shared.cm.*;
import org.knopflerfish.util.cm.*;
import org.knopflerfish.util.sort.Sort;

import org.osgi.service.cm.*;
import org.osgi.framework.*;

import org.xml.sax.SAXException;

import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.math.BigInteger;
import java.net.*;
import java.security.*;
import java.util.*;

//  ********************     CMCommands    ********************
/**
 ** Console interface to the CM.
 **
 ** @author Per Gustafson
 ** @version $Id: CMCommands.java,v 1.1.1.1 2004/03/05 20:34:54 wistrand Exp $
 */

public class CMCommands extends CommandGroupAdapter implements ServiceListener {
  /** Key in the session properties dictionary used to store the
   ** current (open) configuration.**/
  private static final String CURRENT
    = "org.knopflerfish.bundle.cm.commands.impl.current";
  /** Key in the session properties dictionary used to store the
   ** dictionary that is edited for the current configuration.**/
  private static final String EDITED
    = "org.knopflerfish.bundle.cm.commands.impl.edited";
  /** Key in the session properties dictionary used to store the
   ** result of the latest list command for later reference using
   ** -i  options to several commands. **/
  private static final String LISTED_CONFIGS
    = "org.knopflerfish.bundle.cm.commands.impl.listed.configs";
  
  private BundleContext bc;
  private ServiceReference refCA = null;
  
  private static Class classBigDecimal;
  private static Constructor consBigDecimal;

  static {
    try {
      classBigDecimal = Class.forName("java.math.BigDecimal");
      consBigDecimal = classBigDecimal.getConstructor(new Class [] { String.class });
    } catch (Exception ignore) {
      classBigDecimal = null;
      consBigDecimal = null;
    }
  }

  public CMCommands(BundleContext bc) {
    super("configuration", "Configuration commands");
    this.bc = bc;
    refCA = bc.getServiceReference(ConfigurationAdmin.class.getName());
    try {
      bc.addServiceListener
        (this, "(objectClass=" + ConfigurationAdmin.class.getName() + ")");
    } catch(InvalidSyntaxException ignored) {}
  }


  public final static String USAGE_LIST = "[<selection>] ...";
  public final static String [] HELP_LIST = new String [] {
    "List the pids of existing configurations.",
    "<selection>  A pid that can contain wildcards '*',",
    "             or an ldap filter, or an index in output",
    "             from the latest use of this command.",
    "             If no selection is given all existing pids",
    "             will be listed."
    };

  public int cmdList(Dictionary opts, Reader in, PrintWriter out, Session session) {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    ConfigurationAdmin srvCA = null;
    try {    
      srvCA = getCA();

      String[] selection = (String[])opts.get("selection");

      Configuration[] cs = null;
      if(selection == null) {
        cs = srvCA.listConfigurations(null);
      } else {
        cs = getConfigurations(session, srvCA, selection);
      }
      if(cs == null || cs.length == 0) {
        out.println("No configurations available");
      } else {
        sortConfigurationArray(cs);
        setSessionProperty(session, LISTED_CONFIGS, cs);
        out.println("Available configurations:");
        for(int i = 0; i < cs.length; ++i) {
          out.println("[" + i + "] " + cs[i].getPid());
        }
      }

      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("List failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }

  public final static String USAGE_SHOW = "[<selection>] ...";
  public final static String [] HELP_SHOW = new String [] {
    "Show the saved versions of configurations.",
    "<selection>  A pid that can contain wildcards '*',",
    "             or an ldap filter, or an index in output",
    "             from the latest use of the 'list' command.",
    "             If no selection is given all configurations",
    "             will be shown.",
    "             Use 'current' command to see the properties",
    "             of the currently edited configuration."
    };

  public int cmdShow(Dictionary opts, Reader in, PrintWriter out,
                     Session session)
  {
    ConfigurationAdmin srvCA = null;

    try {
      srvCA = getCA();
      String[] selection = (String[])opts.get("selection");
      Configuration[] cs = getConfigurations(session, srvCA, selection);
      if(cs == null || cs.length == 0) {
        throw new Exception("No matching configurations for selection.");
      }
      for(int i = 0; i < cs.length; ++i) {
        if(i > 0) {
          out.println();
        }
        Dictionary d = cs[i].getProperties();
        if(d == null) {
          out.println("No properties set in " + cs[i].getPid());
        } else {
          out.println("Properties for " + cs[i].getPid());
          printDictionary( out, d );
        }
      }
    } catch(Exception e) {
      out.println("Show failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return 0;
  }

  public final static String USAGE_CREATE = "[-f] <pid>";
  public final static String [] HELP_CREATE = new String [] {
    "Create a configuration and open it for editing.",
    "-f     If specified the pid argument is a factory pid.",
    "<pid>  Pid or factory pid of configuration to create",
    "       depending on if -f flag is specified."
    };

  public int cmdCreate(Dictionary opts, Reader in, PrintWriter out, Session session) {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    setCurrent(session,null);
    setEditingDict(session,null);
    ConfigurationAdmin srvCA = null;
    try {
      srvCA = getCA();
      String pid = (String)opts.get("pid");
      boolean createFactoryConfiguration = opts.get("-f") != null;
      Configuration cfg = null;
      if(createFactoryConfiguration) {
        cfg = srvCA.createFactoryConfiguration(pid, null);
      } else {
        cfg = srvCA.getConfiguration(pid, null);
      }
      
      if(cfg == null) {
        throw new Exception("Failed creating configuration for " + pid);
      }
      setCurrent(session,cfg);
      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Create failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }

  public final static String USAGE_DELETE = "<selection>";
  public final static String [] HELP_DELETE = new String [] {
    "Delete an existing configuration.",
    "<selection>  A pid that can contain wildcards '*',",
    "             or an ldap filter, or an index in output",
    "             from the latest use of the 'list' command.",
    "             If the selection doesn't match exactly one",
    "             configuration it will have to be refined."
    };

  public int cmdDelete(Dictionary opts, Reader in, PrintWriter out, Session session) {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    ConfigurationAdmin srvCA = null;
    try {
      String selection = (String)opts.get("selection");

      srvCA = getCA();

      Configuration[] cs = getConfigurations(session, srvCA, selection);
      if(cs == null || cs.length == 0) {
        throw new Exception("Selection didn't match any configurations. " +
                            "Change your selection to match exactly " +
                            "one configuration.");
      } else if(cs.length == 1) {
        out.println("Deleting " + cs[0].getPid());
        Configuration current = getCurrent(session);
        if(current != null && current.getPid().equals(cs[0].getPid())) {
          setCurrent(session,null);
          setEditingDict(session,null);
        }
        cs[0].delete();
      } else {
        throw new Exception("Selection matched " + cs.length + " configurations. " + 
                            "Refine your selection to match exactly one configuration.");
      }
      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Delete failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }


  public final static String USAGE_EDIT = "<selection>";
  public final static String [] HELP_EDIT = new String [] {
    "Edit an existing configuration.",
    "<selection>  A pid that can contain wildcards '*',",
    "             or an ldap filter, or an index in output",
    "             from the latest use of the 'list' command.",
    "             If the selection doesn't match exactly one",
    "             configuration it will have to be refined."
    };

  public int cmdEdit(Dictionary opts, Reader in, PrintWriter out,
                     Session session)
  {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    setEditingDict(session,null);
    setCurrent(session,null);
    
    ConfigurationAdmin srvCA = null;
    try {
      String selection = (String)opts.get("selection");

      srvCA = getCA();

      Configuration[] cs = getConfigurations(session, srvCA, selection);
      if(cs == null || cs.length == 0) {
        throw new Exception("Selection didn't match any configurations. " +
                            "Use 'create' to create the configuration you want to edit " +
                            "if it doesnt exist, or change your selection to match " +
                            "exactly one configuration.");
      } else if(cs.length == 1) {
        out.println("Editing " + cs[0].getPid());
        setCurrent(session, cs[0]);
      } else {
        throw new Exception("Selection matched " + cs.length + " configurations. " + 
                            "Refine your selection to match exactly one configuration.");
      }

      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Edit failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }

  public final static String USAGE_CURRENT = "";
  public final static String [] HELP_CURRENT = new String [] {
    "Show the currently open configuration."
    };

  public int cmdCurrent(Dictionary opts, Reader in, PrintWriter out,
                        Session session)
  {
    Configuration cfg = getCurrent(session);
    if(cfg == null) {
      out.println("No configuration open currently");
    } else {
      if(isEditing(session)) {
        printDictionary( out, getEditingDict(session) );
      } else {
        Dictionary d = cfg.getProperties();
        if(d == null) {
          out.println("No properties set in current configuration");
        } else {
          printDictionary( out, d );
        }
      }
    }
    return 0;
  }

  public final static String USAGE_SAVE = "[-force]";
  public final static String [] HELP_SAVE = new String [] {
    "Save the currently open configuration in the CM.",
    "-force   Force the save"
    };

  public int cmdSave(Dictionary opts, Reader in, PrintWriter out,
                     Session session)
  {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    boolean forceOptionNotSpecified = opts.get("-force") == null;
    ConfigurationAdmin srvCA = null;
    try {
      Configuration cfg = getCurrent(session);
      if(cfg == null) {
        throw new Exception("No configuration open currently");
      }
      srvCA = getCA();

      if(forceOptionNotSpecified && configurationHasChanged(srvCA, cfg)) {
        throw new Exception("The configuration has changed in CM since it was opened." +
                        "Use -force option if you want to force saving of your changes.");
      }

      if(isEditing(session)) {
        cfg.update( getEditingDict(session) );
        setEditingDict(session,null);
      } else {
        throw new Exception("No changes to save");        
      }
      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Save failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }

  public final static String USAGE_SET = "<property> <value> [<type>]";
  public final static String [] HELP_SET = new String [] {
    "Set a property in the currently open configuration.",
    "<property> Name of property to set in configuration",
    "<value>    New value of property",
    "<type>     Type of value",
    "Allowed types:",
    "  String|Integer|Long|Float|Double|Byte|Short|",
    "  Character|Boolean|BigInteger|BigDecimal"
    };

  public int cmdSet(Dictionary opts, Reader in, PrintWriter out,
                    Session session)
  {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    try {
      if (getCurrent(session)==null)
        throw new Exception("No configuration open currently");
      
      String p = (String)opts.get("property");
      String v = (String)opts.get("value");
      String t = (String)opts.get("type");
      Dictionary dict = getEditingDict(session);
      Object ov = dict.get(p);
      
      if(t == null) {
        if(ov == null) {
          dict.put(p, v);
        } else {
          Class ovc = ov.getClass();
          Object nv = stringToObjectOfClass(v, ovc);
          if(nv == null) {
            throw new Exception("Unable to convert argument to the same type as old value of property");
          } else {
            dict.put(p, nv);
          }
        }
      } else {
        Object o = null;
        try {
          o = createValue(t, v);
        } catch(Exception e) {
          o = null;
        }
        if(o == null) {
          throw new Exception("Unable to convert " + v + " to " + t);
        } else {
          dict.put(p, o);
        }          
      }
      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Set failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } finally {
      // bluerg
    }
    
    return retcode;
  }

  public final static String USAGE_UNSET = "<property>";
  public final static String [] HELP_UNSET = new String [] {
    "Remove a property from the currently open configuration.",
    "<property> Name of property to remove from the configuration."
  };

  public int cmdUnset(Dictionary opts, Reader in, PrintWriter out,
                    Session session)
  {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    try {
      if (getCurrent(session)==null) {
        throw new Exception("No configuration open currently");
      }
      String p = (String)opts.get("property");
      Dictionary dict = getEditingDict(session);
      Object o = dict.remove(p);
      if(o == null) {
        throw new Exception("No property named " + p + " in current configuration.");
      }
      retcode = 0; // Success!
    } catch(Exception e) {
      out.println("Unset failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } finally {

    }

    return retcode;
  }

  public final static String USAGE_IMPORT = "<url>";
  public final static String [] HELP_IMPORT = new String [] {
    "Import configuration data from xml file at url.",
    "<url>   URL to an xml file containing configuration data"
    };

  public int cmdImport(Dictionary opts, Reader in, final PrintWriter out, Session session) {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    try {
      final String spec = (String)opts.get("url");
      final URL url = new URL(spec);
      if(url == null) {
        throw new Exception("URL Object construction failed");
      }

      AccessController.doPrivileged
        ( new PrivilegedExceptionAction(){
            public Object run() throws Exception {
              ConfigurationAdmin srvCA = null;
              try {    
                srvCA = getCA();
		CMDataManager.handleCMData(spec, srvCA);
              } finally {
                if(srvCA != null) {
                  bc.ungetService(refCA);
                }
              }
              return null;
            }
          });

      retcode = 0; // Success!
    } catch(MalformedURLException e) {
      out.println("Could not create URL. Details:") ;     
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } catch(IOException e) {
      out.println("Import failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } catch(SAXException e) {
      out.println("Import failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } catch(PrivilegedActionException pae) {
      out.println("Import failed. Details:");
      String reason = pae.getException().toString();
      out.println(reason == null ? "<unknown>" : reason);
    } catch(Exception e) {
      out.println("Import failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);
    } finally {

    }
    return retcode;
  }

  public final static String USAGE_EXPORT = "[-template] <file> [<selection>] ...";
  public final static String [] HELP_EXPORT = new String [] {
    "Export configuration data in xml format to a file.",
    "-template    If the output should be a template.",
    "<file>       Path to file to write xml to.",
    "<selection>  A pid that can contain wildcards '*',",
    "             or an ldap filter, or an index in output",
    "             from the latest use of the 'list' command.",
    "             If no selection is given all existing ",
    "             configurations will be exported."
    };

  public int cmdExport(Dictionary opts, Reader in, PrintWriter out, Session session) {
    int retcode = 1; // 1 initially not set to 0 until end of try block
    OutputStream os = null;
    ConfigurationAdmin srvCA = null;
    try {
      srvCA = getCA();
      
      boolean isTemplate = opts.get("-template") != null;
      String[] selection = (String[])opts.get("selection");
      boolean deleteAllOldConfigs = false;
      Configuration[] cs = null;
      if(selection == null || selection.length == 0) {
        deleteAllOldConfigs = true;
        cs = srvCA.listConfigurations(null);
      } else {
        cs = getConfigurations(session, srvCA, selection);
      }

      if(cs == null || cs.length == 0) {
        throw new Exception("No configurations matching selection.");
      }

      final String fileName = (String)opts.get("file");
      final File f = new File(fileName);
      os = (FileOutputStream) AccessController.doPrivileged
        ( new PrivilegedExceptionAction(){
            public Object run() throws Exception {
              return new FileOutputStream(f);
            }
          });
      try {
        CMDataManager.exportCMData(cs, deleteAllOldConfigs, isTemplate, srvCA, os);
      } finally {
        os.close();
      }
      retcode = 0; // Success!
    } catch(IOException e) {
      out.println("Export failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } catch(PrivilegedActionException pae) {
      out.println("Export failed. Details:");
      String reason = pae.getException().getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } catch(Exception e) {
      out.println("Export failed. Details:");
      String reason = e.getMessage();
      out.println(reason == null ? "<unknown>" : reason);

    } finally {
      if(srvCA != null) {
        bc.ungetService(refCA);
      }
    }
    return retcode;
  }

  /** Helper method that get the CA service.*/
  private ConfigurationAdmin getCA() throws Exception {
    ConfigurationAdmin srvCA = null;
    if(refCA == null) {
      throw new Exception("CM service is not available");
    }
    try {
      srvCA = (ConfigurationAdmin) AccessController.doPrivileged
        ( new PrivilegedExceptionAction(){
            public Object run() throws Exception {
              return bc.getService(refCA);
            }
          });
    } catch (PrivilegedActionException pae) {
      // Rethrow wrapped exception
      throw pae.getException();
    }
    if(srvCA == null) {
      throw new Exception("CM service is not available");
    }
    return srvCA;
  }
  
  /** Helper method that gets the current configuration from the
   ** session. Returns <code>null</code> if not availble.*/
  private Configuration getCurrent(Session session) {
    return (Configuration) session.getProperties().get(CURRENT);
  }

  /** Helper method that sets the current configuration in the
   ** session.*/
  private void setSessionProperty(Session session, String key, Object value) {
    if(value == null) {
      session.getProperties().remove(key);
    } else {
      session.getProperties().put(key, value);
    }
  }

  /** Helper method that sets the current configuration in the
   ** session.*/
  private void setCurrent(Session session, Configuration cfg) {
    setSessionProperty(session, CURRENT, cfg);
  }
  
  /** Helper method that returns true if the current configuration is
   ** set and its dictionary has been fetched. I.e. it returns true if
   ** the EDITING property of the session is set.*/
  private boolean isEditing(Session session) {
    return session.getProperties().get(EDITED) != null;
  }

  /** Helper method that gets the editing dictionary of the current
   ** configuration from the session. Returns a new empty dictionary
   ** if current is set but have no dictionary set yet.*/
  private Dictionary getEditingDict(Session session) {
    Dictionary dict = (Dictionary) session.getProperties().get(EDITED);
    if(dict == null) {
      Configuration cfg = getCurrent(session);
      if(cfg != null) {
        dict = cfg.getProperties();
      }
      if(dict == null) {
        dict = new Hashtable();
      }
      setEditingDict(session,dict);        
    }
    return dict;    
  }
  
  private boolean configurationHasChanged(ConfigurationAdmin ca, Configuration c1) throws Exception {
    String pid = c1.getPid();
    Configuration c2 = ca.getConfiguration(pid, null);
    return DictionaryUtils.dictionariesAreNotEqual(c1.getProperties(), c2.getProperties());
  }

  private Configuration[] getConfigurations(Session session, ConfigurationAdmin cm, String[] selection) throws Exception {
    Filter[] filters = convertToFilters(session, selection);
    return getConfigurationsMatchingFilters(cm, filters);
  }

  private Configuration[] getConfigurations(Session session, ConfigurationAdmin cm, String selection) throws Exception {
    return getConfigurations(session, cm, new String[] { selection });
  }

  private Filter[] convertToFilters(Session session, String[] selection) throws Exception {
    if(selection == null) {
      return null;
    }
    Filter[] filters = new Filter[selection.length];
    for(int i = 0; i < selection.length; ++i) {
      String current = selection[i];
      Filter filter = null;
      if(isInteger(current)) {
        filter = tryToCreateFilterFromIndex(session, current);
      } else if(startsWithParenthesis(current)) {
        filter = tryToCreateFilterFromLdapExpression(current);
      } else {
        filter = tryToCreateFilterFromPidContainingWildcards(current);
      }
      if(filter == null) {
        throw new Exception("Unable to handle selection argument " + current);
      }
      filters[i] = filter;
    }
    return filters;
  }

  private boolean isInteger(String possiblyAnInteger) {
    try {
      int ignored = Integer.parseInt(possiblyAnInteger);
    } catch(NumberFormatException e) {
      return false;
    }
    return true;
  }

  private boolean startsWithParenthesis(String selection) {
    return selection.startsWith("(");
  }


  private Filter tryToCreateFilterFromIndex(Session session, String index) throws Exception {
    String pid = getPidWithIndexInLastList(session, index);
    return tryToCreateFilterFromPidContainingWildcards(pid);
  }

  private Filter tryToCreateFilterFromPidContainingWildcards(String pidContainingWildcards) throws Exception {
    return tryToCreateFilterFromLdapExpression("(" + Constants.SERVICE_PID + "=" + pidContainingWildcards + ")");
  }

  private Filter tryToCreateFilterFromLdapExpression(String ldapExpression) throws Exception {
    return bc.createFilter(ldapExpression);
  }


  private String getPidWithIndexInLastList(Session session, String index) throws Exception {
    Configuration[] cs = (Configuration[])session.getProperties().get(LISTED_CONFIGS);
    if(cs == null) {
      throw new Exception("The 'list' command has not been used yet to create a list.");
    }
    if(cs.length == 0) {
      throw new Exception("No configurations listed by latest 'list' call.");
    }
    int i = Integer.parseInt(index);
    if(i < 0  || cs.length <= i) {
      throw new Exception("Invalid index." +
                          ((cs.length == 1) ?
                            "0 is the only valid index." :
                           ("Valid indices are 0 to " + (cs.length - 1))));
    }

    String pid = cs[i].getPid();
    if(pid == null) {
      throw new Exception("Unable to retrieve pid with index " + index + " from last 'list'.");
    }

    return pid;
  }

  private Configuration[] getConfigurationsMatchingFilters(ConfigurationAdmin cm, Filter[] filters) throws Exception {
    Configuration[] cs = cm.listConfigurations(null);
    if(cs == null || cs.length == 0) {
      return new Configuration[0];
    }
    if(filters == null || filters.length == 0) {
      return cs;
    }

    Vector matching = new Vector();
    for(int i = 0; i < cs.length; ++i) {
      for(int j = 0; j < filters.length; ++j) {
        if(filters[j].match(cs[i].getProperties())) {
          matching.addElement(cs[i]);
          break;
        }
      }
    }

    Configuration[] result = new Configuration[matching.size()];
    matching.copyInto(result);
    return result;
  }

  /** Helper method that sets the editing dictionary of the current
   ** configuration in the session.*/
  private void setEditingDict(Session session, Dictionary dict) {
    if (dict==null)
      session.getProperties().remove(EDITED);
    else
      session.getProperties().put(EDITED,dict);
  }
  

  private void printDictionary( PrintWriter out, Dictionary d ) {
    String[] keyNames = new String[d.size()];
    int i = 0;
    for (Enumeration keys= d.keys(); keys.hasMoreElements();) {
      keyNames[i++]  = (String) keys.nextElement();
    }
    Sort.sortStringArray(keyNames);
    for (i=0;i<keyNames.length;i++) {
      out.print( " " );
      out.print( keyNames[i] );
      out.print( ": " );
      printValue( out, d.get(keyNames[i]) );
      out.println();
    }
  }

  private void printValue( PrintWriter out, Object val ) {
    if (val instanceof Vector) {
      Vector v = (Vector) val;
      out.print( "{" );
      for (int i=0; i<v.size(); i++) {
        if (i>0) out.print( ", " );
        printValue( out, v.elementAt(i) );
      }
      out.print( "}" );
    } else if (val.getClass().isArray()) {
      int length = Array.getLength(val);
      out.print( "[" );
      for (int i=0; i < length; i++) {
        if (i>0) out.print( ", " );
        printValue( out, Array.get(val, i));
      }
      out.print( "]" );
    } else {
      out.print( val.toString() );
    }
  }

  private Object stringToObjectOfClass(String str, Class c) {
    if(str == null) {
      return null;
    }
    Object o = null;
    try {
      if(c == null || c == String.class) {
        o = str;
      } else if(str.length() == 0) {
        // None of the other Classes can handle a zero length String
        o = null;
      } else if(c == Integer.class) {
        return new Integer(str);
      } else if(c == Long.class) {
        o = new Long(str);
      } else if(c == Float.class) {
        o = new Float(str);
      } else if(c == Double.class) {
        o = new Double(str);
      } else if(c == Byte.class) {
        o = new Byte(str);
      } else if(c == Short.class) {
        o = new Short(str);
      } else if(c == BigInteger.class) {
        o = new BigInteger(str);
      } else if(classBigDecimal != null && c == classBigDecimal) {
        if(consBigDecimal != null) {
          o = consBigDecimal.newInstance(new Object [] { str });
        } else {
          o = null;
        }
      } else if(c == Character.class) {
        o = new Character(str.charAt(0));
      } else if(c == Boolean.class) {
        o = new Boolean(str);
      } else {
        o = null;
      }
    } catch(Exception ignored) {
      o = null;
    }
    return o;
  }

  Object createValue(String type, String def) {
    def = def.equals("") ? null : def;
    if(type.equals("String")) {
      return def == null ? new String() : new String(def);
    } else if(type.equals("Integer")) {
      return def == null ? new Integer(0) : new Integer(def);
    } else if(type.equals("Long")) {
      return def == null ? new Long(0) : new Long(def);
    } else if(type.equals("Float")) {
      return def == null ? new Float(0) : new Float(def);
    } else if(type.equals("Double")) {
      return def == null ? new Double(0) : new Double(def);
    } else if(type.equals("Byte")) {
      return def == null ? new Byte("0") : new Byte(def);
    } else if(type.equals("Short")) {
      return def == null ? new Short("0") : new Short(def);
    } else if(type.equals("BigInteger")) {
      return def == null ? new BigInteger("0") : new BigInteger(def);
    } else if(type.equals("BigDecimal")) {
      Object o = null;
      if(classBigDecimal != null && consBigDecimal != null) {
        def = def == null ? "0" : def;
        try {
          o = consBigDecimal.newInstance(new Object [] { def });
        } catch(Exception ignored) {
          o = null;
        }
      }
      return o;
    } else if(type.equals("Character")) {
      return def == null ? new Character('a') : new Character(def.charAt(0));
    } else if(type.equals("Boolean")) {
      return def == null ? new Boolean(false) : new Boolean(def);
    } else {
      // Unsupported type
      return null;
    }
  }
//////////////////
  private static void sortConfigurationArray(Configuration[] a) {
    sortConfigurationArray(a, 0, a.length);
  }

  private static void sortConfigurationArray(Configuration[] a,
                                             int fromIndex,
                                             int toIndex)
  {
    int middle;
    if (a == null)
      return;

    if (fromIndex + 1< toIndex) {
      middle = (fromIndex + toIndex) / 2;
      sortConfigurationArray(a, fromIndex, middle);
      sortConfigurationArray(a, middle, toIndex);
      mergeConfigurationArray(a, fromIndex, toIndex);
    }
  }

  private static void mergeConfigurationArray(Configuration[] a,
                                              int fromIndex,
                                              int toIndex)
  {
    int i, j, k, middle, n;
    n = toIndex - fromIndex;
    Configuration[] b = new Configuration[n];   // temporary array
    
    k = 0;
    middle = (fromIndex + toIndex) / 2;

    // Copy lower half to array b
    for(i = fromIndex; i < middle; i++)
      b[k++] = a[i];
    // Copy upper half to array b in oppsite order
    for(j = toIndex - 1; j >= middle; j--)
      b[k++] = a[j];

    i = 0;
    j = n - 1;
    k = fromIndex;
    
    // Copy back next-greatest element at each time
    // until i and j cross
    while (i <= j) {
      if (b[i].getPid().compareTo(b[j].getPid()) < 0)
        a[k++] = b[i++];
      else
        a[k++] = b[j--];
    }
  }

////////////////////////////////////
  public void serviceChanged(ServiceEvent event) {
    switch(event.getType()) {
      case ServiceEvent.REGISTERED:
        ServiceReference sr = event.getServiceReference();
        if(refCA != sr) {
          refCA = sr;
        }
      break;
      case ServiceEvent.MODIFIED:
      break;
      case ServiceEvent.UNREGISTERING:
        if(refCA != null) {
          refCA = null;
        }  
      break;
      default:
      break;
    }
  }
}

