Improved support for aspects and tracking them using whiteboard style listeners. Extended our internal ServiceTracker to make it aware of aspects.

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@943054 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java
index 31c7a20..230886d 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/AspectImpl.java
@@ -63,7 +63,7 @@
         Object service = properties[1];
         Properties props = new Properties();
         // first add our aspect property
-        props.put(DependencyManager.ASPECT, "true");
+        props.put(DependencyManager.ASPECT, ref.getProperty(Constants.SERVICE_ID));
         // and the ranking
         props.put(Constants.SERVICE_RANKING, Integer.valueOf(m_ranking));
         String[] keys = ref.getPropertyKeys();
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/tracker/ServiceTracker.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/tracker/ServiceTracker.java
index eb0f147..09a42cf 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/tracker/ServiceTracker.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/tracker/ServiceTracker.java
@@ -17,7 +17,11 @@
 
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.util.ServiceUtil;
 import org.osgi.framework.AllServiceListener;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -806,6 +810,86 @@
 	 * @ThreadSafe
 	 */
 	class Tracked extends AbstractTracked implements ServiceListener {
+	    /** A list of services that are currently hidden because there is an aspect available with a higher ranking. */
+	    private final List m_hidden = new ArrayList();
+
+	    /**
+	     * Returns the highest hidden aspect for the specified service ID.
+	     * 
+	     * @param serviceId the service ID
+	     * @return a service reference, or <code>null</code> if there was no such service
+	     */
+	    private ServiceReference highestHidden(long serviceId) {
+	        ServiceReference result = null;
+	        int max = Integer.MIN_VALUE;
+	        for (int i = 0; i < m_hidden.size(); i++) {
+	            ServiceReference ref = (ServiceReference) m_hidden.get(i);
+	            Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+	            Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+	            if ((aid != null && aid.longValue() == serviceId) 
+	                || (aid == null && sid != null && sid.longValue() == serviceId)) {
+	                Integer ranking = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+	                int r = 0;
+	                if (ranking != null) {
+	                    r = ranking.intValue();
+	                }
+	                if (r > max) {
+	                    max = r;
+	                    result = ref;
+	                }
+	            }
+	        }
+	        return result;
+	    }
+	    
+        /**
+         * Returns the highest tracked service for the specified service ID.
+         * 
+         * @param serviceId the service ID
+         * @return a service reference, or <code>null</code> if there was no such service
+         */
+        private ServiceReference highestTracked(long serviceId) {
+            ServiceReference result = null;
+            int max = Integer.MIN_VALUE;
+            Object[] trackedServices = getTracked(new Object[] {});
+            for (int i = 0; i < trackedServices.length; i++) {
+                ServiceReference ref = (ServiceReference) trackedServices[i];
+                Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+                Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+                if ((aid != null && aid.longValue() == serviceId) 
+                    || (aid == null && sid != null && sid.longValue() == serviceId)) {
+                    Integer ranking = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+                    int r = 0;
+                    if (ranking != null) {
+                        r = ranking.intValue();
+                    }
+                    if (r > max) {
+                        max = r;
+                        result = ref;
+                    }
+                }
+            }
+            return result;
+        }
+
+        /**
+         * Hide a service reference, placing it in the list of hidden services.
+         * 
+         * @param ref the service reference to add to the hidden list
+         */
+        private void hide(ServiceReference ref) {
+            m_hidden.add(ref);
+        }
+        
+        /**
+         * Unhide a service reference, removing it from the list of hidden services.
+         * 
+         * @param ref the service reference to remove from the hidden list
+         */
+        private void unhide(ServiceReference ref) {
+            m_hidden.remove(ref);
+        }
+	    
 		/**
 		 * Tracked constructor.
 		 */
@@ -838,9 +922,51 @@
 			switch (event.getType()) {
 				case ServiceEvent.REGISTERED :
 				case ServiceEvent.MODIFIED :
-					if (listenerFilter != null) { // service listener added with
-						// filter
-						track(reference, event);
+				    ServiceReference higher = null;
+				    ServiceReference lower = null;
+				    boolean isAspect = ServiceUtil.isAspect(reference);
+				    if (isAspect) {
+    				    long sid = ServiceUtil.getServiceId(reference);
+    				    ServiceReference sr = highestTracked(sid);
+    				    if (sr != null) {
+    				        int ranking = ServiceUtil.getRanking(reference);
+    				        int trackedRanking = ServiceUtil.getRanking(sr);
+    				        if (ranking > trackedRanking) {
+    				            // found a higher ranked one!
+    				            if (DEBUG) {
+    				                System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a higher ranked aspect: " + ServiceUtil.toString(reference) + " vs " + ServiceUtil.toString(sr));
+    				            }
+    				            higher = sr;
+    				        }
+    				        else {
+    				            // found lower ranked one!
+                                if (DEBUG) {
+                                    System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a lower ranked aspect: " + ServiceUtil.toString(reference) + " vs " + ServiceUtil.toString(sr));
+                                }
+    				            lower = sr;
+    				        }
+    				    }
+				    }
+				    
+					if (listenerFilter != null) { // service listener added with filter
+					    if (lower != null) {
+					        hide(reference);
+					    }
+					    else {
+					        try {
+					            track(reference, event);
+					        }
+					        finally {
+    					        if (higher != null) {
+    					            try {
+    					                untrack(higher, null);
+    					            }
+    					            finally {
+    					                hide(higher);
+    					            }
+    					        }
+					        }
+					    }
 						/*
 						 * If the customizer throws an unchecked exception, it
 						 * is safe to let it propagate
@@ -848,14 +974,51 @@
 					}
 					else { // service listener added without filter
 						if (filter.match(reference)) {
-							track(reference, event);
+	                        if (lower != null) {
+	                            hide(reference);
+	                        }
+	                        else {
+	                            try {
+	                                track(reference, event);
+	                            }
+	                            finally {
+    	                            if (higher != null) {
+    	                                try {
+    	                                    untrack(higher, null);
+    	                                }
+    	                                finally {
+    	                                    hide(higher);
+    	                                }
+    	                            }
+	                            }
+	                        }
 							/*
 							 * If the customizer throws an unchecked exception,
 							 * it is safe to let it propagate
 							 */
 						}
 						else {
-							untrack(reference, event);
+		                    higher = null;
+		                    isAspect = ServiceUtil.isAspect(reference);
+		                    if (isAspect) {
+		                        long sid = ServiceUtil.getServiceId(reference);
+		                        ServiceReference sr = highestHidden(sid);
+		                        if (sr != null) {
+	                                if (DEBUG) {
+	                                    System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a hidden aspect: " + ServiceUtil.toString(reference));
+	                                }
+		                            higher = sr;
+		                        }
+		                    }
+		                    try {
+    		                    if (higher != null) {
+    		                        unhide(higher);
+    		                        track(higher, null);
+    		                    }
+		                    }
+		                    finally {
+		                        untrack(reference, event);
+		                    }
 							/*
 							 * If the customizer throws an unchecked exception,
 							 * it is safe to let it propagate
@@ -865,7 +1028,27 @@
 					break;
                 case 8 /* ServiceEvent.MODIFIED_ENDMATCH */ :
 				case ServiceEvent.UNREGISTERING :
-					untrack(reference, event);
+                    higher = null;
+                    isAspect = ServiceUtil.isAspect(reference);
+                    if (isAspect) {
+                        long sid = ServiceUtil.getServiceId(reference);
+                        ServiceReference sr = highestHidden(sid);
+                        if (sr != null) {
+                            if (DEBUG) {
+                                System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a hidden aspect: " + ServiceUtil.toString(reference));
+                            }
+                            higher = sr;
+                        }
+                    }
+                    try {
+                        if (higher != null) {
+                            unhide(higher);
+                            track(higher, null);
+                        }
+                    }
+                    finally {
+                        untrack(reference, event);
+                    }
 					/*
 					 * If the customizer throws an unchecked exception, it is
 					 * safe to let it propagate
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/util/ServiceUtil.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/util/ServiceUtil.java
new file mode 100644
index 0000000..d6d8d36
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/util/ServiceUtil.java
@@ -0,0 +1,103 @@
+/*
+ * 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.dm.util;
+
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * OSGi service utilities.
+ */
+public class ServiceUtil {
+    /**
+     * Returns the service ranking of a service, based on its service reference. If
+     * the service has a property specifying its ranking, that will be returned. If
+     * not, the default ranking of zero will be returned.
+     * 
+     * @param ref the service reference to determine the ranking for
+     * @return the ranking
+     */
+    public static int getRanking(ServiceReference ref) {
+        Integer rank = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+        if (rank != null) {
+            return rank.intValue();
+        }
+        return 0;
+    }
+    
+    /**
+     * Returns the service ID of a service, based on its service reference. This
+     * method is aware of service aspects as defined by the dependency manager and
+     * will return the ID of the orginal service if you give it an aspect.
+     * 
+     * @param ref the service reference to determine the service ID of
+     * @return the service ID
+     */
+    public static long getServiceId(ServiceReference ref) {
+        Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+        Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+        if (aid != null) {
+            return aid.longValue();
+        }
+        if (sid != null) {
+            return sid.longValue();
+        }
+        throw new IllegalArgumentException("Invalid service reference, no service ID found");
+    }
+
+    /**
+     * Determines if the service is an aspect as defined by the dependency manager.
+     * Aspects are defined by a property and this method will check for its presence.
+     * 
+     * @param ref the service reference
+     * @return <code>true</code> if it's an aspect, <code>false</code> otherwise
+     */
+    public static boolean isAspect(ServiceReference ref) {
+        Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+        return (aid != null);
+    }
+    
+    /**
+     * Converts a service reference to a string, listing both the bundle it was
+     * registered from and all properties.
+     * 
+     * @param ref the service reference
+     * @return a string representation of the service
+     */
+    public static String toString(ServiceReference ref) {
+        if (ref == null) {
+            throw new IllegalArgumentException("Service reference cannot be null.");
+        }
+        StringBuffer buf = new StringBuffer();
+        
+        buf.append("ServiceReference[" + ref.getBundle().getBundleId() + "]{");
+        String[] keys = ref.getPropertyKeys();
+        for (int i = 0; i < keys.length; i++) {
+            if (i > 0) { 
+                buf.append(','); 
+            }
+            buf.append(keys[i]);
+            buf.append('=');
+            buf.append(ref.getProperty(keys[i]));
+        }
+        buf.append("}");
+        return buf.toString();
+    }
+}