/* * Copyright (c) 2014. by Robusta Code and individual contributors * as indicated by the @authors tag. See the copyright.txt in the * distribution for a full listing of individual contributors. * * 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 io.robusta.rra.cache; import io.robusta.rra.representation.Representation; import io.robusta.rra.resource.Resource; import io.robusta.rra.resource.ResourceList; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * An LRU cache, based on <code>LinkedHashMap</code>. * * This cache has a fixed maximum number of elements (<code>cacheSize</code>). * If the cache is full and another entry is added, the LRU (least recently * used) entry is dropped. * * This class is thread-safe. All methods of this class are synchronized. * * @author Jean-Marc Villatte * */ public class MyCache { private static MyCache instance; private static final float hashTableLoadFactor = 0.75f; private LinkedHashMap<String, Representation> mapCache; private Map<String, Set<String>> mapCacheDependencies; public static int defaultCacheSize = 10; private int cacheSize; public static MyCache getInstance() { if ( instance == null ) { instance = new MyCache( defaultCacheSize ); } return instance; } /** * Creates a new LRU cache. * * @param cacheSize * the maximum number of entries that will be kept in this cache. */ private MyCache( int cacheSize ) { this.cacheSize = cacheSize; int hashTableCapacity = (int) Math.ceil( cacheSize / hashTableLoadFactor ) + 1; mapCache = new LinkedHashMap<String, Representation>( hashTableCapacity, hashTableLoadFactor, true ) { // (an anonymous inner class) private static final long serialVersionUID = 1; @Override protected boolean removeEldestEntry( Map.Entry<String, Representation> eldest ) { return size() > MyCache.this.cacheSize; } }; mapCacheDependencies = new Hashtable<String, Set<String>>(); } /** * Retrieves an entry from the cache.<br> * The retrieved entry becomes the MRU (most recently used) entry. * * @param key * the key whose associated value is to be returned. * @return the value associated to this key, or null if no value with this * key exists in the cache. */ public synchronized Representation<?> get( String key ) { Representation<?> representation = mapCache.get( key ); if ( representation != null ) { System.out.println( key + ": get from cache" ); return representation; } return null; } /** * Adds an entry to this cache. The new entry becomes the MRU (most recently * used) entry. If an entry with the specified key already exists in the * cache, it is replaced by the new entry. If the cache is full, the LRU * (least recently used) entry is removed from the cache. * * @param key * the key with which the specified value is to be associated. * @param value * a value to be associated with the specified key. */ public synchronized void put( String representationKey, Representation representation, Resource<?> resource ) { mapCache.put( representationKey, representation ); String resourceKey = resource.getPrefix() + ":" + resource.getId(); mapCacheDependenciesAddEntry( representationKey, resourceKey ); recursiveDependencies( representationKey, resource, Collections.synchronizedSet( new HashSet<String>() ) ); } /** * Adds an entry to this cache. The new entry becomes the MRU (most recently * used) entry. If an entry with the specified key already exists in the * cache, it is replaced by the new entry. If the cache is full, the LRU * (least recently used) entry is removed from the cache. * * @param key * the key with which the specified value is to be associated. * @param representationKey * @param representation * @param resource */ public synchronized void put( String representationKey, Representation representation, ResourceList<?, ?> resources ) { mapCache.put( representationKey, representation ); for ( Resource<?> resource : resources ) { String resourceKey = resource.getPrefix() + ":" + resource.getId(); mapCacheDependenciesAddEntry( representationKey, resourceKey ); recursiveDependencies( representationKey, resource, Collections.synchronizedSet( new HashSet<String>() ) ); } } void recursiveDependencies( String representationKey, Resource<?> resource, Set setLoop ) { setLoop.add( resource.getPrefix() + ":" + resource.getId() ); for ( Field field : getAllFields( new LinkedList<Field>(), resource.getClass() ) ) { if ( Resource.class.isAssignableFrom( field.getType() ) ) { try { field.setAccessible( true ); Resource<?> resourceChild = (Resource<?>) field.get( resource ); if ( resourceChild != null && !setLoop.contains( resourceChild.getPrefix() + ":" + resourceChild.getId() ) ) { String resourceChildKey = resourceChild.getPrefix() + ":" + resourceChild.getId(); mapCacheDependenciesAddEntry( representationKey, resourceChildKey ); recursiveDependencies( representationKey, resourceChild, setLoop ); } } catch ( IllegalArgumentException e ) { e.printStackTrace(); } catch ( IllegalAccessException e ) { e.printStackTrace(); } } // recursive on collections if ( Collection.class.isAssignableFrom( field.getType() ) ) { Type type = field.getGenericType(); if ( type instanceof ParameterizedType ) { ParameterizedType pType = (ParameterizedType) type; Type[] arr = pType.getActualTypeArguments(); for ( Type tp : arr ) { Class<?> clzz = (Class<?>) tp; if ( Resource.class.isAssignableFrom( clzz ) ) { Collection<?> collectionChild; try { field.setAccessible( true ); collectionChild = (Collection<?>) field.get( resource ); if ( collectionChild != null ) { for ( Object cl : collectionChild ) { if ( cl != null && !setLoop.contains( ( (Resource) cl ).getPrefix() + ":" + ( (Resource) cl ).getId() ) ) { String resourceChildKey = ( (Resource) cl ).getPrefix() + ":" + ( (Resource) cl ).getId(); mapCacheDependenciesAddEntry( representationKey, resourceChildKey ); recursiveDependencies( representationKey, (Resource) cl, setLoop ); } } } } catch ( IllegalArgumentException e ) { e.printStackTrace(); } catch ( IllegalAccessException e ) { e.printStackTrace(); } } } } } } } /** * Retrieve All Fields (inherited) from a Class * * @param fields * @param type * @return */ private static List<Field> getAllFields( List<Field> fields, Class<?> type ) { for ( Field field : type.getDeclaredFields() ) { fields.add( field ); } if ( type.getSuperclass() != null ) { fields = getAllFields( fields, type.getSuperclass() ); } return fields; } /** * Retrieve All Fields (inherited) from a Class * * @param fields * @param type * @return */ private void mapCacheDependenciesAddEntry( String representationKey, String resourceKey ) { if ( !mapCacheDependencies.containsKey( resourceKey ) ) { Set<String> set = Collections.synchronizedSet( new HashSet<String>() ); set.add( representationKey ); mapCacheDependencies.put( resourceKey, set ); } else { Set<String> set = mapCacheDependencies.get( resourceKey ); set.add( representationKey ); } } /** * Removes an entry to this cache. * * @param key * the key removed. */ public synchronized void invalidate( String key ) { mapCache.remove( key ); if ( mapCacheDependencies.containsKey( key ) ) { Set<String> set = mapCacheDependencies.get( key ); for ( String keyDependencie : set ) { mapCache.remove( keyDependencie ); } // set.clear(); mapCacheDependencies.remove( key ); } } /** * Clears the cache. */ public synchronized void clear() { mapCache.clear(); } /** * Returns the number of used entries in the cache. * * @return the number of entries currently in the cache. */ public synchronized int usedEntries() { return mapCache.size(); } /** * Returns a <code>Collection</code> that contains a copy of all cache * entries. * * @return a <code>Collection</code> with a copy of the cache content. */ public synchronized Collection<Map.Entry<String, Representation>> getAll() { return new ArrayList<Map.Entry<String, Representation>>( mapCache.entrySet() ); } /** * Returns a <code>ArrayList</code> that contains a copy of all * mapCacheDependencies entries. * * @return a <code>ArrayList</code> with a copy of the mapCacheDependencies * content. */ public synchronized ArrayList<Entry<String, Set<String>>> getAllDependenciesMap() { return new ArrayList<Map.Entry<String, Set<String>>>( mapCacheDependencies.entrySet() ); } /** * Display cache and mapCacheDependencies content * */ public void displayCache() { System.out.println( "*** MyCache content ***" ); System.out.println( getAll() ); System.out.println( "***dependencie map content ***" ); System.out.println( getAllDependenciesMap() ); System.out.println( "----------------" ); } }