Committed the initial version of the UPnP Tester bundle (FELIX-52).
It requires org.osgi.core, org.osgi.compendium and org.apache.felix.upnp.extra.
The tester acts as Generic Control Point useful to monitor the registered UPnPDevice services.



git-svn-id: https://svn.apache.org/repos/asf/incubator/felix/trunk@391455 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Activator.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Activator.java
new file mode 100644
index 0000000..4a0a8f7
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Activator.java
@@ -0,0 +1,50 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.felix.upnp.tester;
+
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * @author Stefano "Kismet" Lenzi
+ * @author Francesco Furfari 
+ * 
+ */
+public class Activator implements BundleActivator {
+
+	public static BundleContext context;
+    
+	private ControlPoint cp;
+	
+	/**
+	 * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+	 */
+	public void start(BundleContext context) throws Exception {
+		Activator.context = context;
+        cp = new ControlPoint();
+	}
+
+	/**
+	 * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+	 */
+	public void stop(BundleContext context) throws Exception {
+		cp.close();
+		Activator.context=null;
+	}
+}
diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/ControlPoint.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/ControlPoint.java
new file mode 100644
index 0000000..cf9bdd7
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/ControlPoint.java
@@ -0,0 +1,376 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester;

+

+

+import java.awt.BorderLayout;

+import java.awt.Component;

+import java.awt.Dimension;

+import java.awt.event.ActionEvent;

+import java.awt.event.ActionListener;

+import java.awt.event.KeyEvent;

+import java.awt.event.WindowAdapter;

+import java.awt.event.WindowEvent;

+import java.net.URL;

+

+import javax.swing.AbstractAction;

+import javax.swing.ButtonGroup;

+import javax.swing.ImageIcon;

+import javax.swing.JFrame;

+import javax.swing.JMenu;

+import javax.swing.JMenuBar;

+import javax.swing.JMenuItem;

+import javax.swing.JPanel;

+import javax.swing.JPopupMenu;

+import javax.swing.JRadioButtonMenuItem;

+import javax.swing.JSplitPane;

+import javax.swing.event.PopupMenuEvent;

+import javax.swing.event.PopupMenuListener;

+

+import org.apache.felix.upnp.tester.discovery.DriverProxy;

+import org.apache.felix.upnp.tester.discovery.RootDeviceListener;

+import org.apache.felix.upnp.tester.gui.LogPanel;

+import org.apache.felix.upnp.tester.gui.PropertiesViewer;

+import org.apache.felix.upnp.tester.gui.TreeViewer;

+import org.apache.felix.upnp.tester.gui.Util;

+import org.osgi.framework.BundleException;

+ 

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class ControlPoint implements PopupMenuListener {

+	RootDeviceListener x;

+	RootDeviceListener listener;

+	TreeViewer viewer;

+	PropertiesViewer properties;

+	JFrame frame;

+	

+	

+	public ControlPoint() {

+		frame = new JFrame("OSGi UPnP Device Tester");

+       try {

+            URL eventIconUrl = Util.class.getResource("IMAGES/logo.gif");           

+            ImageIcon icon=  new ImageIcon(eventIconUrl,"logo");

+            frame.setIconImage(icon.getImage());

+       }

+        catch (Exception ex){

+                System.out.println("Resource: IMAGES/logo.gif not found : " + ex.toString());

+        }

+		//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

+        

+		frame.addWindowListener(new WindowAdapter(){

+			public void windowClosing(WindowEvent e){

+				try {

+					Activator.context.getBundle().stop();

+				} catch (BundleException ex) {

+					ex.printStackTrace();

+				}

+			}

+

+		});       

+		frame.setBounds(0,0,300,200);

+        

+        

+        doMenuBar(frame);

+		doControlPanel();

+        Mediator.setControlPoint(this);

+        Mediator.setMainFrame(frame);

+        Mediator.setRootDeviceListener(listener);

+		listener = new RootDeviceListener();

+        Mediator.setRootDeviceListener(listener);

+		listener.setDeviceNodeListener(viewer);

+		

+		frame.pack();

+		frame.setVisible(true);

+        

+		DriverProxy driverProxy = new DriverProxy();

+        Mediator.setDriverProxy(driverProxy);

+        

+		listener.activate();

+	}

+	

+	public void close(){

+		listener.deactive();

+		frame.dispose();

+        Mediator.getDriverProxy().close();           

+	}

+	

+	private void doControlPanel(){

+		JPanel panel = new JPanel(new BorderLayout());

+		viewer = new TreeViewer();

+		viewer.setPreferredSize(new Dimension(180,450));

+		properties = new PropertiesViewer();

+		Mediator.setPropertiesViewer(properties);

+	

+		JSplitPane treeSplitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,viewer,properties);

+		JPanel logPanel = new LogPanel();

+		logPanel.setPreferredSize(new Dimension(180,100));

+		JSplitPane logSplitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT,treeSplitter,logPanel);

+		panel.add(logSplitter);

+		frame.getContentPane().add(panel);

+		

+		

+	}

+	

+      /////////////////////////// MENU /////////////////////////////

+    JMenu searchMenu,loggerMenu,cyberMenu;  

+    public void doMenuBar(JFrame frame) {

+

+        JMenuBar menuBar = new JMenuBar();

+        

+        //////////////// FILE

+        JMenu file_menu = new JMenu("File");

+        file_menu.setMnemonic(KeyEvent.VK_F);

+

+        searchMenu = new JMenu("Search");

+        final String ALL_DEVICE = "ssdp:all";

+        final String ROOT_DEVICE = "upnp:rootdevice";

+        searchMenu.setMnemonic(KeyEvent.VK_L);

+        searchMenu.setEnabled(false);

+        AbstractAction searchAction = new AbstractAction(){

+            public void actionPerformed(ActionEvent e) {

+                DriverProxy controller = Mediator.getDriverProxy();

+                if (e.getActionCommand().equals(ALL_DEVICE))

+                    controller.doSearch(ALL_DEVICE);

+                else if (e.getActionCommand().equals(ROOT_DEVICE))

+                    controller.doSearch(ROOT_DEVICE);

+            }

+        };

+        

+        JMenuItem rootDeviceItem = new JMenuItem("Root Devices");

+        rootDeviceItem.setMnemonic(KeyEvent.VK_R);

+        rootDeviceItem.addActionListener(searchAction);

+        rootDeviceItem.setActionCommand(ROOT_DEVICE);

+        searchMenu.add(rootDeviceItem);

+        

+        JMenuItem allDeviceItem = new JMenuItem("All Devices");

+        allDeviceItem .setMnemonic(KeyEvent.VK_A);

+        allDeviceItem .addActionListener(searchAction);

+        allDeviceItem .setActionCommand(ALL_DEVICE);

+        searchMenu.add(allDeviceItem);

+        

+        

+        JMenuItem checkIncompleteItem = new JMenuItem("Print Pending Devices");

+        checkIncompleteItem.setMnemonic(KeyEvent.VK_I);

+        checkIncompleteItem.addActionListener(new ActionListener() {

+                public void actionPerformed(ActionEvent e) {

+                    Mediator.getRootDeviceListener().checkIncompleteDevice();

+                }

+            });

+        

+        JMenuItem checkErrataItem = new JMenuItem("Check Errata UPnPDevices");

+        checkErrataItem.setMnemonic(KeyEvent.VK_E);

+        checkErrataItem.addActionListener(new ActionListener() {

+                public void actionPerformed(ActionEvent e) {                    

+                    RootDeviceListener.checkErrataDevice();}

+            });

+        

+        

+        loggerMenu = new JMenu("Domoware Logger");

+        final String NO_LOGGING ="No Logging";

+        final String ERROR ="Error";

+        final String WARNING ="Warning";

+        final String INFO ="Info";

+        final String DEBUG ="Debug";

+

+        loggerMenu.getPopupMenu().addPopupMenuListener(this);

+        loggerMenu.setMnemonic(KeyEvent.VK_L);

+        loggerMenu.setEnabled(false);

+        AbstractAction loggerAction = new AbstractAction(){

+            public void actionPerformed(ActionEvent e) {

+                DriverProxy controller = Mediator.getDriverProxy();

+                if (e.getActionCommand().equals(NO_LOGGING))

+                    controller.setLogLevel(0);

+                else if (e.getActionCommand().equals(ERROR))

+                    controller.setLogLevel(1);

+                else if (e.getActionCommand().equals(WARNING))

+                    controller.setLogLevel(2);

+                else if (e.getActionCommand().equals(INFO))

+                    controller.setLogLevel(3);

+                else if (e.getActionCommand().equals(DEBUG))

+                    controller.setLogLevel(4);

+            }

+        };

+       

+        ButtonGroup group = new ButtonGroup();

+        

+        JRadioButtonMenuItem rbMenuItem = new JRadioButtonMenuItem(NO_LOGGING);

+        rbMenuItem.setSelected(true);

+        rbMenuItem.setMnemonic(KeyEvent.VK_N);

+        rbMenuItem.setActionCommand(NO_LOGGING);

+        rbMenuItem.addActionListener(loggerAction);

+        group.add(rbMenuItem);

+        loggerMenu.add(rbMenuItem);

+        loggerMenu.addSeparator();

+        

+        rbMenuItem = new JRadioButtonMenuItem(ERROR);

+        rbMenuItem.setMnemonic(KeyEvent.VK_E);

+        rbMenuItem.setActionCommand(ERROR);

+        rbMenuItem.addActionListener(loggerAction);

+        group.add(rbMenuItem);

+        loggerMenu.add(rbMenuItem);

+

+        rbMenuItem = new JRadioButtonMenuItem(WARNING);

+        rbMenuItem.setMnemonic(KeyEvent.VK_W);

+        rbMenuItem.setActionCommand(WARNING);

+        rbMenuItem.addActionListener(loggerAction);

+        group.add(rbMenuItem);

+        loggerMenu.add(rbMenuItem);

+        

+        rbMenuItem = new JRadioButtonMenuItem(INFO);

+        rbMenuItem.setMnemonic(KeyEvent.VK_I);

+        rbMenuItem.setActionCommand(INFO);

+        rbMenuItem.addActionListener(loggerAction);

+        group.add(rbMenuItem);

+        loggerMenu.add(rbMenuItem);

+        

+        rbMenuItem = new JRadioButtonMenuItem(DEBUG);

+        rbMenuItem.setMnemonic(KeyEvent.VK_D);

+        rbMenuItem.setActionCommand(DEBUG);

+        rbMenuItem.addActionListener(loggerAction);

+        group.add(rbMenuItem);

+        loggerMenu.add(rbMenuItem);

+

+        final String ON ="On";

+        final String OFF ="Off";

+        cyberMenu = new JMenu("Cyber Debugger");

+        cyberMenu.getPopupMenu().addPopupMenuListener(this);

+        cyberMenu.setMnemonic(KeyEvent.VK_C);

+        cyberMenu.setEnabled(false);

+        AbstractAction cyberAction = new AbstractAction(){

+            public void actionPerformed(ActionEvent e) {

+                DriverProxy controller = Mediator.getDriverProxy();

+                if (e.getActionCommand().equals(ON))

+                    controller.setCyberDebug(true);

+                else if (e.getActionCommand().equals(OFF))

+                    controller.setCyberDebug(false);

+            }

+        };

+

+       

+        ButtonGroup cyberGroup = new ButtonGroup();

+        rbMenuItem = new JRadioButtonMenuItem(ON);

+        rbMenuItem.setSelected(true);

+        rbMenuItem.setMnemonic(KeyEvent.VK_O);

+        rbMenuItem.setActionCommand(ON);

+        rbMenuItem.addActionListener(cyberAction);

+        cyberGroup.add(rbMenuItem);

+        cyberMenu.add(rbMenuItem);

+

+        rbMenuItem = new JRadioButtonMenuItem(OFF);

+        rbMenuItem.setMnemonic(KeyEvent.VK_F);

+        rbMenuItem.setActionCommand(OFF);

+        rbMenuItem.addActionListener(cyberAction);

+        cyberGroup.add(rbMenuItem);

+        cyberMenu.add(rbMenuItem);

+        

+        /*

+        JMenuItem clearSubscriptionItem = new JMenuItem("Clear Subscriptions");

+        clearSubscriptionItem.setMnemonic(KeyEvent.VK_S);

+        clearSubscriptionItem.addActionListener(new ActionListener() {

+                public void actionPerformed(ActionEvent e) {

+// to do

+                    }

+            });

+        */

+        

+        JMenuItem exitItem = new JMenuItem("Exit");

+        exitItem.setMnemonic(KeyEvent.VK_X);

+        exitItem.addActionListener(new ActionListener() {

+                public void actionPerformed(ActionEvent e) {

+                    try {

+                        Activator.context.getBundle().stop();

+                    }catch (Exception ex){

+                        ex.printStackTrace();

+                    }

+                }

+            });

+        

+

+        file_menu.add(searchMenu);

+        file_menu.addSeparator();

+        file_menu.add(loggerMenu);

+        file_menu.add(cyberMenu);

+        file_menu.addSeparator();

+        file_menu.add(checkIncompleteItem);

+        file_menu.add(checkErrataItem);

+        //file_menu.addSeparator();

+        //file_menu.add(clearSubscriptionItem);

+        file_menu.addSeparator();

+        file_menu.add(exitItem);

+

+        menuBar.add(file_menu);                   

+        frame.setJMenuBar(menuBar);

+

+    }

+

+ 

+    public void enableMenus(boolean driverAvailable,int logLevel,boolean cyberDebug) {

+        searchMenu.setEnabled(driverAvailable);

+        Component[] items = searchMenu.getPopupMenu().getComponents();

+        for (int i=0;i < items.length;i++)

+            items[i].setEnabled(driverAvailable);

+

+        loggerMenu.setEnabled(driverAvailable);

+        items = loggerMenu.getPopupMenu().getComponents();

+        for (int i=0;i < items.length;i++)

+            items[i].setEnabled(driverAvailable);

+        if (driverAvailable){

+                ((JRadioButtonMenuItem)items[logLevel>0?logLevel+1:0]).setSelected(true);

+        }

+        cyberMenu.setEnabled(driverAvailable);           

+        items = cyberMenu.getPopupMenu().getComponents();

+        for (int i=0;i < items.length;i++)

+            items[i].setEnabled(driverAvailable);

+        if (driverAvailable){

+            if (cyberDebug)

+                ((JRadioButtonMenuItem)items[0]).setSelected(true);

+            else

+                ((JRadioButtonMenuItem)items[1]).setSelected(true);

+        }

+   }

+

+    public void popupMenuCanceled(PopupMenuEvent e) { }

+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { }

+

+    public void popupMenuWillBecomeVisible(PopupMenuEvent e) {

+        JPopupMenu loggerPopup = loggerMenu.getPopupMenu();

+        JPopupMenu cyberPopup = cyberMenu.getPopupMenu();

+        if (e.getSource()==loggerPopup){

+            int logLevel = Mediator.getDriverProxy().getLogLevel();

+            Component[] items = loggerPopup.getComponents();

+            ((JRadioButtonMenuItem)items[logLevel>0?logLevel+1:0]).setSelected(true);          

+        }

+        else if (e.getSource()==cyberPopup){

+            boolean cyberDebug = Mediator.getDriverProxy().getCyberDebug();

+            Component[] items = cyberPopup.getComponents();

+            if (cyberDebug)

+                ((JRadioButtonMenuItem)items[0]).setSelected(true);

+            else

+                ((JRadioButtonMenuItem)items[1]).setSelected(true);            

+        }

+       

+        

+    }

+

+

+

+	

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Mediator.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Mediator.java
new file mode 100644
index 0000000..7a7c3da
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/Mediator.java
@@ -0,0 +1,90 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester;

+

+

+import javax.swing.*;

+

+import org.apache.felix.upnp.tester.discovery.*;

+import org.apache.felix.upnp.tester.gui.*;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public  class Mediator {

+	static PropertiesViewer props;

+	static JTree tree;

+    static JFrame frame; 

+    static DriverProxy driverProxy;

+    static RootDeviceListener rootDeviceListener; 

+    static ControlPoint controlPoint; 

+    static TreeViewer treeViewer; 

+	

+

+	public static void setPropertiesViewer(PropertiesViewer props){

+		Mediator.props=props;

+	}

+	public static PropertiesViewer getPropertiesViewer(){

+		return props;

+	}

+	

+    public static void setUPnPDeviceTree(JTree tree){

+        Mediator.tree=tree;

+    }

+    public static JTree getUPnPDeviceTree(){

+        return tree;

+    }

+    

+    public static void setTreeViewer(TreeViewer treeViewer){

+        Mediator.treeViewer=treeViewer;

+    }

+    public static TreeViewer getTreeViewer(){

+        return treeViewer;

+    }

+	

+    public static void setMainFrame(JFrame frame){

+        Mediator.frame=frame;

+    }

+    public static JFrame getMainFrame(){

+        return frame;

+    }

+    public static void setControlPoint(ControlPoint controlPoint){

+        Mediator.controlPoint=controlPoint;

+    }

+    public static ControlPoint getControlPoint(){

+        return controlPoint;

+    }

+    

+    public static void setDriverProxy(DriverProxy driverProxy) {

+        Mediator.driverProxy = driverProxy;        

+    }

+    

+    public static DriverProxy getDriverProxy(){

+        return driverProxy;

+    }

+    

+    public static void setRootDeviceListener(RootDeviceListener listener) {

+        Mediator.rootDeviceListener = listener;        

+    }

+    

+    public static RootDeviceListener getRootDeviceListener(){

+        return rootDeviceListener;

+    }

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/UPnPSubscriber.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/UPnPSubscriber.java
new file mode 100644
index 0000000..27ae36f
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/UPnPSubscriber.java
@@ -0,0 +1,84 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester;

+

+import java.util.*;

+

+import org.osgi.framework.*;

+import org.osgi.service.upnp.*;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ * 

+ */

+public class UPnPSubscriber {

+	private BundleContext context;

+	private UPnPEventListener listener;

+	private HashMap hash;

+	

+	private class Subscription implements UPnPEventListener{

+	    ServiceRegistration registration;

+	    

+	    Subscription(String keys){

+	 		try {

+				Filter filter = context.createFilter(keys);

+				Properties props = new Properties();

+				props.put(UPnPEventListener.UPNP_FILTER, filter);

+				registration = context.registerService(UPnPEventListener.class.getName(), this, props);

+			}catch (Exception ex){

+				System.out.println(ex);

+			}

+	    }

+	    

+	    public void unsubscribe(){

+			registration.unregister();

+	    }

+	    

+        public void notifyUPnPEvent(String arg0, String arg1, Dictionary arg2) {

+            listener.notifyUPnPEvent( arg0,  arg1,  arg2);

+        }

+	}

+	

+	

+	public UPnPSubscriber(BundleContext context,UPnPEventListener listener){

+		this.context = context;

+		this.listener = listener;

+		hash = new HashMap();

+	}

+	

+	public void subscribe(String deviceId, String serviceId){

+		String keys = "(&(" + UPnPDevice.ID + "="+ deviceId + ")(" + UPnPService.ID + "=" + serviceId + "))";

+		if (hash.get(keys) == null){

+		    hash.put(keys, new Subscription(keys));

+		}

+	}

+	

+	public void unsubscribe(String deviceId, String serviceId){

+		String keys = "(&(" + UPnPDevice.ID + "="+ deviceId + ")(" + UPnPService.ID + "=" + serviceId + "))";

+		if (hash.containsKey(keys)) {

+		    Subscription subscription = (Subscription) hash.get(keys);

+		    subscription.unsubscribe();

+			hash.remove(keys);

+		}

+	}

+

+

+ 	

+

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceChangeListener.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceChangeListener.java
new file mode 100644
index 0000000..5999ddd
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceChangeListener.java
@@ -0,0 +1,31 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.felix.upnp.tester.discovery;
+
+/**
+ * @author Stefano "Kismet" Lenzi 
+ * @author Francesco Furfari 
+ *  
+ */
+public interface DeviceChangeListener {
+	
+	public void addedDevice(DeviceNode dn);
+	
+	public void removedDevice(DeviceNode dn);
+	
+}
diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNode.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNode.java
new file mode 100644
index 0000000..fd24dd2
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNode.java
@@ -0,0 +1,211 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.discovery;

+import java.util.*;

+

+import org.osgi.framework.*;

+import org.osgi.service.upnp.*;

+

+/**

+ * @author Stefano "Kismet" Lenzi

+ * @author Francesco Furfari 

+ *  

+ */

+public class DeviceNode {

+	

+	private ServiceReference sr;

+	private boolean isRootNode;

+	private String udn ;

+	private boolean hasChild;

+	private int numberOfSons;

+	private ArrayList children;

+	private DeviceNode parent;

+	

+	public DeviceNode(ServiceReference sr){

+		//PRE: argument is always UPnPDevice service reference

+		if (sr == null) 

+			throw new IllegalArgumentException("null is not a valid arg in DeviceNode constructor");

+		this.sr = sr;

+		udn = (String) sr.getProperty(UPnPDevice.UDN);

+		parent=null;

+		isRootNode = (sr.getProperty(UPnPDevice.PARENT_UDN) == null);

+		String[] sons = ((String[]) sr.getProperty(UPnPDevice.CHILDREN_UDN));

+		hasChild = (sons != null);

+		if (hasChild) {

+			children = new ArrayList();

+			numberOfSons = sons.length;

+		}

+		/*

+		//Operation to let DeviceNode::isComplete() O(1)

+		isComplete = !hasChild;

+		*/					

+	}

+	

+	public ServiceReference getReference(){

+		return sr;

+	}

+	public UPnPDevice getDevice(BundleContext ctx){

+		return (UPnPDevice)ctx.getService(sr);

+	}

+	

+	public void attach(DeviceNode node){

+		if (node == null) 

+			throw new IllegalArgumentException("null is not a valid arg in DeviceNode.attach() method");

+		node.parent = this;

+		/*

+		//Operation to let DeviceNode::isComplete() O(1)

+		if((numberOfSons==children.size()-1)

+				&&(node.isComplete())){

+			this.isComplete = true;

+		}

+		*/

+		children.add(node);

+	}

+	

+	public DeviceNode dethatch(String name){

+		DeviceNode dn = this.search(name);

+		if(dn==null) 

+			return null;

+		

+		if(dn.parent!=null){

+			Iterator list = dn.parent.children.iterator();

+			while (list.hasNext()) {

+				DeviceNode x = (DeviceNode) list.next();

+				if(x.udn.equals(name)){

+					list.remove();

+					/*

+					//Operation to let DeviceNode::isComplete() O(1)

+					dn.parent.isComplete=false;

+					*/					

+					break;

+				}

+			}

+		}

+		dn.parent=null;

+		return dn;

+	}

+	

+	public Collection getAllChildren(){

+		if((this.children==null)||(this.children.size()==0)) 

+			return new ArrayList();

+		

+		Vector v = new Vector(this.children);

+		Iterator list = this.children.iterator();

+		while (list.hasNext()) {

+			DeviceNode x = (DeviceNode) list.next();

+			Collection c = x.getAllChildren();

+			if(c==null) continue;

+			v.addAll(c);

+		}

+		return v;

+	}

+

+	public Collection getChildren(){

+		if((this.children==null)||(this.children.size()==0)) 

+			return new ArrayList();

+		return this.children;

+	}	

+	

+	/**

+	 * @param name <code>String</code> that contain the UDN to look for

+	 * @return return a <code>DeviceNode</code> that have the UDN equals to name and <br>

+	 * 		if there is any <code>DeviceNode</code> with the proper UDN value return <code>null</code>

+	 */

+	public DeviceNode search(String name){

+		if (name == null) 

+			throw new IllegalArgumentException("null is not a valid arg in DeviceNode.search() method");

+		if (name.equals(udn))

+			return this;

+		else if (hasChild){

+			Iterator list = children.iterator();

+			while (list.hasNext()){

+				DeviceNode child = (DeviceNode)list.next();

+				DeviceNode node = child.search(name);

+				if (node != null) return node;				

+			}

+		}

+		return null;

+	}

+	

+	/**

+	 * 

+	 * @param udn

+	 * @return <code>true</code> if and only if this <code>DeviceNode</code>

+	 * 		contains a DeviceNode with UDN equals to passed argument or if

+	 * 		its USN is equal to passed argument

+	 */

+	public boolean contains(String udn){

+		return this.search(udn)!=null;

+	}

+	

+	public boolean isComplete(){	

+		/*

+		//Operation to let DeviceNode::isComplete() O(1)

+		return isComplete;

+		*/		

+		if (! hasChild) return true;

+		if (numberOfSons != children.size())return false;

+		Iterator list = children.iterator();

+		while (list.hasNext()){

+			DeviceNode child = (DeviceNode)list.next();

+			if (! child.isComplete()) return false;

+		}

+		return true;

+	}

+	

+	public DeviceNode isAttachable(DeviceNode node){

+		if (node == null) 

+			throw new IllegalArgumentException("null is not a valid arg in DeviceNode.isAttachable() method");

+		String parentUDN=(String) node.getReference().getProperty(UPnPDevice.PARENT_UDN);

+		if(parentUDN==null) return null;

+		return search(parentUDN);		

+	}

+		

+	public boolean isRoot(){

+		return isRootNode;		

+	}

+	

+	public boolean equals(String udn){

+		return this.udn.equals(udn);

+	}

+	

+	public String toString(){

+		return udn;

+	}		

+	public boolean isLeaf() {

+		return !hasChild;

+	}

+    

+    public void print(){

+        System.out.println("####Device Node");

+        String[] props = sr.getPropertyKeys();

+        for (int i=0;i< props.length;i++){

+            Object prop= sr.getProperty(props[i]);

+            if (prop instanceof String[]){

+                System.out.println(props[i]+ "=");

+                String[] multiple = (String[])prop;

+                for (int j=0;j< multiple.length;j++){

+                    System.out.println(multiple[j]+ ";"); 

+                }

+

+            }

+            else System.out.println(props[i]+ "="+ prop);

+        }

+        System.out.println("####Device Node");

+   }

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNodeListener.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNodeListener.java
new file mode 100644
index 0000000..d9becb3
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DeviceNodeListener.java
@@ -0,0 +1,28 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.discovery;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public interface DeviceNodeListener {

+	public void deviceDetected(DeviceNode d);

+	public void rootDeviceUnplugged(String udn);

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DevicesList.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DevicesList.java
new file mode 100644
index 0000000..fcfe4b3
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DevicesList.java
@@ -0,0 +1,62 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.felix.upnp.tester.discovery;
+
+import java.util.*;
+
+/**
+ * @author Stefano "Kismet" Lenzi 
+ * @author Francesco Furfari 
+ *  
+ */
+public class DevicesList {
+	
+	ArrayList devices;	
+	DeviceChangeListener listener = null;
+	
+	public DevicesList(){
+		devices = new ArrayList();
+	}
+	
+	
+	public boolean add(DeviceNode dev) {
+		if(listener!=null) 
+			listener.addedDevice(dev);
+		return devices.add(dev);
+	}
+
+	public Iterator iterator() {
+		return devices.iterator();
+	}
+
+	public boolean remove(DeviceNode dev) {
+		if(listener!=null)
+			listener.removedDevice(dev);
+		return devices.remove(dev);
+	}
+
+	public DeviceChangeListener getChangeListener() {
+		return listener;
+	}
+
+	public void setChangeListener(DeviceChangeListener listener) {
+		this.listener = listener;
+	}
+	
+	
+}
diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DriverProxy.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DriverProxy.java
new file mode 100644
index 0000000..fce3e03
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/DriverProxy.java
@@ -0,0 +1,118 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.discovery;

+

+import org.apache.felix.upnp.extra.controller.*;

+import org.apache.felix.upnp.tester.*;

+

+import org.osgi.framework.*;

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class DriverProxy implements ServiceListener {

+    private DevicesInfo devicesInfo;

+    private DriverController drvController;

+    public DriverProxy(){

+        ServiceReference sr = Activator.context.getServiceReference(DevicesInfo.class.getName());

+        if (sr != null){

+            devicesInfo = (DevicesInfo)Activator.context.getService(sr);

+            drvController = (DriverController) devicesInfo;

+            Mediator.getControlPoint().enableMenus(true,getLogLevel(),getCyberDebug());

+            Mediator.getTreeViewer().setPopupMenuEnabled(true);

+        }

+        String filter =  "(" + Constants.OBJECTCLASS + "=" + DevicesInfo.class.getName() + ")" ;

+        try {

+            Activator.context.addServiceListener(this,filter);  

+        } catch (Exception ignored){};

+     }

+    

+    public boolean isDriverAvailable(){

+        return (drvController != null);

+    }

+    

+    public String getDeviceDescriptionURI(String udn){

+        if (devicesInfo != null)

+            return devicesInfo.getLocationURL(udn);

+        return "";

+    }

+    

+    public String getServiceDescriptionURI(String udn,String serviceId){

+        if (devicesInfo != null)

+            return devicesInfo.getSCPDURL(udn,serviceId);

+        return null;

+    }

+    

+    public String resolveRelativeUrl(String udn,String link){

+        if (devicesInfo != null)

+            return devicesInfo.resolveRelativeUrl(udn,link);

+        return null;

+    }

+    

+    public boolean getCyberDebug(){

+        if (drvController != null)

+            return drvController.getCyberDebug();

+        return false;

+    }

+    public void setCyberDebug(boolean b){

+        if (drvController != null)

+            drvController.setCyberDebug(b);

+    }

+    public int getLogLevel(){

+        if (drvController != null)

+            return drvController.getLogLevel();

+        return 0;

+    }

+    

+    public void setLogLevel(int value){

+        if (drvController != null)

+            drvController.setLogLevel(value);

+    }

+    public void doSearch(String target){

+        if (drvController != null)

+            drvController.search(target);

+    }

+    

+    

+    public void serviceChanged(ServiceEvent e) {

+        switch(e.getType()){

+            case ServiceEvent.REGISTERED:{

+                Object service = Activator.context.getService(e.getServiceReference());

+                if (service != null){

+                devicesInfo = (DevicesInfo) service;

+                drvController = (DriverController) devicesInfo;

+                Mediator.getControlPoint().enableMenus(true,getLogLevel(),getCyberDebug());

+                Mediator.getTreeViewer().setPopupMenuEnabled(true);

+                }

+            };break;

+            case ServiceEvent.UNREGISTERING:{   

+                devicesInfo = null;

+                drvController =null;

+                Mediator.getControlPoint().enableMenus(false,0,false);

+                Mediator.getTreeViewer().setPopupMenuEnabled(false);

+            };break;

+        }       

+    }

+    

+    public void close(){

+        Activator.context.removeServiceListener(this);           

+    }

+

+

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/RootDeviceListener.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/RootDeviceListener.java
new file mode 100644
index 0000000..12d62a1
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/discovery/RootDeviceListener.java
@@ -0,0 +1,295 @@
+/*
+ *   Copyright 2006 The Apache Software Foundation
+ *
+ *   Licensed 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.felix.upnp.tester.discovery;
+
+
+import java.util.*;
+
+import org.apache.felix.upnp.tester.Activator;
+import org.apache.felix.upnp.tester.gui.*;
+import org.osgi.framework.*;
+import org.osgi.service.upnp.*;
+
+/**
+ * @author Stefano "Kismet" Lenzi 
+ * @author Francesco Furfari
+ *  
+ */
+public class RootDeviceListener implements ServiceListener {
+		
+	//This list contains all partial tree of UPnP Device
+	//when a device is complete it will be removed from the list
+    private ArrayList devices;
+	
+	private DeviceNodeListener listener;
+
+	public RootDeviceListener(){
+		devices = new ArrayList();
+
+	}
+	
+	public void setDeviceNodeListener(DeviceNodeListener listener){
+		this.listener = listener;
+	}
+	
+	public synchronized void addNewDevice(ServiceReference sr){
+		LogPanel.log("adding device:"+sr.getProperty(UPnPDevice.FRIENDLY_NAME));
+		DeviceNode node = new DeviceNode(sr);
+		//node.print();	
+		if(node.isRoot() && node.isLeaf()){
+			//Obiovsly
+			listener.deviceDetected(node);
+			return;
+		}
+
+		if(!node.isLeaf()){
+			//I look if there is some partial tree that is child 
+			//of my new node. This operation may complete the tree 
+			Iterator list = devices.iterator();
+			DeviceNode handle = null;
+			while(list.hasNext()){
+				DeviceNode tree = (DeviceNode) list.next();
+				if((handle = node.isAttachable(tree)) != null){
+					handle.attach(tree);
+					list.remove();
+                    monitorDevices();
+					if(node.isRoot() && node.isComplete()){
+						listener.deviceDetected(node);
+//						removeChildren(node);
+						return;
+					}
+				}
+			}
+		}
+		
+		if(!node.isRoot()){
+			//I look if there is some partial tree that should own
+			//my new node. This operation may complete the tree			
+			Iterator list = devices.iterator();
+			DeviceNode handle = null;
+			while(list.hasNext()){
+				DeviceNode tree = (DeviceNode) list.next();
+				if((handle = tree.isAttachable(node)) != null){
+					handle.attach(node);
+					if(tree.isRoot() && tree.isComplete()){
+						list.remove();
+                        monitorDevices();
+						listener.deviceDetected(tree);
+//						removeChildren(tree);
+					}
+					return;
+				}
+			}		
+		}
+		
+		devices.add(node);
+        monitorDevices();
+	}
+
+	private void monitorDevices() {
+        LogPanel.status("Pending Devices: " + devices.size() );      
+    }
+
+    public void removeDevice(ServiceReference sr){
+		DeviceNode node = new DeviceNode(sr);
+		if (node.isRoot()) {
+			LogPanel.log("removing root device ..."+sr.getProperty(UPnPDevice.FRIENDLY_NAME));
+			listener.rootDeviceUnplugged(node.toString());
+			return;
+		}
+
+	}
+	/**
+	 * @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
+	 */
+	public void serviceChanged(ServiceEvent e) {
+		switch(e.getType()){
+			case ServiceEvent.REGISTERED:{
+				addNewDevice(e.getServiceReference());
+			};break;
+			
+			case ServiceEvent.MODIFIED:{				
+			};break;
+			
+			case ServiceEvent.UNREGISTERING:{	
+				removeDevice(e.getServiceReference());
+			};break;
+				
+		}
+	}
+
+
+	/**
+	 * Register this object to listen to all "well registered" UPnPDevice<br> 
+	 * that should be Exported<br>
+	 * And look for all the already registered UPnPDevice to be exported
+	 * 
+	 */
+	public void activate() {
+		/*
+		 * I listen for the UPnPDevice service that are Root 
+		 * that should be exported to UPnP Network.
+		 */
+	    try {
+			Activator.context.addServiceListener(this,
+				"(&"+
+					"("+Constants.OBJECTCLASS+"="+UPnPDevice.class.getName()+")"+
+					"("+UPnPDevice.UDN+"=*)"+
+				")"
+				);
+		} catch (InvalidSyntaxException e) {
+			e.printStackTrace();
+		}
+		ServiceReference[] roots = null; 
+		try {
+			roots = Activator.context.getServiceReferences(
+					UPnPDevice.class.getName(),
+					"(&"+
+					"("+Constants.OBJECTCLASS+"="+UPnPDevice.class.getName()+")"+
+					"("+UPnPDevice.UDN+"=*)"+
+					")"
+				);
+		} catch (InvalidSyntaxException e) {
+			e.printStackTrace();
+		}
+		if(roots!=null){
+			for (int i = 0; i < roots.length; i++) {
+				addNewDevice(roots[i]);
+			}
+		}
+	}
+
+	/**
+	 * 
+	 */
+	public void deactive() {
+	    Activator.context.removeServiceListener(this);
+	}
+    
+    
+    
+    
+    
+    
+    public void checkIncompleteDevice() {
+        if (devices.size() == 0){
+            LogPanel.log("[check incomplete device] no pending devices !"  );
+            return;
+        }
+        Iterator list = devices.iterator();
+        while(list.hasNext()){
+            LogPanel.log("[check incomplete device] "  );           
+            LogPanel.log("------- device -------- "  );           
+            DeviceNode tree = (DeviceNode) list.next();
+            printProperties(tree.getReference());
+        }
+        
+    }
+    
+    public static void checkErrataDevice() {
+        ServiceReference[] allUPnPDevice = null; 
+        ServiceReference[] UPnPbaseDriverDevice = null;
+        try {
+            allUPnPDevice = Activator.context.getServiceReferences(
+                    UPnPDevice.class.getName(),
+                    "(&" + "(" + UPnPDevice.UPNP_EXPORT + "=*)" 
+                         + "("+Constants.OBJECTCLASS+"="+UPnPDevice.class.getName()+")"
+                    + ")"
+                );
+            // filter used by UPnP base Driver
+            UPnPbaseDriverDevice = Activator.context.getServiceReferences(UPnPDevice.class.getName(),
+                        "(&" + "(" + UPnPDevice.UPNP_EXPORT + "=*)" + 
+                                "(DEVICE_CATEGORY=UPnP)" + 
+                                "(" + UPnPDevice.UDN + "=*)" + 
+                                "(" + UPnPDevice.FRIENDLY_NAME + "=*)" +  
+                                "(" + UPnPDevice.MANUFACTURER + "=*)" + 
+                                "(" + UPnPDevice.MODEL_NAME + "=*)" + 
+                                "(" + UPnPDevice.TYPE + "=*)" + 
+                                "(!("+ UPnPDevice.PARENT_UDN + "=*))" + 
+            ")");
+
+        } catch (InvalidSyntaxException e) {
+            e.printStackTrace();
+        }
+        if (allUPnPDevice!=null){
+            if (UPnPbaseDriverDevice!= null){
+                if (allUPnPDevice.length == UPnPbaseDriverDevice.length){                   
+                    LogPanel.log("---- Check Errata Device ----");
+                    LogPanel.log("All registered Devices have mandatory properties specified");
+                    LogPanel.log("--------  End Check  --------");
+                }
+                else if (allUPnPDevice.length > UPnPbaseDriverDevice.length)
+                    printErrataDevice(allUPnPDevice,UPnPbaseDriverDevice) ;               
+            }
+            else {
+                printErrataDevice(allUPnPDevice,UPnPbaseDriverDevice) ;               
+            }
+        }
+        
+    }
+    
+    public static void  printErrataDevice(ServiceReference[] allUPnPDevice,ServiceReference[] UPnPbaseDriverDevice) {
+        LogPanel.log("---- Check Errata Device ----");
+        for (int i =0;i< allUPnPDevice.length;i++){
+             if (! isPresent(allUPnPDevice[i],UPnPbaseDriverDevice)){
+                 LogPanel.log("---- Device ----");
+                 printProperties(allUPnPDevice[i]);
+             }
+         }
+        LogPanel.log("--------  End Check  --------");
+    }
+    
+    public static boolean isPresent(ServiceReference sr, ServiceReference[] list) {
+        Object s = Activator.context.getService(sr);
+        for(int i = 0; i<list.length;i++){
+            Object item =Activator.context.getService(list[i]);
+            if (s == item) {
+                Activator.context.ungetService(sr);
+                Activator.context.ungetService(list[i]);
+                return true;
+            }
+            Activator.context.ungetService(list[i]);           
+        }
+        Activator.context.ungetService(sr);
+        return false;
+    }
+    
+    public static void printProperties(ServiceReference service){
+        String[] properties = service.getPropertyKeys();
+        if (properties == null)
+            LogPanel.log("properties are null");
+        else {
+            for(int j =0;j<properties.length;j++)
+                LogPanel.log(properties[j] + "=" + Util.justString(service.getProperty(properties[j])));
+        }
+        
+        if ( service.getProperty(UPnPDevice.UDN)== null) 
+            LogPanel.log("[Warning] missing property: "+UPnPDevice.UDN);
+        if ( service.getProperty(UPnPDevice.FRIENDLY_NAME)== null) 
+            LogPanel.log("[Warning] missing property: "+UPnPDevice.FRIENDLY_NAME);
+        if ( service.getProperty(UPnPDevice.MANUFACTURER)== null) 
+            LogPanel.log("[Warning] missing property: "+UPnPDevice.MANUFACTURER);
+        if ( service.getProperty(UPnPDevice.MODEL_NAME)== null) 
+            LogPanel.log("[Warning] missing property: "+UPnPDevice.MODEL_NAME);
+        if ( service.getProperty(UPnPDevice.TYPE)== null) 
+            LogPanel.log("[Warning] missing property: "+UPnPDevice.TYPE);
+
+    }
+    
+
+}
diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/ActionPanel.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/ActionPanel.java
new file mode 100644
index 0000000..2c61c99
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/ActionPanel.java
@@ -0,0 +1,365 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+import java.awt.BorderLayout;

+import java.awt.Component;

+import java.awt.GridBagLayout;

+import java.awt.event.ActionEvent;

+import java.util.Dictionary;

+import java.util.Hashtable;

+

+import javax.swing.AbstractAction;

+import javax.swing.AbstractCellEditor;

+import javax.swing.DefaultCellEditor;

+import javax.swing.JButton;

+import javax.swing.JComboBox;

+import javax.swing.JOptionPane;

+import javax.swing.JPanel;

+import javax.swing.JScrollPane;

+import javax.swing.JSlider;

+import javax.swing.JTable;

+import javax.swing.JTextField;

+import javax.swing.SwingConstants;

+import javax.swing.event.ChangeEvent;

+import javax.swing.event.ChangeListener;

+import javax.swing.event.TableModelEvent;

+import javax.swing.table.AbstractTableModel;

+import javax.swing.table.TableCellEditor;

+import javax.swing.table.TableModel;

+

+import org.apache.felix.upnp.extra.util.Converter;

+import org.apache.felix.upnp.extra.util.UPnPException;

+import org.apache.felix.upnp.tester.Mediator;

+import org.osgi.service.upnp.UPnPAction;

+import org.osgi.service.upnp.UPnPStateVariable;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+  * @author Francesco  Furfari 

+ *  

+ */

+public class ActionPanel extends JPanel {

+	

+	UPnPAction action;

+	ArgumentsModel argsModel;

+	MyTable table; 

+	JPanel buttonPanel;

+	

+	OutputArgumentsModel outArgsModel; 

+	JTable outTable; 

+	

+	/**

+	 * 

+	 */

+	public ActionPanel() {

+		super(new GridBagLayout());

+		buildButtonPanel();

+		buildTable();

+		add(new JScrollPane(table),Util.setConstrains(0,0,1,1,100,100));

+		add(new JScrollPane(outTable),Util.setConstrains(0,1,1,1,100,100)); 

+		add(buttonPanel,Util.setConstrains(1,0,1,1,1,1));

+	}

+

+	private void buildButtonPanel(){

+		buttonPanel = new JPanel();

+		JButton doAction = new JButton("Do Action");

+		doAction.addActionListener(new AbstractAction(){

+			public void actionPerformed(ActionEvent e) {

+				outArgsModel.clearData(); 

+

+				Dictionary params = null;

+				Dictionary result = null;

+				if (argsModel.getRowCount()!= 0){

+					if (table.isEditing()) table.getCellEditor().stopCellEditing();

+					params = new Hashtable();

+					for (int i =0;i<argsModel.getRowCount();i++){

+						String name = (String) argsModel.getValueAt(i,0);

+						String value = (String)argsModel.getValueAt(i,3);

+						try {

+                            params.put(

+                            	name,

+                                Converter.parseString(

+                                    value,

+                                    action.getStateVariable(name).getUPnPDataType()));

+                        } catch (Exception ex) {

+                        	LogPanel.log("Error invoking action (bad parameter)");

+                         	return ;

+                        }

+					}

+				}

+				try {result = action.invoke(params);}

+                catch (UPnPException ex){

+                    String error =  

+                        "===== Action Failed =====" +

+                        "\nUPnP Error Code::\n    " +ex.getErrorCode() +

+                        "\nUPnP Error Description::\n    " +ex.getErrorDescription();

+                    printReport(params,error);

+                    JOptionPane.showMessageDialog(

+                        Mediator.getPropertiesViewer(),error);                   

+                }

+                catch (Exception ex){

+                    printReport(params,ex.getMessage());

+					JOptionPane.showMessageDialog(

+						Mediator.getPropertiesViewer(),

+						ex.getMessage()

+					);

+				}

+				if (result != null) {

+                    printReport(params,result);

+					outArgsModel.setData(action,result);

+					JOptionPane.showMessageDialog(

+						Mediator.getPropertiesViewer(),

+						"Action invoked!"

+					);

+				

+				}

+			}

+            private void printReport(Dictionary params,Object result) {

+                String input = "";

+                String output = "";

+                if (params != null) input = params.toString();

+                if (output != null) output = result.toString();

+                String method = action.getName();

+                String report = "\n==== Action:: " + method + " ====\n"

+                                + input 

+                                + "\n----------- result ----------\n"

+                                + output

+                                + "\n-----------------------------";

+               LogPanel.log(report);

+            }

+		});		

+	    buttonPanel.add(doAction);

+	}

+	

+	private void buildTable(){

+		argsModel = new ArgumentsModel();

+	    table = new MyTable(argsModel); 

+		argsModel.setTable(table); 		

+

+		outArgsModel = new OutputArgumentsModel();

+	    outTable = new JTable(outArgsModel); 

+	}

+

+	public void setArguments(UPnPAction action){

+		this.action = action;

+		argsModel.setData(action);		

+		outArgsModel.clearData(); 

+	}

+	

+}

+

+//thanks to Thomas Wagner 18/10/2005

+class OutputArgumentsModel extends ArgumentsModel { 

+

+	public OutputArgumentsModel() {

+		super();

+        header = new String[]{"output arg name","related Var","Java \\ UpnP type","value"};

+	}

+

+	public void clearData() {

+		names = new String[]{""};

+        related = new String[]{""};

+    	types = new String[]{""};

+    	values = new String[]{""};

+		size=0;

+		this.fireTableChanged(new TableModelEvent(this));

+	}

+

+    public void setData(UPnPAction  action, Object result){

+

+		Hashtable res = (Hashtable) result;

+

+        String[] names = action.getOutputArgumentNames();

+   	    size = 0;

+    	this.names = names;

+    	if (names!=null){

+	    	values = new String[names.length];

+            types = new String[names.length];

+            related = new String[names.length];

+	    	for (int i=0;i<names.length;i++) {

+                UPnPStateVariable currentStateVar = action.getStateVariable(names[i]);

+

+				Object value = res.get(names[i]);

+                values[i]=value.toString();

+                related[i] = currentStateVar.getName();

+                

+                String javaType =currentStateVar.getJavaDataType().toString();

+                javaType = javaType.substring(javaType.lastIndexOf('.')+1);

+                String upnpType = currentStateVar.getUPnPDataType();

+                types[i] = javaType + " \\ " + upnpType;

+            }   	

+	    	size = names.length;

+    	}

+		this.fireTableChanged(new TableModelEvent(this));

+    }

+}

+

+//thanks to Thomas Wagner 12/10/2005

+class ArgumentsModel extends  AbstractTableModel {

+	int size = 0;

+	private MyTable table;	

+	String[] names = new String[]{""};

+    String[] related = new String[]{""};

+    String[] types = new String[]{""};

+    String[] values = new String[]{""};

+	String[] header = new String[]{"arg name","related Var","Java \\ UpnP type","value"};

+   

+	public void setTable(MyTable table) { 

+		this.table = table;

+	}

+ 

+	public int getColumnCount() { return 4; }

+    public int getRowCount() { return size;}

+    public String getColumnName(int col) { return header[col]; }

+    public boolean isCellEditable(int rowIndex, int columnIndex) {

+    	return (columnIndex ==3);

+    }

+    public Object getValueAt(int row, int col) { 

+    	if (col == 0) return names[row];

+        else if (col == 1) return related[row];

+        else if (col == 2) return types[row];

+        else if (col == 3) return values[row];

+    	return null;

+	}   

+    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {

+    	values[rowIndex]= (String) aValue; 

+    }

+

+    public void setData(UPnPAction  action){

+

+		table.deleteMyCellEditors();

+        

+

+        String[] names = action.getInputArgumentNames();

+   	    size = 0;

+    	this.names = names;

+    	if (names!=null){

+	    	values = new String[names.length];

+            related = new String[names.length];

+            types = new String[names.length];

+	    	for (int i=0;i<names.length;i++) {

+                values[i]="";

+                UPnPStateVariable currentStateVar = action.getStateVariable(names[i]);

+                related[i] = currentStateVar.getName();

+                String javaType = currentStateVar.getJavaDataType().toString();

+                javaType = javaType.substring(javaType.lastIndexOf('.')+1);

+                String upnpType = currentStateVar.getUPnPDataType();

+                types[i] = javaType + " \\ " + upnpType;

+

+				//handle allowed value list

+				if ( currentStateVar.getAllowedValues() != null) {

+					String av[] = currentStateVar.getAllowedValues();

+					JComboBox comboBox = new JComboBox();

+					for (int j = 0; j < av.length; j++) {

+						comboBox.addItem(av[j]);

+					}

+					values[i] = av[0]; //preset the first value from list

+					table.setMyCellEditor(new DefaultCellEditor(comboBox),i);

+				}

+

+				//handle default value

+				if (currentStateVar.getDefaultValue() != null) {

+					String val = currentStateVar.getDefaultValue().toString();

+					if(val.length() > 0)

+						values[i] = val;

+				}

+				

+               //handle range values

+               if ((currentStateVar.getMaximum()!= null)

+                   &&(currentStateVar.getMinimum()!= null)){

+                   int max = currentStateVar.getMaximum().intValue();

+                   int min = currentStateVar.getMinimum().intValue();

+                   int value = min;

+                   try { value = Integer.parseInt(values[i]);}

+                   catch (NumberFormatException ignored){}

+                   table.setMyCellEditor(new SliderEditor(min,max,value),i);

+               }

+

+            }   	

+	    	size = names.length;

+    	}

+		this.fireTableChanged(new TableModelEvent(this));

+        this.fireTableStructureChanged();

+    }

+}

+

+// thanks to Thomas Wagner 12/10/2005

+class MyTable extends JTable { 

+

+	private Hashtable cellEditors;

+	

+	public MyTable(TableModel dm) {

+		super(dm);

+		cellEditors = new Hashtable();

+	}

+

+	public void setMyCellEditor(TableCellEditor editor,int col){

+		cellEditors.put(new Integer(col),editor);

+	}

+

+	public void deleteMyCellEditors(){

+		cellEditors.clear();

+	}

+

+	//overwritten JTable method	

+	public TableCellEditor getCellEditor(int row,int col) {

+		TableCellEditor tce = (TableCellEditor) cellEditors.get(new Integer(row));

+		if( tce != null) {

+			return tce;

+		} else {

+			return super.getCellEditor(row,col);

+		}

+	}

+}

+

+class SliderEditor extends AbstractCellEditor implements TableCellEditor

+{

+    JPanel editor;

+    JTextField text;

+    JSlider slider;

+    public SliderEditor(int min, int max, int value){

+         editor = new JPanel(new BorderLayout());

+         //editor.setBorder(new EmptyBorder(1,0,1,0));

+         text = new JTextField(Integer.toString(max).length());

+         slider = new JSlider(SwingConstants.HORIZONTAL,min,max,value);

+         editor.add(text,BorderLayout.WEST);

+         editor.add(slider);

+         slider.addChangeListener(new ChangeListener(){

+            public void stateChanged(ChangeEvent e) {

+                //if (! replSlider.getValueIsAdjusting()){

+                text.setText(new Integer(slider.getValue()).toString());

+                //}

+            }

+         });

+

+    }

+    

+    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {

+        text.setText((String) value);

+        try {

+            slider.setValue(Integer.parseInt((String)value));

+        } catch (NumberFormatException ignored) {}

+        return editor;

+    }

+

+    public Object getCellEditorValue() {

+        return text.getText();

+    }

+}

+

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/LogPanel.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/LogPanel.java
new file mode 100644
index 0000000..9300686
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/LogPanel.java
@@ -0,0 +1,74 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+import java.awt.BorderLayout;

+

+import javax.swing.JLabel;

+import javax.swing.JPanel;

+import javax.swing.JScrollBar;

+import javax.swing.JScrollPane;

+import javax.swing.JTextArea;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class LogPanel extends JPanel {

+    private static JTextArea logArea = new JTextArea(4,50);

+    private static JPanel statusBar = new JPanel(new BorderLayout());

+    private static JLabel statusText = new JLabel();

+    

+    private static JScrollPane scroll = new JScrollPane(logArea);

+    

+    public LogPanel() {

+        super(new BorderLayout());

+        logArea.setText("");

+        //add(new JScrollPane(new JTextArea(4,80)));

+        add(scroll);

+        statusBar.add(statusText,BorderLayout.EAST);

+        add(statusBar,BorderLayout.SOUTH);

+        }

+

+    /* public static void log(String msg) {

+        logArea.append(msg + "\n\r");

+		JScrollBar scrBar = scroll.getVerticalScrollBar();

+		int maxPos = scrBar.getMaximum();

+		scrBar.setValue(maxPos);

+   }*/

+    

+    public static void log(final String msg){ 

+        synchronized (logArea) {

+            logArea.append(msg );

+            logArea.append("\n\r");

+        }

+        JScrollBar scrBar = scroll.getVerticalScrollBar();

+        int maxPos = scrBar.getMaximum();

+        scrBar.setValue(maxPos);

+    }

+    

+    public static void status(final String msg){ 

+        statusText.setText(msg);

+     }

+

+   public static void clear(String msg) {

+        logArea.setText("");

+   }

+

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/PropertiesViewer.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/PropertiesViewer.java
new file mode 100644
index 0000000..917bbb7
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/PropertiesViewer.java
@@ -0,0 +1,225 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+

+

+import java.awt.Component;

+import java.awt.Cursor;

+import java.awt.Dimension;

+import java.awt.GridBagLayout;

+import java.awt.Point;

+import java.awt.event.MouseAdapter;

+import java.awt.event.MouseEvent;

+import java.awt.event.MouseMotionListener;

+

+import javax.swing.JPanel;

+import javax.swing.JScrollPane;

+import javax.swing.JTable;

+import javax.swing.JTree;

+import javax.swing.event.TableModelEvent;

+import javax.swing.table.AbstractTableModel;

+import javax.swing.table.DefaultTableCellRenderer;

+

+import org.apache.felix.upnp.tester.Mediator;

+import org.apache.felix.upnp.tester.discovery.DeviceNode;

+import org.osgi.service.upnp.UPnPAction;

+import org.osgi.service.upnp.UPnPDevice;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco  Furfari

+ *  

+ */

+public class PropertiesViewer extends JPanel {

+

+    ActionPanel actionsPanel;

+	SubscriptionPanel subscriptionPanel;

+	DataModel dataModel;

+	JTable table;

+	

+	public PropertiesViewer(){

+		super(new GridBagLayout());

+		buildTable();

+		actionsPanel = new ActionPanel();

+		subscriptionPanel = new SubscriptionPanel();

+		

+		JScrollPane scroll = new JScrollPane(table);

+		scroll.setPreferredSize(new Dimension(500,200));

+		//actionsPanel.setPreferredSize(new Dimension(500,100)); twa

+		actionsPanel.setPreferredSize(new Dimension(500,200));

+		add(scroll,Util.setConstrains(0,0,6,2,100,50));

+		add(actionsPanel,Util.setConstrains(0,2,6,1,100,20));

+		add(subscriptionPanel,Util.setConstrains(0,3,6,1,100,5));

+		showActionPanel(false);

+		showSubscriptionPanel(false);

+         

+        table.addMouseMotionListener(new MouseMotionListener(){

+           private final Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);

+           

+           public void mouseDragged(MouseEvent e) {}

+           public void mouseMoved(MouseEvent e) {

+                String link = getDataLink(e.getPoint());

+                if (link != null)

+                    table.setCursor(handCursor);                       

+                else

+                    table.setCursor(Cursor.getDefaultCursor());

+           }

+

+        });

+        

+        table.addMouseListener(new MouseAdapter(){

+            public void mouseClicked(MouseEvent e){

+                String link = getDataLink(e.getPoint());

+                if (link != null)

+                    Util.openUrl(link);              

+            }

+        });

+

+    }   

+        

+    public String getDataLink(Point p) {

+        int col = table.columnAtPoint(p);

+        if (col != 1) 

+            return null;

+        else {

+            int row = table.rowAtPoint(p);

+            String data = (String) dataModel.getValueAt(row,col);

+            if (data == null) return null;

+            if (data.length()<4) return null;

+            String begin = data.substring(0,4);

+            if (begin.equalsIgnoreCase("http"))

+                return data;

+            else {

+                String propertyName= (String) dataModel.getValueAt(row,0);

+                if (propertyName.equalsIgnoreCase(UPnPDevice.PRESENTATION_URL)

+                    ||propertyName.equalsIgnoreCase(UPnPDevice.MANUFACTURER_URL)

+                    ||propertyName.equalsIgnoreCase(UPnPDevice.MODEL_URL) )

+                    {

+                    JTree tree = Mediator.getUPnPDeviceTree();

+                    UPnPDeviceTreeNode selectedNode = (UPnPDeviceTreeNode)tree.getLastSelectedPathComponent();   

+                    String url = "";

+                    if (selectedNode.category.equals(UPnPDeviceTreeNode.DEVICE)){

+                        UPnPDeviceTreeNode parent =  (UPnPDeviceTreeNode)selectedNode.getParent();

+                        while (parent.category!=UPnPDeviceTreeNode.ROOT_DEVICE)

+                             parent =  (UPnPDeviceTreeNode)parent.getParent();

+                        DeviceNode device =  (DeviceNode) parent.getUserObject();

+                        String udn = (String)device.getReference().getProperty(UPnPDevice.UDN);

+                        url = Mediator.getDriverProxy().resolveRelativeUrl(udn,data);

+                        return url;

+                    }                           

+                    else if (selectedNode.category.equals(UPnPDeviceTreeNode.ROOT_DEVICE))

+                    {

+                        DeviceNode node =  (DeviceNode) selectedNode.getUserObject();

+                        String udn = (String)node.getReference().getProperty(UPnPDevice.UDN);

+                        url = Mediator.getDriverProxy().resolveRelativeUrl(udn,data);

+                        return url;

+                    }

+                }

+                return null;     

+            }

+        }

+	}

+	

+	public void setProperties(String[]name,String[]values){

+		dataModel.setData(name,values);

+	}

+	

+	public void setAction(UPnPAction action){

+		actionsPanel.setArguments(action);

+		

+	}

+	

+	public void showActionPanel(boolean show){

+		actionsPanel.setVisible(show);

+	}

+	

+	public void showSubscriptionPanel(boolean show){

+	    subscriptionPanel.setVisible(show);

+	}

+	

+	private void buildTable(){

+		dataModel = new DataModel();

+	    table = new JTable(dataModel);

+        table.setDefaultRenderer(table.getColumnClass(1), new LinkCellRenderer());

+	}

+

+

+}

+

+class DataModel extends  AbstractTableModel {

+	int size = 0;

+	String[] names = new String[]{""};

+	String[] values = new String[]{""};

+    String[] header = new String[]{"property key","value"};

+    

+    public String getColumnName(int col) { return header[col]; }

+	public int getColumnCount() { return 2; }

+    public int getRowCount() { return size;}

+    public Object getValueAt(int row, int col) { 

+    	if (col==0) return names[row];

+    	else return values[row];

+    }

+    

+    /*

+    public boolean isCellEditable(int rowIndex, int columnIndex) {

+        return false;

+    }

+    */

+    

+    public void setData(String[]names,String[]values){

+        String[] lowerNames = new String[names.length];

+        for (int i=0;i<names.length;i++)

+            lowerNames[i]= names[i].toLowerCase();

+    	this.names=lowerNames;

+    	this.values=values;

+    	size = names.length;

+		this.fireTableChanged(new TableModelEvent(this));

+    }

+    

+}

+

+class LinkCellRenderer extends DefaultTableCellRenderer

+{

+    public LinkCellRenderer(){

+        super();

+    }

+    

+    public Component getTableCellRendererComponent(JTable table, Object value,

+            boolean isSelected, boolean hasFocus, int row, int column) {

+        super.getTableCellRendererComponent(table,value,isSelected,hasFocus,row,column);

+        if (column==1){

+            String propertyName = (String)table.getModel().getValueAt(row,0);

+            if (propertyName.equalsIgnoreCase(UPnPDevice.PRESENTATION_URL)

+                ||propertyName.equalsIgnoreCase(UPnPDevice.MODEL_URL)

+                ||propertyName.equalsIgnoreCase(UPnPDevice.MANUFACTURER_URL))

+            {

+                if (!value.equals(""))

+                    setValue("<html><a href=''>"+value+"</a></html>");

+                return this;

+            }

+            if (((String) value).length()<4) return this;

+            String begin = ((String) value).substring(0,4);

+            if (begin.equalsIgnoreCase("http"))

+                setValue("<html><a href=''>"+value+"</a></html>");

+        }

+        return this;

+    }

+ 

+}

+

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/SubscriptionPanel.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/SubscriptionPanel.java
new file mode 100644
index 0000000..18476f8
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/SubscriptionPanel.java
@@ -0,0 +1,154 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+

+

+import java.awt.event.ActionEvent;

+import java.util.Dictionary;

+import java.util.Enumeration;

+

+import javax.swing.AbstractAction;

+import javax.swing.JButton;

+import javax.swing.JPanel;

+import javax.swing.JTree;

+import javax.swing.SwingUtilities;

+

+import org.apache.felix.upnp.tester.Activator;

+import org.apache.felix.upnp.tester.Mediator;

+import org.apache.felix.upnp.tester.UPnPSubscriber;

+import org.apache.felix.upnp.tester.discovery.DeviceNode;

+import org.osgi.service.upnp.UPnPDevice;

+import org.osgi.service.upnp.UPnPEventListener;

+import org.osgi.service.upnp.UPnPService;

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class SubscriptionPanel extends JPanel implements UPnPEventListener{

+	public SubscriptionPanel() {

+		super();

+		buildButtonPanel();

+	}

+

+	private void buildButtonPanel(){

+		JButton subscribeBtn = new JButton("Subscribe");

+		subscribeBtn.addActionListener(new AbstractAction(){

+			public void actionPerformed(ActionEvent e) {

+			    //System.out.println("subscribing ...");

+			    doSubscribe();

+			}

+		});		

+		JButton unsubscribeBtn = new JButton("Unsubscribe");

+		unsubscribeBtn.addActionListener(new AbstractAction(){

+			public void actionPerformed(ActionEvent e) {

+			    //System.out.println("unsubscribing ...");

+			    doUnsubscribe();

+				}

+		});		

+	    add(subscribeBtn);

+	    add(unsubscribeBtn);

+	}

+	

+	UPnPSubscriber subscriber;

+	public void doSubscribe()

+	{

+		if (subscriber == null)

+		    subscriber = new UPnPSubscriber(Activator.context,this);

+		

+		UPnPDeviceTreeNode selectedNode = getSelectedNode();

+		String serviceId = getServiceId(selectedNode);

+		String parentId = getDeviceId(selectedNode);

+		LogPanel.log("subscribing ... "+ "ServiceId ["+serviceId+"] of DeviceId ["+parentId +"]");

+		subscriber.subscribe(parentId,serviceId);

+		setSubscribedVariableOf(selectedNode);

+	}

+	

+	public void doUnsubscribe(){

+		UPnPDeviceTreeNode selectedNode = getSelectedNode();

+		String serviceId = getServiceId(selectedNode);

+		String parentId = getDeviceId(selectedNode);

+		LogPanel.log("unsubscribing ... "+ "ServiceId ["+serviceId+"] of DeviceId ["+parentId +"]");

+		subscriber.unsubscribe(parentId,serviceId);

+		setUnubscribedVariableOf(selectedNode);

+		}

+	

+	public void notifyUPnPEvent(final String deviceId, final String serviceId, final Dictionary events) {

+        // UPnP base driver notify are synchronous !!

+		Runnable doShowMsg = new Runnable() {

+       	 public void run() {

+			LogPanel.log("notifyUPnPEvent::[DeviceID "+deviceId+"][ServiceId "+serviceId+"]");

+			Enumeration elements = events.keys();

+			while (elements.hasMoreElements()){

+			    Object key = elements.nextElement();

+			    Object value = events.get(key);

+			    LogPanel.log("["+key+"][value "+value+"]");

+			}

+       	 }

+        };

+        SwingUtilities.invokeLater(doShowMsg);

+		

+	}

+	

+	private void setSubscribedVariableOf(UPnPDeviceTreeNode selectedNode){

+	    Enumeration list = selectedNode.children();

+	    while (list.hasMoreElements()){

+	        UPnPDeviceTreeNode node =  (UPnPDeviceTreeNode) list.nextElement();

+	        if (node.category == UPnPDeviceTreeNode.EVENTED_STATE)

+	            node.category = UPnPDeviceTreeNode.SUBSCRIBED_STATE;

+	    }

+	    JTree tree = Mediator.getUPnPDeviceTree();

+	    tree.validate();

+	    tree.repaint();

+	}

+	

+	private void setUnubscribedVariableOf(UPnPDeviceTreeNode selectedNode){

+	    Enumeration list = selectedNode.children();

+	    while (list.hasMoreElements()){

+	        UPnPDeviceTreeNode node =  (UPnPDeviceTreeNode) list.nextElement();

+	        if (node.category == UPnPDeviceTreeNode.SUBSCRIBED_STATE)

+	            node.category = UPnPDeviceTreeNode.EVENTED_STATE;

+	    }

+	    JTree tree = Mediator.getUPnPDeviceTree();

+	    tree.validate();

+	    tree.repaint();

+	}

+	

+	private  UPnPDeviceTreeNode getSelectedNode(){

+		JTree tree = Mediator.getUPnPDeviceTree();

+		UPnPDeviceTreeNode selectedNode = (UPnPDeviceTreeNode)tree.getLastSelectedPathComponent();

+	    return selectedNode;

+	}

+	private  String getServiceId (UPnPDeviceTreeNode selectedNode){

+		Object userObj = selectedNode.getUserObject();

+	    String serviceId = ((UPnPService) userObj).getId();

+	    return serviceId;

+	}

+	private String getDeviceId (UPnPDeviceTreeNode selectedNode){

+	     UPnPDeviceTreeNode parent = (UPnPDeviceTreeNode)selectedNode.getParent();

+	     DeviceNode node =(DeviceNode)parent.getUserObject();

+	     String parentId = (String) node.getReference().getProperty(UPnPDevice.ID);

+	    return parentId;

+	}

+	

+	

+	

+}

+

+

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/TreeViewer.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/TreeViewer.java
new file mode 100644
index 0000000..403533e
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/TreeViewer.java
@@ -0,0 +1,323 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+

+import java.awt.BorderLayout;

+import java.awt.event.ActionEvent;

+import java.awt.event.MouseAdapter;

+import java.awt.event.MouseEvent;

+import java.util.ArrayList;

+import java.util.Dictionary;

+import java.util.Enumeration;

+

+import javax.swing.AbstractAction;

+import javax.swing.JMenuItem;

+import javax.swing.JPanel;

+import javax.swing.JPopupMenu;

+import javax.swing.JScrollPane;

+import javax.swing.JTree;

+import javax.swing.SwingUtilities;

+import javax.swing.ToolTipManager;

+import javax.swing.event.PopupMenuEvent;

+import javax.swing.event.PopupMenuListener;

+import javax.swing.event.TreeSelectionEvent;

+import javax.swing.event.TreeSelectionListener;

+import javax.swing.tree.DefaultTreeModel;

+import javax.swing.tree.TreePath;

+

+import org.apache.felix.upnp.tester.Activator;

+import org.apache.felix.upnp.tester.Mediator;

+import org.apache.felix.upnp.tester.discovery.DeviceNode;

+import org.apache.felix.upnp.tester.discovery.DeviceNodeListener;

+import org.osgi.service.upnp.UPnPAction;

+import org.osgi.service.upnp.UPnPDevice;

+import org.osgi.service.upnp.UPnPService;

+import org.osgi.service.upnp.UPnPStateVariable;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class TreeViewer extends JPanel 	implements DeviceNodeListener 

+{

+	

+	private UPnPDeviceTreeNode root;

+	private DefaultTreeModel treeModel;

+	private JTree tree;

+    final TreePopup popup ;

+	public TreeViewer(){

+		super(new BorderLayout());

+		Mediator.setTreeViewer(this);

+		root = new UPnPDeviceTreeNode("UPnP Devices");

+		treeModel= new DefaultTreeModel(root);

+		tree = new JTree(treeModel);

+		Mediator.setUPnPDeviceTree(tree);

+		tree.setCellRenderer(new TreeNodeCellRenderer() );

+		tree.putClientProperty("JTree.lineStyle", "Angled");

+        add(new JScrollPane(tree));

+		addTreeSelectionListener();

+        

+        

+        popup = new TreePopup(tree);

+        popup.setEnabled(false);

+        tree.addMouseListener(new MouseAdapter(){

+            public void mouseClicked(MouseEvent e){

+                if (SwingUtilities.isRightMouseButton(e)){

+                    TreePath path = tree.getClosestPathForLocation(e.getX(), e.getY());

+                    tree.setSelectionPath(path);

+                    tree.scrollPathToVisible(path);

+                    popup.show(tree, e.getX(), e.getY());

+                }

+            }

+        });

+        ToolTipManager.sharedInstance().registerComponent(tree);

+         

+

+	}

+	

+    public void setPopupMenuEnabled(boolean driverControllerAvailable){

+        popup.getComponent(0).setEnabled(driverControllerAvailable);

+    }

+	/* (non-Javadoc)

+	 * @see org.apache.felix.sample.upnp.DeviceNodeListener#deviceDetected(it.cnr.isti.niche.osgi.upnpbase.export.DeviceNode)

+	 */

+	public void deviceDetected(DeviceNode node) {

+		root.add(new UPnPDeviceTreeNode(node,Activator.context));

+		treeModel.nodeStructureChanged(root);

+	}

+	

+	public void rootDeviceUnplugged(String udn){

+		Enumeration list = root.children();

+		while (list.hasMoreElements()){

+			UPnPDeviceTreeNode node= (UPnPDeviceTreeNode)list.nextElement();

+			DeviceNode device = (DeviceNode)node.getUserObject();

+			if (udn.equals(device.toString())) {

+				node.removeFromParent();

+				treeModel.nodeStructureChanged(root);

+			}

+		}

+	}

+

+	private void addTreeSelectionListener(){

+		tree.addTreeSelectionListener(new TreeSelectionListener(){

+			public void valueChanged(TreeSelectionEvent e){

+				UPnPDeviceTreeNode selectedNode = (UPnPDeviceTreeNode)tree.getLastSelectedPathComponent();				

+				doNodeAction(selectedNode);			

+			}		

+		});

+	}

+	

+	private void doNodeAction(UPnPDeviceTreeNode node){

+		if (node == null) {

+			clearPropertiesViewer();

+			return;

+		}

+		if (node.category.equals(UPnPDeviceTreeNode.ACTION))

+			Mediator.getPropertiesViewer().showActionPanel(true);

+		else

+			Mediator.getPropertiesViewer().showActionPanel(false);

+

+		if (node.category.equals(UPnPDeviceTreeNode.SERVICE))

+			Mediator.getPropertiesViewer().showSubscriptionPanel(true);

+		else

+			Mediator.getPropertiesViewer().showSubscriptionPanel(false);

+

+		if ( node.category.equals(UPnPDeviceTreeNode.DEVICE)

+			||node.category.equals(UPnPDeviceTreeNode.ROOT_DEVICE)){

+			DeviceNode device = (DeviceNode) node.getUserObject();

+			UPnPDevice upnpDevice = device.getDevice(Activator.context);

+			makeProperties(upnpDevice);

+		} 

+		else if (node.category.equals(UPnPDeviceTreeNode.SERVICE)){

+			UPnPService service = (UPnPService) node.getUserObject();			

+			makeProperties(service);

+		}

+		else if (node.category.equals(UPnPDeviceTreeNode.ACTION)){

+			UPnPAction action = (UPnPAction) node.getUserObject();

+			makeProperties(action);

+			Mediator.getPropertiesViewer().setAction(action);

+		}

+		else if (node.category.equals(UPnPDeviceTreeNode.STATE)

+		        ||node.category.equals(UPnPDeviceTreeNode.EVENTED_STATE)

+		        ||node.category.equals(UPnPDeviceTreeNode.SUBSCRIBED_STATE)){

+			UPnPStateVariable state = (UPnPStateVariable) node.getUserObject();

+			makeProperties(state);

+		}

+		

+	}

+	

+	private void clearPropertiesViewer(){

+		String[] names = new String[]{};

+		String[] values = new String[]{};

+		PropertiesViewer viewer = Mediator.getPropertiesViewer();

+		viewer.setProperties(names,values);

+		viewer.showActionPanel(false);

+		viewer.showSubscriptionPanel(false);

+	}

+	

+	private void makeProperties(UPnPDevice upnpDevice){

+		Dictionary dict = upnpDevice.getDescriptions(null);

+		int size = dict.size();

+		String[] names = new String[size];

+		String[] values = new String[size];

+		Enumeration keys = dict.keys();

+		for (int i=0;i<size;i++){

+			names[i]= (String) keys.nextElement();

+			values[i]= Util.justString(dict.get(names[i]));

+		}

+		Mediator.getPropertiesViewer().setProperties(names,values);

+	}

+	

+	private void makeProperties(UPnPService service){

+		String[] names = new String[]{"Id","Type","Version"};

+		String[] values = new String[]{service.getId(),service.getType(),service.getType()};

+		Mediator.getPropertiesViewer().setProperties(names,values);

+	}

+	

+	private void makeProperties(UPnPAction action){

+		ArrayList names = new ArrayList();

+		ArrayList values = new ArrayList();

+		names.add("Name");

+		values.add(action.getName());

+		

+		String returnName = action.getReturnArgumentName();

+		if (returnName != null){

+			names.add("Return value name");

+			values.add(returnName);

+		}

+		String[] inArg = action.getInputArgumentNames();

+		if (inArg != null){

+			for (int i = 0; i<inArg.length;i++){

+				names.add("Input arg["+ (i+1)+"]");

+				values.add(inArg[i]);			

+			}

+		}

+		String[] outArg = action.getOutputArgumentNames();

+		if (outArg != null){

+			for (int i = 0; i<outArg.length;i++){

+				names.add("Output arg["+ (i+1)+"]");

+				values.add(outArg[i]);			

+			}

+		}

+		

+		Mediator.getPropertiesViewer().setProperties(

+				(String[])names.toArray(new String[]{}),

+				(String[])values.toArray(new String[]{})

+		);

+		

+	}

+	

+	private void makeProperties(UPnPStateVariable state){

+		ArrayList names = new ArrayList();

+		ArrayList values = new ArrayList();

+		names.add("Name");

+		values.add(state.getName());

+		names.add("Evented");

+		values.add(state.sendsEvents()? "yes":"no");

+		names.add("Default Value");

+		values.add(Util.justString(state.getDefaultValue()));

+		names.add("Java Data Type");

+		values.add(state.getJavaDataType().getName());

+		names.add("Java UPnP Type");

+		values.add(state.getUPnPDataType());

+		names.add("Minimum");

+		values.add(Util.justString(state.getMinimum()));

+		names.add("Maximum");

+		values.add(Util.justString(state.getMaximum()));

+		names.add("Step");

+		values.add(Util.justString(state.getStep()));		

+		String[] allowed = state.getAllowedValues();

+		if (allowed!=null){

+			for (int i=0;i<allowed.length;i++){

+				names.add("Allowed value["+i+1+"]");

+				values.add(allowed[i]);		

+			}			

+		}

+		Mediator.getPropertiesViewer().setProperties(

+				(String[])names.toArray(new String[]{}),

+				(String[])values.toArray(new String[]{})

+		);

+	}

+	

+	

+}

+

+class TreePopup extends JPopupMenu implements PopupMenuListener {

+    JTree tree;

+    JMenuItem item;

+

+    public TreePopup(final JTree tree){

+        super();

+        this.tree = tree;

+        (item = add(new AbstractAction(){

+            public void actionPerformed(ActionEvent e){

+                UPnPDeviceTreeNode selectedNode = (UPnPDeviceTreeNode)tree.getLastSelectedPathComponent();   

+                String url = "";

+                if (selectedNode.category.equals(UPnPDeviceTreeNode.DEVICE)){

+                    UPnPDeviceTreeNode parent =  (UPnPDeviceTreeNode)selectedNode.getParent();

+                    while (parent.category!=UPnPDeviceTreeNode.ROOT_DEVICE)

+                         parent =  (UPnPDeviceTreeNode)parent.getParent();

+                    DeviceNode device =  (DeviceNode) parent.getUserObject();

+                    String udn = (String)device.getReference().getProperty(UPnPDevice.UDN);

+                    url = Mediator.getDriverProxy().getDeviceDescriptionURI(udn);

+                }

+                        

+                else if (selectedNode.category.equals(UPnPDeviceTreeNode.ROOT_DEVICE))

+                {

+                    DeviceNode node =  (DeviceNode) selectedNode.getUserObject();

+                    String udn = (String)node.getReference().getProperty(UPnPDevice.UDN);

+                    url = Mediator.getDriverProxy().getDeviceDescriptionURI(udn);

+                }

+                else if (selectedNode.category.equals(UPnPDeviceTreeNode.SERVICE))

+                {

+                    UPnPDeviceTreeNode parent =  (UPnPDeviceTreeNode)selectedNode.getParent();

+                    while (parent.category!=UPnPDeviceTreeNode.ROOT_DEVICE)

+                         parent =  (UPnPDeviceTreeNode)parent.getParent();

+                    DeviceNode device =  (DeviceNode) parent.getUserObject();

+                    String udn = (String)device.getReference().getProperty(UPnPDevice.UDN);

+                    UPnPService service =  (UPnPService) selectedNode.getUserObject();

+                    url = Mediator.getDriverProxy().getServiceDescriptionURI(udn,service.getId());

+                }                    

+                Util.openUrl(url);   

+            }

+        })).setText("Show Description");

+        addPopupMenuListener(this);

+

+    }

+    

+    public void popupMenuCanceled(PopupMenuEvent e){}

+    public void popupMenuWillBecomeInvisible(PopupMenuEvent e){}

+    public void popupMenuWillBecomeVisible(PopupMenuEvent e){

+        if (Mediator.getDriverProxy().isDriverAvailable()){

+        UPnPDeviceTreeNode selectedNode = (UPnPDeviceTreeNode)tree.getLastSelectedPathComponent();              

+            if (selectedNode.category.equals(UPnPDeviceTreeNode.DEVICE)

+                ||selectedNode.category.equals(UPnPDeviceTreeNode.ROOT_DEVICE)

+                ||selectedNode.category.equals(UPnPDeviceTreeNode.SERVICE))

+            {

+                item.setEnabled(true);

+            } 

+            else

+                item.setEnabled(false);

+        }

+        else

+            item.setEnabled(false);

+           

+    }

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/UPnPDeviceTreeNode.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/UPnPDeviceTreeNode.java
new file mode 100644
index 0000000..ecda361
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/UPnPDeviceTreeNode.java
@@ -0,0 +1,235 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+import java.awt.Color;

+import java.awt.Component;

+import java.awt.image.ImageObserver;

+import java.net.URL;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.Iterator;

+

+import javax.swing.Icon;

+import javax.swing.ImageIcon;

+import javax.swing.JToolTip;

+import javax.swing.JTree;

+import javax.swing.tree.DefaultMutableTreeNode;

+import javax.swing.tree.DefaultTreeCellRenderer;

+

+import org.apache.felix.upnp.tester.discovery.DeviceNode;

+import org.osgi.framework.BundleContext;

+import org.osgi.service.upnp.UPnPAction;

+import org.osgi.service.upnp.UPnPDevice;

+import org.osgi.service.upnp.UPnPService;

+import org.osgi.service.upnp.UPnPStateVariable;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+public class UPnPDeviceTreeNode extends DefaultMutableTreeNode {

+	public final static String ROOT_DEVICE = "RootDeviceNode";

+	public final static String DEVICE = "DeviceNode";

+	public final static String SERVICE = "UPnPService";

+	public final static String ACTION = "UPnPAction";

+	public final static String STATE = "UPnPStateVariable";

+	public final static String EVENTED_STATE = "EventedStateVariable";

+	public final static String SUBSCRIBED_STATE = "SubscribedStateVariable";

+	

+	protected String category;

+	public UPnPDeviceTreeNode(String obj) {

+		super(obj);

+		category = obj.getClass().getName();

+	}

+	

+	public UPnPDeviceTreeNode(DeviceNode obj, BundleContext ctx) {

+		super(obj);

+		

+		if (obj.isRoot()) category = ROOT_DEVICE;

+		else category = DEVICE;

+		

+		UPnPDevice device = (UPnPDevice)ctx.getService(obj.getReference());

+		UPnPService[] services = device.getServices();

+		

+		Collection nodeChildren = obj.getChildren();

+		if (nodeChildren != null){		

+			Iterator list = nodeChildren.iterator();

+			while (list.hasNext()){

+				DeviceNode node = (DeviceNode)list.next();

+				this.add(new UPnPDeviceTreeNode(node,ctx));

+			}

+		}

+		if (services != null){

+			for (int i=0;i<services.length;i++)

+				this.add(new UPnPDeviceTreeNode(services[i]));

+		}

+		

+	}

+	

+	public UPnPDeviceTreeNode(UPnPService obj) {

+		super(obj);

+		category = SERVICE;

+		UPnPStateVariable[] variables = obj.getStateVariables();

+		if (variables != null){

+			for (int i=0;i<variables.length;i++)

+				this.add(new UPnPDeviceTreeNode(variables[i]));

+		}

+		UPnPAction[] actions = obj.getActions();

+		if (actions != null){

+			for (int i=0;i<actions.length;i++)

+				this.add(new UPnPDeviceTreeNode(actions[i]));

+		}

+	}

+	public UPnPDeviceTreeNode(UPnPAction obj) {

+		super(obj);

+		category = ACTION;

+	}

+	

+	public UPnPDeviceTreeNode(UPnPStateVariable obj) {

+		super(obj);

+		if (obj.sendsEvents()) category = EVENTED_STATE;

+		else category = STATE;

+	}

+	

+	public String toString() {

+		if (category.equals(DEVICE)||category.equals(ROOT_DEVICE)){

+			DeviceNode node =  (DeviceNode) getUserObject();

+			return node.toString();

+		}

+		else if (category.equals(SERVICE)){

+			UPnPService node =  (UPnPService) getUserObject();

+			return node.getType();

+		}

+		else if (category.equals(ACTION)){

+			UPnPAction node =  (UPnPAction) getUserObject();

+			return node.getName();

+		}

+		else if (category.equals(STATE) ||category.equals(EVENTED_STATE)||category.equals(SUBSCRIBED_STATE)){

+			UPnPStateVariable node =  (UPnPStateVariable) getUserObject();

+			return node.getName();

+		}

+		else

+			return getUserObject().toString();

+	}

+}

+

+//   local class for JTree icon renderer

+class TreeNodeCellRenderer extends DefaultTreeCellRenderer implements ImageObserver{

+	

+	private HashMap icons ;

+	ImageIcon image;

+	public TreeNodeCellRenderer() {

+		super();

+		icons = new HashMap();

+		try {

+			icons.put(UPnPDeviceTreeNode.EVENTED_STATE, loadIcon(UPnPDeviceTreeNode.EVENTED_STATE));

+			image =  loadIcon(UPnPDeviceTreeNode.SUBSCRIBED_STATE);

+			// to use animate gif

+			//image.setImageObserver(this);

+ 			icons.put(UPnPDeviceTreeNode.SUBSCRIBED_STATE, image);

+

+ 			icons.put(UPnPDeviceTreeNode.ROOT_DEVICE, loadIcon(UPnPDeviceTreeNode.ROOT_DEVICE));

+			icons.put(UPnPDeviceTreeNode.DEVICE, loadIcon(UPnPDeviceTreeNode.DEVICE));

+			icons.put(UPnPDeviceTreeNode.SERVICE, loadIcon(UPnPDeviceTreeNode.SERVICE));

+			icons.put(UPnPDeviceTreeNode.ACTION, loadIcon(UPnPDeviceTreeNode.ACTION));

+			icons.put(UPnPDeviceTreeNode.STATE, loadIcon(UPnPDeviceTreeNode.STATE));

+		} catch (Exception ex) {

+			System.out.println(ex);

+		}

+

+	}

+    

+    public JToolTip createToolTip() {

+        JToolTip tip = super.createToolTip();

+        tip.setBackground(Color.yellow);

+        return tip;

+    }

+

+	//test to display animated gif

+	/* 

+	 public boolean imageUpdate(Image img, int infoflags,

+		       int x, int y, int width, int height){

+	       	//System.out.println("image update");

+	        Mediator.getUPnPDeviceTree().validate();

+	        Mediator.getUPnPDeviceTree().repaint();

+	        return true;

+	   }

+	 */

+

+	public Component getTreeCellRendererComponent(JTree tree, Object value,

+			boolean sel, boolean expanded, boolean leaf, int row,

+			boolean hasFocus) {

+		Icon icon = selectIcon((UPnPDeviceTreeNode) value);

+        setToolTip((UPnPDeviceTreeNode) value);

+		if (icon != null) {

+			setOpenIcon(icon);

+			setClosedIcon(icon);

+			setLeafIcon(icon);

+		} else {

+			setOpenIcon(getDefaultOpenIcon());

+			setClosedIcon(getDefaultClosedIcon());

+			setLeafIcon(getDefaultLeafIcon());

+		}

+		return super.getTreeCellRendererComponent(tree, value, sel, expanded,

+				leaf, row, hasFocus);

+	}

+	

+    public Icon selectIcon(UPnPDeviceTreeNode node) {

+        Icon icon = null;

+        try {

+                String tag = node.category;

+                icon = (Icon) icons.get(tag);

+        } catch (Exception ex) {

+            System.out.println("getTreeCellRendererComponent Exception:" + ex);

+        }

+        return icon;

+    }

+    

+    public void setToolTip(UPnPDeviceTreeNode node) {

+        String tag = node.category;

+        if (tag.equals(UPnPDeviceTreeNode.ROOT_DEVICE)

+             ||tag.equals(UPnPDeviceTreeNode.DEVICE))

+        {

+            DeviceNode device = (DeviceNode) node.getUserObject();

+            setToolTipText("<html><TABLE BORDER='0' CELLPADDING='0' CELLSPACING='0' ><TR BGCOLOR='#F9FF79' ><TD>" 

+                    + device.getReference().getProperty(UPnPDevice.FRIENDLY_NAME).toString()

+                    +"</TD></TR></TABLE ></html>");

+        }

+        else

+            setToolTipText(null);

+     }

+	

+    public  static ImageIcon loadIcon(String name)

+    {

+        try {

+            /*

+            System.out.println("loading image ..."+name);

+            System.out.println("from "+"IMAGES/" + name + ".gif");

+            */

+            URL eventIconUrl = UPnPDeviceTreeNode.class.getResource("IMAGES/" + name + ".gif");           

+            return new ImageIcon(eventIconUrl,name);

+        }

+        catch (Exception ex){

+		        System.out.println("Resource:" + name + " not found : " + ex.toString());

+            return null;

+        }

+    }

+

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/Util.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/Util.java
new file mode 100644
index 0000000..75338ac
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/gui/Util.java
@@ -0,0 +1,104 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester.gui;

+

+import java.awt.GridBagConstraints;

+import java.io.BufferedReader;

+import java.io.InputStreamReader;

+

+/**

+ * @author Stefano "Kismet" Lenzi 

+ * @author Francesco Furfari 

+ *  

+ */

+

+public class Util {

+	final static GridBagConstraints constrains = new GridBagConstraints();

+

+	public  static GridBagConstraints setConstrains(int x,int y,int w,int h,int wx, int wy)

+    {

+        constrains.insets.left=5;

+        constrains.insets.right=5;

+        constrains.insets.bottom=3;

+        constrains.fill=GridBagConstraints.BOTH;

+        constrains.anchor=GridBagConstraints.WEST;

+        constrains.gridx=x;

+        constrains.gridy=y;

+        constrains.gridwidth=w;

+        constrains.gridheight=h;

+        constrains.weightx=wx;

+        constrains.weighty=wy;

+        return constrains;

+    }

+    

+    public static String justString(Object obj){

+        if (obj == null) return "";

+        else if (obj instanceof String[]){

+            String[] items = (String[])obj;

+            String tmp = "";

+            for (int i = 0; i < items.length; i++) {

+                tmp+=items[i]+"; ";

+            }

+            return tmp;

+        }

+        return obj.toString();

+    }

+

+    public static void openUrl(String url) {

+        try {

+            if (url == null) return;

+            String os=System.getProperty("os.name","").toLowerCase();

+            Process p = null;

+            if(os.indexOf("windows")!=-1){

+                String cmd = null;

+                cmd = "cmd.exe /C start "+url;

+                LogPanel.log("[Executing cmd] " +cmd);

+                p = Runtime.getRuntime().exec(cmd);

+                

+            }else if(os.indexOf("linux")!=-1){

+            	String[] cmd = new String[]{

+                		"/bin/sh",

+                		"-c",

+                		"( $BROWSER " + url + " || mozilla-firefox '" + url + "' || firefox '" + url 

+                		+ "' || mozilla '" + url + "' || konqueror '" + url + "' || opera '" + url + "' )"

+                };

+            	StringBuffer sb = new StringBuffer();

+            	for (int i = 0; i < cmd.length; i++) {

+					sb.append(" ").append(cmd[i]);

+					

+				}

+                LogPanel.log("[Executing cmd] " +sb.toString());

+                p = Runtime.getRuntime().exec(cmd);

+            }            

+            BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));

+            BufferedReader out = new BufferedReader(new InputStreamReader(p.getInputStream()));            

+            while (true) {

+            	while(err.ready()) System.err.println(err.readLine());

+            	while(out.ready()) System.out.println(out.readLine());            	

+            	try{

+            		p.exitValue();

+            		break;

+            	}catch (IllegalThreadStateException e) {

+				}

+			}

+        } catch (Exception ex){

+            System.out.println(ex);

+        }

+    }

+

+}

diff --git a/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/test.java b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/test.java
new file mode 100644
index 0000000..4ab9df3
--- /dev/null
+++ b/org.apache.felix.upnp.tester/src/main/java/org/apache/felix/upnp/tester/test.java
@@ -0,0 +1,77 @@
+/*

+ *   Copyright 2006 The Apache Software Foundation

+ *

+ *   Licensed 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.felix.upnp.tester;

+

+import java.awt.Image;

+import java.net.URL;

+

+import javax.swing.ImageIcon;

+import javax.swing.JFrame;

+import javax.swing.JLabel;

+import javax.swing.JPanel;

+

+public class test extends JFrame {

+

+   Image image;

+

+   public test(String filename) {

+     super(filename);

+     setDefaultCloseOperation(EXIT_ON_CLOSE);

+     //image = getToolkit().getImage(filename);

+     ImageIcon image =loadIcon("EventedStateVariable");

+     //image.setImageObserver(this);

+     JPanel panel = new JPanel();

+     

+     panel.add(new JLabel(image));

+     JLabel lab = new JLabel();

+     lab.setIcon(image);

+     panel.add(lab);

+     this.getContentPane().add(panel);

+       }

+

+   /*public void paint(Graphics g) {

+     super.paint(g);

+     g.drawImage(image, 25, 25, this);

+   }*/

+

+   public static void main(String args[]) {

+     if (args.length > 0) {

+       JFrame f = new test(args[0]);

+       f.setSize(300, 300);

+       f.show();

+     } else {

+       System.err.println(

+        "You must specify an image filename to display");

+     }

+   }

+   

+   

+   public  static ImageIcon loadIcon(String name)

+   {

+       try {

+           //System.out.println("loading image ..."+path);

+           URL eventIconUrl = test.class.getResource("images/" + name + ".gif");           

+           return new ImageIcon(eventIconUrl,name);

+       }

+       catch (Exception ex){

+		        System.out.println("Resource:" + name + " not found : " + ex.toString());

+           return null;

+       }

+   }

+

+}
\ No newline at end of file