/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package com.sun.jini.example.browser;

import com.sun.jini.admin.DestroyAdmin;
import com.sun.jini.config.Config;
import com.sun.jini.logging.Levels;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.rmi.server.ExportException;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
import net.jini.admin.Administrable;
import net.jini.admin.JoinAdmin;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.export.Exporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lease.LeaseListener;
import net.jini.lease.LeaseRenewalEvent;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.DiscoveryAdmin;
import net.jini.security.ProxyPreparer;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;

/**
 * JoinAdministrator is a gui-based utility to add/modify
 * attributes, groups and lookup locators.
 * And also supports some well known admin interfaces.
 * (DestroyAdmin, DiscoveryAdmin)
 *
 * <p>
 * current issues<br>
 * <ul>
 *   <li> can't operate(add/remove/modify) array elements in an entry.
 *   <li> does not support EntryBean.
 *   <li> field modification is not based on EditableTree
 * </ul>
 *
 * @author Sun Microsystems, Inc.
 */
class ServiceEditor extends JFrame {
  private static final Logger logger = Browser.logger;

  private Browser browser;
  private ServiceItem item;
  private ServiceRegistrar registrar;
  protected Object adminRoot = null;
  protected JoinAdmin jadmin = null;
  private ServiceTemplate stmpl;
  private NotifyReceiver receiver;
  private Lease elease = null;
  private long eventID = 0;
  private long seqNo = Long.MAX_VALUE;
  private AttributeTreePanel attrPanel;

  private final static int MINIMUM_WINDOW_WIDTH = 320;

  public ServiceEditor(ServiceItem item,
		       ServiceRegistrar registrar,
		       Browser browser)
    {
    super("ServiceItem Editor");
    
    this.item = item;
    this.registrar = registrar;
    this.browser = browser;
    try {
      adminRoot = browser.adminPreparer.prepareProxy(
			       ((Administrable) (item.service)).getAdmin());
      jadmin = (JoinAdmin) adminRoot;
    } catch (Throwable t) {
      logger.log(Level.INFO, "obtaining service admin failed", t);
    }

    // init main components
    attrPanel = new AttributeTreePanel();

    // setup notify
    try {
      stmpl = new ServiceTemplate(item.serviceID,
				  new Class[] { item.service.getClass() },
				  new Entry[] {});
      receiver = new NotifyReceiver();

      setupNotify();
    } catch (Throwable t) {
      logger.log(Level.INFO, "event registration failed", t);
      cancelNotify();
    }

    // add menu and attr panel
    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(new JoinMenuBar(), "North");
    getContentPane().add(attrPanel, "Center");

    validate();
    pack();
    setSize(((getSize().width < MINIMUM_WINDOW_WIDTH) ? MINIMUM_WINDOW_WIDTH : getSize().width),
	    getSize().height);

    // center in parent frame
    Rectangle bounds = browser.getBounds();
    Dimension dialogSize = getPreferredSize();
    int xpos = bounds.x + (bounds.width - dialogSize.width)/ 2;
    int ypos = bounds.y + (bounds.height - dialogSize.height)/2;
    setLocation((xpos < 0) ? 0 : xpos,
		(ypos < 0) ? 0 : ypos);
  }

  protected void cancelNotify() {
    if(elease != null) {
      try {
	browser.leaseMgr.cancel(elease);
      } catch (Throwable t) {
	logger.log(Levels.HANDLED, "event cancellation failed", t);
      }
      elease = null;
      seqNo = Long.MAX_VALUE;
    }
  }

  protected void setupNotify() {
    if(registrar != null) {
      try {
	EventRegistration reg =
	    registrar.notify(stmpl,
			     ServiceRegistrar.TRANSITION_MATCH_NOMATCH |
			     ServiceRegistrar.TRANSITION_NOMATCH_MATCH |
			     ServiceRegistrar.TRANSITION_MATCH_MATCH,
			     receiver.proxy,
			     null,
			     Lease.ANY);
	elease = (Lease) browser.leasePreparer.prepareProxy(reg.getLease());
	browser.leaseMgr.renewUntil(elease, Lease.ANY,
				    new Browser.LeaseNotify());
	eventID = reg.getID();
	seqNo = reg.getSequenceNumber();
      } catch (Throwable t) {
	logger.log(Level.INFO, "event registration failed", t);
      }
    }
  }

  private class NotifyReceiver implements RemoteEventListener, ServerProxyTrust
  {
    private final Exporter exporter;
    final RemoteEventListener proxy;

    public NotifyReceiver() throws ConfigurationException, ExportException {
      exporter = (Exporter)
	  Config.getNonNullEntry(browser.config, Browser.BROWSER,
				 "listenerExporter", Exporter.class,
				 new BasicJeriExporter(
					     TcpServerEndpoint.getInstance(0),
					     new BasicILFactory()));
      proxy = (RemoteEventListener) exporter.export(this);
    }

    public void notify(final RemoteEvent ev) {
      SwingUtilities.invokeLater(browser.wrap(new Runnable() {
	public void run() {
	  if (eventID == ev.getID() && seqNo < ev.getSequenceNumber()) {
	    seqNo = ev.getSequenceNumber();
	    attrPanel.receiveNotify();
	  }
	}
      }));
    }

    public TrustVerifier getProxyVerifier() {
      return new BasicProxyTrustVerifier(proxy);
    }
  }

  class JoinMenuBar extends JMenuBar {
    public JoinMenuBar() {
      JMenuItem mitem;

      // "File" Menu
      JMenu fileMenu = (JMenu) add(new JMenu("File"));
      mitem = (JMenuItem) fileMenu.add(new JMenuItem("Show Info"));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  Class[] infs = Browser.getInterfaces(item.service.getClass());
	  String[] msg = new String[3 + infs.length];
	  msg[0] = "ServiceID: " + item.serviceID;
	  msg[1] = "Service Instance: " + item.service.getClass().getName();
	  if(infs.length == 1)
	    msg[2] = "Implemented Interface:";
	  else
	    msg[2] = "Implemented Interfaces:";
	  for(int i = 0; i < infs.length; i++)
	    msg[3 + i] = "    " + infs[i].getName();

	  JOptionPane.showMessageDialog(ServiceEditor.this,
					msg,
					"ServiceItem Information",
					JOptionPane.INFORMATION_MESSAGE);
	}
      }));
      mitem = (JMenuItem) fileMenu.add(new JMenuItem("Refresh"));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  attrPanel.refreshPanel();
	}
      }));
      mitem = (JMenuItem) fileMenu.add(new JMenuItem("Close"));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  // cancel lease
	  cancelNotify();

	  // release resources and close all child frames
	  ServiceEditor.this.dispose();
	}
      }));

      // "Edit" Menu
      JMenu editMenu = (JMenu) add(new JMenu("Edit"));
      mitem = (JMenuItem) editMenu.add(new JMenuItem("Add Attribute..."));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  attrPanel.addAttr();
	}
      }));
      mitem = (JMenuItem) editMenu.add(new JMenuItem("Remove Attribute"));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  attrPanel.removeAttr();
	}
      }));

      // "Admin" Menu
      JMenu adminMenu = (JMenu) add(new JMenu("Admin"));

      // Group (JoinAdmin)
      mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining groups..."));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  new GroupLister("Joining Groups").showFrame();
	}
      }));

      // Locator (JoinAdmin)
      mitem = (JMenuItem) adminMenu.add(new JMenuItem("Joining locators..."));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  new LocatorLister("Joining Locators").showFrame();
	}
      }));

      // separator
      adminMenu.addSeparator();

      // Group (DiscoveryAdmin)
      mitem = (JMenuItem) adminMenu.add(new JMenuItem("Member groups..."));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  new MemberGroupLister("Member Groups").showFrame();
	}
      }));
      if(! (adminRoot instanceof DiscoveryAdmin))
	mitem.setEnabled(false);

      // Unicast port (DiscoveryAdmin)
      mitem = (JMenuItem) adminMenu.add(new JMenuItem("Unicast port..."));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  try {
	    String[] msg = { "Current port is " + ((DiscoveryAdmin) adminRoot).getUnicastPort(),
			     "Input a new value" };
	    String result = JOptionPane.showInputDialog(ServiceEditor.this,
							msg,
							"Unicast Port",
							JOptionPane.QUESTION_MESSAGE);

	    if(result == null)
	      return;

	    try {
	      int port = Integer.parseInt(result);
	      ((DiscoveryAdmin) adminRoot).setUnicastPort(port);
	    } catch (NumberFormatException e) {
	      JOptionPane.showMessageDialog(ServiceEditor.this,
					    result + " is not acceptable.",
					    "Error",
					    JOptionPane.ERROR_MESSAGE);
	    } catch (Throwable t) {
	      logger.log(Level.INFO, "setting unicast port failed", t);
	      JOptionPane.showMessageDialog(ServiceEditor.this,
					    t.getMessage(),
					    t.getClass().getName(),
					    JOptionPane.ERROR_MESSAGE);
	    }
	  } catch (Throwable t) {
	    logger.log(Level.INFO, "getting unicast port failed", t);
	    JOptionPane.showMessageDialog(ServiceEditor.this,
					  t.getMessage(),
					  t.getClass().getName(),
					  JOptionPane.ERROR_MESSAGE);
	  }
	}
      }));
      if(! (adminRoot instanceof DiscoveryAdmin))
	mitem.setEnabled(false);

      // separator
      adminMenu.addSeparator();

      // DestroyAdmin
      mitem = (JMenuItem) adminMenu.add(new JMenuItem("Destroy"));
      mitem.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  if(JOptionPane.showConfirmDialog(ServiceEditor.this,
					   "Are you sure to destroy this service?",
					   "Query",
					   JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
	    try {
	      ((DestroyAdmin) adminRoot).destroy();
	      // cancel lease
	      cancelNotify();

	      // release resources and close all child frames
	      ServiceEditor.this.dispose();
	    } catch (Throwable t) {
	      logger.log(Level.INFO, "service destroy failed", t);
	      JOptionPane.showMessageDialog(ServiceEditor.this,
					    t.getMessage(),
					    t.getClass().getName(),
					    JOptionPane.ERROR_MESSAGE);
	    }
	}
      }));
      if(! (adminRoot instanceof DestroyAdmin))
	mitem.setEnabled(false);
    }
  }

  class AttributeTreePanel extends EntryTreePanel {

    public AttributeTreePanel() {
      super(true);

      tree.addMouseListener(browser.wrap(new DoubleClicker(this)));
      refreshPanel();
    }

    protected Entry[] getEntryArray() {
      try {
	return jadmin.getLookupAttributes();
      } catch (Throwable t) {
	logger.log(Level.INFO, "obtaining attributes failed", t);
      }
      return null;
    }

    protected void receiveNotify() {
      String[] msg = {"Attributes have been modified by another client or service provider itself.",
		      "Do you want refresh attributes ?"};
      int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
						 msg,
						 "Query",
						 JOptionPane.YES_NO_OPTION);
	
      if(result == JOptionPane.YES_OPTION)
	refreshPanel();
    }

    public void editField(ObjectNode node) {

      String result = JOptionPane.showInputDialog(this,
						  "Input a new value",
						  "Modify a field",
						  JOptionPane.QUESTION_MESSAGE);

      if(result == null){
      } else {
	// Save current value as template
	Entry template = cloneEntry((Entry) node.getEntryTop());
	Object oldVal = null;

	if(result.length() == 0){
	  oldVal = node.setValue(null);
	} else {
	  oldVal = node.setValue(result);
	}
	// modifyAttribute
	try {
	  node.setObjectRecursive();
	  Entry attr = (Entry) node.getEntryTop();
	  //Entry template = (Entry) generateTemplate(attr);

	  // cancel notify while adding an attribute
	  cancelNotify();

	  jadmin.modifyLookupAttributes(new Entry[] { template }, new Entry[] { attr });

	  setupNotify();

	  // Redraw node value
	  model.nodeChanged(node);
	} catch (Throwable t) {
	  logger.log(Level.INFO, "attribute modification failed", t);
	  // recover tree node
	  try {
	    node.setValue(oldVal);
	    node.setObjectRecursive();
	  } catch (Throwable tt) {
	    logger.log(Levels.HANDLED, "node reset failed", tt);
	  }
	  model.nodeChanged(node);
	  //model.nodeStructureChanged(node);

	  // show dialog
	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
					t.getMessage(),
					t.getClass().getName(),
					JOptionPane.ERROR_MESSAGE);
	}
      }
    }

    public void addAttr() {

      String result = JOptionPane.showInputDialog(this,
						  "Input an entry class name",
						  "Add an attribute",
						  JOptionPane.QUESTION_MESSAGE);

      if(result == null || result.length() == 0){
      } else {
	try {
	  Class clazz = Class.forName(result);
	  Object attr = clazz.newInstance();

	  if(! (attr instanceof Entry)){
	    JOptionPane.showMessageDialog(AttributeTreePanel.this,
					  "Does not implement Entry interface",
					  "Unacceptable Class",
					  JOptionPane.WARNING_MESSAGE);

	  } else if(attr instanceof net.jini.lookup.entry.ServiceControlled){
	    JOptionPane.showMessageDialog(AttributeTreePanel.this,
					  "Implements ServiceControlled interface",
					  "Unacceptable Class",
					  JOptionPane.WARNING_MESSAGE);
	  } else {
	    // cancel notify while adding an attribute
	    cancelNotify();

	    jadmin.addLookupAttributes(new Entry[] { (Entry) attr });
	    // add node of this attribute
	    ObjectNode node = new ObjectNode(attr, true);
	    root.add(node);
	    recursiveObjectTree(node);

	    // 
	    setupNotify();

	    // refresh view
	    model.nodesWereInserted(root,
				    new int[] { model.getIndexOfChild(root, node) });
	  }
	} catch (ClassNotFoundException e) {
	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
					e.getMessage(),
					"Class Not Found",
					JOptionPane.WARNING_MESSAGE);
	} catch (Throwable t) {
	  logger.log(Level.INFO, "adding attribute failed", t);
	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
					t.getMessage(),
					t.getClass().getName(),
					JOptionPane.WARNING_MESSAGE);
	}
      }
    }

    public void removeAttr() {

      ObjectNode node = (ObjectNode) tree.getLastSelectedPathComponent();
      if(node == null){
	JOptionPane.showMessageDialog(AttributeTreePanel.this,
				      "Select an attribute folder to remove.",
				      "Warning",
				      JOptionPane.WARNING_MESSAGE);
	
	return;
      } else if(! node.isControllable()){
	JOptionPane.showMessageDialog(AttributeTreePanel.this,
				      "This attribute is under service provider's control.",
				      "Warning",
				      JOptionPane.WARNING_MESSAGE);
	
	return;
      } else if(! node.isEntryTop()){
	JOptionPane.showMessageDialog(AttributeTreePanel.this,
				      "Select a top of attribute folder.",
				      "Warning",
				      JOptionPane.WARNING_MESSAGE);
	
	return;
      }

      Entry target = (Entry) node.getObject();
      int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
						 new String[] {"Remove attribute:",
								 target.toString() },
						 "Query",
						 JOptionPane.YES_NO_OPTION);
	
      if(result == JOptionPane.YES_OPTION){
	// Remote Attribute
	try {

	  // cancel notify while adding an attribute
	  cancelNotify();

	  jadmin.modifyLookupAttributes(new Entry[] { target }, new Entry[] { null });

	  // 
	  setupNotify();

	  int index = root.getIndex(node);
	  root.remove(node);
	  model.nodesWereRemoved(root, new int[] {index}, new Object[] {node});
	  node = null;
	} catch (Throwable t) {
	  logger.log(Level.INFO, "attribute removal failed", t);
	  JOptionPane.showMessageDialog(AttributeTreePanel.this,
					t.getMessage(),
					t.getClass().getName(),
					JOptionPane.ERROR_MESSAGE);
	}
      }
    }

    private Entry cloneEntry(Entry attr) {
      try {
	Class realClass = attr.getClass();
	Entry template = (Entry) realClass.newInstance();

	Field[] f = realClass.getFields();
	for(int i = 0; i < f.length; i++) {
	  if(! usableField(f[i]))
	    continue;
	  f[i].set(template, f[i].get(attr));
	}

	return template;
      } catch (Throwable t) {
	logger.log(Level.INFO, "duplicating entry failed", t);
      }
      return null;
    }

    // from EntryRep
    private boolean usableField(Field field) {
      Class desc = field.getDeclaringClass();

      if(desc.isPrimitive()) {
	throw new IllegalArgumentException("primitive types not allowed in an Entry");
      }

      // skip anything that isn't a public per-object mutable field
      int mods = field.getModifiers();
      return (0 == (mods & (Modifier.TRANSIENT | Modifier.STATIC | Modifier.FINAL)));
    }

    private Entry generateTemplate(Entry attr) {
      try {
	Class realClass = attr.getClass();
	Entry template = (Entry) realClass.newInstance();

	Field[] f = realClass.getFields();
	for(int i = 0; i < f.length; i++)
	  f[i].set(template, null);

	return template;
      } catch (Throwable t) {
	logger.log(Level.INFO, "instantiating template failed", t);
      }
      return null;
    }

    class DoubleClicker extends MouseAdapter {
      AttributeTreePanel parent;

      public DoubleClicker(AttributeTreePanel parent){
	this.parent = parent;
      }

      public void mouseClicked(MouseEvent ev){
	if(ev.getClickCount() >= 2){
	  JTree tree = (JTree) ev.getSource();
	  TreePath path = tree.getPathForLocation(ev.getX(), ev.getY());
	  if(path == null)
	    return;
	  ObjectNode node = (ObjectNode) path.getLastPathComponent();

	  if(node.isLeaf()){
	    if(! node.isControllable()){
	      JOptionPane.showMessageDialog(AttributeTreePanel.this,
					    "This attribute is under service provider's control.",
					    "Warning",
					    JOptionPane.WARNING_MESSAGE);
	    } else if(node.isEditable() &&
		      ((ObjectNode) node.getParent()).isEntryTop())
	    {
	      parent.editField(node);
	    } else {
	      JOptionPane.showMessageDialog(AttributeTreePanel.this,
					    "This field is not editable.",
					    "Warning",
					    JOptionPane.WARNING_MESSAGE);
	    }
	  }

	  tree.scrollPathToVisible(path);
	}
      }
    }
  }

  abstract class ListerFrame extends JFrame {

    private JList listBox;
    private JScrollPane scrollPane;
    protected DefaultListModel model = new DefaultListModel();
    private DefaultListModel dummyModel = new DefaultListModel();	// to keep away from Swing's bug

    private JButton addButton;
    private JButton removeButton;
    private JButton closeButton;

    public ListerFrame(String title) {
      super(title);

      getContentPane().setLayout(new BorderLayout());

      // create the initial list
      listBox = new JList(model);
      listBox.setFixedCellHeight(20);
      scrollPane = new JScrollPane(listBox);
      getContentPane().add(scrollPane, "Center");
      //resetListModel();

      // Create the controls 
      JPanel buttonPanel = new JPanel();
      addButton = new JButton("Add");
      addButton.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  String result = JOptionPane.showInputDialog(ListerFrame.this, getAddMessage());

	  if(result != null){
	    StringTokenizer st = new StringTokenizer(result);
	    String[] tokens = new String[st.countTokens()];
	    for(int i = 0; i < tokens.length; i++)
	      tokens[i] = st.nextToken().trim();

	    addItems(tokens);
	    resetListModel();
	    scrollPane.validate();
	  }
	}
      }));
      buttonPanel.add(addButton);

      removeButton = new JButton("Remove");
      removeButton.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  Object[] selected = listBox.getSelectedValues();

	  if(selected == null || selected.length == 0){
	    // no items are selected
	    JOptionPane.showMessageDialog(ListerFrame.this,
					  "No items are selected",
					  "Warning",
					  JOptionPane.WARNING_MESSAGE);
	    return;
	  }

	  int result = JOptionPane.showConfirmDialog(ListerFrame.this,
						     getRemoveMessage(selected),
						     "Query",
						     JOptionPane.YES_NO_OPTION);
	
	  if(result == JOptionPane.YES_OPTION){
	    removeItems(selected);
	    resetListModel();
	    scrollPane.validate();
	  }
	}
      }));
      buttonPanel.add(removeButton);

      closeButton = new JButton("Close");
      closeButton.addActionListener(browser.wrap(new ActionListener() {
	public void actionPerformed(ActionEvent ev) {
	  setVisible(false);
	}
      }));
      buttonPanel.add(closeButton);
      getContentPane().add(buttonPanel, "South");

      pack();
    }

    public void showFrame() {
      // init list data
      resetListModel();

      // center in parent frame
      Rectangle bounds = ServiceEditor.this.getBounds();
      Dimension dialogSize = getPreferredSize();

      setLocation(bounds.x + (bounds.width - dialogSize.width)/ 2,
		  bounds.y + (bounds.height - dialogSize.height)/2);

      setVisible(true);
    }

    private void resetListModel() {
      //listBox.setModel(null);
      listBox.setModel(dummyModel);	// to keep away from NullException (Swing's bug)

      model.removeAllElements();
      initListModel();

      listBox.setModel(model);
      listBox.clearSelection();
      listBox.ensureIndexIsVisible(0);
      listBox.repaint();
      listBox.revalidate();
    }

    protected abstract void initListModel();

    protected abstract String getAddMessage();

    protected abstract String getRemoveMessage(Object[] items);

    protected abstract void addItems(String[] items);

    protected abstract void removeItems(Object[] items);
  }

  class GroupLister extends ListerFrame {

    public GroupLister(String title) {
      super(title);
    }

    protected void initListModel() {
      try {
	String[] groups = jadmin.getLookupGroups();
	for(int i = 0; i < groups.length; i++) {
	  model.addElement(new GroupItem(groups[i]));
	}
      } catch (Throwable t) {
	logger.log(Level.INFO, "obtaining groups failed", t);
      }
    }

    protected String getAddMessage() {
      return "Enter adding group(s)";
    }

    protected String getRemoveMessage(Object[] items) {
      StringBuffer msg = new StringBuffer();
      if(items.length > 1)
	msg.append("Remove these groups : ");
      else
	msg.append("Remove a group : ");
      for(int i = 0; i < items.length; i++) {
	if(i != 0)
	  msg.append(", ");
	msg.append(((GroupItem) items[i]).toString());
      }
      return msg.toString();
    }

    protected void addItems(String[] items) {
      // check "public"
      String[] grps = new String[items.length];
      for(int i = 0; i < items.length; i++)
	grps[i] = new GroupItem(items[i]).group;

      try {
	jadmin.addLookupGroups(grps);
      } catch (Throwable t) {
	logger.log(Level.INFO, "adding groups failed", t);
      }
    }

    protected void removeItems(Object[] items) {
      String[] grps = new String[items.length];
      for(int i = 0; i < items.length; i++)
	grps[i] = ((GroupItem) items[i]).group;

      try {
	jadmin.removeLookupGroups(grps);
      } catch (Throwable t) {
	logger.log(Level.INFO, "removing groups failed", t);
      }
    }
  }

  class MemberGroupLister extends ListerFrame {

    public MemberGroupLister(String title) {
      super(title);
    }

    protected void initListModel() {
      try {
	String[] groups = ((DiscoveryAdmin) adminRoot).getMemberGroups();
	for(int i = 0; i < groups.length; i++) {
	  model.addElement(new GroupItem(groups[i]));
	}
      } catch (Throwable t) {
	logger.log(Level.INFO, "obtaining groups failed", t);
      }
    }

    protected String getAddMessage() {
      return "Enter adding group(s)";
    }

    protected String getRemoveMessage(Object[] items) {
      StringBuffer msg = new StringBuffer();
      if(items.length > 1)
	msg.append("Remove these groups : ");
      else
	msg.append("Remove a group : ");
      for(int i = 0; i < items.length; i++) {
	if(i != 0)
	  msg.append(", ");
	msg.append(((GroupItem) items[i]).toString());
      }
      return msg.toString();
    }

    protected void addItems(String[] items) {
      // check "public"
      String[] grps = new String[items.length];
      for(int i = 0; i < items.length; i++)
	grps[i] = new GroupItem(items[i]).group;

      try {
	((DiscoveryAdmin) adminRoot).addMemberGroups(grps);
      } catch (Throwable t) {
	logger.log(Level.INFO, "adding groups failed", t);
      }
    }

    protected void removeItems(Object[] items) {
      String[] grps = new String[items.length];
      for(int i = 0; i < items.length; i++)
	grps[i] = ((GroupItem) items[i]).group;

      try {
	((DiscoveryAdmin) adminRoot).removeMemberGroups(grps);
      } catch (Throwable t){
	logger.log(Level.INFO, "removing groups failed", t);
      }
    }
  }

  class GroupItem {
    public String group;

    public GroupItem(String group) {
      if(group.equals("public"))
	this.group = "";
      else
	this.group = group;
    }

    public String toString() {
      if("".equals(group))
	return "public";
      else
	return group;
    }
  }

  class LocatorLister extends ListerFrame {

    public LocatorLister(String title) {
      super(title);
    }

    protected void initListModel() {
      try {
	LookupLocator[] locators = jadmin.getLookupLocators();
	for(int i = 0; i < locators.length; i++) {
	  model.addElement(locators[i]);
	}
      } catch (Throwable t) {
	logger.log(Level.INFO, "obtaining locators failed", t);
      }
    }

    protected String getAddMessage() {
      return "Enter a new locator's URL";
    }

    protected String getRemoveMessage(Object[] items) {
      StringBuffer msg = new StringBuffer();
      if(items.length > 1)
	msg.append("Remove these locators : ");
      else
	msg.append("Remove a locator : ");
      for(int i = 0; i < items.length; i++) {
	if(i != 0)
	  msg.append(", ");
	msg.append(items[i].toString());
      }
      return msg.toString();
    }

    protected void addItems(String[] items) {
      LookupLocator[] locs = new LookupLocator[items.length];
      for(int i = 0; i < items.length; i++) {
	try {
	  locs[i] = new LookupLocator(items[i]);
	} catch (MalformedURLException e) {
	  JOptionPane.showMessageDialog(LocatorLister.this,
					"\"" + items[i] + "\": " +
					e.getMessage(),
					"Bad Locator",
					JOptionPane.WARNING_MESSAGE);
	  return;
	}
      }

      try {
	jadmin.addLookupLocators(locs);
      } catch (Throwable t){
	logger.log(Level.INFO, "adding locators failed", t);
      }
    }

    protected void removeItems(Object[] items) {
      LookupLocator[] locs = new LookupLocator[items.length];
      for(int i = 0; i < items.length; i++)
	locs[i] = (LookupLocator) items[i];

      try {
	jadmin.removeLookupLocators(locs);
      } catch (Throwable t) {
	logger.log(Level.INFO, "removing locators failed", t);
      }
    }
  }
}
