blob: bf56680955407ae6401f5729f9d9c5ad2b1396dd [file] [log] [blame]
/*
* 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 org.apache.bval.jsr303.resolver;
import javax.validation.Path;
import javax.validation.TraversableResolver;
import java.lang.annotation.ElementType;
import java.util.HashMap;
import java.util.Map;
/**
* Cache results of a delegated traversable resovler to optimize calls
* It works only for a single validate* call and should not be used if
* the TraversableResolver is accessed concurrently
* <p/>
* Date: 25.11.2009 <br/>
* Time: 13:56:18 <br/>
*
* @author Roman Stumm (based on the code of Emmanuel Bernard)
*/
public class CachingTraversableResolver implements TraversableResolver, CachingRelevant {
private TraversableResolver delegate;
private Map<CacheEntry, CacheEntry> cache = new HashMap<CacheEntry, CacheEntry>();
/**
* Convenience method to check whether caching is necessary on a given {@link TraversableResolver}.
* @param resolver to check
* @return true when a CachingTraversableResolver is to be used during validation
*/
public static boolean needsCaching(TraversableResolver resolver) {
// caching, if we do not know exactly
return !(resolver instanceof CachingRelevant) ||
((CachingRelevant) resolver).needsCaching();
}
/**
* Create a new CachingTraversableResolver instance.
* @param delegate
*/
public CachingTraversableResolver(TraversableResolver delegate) {
this.delegate = delegate;
}
/**
* If necessary, return a caching wrapper for the specified {@link TraversableResolver}.
* @param traversableResolver
* @return {@link TraversableResolver}
* @see #needsCaching(TraversableResolver)
*/
public static TraversableResolver cacheFor(TraversableResolver traversableResolver) {
if (needsCaching(traversableResolver)) {
return new CachingTraversableResolver(traversableResolver);
} else {
return traversableResolver;
}
}
/**
* {@inheritDoc}
*/
public boolean isReachable(Object traversableObject, Path.Node traversableProperty,
Class<?> rootBeanType, Path pathToTraversableObject,
ElementType elementType) {
CacheEntry currentLH = new CacheEntry(traversableObject, traversableProperty,
rootBeanType, pathToTraversableObject, elementType);
CacheEntry cachedLH = cache.get(currentLH);
if (cachedLH == null) {
currentLH.reachable = delegate.isReachable(traversableObject, traversableProperty,
rootBeanType, pathToTraversableObject, elementType);
cache.put(currentLH, currentLH);
cachedLH = currentLH;
} else if (cachedLH.reachable == null) {
cachedLH.reachable = delegate.isReachable(traversableObject, traversableProperty,
rootBeanType, pathToTraversableObject, elementType);
}
return cachedLH.reachable;
}
/**
* {@inheritDoc}
*/
public boolean isCascadable(Object traversableObject, Path.Node traversableProperty,
Class<?> rootBeanType, Path pathToTraversableObject,
ElementType elementType) {
CacheEntry currentLH = new CacheEntry(traversableObject, traversableProperty,
rootBeanType, pathToTraversableObject, elementType);
CacheEntry cachedLH = cache.get(currentLH);
if (cachedLH == null) {
currentLH.cascadable = delegate.isCascadable(traversableObject,
traversableProperty, rootBeanType, pathToTraversableObject, elementType);
cache.put(currentLH, currentLH);
cachedLH = currentLH;
} else if (cachedLH.cascadable == null) {
cachedLH.cascadable = delegate.isCascadable(traversableObject, traversableProperty,
rootBeanType, pathToTraversableObject, elementType);
}
return cachedLH.cascadable;
}
/**
* {@inheritDoc}
*/
public boolean needsCaching() {
return false; // I am the cache. Do not need cache for cache
}
/**
* Entry in the cache.
*/
private static class CacheEntry {
private final Object object;
private final Path.Node node;
private final Class<?> type;
private final Path path;
private final ElementType elementType;
private final int hashCode;
private Boolean reachable;
private Boolean cascadable;
/**
* Create a new CacheEntry instance.
* @param traversableObject
* @param traversableProperty
* @param rootBeanType
* @param pathToTraversableObject
* @param elementType
*/
private CacheEntry(Object traversableObject, Path.Node traversableProperty,
Class<?> rootBeanType, Path pathToTraversableObject,
ElementType elementType) {
this.object = traversableObject;
this.node = traversableProperty;
this.type = rootBeanType;
this.path = pathToTraversableObject;
this.elementType = elementType;
this.hashCode = buildHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheEntry that = (CacheEntry) o;
return elementType == that.elementType && path.equals(that.path) &&
type.equals(that.type) &&
!(object != null ? !object.equals(that.object) : that.object != null) &&
node.equals(that.node);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return hashCode;
}
private int buildHashCode() {
int result = object != null ? object.hashCode() : 0;
result = 31 * result + node.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + path.hashCode();
result = 31 * result + elementType.hashCode();
return result;
}
}
}