You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by gt...@apache.org on 2014/01/13 04:35:26 UTC

[19/40] Ongoing work to add client profile and run browser inside it.

http://git-wip-us.apache.org/repos/asf/river-container/blob/7ff8b15a/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java
----------------------------------------------------------------------
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java b/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java
new file mode 100644
index 0000000..30987bb
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/MetalIcons.java
@@ -0,0 +1,348 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+import java.io.Serializable;
+import javax.swing.Icon;
+
+/**
+ * Based on "MetalIconFactory.java"
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ */
+class MetalIcons implements Serializable {
+
+  private static Icon blueFolderIcon;
+  private static Icon blueFileIcon;
+  private static Icon orangeFolderIcon;
+  private static Icon orangeFileIcon;
+  private static Icon grayFolderIcon;
+  private static Icon grayFileIcon;
+  private static Icon unusableFolderIcon;
+  private static Icon unusableFileIcon;
+
+  // Colors for Orange icons
+  private static Color orangeDarkShadowColor = new Color(0xcc, 0x99, 0x66);
+  private static Color orangeColor           = new Color(0xff, 0x99, 0x33);
+  private static Color orangeShadowColor     = new Color(0xcc, 0x99, 0x66);
+  private static Color orangeInfoColor       = new Color(0x99, 0x00, 0x33);
+  private static Color orangeHighlightColor  = new Color(0xff, 0xff, 0x33);
+
+  // Colors for Gray icons
+  private static Color grayDarkShadowColor = new Color(0x99, 0x99, 0x99);
+  private static Color grayColor           = new Color(0xbb, 0xbb, 0xbb);
+  private static Color grayShadowColor     = new Color(0xaa, 0xaa, 0xaa);
+  private static Color grayInfoColor       = new Color(0x88, 0x88, 0x88);
+  private static Color grayHighlightColor  = new Color(0xdd, 0xdd, 0xdd);
+
+  // Constants
+  public static final boolean DARK = false;
+  public static final boolean LIGHT = true;
+
+  // Accessor functions for Icons. Does the caching work.
+  public static Icon getBlueFolderIcon() {
+    if(blueFolderIcon == null)
+      blueFolderIcon = new BlueFolderIcon();
+    return blueFolderIcon;
+  }
+
+  public static Icon getBlueFileIcon() {
+    if(blueFileIcon == null)
+      blueFileIcon = new BlueFileIcon();
+    return blueFileIcon;
+  }
+
+  public static Icon getOrangeFolderIcon() {
+    if(orangeFolderIcon == null)
+      orangeFolderIcon = new OrangeFolderIcon();
+    return orangeFolderIcon;
+  }
+
+  public static Icon getOrangeFileIcon() {
+    if(orangeFileIcon == null)
+      orangeFileIcon = new OrangeFileIcon();
+    return orangeFileIcon;
+  }
+
+  public static Icon getGrayFolderIcon() {
+    if(grayFolderIcon == null)
+      grayFolderIcon = new GrayFolderIcon();
+    return grayFolderIcon;
+  }
+
+  public static Icon getGrayFileIcon() {
+    if(grayFileIcon == null)
+      grayFileIcon = new GrayFileIcon();
+    return grayFileIcon;
+  }
+
+  public static Icon getUnusableFolderIcon() {
+    if(unusableFolderIcon == null)
+      unusableFolderIcon = new UnusableFolderIcon();
+    return unusableFolderIcon;
+  }
+
+  public static Icon getUnusableFileIcon() {
+    if(unusableFileIcon == null)
+      unusableFileIcon = new UnusableFileIcon();
+    return unusableFileIcon;
+  }
+
+
+  static private final Dimension folderIcon16Size = new Dimension( 16, 16 );
+  static private final Dimension fileIcon16Size = new Dimension( 16, 16 );
+
+  /**
+   */
+  static class BlueFolderIcon extends FolderIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      MetalLookAndFeel.getPrimaryControlDarkShadow(),
+		      MetalLookAndFeel.getPrimaryControl(),
+		      MetalLookAndFeel.getPrimaryControlShadow(),
+		      MetalLookAndFeel.getPrimaryControlInfo(),
+		      MetalLookAndFeel.getPrimaryControlHighlight());
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class OrangeFolderIcon extends FolderIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      orangeDarkShadowColor,
+		      orangeColor,
+		      orangeShadowColor,
+		      orangeInfoColor,
+		      orangeHighlightColor);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class GrayFolderIcon extends FolderIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      grayDarkShadowColor,
+		      grayColor,
+		      grayShadowColor,
+		      grayInfoColor,
+		      grayHighlightColor);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class UnusableFolderIcon extends FolderIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      grayDarkShadowColor,
+		      grayColor,
+		      grayShadowColor,
+		      grayInfoColor,
+		      grayHighlightColor);
+      super.drawCross(c, g, x, y);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  /**
+   */
+  private static abstract class FolderIcon16 implements Serializable {
+
+    public void paintIcon(Component c, Graphics g, int x, int y,
+			  Color controlDarkShadowColor,
+			  Color controlColor,
+			  Color controlShadowColor,
+			  Color controlInfoColor,
+			  Color controlHighlightColor) {
+      g.translate( x, y + getShift() );
+
+      int right = folderIcon16Size.width - 1;
+      int bottom = folderIcon16Size.height - 1;
+
+      // Draw tab top
+      g.setColor( controlDarkShadowColor );
+      g.drawLine( right - 5, 3, right, 3 );
+      g.drawLine( right - 6, 4, right, 4 );
+
+      // Draw folder front
+      g.setColor( controlColor );
+      g.fillRect( 2, 7, 13, 8 );
+
+      // Draw tab bottom
+      g.setColor( controlShadowColor );
+      g.drawLine( right - 6, 5, right - 1, 5 );
+
+      // Draw outline
+      g.setColor( controlInfoColor );
+      g.drawLine( 0, 6, 0, bottom );            // left side
+      g.drawLine( 1, 5, right - 7, 5 );         // first part of top
+      g.drawLine( right - 6, 6, right - 1, 6 ); // second part of top
+      g.drawLine( right, 5, right, bottom );    // right side
+      g.drawLine( 0, bottom, right, bottom );   // bottom
+
+      // Draw highlight
+      g.setColor( controlHighlightColor );
+      g.drawLine( 1, 6, 1, bottom - 1 );
+      g.drawLine( 1, 6, right - 7, 6 );
+      g.drawLine( right - 6, 7, right - 1, 7 );
+
+      g.translate( -x, -(y + getShift()) );
+    }
+
+    public void drawCross(Component c, Graphics g, int x, int y) {
+      g.translate( x, y + getShift() );
+
+      int right = folderIcon16Size.width - 1;
+      int bottom = folderIcon16Size.height - 1;
+
+      // Draw tab top
+      Color crossColor = Color.red;
+      g.setColor( crossColor );
+      g.drawLine( 2, 2, right, bottom );
+      g.drawLine( 2, bottom, right, 2 );
+    }
+
+    public int getShift() { return 0; }
+    public int getAdditionalHeight() { return 0; }
+
+    public int getIconWidth() { return folderIcon16Size.width; }
+    public int getIconHeight() { return folderIcon16Size.height + getAdditionalHeight(); }
+  }
+
+
+
+
+  static class BlueFileIcon extends FileIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      MetalLookAndFeel.getPrimaryControlHighlight(),
+		      MetalLookAndFeel.getPrimaryControlInfo(),
+		      MetalLookAndFeel.getPrimaryControl());
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class OrangeFileIcon extends FileIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      orangeHighlightColor,
+		      orangeInfoColor,
+		      orangeColor);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class GrayFileIcon extends FileIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      grayHighlightColor,
+		      grayInfoColor,
+		      grayColor);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  static class UnusableFileIcon extends FileIcon16 implements Icon {
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+      super.paintIcon(c, g, x, y,
+		      grayHighlightColor,
+		      grayInfoColor,
+		      grayColor);
+      super.drawCross(c, g, x, y);
+    }
+    public int getShift() { return -1; }
+    public int getAdditionalHeight() { return 2; }
+  }
+
+  /**
+   */
+  private static abstract class FileIcon16 implements Serializable {
+    public void paintIcon(Component c, Graphics g, int x, int y,
+			  Color controlHighlightColor,
+			  Color controlInfoColor,
+			  Color controlColor) {
+      g.translate( x, y + getShift() );
+
+      int right = fileIcon16Size.width - 1;
+      int bottom = fileIcon16Size.height - 1;
+
+      // Draw fill
+      g.setColor( controlHighlightColor );
+      g.fillRect( 4, 2, 9, 12 );
+
+      // Draw frame
+      g.setColor( controlInfoColor );
+      g.drawLine( 2, 0, 2, bottom );                 // left
+      g.drawLine( 2, 0, right - 4, 0 );              // top
+      g.drawLine( 2, bottom, right - 1, bottom );    // bottom
+      g.drawLine( right - 1, 6, right - 1, bottom ); // right
+      g.drawLine( right - 6, 2, right - 2, 6 );      // slant 1
+      g.drawLine( right - 5, 1, right - 4, 1 );      // part of slant 2
+      g.drawLine( right - 3, 2, right - 3, 3 );      // part of slant 2
+      g.drawLine( right - 2, 4, right - 2, 5 );      // part of slant 2
+
+      // Draw highlight
+      g.setColor( controlColor );
+      g.drawLine( 3, 1, 3, bottom - 1 );                  // left
+      g.drawLine( 3, 1, right - 6, 1 );                   // top
+      g.drawLine( right - 2, 7, right - 2, bottom - 1 );  // right
+      g.drawLine( right - 5, 2, right - 3, 4 );           // slant
+      g.drawLine( 3, bottom - 1, right - 2, bottom - 1 ); // bottom
+
+      g.translate( -x, -(y + getShift()) );
+    }
+
+    public void drawCross(Component c, Graphics g, int x, int y) {
+      g.translate( x, y + getShift() );
+
+      int right = folderIcon16Size.width - 1;
+      int bottom = folderIcon16Size.height - 1;
+
+      // Draw tab top
+      Color crossColor = Color.red;
+      g.setColor( crossColor );
+      g.drawLine( 1, 1, right - 1, bottom - 1 );
+      g.drawLine( 1, bottom - 1, right - 1, 1 );
+    }
+
+    public int getShift() { return 0; }
+    public int getAdditionalHeight() { return 0; }
+
+    public int getIconWidth() { return fileIcon16Size.width; }
+    public int getIconHeight() { return fileIcon16Size.height + getAdditionalHeight(); }
+  }
+
+
+  static class TreeLeafIcon extends FileIcon16 {
+    public int getShift() { return 2; }
+    public int getAdditionalHeight() { return 4; }
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/river-container/blob/7ff8b15a/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java
----------------------------------------------------------------------
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java b/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java
new file mode 100644
index 0000000..6409ba1
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ObjectNode.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import java.lang.reflect.Field;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreePath;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.Icon;
+
+/**
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+class ObjectNode extends DefaultMutableTreeNode implements java.io.Serializable {
+
+  // "classname", "classname fieldName" or "classname fieldName=value"
+  /**
+   * @serial
+   */
+  private String name;
+
+  /**
+   * @serial
+   */
+  private Object obj;
+
+  /**
+   * @serial
+   */
+  private Class clazz;
+
+  /**
+   * @serial
+   */
+  private String fieldName;
+
+  /**
+   * @serial
+   */
+  private int arrayIndex = -1;
+
+  /**
+   * @serial
+   */
+  private boolean editable = false;
+
+  /**
+   * @serial
+   */
+  private boolean isLeaf;
+
+  /**
+   * @serial
+   */
+  private boolean isAdministrable;	// root level
+
+  /**
+   * @serial
+   */
+  private boolean isControllable;	// entry level
+
+  /**
+   * @serial
+   */
+  private boolean isRoot = false;
+
+  /**
+   * @serial
+   */
+  private boolean isEntryTop = false;
+
+  // icons
+  private static Icon[] icons = new Icon[6];
+  static {
+    icons[0] = MetalIcons.getBlueFolderIcon();	// Administrable Service, Controllable Attribute
+    icons[1] = MetalIcons.getGrayFolderIcon();	// Non-administrable Service
+    icons[2] = MetalIcons.getOrangeFolderIcon();	// Uncontrollable Attribute
+    icons[3] = MetalIcons.getBlueFileIcon();	// Administrable Service, Controllable Attribute
+    icons[4] = MetalIcons.getGrayFileIcon();	// Non-administrable Service
+    icons[5] = MetalIcons.getOrangeFileIcon();	// Uncontrollable Attribute
+  }
+
+  /**
+     Constructor for a root node.
+  */
+  public ObjectNode(boolean isAdministrable) {
+    this("Root node", "".getClass(), null, -1, false);
+
+    this.isAdministrable = isAdministrable;
+    this.isRoot = true;
+    this.isEntryTop = true;
+  }
+
+  /**
+     Constructor for an entry (attribute) top nodes.
+  */
+  public ObjectNode(Object obj, boolean isControllable) {
+    this(obj, obj.getClass(), null, -1, false);
+
+    this.isControllable = isControllable;
+    this.isEntryTop = true;
+  }
+
+  /**
+     Constructor for an ordinary field.
+  */
+  public ObjectNode(Object obj, Class clazz, String fieldName, boolean isLeaf) {
+    this(obj, clazz, fieldName, -1, isLeaf);
+  }
+
+  /**
+     Constructor for an array element.
+  */
+  public ObjectNode(Object obj, Class clazz, String fieldName, int arrayIndex, boolean isLeaf) {
+    this.obj = obj;
+    this.clazz = clazz;
+    this.fieldName = fieldName;
+    this.arrayIndex = arrayIndex;
+    this.isLeaf = isLeaf;
+
+    super.setAllowsChildren(! isLeaf);
+    setNodeName();
+  }
+
+  private void setNodeName() {
+    name = Introspector.getTypename(clazz, false);
+    if(fieldName != null)
+      name += " " + fieldName;
+
+    if(isLeaf) {
+      //Class clazz = obj.getClass();
+      String value = "";
+      if(arrayIndex >= 0)
+	value += ("[" + arrayIndex + "]");
+      value += "=";
+      if(clazz.isPrimitive()){
+	value += "" + obj;
+	editable = false;
+      } else if(Introspector.isWrapper(clazz)){
+	// Wrapper objects
+	value += "" + obj;
+	editable = true;
+      } else if(Introspector.isString(clazz)) {
+	value += "\"" + obj + "\"";
+	editable = true;
+      } else {
+	value += (obj == null ? "null" : obj.toString());
+      }
+      name += value;
+    } else if(obj == null) {
+      name += "=null";
+    }
+
+    super.setUserObject(name);
+  }
+
+  public void add(ObjectNode child){
+    child.setAdministrable(isAdministrable);
+    if(! isRoot){
+      child.setControllable(isControllable);
+    }
+
+    super.add(child);
+  }
+
+  public Object getEntryTop() {
+    ObjectNode snode = this;
+    do {
+      snode = (ObjectNode) snode.getParent();
+    } while(! snode.isEntryTop());
+
+    return snode.getObject();
+  }
+
+  protected boolean isEntryTop() {
+    return isEntryTop;
+  }
+
+  public void setObjectRecursive() throws NoSuchFieldException, IllegalAccessException {
+    ObjectNode pnode = this;
+    do {
+      pnode = (ObjectNode) pnode.getParent();
+      Object pobj = pnode.getObject();
+      // Needs to think about array modifications
+      Field f = pobj.getClass().getField(fieldName);
+      f.set(pobj, obj);
+    } while(! pnode.isEntryTop());
+  }
+
+  public String getTitle() {
+    return name;
+  }
+
+  public Icon getIcon() {
+    /*
+      if(isAdministrable){
+      if(isControllable){
+      if(isLeaf)	    return icons[3];
+      else		    return icons[0];
+      } else {
+      if(isLeaf)	    return icons[5];
+      else		    return icons[2];
+      }
+      } else {
+      if(isLeaf)	    return icons[4];
+      else		    return icons[1];
+      }
+    */
+    if(isAdministrable && isControllable)
+      if(isLeaf)	return icons[3];
+      else		return icons[0];
+    else
+      if(isLeaf)	return icons[4];
+      else		return icons[1];
+  }
+
+
+  // Overwrite
+  public void setUserObject(Object obj){
+    if(obj instanceof String)
+      name = (String) obj;
+
+    super.setUserObject(obj);
+  }
+
+  public Object getUserObject(){
+    return name;
+  }
+
+  public String toString() {
+    return name;
+  }
+
+  public String getFieldName() {
+    return fieldName;
+  }
+
+  public Object getObject() {
+    return obj;
+  }
+
+  public boolean isEditable() {
+    return editable;
+  }
+
+  public boolean isAdministrable() {
+    return isAdministrable;
+  }
+
+  public void setAdministrable(boolean val) {
+    isAdministrable = val;
+  }
+
+  public boolean isControllable() {
+    return isControllable;
+  }
+
+  public void setControllable(boolean val) {
+    isControllable = val;
+  }
+
+  public Object setValue(Object val) throws NumberFormatException {
+    String clazzName = clazz.getName();
+    Object newObj = null;
+
+    if(val instanceof String || val == null) {
+      String sval = (String) val;
+      if(clazzName.equals("java.lang.Integer"))
+	newObj = new Integer(sval);
+      else if(clazzName.equals("java.lang.Boolean"))
+	newObj = new Boolean(sval);
+      else if(clazzName.equals("java.lang.Byte"))
+	newObj = new Byte(sval);
+      else if(clazzName.equals("java.lang.Character"))
+	newObj = new Character(sval.charAt(0));
+      else if(clazzName.equals("java.lang.Double"))
+	newObj = new Double(sval);
+      else if(clazzName.equals("java.lang.Float"))
+	newObj = new Float(sval);
+      else if(clazzName.equals("java.lang.Long"))
+	newObj = new Long(sval);
+      else if(clazzName.equals("java.lang.String"))
+	newObj = new String(sval);	// clone 
+    } else if(val.getClass().equals(obj.getClass())) {
+      // same class type
+      newObj = val;
+    }
+
+    Object oldObj = obj;
+    obj = newObj;
+
+    setNodeName();
+
+    return oldObj;
+  }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/7ff8b15a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java
----------------------------------------------------------------------
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java
new file mode 100644
index 0000000..6af743b
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceBrowser.java
@@ -0,0 +1,282 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.browser;
+
+import net.jini.core.lookup.ServiceItem;
+import net.jini.core.lookup.ServiceRegistrar;
+import net.jini.core.lookup.ServiceMatches;
+import net.jini.core.lookup.ServiceTemplate;
+import net.jini.core.entry.Entry;
+import net.jini.lookup.ui.factory.JFrameFactory;
+import net.jini.lookup.entry.UIDescriptor;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.logging.Level;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.event.PopupMenuEvent;
+
+/**
+ * A browser utility to browse entries in a specified space.
+ *
+ * @author Sun Microsystems, Inc.
+ *
+ * @version 0.2 06/04/98
+ *
+ */
+class ServiceBrowser extends JFrame {
+  private Browser browser;
+  private AttributePanel attrPanel;
+  private final static int MINIMUM_WINDOW_WIDTH = 320;
+
+  public ServiceBrowser(ServiceItem item,
+			ServiceRegistrar registrar,
+			Browser browser)
+  {
+    super("ServiceItem Browser");
+
+    this.browser = browser;
+    // init main components
+    attrPanel = new AttributePanel(item, registrar);
+
+    // add menu and attr panel
+    getContentPane().setLayout(new BorderLayout());
+    getContentPane().add(new BrowserMenuBar(), "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);
+  }
+
+
+  class BrowserMenuBar extends JMenuBar {
+    public BrowserMenuBar() {
+      JMenuItem mitem;
+
+      // "File" Menu
+      JMenu fileMenu = (JMenu) add(new JMenu("File"));
+      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) {
+	  ServiceBrowser.this.setVisible(false);
+	}
+      }));
+    }
+  }
+
+  class AttributePanel extends EntryTreePanel {
+    private ServiceItem item;
+    private ServiceRegistrar registrar;
+
+    public AttributePanel(ServiceItem item, ServiceRegistrar registrar) {
+      super(false);	// Entries are not editable.
+
+      this.item = item;
+      this.registrar = registrar;
+
+      tree.addMouseListener(browser.wrap(new ServiceBrowser.MouseReceiver(item,
+					 uiDescriptorPopup())));
+
+      refreshPanel();
+    }
+
+    protected Entry[] getEntryArray() {
+      try{
+	ServiceMatches matches = registrar.lookup(new ServiceTemplate(item.serviceID,
+								      new Class[] { item.service.getClass() },
+								      new Entry[] {}),
+						  10);
+	if(matches.totalMatches != 1)
+	  Browser.logger.log(Level.INFO, "unexpected lookup matches: {0}",
+			     new Integer(matches.totalMatches));
+	else
+	  return matches.items[0].attributeSets;
+      } catch (Throwable t) {
+	Browser.logger.log(Level.INFO, "lookup failed", t);
+      }
+      return null;
+    }
+  }
+
+    // provides support for ServiceUI
+    public class UIDescriptorPopup extends JPopupMenu implements ActionListener,
+	PopupMenuListener {
+
+	protected transient JMenuItem showUIItem;
+	protected transient ServiceItem serviceItem;
+
+	public UIDescriptorPopup() {
+	    super();
+
+	    showUIItem = new JMenuItem("Show UI");
+
+	    showUIItem.addActionListener(this);
+	    showUIItem.setActionCommand("showUI");
+	    add(showUIItem);
+
+	    addPopupMenuListener(this);
+	    setOpaque(true);
+	    setLightWeightPopupEnabled(true);
+	}
+
+	public void actionPerformed(ActionEvent anEvent) {
+
+	    UIDescriptor uiDescriptor = getSelectedUIDescriptor();
+
+	    if (uiDescriptor == null) {
+		return;
+	    }
+
+	    try {
+		JFrameFactory uiFactory = (JFrameFactory)
+		    uiDescriptor.getUIFactory(Thread.currentThread().getContextClassLoader());
+		JFrame frame = uiFactory.getJFrame(serviceItem);
+
+		frame.validate();
+		frame.setVisible(true);
+	    }
+	    catch (Exception e) {
+		e.printStackTrace();
+
+		return;
+	    }
+	}
+
+	public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+	}
+
+	public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
+	}
+
+	public void popupMenuCanceled(PopupMenuEvent ev) {
+	}
+
+	public void setServiceItem(ServiceItem anItem) {
+	    serviceItem = anItem;
+	}
+    }
+
+    class MouseReceiver extends MouseAdapter {
+
+	private ServiceBrowser.UIDescriptorPopup popup;
+	private ServiceItem serviceItem;
+
+	public MouseReceiver(ServiceItem aServiceItem,
+			ServiceBrowser.UIDescriptorPopup popup) {
+		this.popup = popup;
+		serviceItem = aServiceItem;
+	}
+
+	public void mouseReleased(MouseEvent ev) {
+
+	    higlightSelection(ev);
+
+	    if (!ev.isPopupTrigger()) {
+		return;
+	    }
+
+	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+	    if (selectedDescriptor == null) {
+		return;
+	    }
+
+	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+		return;
+	    }
+
+	    popup.setServiceItem(serviceItem);
+	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
+	}
+
+	public void mousePressed(MouseEvent ev) {
+
+	    higlightSelection(ev);
+
+	    if (!ev.isPopupTrigger()) {
+		return;
+	    }
+
+	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+	    if (selectedDescriptor == null) {
+		return;
+	    }
+
+	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+		return;
+	    }
+
+	    popup.setServiceItem(serviceItem);
+	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
+	}
+    }
+
+    private UIDescriptor getSelectedUIDescriptor() {
+
+	ObjectNode selectedNode =
+		(ObjectNode) attrPanel.tree.getLastSelectedPathComponent();
+
+	if (selectedNode == null) {
+	    return null;
+	}
+
+	Object selectedObject = selectedNode.getObject();
+
+	try {
+	    return (UIDescriptor) selectedObject;
+	}
+	catch (ClassCastException e) {
+	    return null;
+	}
+    }
+
+    private void higlightSelection(MouseEvent anEvent) {
+	attrPanel.tree.setSelectionPath(attrPanel.tree.getPathForLocation(
+	    anEvent.getX(), anEvent.getY()));
+    }
+
+    private ServiceBrowser.UIDescriptorPopup uiDescriptorPopup() {
+	return new ServiceBrowser.UIDescriptorPopup();
+    }
+}

http://git-wip-us.apache.org/repos/asf/river-container/blob/7ff8b15a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java
----------------------------------------------------------------------
diff --git a/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java
new file mode 100644
index 0000000..ee972da
--- /dev/null
+++ b/browser/src/main/java/org/apache/river/container/examples/browser/ServiceEditor.java
@@ -0,0 +1,1213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.river.container.examples.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.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+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.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.tree.TreePath;
+import net.jini.admin.JoinAdmin;
+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.ServiceEvent;
+import net.jini.core.lookup.ServiceItem;
+import net.jini.core.lookup.ServiceMatches;
+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.lookup.DiscoveryAdmin;
+import net.jini.lookup.entry.UIDescriptor;
+import net.jini.lookup.ui.factory.JFrameFactory;
+import net.jini.security.TrustVerifier;
+import net.jini.security.proxytrust.ServerProxyTrust;
+
+/**
+ * ServiceEditor 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 admin;
+  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,
+		       Object admin,
+		       ServiceRegistrar registrar,
+		       Browser browser)
+    {
+    super("ServiceItem Editor");
+    
+    this.item = item;
+    this.admin = admin;
+    this.registrar = registrar;
+    this.browser = browser;
+
+    // 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();
+    }
+
+    addWindowListener(browser.wrap(new WindowAdapter() {
+	public void windowClosing(WindowEvent e) {
+	  cleanup();
+	}
+      }));
+    // 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);
+  }
+
+  void cleanup() {
+    // cancel lease
+    cancelNotify();
+    // release resources and close all child frames
+    dispose();
+    receiver.unexport();
+  }
+
+  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(),
+					     false, false));
+      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(((ServiceEvent) ev).getTransition());
+	  }
+	}
+      }));
+    }
+
+    public TrustVerifier getProxyVerifier() {
+      return new BasicProxyTrustVerifier(proxy);
+    }
+
+    void unexport() {
+	exporter.unexport(true);
+    }
+  }
+
+  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) {
+	  cleanup();
+	}
+      }));
+
+      // "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();
+	}
+      }));
+      if(! (admin instanceof JoinAdmin))
+	mitem.setEnabled(false);
+      mitem = (JMenuItem) editMenu.add(new JMenuItem("Remove Attribute"));
+      mitem.addActionListener(browser.wrap(new ActionListener() {
+	public void actionPerformed(ActionEvent ev) {
+	  attrPanel.removeAttr();
+	}
+      }));
+      if(! (admin instanceof JoinAdmin))
+	mitem.setEnabled(false);
+
+      // "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();
+	}
+      }));
+      if(! (admin instanceof JoinAdmin))
+	mitem.setEnabled(false);
+
+      // 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();
+	}
+      }));
+      if(! (admin instanceof JoinAdmin))
+	mitem.setEnabled(false);
+
+      // 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(! (admin 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) admin).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) admin).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(! (admin 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) admin).destroy();
+	      cleanup();
+	    } catch (Throwable t) {
+	      logger.log(Level.INFO, "service destroy failed", t);
+	      JOptionPane.showMessageDialog(ServiceEditor.this,
+					    t.getMessage(),
+					    t.getClass().getName(),
+					    JOptionPane.ERROR_MESSAGE);
+	    }
+	}
+      }));
+      if(! (admin instanceof DestroyAdmin))
+	mitem.setEnabled(false);
+    }
+  }
+
+  class AttributeTreePanel extends EntryTreePanel {
+
+    public AttributeTreePanel() {
+      super(admin instanceof JoinAdmin);
+
+      if(admin instanceof JoinAdmin) {
+	tree.addMouseListener(browser.wrap(new DoubleClicker(this)));
+      }
+      tree.addMouseListener(browser.wrap(new MouseReceiver(item, uiDescriptorPopup())));
+
+      refreshPanel();
+    }
+
+    protected Entry[] getEntryArray() {
+      if(admin instanceof JoinAdmin) {
+	try {
+	  item.attributeSets = ((JoinAdmin) admin).getLookupAttributes();
+	} catch (Throwable t) {
+	  logger.log(Level.INFO, "obtaining attributes failed", t);
+	}
+      } else {
+	try{
+	  ServiceMatches matches = registrar.lookup(stmpl, 1);
+	  if(matches.totalMatches != 1)
+	    Browser.logger.log(Level.INFO, "unexpected lookup matches: {0}",
+			       new Integer(matches.totalMatches));
+	  else
+	    item.attributeSets = matches.items[0].attributeSets;
+	} catch (Throwable t) {
+	  Browser.logger.log(Level.INFO, "lookup failed", t);
+	}
+      }
+      return item.attributeSets;
+    }
+
+    protected void receiveNotify(int transition) {
+
+      if (browser.isAutoConfirm()) {
+        if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
+          cleanup();
+        else
+          refreshPanel();
+
+        return;
+      }
+
+	String[] msg =
+	    (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH) ?
+	    new String[]{
+		"Service has been removed from lookup service.",
+		"Do you want to close the service editor window ?"} :
+	    new String[]{
+		"Attributes have been modified by another client or the service itself.",
+		"Do you want to refresh the attributes ?"};
+      int result = JOptionPane.showConfirmDialog(AttributeTreePanel.this,
+						 msg,
+						 "Query",
+						 JOptionPane.YES_NO_OPTION);
+	
+      if(result == JOptionPane.YES_OPTION) {
+	  if (transition == ServiceRegistrar.TRANSITION_MATCH_NOMATCH)
+	      cleanup();
+	  else
+	      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();
+
+	  ((JoinAdmin) admin).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();
+
+	    ((JoinAdmin) admin).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();
+
+	  ((JoinAdmin) admin).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() {
+	if (!(admin instanceof JoinAdmin)) {
+	    return;
+	}
+
+      try {
+	String[] groups = ((JoinAdmin) admin).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 {
+	((JoinAdmin) admin).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 {
+	((JoinAdmin) admin).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) admin).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) admin).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) admin).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() {
+	if (!(admin instanceof JoinAdmin)) {
+	    return;
+	}
+
+      try {
+	LookupLocator[] locators = ((JoinAdmin) admin).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 {
+	((JoinAdmin) admin).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 {
+	((JoinAdmin) admin).removeLookupLocators(locs);
+      } catch (Throwable t) {
+	logger.log(Level.INFO, "removing locators failed", t);
+      }
+    }
+  }
+
+    // provides support for ServiceUI
+    public class UIDescriptorPopup extends JPopupMenu implements ActionListener,
+	PopupMenuListener {
+
+	protected transient JMenuItem showUIItem;
+	protected transient ServiceItem serviceItem;
+
+	public UIDescriptorPopup() {
+	    super();
+
+	    showUIItem = new JMenuItem("Show UI");
+
+	    showUIItem.addActionListener(this);
+	    showUIItem.setActionCommand("showUI");
+	    add(showUIItem);
+
+	    addPopupMenuListener(this);
+	    setOpaque(true);
+	    setLightWeightPopupEnabled(true);
+	}
+
+	public void actionPerformed(ActionEvent anEvent) {
+
+	    UIDescriptor uiDescriptor = getSelectedUIDescriptor();
+
+	    if (uiDescriptor == null) {
+		return;
+	    }
+
+	    try {
+		JFrameFactory uiFactory = (JFrameFactory)
+		    uiDescriptor.getUIFactory(Thread.currentThread().getContextClassLoader());
+		JFrame frame = uiFactory.getJFrame(serviceItem);
+
+		frame.validate();
+		frame.setVisible(true);
+	    }
+	    catch (Exception e) {
+		e.printStackTrace();
+
+		return;
+	    }
+	}
+
+	public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
+	}
+
+	public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
+	}
+
+	public void popupMenuCanceled(PopupMenuEvent ev) {
+	}
+
+	public void setServiceItem(ServiceItem anItem) {
+	    serviceItem = anItem;
+	}
+    }
+
+    class MouseReceiver extends MouseAdapter {
+
+	private ServiceEditor.UIDescriptorPopup popup;
+	private ServiceItem serviceItem;
+
+	public MouseReceiver(ServiceItem aServiceItem,
+			ServiceEditor.UIDescriptorPopup popup) {
+		this.popup = popup;
+		serviceItem = aServiceItem;
+	}
+
+	public void mouseReleased(MouseEvent ev) {
+
+	    higlightSelection(ev);
+
+	    if (!ev.isPopupTrigger()) {
+		return;
+	    }
+
+	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+	    if (selectedDescriptor == null) {
+		return;
+	    }
+
+	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+		return;
+	    }
+
+	    popup.setServiceItem(serviceItem);
+	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
+	}
+
+	public void mousePressed(MouseEvent ev) {
+
+	    higlightSelection(ev);
+
+	    if (!ev.isPopupTrigger()) {
+		return;
+	    }
+
+	    UIDescriptor selectedDescriptor = getSelectedUIDescriptor();
+
+	    if (selectedDescriptor == null) {
+		return;
+	    }
+
+	    if (!"javax.swing".equals(selectedDescriptor.toolkit)) {
+		return;
+	    }
+
+	    popup.setServiceItem(serviceItem);
+	    popup.show(ev.getComponent(), ev.getX(), ev.getY());
+	}
+    }
+
+    private UIDescriptor getSelectedUIDescriptor() {
+
+	ObjectNode selectedNode =
+		(ObjectNode) attrPanel.tree.getLastSelectedPathComponent();
+
+	if (selectedNode == null) {
+	    return null;
+	}
+
+	Object selectedObject = selectedNode.getObject();
+
+	try {
+	    return (UIDescriptor) selectedObject;
+	}
+	catch (ClassCastException e) {
+	    return null;
+	}
+    }
+
+    private void higlightSelection(MouseEvent event) {
+	attrPanel.tree.setSelectionPath(attrPanel.tree.getPathForLocation(
+	    event.getX(), event.getY()));
+    }
+
+    private ServiceEditor.UIDescriptorPopup uiDescriptorPopup() {
+	return new ServiceEditor.UIDescriptorPopup();
+    }
+}