001    /*
002     * Copyright (c) 2007-2015 Concurrent, 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    
021    package cascading.util.cache;
022    
023    import java.util.Collection;
024    import java.util.LinkedHashMap;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    /**
032     * Implementation of the {@link CascadingCache} interface backed by a {@link java.util.LinkedHashMap}. This implementation
033     * is used by default by {@link cascading.pipe.assembly.Unique} and {@link cascading.pipe.assembly.AggregateBy} and their
034     * subclasses.
035     *
036     * @see cascading.pipe.assembly.Unique
037     * @see cascading.pipe.assembly.AggregateBy
038     * @see DirectMappedCacheFactory
039     * @see OrderedHashMapCacheFactory
040     */
041    public class OrderedHashMapCache<Key, Value> implements CascadingCache<Key, Value>
042      {
043      /** logger */
044      private static final Logger LOG = LoggerFactory.getLogger( OrderedHashMapCache.class );
045    
046      /** capacity of the map. */
047      private int capacity;
048    
049      /** call-back used, when entries are removed from the cache. */
050      private CacheEvictionCallback callback = CacheEvictionCallback.NULL;
051    
052      /** counts flushes. */
053      private long flushes = 0;
054    
055      private LinkedHashMap<Key, Value> backingMap;
056    
057      @Override
058      public int getCapacity()
059        {
060        return capacity;
061        }
062    
063      @Override
064      public void setCacheEvictionCallback( CacheEvictionCallback cacheEvictionCallback )
065        {
066        this.callback = cacheEvictionCallback;
067        }
068    
069      @Override
070      public void setCapacity( int capacity )
071        {
072        this.capacity = capacity;
073        }
074    
075      @Override
076      public void initialize()
077        {
078        this.backingMap = new LinkedHashMap<Key, Value>( capacity, 0.75f, true )
079        {
080        @Override
081        protected boolean removeEldestEntry( Map.Entry<Key, Value> eldest )
082          {
083          boolean doRemove = size() > capacity;
084          if( doRemove )
085            {
086            callback.evict( eldest );
087            if( flushes % getCapacity() == 0 ) // every multiple, write out data
088              {
089              Runtime runtime = Runtime.getRuntime();
090              long freeMem = runtime.freeMemory() / 1024 / 1024;
091              long maxMem = runtime.maxMemory() / 1024 / 1024;
092              long totalMem = runtime.totalMemory() / 1024 / 1024;
093              LOG.info( "flushed keys num times: {}, with capacity: {}", flushes + 1, capacity );
094              LOG.info( "mem on flush (mb), free: " + freeMem + ", total: " + totalMem + ", max: " + maxMem );
095              float percent = (float) totalMem / (float) maxMem;
096              if( percent < 0.80F )
097                LOG.info( "total mem is {}% of max mem, to better utilize unused memory consider increasing the cache size", (int) ( percent * 100.0F ) );
098              }
099            flushes++;
100            }
101          return doRemove;
102          }
103        };
104        }
105    
106      @Override
107      public int size()
108        {
109        return backingMap.size();
110        }
111    
112      @Override
113      public boolean isEmpty()
114        {
115        return backingMap.isEmpty();
116        }
117    
118      @Override
119      public boolean containsKey( Object key )
120        {
121        return backingMap.containsKey( key );
122        }
123    
124      @Override
125      public boolean containsValue( Object value )
126        {
127        return backingMap.containsValue( value );
128        }
129    
130      @Override
131      public Value get( Object key )
132        {
133        return backingMap.get( key );
134        }
135    
136      @Override
137      public Value put( Key key, Value value )
138        {
139        return backingMap.put( key, value );
140        }
141    
142      @Override
143      public Value remove( Object key )
144        {
145        return backingMap.remove( key );
146        }
147    
148      @Override
149      public void putAll( Map<? extends Key, ? extends Value> m )
150        {
151        backingMap.putAll( m );
152        }
153    
154      @Override
155      public void clear()
156        {
157        backingMap.clear();
158        }
159    
160      @Override
161      public Set<Key> keySet()
162        {
163        return backingMap.keySet();
164        }
165    
166      @Override
167      public Collection<Value> values()
168        {
169        return backingMap.values();
170        }
171    
172      @Override
173      public Set<Entry<Key, Value>> entrySet()
174        {
175        return backingMap.entrySet();
176        }
177      }