Imported the initial version of the device manager code, as donated by Dennis Geurts, into the repository. Made some minor modifications to the pom.xml, but otherwise it's exactly the ZIP attached to FELIX-10.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@752659 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/Activator.java b/deviceaccess/src/main/java/org/apache/felix/das/Activator.java
new file mode 100644
index 0000000..d43fca5
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/Activator.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das;
+
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+
+import org.osgi.service.device.Device;
+import org.osgi.service.device.Driver;
+import org.osgi.service.device.DriverLocator;
+import org.osgi.service.device.DriverSelector;
+import org.osgi.service.log.LogService;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.apache.felix.dependencymanager.Service;
+
+import org.apache.felix.das.util.Util;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Activator extends DependencyActivatorBase
+{
+
+    /** the bundle context */
+    private BundleContext m_context;
+
+    /** the dependency manager */
+    private DependencyManager m_manager;
+
+    /** the device manager */
+    private DeviceManager m_deviceManager;
+
+
+    /**
+     * Here, we create and start the device manager, but we do not register it
+     * within the framework, since that is not specified by the specification.
+     * 
+     * @see org.apache.felix.dependencymanager.DependencyActivatorBase#init(org.osgi.framework.BundleContext,
+     *      org.apache.felix.dependencymanager.DependencyManager)
+     */
+    public void init( BundleContext context, DependencyManager manager ) throws Exception
+    {
+
+        m_context = context;
+        m_manager = manager;
+
+        m_deviceManager = new DeviceManager( m_context );
+
+        // the real device manager
+        startDeviceManager();
+
+        // the analyzers to inform the user (and me) if something is wrong
+        // startAnalyzers();
+
+    }
+
+
+    private void startDeviceManager()
+    {
+
+        final String driverFilter = Util.createFilterString( "(&(%s=%s)(%s=%s))", new String[]
+            { Constants.OBJECTCLASS, Driver.class.getName(), org.osgi.service.device.Constants.DRIVER_ID, "*" } );
+
+        final String deviceFilter = Util.createFilterString( "(|(%s=%s)(%s=%s))", new String[]
+            { Constants.OBJECTCLASS, Device.class.getName(), org.osgi.service.device.Constants.DEVICE_CATEGORY, "*" } );
+
+        Service svc = createService();
+
+        svc.setImplementation( m_deviceManager );
+
+        svc.add( createServiceDependency().setService( LogService.class ).setRequired( false ) );
+
+        svc.add( createServiceDependency().setService( DriverSelector.class ).setRequired( false )
+            .setAutoConfig( false ) );
+
+        svc.add( createServiceDependency().setService( DriverLocator.class ).setRequired( false ).setAutoConfig( false )
+            .setCallbacks( "locatorAdded", "locatorRemoved" ) );
+        svc.add( createServiceDependency().setService( Driver.class, driverFilter ).setRequired( false ).setCallbacks(
+            "driverAdded", "driverRemoved" ) );
+        svc.add( createServiceDependency().setService( Device.class, deviceFilter ).setRequired( false ).setCallbacks(
+            "deviceAdded", "deviceModified", "deviceRemoved" ) );
+
+        m_manager.add( svc );
+
+    }
+
+
+    public void destroy( BundleContext context, DependencyManager manager ) throws Exception
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/DeviceManager.java b/deviceaccess/src/main/java/org/apache/felix/das/DeviceManager.java
new file mode 100644
index 0000000..f13342b
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/DeviceManager.java
@@ -0,0 +1,672 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.das.util.DriverLoader;
+import org.apache.felix.das.util.DriverMatcher;
+import org.apache.felix.das.util.Util;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.device.Device;
+import org.osgi.service.device.Driver;
+import org.osgi.service.device.DriverLocator;
+import org.osgi.service.device.DriverSelector;
+import org.osgi.service.device.Match;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DeviceManager implements Log
+{
+
+    private final long DEFAULT_TIMEOUT_SEC = 1;
+
+    // the logger
+    private LogService m_log;
+
+    // the bundle context
+    private final BundleContext m_context;
+
+    // the driver selector
+    private DriverSelector m_selector;
+
+    // the driver locators
+    private List<DriverLocator> m_locators;
+
+    // the devices
+    private Map<ServiceReference, Object> m_devices;
+
+    // the drivers
+    private Map<ServiceReference, DriverAttributes> m_drivers;
+
+    // performs all the background actions
+    private ExecutorService m_worker;
+
+    // used to add delayed actions
+    private ScheduledExecutorService m_delayed;
+
+    private Filter m_deviceImplFilter;
+
+    private Filter m_driverImplFilter;
+
+
+    public DeviceManager( BundleContext context )
+    {
+        m_context = context;
+    }
+
+
+    public void debug( String message )
+    {
+        m_log.log( LogService.LOG_DEBUG, message );
+    }
+
+
+    public void info( String message )
+    {
+        m_log.log( LogService.LOG_INFO, message );
+    }
+
+
+    public void warning( String message )
+    {
+        m_log.log( LogService.LOG_WARNING, message );
+    }
+
+
+    public void error( String message, Throwable e )
+    {
+        System.err.println( message );
+        if ( e != null )
+        {
+            e.printStackTrace();
+        }
+        m_log.log( LogService.LOG_ERROR, message, e );
+    }
+
+
+    // dependency manager methods
+    @SuppressWarnings("unused")
+    private void init() throws InvalidSyntaxException
+    {
+        m_locators = Collections.synchronizedList( new ArrayList<DriverLocator>() );
+        m_worker = Executors.newSingleThreadExecutor( new NamedThreadFactory( "Apache Felix Device Manager" ) );
+        m_delayed = Executors.newScheduledThreadPool( 1, new NamedThreadFactory(
+            "Apache Felix Device Manager - delayed" ) );
+        m_deviceImplFilter = Util.createFilter( "(%s=%s)", new Object[]
+            { org.osgi.framework.Constants.OBJECTCLASS, Device.class.getName() } );
+        m_driverImplFilter = Util.createFilter( "(%s=%s)", new Object[]
+            { org.osgi.framework.Constants.OBJECTCLASS, Driver.class.getName() } );
+    }
+
+
+    @SuppressWarnings("unused")
+    private void start()
+    {
+        m_drivers = new HashMap<ServiceReference, DriverAttributes>();
+        m_devices = new HashMap<ServiceReference, Object>();
+        submit( new WaitForStartFramework() );
+    }
+
+
+    public void stop()
+    {
+        // nothing to do ?
+    }
+
+
+    public void destroy()
+    {
+        m_worker.shutdownNow();
+        m_delayed.shutdownNow();
+    }
+
+
+    // callback methods
+
+    public void locatorAdded( DriverLocator locator )
+    {
+        m_locators.add( locator );
+        debug( "driver locator appeared" );
+    }
+
+
+    public void locatorRemoved( DriverLocator locator )
+    {
+        m_locators.remove( locator );
+        debug( "driver locator lost" );
+    }
+
+
+    public void driverAdded( ServiceReference ref, Object obj )
+    {
+        final Driver driver = Driver.class.cast( obj );
+        m_drivers.put( ref, new DriverAttributes( ref, driver ) );
+
+        debug( "driver appeared: " + Util.showDriver( ref ) );
+    }
+
+
+    public void driverRemoved( ServiceReference ref )
+    {
+        String driverId = String.class.cast( ref.getProperty( Constants.DRIVER_ID ) );
+        debug( "driver lost: " + Util.showDriver( ref ) );
+        m_drivers.remove( driverId );
+
+        // check if devices have become idle
+        // after some time
+        schedule( new CheckForIdleDevices() );
+
+    }
+
+
+    public void deviceAdded( ServiceReference ref, Object device )
+    {
+        m_devices.put( ref, device );
+        debug( "device appeared: " + Util.showDevice( ref ) );
+        submit( new DriverAttachAlgorithm( ref, device ) );
+    }
+
+
+    public void deviceModified( ServiceReference ref, Object device )
+    {
+        debug( "device modified: " + Util.showDevice( ref ) );
+        // nothing further to do ?
+        // DeviceAttributes da = m_devices.get(ref);
+        // submit(new DriverAttachAlgorithm(da));
+    }
+
+
+    public void deviceRemoved( ServiceReference ref )
+    {
+        debug( "device removed: " + Util.showDevice( ref ) );
+        m_devices.remove( ref );
+        // nothing further to do ?
+        // the services that use this
+        // device should track it.
+    }
+
+
+    /**
+     * perform this task as soon as possible.
+     * 
+     * @param task
+     *            the task
+     */
+    private void submit( Callable<Object> task )
+    {
+        m_worker.submit( new LoggedCall( task ) );
+    }
+
+
+    /**
+     * perform this task after the default delay.
+     * 
+     * @param task
+     *            the task
+     */
+    private void schedule( Callable<Object> task )
+    {
+        m_delayed.schedule( new DelayedCall( task ), DEFAULT_TIMEOUT_SEC, TimeUnit.SECONDS );
+    }
+
+    // worker callables
+
+    /**
+     * Callable used to start the DeviceManager. It either waits (blocking the
+     * worker thread) for the framework to start, or if it has already started,
+     * returns immediately, freeing up the worker thread.
+     * 
+     * @author dennisg
+     * 
+     */
+    private class WaitForStartFramework implements Callable<Object>, FrameworkListener
+    {
+
+        private final CountDownLatch m_latch = new CountDownLatch( 1 );
+
+
+        public Object call() throws Exception
+        {
+            boolean addedAsListener = false;
+            if ( m_context.getBundle( 0 ).getState() == Bundle.ACTIVE )
+            {
+                m_latch.countDown();
+                debug( "Starting Device Manager immediately" );
+            }
+            else
+            {
+                m_context.addFrameworkListener( this );
+                addedAsListener = true;
+                debug( "Waiting for framework to start" );
+            }
+
+            m_latch.await();
+            for ( Map.Entry<ServiceReference, Object> entry : m_devices.entrySet() )
+            {
+                submit( new DriverAttachAlgorithm( entry.getKey(), entry.getValue() ) );
+            }
+            // cleanup
+            if ( addedAsListener )
+            {
+                m_context.removeFrameworkListener( this );
+            }
+            return null;
+        }
+
+
+        // FrameworkListener method
+        public void frameworkEvent( FrameworkEvent event )
+        {
+            switch ( event.getType() )
+            {
+                case FrameworkEvent.STARTED:
+                    debug( "Framework has started" );
+                    m_latch.countDown();
+                    break;
+            }
+        }
+
+
+        @Override
+        public String toString()
+        {
+            return getClass().getSimpleName();
+        }
+    }
+
+    private class LoggedCall implements Callable<Object>
+    {
+
+        private final Callable<Object> m_call;
+
+
+        public LoggedCall( Callable<Object> call )
+        {
+            m_call = call;
+        }
+
+
+        private String getName()
+        {
+            return m_call.getClass().getSimpleName();
+        }
+
+
+        public Object call() throws Exception
+        {
+
+            try
+            {
+                return m_call.call();
+            }
+            catch ( Exception e )
+            {
+                error( "call failed: " + getName(), e );
+                throw e;
+            }
+            catch ( Throwable e )
+            {
+                error( "call failed: " + getName(), e );
+                throw new RuntimeException( e );
+            }
+        }
+
+    }
+
+    private class DelayedCall implements Callable<Object>
+    {
+
+        private final Callable<Object> m_call;
+
+
+        public DelayedCall( Callable<Object> call )
+        {
+            m_call = call;
+        }
+
+
+        private String getName()
+        {
+            return m_call.getClass().getSimpleName();
+        }
+
+
+        public Object call() throws Exception
+        {
+            info( "Delayed call: " + getName() );
+            return m_worker.submit( m_call );
+        }
+    }
+
+    /**
+     * Checks for Idle devices, and attaches them
+     * 
+     * @author dennisg
+     * 
+     */
+    private class CheckForIdleDevices implements Callable<Object>
+    {
+
+        public Object call() throws Exception
+        {
+            debug( "START - check for idle devices" );
+            for ( ServiceReference ref : getIdleDevices() )
+            {
+                info( "IDLE: " + ref.getBundle().getSymbolicName() );
+                submit( new DriverAttachAlgorithm( ref, m_devices.get( ref ) ) );
+            }
+
+            submit( new IdleDriverUninstallAlgorithm() );
+            debug( "STOP - check for idle devices" );
+            return null;
+        }
+
+
+        /**
+         * get a list of all idle devices.
+         * 
+         * @return
+         */
+        private List<ServiceReference> getIdleDevices()
+        {
+            List<ServiceReference> list = new ArrayList<ServiceReference>();
+
+            for ( ServiceReference ref : m_devices.keySet() )
+            {
+                info( "checking if idle: " + ref.getBundle().getSymbolicName() );
+
+                final Bundle[] usingBundles = ref.getUsingBundles();
+                for ( Bundle bundle : usingBundles )
+                {
+                    if ( isDriverBundle( bundle ) )
+                    {
+                        info( "used by driver: " + bundle.getSymbolicName() );
+                        debug( "not idle: " + ref.getBundle().getSymbolicName() );
+                        list.add( ref );
+                        break;
+                    }
+                }
+            }
+            return list;
+        }
+    }
+
+
+    private boolean isDriverBundle( Bundle bundle )
+    {
+        ServiceReference[] refs = bundle.getRegisteredServices();
+        for ( ServiceReference ref : refs )
+        {
+            if ( m_driverImplFilter.match( ref ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 
+     * Used to uninstall unused drivers
+     * 
+     * @author dennisg
+     * 
+     */
+    private class IdleDriverUninstallAlgorithm implements Callable<Object>
+    {
+
+        public Object call() throws Exception
+        {
+
+            info( "cleaning driver cache" );
+            for ( DriverAttributes da : m_drivers.values() )
+            {
+                // just call the tryUninstall; the da itself
+                // will know if it should really uninstall the driver.
+                da.tryUninstall();
+            }
+
+            return null;
+        }
+    }
+
+    private class DriverAttachAlgorithm implements Callable<Object>
+    {
+
+        private final ServiceReference m_ref;
+
+        private final Device m_device;
+
+        private List<DriverAttributes> m_included;
+
+        private List<DriverAttributes> m_excluded;
+
+        private final DriverLoader m_driverLoader;
+
+        private DriverAttributes m_finalDriver;
+
+
+        public DriverAttachAlgorithm( ServiceReference ref, Object obj )
+        {
+            m_ref = ref;
+            if ( m_deviceImplFilter.match( ref ) )
+            {
+                m_device = Device.class.cast( obj );
+            }
+            else
+            {
+                m_device = null;
+            }
+
+            m_driverLoader = new DriverLoader( DeviceManager.this, m_context );
+        }
+
+
+        @SuppressWarnings("all")
+        private Dictionary createDictionary( ServiceReference ref )
+        {
+            final Properties p = new Properties();
+
+            for ( String key : ref.getPropertyKeys() )
+            {
+                p.put( key, ref.getProperty( key ) );
+            }
+            return p;
+        }
+
+
+        @SuppressWarnings("all")
+        public Object call() throws Exception
+        {
+            info( "finding suitable driver for: " + Util.showDevice( m_ref ) );
+
+            final Dictionary dict = createDictionary( m_ref );
+
+            // first create a copy of all the drivers that are already there.
+            // during the process, drivers will be added, but also excluded.
+            m_included = new ArrayList<DriverAttributes>( m_drivers.values() );
+            m_excluded = new ArrayList<DriverAttributes>();
+
+            // first find matching driver bundles
+            // if there are no driver locators
+            // we'll have to do with the drivers that where
+            // added 'manually'
+            List<String> driverIds = m_driverLoader.findDrivers( m_locators, dict );
+
+            // remove the driverIds that are already available
+            for ( DriverAttributes da : m_drivers.values() )
+            {
+                driverIds.remove( da.getDriverId() );
+            }
+            driverIds.removeAll( m_drivers.keySet() );
+            try
+            {
+                return driverAttachment( dict, driverIds.toArray( new String[0] ) );
+            }
+            finally
+            {
+                // unload loaded drivers
+                // that were unnecessarily loaded
+                m_driverLoader.unload( m_finalDriver );
+            }
+        }
+
+
+        @SuppressWarnings("all")
+        private Object driverAttachment( Dictionary dict, String[] driverIds ) throws Exception
+        {
+            m_finalDriver = null;
+
+            // remove the excluded drivers
+            m_included.removeAll( m_excluded );
+
+            // now load the drivers
+            List<ServiceReference> driverRefs = m_driverLoader.loadDrivers( m_locators, driverIds );
+            // these are the possible driver references that have been added
+            // add the to the list of included drivers
+            for ( ServiceReference serviceReference : driverRefs )
+            {
+                DriverAttributes da = m_drivers.get( serviceReference );
+                if ( da != null )
+                {
+                    m_included.add( da );
+                }
+            }
+
+            // now start matching all drivers
+            final DriverMatcher mi = new DriverMatcher( DeviceManager.this );
+
+            for ( DriverAttributes driver : m_included )
+            {
+                try
+                {
+                    int match = driver.match( m_ref );
+                    if ( match <= Device.MATCH_NONE )
+                        continue;
+                    mi.add( match, driver );
+                }
+                catch ( Throwable t )
+                {
+                    error( "match threw an exception", new Exception( t ) );
+                }
+            }
+
+            // get the best match
+            Match bestMatch;
+
+            // local copy
+            final DriverSelector selector = m_selector;
+            if ( selector != null )
+            {
+                bestMatch = mi.selectBestMatch( m_ref, selector );
+            }
+            else
+            {
+                bestMatch = mi.getBestMatch();
+            }
+
+            if ( bestMatch == null )
+            {
+                noDriverFound();
+                // really return
+                return null;
+            }
+
+            String driverId = String.class.cast( bestMatch.getDriver().getProperty( Constants.DRIVER_ID ) );
+
+            debug( "best match: " + driverId );
+            m_finalDriver = m_drivers.get( bestMatch.getDriver() );
+
+            if ( m_finalDriver == null )
+            {
+                error( "we found a driverId, but not the corresponding driver: " + driverId, null );
+                noDriverFound();
+                return null;
+            }
+
+            // here we get serious...
+            try
+            {
+                debug( "attaching to: " + driverId );
+                String newDriverId = m_finalDriver.attach( m_ref );
+                if ( newDriverId == null )
+                {
+                    // successful attach
+                    return null;
+                }
+                // its a referral
+                info( "attach led to a referral to: " + newDriverId );
+                m_excluded.add( m_finalDriver );
+                return driverAttachment( dict, new String[]
+                    { newDriverId } );
+            }
+            catch ( Throwable t )
+            {
+                error( "attach failed due to an exception", t );
+            }
+            m_excluded.add( m_finalDriver );
+            return driverAttachment( dict, driverIds );
+        }
+
+
+        private void noDriverFound()
+        {
+            debug( "no suitable driver found for: " + Util.showDevice( m_ref ) );
+            if ( m_device != null )
+            {
+                m_device.noDriverFound();
+            }
+        }
+
+
+        @Override
+        public String toString()
+        {
+            return getClass().getSimpleName();// + ": " +
+            // Util.showDevice(m_ref);
+        }
+
+    }
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/DriverAttributes.java b/deviceaccess/src/main/java/org/apache/felix/das/DriverAttributes.java
new file mode 100644
index 0000000..233be2d
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/DriverAttributes.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das;
+
+
+import org.apache.felix.das.util.DriverLoader;
+import org.apache.felix.das.util.Util;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.device.Driver;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DriverAttributes
+{
+
+    private final Bundle m_bundle;
+
+    private final ServiceReference m_ref;
+
+    private final Driver m_driver;
+
+    private final boolean m_dynamic;
+
+    public DriverAttributes( ServiceReference ref, Driver driver )
+    {
+        m_ref = ref;
+        m_driver = driver;
+        m_bundle = ref.getBundle();
+        m_dynamic = m_bundle.getLocation().startsWith( DriverLoader.DRIVER_LOCATION_PREFIX );
+    }
+
+
+    public ServiceReference getReference()
+    {
+        return m_ref;
+    }
+
+
+    public String getDriverId()
+    {
+        return m_ref.getProperty( Constants.DRIVER_ID ).toString();
+    }
+
+
+    public int match( ServiceReference ref ) throws Exception
+    {
+        return m_driver.match( ref );
+    }
+
+
+    /**
+     * a driver bundle is idle if it isn't connected to a device bundle.
+     * 
+     * @return
+     */
+    private boolean isInUse()
+    {
+        ServiceReference[] used = m_bundle.getServicesInUse();
+        if ( used == null || used.length == 0 )
+        {
+            return false;
+        }
+
+        for ( ServiceReference ref : used )
+        {
+            if ( Util.isDeviceInstance( ref ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    public String attach( ServiceReference ref ) throws Exception
+    {
+        return m_driver.attach( ref );
+    }
+
+
+    public void tryUninstall() throws BundleException
+    {
+        if ( !isInUse() )
+        {
+            // only install if _we_ loaded the driver
+            if ( m_dynamic )
+            {
+                m_bundle.uninstall();
+            }
+        }
+    }
+
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/Log.java b/deviceaccess/src/main/java/org/apache/felix/das/Log.java
new file mode 100644
index 0000000..698f4cf
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/Log.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface Log
+{
+
+    public void debug( String message );
+
+
+    public void info( String message );
+
+
+    public void warning( String message );
+
+
+    public void error( String message, Throwable e );
+
+}
\ No newline at end of file
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/NamedThreadFactory.java b/deviceaccess/src/main/java/org/apache/felix/das/NamedThreadFactory.java
new file mode 100644
index 0000000..c4ad6b3
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/NamedThreadFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das;
+
+
+import java.util.concurrent.ThreadFactory;
+
+
+/**
+ * This class is able to create a named Thread.
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class NamedThreadFactory implements ThreadFactory
+{
+
+    private final String m_name;
+
+
+    public NamedThreadFactory( String name )
+    {
+        m_name = name;
+    }
+
+
+    public Thread newThread( Runnable r )
+    {
+        return new Thread( r, m_name );
+    }
+
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/util/DeviceAnalyzer.java b/deviceaccess/src/main/java/org/apache/felix/das/util/DeviceAnalyzer.java
new file mode 100644
index 0000000..e34eee7
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/util/DeviceAnalyzer.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das.util;
+
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.device.Device;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DeviceAnalyzer
+{
+
+    private LogService m_log;
+
+    private Filter deviceImpl;
+
+    private Filter validCategory;
+
+    private final BundleContext m_context;
+
+
+    public DeviceAnalyzer( BundleContext context )
+    {
+        m_context = context;
+    }
+
+
+    @SuppressWarnings("unused")
+    private void start() throws InvalidSyntaxException
+    {
+        String deviceString = Util.createFilterString( "(%s=%s)", new Object[]
+            { org.osgi.framework.Constants.OBJECTCLASS, Device.class.getName() } );
+
+        deviceImpl = m_context.createFilter( deviceString );
+
+        String categoryString = Util.createFilterString( "(%s=%s)", new Object[]
+            { Constants.DEVICE_CATEGORY, "*" } );
+
+        validCategory = m_context.createFilter( categoryString );
+    }
+
+
+    /**
+     * used to analyze invalid devices
+     * 
+     * @param ref
+     */
+    public void deviceAdded( ServiceReference ref )
+    {
+
+        if ( deviceImpl.match( ref ) )
+        {
+            return;
+        }
+        if ( validCategory.match( ref ) )
+        {
+            Object cat = ref.getProperty( Constants.DEVICE_CATEGORY );
+            if ( !String[].class.isInstance( cat ) )
+            {
+                m_log.log( LogService.LOG_ERROR, "invalid device: invalid device category: " + Util.showDevice( ref ) );
+            }
+            if ( String[].class.cast( cat ).length == 0 )
+            {
+                m_log.log( LogService.LOG_ERROR, "invalid device: empty device category: " + Util.showDevice( ref ) );
+            }
+        }
+        else
+        {
+            m_log.log( LogService.LOG_ERROR, "invalid device: no device category: " + Util.showDevice( ref ) );
+        }
+    }
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/util/DriverAnalyzer.java b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverAnalyzer.java
new file mode 100644
index 0000000..eef7a71
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverAnalyzer.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das.util;
+
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.log.LogService;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DriverAnalyzer
+{
+
+    private LogService m_log;
+
+
+    /**
+     * used to analyze invalid drivers
+     * 
+     * @param ref
+     */
+    public void driverAdded( ServiceReference ref )
+    {
+        Object driverId = ref.getProperty( Constants.DRIVER_ID );
+        if ( driverId == null || !String.class.isInstance( driverId ) )
+        {
+            m_log.log( LogService.LOG_ERROR, "invalid driver: no driver id: " + Util.showDriver( ref ) );
+            return;
+        }
+        if ( String.class.isInstance( driverId ) )
+        {
+            String value = String.class.cast( driverId );
+            if ( value.length() == 0 )
+            {
+                m_log.log( LogService.LOG_ERROR, "invalid driver: empty driver id: " + Util.showDriver( ref ) );
+            }
+        }
+    }
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/util/DriverLoader.java b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverLoader.java
new file mode 100644
index 0000000..199b8ec
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverLoader.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das.util;
+
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.List;
+
+import org.apache.felix.das.DriverAttributes;
+import org.apache.felix.das.Log;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.device.DriverLocator;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DriverLoader
+{
+
+    private final BundleContext m_context;
+
+    private final Log m_log;
+
+    public final static String DRIVER_LOCATION_PREFIX = "_DD_";
+
+    /**
+     * to keep track of all loaded drivers
+     */
+    private final List<ServiceReference> m_loadedDrivers;
+
+
+    public DriverLoader( Log log, BundleContext context )
+    {
+        m_log = log;
+        m_context = context;
+        m_loadedDrivers = new ArrayList<ServiceReference>();
+    }
+
+
+    @SuppressWarnings("all")
+    public List<String> findDrivers( Collection<DriverLocator> locators, Dictionary dict )
+    {
+        final List<String> list = new ArrayList<String>();
+        for ( DriverLocator locator : locators )
+        {
+            list.addAll( findDrivers( locator, dict ) );
+        }
+
+        return list;
+    }
+
+
+    @SuppressWarnings("all")
+    private List<String> findDrivers( DriverLocator locator, Dictionary dict )
+    {
+        final List<String> list = new ArrayList<String>();
+        try
+        {
+            String[] ids = locator.findDrivers( dict );
+            if ( ids != null )
+            {
+                list.addAll( Arrays.asList( ids ) );
+            }
+        }
+        catch ( Exception e )
+        {
+            // ignore, will also frequently happen (?)
+            // m_log.error("findDrivers failed for: " + locator, e);
+        }
+
+        return list;
+    }
+
+
+    /**
+     * load all drivers that belong to the given driver Ids
+     * 
+     * @param locator
+     * @param driverIds
+     */
+    public List<ServiceReference> loadDrivers( List<DriverLocator> locators, String[] driverIds )
+    {
+        List<ServiceReference> driverRefs = new ArrayList<ServiceReference>();
+
+        for ( String driverId : driverIds )
+        {
+            driverRefs.addAll( loadDriver( locators, driverId ) );
+        }
+
+        return driverRefs;
+    }
+
+
+    private List<ServiceReference> loadDriver( List<DriverLocator> locators, String driverId )
+    {
+        List<ServiceReference> driverRefs = new ArrayList<ServiceReference>();
+
+        for ( DriverLocator driverLocator : locators )
+        {
+            List<ServiceReference> drivers = loadDriver( driverLocator, driverId );
+            driverRefs.addAll( drivers );
+            if ( drivers.size() > 0 )
+            {
+                break;
+            }
+        }
+
+        return driverRefs;
+    }
+
+
+    private List<ServiceReference> loadDriver( DriverLocator locator, String driverId )
+    {
+        List<ServiceReference> driverRefs = new ArrayList<ServiceReference>();
+        try
+        {
+            InputStream in = locator.loadDriver( driverId );
+            // System.out.println(driverId + ", " + locator + " returned: " +
+            // in);
+            Bundle driverBundle = m_context.installBundle( DRIVER_LOCATION_PREFIX + driverId, in );
+
+            driverBundle.start();
+
+            ServiceReference[] refs = driverBundle.getRegisteredServices();
+
+            driverRefs.addAll( Arrays.asList( refs ) );
+            // keep track of them locally
+            m_loadedDrivers.addAll( Arrays.asList( refs ) );
+
+        }
+        catch ( Throwable t )
+        {
+            // ignore, this will happen frequently, if there are multiple
+            // locators
+        }
+        return driverRefs;
+    }
+
+
+    public void unload( DriverAttributes finalDriver )
+    {
+
+        ServiceReference finalRef = null;
+        if ( finalDriver != null )
+        {
+            finalRef = finalDriver.getReference();
+            m_log.debug( "unloading all except: " + finalRef.getProperty( Constants.DRIVER_ID ) );
+        }
+        for ( ServiceReference ref : m_loadedDrivers )
+        {
+            if ( !ref.equals( finalRef ) )
+            {
+                try
+                {
+                    m_log.debug( "uninstalling: " + ref.getProperty( Constants.DRIVER_ID ) );
+                    ref.getBundle().uninstall();
+                }
+                catch ( BundleException e )
+                {
+                    m_log.warning( "unable to uninstall: " + ref.getProperty( Constants.DRIVER_ID ) );
+                }
+            }
+        }
+    }
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/util/DriverMatcher.java b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverMatcher.java
new file mode 100644
index 0000000..7bc8eea
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/util/DriverMatcher.java
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das.util;
+
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.felix.das.DriverAttributes;
+import org.apache.felix.das.Log;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Constants;
+import org.osgi.service.device.DriverSelector;
+import org.osgi.service.device.Match;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DriverMatcher
+{
+
+    private final Log m_log;
+
+    SortedMap<Integer, List<DriverAttributes>> m_map = new TreeMap<Integer, List<DriverAttributes>>();
+
+    List<Match> m_matches = new ArrayList<Match>();
+
+
+    public DriverMatcher( Log log )
+    {
+        m_log = log;
+    }
+
+
+    // we keep track of the driver attributes in two
+    // lists, one to aid us if there is no driver selector, one
+    // if there is...
+    public void add( Integer match, DriverAttributes value )
+    {
+        List<DriverAttributes> da = get( match );
+        da.add( value );
+        m_matches.add( new MatchImpl( value.getReference(), match ) );
+    }
+
+
+    private List<DriverAttributes> get( Integer key )
+    {
+        List<DriverAttributes> da = m_map.get( key );
+        if ( da == null )
+        {
+            m_map.put( ( Integer ) key, new ArrayList<DriverAttributes>() );
+        }
+        return m_map.get( key );
+    }
+
+
+    public Match getBestMatch()
+    {
+        if ( m_map.isEmpty() )
+        {
+            return null;
+        }
+
+        int matchValue = m_map.lastKey();
+
+        // these are the matches that
+        // got the highest match value
+        List<DriverAttributes> das = m_map.get( matchValue );
+        if ( das.size() == 1 )
+        {
+            // a shortcut: there's only one with the highest match
+            return new MatchImpl( das.get( 0 ).getReference(), matchValue );
+        }
+
+        // get the highest ranking driver
+        final SortedMap<ServiceReference, Match> matches = new TreeMap<ServiceReference, Match>( new ServicePriority() );
+
+        for ( DriverAttributes da : das )
+        {
+            matches.put( da.getReference(), new MatchImpl( da.getReference(), matchValue ) );
+        }
+
+        ServiceReference last = matches.lastKey();
+        return matches.get( last );
+    }
+
+
+    public Match selectBestMatch( ServiceReference deviceRef, DriverSelector selector )
+    {
+        Match[] matches = m_matches.toArray( new Match[0] );
+        try
+        {
+            int index = selector.select( deviceRef, matches );
+            if ( index != DriverSelector.SELECT_NONE && index >= 0 && index < matches.length )
+            {
+                return matches[index];
+            }
+        }
+        catch ( Exception e )
+        {
+            m_log.error( "exception thrown in DriverSelector.select()", e );
+        }
+        return null;
+    }
+
+    private class MatchImpl implements Match
+    {
+
+        private final ServiceReference ref;
+        private final int match;
+
+
+        public MatchImpl( ServiceReference ref, int match )
+        {
+            this.ref = ref;
+            this.match = match;
+        }
+
+
+        public ServiceReference getDriver()
+        {
+            return ref;
+        }
+
+
+        public int getMatchValue()
+        {
+            return match;
+        }
+
+
+        public String toString()
+        {
+            return "[MatchImpl: DRIVER_ID=" + ref.getProperty( Constants.DRIVER_ID ) + ", match=" + match + "]";
+        }
+
+    }
+
+    private class ServicePriority implements Comparator<ServiceReference>
+    {
+
+        private int getValue( ServiceReference ref, String key, int defaultValue )
+        {
+            Object obj = ref.getProperty( key );
+            if ( obj == null )
+            {
+                return defaultValue;
+            }
+            try
+            {
+                return Integer.class.cast( obj );
+            }
+            catch ( Exception e )
+            {
+                return defaultValue;
+            }
+        }
+
+
+        public int compare( ServiceReference o1, ServiceReference o2 )
+        {
+            int serviceRanking1 = getValue( o1, org.osgi.framework.Constants.SERVICE_RANKING, 0 );
+            int serviceRanking2 = getValue( o2, org.osgi.framework.Constants.SERVICE_RANKING, 0 );
+
+            if ( serviceRanking1 != serviceRanking2 )
+            {
+                return ( serviceRanking1 - serviceRanking2 );
+            }
+            int serviceId1 = getValue( o1, org.osgi.framework.Constants.SERVICE_ID, Integer.MAX_VALUE );
+            int serviceId2 = getValue( o2, org.osgi.framework.Constants.SERVICE_ID, Integer.MAX_VALUE );
+
+            return ( serviceId2 - serviceId1 );
+        }
+    }
+}
diff --git a/deviceaccess/src/main/java/org/apache/felix/das/util/Util.java b/deviceaccess/src/main/java/org/apache/felix/das/util/Util.java
new file mode 100644
index 0000000..69ba60d
--- /dev/null
+++ b/deviceaccess/src/main/java/org/apache/felix/das/util/Util.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.das.util;
+
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.device.Device;
+
+
+/**
+ * TODO: add javadoc
+ * 
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Util
+{
+
+    private Util()
+    {
+    }
+
+
+    public static String showDriver( ServiceReference ref )
+    {
+        Object objectClass = ref.getProperty( Constants.OBJECTCLASS );
+        Object driverId = ref.getProperty( org.osgi.service.device.Constants.DRIVER_ID );
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append( "Driver: " );
+        buffer.append( Constants.OBJECTCLASS ).append( "=" );
+
+        if ( String[].class.isInstance( objectClass ) )
+        {
+            buffer.append( enumerateStringArray( String[].class.cast( objectClass ) ) );
+        }
+        else
+        {
+            buffer.append( objectClass );
+        }
+        buffer.append( " " );
+
+        buffer.append( org.osgi.service.device.Constants.DRIVER_ID ).append( "=" );
+        buffer.append( driverId );
+        return buffer.toString();
+    }
+
+
+    public static boolean isDeviceInstance( ServiceReference ref )
+    {
+        try
+        {
+            Filter device = createFilter( "(%s=%s)", new Object[]
+                { Constants.OBJECTCLASS, Device.class.getName() } );
+            return device.match( ref );
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+        }
+        return false;
+    }
+
+
+    public static String createFilterString( String input, Object[] data )
+    {
+        return String.format( input, data );
+    }
+
+
+    public static Filter createFilter( String input, Object[] data ) throws InvalidSyntaxException
+    {
+        return FrameworkUtil.createFilter( String.format( input, data ) );
+    }
+
+
+    public static String showDevice( ServiceReference ref )
+    {
+        Object objectClass = ref.getProperty( Constants.OBJECTCLASS );
+        Object category = ref.getProperty( org.osgi.service.device.Constants.DEVICE_CATEGORY );
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append( "Device: " );
+        buffer.append( Constants.OBJECTCLASS ).append( "=" );
+
+        appendObject( buffer, objectClass );
+        buffer.append( " " );
+
+        buffer.append( org.osgi.service.device.Constants.DEVICE_CATEGORY ).append( "=" );
+        appendObject( buffer, category );
+
+        buffer.append( "\n{ " );
+        String[] keys = ref.getPropertyKeys();
+        
+        for ( String key : keys )
+        {
+            if ( key.equals( Constants.OBJECTCLASS ) )
+            {
+                continue;
+            }
+            if ( key.equals( org.osgi.service.device.Constants.DEVICE_CATEGORY ) )
+            {
+                continue;
+            }
+            buffer.append( key ).append( "=" );
+            appendObject( buffer, ref.getProperty( key ) );
+            buffer.append( " " );
+        }
+        buffer.append( "}\n" );
+
+        return buffer.toString();
+    }
+
+
+    private static void appendObject( StringBuffer buffer, Object obj )
+    {
+        if ( String[].class.isInstance( obj ) )
+        {
+            buffer.append( enumerateStringArray( String[].class.cast( obj ) ) );
+        }
+        else
+        {
+            buffer.append( obj );
+        }
+    }
+
+
+    private static String enumerateStringArray( String[] strings )
+    {
+        StringBuffer buffer = new StringBuffer();
+
+        buffer.append( "[" );
+        for ( String str : strings )
+        {
+            buffer.append( str );
+            buffer.append( " " );
+        }
+        buffer.deleteCharAt( buffer.length() - 1 );
+        buffer.append( "]" );
+        return buffer.toString();
+    }
+}