001/*
002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package cascading.util.cache;
023
024import java.util.AbstractCollection;
025import java.util.AbstractSet;
026import java.util.Collection;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.NoSuchElementException;
030import java.util.Set;
031
032import org.slf4j.Logger;
033import org.slf4j.LoggerFactory;
034
035/**
036 * DirectMappedCache is an implementation of the {@link cascading.util.cache.CascadingCache} interface following the semantics of
037 * http://en.wikipedia.org/wiki/CPU_cache#Direct-mapped_cache. The Cache is backed by an array that stays constant in size.
038 * <p>
039 * Unlike other implementation of a Map a hash collision will lead to the entry being overwritten.
040 * The {@link CacheEvictionCallback} is called with the entry that will be overwritten.
041 * <p>
042 * Use this cache if the keys are arriving in a random if not uniformly distributed order in order to reduce the number
043 * of hash and equality comparisons.
044 * <p>
045 * If duplicate keys are clustered near each other in the incoming tuple stream, consider the {@link cascading.util.cache.LRUHashMapCache} cache
046 * instead.
047 * <p>
048 * DirectMappedCache does not permit {@code null} keys nor {@code null} values
049 *
050 * @see cascading.pipe.assembly.Unique
051 * @see cascading.pipe.assembly.AggregateBy
052 * @see DirectMappedCacheFactory
053 * @see LRUHashMapCacheFactory
054 * @see LRUHashMapCache
055 */
056public final class DirectMappedCache<Key, Value> implements CascadingCache<Key, Value>
057  {
058  /** logger */
059  private static final Logger LOG = LoggerFactory.getLogger( DirectMappedCache.class );
060
061  /** size of the backing array. */
062  private int capacity;
063
064  /** number of actual key in the map. */
065  private int actualSize = 0;
066
067  /** array used for storing the entries */
068  private Entry<Key, Value>[] elements;
069
070  private long collisions = 0;
071
072  private long putCalls = 0;
073
074  public boolean initialized = false;
075
076  /** callback that is called whenever an entry is overwritten or removed from the cache. */
077  private CacheEvictionCallback evictionCallBack = CacheEvictionCallback.NULL;
078
079  @Override
080  public int size()
081    {
082    return actualSize;
083    }
084
085  @Override
086  public boolean isEmpty()
087    {
088    return actualSize < 1;
089    }
090
091  @Override
092  public boolean containsKey( Object key )
093    {
094    if( key == null )
095      throw new IllegalArgumentException( "null keys are not permitted" );
096
097    return get( key ) != null;
098    }
099
100  @Override
101  public boolean containsValue( Object value )
102    {
103    if( value == null )
104      throw new IllegalArgumentException( "value cannot be null" );
105
106    Iter<Entry<Key, Value>> iter = new Iter<Entry<Key, Value>>( elements );
107
108    while( iter.hasNext() )
109      {
110      Value current = iter.next().getValue();
111      if( current.equals( value ) )
112        return true;
113      }
114
115    return false;
116    }
117
118  @Override
119  public Value get( Object key )
120    {
121    int index = index( key );
122    Entry<Key, Value> existing = elements[ index ];
123
124    if( existing == null || !key.equals( existing.getKey() ) )
125      return null;
126
127    return existing.getValue();
128    }
129
130  @Override
131  public Value put( Key key, Value value )
132    {
133    if( key == null )
134      throw new IllegalArgumentException( "key cannot be null" );
135
136    if( value == null )
137      throw new IllegalArgumentException( "value cannot be null" );
138
139    putCalls++;
140
141    Value previous = null;
142    int index = index( key );
143    Entry<Key, Value> existing = elements[ index ];
144
145    if( existing != null )
146      {
147      previous = existing.getValue();
148      collisions++;
149      evictionCallBack.evict( existing );
150      }
151    else
152      actualSize++;
153
154    elements[ index ] = new CacheEntry<Key, Value>( key, value );
155
156    if( putCalls % getCapacity() == 0 )
157      {
158      Runtime runtime = Runtime.getRuntime();
159      long freeMem = runtime.freeMemory() / 1024 / 1024;
160      long maxMem = runtime.maxMemory() / 1024 / 1024;
161      long totalMem = runtime.totalMemory() / 1024 / 1024;
162      LOG.info( "mem on flush (mb), free: " + freeMem + ", total: " + totalMem + ", max: " + maxMem );
163      LOG.info( "capacity={}, puts={}, collisions={}, fill factor={}%", getCapacity(), putCalls, collisions,
164        ( (double) getCapacity() / actualSize ) * 100 );
165      float percent = (float) totalMem / (float) maxMem;
166      if( percent < 0.80F )
167        LOG.info( "total mem is {}% of max mem, to better utilize unused memory consider increasing the cache size", (int) ( percent * 100.0F ) );
168      }
169
170    return previous;
171    }
172
173  private int index( Object key )
174    {
175    return Math.abs( key.hashCode() % capacity );
176    }
177
178  @Override
179  public Value remove( Object key )
180    {
181    if( key == null )
182      throw new IllegalArgumentException( "key cannot be null" );
183
184    int index = index( key );
185    Entry<Key, Value> existing = elements[ index ];
186
187    if( existing == null || !existing.getKey().equals( key ) )
188      return null;
189
190    elements[ index ] = null;
191    actualSize--;
192    evictionCallBack.evict( existing );
193
194    return existing.getValue();
195    }
196
197  @Override
198  public void putAll( Map<? extends Key, ? extends Value> m )
199    {
200    for( Entry<? extends Key, ? extends Value> entry : m.entrySet() )
201      put( entry.getKey(), entry.getValue() );
202    }
203
204  @Override
205  public void clear()
206    {
207    elements = new CacheEntry[ capacity ];
208    actualSize = 0;
209    }
210
211  @Override
212  public Set<Key> keySet()
213    {
214    return new KeySetView<Key>( new SetView<Map.Entry<Key, ?>>( elements, actualSize ) );
215    }
216
217  @Override
218  public Collection<Value> values()
219    {
220    return new ValueCollection( new SetView<Map.Entry<?, Value>>( elements, actualSize ) );
221    }
222
223  @Override
224  public Set<Map.Entry<Key, Value>> entrySet()
225    {
226    return new SetView<Entry<Key, Value>>( elements, actualSize );
227    }
228
229  @Override
230  public int getCapacity()
231    {
232    return capacity;
233    }
234
235  @Override
236  public void setCacheEvictionCallback( CacheEvictionCallback cacheEvictionCallback )
237    {
238    if( initialized )
239      throw new IllegalStateException( "cannot set callback after initialization" );
240
241    this.evictionCallBack = cacheEvictionCallback;
242    }
243
244  @Override
245  public void setCapacity( int capacity )
246    {
247    if( initialized )
248      throw new IllegalArgumentException( "cannot set size after initialization" );
249
250    this.capacity = capacity;
251    }
252
253  @Override
254  public void initialize()
255    {
256    if( capacity < 1 )
257      throw new IllegalStateException( "capacity must be larger than 0" );
258
259    if( evictionCallBack == null )
260      throw new IllegalStateException( "evictionCallback cannot be null" );
261
262    elements = new CacheEntry[ capacity ];
263    initialized = true;
264    }
265
266  static class CacheEntry<Key, Value> implements Entry<Key, Value>
267    {
268    private final Key key;
269    private Value value;
270
271    CacheEntry( Key key, Value value )
272      {
273      this.key = key;
274      this.value = value;
275      }
276
277    @Override
278    public Key getKey()
279      {
280      return key;
281      }
282
283    @Override
284    public Value getValue()
285      {
286      return value;
287      }
288
289    @Override
290    public Value setValue( Value value )
291      {
292      Value oldValue = this.value;
293      this.value = value;
294      return oldValue;
295      }
296
297    @Override
298    public boolean equals( Object object )
299      {
300      if( this == object )
301        return true;
302      if( object == null || getClass() != object.getClass() )
303        return false;
304
305      Entry that = (Entry) object;
306
307      if( key != null ? !key.equals( that.getKey() ) : that.getKey() != null )
308        return false;
309
310      if( value != null ? !value.equals( that.getValue() ) : that.getValue() != null )
311        return false;
312
313      return true;
314      }
315
316    @Override
317    public int hashCode()
318      {
319      int result = key != null ? key.hashCode() : 0;
320      result = 31 * result + ( value != null ? value.hashCode() : 0 );
321      return result;
322      }
323
324    @Override
325    public String toString()
326      {
327      return "CacheEntry{" +
328        "key=" + key +
329        ", value=" + value +
330        '}';
331      }
332    }
333
334  class SetView<T> extends AbstractSet<T>
335    {
336    private final T[] elements;
337    private final int size;
338    private final Iter<T> iter;
339
340    SetView( T[] elements, int size )
341      {
342      this.elements = elements;
343      this.size = size;
344      this.iter = new Iter<T>( elements );
345      }
346
347    @Override
348    public int size()
349      {
350      return size;
351      }
352
353    @Override
354    public Iterator<T> iterator()
355      {
356      return new Iter<T>( elements );
357      }
358
359    @Override
360    public boolean containsAll( Collection<?> c )
361      {
362      for( Object object : c )
363        {
364        if( !contains( object ) )
365          return false;
366        }
367
368      return true;
369      }
370
371    @Override
372    public boolean addAll( Collection<? extends T> c )
373      {
374      throw new UnsupportedOperationException( "SetView is read only." );
375      }
376
377    @Override
378    public boolean retainAll( Collection<?> c )
379      {
380      throw new UnsupportedOperationException( "SetView is read only." );
381      }
382
383    @Override
384    public boolean removeAll( Collection<?> c )
385      {
386      throw new UnsupportedOperationException( "SetView is read only." );
387      }
388
389    @Override
390    public void clear()
391      {
392      throw new UnsupportedOperationException( "SetView is read only." );
393      }
394    }
395
396  static class Iter<T> implements Iterator<T>
397    {
398    private final T[] elements;
399    private T nextElement;
400    int currentIndex = -1;
401
402    public Iter( T[] elements )
403      {
404      this.elements = elements;
405      this.nextElement = findNext();
406      }
407
408    private T findNext()
409      {
410      while( currentIndex < elements.length - 1 )
411        {
412        currentIndex++;
413
414        if( elements[ currentIndex ] != null )
415          return elements[ currentIndex ];
416        }
417
418      return null;
419      }
420
421    @Override
422    public boolean hasNext()
423      {
424      return nextElement != null;
425      }
426
427    @Override
428    public T next()
429      {
430      if( !hasNext() )
431        throw new NoSuchElementException();
432
433      T current = nextElement;
434
435      nextElement = findNext();
436
437      return current;
438      }
439
440    @Override
441    public void remove()
442      {
443      throw new UnsupportedOperationException( "Not supported" );
444      }
445
446    public void reset()
447      {
448      currentIndex = -1;
449      nextElement = findNext();
450      }
451    }
452
453  static class KeySetView<Key> extends AbstractSet<Key>
454    {
455    private final Set<Map.Entry<Key, ?>> parent;
456
457    public KeySetView( Set<Map.Entry<Key, ?>> parent )
458      {
459      this.parent = parent;
460      }
461
462    @Override
463    public Iterator<Key> iterator()
464      {
465      return new KeyIterator<Key>( parent.iterator() );
466      }
467
468    @Override
469    public int size()
470      {
471      return parent.size();
472      }
473    }
474
475  static class ValueCollection<Value> extends AbstractCollection<Value>
476    {
477    private Set<Entry<?, Value>> parent;
478
479    ValueCollection( Set<Entry<?, Value>> parent )
480      {
481      this.parent = parent;
482      }
483
484    @Override
485    public Iterator<Value> iterator()
486      {
487      return new ValueIterator<Value>( parent.iterator() );
488      }
489
490    @Override
491    public int size()
492      {
493      return parent.size();
494      }
495    }
496
497  static class KeyIterator<Key> implements Iterator<Key>
498    {
499    private final Iterator<Map.Entry<Key, ?>> parent;
500
501    public KeyIterator( Iterator<Map.Entry<Key, ?>> parent )
502      {
503      this.parent = parent;
504      }
505
506    @Override
507    public Key next()
508      {
509      return parent.next().getKey();
510      }
511
512    @Override
513    public boolean hasNext()
514      {
515      return parent.hasNext();
516      }
517
518    @Override
519    public void remove()
520      {
521      parent.remove();
522      }
523    }
524
525  static class ValueIterator<Value> implements Iterator<Value>
526    {
527    private final Iterator<Map.Entry<?, Value>> parent;
528
529    public ValueIterator( Iterator<Map.Entry<?, Value>> parent )
530      {
531      this.parent = parent;
532      }
533
534    @Override
535    public Value next()
536      {
537      return parent.next().getValue();
538      }
539
540    @Override
541    public boolean hasNext()
542      {
543      return parent.hasNext();
544      }
545
546    @Override
547    public void remove()
548      {
549      parent.remove();
550      }
551    }
552  }