Resolved FELIX-4014 (possible deadlock on ServiceDependencyImpl.handleAspectAwareRemoved), FELIX-4097 (debug logging in ServiceDependency), FELIX-4098 (wrong swap order with multiple threads), FELIX-4099 (support for negate in multi property filter index).


git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1488970 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
index 7a148fe..8a76180 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/DependencyManager.java
@@ -39,9 +39,9 @@
 import org.apache.felix.dm.impl.dependencies.ServiceDependencyImpl;
 import org.apache.felix.dm.impl.dependencies.TemporalServiceDependencyImpl;
 import org.apache.felix.dm.impl.index.AspectFilterIndex;
-import org.apache.felix.dm.impl.index.MultiPropertyExactFilter;
 import org.apache.felix.dm.impl.index.AdapterFilterIndex;
 import org.apache.felix.dm.impl.index.ServiceRegistryCache;
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
 import org.apache.felix.dm.impl.metatype.PropertyMetaDataImpl;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -110,8 +110,7 @@
                     	m_serviceRegistryCache.addFilterIndex(new AdapterFilterIndex());
                     }
                     else {
-                        String[] propList = props[i].split(",");
-                        m_serviceRegistryCache.addFilterIndex(new MultiPropertyExactFilter(propList));
+                    	m_serviceRegistryCache.addFilterIndex(new MultiPropertyFilterIndex(props[i]));
                     }
                 }
             }
@@ -146,7 +145,6 @@
     
     private BundleContext createContext(BundleContext context) {
         if (m_serviceRegistryCache != null) {
-//            System.out.println("DM: Enabling bundle context interceptor for bundle #" + context.getBundle().getBundleId());
             return m_serviceRegistryCache.createBundleContextInterceptor(context);
         }
         else {
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
index aa9400a..dde8953 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/ServiceDependency.java
@@ -214,4 +214,11 @@
 	 * @param isInstanceBound <code>true</code> if this dependency should be instance bound
 	 */
     public ServiceDependency setInstanceBound(boolean isInstanceBound);    
+    
+    /**
+     * Enabled debug logging for this dependency instance. The logging is prefixed with the given identifier.
+     * @param identifier
+     * @return this service dependency.
+     */
+    public ServiceDependency setDebug(String identifier);
 }
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
index 9b11fbb..e2bca98 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ResourceDependencyImpl.java
@@ -200,6 +200,10 @@
     		long counter;
     		Object[] services;
     		synchronized (this) {
+    			if (m_resources.indexOf(resource) == -1) {
+    				m_logger.log(Logger.LOG_WARNING, "handleResourceRemoved called for unknown resource: " + resource);
+    				return;
+    			}
     			m_resourceProperties.remove(m_resources.indexOf(resource));
     		    m_resources.remove(resource);
     			counter = m_resources.size();
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
index b3db89e..1ffe7f7 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/dependencies/ServiceDependencyImpl.java
@@ -28,6 +28,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -83,6 +84,16 @@
     private String m_propagateCallbackMethod;
     private final Map m_sr = new HashMap(); /* <DependencyService, Set<Tuple<ServiceReference, Object>> */
 	private Map m_componentByRank = new HashMap(); /* <Component, Map<Long, Map<Integer, Tuple>>> */
+	private boolean m_debug = false;
+	private String m_debugKey = null;
+	
+	private final LinkedList m_injectionQueue = new LinkedList();
+	
+	public ServiceDependency setDebug(String identifier) {
+		this.m_debug = true;
+		this.m_debugKey = identifier;
+		return this;
+	}
     
     private static final Comparator COMPARATOR = new Comparator() {
         public int getRank(ServiceReference ref) {
@@ -416,7 +427,13 @@
             }
         }
         if (needsStarting) {
-            m_tracker.open();
+        	// when the swapped callback is set, also track the aspects
+        	boolean trackAllServices = false;
+        	boolean trackAllAspects = false;
+        	if (m_callbackSwapped != null) {
+        		trackAllAspects = true;
+        	} 
+        	m_tracker.open(trackAllServices, trackAllAspects);
         }
     }
 
@@ -448,6 +465,9 @@
     }
 
     public void addedService(ServiceReference ref, Object service) {
+    	if (m_debug) {
+    		log("addedservice: " + ref);
+    	}
         boolean makeAvailable = makeAvailable();
         
         Object[] services;
@@ -458,20 +478,32 @@
             DependencyService ds = (DependencyService) services[i];
             if (makeAvailable) {
                 if (ds.isInstantiated() && isInstanceBound() && isRequired()) {
-                    invokeAdded(ds, ref, service);
+                	if (m_debug) {
+                		log("invoke added: " + ref);
+                	}
+                    invokeAdded(ds, ref, service); //**
                 }
-                // The dependency callback will be defered until all required dependency are available.
-                ds.dependencyAvailable(this);
+                // The dependency callback will be deferred until all required dependency are available.
+                if (m_debug) {
+                	log("dependency available: " + ref);
+                }
+                ds.dependencyAvailable(this); //**
                 if (!isRequired()) {
                     // For optional dependency, we always invoke callback, because at this point, we know
                     // that the service has been started, and the service start method has been called.
                     // (See the ServiceImpl.bindService method, which will activate optional dependencies using 
-                    // startTrackingOptional() method). 
-                    invokeAdded(ds, ref, service);
+                    // startTrackingOptional() method).
+                	if (m_debug) {
+                		log("invoke added: " + ref);
+                	}
+                    invokeAdded(ds, ref, service); //**
                 }
             }
             else {
-                ds.dependencyChanged(this);
+            	if (m_debug) {
+            		log("dependency changed: " + ref);
+            	}
+                ds.dependencyChanged(this); //**
                 // At this point, either the dependency is optional (meaning that the service has been started,
                 // because if not, then our dependency would not be active); or the dependency is required,
                 // meaning that either the service is not yet started, or already started.
@@ -479,7 +511,10 @@
                 
                 // we only try to invoke the method here if we are really already instantiated
                 if (ds.isInstantiated() && ds.getCompositionInstances().length > 0) {
-                    invokeAdded(ds, ref, service);
+                	if (m_debug) {
+                		log("invoke added: " + ref);
+                	}
+                    invokeAdded(ds, ref, service); //**
                 }
             }
         }
@@ -500,8 +535,13 @@
     }
 
     public void removedService(ServiceReference ref, Object service) {
+    	if (m_debug) {
+    		log("removedservice: " + ref);
+    	}
         boolean makeUnavailable = makeUnavailable();
-        
+        if (m_debug) {
+        	log("make unavailable: " + makeUnavailable);
+        }
         Object[] services;
         synchronized (this) {
             services = m_services.toArray();
@@ -511,6 +551,8 @@
             DependencyService ds = (DependencyService) services[i];
             if (makeUnavailable) {
                 ds.dependencyUnavailable(this);
+                // when the dependency is optional or the dependency is instance bound and the component is instantiated (and the dependency is required)
+                // then remove is invoked. In other cases the removed has been when the component was unconfigured.
                 if (!isRequired() || (ds.isInstantiated() && isInstanceBound())) {
                     invokeRemoved(ds, ref, service);
                 }
@@ -526,6 +568,9 @@
     }
     
     public void invokeAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+    	if (m_debug) {
+    		log("invoke added");
+    	}
         boolean added = false;
         synchronized (m_sr) {
             Set set = (Set) m_sr.get(dependencyService);
@@ -546,14 +591,39 @@
         }
     }
     
-    private void handleAspectAwareAdded(DependencyService dependencyService, ServiceReference reference, Object service) {
+    private synchronized void waitForCallbackLock(Runnable runnable) {
+    	while (m_injectionQueue.indexOf(runnable) != 0) {
+    		try {
+				wait();
+			} catch (InterruptedException e) {
+			}
+    	}
+    }
+    
+    private synchronized void enqueueCallback(Runnable runnable) {
+    	m_injectionQueue.addLast(runnable);
+    }
+    
+    private synchronized void releaseCallback(Runnable runnable) {
+    	m_injectionQueue.remove(runnable);
+    	notifyAll();
+    }
+    
+    private void handleAspectAwareAdded(final DependencyService dependencyService, final ServiceReference reference, final Object service) {
+    	if (m_debug) {
+    		log("aspectawareadded: " + reference.getProperty("service.ranking"));
+    	}
 		if (componentIsDependencyManagerFactory(dependencyService)) {
 			// component is either aspect or adapter factory instance, these must be ignored.
 			return;
 		}
 		boolean invokeAdded = false;
+		boolean invokeSwapped = false;
 		Integer ranking = ServiceUtil.getRankingAsInteger(reference);
-		Tuple highestRankedService = null;
+		Tuple newHighestRankedService = null;
+		Tuple prevHighestRankedService = null;
+		Runnable callbackRunnable = null;
+		Map rankings = null;
 		synchronized (m_componentByRank) {
 			Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
 			Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
@@ -562,7 +632,7 @@
 				componentMap = new HashMap(); /* <Long, Map<Integer, Tuple>> */
 				m_componentByRank.put(dependencyService, componentMap);
 			}
-			Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+			rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
 			if (rankings == null) {
 				// new component added
 				rankings = new HashMap(); /* <Integer, Tuple> */
@@ -572,14 +642,84 @@
 			} 
 			
 			if (!invokeAdded) {
-				highestRankedService = swapHighestRankedService(dependencyService, originalServiceId, reference, service, ranking);
+				// current highest ranked
+				prevHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+				newHighestRankedService = swapHighestRankedService(dependencyService, originalServiceId, reference, service, ranking);
+				if (m_debug) {
+					log("prevhigh: " + prevHighestRankedService.getServiceReference().getProperty("service.ranking") + ", new high: " + newHighestRankedService.getServiceReference().getProperty("service.ranking"));
+				}
+				if (!prevHighestRankedService.getServiceReference().equals(newHighestRankedService.getServiceReference())) {
+					// new highest ranked service
+					if (m_debug) {
+						log("New highest ranked to swap to");
+					}
+					invokeSwapped = true;
+				} else {
+					if (m_debug) {
+						log("Ignoring lower ranked or irrelevant swap");
+					}
+				}
+			}
+			if (m_debug) {
+				log(m_componentByRank.toString());
+			}
+			
+			// up until this point should be synchronized on m_componentsByRank to keep integrity of the administration and consequences
+			// then the do phase comes, here we want to guarantee the effects of this operation are done like they were synchronized, however
+			// synchronization on m_componentsByRank to too course grained here, so we'd like to switch to synchronization on the
+			// original service id, therefore we're using our own guarded block to ensure the correct order.
+			
+			if (invokeAdded) {
+				if (m_debug) {
+					log("invoke added: " + reference.getProperty("service.ranking"));
+				}
+				callbackRunnable = createCallbackRunnable(dependencyService, reference, service, m_callbackAdded);
+				enqueueCallback(callbackRunnable);
+			} else if (invokeSwapped) {
+				if (m_debug) {
+					log("invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + prevHighestRankedService.getServiceReference().getProperty("service.ranking"));
+				}
+				callbackRunnable = createSwapRunnable(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(), newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+				enqueueCallback(callbackRunnable);
+			}    	
+		}
+		if (callbackRunnable != null) {
+			waitForCallbackLock(callbackRunnable);
+			synchronized (rankings) {
+				releaseCallback(callbackRunnable);
+				execute(callbackRunnable);
 			}
 		}
-		if (invokeAdded) {
-			invoke(dependencyService, reference, service, m_callbackAdded);
-		} else {
-			invokeSwappedCallback(dependencyService, highestRankedService.getServiceReference(), highestRankedService.getService(), reference, service);
-		}    	
+    }
+    
+    private Runnable createCallbackRunnable(final DependencyService dependencyService, final ServiceReference reference, final Object service, final String callback) {
+    	return new Runnable() {
+			public void run() {
+				invoke(dependencyService, reference, service, callback);			
+			}
+			public String toString() {
+				return callback + " on " + dependencyService;
+			}
+		};
+    }
+    
+    private Runnable createSwapRunnable(final DependencyService dependencyService, final ServiceReference prevReference, final Object prevService, final ServiceReference newReference, final Object newService) {
+    	return new Runnable() {
+			public void run() {
+				invokeSwappedCallback(dependencyService, prevReference, prevService, newReference, newService);					
+			}
+			public String toString() {
+				return "swap on " + dependencyService;
+			}
+		};
+    }
+    
+    private void execute(Runnable runnable) {
+    	runnable.run();
+    }
+    
+    private void log(String message) {
+    	m_logger.log(Logger.LOG_DEBUG, message);
     }
     
     private boolean componentIsDependencyManagerFactory(DependencyService dependencyService) {
@@ -594,13 +734,12 @@
     }
     
 	private Tuple swapHighestRankedService(DependencyService dependencyService, Long serviceId, ServiceReference newReference, Object newService, Integer newRanking) {
-		// does a component with a higher ranking exists
+		// does a component with a higher ranking exist
 		synchronized (m_componentByRank) {
 			Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
 			Map rankings = (Map) componentMap.get(serviceId); /* <Integer, Tuple> */
-			Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
-			rankings.remove(highestEntry.getKey());
 			rankings.put(newRanking, new Tuple(newReference, newService));
+			Entry highestEntry = getHighestRankedService(dependencyService, serviceId); /* <Integer, Tuple> */
 			return (Tuple) highestEntry.getValue();
 		}
 	}
@@ -643,6 +782,9 @@
 		// 1) a call to added(original-service)
 		// 2) that causes a swap
 		// 3) a call to removed(aspect-service) <-- that's what we're talking about
+		if (m_debug) {
+			log(m_componentByRank.toString());
+		}
 		return (componentMap != null && rankings != null && rankings.size() == 1 && ((Entry)rankings.entrySet().iterator().next()).getKey()
 				.equals(ServiceUtil.getRankingAsInteger(reference)));
 	}
@@ -653,11 +795,17 @@
     }
 
     public void invokeRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+    	if (m_debug) {
+    		log("invoke removed");
+    	}
         boolean removed = false;
         synchronized (m_sr) {
             Set set = (Set) m_sr.get(dependencyService);
             removed = (set != null && set.remove(new Tuple(reference, service)));
         }
+        if (m_debug) {
+        	log("removed: " + removed);
+        }
         if (removed) {
         	if (m_callbackSwapped != null) {
         		handleAspectAwareRemoved(dependencyService, reference, service);
@@ -669,37 +817,94 @@
     }
     
 	private void handleAspectAwareRemoved(DependencyService dependencyService, ServiceReference reference, Object service) {
+		if (m_debug) {
+			log("aspectawareremoved: " + reference.getProperty("service.ranking"));
+		}
 		if (componentIsDependencyManagerFactory(dependencyService)) {
 			// component is either aspect or adapter factory instance, these must be ignored.
 			return;
 		}
+		// we might need to swap here too!
+		boolean invokeRemoved = false;
 		Long serviceId = ServiceUtil.getServiceIdAsLong(reference);
-			synchronized (m_componentByRank) {
-				if (isLastService(dependencyService, reference, service, serviceId)) {
-					invoke(dependencyService, reference, service, m_callbackRemoved);
-				}
-				Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
-				Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
-				if (componentMap != null) {
-					Map rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
-					for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) {
-						Entry mapEntry = (Entry) entryIterator.next();
-						if (((Tuple)mapEntry.getValue()).getServiceReference().equals(reference)) {
-							// remove the reference
-							rankings.remove(mapEntry.getKey());
-						}
+		Tuple prevHighestRankedService = null;
+		Tuple newHighestRankedService = null;
+		boolean invokeSwapped = false;
+		Map rankings = null;
+		Runnable callbackRunnable = null;
+		synchronized (m_componentByRank) {
+			Long originalServiceId = ServiceUtil.getServiceIdAsLong(reference);
+			if (isLastService(dependencyService, reference, service, serviceId)) {
+				invokeRemoved = true;
+			} else {
+				// not the last service, but should we swap?
+				prevHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+				if (prevHighestRankedService.getServiceReference().equals(reference)) {
+					// swapping out
+					if (m_debug) {
+						log("Swap out on remove!");
 					}
-					if (rankings.size() == 0) {
-						componentMap.remove(originalServiceId);
-					}
-					if (componentMap.size() == 0) {
-						m_componentByRank.remove(dependencyService);
-					}
+					invokeSwapped = true;
 				}
 			}
+			if (m_debug) {
+				log("is last service: " + invokeRemoved);
+			}
+			// cleanup
+			Map componentMap = (Map) m_componentByRank.get(dependencyService); /* <Long, Map<Integer, Tuple>> */
+			if (componentMap != null) {
+				rankings = (Map) componentMap.get(originalServiceId); /* <Integer, Tuple> */
+				List rankingsToRemove = new ArrayList();
+				for (Iterator entryIterator = rankings.entrySet().iterator(); entryIterator.hasNext(); ) {
+					Entry mapEntry = (Entry) entryIterator.next();
+					if (((Tuple)mapEntry.getValue()).getServiceReference().equals(reference)) {
+						// remove the reference
+						// rankings.remove(mapEntry.getKey());
+						rankingsToRemove.add(mapEntry.getKey());
+					}
+				}
+				for (Iterator rankingIterator = rankingsToRemove.iterator(); rankingIterator.hasNext(); ) {
+					rankings.remove(rankingIterator.next());
+				}
+				if (rankings.size() == 0) {
+					componentMap.remove(originalServiceId);
+				}
+				if (componentMap.size() == 0) {
+					m_componentByRank.remove(dependencyService);
+				}
+			}
+			// determine current highest ranked service
+			if (invokeSwapped) {
+				newHighestRankedService = (Tuple)getHighestRankedService(dependencyService, originalServiceId).getValue();
+			}
+			if (invokeRemoved) {
+				// handle invoke outside the sync block since we won't know what will happen there
+				if (m_debug) {
+					log("invoke removed: " + reference.getProperty("service.ranking"));
+				}
+				callbackRunnable = createCallbackRunnable(dependencyService, reference, service, m_callbackRemoved);
+				enqueueCallback(callbackRunnable);
+			} else if (invokeSwapped) {
+				if (m_debug) {
+					log("invoke swapped: " + newHighestRankedService.getServiceReference().getProperty("service.ranking") + " replacing " + prevHighestRankedService.getServiceReference().getProperty("service.ranking"));
+				}
+				callbackRunnable = createSwapRunnable(dependencyService, prevHighestRankedService.getServiceReference(), prevHighestRankedService.getService(), newHighestRankedService.getServiceReference(), newHighestRankedService.getService());
+				enqueueCallback(callbackRunnable);
+			}
+		}
+		if (callbackRunnable != null) {
+			waitForCallbackLock(callbackRunnable);
+			synchronized (rankings) {
+				releaseCallback(callbackRunnable);
+				execute(callbackRunnable);
+			}
+		}
 	}    
 
     public void invoke(DependencyService dependencyService, ServiceReference reference, Object service, String name) {
+    	if (m_debug) {
+    		log("invoke: " + name);
+    	}
         if (name != null) {
             dependencyService.invokeCallbackMethod(getCallbackInstances(dependencyService), name, 
                 new Class[][] {
@@ -724,11 +929,26 @@
 			throw new IllegalStateException("Attempt to swap a service for a service with the same rank! previousReference: " + previousReference + ", currentReference: " + currentServiceReference);
 		}
 		
-		component.invokeCallbackMethod(getCallbackInstances(component), m_callbackSwapped, new Class[][] { { m_trackedServiceName, m_trackedServiceName },
-				{ Object.class, Object.class }, { ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
-				{ ServiceReference.class, Object.class, ServiceReference.class, Object.class } }, new Object[][] { { previous, current },
-				{ previous, current }, { previousReference, previous, currentServiceReference, current },
-				{ previousReference, previous, currentServiceReference, current } });
+		component.invokeCallbackMethod(getCallbackInstances(component), m_callbackSwapped, new Class[][] { 
+					{ m_trackedServiceName, m_trackedServiceName },
+					{ Object.class, Object.class }, 
+					{ ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
+					{ ServiceReference.class, Object.class, ServiceReference.class, Object.class }, 
+					{ Component.class, m_trackedServiceName, m_trackedServiceName },
+					{ Component.class, Object.class, Object.class }, 
+					{ Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName },
+					{ Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class } 
+				}, 
+				new Object[][] { 
+					{ previous, current },
+					{ previous, current }, 
+					{ previousReference, previous, currentServiceReference, current },
+					{ previousReference, previous, currentServiceReference, current },
+					{ component, previous, current },
+					{ component, previous, current }, 
+					{ component, previousReference, previous, currentServiceReference, current },
+					{ component, previousReference, previous, currentServiceReference, current } 					
+				});
 	}    
 
     protected synchronized boolean makeAvailable() {
@@ -1066,6 +1286,9 @@
     }
 
     public void invokeAdded(DependencyService service) {
+    	if (m_debug) {
+    		log("invoke added due to configure. (component is activated)");
+    	}
         ServiceReference[] refs = m_tracker.getServiceReferences();
         if (refs != null) {
             for (int i = 0; i < refs.length; i++) {
@@ -1077,20 +1300,26 @@
     }
     
     public void invokeRemoved(DependencyService service) {
+    	if (m_debug) {
+    		log("invoke removed due to unconfigure. (component is destroyed)");
+    	}
         Set references = null;
+        Object[] tupleArray = null;
         synchronized (m_sr) {
             references = (Set) m_sr.get(service);
+            // is this null check necessary ??
+            if (references != null) {
+	            tupleArray = references.toArray(new Tuple[references.size()]);
+            }
         }
-        Tuple[] refs = (Tuple[]) (references != null ? references.toArray(new Tuple[references.size()]) : new Tuple[0]);
+
+        Tuple[] refs = (Tuple[]) (tupleArray != null ?  tupleArray : new Tuple[0]);
     
         for (int i = 0; i < refs.length; i++) {
             ServiceReference sr = refs[i].getServiceReference();
             Object svc = refs[i].getService();
             invokeRemoved(service, sr, svc);
         }
-        if (references != null) {
-            references.clear();
-        }
     }
 
     public Dictionary getProperties() {
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
index e39de62..1f25811 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
@@ -21,8 +21,10 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.Logger;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
@@ -34,11 +36,16 @@
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
 public class BundleContextInterceptor extends BundleContextInterceptorBase {
+	private static final String INDEX_LOG_BAD_PERFORMING_FILTERS = "org.apache.felix.dependencymanager.index.logbadperformingfilters";
+	private static AtomicLong maxLookupTime = new AtomicLong(0L);
     private final ServiceRegistryCache m_cache;
+    private final boolean perfmon = "true".equals(System.getProperty(INDEX_LOG_BAD_PERFORMING_FILTERS));
+	private final Logger m_logger;
 
-    public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context) {
+    public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context, Logger logger) {
         super(context);
         m_cache = cache;
+		m_logger = logger;
     }
 
     public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
@@ -74,6 +81,10 @@
     }
 
     public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+    	long start = 0L;
+    	if (perfmon) {
+    		start = System.currentTimeMillis();
+    	}
         // first we ask the cache if there is an index for our request (class and filter combination)
         FilterIndex filterIndex = m_cache.hasFilterIndexFor(clazz, filter);
         if (filterIndex != null) {
@@ -89,6 +100,13 @@
                     }
                 }
             }
+            if (perfmon) {
+	        	long duration = System.currentTimeMillis() - start;
+	        	if (maxLookupTime.get() < duration) {
+	        		maxLookupTime.set(duration);
+	        		m_logger.log(org.apache.felix.dm.impl.Logger.LOG_DEBUG, "new worst performing filter (" + duration + "ms.): " + clazz + " " + filter);
+	        	}
+            }
             if (result == null || result.size() == 0) {
                 return null;
             }
@@ -96,7 +114,15 @@
         }
         else {
             // if they don't know, we ask the real bundle context instead
-            return m_context.getServiceReferences(clazz, filter);
+            ServiceReference[] serviceReferences = m_context.getServiceReferences(clazz, filter);
+            if (perfmon) {
+	        	long duration = System.currentTimeMillis() - start;
+	        	if (maxLookupTime.get() < duration) {
+	        		maxLookupTime.set(duration);
+	        		System.out.println("new worst performing filter (" + duration + "ms.): " + clazz + " " + filter);
+	        	}
+            }
+        	return serviceReferences;
         }
     }
 
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java
deleted file mode 100644
index f0b7705..0000000
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/MultiPropertyExactFilter.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * 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.impl.index;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.apache.felix.dm.FilterIndex;
-import org.apache.felix.dm.tracker.ServiceTracker;
-import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Constants;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceEvent;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-
-/**
- * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
- */
-public class MultiPropertyExactFilter implements FilterIndex, ServiceTrackerCustomizer {
-    private final Object m_lock = new Object();
-    private ServiceTracker m_tracker;
-    private BundleContext m_context;
-    private final TreeSet /* <String> */ m_propertyKeys = new TreeSet(String.CASE_INSENSITIVE_ORDER);
-    private final Map /* <String, List<ServiceReference>> */ m_keyToServiceReferencesMap = new HashMap();
-    private final Map /* <String, List<ServiceListener>> */ m_keyToListenersMap = new HashMap();
-    private final Map /* <ServiceListener, String> */ m_listenerToFilterMap = new HashMap();
-
-    public MultiPropertyExactFilter(String[] propertyKeys) {
-        for (int i = 0; i < propertyKeys.length; i++) {
-            m_propertyKeys.add(propertyKeys[i]);
-        }
-    }
-    
-    public void open(BundleContext context) {
-        synchronized (m_lock) {
-            if (m_context != null) {
-                throw new IllegalStateException("Filter already open.");
-            }
-            try {
-                m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
-            }
-            catch (InvalidSyntaxException e) {
-                throw new Error();
-            }
-            m_context = context;
-        }
-        m_tracker.open(true, true);
-    }
-
-    public void close() {
-        ServiceTracker tracker;
-        synchronized (m_lock) {
-            if (m_context == null) {
-                throw new IllegalStateException("Filter already closed.");
-            }
-            tracker = m_tracker;
-            m_tracker = null;
-            m_context = null;
-        }
-        tracker.close();
-    }
-
-    public List /* <ServiceReference> */ getAllServiceReferences(String clazz, String filter) {
-        List /* <ServiceReference> */ result = new ArrayList();
-        List keys = createKeysFromFilter(clazz, filter);
-        Iterator iterator = keys.iterator();
-        while (iterator.hasNext()) {
-            String key = (String) iterator.next();
-            ServiceReference reference;
-            synchronized (m_keyToServiceReferencesMap) {
-                List references = (List) m_keyToServiceReferencesMap.get(key);
-                if (references != null) {
-                    result.addAll(references);
-                }
-            }
-        }
-        return result;
-    }
-
-    public Object addingService(ServiceReference reference) {
-        BundleContext context;
-        synchronized (m_lock) {
-            context = m_context;
-        }
-        if (context != null) {
-            return context.getService(reference);
-        }
-        else {
-            throw new IllegalStateException("No valid bundle context.");
-        }
-    }
-
-    public void addedService(ServiceReference reference, Object service) {
-        if (isApplicable(reference.getPropertyKeys())) {
-            add(reference);
-        }
-    }
-
-    public void modifiedService(ServiceReference reference, Object service) {
-        if (isApplicable(reference.getPropertyKeys())) {
-            modify(reference);
-        }
-    }
-
-    public void removedService(ServiceReference reference, Object service) {
-        if (isApplicable(reference.getPropertyKeys())) {
-            remove(reference);
-        }
-    }
-
-    public void add(ServiceReference reference) {
-        List /* <String> */ keys = createKeys(reference);
-        synchronized (m_keyToServiceReferencesMap) {
-            for (int i = 0; i < keys.size(); i++) {
-                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
-                if (references == null) {
-                    references = new ArrayList();
-                    m_keyToServiceReferencesMap.put(keys.get(i), references);
-                }
-                references.add(reference);
-            }
-        }
-    }
-
-    public void modify(ServiceReference reference) {
-        List /* <String> */ keys = createKeys(reference);
-        synchronized (m_keyToServiceReferencesMap) {
-            // TODO this is a quite expensive linear scan over the existing collection
-            // because we first need to remove any existing references and they can be
-            // all over the place :)
-            Iterator iterator = m_keyToServiceReferencesMap.values().iterator();
-            while (iterator.hasNext()) {
-                List /* <ServiceReference> */ list = (List) iterator.next();
-                if (list != null) {
-                    Iterator i2 = list.iterator();
-                    while (i2.hasNext()) {
-                        ServiceReference ref = (ServiceReference) i2.next();
-                        if (ref.equals(reference)) {
-                            i2.remove();
-                        }
-                    }
-                }
-            }
-            
-            for (int i = 0; i < keys.size(); i++) {
-                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
-                if (references == null) {
-                    references = new ArrayList();
-                    m_keyToServiceReferencesMap.put(keys.get(i), references);
-                }
-                references.add(reference);
-            }
-        }
-    }
-
-    public void remove(ServiceReference reference) {
-        List /* <String> */ keys = createKeys(reference);
-        synchronized (m_keyToServiceReferencesMap) {
-            for (int i = 0; i < keys.size(); i++) {
-                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
-                if (references != null) {
-                    references.remove(reference);
-                }
-            }
-        }
-    }
-
-    public boolean isApplicable(String[] propertyKeys) {
-        TreeSet list = new TreeSet(String.CASE_INSENSITIVE_ORDER);
-        for (int i = 0; i < propertyKeys.length; i++) {
-            list.add(propertyKeys[i]);
-        }
-        Iterator iterator = m_propertyKeys.iterator();
-        while (iterator.hasNext()) {
-            String item = (String) iterator.next();
-            if (!(list.contains(item))) {
-                return false;
-            }
-        }
-        return true;
-    }
-    
-    public boolean isApplicable(String clazz, String filter) {
-        // "(&(a=b)(c=d))"
-        // "(&(&(a=b)(c=d))(objC=aaa))"
-        // startsWith "(&" en split in "(x=y)" -> elke x bestaat in m_propertykeys
-        
-        // (&(objectClass=xyz)(&(a=x)(b=y)))
-        
-        Set /* <String> */found = new HashSet();
-        if (filter != null && filter.startsWith("(&(") && filter.substring(3, 14).equalsIgnoreCase(Constants.OBJECTCLASS) && filter.substring(14, 15).equals("=") && filter.contains(")(&(") && filter.endsWith(")))")) {
-            int i1 = filter.indexOf(")(&(");
-            String className = filter.substring(("(&(" + Constants.OBJECTCLASS + "=").length(), i1);
-            if (!m_propertyKeys.contains(Constants.OBJECTCLASS)) {
-                return false;
-            }
-            else {
-                found.add(Constants.OBJECTCLASS);
-            }
-            String[] parts = filter.substring(i1 + ")(&(".length(), filter.length() - ")))".length()).split("\\)\\(");
-            for (int i = 0; i < parts.length; i++) {
-                String part = parts[i];
-                String[] tuple = part.split("=");
-                if (!m_propertyKeys.contains(tuple[0])) {
-                    return false;
-                }
-                else {
-                    found.add(tuple[0]);
-                }
-                // TODO check value tuple[1]
-            }
-            return found.size() == m_propertyKeys.size();
-        }
-        else if (filter != null && filter.startsWith("(&(") && filter.endsWith("))")) {
-            String[] parts = filter.substring(3, filter.length() - 2).split("\\)\\(");
-            for (int i = 0; i < parts.length; i++) {
-                String part = parts[i];
-                String[] tuple = part.split("=");
-                if (!m_propertyKeys.contains(tuple[0])) {
-                    return false;
-                }
-                else {
-                    found.add(tuple[0]);
-                }
-                // TODO check value tuple[1]
-            }
-            return found.size() == m_propertyKeys.size();
-        }
-        else if (filter != null && filter.startsWith("(") && filter.endsWith(")") && m_propertyKeys.size() == 1) { // TODO quick hack
-            String part = filter.substring(1, filter.length() - 1);
-            String[] tuple = part.split("=");
-            if (!m_propertyKeys.contains(tuple[0])) {
-                return false;
-            }
-            else {
-                return true;
-            }
-        }
-        else if (clazz != null && filter == null && m_propertyKeys.size() == 1 && Constants.OBJECTCLASS.equalsIgnoreCase((String) m_propertyKeys.first())) {
-            return true;
-        }
-        return false;
-    }
-    
-    private List /* <String> */ createKeys(ServiceReference reference) {
-        List /* <String> */ results = new ArrayList();
-        
-        results.add(""); // ???
-        
-        String[] keys = reference.getPropertyKeys();
-        Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
-        StringBuffer result = new StringBuffer();
-        for (int i = 0; i < keys.length; i++) {
-            String key = keys[i].toLowerCase();
-            if (m_propertyKeys.contains(key)) {
-                Object value = reference.getProperty(key);
-                if (value instanceof String[]) {
-                    String[] values = (String[]) value;
-                    List newResults = new ArrayList();
-                    for (int j = 0; j < values.length; j++) {
-                        String val = values[j];
-                        for (int k = 0; k < results.size(); k++) {
-                            String head = (String) results.get(k);
-                            if (head != null && head.length() > 0) {
-                                head = head + ";";
-                            }
-                            newResults.add(head + key + "=" + val);
-                        }
-                    }
-                    results = newResults;
-                }
-                else {
-                    for (int k = 0; k < results.size(); k++) {
-                        String head = (String) results.get(k);
-                        if (head != null && head.length() > 0) {
-                            head = head + ";";
-                        }
-                        results.set(k, head + key + "=" + value);
-                    }
-                }
-            }
-        }
-        return results;
-    }
-    
-    private List /* <String> */ createKeysFromFilter(String clazz, String filter) {
-        List result = new ArrayList();
-        StringBuffer index = new StringBuffer();
-        Iterator iterator = m_propertyKeys.iterator();
-        while (iterator.hasNext()) {
-            String key = ((String) iterator.next()).toLowerCase();
-            if (index.length() > 0) {
-                index.append(';');
-            }
-            index.append(key);
-            index.append('=');
-            String value = null;
-            if (clazz != null && Constants.OBJECTCLASS.equalsIgnoreCase(key)) {
-                value = clazz;
-            } // (&(obC=a)(&(a=b)(c=d)))
-            if (filter != null) {
-                String startString = "(" + key + "=";
-                int i1 = filter.toLowerCase().indexOf(startString);
-                if (i1 != -1) {
-                    int i2 = filter.indexOf(")(", i1);
-                    if (i2 == -1) {
-                        if (filter.endsWith(")))")) {
-                            i2 = filter.length() - 3;
-                        }
-                        else if (filter.endsWith("))")) {
-                            i2 = filter.length() - 2;
-                        }
-                        else {
-                            i2 = filter.length() - 1;
-                        }
-                    }
-                    String value2 = filter.substring(i1 + startString.length(), i2);
-                    if (value != null && !value.equals(value2)) {
-                        // corner case: someone specified a clazz and
-                        // also a filter containing a different clazz
-                        return result;
-                    }
-                    value = value2;
-                }
-            }
-            index.append(value);
-        }
-        result.add(index.toString());
-        return result;
-    }
-
-    public void serviceChanged(ServiceEvent event) {
-        if (isApplicable(event.getServiceReference().getPropertyKeys())) {
-            List /* <String> */ keys = createKeys(event.getServiceReference());
-            List list = new ArrayList();
-            synchronized (m_keyToListenersMap) {
-                for (int i = 0; i < keys.size(); i++) {
-                    String key = (String) keys.get(i);
-                    List listeners = (List) m_keyToListenersMap.get(key);
-                    if (listeners != null) {
-                        list.addAll(listeners);
-                    }
-                }
-            }
-            if (list != null) {
-                Iterator iterator = list.iterator();
-                while (iterator.hasNext()) {
-                    ServiceListener listener = (ServiceListener) iterator.next();
-                    listener.serviceChanged(event);
-                }
-            }
-        }
-    }
-
-    public void addServiceListener(ServiceListener listener, String filter) {
-        List keys = createKeysFromFilter(null, filter);
-        Iterator iterator = keys.iterator();
-        while (iterator.hasNext()) {
-            String key = (String) iterator.next();
-            synchronized (m_keyToListenersMap) {
-                List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
-                if (listeners == null) {
-                    listeners = new CopyOnWriteArrayList();
-                    m_keyToListenersMap.put(key, listeners);
-                }
-                listeners.add(listener);
-                m_listenerToFilterMap.put(listener, filter);
-            }
-        }
-    }
-
-    public void removeServiceListener(ServiceListener listener) {
-        synchronized (m_keyToListenersMap) {
-            String filter = (String) m_listenerToFilterMap.remove(listener);
-            if (filter != null) {
-            	// the listener does exist
-            	List keys = createKeysFromFilter(null, filter);
-            	Iterator iterator = keys.iterator();
-            	while (iterator.hasNext()) {
-            		String key = (String) iterator.next();
-            		
-            		boolean result = filter != null;
-            		if (result) {
-            			List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
-            			if (listeners != null) {
-            				listeners.remove(listener);
-            			}
-            			// TODO actually, if listeners == null that would be strange....
-            		}
-            	}
-            }
-        }
-    }
-    
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("MultiPropertyExactFilter[");
-        sb.append("K2L: " + m_keyToListenersMap.size());
-        sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
-        sb.append(", L2F: " + m_listenerToFilterMap.size());
-        sb.append("]");
-        return sb.toString();
-    }
-}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
index 41f7fd6..bbb0995 100644
--- a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
@@ -18,6 +18,7 @@
  */
 package org.apache.felix.dm.impl.index;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -25,6 +26,7 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.Logger;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceListener;
@@ -34,7 +36,8 @@
  * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
  */
 public class ServiceRegistryCache implements ServiceListener/*, CommandProvider*/ {
-    private final List /* <FilterIndex> */ m_filterIndexList = new CopyOnWriteArrayList();
+    private static final String INDEX_PERFLOG = "org.apache.felix.dependencymanager.index.logmissingindices";
+	private final List /* <FilterIndex> */ m_filterIndexList = new CopyOnWriteArrayList();
     private final BundleContext m_context;
     private final FilterIndexBundleContext m_filterIndexBundleContext;
     private final Map /* <BundleContext, BundleContextInterceptor> */ m_bundleContextInterceptorMap = new HashMap();
@@ -42,10 +45,16 @@
     private long m_arrayVersion = -1;
     private BundleContextInterceptor[] m_interceptors = null;
     private ServiceRegistration m_registration;
-
+    private boolean m_dumpUnIndexedFilters = "true".equals(System.getProperty(INDEX_PERFLOG));
+    private List m_unindexedFilters = new ArrayList();
+	private Logger m_logger;
     
     public ServiceRegistryCache(BundleContext context) {
         m_context = context;
+        // only obtain the logservice when we actually want to log something.
+        if (System.getProperty(INDEX_PERFLOG) != null) {
+        	m_logger = new Logger(context);
+        }
         m_filterIndexBundleContext = new FilterIndexBundleContext(m_context);
     }
     
@@ -89,7 +98,7 @@
         synchronized (m_bundleContextInterceptorMap) {
             BundleContextInterceptor bundleContextInterceptor = (BundleContextInterceptor) m_bundleContextInterceptorMap.get(context);
             if (bundleContextInterceptor == null) {
-                bundleContextInterceptor = new BundleContextInterceptor(this, context);
+                bundleContextInterceptor = new BundleContextInterceptor(this, context, m_logger);
                 m_bundleContextInterceptorMap.put(context, bundleContextInterceptor);
                 m_currentVersion++;
                 // TODO figure out a good way to clean up bundle contexts that are no longer valid so they can be garbage collected
@@ -106,6 +115,13 @@
                 return filterIndex;
             }
         }
+        if (m_dumpUnIndexedFilters) {
+        	String filterStr = clazz + ":" + filter;
+	        if (!m_unindexedFilters.contains(filterStr)) {
+	        	m_unindexedFilters.add(filterStr);
+	        	m_logger.log(Logger.LOG_DEBUG, "No filter index for " + filterStr);
+	        }
+        }
         return null;
     }
 
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java
new file mode 100644
index 0000000..66f4731
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Filter.java
@@ -0,0 +1,149 @@
+/*
+ * 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.impl.index.multiproperty;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Filter {
+	
+	private boolean m_valid = true;
+	private Map /* <String, Property> */ m_properties = new HashMap();
+	private Set /* <String> */ m_propertyKeys = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+	
+	private Filter() {
+		
+	}
+	
+	// Sample valid filter string (&(objectClass=OBJECTCLASS)(&(model=MODEL)(concept=CONCEPT)(role=ROLE)(!(context=*))))
+	public static Filter parse(String filterString) {
+		Filter filter = new Filter();
+		StringTokenizer tokenizer = new StringTokenizer(filterString, "(&|=)", true);
+		
+		String token = null;
+		String prevToken = null;
+		String key = null;
+		StringBuilder valueBuilder = new StringBuilder();
+		boolean negate = false;
+
+		while (tokenizer.hasMoreTokens()) {
+			prevToken = token;
+			token = tokenizer.nextToken();
+			if (token.equals("|")) {
+				// we're not into OR's
+				filter.m_valid = false;
+				break;
+			}
+			if (token.equals("!")) {
+				negate = true;
+			} else if (token.equals("=")) {
+				key = prevToken.toLowerCase();
+			} else if (key != null) {
+				if (!token.equals(")")) {
+					valueBuilder.append(token); // might be superseded by a &
+				}
+				if (token.equals(")")) {
+					// set complete
+					if (filter.m_properties.containsKey(key)) {
+						// set current property to multivalue
+						Property property = (Property) filter.m_properties.get(key);
+						property.addValue(valueBuilder.toString(), negate);
+					} else {
+						Property property = new Property(negate, key, valueBuilder.toString());
+						filter.m_properties.put(key, property);
+						filter.m_propertyKeys.add(key);
+					}
+					negate = false;
+					key = null;
+					valueBuilder = new StringBuilder();
+				}
+			} 
+		}
+		return filter;
+	}
+	
+	public boolean containsProperty(String propertyKey) {
+		return m_properties.containsKey(propertyKey);
+	}
+	
+	public Set /* <String> */ getPropertyKeys() {
+		return m_properties.keySet();
+	}
+	
+	public Property getProperty(String key) {
+		return (Property) m_properties.get(key);
+	}
+	
+	public boolean isValid() {
+		if (!m_valid) {
+			return m_valid;
+		} else {
+			// also check the properties
+			Iterator propertiesIterator = m_properties.values().iterator();
+			while (propertiesIterator.hasNext()) {
+				Property property = (Property) propertiesIterator.next();
+				if (!property.isValid()) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+	
+	public static void main(String args[]) {
+		Filter parser = Filter.parse("(&(objectClass=OBJECTCLASS)(&(a=x)(a=n)(a=y)(b=y)(c=z)))");
+		System.out.println("key: " + parser.createKey());
+	}
+
+	protected String createKey() {
+		StringBuilder builder = new StringBuilder();
+		Iterator keys = m_propertyKeys.iterator();
+		
+		while (keys.hasNext()) {
+			String key = (String) keys.next();
+			Property prop = (Property) m_properties.get(key);
+			if (!prop.isWildcard()) {
+				Iterator values = prop.getValues().iterator();
+				while (values.hasNext()) {
+					String value = (String) values.next();
+					builder.append(key);
+					builder.append("=");
+					builder.append(value);
+					if (keys.hasNext() || values.hasNext()) {
+						builder.append(";");
+					}
+				}
+			}
+		}
+		// strip the final ';' in case the last key was a wildcard property
+		if (builder.charAt(builder.length() - 1) == ';') {
+			return builder.toString().substring(0, builder.length() - 1);
+		} else {
+			return builder.toString();
+		}
+	}
+	
+}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
new file mode 100644
index 0000000..513be72
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
@@ -0,0 +1,486 @@
+/*
+ * 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.impl.index.multiproperty;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class MultiPropertyFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+
+    private final Object m_lock = new Object();
+    private ServiceTracker m_tracker;
+    private BundleContext m_context;
+	private Map /* <String, Property> */ m_configProperties = new LinkedHashMap();
+	private List /* <String> */ m_negatePropertyKeys = new ArrayList();
+    private final Map /* <String, List<ServiceReference>> */ m_keyToServiceReferencesMap = new HashMap();
+    private final Map /* <String, List<ServiceListener>> */ m_keyToListenersMap = new HashMap();
+    private final Map /* <ServiceListener, String> */ m_listenerToFilterMap = new HashMap();
+
+	public MultiPropertyFilterIndex(String configString) {
+		parseConfig(configString);
+	}
+	
+	public boolean isApplicable(String clazz, String filterString) {
+		Filter filter = createFilter(clazz, filterString);
+		
+		if (!filter.isValid()) {
+			return false;
+		}
+		// compare property keys to the ones in the configuration
+		Set /* <String> */ filterPropertyKeys = filter.getPropertyKeys();
+		if (m_configProperties.size() != filterPropertyKeys.size()) {
+			return false;
+		}
+		Iterator filterPropertyKeysIterator = filterPropertyKeys.iterator();
+		while (filterPropertyKeysIterator.hasNext()) {
+			String filterPropertyKey = (String) filterPropertyKeysIterator.next();
+			if (!m_configProperties.containsKey(filterPropertyKey)) {
+				return false;
+			} else if (((Property)m_configProperties.get(filterPropertyKey)).isNegate() != filter.getProperty(filterPropertyKey).isNegate()) {
+				// negation should be equal
+				return false;
+			} else if (!filter.getProperty(filterPropertyKey).isNegate() && filter.getProperty(filterPropertyKey).getValue().equals("*")) {
+				// no wildcards without negation allowed
+				return false;
+			} 
+		}
+		// our properties match so we're applicable
+		return true;
+	}
+	
+    public boolean isApplicable(ServiceReference ref) {
+    	String[] propertyKeys = ref.getPropertyKeys();
+        TreeSet referenceProperties = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+        for (int i = 0; i < propertyKeys.length; i++) {
+            referenceProperties.add(propertyKeys[i]);
+        }
+        Iterator iterator = m_configProperties.keySet().iterator();
+        while (iterator.hasNext()) {
+            String item = (String) iterator.next();
+            Property configProperty = (Property) m_configProperties.get(item);
+            if (!configProperty.isNegate() && !(referenceProperties.contains(item))) {
+                return false;
+            } else if (configProperty.isNegate() && referenceProperties.contains(item)) {
+            	return false;
+            }
+        }
+        return true;
+    }
+	
+	private void parseConfig(String configString) {
+		String[] propertyConfigs = configString.split(",");
+		for (int i = 0; i < propertyConfigs.length; i++) {
+			String propertyConfig = propertyConfigs[i];
+			Property property = new Property();
+			String key;
+			String value = null;
+			if (propertyConfig.startsWith("!")) {
+				property.setNegate(true);
+				key = propertyConfig.substring(1);
+			} else {
+				key = propertyConfig;
+			}
+			if (key.endsWith("*")) {
+				key = key.substring(0, key.indexOf("*"));
+				value = "*";
+			}
+			property.setKey(key.toLowerCase());
+			property.addValue(value, property.isNegate());
+			m_configProperties.put(key.toLowerCase(), property);
+			if (property.isNegate()) {
+				m_negatePropertyKeys.add(key);
+			}
+		}
+	}
+	
+	protected Collection /* <Property> */ getProperties() {
+		return m_configProperties.values();
+	}
+	
+    protected String createKeyFromFilter(String clazz, String filterString) {
+    	return createFilter(clazz, filterString).createKey();
+    }
+    
+    private Filter createFilter(String clazz, String filterString) {
+		String filterStringWithObjectClass = filterString;
+		if (clazz != null) {
+			if (filterString != null) {
+				if (!filterStringWithObjectClass.startsWith("(&(objectClass=")) {
+					filterStringWithObjectClass = "(&(objectClass=" + clazz + ")" + filterString + ")";
+				}
+			} else {
+				filterStringWithObjectClass = "(objectClass=" + clazz + ")";
+			}
+		}
+		Filter filter = Filter.parse(filterStringWithObjectClass);
+		return filter;
+    }
+    
+    protected List createKeys(ServiceReference reference) {
+    	List /* <String> */ results = new ArrayList();
+    	List sets = new ArrayList();   	
+    	String[] keys = reference.getPropertyKeys();
+    	Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
+    	for (int i = 0; i < keys.length; i++) {
+    		List set = new ArrayList();
+    		String key = keys[i].toLowerCase();
+    		if (m_configProperties.containsKey(key)) {
+	    		Object valueObject = reference.getProperty(key);
+	    		if (valueObject instanceof String[]) {
+	    			set.addAll(getPermutations(key, (String[]) valueObject));
+	    		} else {
+	    			set.add(toKey(key, valueObject));
+	    		}
+	    		sets.add(set);
+    		}
+    	}
+    	
+    	List reversedSets = new ArrayList();
+    	int size = sets.size();
+    	for (int i = size - 1; i > -1; i--) {
+    		reversedSets.add(sets.get(i));
+    	}
+    	List products = carthesianProduct(0, reversedSets);
+    	// convert sets into strings
+    	for (int i = 0; i < products.size(); i++) {
+    		List set = (List) products.get(i);
+    		StringBuilder b = new StringBuilder();
+    		for (int j = 0; j < set.size(); j++) {
+    			String item = (String) set.get(j);
+    			b.append(item);
+    			if (j < set.size() - 1) {
+    				b.append(";");
+    			}
+    		}
+    		results.add(b.toString());
+    	}
+    	
+    	return results;
+    }
+    
+    /**
+     * Note that we calculate the carthesian product for multi value properties. Use filters on these sparingly since memory
+     * consumption can get really high when multiple properties have a lot of values.
+     * 
+     * @param index
+     * @param sets
+     * @return
+     */
+    private List carthesianProduct(int index, List sets) {
+    	List result = new ArrayList();
+    	if (index == sets.size()) {
+    		result.add(new ArrayList());
+    	} else {
+			List set = (List) sets.get(index);
+			for (int i = 0; i < set.size(); i++) {
+				Object object = set.get(i);
+    			List pSets = carthesianProduct(index + 1, sets);
+    			for (int j = 0; j < pSets.size(); j++) {
+    				List pSet = (List) pSets.get(j);
+    				pSet.add(object);
+    				result.add(pSet);
+    			}
+    		}
+    	}
+    	return result;
+    }
+    
+    List getPermutations(String key, String[] values) {
+    	List results = new ArrayList();
+		Arrays.sort(values, String.CASE_INSENSITIVE_ORDER);
+		for (int v = 0; v < values.length; v++) {
+			String processValue = values[v];
+			List /* <String> */ items = new ArrayList();
+			items.add(processValue);
+			// per value get combinations
+			List /* <String> */ subItems = new ArrayList(items);
+			for (int w = v; w < values.length; w++) {
+				// make a copy of the current list
+				subItems = new ArrayList(subItems);
+				if (w != v) {
+					String value = values[w];
+					subItems.add(value);
+				}
+				results.add(toKey(key, subItems));
+			}
+		}
+		return results;
+    }
+    
+    protected String toKey(String key, List values) {
+    	StringBuilder builder = new StringBuilder();
+    	for (int i = 0; i < values.size(); i++) {
+    		builder.append(toKey(key, (String) values.get(i)));
+    		if (i < values.size() - 1) {
+    			builder.append(";");
+    		}
+    	}
+    	return builder.toString();
+    }
+    
+    protected String toKey(String key, Object value) {
+    	StringBuilder builder = new StringBuilder();
+    	builder.append(key);
+		builder.append("=");
+		builder.append(value.toString());
+		return builder.toString();
+    }
+    
+    public Object addingService(ServiceReference reference) {
+        BundleContext context;
+        synchronized (m_lock) {
+            context = m_context;
+        }
+        if (context != null) {
+            return context.getService(reference);
+        }
+        else {
+            throw new IllegalStateException("No valid bundle context.");
+        }
+    }
+
+    public void addedService(ServiceReference reference, Object service) {
+        if (isApplicable(reference) && shouldBeIndexed(reference)) {
+            handleServiceAdd(reference);
+        }
+    }
+
+    public void modifiedService(ServiceReference reference, Object service) {
+        if (isApplicable(reference)) {
+            handleServicePropertiesChange(reference);
+        }
+    }
+
+    public void removedService(ServiceReference reference, Object service) {
+        if (isApplicable(reference) && shouldBeIndexed(reference)) {
+            handleServiceRemove(reference);
+        }
+    }
+    
+    protected void handleServiceAdd(ServiceReference reference) {
+        List /* <String> */ keys = createKeys(reference);
+        synchronized (m_keyToServiceReferencesMap) {
+            for (int i = 0; i < keys.size(); i++) {
+                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+                if (references == null) {
+                    references = new ArrayList();
+                    m_keyToServiceReferencesMap.put(keys.get(i), references);
+                }
+                references.add(reference);
+            }
+        }
+    }
+
+    protected void handleServicePropertiesChange(ServiceReference reference) {
+        
+        synchronized (m_keyToServiceReferencesMap) {
+            // TODO this is a quite expensive linear scan over the existing collection
+            // because we first need to remove any existing references and they can be
+            // all over the place :)
+            Iterator iterator = m_keyToServiceReferencesMap.values().iterator();
+            while (iterator.hasNext()) {
+                List /* <ServiceReference> */ list = (List) iterator.next();
+                if (list != null) {
+                    Iterator i2 = list.iterator();
+                    while (i2.hasNext()) {
+                        ServiceReference ref = (ServiceReference) i2.next();
+                        if (ref.equals(reference)) {
+                            i2.remove();
+                        }
+                    }
+                }
+            }
+            // only re-add the reference when it is still applicable for this filter index
+            if (shouldBeIndexed(reference)) {
+            	List /* <String> */ keys = createKeys(reference);
+	            for (int i = 0; i < keys.size(); i++) {
+	                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+	                if (references == null) {
+	                    references = new ArrayList();
+	                    m_keyToServiceReferencesMap.put(keys.get(i), references);
+	                }
+	                references.add(reference);
+	            }
+            }
+        }
+    }
+
+    protected void handleServiceRemove(ServiceReference reference) {
+        List /* <String> */ keys = createKeys(reference);
+        synchronized (m_keyToServiceReferencesMap) {
+            for (int i = 0; i < keys.size(); i++) {
+                List /* <ServiceReference> */ references = (List) m_keyToServiceReferencesMap.get(keys.get(i));
+                if (references != null) {
+                    references.remove(reference);
+                    if (references.isEmpty()) {
+                    	m_keyToServiceReferencesMap.remove(keys.get(i));
+                    }
+                }
+            }
+        }
+    }
+    
+    protected boolean shouldBeIndexed(ServiceReference reference) {
+    	// is already applicable, so we should only check whether there's a negate field in the filter which has a value in the reference
+    	Iterator negatePropertyKeyIterator = m_negatePropertyKeys.iterator();
+    	while (negatePropertyKeyIterator.hasNext()) {
+    		String negatePropertyKey = (String) negatePropertyKeyIterator.next();
+    		if (reference.getProperty(negatePropertyKey) != null) {
+    			return false;
+    		}
+    	}
+    	return true;
+    }
+
+    public void open(BundleContext context) {
+        synchronized (m_lock) {
+            if (m_context != null) {
+                throw new IllegalStateException("Filter already open.");
+            }
+            try {
+                m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
+            }
+            catch (InvalidSyntaxException e) {
+                throw new Error();
+            }
+            m_context = context;
+        }
+        m_tracker.open(true, true);
+    }
+
+	public void close() {
+        ServiceTracker tracker;
+        synchronized (m_lock) {
+            if (m_context == null) {
+                throw new IllegalStateException("Filter already closed.");
+            }
+            tracker = m_tracker;
+            m_tracker = null;
+            m_context = null;
+        }
+        tracker.close();
+	}
+
+    public List /* <ServiceReference> */ getAllServiceReferences(String clazz, String filter) {
+        List /* <ServiceReference> */ result = new ArrayList();
+        String key = createKeyFromFilter(clazz, filter);
+        ServiceReference reference;
+        synchronized (m_keyToServiceReferencesMap) {
+            List references = (List) m_keyToServiceReferencesMap.get(key);
+            if (references != null) {
+                result.addAll(references);
+            }
+        }
+        return result;
+    }
+
+    public void serviceChanged(ServiceEvent event) {
+        if (isApplicable(event.getServiceReference())) {
+            List /* <String> */ keys = createKeys(event.getServiceReference());
+            List list = new ArrayList();
+            synchronized (m_keyToListenersMap) {
+                for (int i = 0; i < keys.size(); i++) {
+                    String key = (String) keys.get(i);
+                    List listeners = (List) m_keyToListenersMap.get(key);
+                    if (listeners != null) {
+                        list.addAll(listeners);
+                    }
+                }
+            }
+            if (list != null) {
+                Iterator iterator = list.iterator();
+                while (iterator.hasNext()) {
+                    ServiceListener listener = (ServiceListener) iterator.next();
+                    listener.serviceChanged(event);
+                }
+            }
+        }
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter) {
+        String key = createKeyFromFilter(null, filter);
+        synchronized (m_keyToListenersMap) {
+            List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
+            if (listeners == null) {
+                listeners = new CopyOnWriteArrayList();
+                m_keyToListenersMap.put(key, listeners);
+            }
+            listeners.add(listener);
+            m_listenerToFilterMap.put(listener, filter);
+        }
+    }
+
+    public void removeServiceListener(ServiceListener listener) {
+        synchronized (m_keyToListenersMap) {
+            String filter = (String) m_listenerToFilterMap.remove(listener);
+            if (filter != null) {
+            	// the listener does exist
+        		String key = createKeyFromFilter(null, filter);
+        		
+        		boolean result = filter != null;
+        		if (result) {
+        			List /* <ServiceListener> */ listeners = (List) m_keyToListenersMap.get(key);
+        			if (listeners != null) {
+        				listeners.remove(listener);
+        				if (listeners.isEmpty()) {
+        					m_keyToListenersMap.remove(key);
+        				}
+        			}
+        			// TODO actually, if listeners == null that would be strange....
+        		}
+            }
+        }
+    }
+    
+    protected Collection getServiceListeners() {
+    	return m_listenerToFilterMap.keySet();
+    }
+    
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append(" dMultiPropertyExactFilter[");
+        sb.append("K2L: " + m_keyToListenersMap.size());
+        sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
+        sb.append(", L2F: " + m_listenerToFilterMap.size());
+        sb.append("]");
+        return sb.toString();
+    }
+}
diff --git a/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java
new file mode 100644
index 0000000..e4b466d
--- /dev/null
+++ b/dependencymanager/core/src/main/java/org/apache/felix/dm/impl/index/multiproperty/Property.java
@@ -0,0 +1,99 @@
+/*
+ * 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.impl.index.multiproperty;
+
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+import java.util.Set;
+import java.util.TreeSet;
+
+public class Property {
+	boolean m_negate;
+	boolean m_valid = true;
+	String m_key;
+	String m_value;
+	Set m_values = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+	
+	public Property() {
+	}
+	
+	public Property(boolean negate, String key, String value) {
+		super();
+		this.m_negate = negate;
+		this.m_key = key.toLowerCase();
+		this.m_values.add(value);
+		this.m_value = value;
+	}
+
+	public void setNegate(boolean negate) {
+		this.m_negate = negate;
+	}
+	
+	public void setKey(String key) {
+		this.m_key = key.toLowerCase();
+	}
+	
+	public void addValue(String value, boolean negate) {
+		if (this.m_negate != negate) {
+			// multiproperty with different negations, causes invalid configuration.
+			m_valid = false;
+		}
+		if (this.m_value == null) {
+			this.m_value = value;
+		}
+		m_values.add(value);
+	}
+	
+	public boolean isNegate() {
+		return m_negate;
+	}
+	
+	public String getKey() {
+		return m_key;
+	}
+	
+	public String getValue() {
+		return m_value;
+	}
+	
+	public Set getValues() {
+		return m_values;
+	}
+	
+	public boolean isWildcard() {
+		return "*".equals(m_value);
+	}
+	
+	public boolean isMultiValue() {
+		return m_values.size() > 1;
+	}
+
+	public String toString() {
+		return "Property [negate=" + m_negate + ", key=" + m_key + ", values="
+				+ m_values + "]";
+	}
+	
+	public boolean isValid() {
+		return m_valid;
+	}
+}
\ No newline at end of file