/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.core.persistence; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import org.eclipse.skalli.model.EntityBase; import org.eclipse.skalli.model.EntityFilter; /** * Most trivial implementation of an in-memory cache for all kind of entities. * <p> * For any given entity class, there is a separate cache that can store * instances of the entity class alongside with instances of derived classes. * Note however, that the accessor methods of this class should be used * with the base class as parameter only, otherwise these accessors likely * will fail with {@link ClassCastException class cast exceptions}. */ class EntityCache { private final Map<Class<? extends EntityBase>, Map<UUID, EntityBase>> cache = new HashMap<Class<? extends EntityBase>, Map<UUID, EntityBase>>(0); /** * Checks if the given entity class has been registered with this cache before. * * @param entityClass the class of the entity to check. */ synchronized <T extends EntityBase> boolean isRegistered(Class<T> entityClass) { return cache.get(mapEntityType(entityClass)) != null; } /** * Registers the given entity class as entity base class with this cache. * * @param entityClass the class of the entity to register. * * @throws IllegalArgumentException if the entity class (or any base class of the * entity class) has already been registered before. */ synchronized <T extends EntityBase> void registerEntityClass(Class<T> entityClass) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap != null) { throw new IllegalArgumentException(MessageFormat.format( "Entity type \"{0}\" already registered", entityClass.getName())); } entityMap = new HashMap<UUID, EntityBase>(0); cache.put(entityClass, entityMap); } /** * Maps the given entity class to a entity base class that has been registered before. * * @param entityClass the class of the entity to map. * * @return a base class of the given entity class, or the entity class itself. */ synchronized <T extends EntityBase> Class<? extends EntityBase> mapEntityType(Class<T> entityClass) { for (Class<? extends EntityBase> knownEntityClass: cache.keySet()) { if (knownEntityClass.isAssignableFrom(entityClass)) { return knownEntityClass; } } return entityClass; } /** * Returns the number of entities for the given entity class. * * @param entityClass the class of the entity. */ synchronized <T extends EntityBase> int size(Class<T> entityClass) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); return entityMap != null ? entityMap.size() : 0; } /** * Adds the given entity to the cache. * * @param entity the entity to add. * * @throws IllegalStateException if the entity class has not * yet been {@link #registerEntityClass(Class) registered}. */ synchronized void putEntity(EntityBase entity) { if (entity == null) { return; } Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entity.getClass())); if (entityMap == null) { throw new IllegalStateException(MessageFormat.format( "Entity type \"{0}\" has not been registered", entity.getClass().getName())); } entityMap.put(entity.getUuid(), entity); } /** * Removes the given entity from the cache. * If the entity does not exist, this method does nothing. * * @param entity the entity to remove. */ synchronized void removeEntity(EntityBase entity) { if (entity == null) { return; } Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entity.getClass())); if (entityMap != null) { entityMap.remove(entity.getUuid()); } } /** * Returns the entity with the given unique identifier. * * @param <T> a type derived from <code>EntityBase</code>. * @param entityClass the class of the entity. * @param uuid the unique identifier of the entity. * * @return the entity with the given unique identifier, or * <code>null</code> if no matching entity exists. * * @throws ClassCastException if the resulting cache entry cannot be * {@link Class#cast(Object) cast} to the requested <code>entityClass</code>. */ synchronized <T extends EntityBase> T getEntity(Class<T> entityClass, UUID uuid) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); return entityMap != null ? entityClass.cast(entityMap.get(uuid)) : null; } /** * Returns the first entity matching the given filter. * * @param <T> a type derived from <code>EntityBase</code>. * @param entityClass the class of the entity. * @param filter an entity filter that should match exactly one entity. * * @return the first entity that matched the filter, or <code>null</code>. * * @throws ClassCastException if a cache entry cannot be * {@link Class#cast(Object) cast} to the requested <code>entityClass</code>. */ synchronized <T extends EntityBase> T getEntity(Class<T> entityClass, EntityFilter<T> filter) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap != null && entityMap.size() > 0) { for (EntityBase value : entityMap.values()) { T entity = entityClass.cast(value); if (filter.accept(entityClass, entity)) { return entity; } } } return null; } /** * Returns all entities of the given type. * * @param <T> a type derived from <code>EntityBase</code>. * @param entityClass the class of the entity. * @return all entities of the given type, or an empty list. * * @throws ClassCastException if a cache entry cannot be * {@link Class#cast(Object) cast} to the requested <code>entityClass</code>. */ synchronized <T extends EntityBase> List<T> getEntities(Class<T> entityClass) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap == null || entityMap.isEmpty()) { return Collections.emptyList(); } ArrayList<T> result = new ArrayList<T>(); for (EntityBase value : entityMap.values()) { result.add(entityClass.cast(value)); } return result; } /** * Returns all entities matching the given filter. * * @param <T> a type derived from <code>EntityBase</code>. * @param entityClass the class of the entity. * @param filter an entity filter, or <code>null</code>. If no filter * is specified, all entities are returned. * * @return all entities of the given type matching the filter, * or an empty list. * * @throws ClassCastException if a cache entry cannot be * {@link Class#cast(Object) cast} to the requested <code>entityClass</code>. */ synchronized <T extends EntityBase> List<T> getEntities(Class<T> entityClass, EntityFilter<T> filter) { ArrayList<T> result = new ArrayList<T>(); Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap != null && entityMap.size() > 0) { for (EntityBase value : entityMap.values()) { T entity = entityClass.cast(value); if (filter == null || filter.accept(entityClass, entity)) { result.add(entity); } } } return result; } /** * Returns the entities with the given unique identifiers. * * @param <T> a type derived from <code>EntityBase</code>. * @param entityClass the class of the entity. * @param uuids a collection of unique identifiers. * * @return all entities referenced by the collection of unique identifiers. * * @throws ClassCastException if a cache entry cannot be * {@link Class#cast(Object) cast} to the requested <code>entityClass</code>. */ synchronized <T extends EntityBase> List<T> getEntities(Class<T> entityClass, Collection<UUID> uuids) { ArrayList<T> result = new ArrayList<T>(); if (uuids != null && uuids.size() > 0) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap != null && entityMap.size() > 0) { for (UUID uuid : uuids) { T targetEntity = entityClass.cast(entityMap.get(uuid)); if (targetEntity != null) { result.add(targetEntity); } } } } return result; } /** * Returns the set of unique identifiers of the entities of a given entity class. * * @param entityClass the class of the entity. * * @return a set of unique identifiers, or an empty set. */ synchronized <T extends EntityBase> Set<UUID> keySet(Class<T> entityClass) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap == null || entityMap.isEmpty()) { return Collections.emptySet(); } return entityMap.keySet(); } /** * Returns the entity classes managed by this cache. * * @return a set of entity classes, or an empty set. */ synchronized Set<Class<? extends EntityBase>> getEntityTypes() { return cache.keySet(); } /** * Clears the entity cache. */ synchronized void clearAll() { for (Map<UUID, EntityBase> entityMap: cache.values()) { entityMap.clear(); } } /** * Clears the entity cache for the given class of entities. * @param entityClass the class of the entities. */ synchronized <T extends EntityBase> void clearAll(Class<T> entityClass) { Map<UUID, EntityBase> entityMap = cache.get(mapEntityType(entityClass)); if (entityMap != null) { entityMap.clear(); } } }