/*
* Copyright 2015-2017 the original author or authors.
*
* 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
*/
package org.junit.jupiter.engine.execution;
import static org.junit.platform.commons.meta.API.Usage.Internal;
import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType;
import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContextException;
import org.junit.platform.commons.meta.API;
import org.junit.platform.commons.util.Preconditions;
/**
* {@code ExtensionValuesStore} is used inside implementations of
* {@link ExtensionContext} to store and retrieve attributes.
*
* @since 5.0
*/
@API(Internal)
public class ExtensionValuesStore {
private final ExtensionValuesStore parentStore;
private final Map<Object, StoredValue> storedValues = new HashMap<>(4);
private final Object monitor = new Object();
ExtensionValuesStore() {
this(null);
}
public ExtensionValuesStore(ExtensionValuesStore parentStore) {
this.parentStore = parentStore;
}
Object get(Namespace namespace, Object key) {
synchronized (this.monitor) {
StoredValue storedValue = getStoredValue(namespace, key);
if (storedValue != null) {
return storedValue.value;
}
else if (this.parentStore != null) {
return this.parentStore.get(namespace, key);
}
else {
return null;
}
}
}
<T> T get(Namespace namespace, Object key, Class<T> requiredType) {
Object value = get(namespace, key);
return castToRequiredType(key, value, requiredType);
}
<K, V> Object getOrComputeIfAbsent(Namespace namespace, K key, Function<K, V> defaultCreator) {
synchronized (this.monitor) {
StoredValue storedValue = getStoredValue(namespace, key);
if (storedValue == null) {
if (this.parentStore != null) {
storedValue = this.parentStore.getStoredValue(namespace, key);
}
if (storedValue == null) {
storedValue = new StoredValue(defaultCreator.apply(key));
putStoredValue(namespace, key, storedValue);
}
}
return storedValue.value;
}
}
<K, V> V getOrComputeIfAbsent(Namespace namespace, K key, Function<K, V> defaultCreator, Class<V> requiredType) {
Object value = getOrComputeIfAbsent(namespace, key, defaultCreator);
return castToRequiredType(key, value, requiredType);
}
void put(Namespace namespace, Object key, Object value) {
Preconditions.notNull(namespace, "Namespace must not be null");
Preconditions.notNull(key, "key must not be null");
synchronized (this.monitor) {
putStoredValue(namespace, key, new StoredValue(value));
}
}
Object remove(Namespace namespace, Object key) {
synchronized (this.monitor) {
StoredValue previous = this.storedValues.remove(new CompositeKey(namespace, key));
return (previous != null ? previous.value : null);
}
}
<T> T remove(Namespace namespace, Object key, Class<T> requiredType) {
Object value = remove(namespace, key);
return castToRequiredType(key, value, requiredType);
}
private StoredValue getStoredValue(Namespace namespace, Object key) {
CompositeKey compositeKey = new CompositeKey(namespace, key);
return this.storedValues.get(compositeKey);
}
private void putStoredValue(Namespace namespace, Object key, StoredValue storedValue) {
CompositeKey compositeKey = new CompositeKey(namespace, key);
this.storedValues.put(compositeKey, storedValue);
}
@SuppressWarnings("unchecked")
private <T> T castToRequiredType(Object key, Object value, Class<T> requiredType) {
if (value == null) {
return null;
}
if (isAssignableTo(value, requiredType)) {
if (requiredType.isPrimitive()) {
return (T) getWrapperType(requiredType).cast(value);
}
return requiredType.cast(value);
}
// else
throw new ExtensionContextException(
String.format("Object stored under key [%s] is not of required type [%s]", key, requiredType.getName()));
}
private static class CompositeKey {
private final Namespace namespace;
private final Object key;
private CompositeKey(Namespace namespace, Object key) {
this.namespace = namespace;
this.key = key;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
CompositeKey that = (CompositeKey) o;
return this.namespace.equals(that.namespace) && this.key.equals(that.key);
}
@Override
public int hashCode() {
return Objects.hash(this.namespace, this.key);
}
}
private static class StoredValue {
private final Object value;
private StoredValue(Object value) {
this.value = value;
}
}
}