/*
* 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.wicket.security.util;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Maps keys to lists of values and values to lists of keys. The whole concept of key
* value is a bit vague here because each value is also a key. Consider the following
* example: A maps to B and C, B maps to D. get(A) would return B and C, get(B) would
* return A and D, get(C) would return A, get(D) would return B. Each mapping is
* bidirectional.
*
* @author marrink
*/
public class ManyToManyMap<L, R>
{
private static final long serialVersionUID = 1L;
private Map<L, Set<R>> lToRMappings;
private Map<R, Set<L>> rToLMappings;
/**
* Creates map with default initial size and load factor.
*/
public ManyToManyMap()
{
lToRMappings = new HashMap<L, Set<R>>();
rToLMappings = new HashMap<R, Set<L>>();
}
/**
* Creates map with default load factor and specified initial size.
*
* @param initialCapacity
*/
public ManyToManyMap(int initialCapacity)
{
lToRMappings = new HashMap<L, Set<R>>(initialCapacity);
rToLMappings = new HashMap<R, Set<L>>(initialCapacity);
}
/**
* Creates map with specified initial size and load factor. For more information about
* these see {@link HashMap}
*
* @param initialCapacity
* @param loadFactor
*/
public ManyToManyMap(int initialCapacity, float loadFactor)
{
lToRMappings = new HashMap<L, Set<R>>(initialCapacity, loadFactor);
rToLMappings = new HashMap<R, Set<L>>(initialCapacity, loadFactor);
}
/**
* Adds a key value mapping in this map. Since this maps many to many relations no
* previous mappings will be overridden. The size of the map may or may not change
* depending on whether both objects are already present or not
*
* @param left
* @param right
*/
public void add(L left, R right)
{
if (left == null)
throw new NullPointerException("left must not be null.");
if (right == null)
throw new NullPointerException("right must not be null.");
Set<R> rights = lToRMappings.get(left);
if (rights == null)
rights = new HashSet<R>();
rights.add(right);
lToRMappings.put(left, rights);
Set<L> lefts = rToLMappings.get(right);
if (lefts == null)
lefts = new HashSet<L>();
lefts.add(left);
rToLMappings.put(right, lefts);
}
/**
* Removes a many to many mapping between two objects. The size of the map may or may
* not change depending on on whether or not both objects have other mappings.
*
* @param left
* left side of the mapping
* @param right
* right side of the mapping
* @return false if the mapping did not exist, true otherwise
*/
public boolean remove(L left, R right)
{
Set<R> rights = lToRMappings.get(left);
if (rights != null)
{
if (rights.remove(right))
{
if (rights.isEmpty())
lToRMappings.remove(left);
Set<L> lefts = rToLMappings.get(right);
lefts.remove(left);
if (lefts.isEmpty())
rToLMappings.remove(right);
return true;
}
}
return false;
}
/**
* Remove all mappings for an object. The size of the map will at least decrease by
* one (if the object is present) but possibly more.
*
* @param left
* the left side of the many to many mapping
* @return the mappings that will be removed by this action
*/
public Set<R> removeAllMappingsForLeft(L left)
{
Set<R> rights = lToRMappings.remove(left);
if (rights != null)
{
for (R curRight : rights)
{
Set<L> curLefts = rToLMappings.get(curRight);
curLefts.remove(left);
if (curLefts.isEmpty())
rToLMappings.remove(curRight);
}
}
return rights;
}
/**
* Remove all mappings for an object. The size of the map will at least decrease by
* one (if the object is present) but possibly more.
*
* @param right
* the right side of the many to many mapping
* @return the mappings that will be removed by this action
*/
public Set<L> removeAllMappingsForRight(R right)
{
Set<L> lefts = rToLMappings.remove(right);
if (lefts != null)
{
for (L curLeft : lefts)
{
Set<R> curRights = lToRMappings.get(curLeft);
curRights.remove(right);
if (curRights.isEmpty())
lToRMappings.remove(curLeft);
}
}
return lefts;
}
/**
* Gets the bidirectional mappings for this object.
*
* @param left
* @return the many to many mappings for this object
*/
public Set<R> getRight(L left)
{
Set<R> set = lToRMappings.get(left);
if (set == null)
return Collections.emptySet();
return Collections.unmodifiableSet(set);
}
/**
* Gets the bidirectional mappings for this object.
*
* @param right
* @return the many to many mappings for this object
*/
public Set<L> getLeft(R right)
{
Set<L> set = rToLMappings.get(right);
if (set == null)
return Collections.emptySet();
return Collections.unmodifiableSet(set);
}
/**
* Returns the number of mapped values, left or right
*
* @return the number of mapped values
*/
public int size()
{
return lToRMappings.size() + rToLMappings.size();
}
/**
* Returns the number of keys mapped to a value.
*
* @param left
* @return the number of keys mapped to this value
*/
public int numberOfmappingsForLeft(L left)
{
Set<R> set = lToRMappings.get(left);
if (set == null)
return 0;
return set.size();
}
/**
* Returns the number of keys mapped to a value.
*
* @param right
* @return the number of keys mapped to this value
*/
public int numberOfmappingsForRight(R right)
{
Set<L> set = rToLMappings.get(right);
if (set == null)
return 0;
return set.size();
}
/**
* Check if this map contains a key.
*
* @param left
* a mapped object
* @return true if this map contains the key, false otherwise
*/
public boolean containsLeft(L left)
{
return lToRMappings.containsKey(left);
}
/**
* Check if this map contains a key.
*
* @param right
* a mapped object
* @return true if this map contains the key, false otherwise
*/
public boolean containsRight(R right)
{
return rToLMappings.containsKey(right);
}
/**
* Check if this map contains any mappings. If this map does is empty size will be 0.
*
* @return true if no mappings are present, false otherwise
*/
public boolean isEmpty()
{
return lToRMappings.isEmpty();
}
/**
* Removes all mappings.
*/
public void clear()
{
lToRMappings.clear();
rToLMappings.clear();
}
/**
* Returns an <tt>Iterator</tt> over every left hand mapping in this map. In no
* particular order.
*
* @return an iterator over this map
*/
public Iterator<L> leftIterator()
{
return lToRMappings.keySet().iterator();
}
/**
* Returns an <tt>Iterator</tt> over every rightt hand mapping in this map. In no
* particular order.
*
* @return an iterator over this map
*/
public Iterator<R> rightIterator()
{
return rToLMappings.keySet().iterator();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (obj instanceof ManyToManyMap< ? , ? >)
{
ManyToManyMap< ? , ? > other = (ManyToManyMap< ? , ? >) obj;
return lToRMappings.equals(other.lToRMappings)
&& rToLMappings.equals(other.lToRMappings);
}
return false;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return (7 * rToLMappings.hashCode()) ^ (37 * lToRMappings.hashCode()) + 1979;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return lToRMappings.toString() + '\n' + rToLMappings.toString();
}
}