All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jadex.bridge.service.search.Indexer Maven / Gradle / Ivy

Go to download

Jadex bridge is a base package for kernels and platforms, i.e., it is used by both and provides commonly used interfaces and classes for active components and their management.

There is a newer version: 4.0.267
Show newest version
package jadex.bridge.service.search;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jadex.bridge.ClassInfo;
import jadex.bridge.service.IService;
import jadex.bridge.service.ServiceScope;
import jadex.bridge.service.search.ServiceKeyExtractor.SetWrapper;
import jadex.bridge.service.types.library.ILibraryService;
import jadex.commons.SUtil;
import jadex.commons.Tuple2;
import jadex.commons.Tuple3;

/**
 *  Indexer for values.
 */
public class Indexer
{
	/** Cutoff value for building index set intersections. */
	public static final int INTERSECT_CUTOFF = 32;
	
	/** Service key extractor. */
	protected IKeyExtractor keyextractor;
	
	/** All values. */
	protected Set values = new LinkedHashSet();
	
	/** The index of published values. First string is the index key name. */
	protected Map>> indexedvalues = new HashMap>>();
	
	/** Include null values during indexing. */
	protected boolean includenull;
	
	/**
	 *  Create a new ServiceIndexer.
	 */
	public Indexer(IKeyExtractor keyextractor, boolean includenull, String... indextypes)
	{
		this.keyextractor = keyextractor;
		this.includenull = includenull;
		for(String indextype: indextypes)
			indexedvalues.put(indextype, new HashMap>());
	}
	
	/**
	 *  Test if a property is indexed per name.
	 *  @param indexname The index name.
	 *  @return True, if is indexed.
	 */
	public boolean isIndexed(String indexname)
	{
		return indexedvalues.containsKey(indexname);
	}
	
	/**
	 *  Get values per specification. 'And' relates to inter-term, i.e. example
	 *  type=ICMS, tags=a,b means an object must have both fulfilled. For multi-valued
	 *  intra-term values it can be 'and' or 'or' as well.
	 *  @param spec The key values (first element is key name and array are values)
	 *  @return The values matching the spec.
	 */
	public Tuple2, Object> getValues(List> spec)
	{
		//System.out.println("spec: "+spec);
		List> valuesets = null;
		
		Set ret = null;
		if(spec == null || spec.size() == 0)
		{
			ret = new LinkedHashSet(values);
		}
		else
		{
			int speccount = 0;
			for(Iterator> it = spec.iterator(); it.hasNext();)
			{
				Tuple3 tup = it.next();
				speccount += tup.getSecondEntity().length;
				
				// Fetch index service map per key
				Map> index = indexedvalues.get(tup.getFirstEntity());
				if(index != null)
				{
					// Remove this part from spec because index was found
					it.remove();
					
					if(valuesets == null)
						valuesets = new ArrayList>();
					
					if(tup.getThirdEntity()==null || tup.getThirdEntity())
					{
						// AND treatment. All sets are added to the set
						
						// Elements that have been added with ALWAYS value
						Set always = index.get(IKeyExtractor.MATCH_ALWAYS);
						
						for(String key: tup.getSecondEntity())
						{
							Set iset = index.get(key);
							
							if(iset == null || iset.isEmpty())
								iset = always;
							else if(always!=null)
								iset.addAll(always);
							
							if(iset == null || iset.isEmpty())
								return new Tuple2(ret, valuesets);
							
							valuesets.add(iset);
						}						
					}
					else // or
					{
						Set always = index.get(IKeyExtractor.MATCH_ALWAYS);
						
						Set tmp = new HashSet();
						if(always!=null)
							tmp.addAll(always);
						
						for(String key: tup.getSecondEntity())
						{
							Set iset = index.get(key);
							
							if(iset != null)
								tmp.addAll(iset);
						}
						
						//if(!tmp.isEmpty()) // must always be added because otherwise no results are removed!
						valuesets.add(tmp);
					}
				}
			}
			
			if(valuesets != null)
			{
				// Start with shortest collection
				if(valuesets.size()>1)
				{
					Collections.sort(valuesets, new Comparator>()
					{
						public int compare(Set o1, Set o2)
						{
							return o1.size() - o2.size();
						}
					});
				}
				//System.out.println("indexer.getValues(): "+valuesets);
				
				int i = 0;
				for(i = 0; i < valuesets.size() && (ret == null || ret.size() < INTERSECT_CUTOFF); ++i)
				{
					if(ret == null)
					{
						ret = new LinkedHashSet(valuesets.get(i));
					}
					else
					{
						Set iset = valuesets.get(i);
						for(Iterator it = ret.iterator(); it.hasNext(); )
						{
							T serv = it.next();
							if(!iset.contains(serv))
								it.remove();
						}
					}
				}
				
				// If all were used directly return intersection result
				if(ret != null && i == speccount)
					return new Tuple2(ret, valuesets);
			}
			
			if(ret == null)
				ret = new LinkedHashSet(values);
			
			// Otherwise use single matching
			for(Iterator it = ret.iterator(); it.hasNext(); )
			{
				T serv = it.next();
				if(!match(spec, serv))
					it.remove();
			}
		}
		
		return new Tuple2(ret, valuesets);
	}
	
	/**
	 *  Get values per specification (multivalues considered as OR match, when queries are the values).
	 *  @param spec The key values (first element is key name and array are values)
	 *  @return The values matching the spec.
	 */
	public Set getValuesInverted(List> spec)
	{
		Set ret = null;
		if(spec == null || spec.size() == 0)
		{
			ret = new LinkedHashSet(values);
		}
		else
		{
			List> valuesets = null;
			for(Iterator> it = spec.iterator(); it.hasNext();)
			{
				Tuple2 tup = it.next();
				
				// Fetch index service map per key
				Map> index = indexedvalues.get(tup.getFirstEntity());
				if(index != null)
				{					
					if(valuesets == null)
						valuesets = new ArrayList>();
					Set vals = new HashSet();
					
					for(String key: tup.getSecondEntity())
					{
						Set iset = index.get(key);
						if(iset!=null)
							vals.addAll(iset);
					}
					Set iset = index.get("null");
					if(iset!=null)
						vals.addAll(iset);
					
					if(vals.isEmpty())
						return null;
					
					valuesets.add(vals);
				}
			}
			
			if(valuesets != null)
			{
				// Start with shortest collection
				if(valuesets.size()>1)
				{
					Collections.sort(valuesets, new Comparator>()
					{
						public int compare(Set o1, Set o2)
						{
							return o1.size() - o2.size();
						}
					});
				}
				
				int i = 0;
				for(i = 0; i < valuesets.size() && (ret == null || ret.size() < INTERSECT_CUTOFF); ++i)
				{
					if(ret == null)
					{
						ret = new LinkedHashSet(valuesets.get(i));
					}
					else
					{
						Set iset = valuesets.get(i);
						for(Iterator it = ret.iterator(); it.hasNext(); )
						{
							T serv = it.next();
							if(!iset.contains(serv))
								it.remove();
						}
					}
				}
			}
			
			for(Iterator it = ret.iterator(); it.hasNext(); )
			{
				T val = it.next();
				if(!matchOr(spec, val))
					it.remove();
			}
		}
		
		return ret;
	}
	
	/**
	 *  Get values per type.
	 *  @param keytype the key type.
	 *  @param key The key value.
	 *  @return The values matching the key.
	 */
	public Set getValues(String keytype, String key)
	{
		Set ret = null;
		Map> index = indexedvalues.get(keytype);
		if(index != null)
		{
			 ret = index.get(key);
			 if (ret != null)
				 ret = new LinkedHashSet(ret);
		}
		else
		{
			for(T val: values)
			{
				Set keys = keyextractor.getKeyValues(keytype, val);
				if(keys != null && keys.contains(key))
				{
					if (ret == null)
						ret = new LinkedHashSet();
					ret.add(val);
				}
			}
		}
		return ret;
	}
	
	/** 
	 *  Returns all values. 
	 *  @return All values.
	 */
	public Set getAllValues()
	{
		return new LinkedHashSet(values);
	}
	
	/**
	 *  Add a value to the indexer.
	 *  @param value The value.
	 */
	public void addValue(T value)
	{
		// Add value to set of all values
		values.add(value);
		
		if(indexedvalues != null)
		{
			for(Map.Entry>> entry: indexedvalues.entrySet())
			{
				// Fetch all key values used 
				Set keys = keyextractor.getKeyValues(entry.getKey(), value);
				
				if(includenull && keys==null)
				{
					keys = new HashSet();
					keys.add("null");
				}	
				
				for(String key: SUtil.notNull(keys))
				{
					// Fetch the set of indexed elements for this key value and add the value
					Set valset = entry.getValue().get(key);
					if(valset == null)
					{
						valset = new HashSet();
						entry.getValue().put(key, valset);
					}
					valset.add(value);
				}
			}
		}
	}
	
	/**
	 *  Remove a value from the indexer.
	 *  @param value The value.
	 */
	public void removeValue(T value)
	{
//		if(!values.remove(value))
//			System.out.println("Could not remove value from indexer: "+value+", "+value.toString());
		
		if(indexedvalues != null)
		{
			for(Map.Entry>> entry : indexedvalues.entrySet())
			{
				Set keys = keyextractor.getKeyValues(entry.getKey(), value);
				if (keys != null)
				{
					for(String key : keys)
					{
						Set servset = entry.getValue().get(key);
						if (servset != null)
						{
							servset.remove(value);
							if (servset.isEmpty())
								entry.getValue().remove(key);
						}
					}
				}
			}
		}
		
		values.remove(value);
	}
	
	/**
	 *  Tests if the search specification matches a value (spec=query).
	 *  @param value The value.
	 *  @return True, if the value matches.
	 */
	public boolean match(List> spec, T value)
	{
		for(Tuple3 tup: spec)
		{
			// Fetch the values of the cached element
			Set keyvals = keyextractor.getKeyValues(tup.getFirstEntity(), value);
			
			if(keyvals == null)
				return false;
			
			// All vals of query must be contained in object
			if(tup.getThirdEntity())
			{
				for(String val: tup.getSecondEntity())
				{
					if(!keyvals.contains(val))
						return false;
				}
			}
			else // or
			{
				boolean found = false;
				for(String val: tup.getSecondEntity())
				{
					if(keyvals.contains(val))
					{
						found = true;
						break;
					}
				}
				if(!found)
					return false;
			}
		}
		
		return true;
	}
	
	/**
	 *  Tests if the search specification matches a value (spec=service, value=query).
	 *  
	 *  @param value The value.
	 *  @return True, if the value matches.
	 */
	public boolean matchOr(List> spec, T value)
	{
		Map> totest = new HashMap>();
		for(Tuple2 tup: spec)
		{
			if(tup.getSecondEntity()!=null)
			{
				Set vals = new HashSet();
				for(String val: tup.getSecondEntity())
				{
					vals.add(val);
				}
				totest.put(tup.getFirstEntity(), vals);
			}
		}
		
		// Get keys from value (query) 
		for(String keyname: keyextractor.getKeyNames())
		{
			Set vs = keyextractor.getKeyValues(keyname, value);
			if(vs!=null && vs.size()>0)
			{
				Boolean mode = keyextractor.getKeyMatchingMode(keyname, value);
				Set svals = totest.get(keyname);
				
				// All tags of query must be contained in service
				if(mode==null || mode) // TRUE=AND
				{
					for(String val: vs)
					{
						if(svals==null || !svals.contains(val))
							return false;
					}
				}
				else if(mode!=null && !mode)
				{
					boolean found = false;
					for(String val: vs)
					{
						if(svals!=null && svals.contains(val))
						{
							found = true;
							break;
						}
						if(!found)
							return false;
					}
				}
			}
		}
		
		return true;
	}
	
	/**
	 *  Gets the key extractor used by the service.
	 * 
	 *  @return The key extractor.
	 */
	public IKeyExtractor getKeyExtractor()
	{
		return keyextractor;
	}
	
	/**
	 *  Clears all contained values.
	 */
	public void clear()
	{
		for(Map> index : indexedvalues.values())
			index.clear();
		values.clear();
	}
	
	/**
	 *  Update an index for all values.
	 */
	public void updateIndex(String indexname)
	{
		Map> index = new HashMap>();
		for(T value: values)
		{
			Set keys = keyextractor.getKeyValues(indexname, value);
		
			if(includenull && keys==null)
			{
				keys = new HashSet();
				keys.add("null");
			}	
			
			if(keys!=null)
			{
				for(String key: keys)
				{
					Set valset = index.get(key);
					if(valset == null)
					{
						valset = new HashSet();
						index.put(key, valset);
					}
					valset.add(value);
				}
			}
		}
		indexedvalues.put(indexname, index);
	}
	
	/**
	 *  Main for testing.
	 */
	public static void main(String[] args)
	{
		Indexer> idx = new Indexer>(new IKeyExtractor>()
		{
			public Set getKeyValues(String keytype, ServiceQuery query)
			{
				Set ret = null;
				if(QueryInfoExtractor.KEY_TYPE_INTERFACE.equals(keytype))
				{
					if(query.getServiceType()!=null)
					{
						ret = new HashSet();
						ret.add(query.getServiceType().toString());
					}
					
					// todo:
//					ClassInfo[] supertypes = service.getId().getServiceSuperTypes();
//					if (supertypes != null)
//					{
//						for (ClassInfo supertype : supertypes)
//							ret.add(supertype.toString());
//					}
				}
				else if(QueryInfoExtractor.KEY_TYPE_TAGS.equals(keytype))
				{
					String[] tags = query.getServiceTags();
					if(tags!=null)
					{
						ret = new HashSet();
						for(String tag: tags)
						{
							ret.add(tag);
						}
					}
				}
				else if(QueryInfoExtractor.KEY_TYPE_PROVIDER.equals(keytype))
				{
					if(ServiceScope.COMPONENT_ONLY.equals(query.getScope()))
						ret = new SetWrapper(query.getSearchStart() != null ? query.getSearchStart().toString() : query.getOwner().toString());
				}
				else if(QueryInfoExtractor.KEY_TYPE_PLATFORM.equals(keytype))
				{
					//if(query.getProvider()!=null)
						ret = new SetWrapper(query.getPlatform().toString());
				}
				else if("owner".equals(keytype))
				{
					if(query.getOwner()!=null)
						ret = new SetWrapper(query.getOwner().toString());
				}
				return ret;
			}
		
			/**
			 *  Extracts the matching mode from a multivalued term.
			 *  true = AND, false = OR
			 *  
			 *  @param keytype The type of key being extracted.
			 *  @param value The value.
			 *  @return The key matching mode.
			 */
			public Boolean getKeyMatchingMode(String keytype, ServiceQuery query)
			{
				if(QueryInfoExtractor.KEY_TYPE_TAGS.equals(keytype))
					return Boolean.TRUE;
				return null;
			}
			
			/**
			 *  Get the key names for this type of extractor.
			 *  @return The key names.
			 */
			public String[] getKeyNames()
			{
				return ServiceKeyExtractor.SERVICE_KEY_TYPES;
			}
		
		}, true, ServiceKeyExtractor.SERVICE_KEY_TYPES); // todo: change to query types
		
		ServiceQuery q0 = new ServiceQuery<>((Class)null, null, null);
		idx.addValue(q0);

		ServiceQuery q1 = new ServiceQuery<>(new ClassInfo(ILibraryService.class), null, null);
		q1.setServiceTags(new String[]{"a", "b", "c"});
		idx.addValue(q1);
		ServiceQuery q2 = new ServiceQuery<>(new ClassInfo(ILibraryService.class), null, null);
		q2.setServiceTags(new String[]{"a", "b"});
		idx.addValue(q2);
		ServiceQuery q3 = new ServiceQuery<>(new ClassInfo(ILibraryService.class), null, null);

		q3.setServiceTags(new String[]{"a"});
		idx.addValue(q3);
		
		List> spec = new ArrayList>();
		Tuple2 s1 = new Tuple2(ServiceKeyExtractor.KEY_TYPE_INTERFACE, new String[]{ILibraryService.class.getName()});
		spec.add(s1);
		Tuple2 s2 = new Tuple2(ServiceKeyExtractor.KEY_TYPE_TAGS, new String[]{"a", "b"});
		spec.add(s2);
		
		Set> res = idx.getValuesInverted(spec);
		if(res!=null)
		{
			for(ServiceQuery r: res)
				System.out.println(r);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy