A variation of a map that supports querying on multiple keys

Last evening, for unknown reasons, I started pondering how easy if would be to implement a map data structure that could be queried using different means. For example, what if the AddressBean described below could be queried by name, owner etc etc . After some aimless coding, this is what I came up. Not strictly generic compliant, but close enough for a proof of concept.

The implementation I came up with is, strictly speaking, not key based, rather bean-property based as defined by the chosen implementation of the interface ObjectKeyGenerator. In that sense, it is more like a variation on hashkeys. Note, however, that it doesn’t enforce uniqueness, so if there were multiple owners with name “a” in the described example, the last inserted one would overwrite peior entries mapped to that key. Easily addressable via a multimap implementation, but beyond the scope of this post, however.

A simple application demoing the use of the datastructure.

package com.vgb.multikeymap;


public class AddressBeanApp {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		AddressBean a1 = new AddressBean("a", "street 1", 1000, 101, 75080);
		AddressBean a2 = new AddressBean("b", "street 1", 1002, 102, 75080);
			MultiKeyMap<AddressBean> map = new AddressMultKeyMap<AddressBean>();
			map.put(a1);
			map.put(a2);
			
			System.out.println(map.get(AddressMultKeyMap.OWNER, "a"));
			System.out.println(map.get(AddressMultKeyMap.SUITE, 101));
			System.out.println(map.get(AddressMultKeyMap.OWNER, "b"));
			System.out.println(map.get(AddressMultKeyMap.OWNER, "foo"));
			System.out.println(map.get("INVALID_TYPE", "a"));
	}

}

The bean that is stored.

package com.vgb.multikeymap;

public class AddressBean {
	private String owner;
	private String street;
	private int houseNum;
	private int suite;
	private int zipCode;
	
	public AddressBean(String owner, String street, int houseNum, int suite, int zipCode) {
		super();
		this.owner = owner;
		this.street = street;
		this.houseNum = houseNum;
		this.suite = suite;
		this.zipCode = zipCode;
	}

	public String getStreet() {
		return street;
	}

	public void setStreet(String street) {
		this.street = street;
	}

	public int getHouseNum() {
		return houseNum;
	}

	public void setHouseNum(int houseNum) {
		this.houseNum = houseNum;
	}

	public int getSuite() {
		return suite;
	}

	public void setSuite(int suite) {
		this.suite = suite;
	}

	public int getZipCode() {
		return zipCode;
	}

	public void setZipCde(int zipCode) {
		this.zipCode = zipCode;
	}

	public String getOwner() {
		return owner;
	}

	public void setOwner(String owner) {
		this.owner = owner;
	}

	@Override
	public String toString() {
		return "AddressBean [owner=" + owner + ", street=" + street + ", houseNum=" + houseNum + ", suite=" + suite + ", zipCode=" + zipCode + "]";
	}


}

The implementation of the multi-key map with key definitions explicitly coded in

package com.vgb.multikeymap;

import java.util.AbstractMap.SimpleEntry;

public class AddressMultKeyMap<V> extends MultiKeyMap<AddressBean> {

	public static final String OWNER = "owner";
	public static final String SUITE = "suite";

	public AddressMultKeyMap() {
		super();
	}

	@SuppressWarnings("unchecked")
	@Override
	public void generateTypesAndKeysFromValues() {

		initInnerMapItem(new SimpleEntry<String, ObjectKeyGenerator<?>>(SUITE, new ObjectKeyGenerator<AddressBean>() {

			@Override
			public Object extractKey(AddressBean data) {
				return data.getSuite();
			}
		}));

		initInnerMapItem(new SimpleEntry<String, ObjectKeyGenerator<?>>(OWNER, new ObjectKeyGenerator<AddressBean>() {

			@Override
			public Object extractKey(AddressBean data) {
				return data.getOwner();
			}
		}));

	}

};

The abstract class that does all the heavy lifting

package com.vgb.multikeymap;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public abstract class MultiKeyMap<V> {

	private Map<String, ObjectKeyGenerator<V>> typeKeyGenMap = new HashMap<String, ObjectKeyGenerator<V>>();

	protected Map<String, Map<Object, V>> mainMap = new HashMap<String, Map<Object, V>>();
	
	public MultiKeyMap() {
		generateTypesAndKeysFromValues();
	}
	
	public Map<Object, V> getMap(String type) {
		if (mainMap.containsKey(type)) {
			return mainMap.get(type);
		}
		return null;
	}

	public V get(String type, Object key) {
		Map<Object, V> myMap = getMap(type);
		if (myMap == null) {
			return null;
		}
		return myMap.get(key);
	}

	public void put(V value) {
		for (Entry<String, ObjectKeyGenerator<V>> e : typeKeyGenMap.entrySet()) {
			String type = e.getKey();
			Object key = e.getValue().extractKey(value);

			mainMap.get(type).put(key, value);
		}
	}

	public abstract void generateTypesAndKeysFromValues();

	private Map<Object, V> createInnerMap() {
		return new HashMap<Object, V>();
	}


	protected void initInnerMap(Entry<String, ObjectKeyGenerator>... vargs) {
		for (Entry<String, ObjectKeyGenerator> entry : vargs) {
			if (entry.getKey() == null || entry.getKey().trim().isEmpty()) {
				continue;
			}
			typeKeyGenMap.put(entry.getKey(), entry.getValue());
			mainMap.put(entry.getKey(), createInnerMap());
		}
	}

	protected void initInnerMapItem(Entry<String, ObjectKeyGenerator<?>> e1) {

		if (e1.getKey() == null || e1.getKey().trim().isEmpty()) {
			return;
		}
		typeKeyGenMap.put(e1.getKey(), (ObjectKeyGenerator<V>) e1.getValue());
		mainMap.put(e1.getKey(), createInnerMap());
	}

}

An interface defining how the key extraction behavior

package com.vgb.multikeymap;

public interface ObjectKeyGenerator<V> {

	public Object extractKey(V data);

}

A helper transport class for input parameter pairs

package com.vgb.multikeymap;

public class TypeObjectKeyGenPair<V> {
	String type;
	ObjectKeyGenerator<V> keyGen;

	public TypeObjectKeyGenPair(String type, ObjectKeyGenerator<V> keyGen) {
		super();
		this.type = type;
		this.keyGen = keyGen;
	}

	public String getType() {
		return type;
	}

	public ObjectKeyGenerator<V> getKeyGen() {
		return keyGen;
	}

}

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s