001/*
002 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
003 *
004 * Project and contact information: http://www.cascading.org/
005 *
006 * This file is part of the Cascading project.
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *     http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020
021package cascading.util;
022
023import java.io.Serializable;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029
030/**
031 *
032 */
033public abstract class MultiMap<C extends Collection<V>, K, V> implements Serializable
034  {
035  private Map<K, C> map = null;
036
037  protected Map<K, C> getMap()
038    {
039    if( map == null )
040      map = createMap();
041
042    return map;
043    }
044
045  protected abstract Map<K, C> createMap();
046
047  protected abstract C createCollection();
048
049  protected abstract C emptyCollection();
050
051  public boolean containsKey( K key )
052    {
053    return getMap().containsKey( key );
054    }
055
056  public Set<K> getKeys()
057    {
058    return getMap().keySet();
059    }
060
061  public Set<Map.Entry<K, C>> getEntries()
062    {
063    return getMap().entrySet();
064    }
065
066  public void addAll( MultiMap<C, K, V> sourceMap )
067    {
068    if( sourceMap == null )
069      return;
070
071    Map<K, C> destinationMap = sourceMap.getMap();
072
073    for( Map.Entry<K, C> entry : destinationMap.entrySet() )
074      addAll( entry.getKey(), entry.getValue() );
075    }
076
077  public void put( K key, V value )
078    {
079    getMultiValues( key ).add( value );
080    }
081
082  public C remove( K key )
083    {
084    return getMap().remove( key );
085    }
086
087  protected C getMultiValues( K key )
088    {
089    Map<K, C> map = getMap();
090    C values = map.get( key );
091
092    if( values == null )
093      {
094      values = createCollection();
095      map.put( key, values );
096      }
097
098    return values;
099    }
100
101  public void addAll( K key, V... values )
102    {
103    addAll( key, Arrays.asList( values ) );
104    }
105
106  public void addAll( K key, Collection<V> values )
107    {
108    getMultiValues( key ).addAll( values );
109    }
110
111  public C getValues()
112    {
113    if( getMap().isEmpty() )
114      return emptyCollection();
115
116    C results = createCollection();
117
118    for( C values : getMap().values() )
119      results.addAll( values );
120
121    return results;
122    }
123
124  public C getValues( K key )
125    {
126    C values = getMap().get( key );
127
128    if( values == null )
129      return emptyCollection();
130
131    return values;
132    }
133
134  public C getAllValues( K... keys )
135    {
136    if( keys.length == 0 )
137      return emptyCollection();
138
139    if( keys.length == 1 )
140      return getValues( keys[ 0 ] );
141
142    C values = createCollection();
143
144    for( K key : keys )
145      {
146      C current = getMap().get( key );
147
148      if( current != null )
149        values.addAll( current );
150      }
151
152    return values;
153    }
154
155  public Set<K> getKeysFor( V value )
156    {
157    Set<K> results = new HashSet<>();
158
159    for( Map.Entry<K, C> entry : getMap().entrySet() )
160      {
161      if( entry.getValue().contains( value ) )
162        results.add( entry.getKey() );
163      }
164
165    return results;
166    }
167
168  public boolean hasKey( V value )
169    {
170    Set<K> keys = getKeysFor( value );
171
172    return keys != null && !keys.isEmpty();
173    }
174
175  public boolean hadKey( K key, V value )
176    {
177    C values = getMap().get( key );
178
179    return values != null && values.contains( value );
180    }
181
182  public boolean isEmpty()
183    {
184    return getMap().isEmpty();
185    }
186
187  @Override
188  public String toString()
189    {
190    final StringBuilder sb = new StringBuilder( "MultiMap{" );
191    sb.append( "map=" ).append( getMap() );
192    sb.append( '}' );
193    return sb.toString();
194    }
195
196  @Override
197  public boolean equals( Object object )
198    {
199    if( this == object )
200      return true;
201
202    if( !( object instanceof MultiMap ) )
203      return false;
204
205    MultiMap multiMap = (MultiMap) object;
206
207    if( map != null ? !map.equals( multiMap.map ) : multiMap.map != null )
208      return false;
209
210    return true;
211    }
212
213  @Override
214  public int hashCode()
215    {
216    return map != null ? map.hashCode() : 0;
217    }
218  }