package org.springframework.roo.classpath;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.metadata.MetadataNotificationListener;
import org.springframework.roo.model.JavaType;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Utility class which handles cache of a MetadataLocator
*
* @author Jose Manuel Vivó
* @since 2.0.0
*/
public class MetadataLocatorUtils<CONTEXT> implements MetadataNotificationListener {
/**
* This interface is used by {@link MetadataLocatorUtils} to evaluate
* which values match
*
* @author Jose Manuel Vivó
*
* @param <CONTEXT> context class used if more than one types
*/
public interface LocatorEvaluator<CONTEXT> {
/**
* Should return true if value is suitable for key
* for required context
*
* @param key
* @param valueToEvalueate
* @param context
* @return
*/
boolean evaluateForKey(JavaType key, ClassOrInterfaceTypeDetails valueToEvalueate,
CONTEXT context);
/**
* Get key to evict based on metadata dependency id
*
* @param streamDependency
* @return JavaType to evict or null if doesn't need to handle
*/
JavaType evalueteForEvict(String streamDependency);
/**
* Returns all details which can match to context
*
* @param context
* @return
*/
Set<ClassOrInterfaceTypeDetails> getAllPosibilities(CONTEXT context);
}
private final LocatorEvaluator<CONTEXT> evaluator;
private final Map<JavaType, Map<CONTEXT, Set<ClassOrInterfaceTypeDetails>>> cacheMap =
new HashMap<JavaType, Map<CONTEXT, Set<ClassOrInterfaceTypeDetails>>>();
/**
* stores current cache values with cacheMap keys.
* Useful to evict cache
*/
private final Map<JavaType, JavaType> cacheMapInverse = new HashMap<JavaType, JavaType>();
/**
* Default constructor
*
* @param locator
*/
public MetadataLocatorUtils(LocatorEvaluator<CONTEXT> locator) {
this.evaluator = locator;
}
@Override
public void notify(String upstreamDependency, String downstreamDependency) {
checkEvictCache(evaluator.evalueteForEvict(downstreamDependency));
}
/**
* Evict cache from cacheMap if type is found on cacheMapInverse
*
* @param type
*/
public void checkEvictCache(final JavaType type) {
if (type == null) {
return;
}
final JavaType mainType = cacheMapInverse.remove(type);
if (mainType != null) {
cacheMap.remove(mainType);
}
}
/**
* Get value for type
*
* Use cache value if any.
*
* @param type
* @param context
* @return
*/
public Collection<ClassOrInterfaceTypeDetails> getValue(final JavaType type, final CONTEXT context) {
Map<CONTEXT, Set<ClassOrInterfaceTypeDetails>> currentMap;
if (type == null) {
return evaluator.getAllPosibilities(context);
}
if (!cacheMap.containsKey(type)) {
currentMap = new HashMap<CONTEXT, Set<ClassOrInterfaceTypeDetails>>();
cacheMap.put(type, currentMap);
} else {
currentMap = cacheMap.get(type);
}
if (!currentMap.containsKey(context)) {
currentMap.put(context, new HashSet<ClassOrInterfaceTypeDetails>());
}
final Set<ClassOrInterfaceTypeDetails> existing = currentMap.get(context);
final Set<ClassOrInterfaceTypeDetails> located = evaluator.getAllPosibilities(context);
if (existing.containsAll(located)) {
return existing;
}
final Map<String, ClassOrInterfaceTypeDetails> toReturn =
new HashMap<String, ClassOrInterfaceTypeDetails>();
for (final ClassOrInterfaceTypeDetails cid : located) {
if (evaluator.evaluateForKey(type, cid, context)) {
toReturn.put(cid.getDeclaredByMetadataId(), cid);
cacheMapInverse.put(cid.getName(), type);
}
}
existing.clear();
existing.addAll(toReturn.values());
return toReturn.values();
}
/**
* = _LocatorEvaluatorByAnnotation_
*
* Abstract class which implements location by annotation
*
* @author Jose Manuel Vivó
* @since 2.0.0
*/
public static abstract class LocatorEvaluatorByAnnotation implements LocatorEvaluator<JavaType> {
private final TypeLocationService typeLocationService;
public LocatorEvaluatorByAnnotation(TypeLocationService typeLocationService) {
this.typeLocationService = typeLocationService;
}
@Override
public Set<ClassOrInterfaceTypeDetails> getAllPosibilities(JavaType context) {
return typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(context);
}
}
}