package org.togglz.core.activation;
import java.util.ArrayList;
import java.util.List;
import org.togglz.core.repository.FeatureState;
import org.togglz.core.spi.ActivationStrategy;
import org.togglz.core.user.FeatureUser;
import org.togglz.core.util.Strings;
/**
* <p>
* An abstract activation strategy that is designed to support cases where a specific parameter contains comma-separated
* tokens that can be negated by prefixing the value with the NOT operator ({@code !}).
* </p>
* <p>
* {@code AbstractTokenizedActivationStrategy} makes no real assumptions on how implementations will use the tokens to
* determine whether the feature is active and, instead, simply tokenizes the parameter value. It even allows for the
* token values to be transformed during this process by specifying a {@link TokenTransformer}.
* </p>
* <p>
* Implementations are responsible for honoring the negated state of any tokens. Fortunately, this is very simple to do:
* </p>
* <pre>
* @Override
* protected boolean isActive(FeatureState featureState, FeatureUser user, List<Token> tokens) {
* for (Token token : tokens) {
* boolean active = doSomeCheckOnTokenValue(token.getValue());
* if (active != token.isNegated()) {
* return true;
* }
* }
*
* return false;
* }
* </pre>
*
* @author Alasdair Mercer
* @see #getTokenParameterName()
* @see #getTokenParameterTransformer()
*/
public abstract class AbstractTokenizedActivationStrategy implements ActivationStrategy {
@Override
public final boolean isActive(FeatureState featureState, FeatureUser user) {
List<Token> tokens = tokenize(featureState, getTokenParameterName(), getTokenParameterTransformer());
return isActive(featureState, user, tokens);
}
/**
* <p>
* This method is called by {@link #isActive(FeatureState, FeatureUser)} with the parsed {@code tokens} to make the
* decision as to whether the feature is active.
* </p>
*
* @param featureState
* the {@link FeatureState} which represents the current configuration of the feature
* @param user
* the {@link FeatureUser user} for which to decide whether the feature is active (may be {@literal null})
* @param tokens
* the {@code List} of {@link Token Tokens} parsed from the parameter value
* @return {@literal true} if the feature should be active; otherwise {@literal false}.
*/
protected abstract boolean isActive(FeatureState featureState, FeatureUser user, List<Token> tokens);
/**
* <p>
* Looks up and tokenizes the value of the parameter with the given name on the feature.
* </p>
* <p>
* If {@code transformer} is not {@literal null}, it will be asked to transform each individual token value.
* </p>
*
* @param featureState
* the {@link FeatureState} which represents the current configuration of the feature
* @param parameterName
* the name of the parameter whose value is to be tokenized
* @param transformer
* the {@link TokenTransformer} to be used to transform the value of each token (may be {@literal null} to use
* the token values as-provided)
* @return A {@code List} of {@link Token Tokens} extracted from the value of the parameter with the specified name.
*/
protected List<Token> tokenize(FeatureState featureState, String parameterName, TokenTransformer transformer) {
List<String> values = Strings.splitAndTrim(featureState.getParameter(parameterName), "[\\s,]+");
List<Token> tokens = new ArrayList<>(values.size());
for (String value : values) {
if (transformer != null) {
value = transformer.transform(value);
}
boolean negated = value.startsWith("!");
if (negated) {
value = value.substring(1);
}
tokens.add(new Token(value, negated));
}
return tokens;
}
/**
* <p>
* Returns the name of the parameter whose value is to be tokenized.
* </p>
*
* @return The name of the parameter containing tokens.
*/
public abstract String getTokenParameterName();
/**
* <p>
* Returns the transformer to be used to transform the value of each {@link Token}.
* </p>
* <p>
* By default, this method returns {@literal null}, meaning that the token values are used as-provided.
* </p>
*
* @return The {@link TokenTransformer} to be used to transform token values or {@literal null} to use the original
* values.
*/
public TokenTransformer getTokenParameterTransformer() {
return null;
}
/**
* <p>
* Contains information for a specific token including the token value and whether it has been negated.
* </p>
*/
public static final class Token {
private final boolean negated;
private final String value;
private Token(String value, boolean negated) {
this.value = value;
this.negated = negated;
}
/**
* <p>
* Returns whether or not this {@link Token} is negated.
* </p>
*
* @return {@literal true} if this token is negated; otherwise {@literal false}.
*/
public boolean isNegated() {
return negated;
}
/**
* <p>
* Returns the value for this {@link Token}.
* </p>
*
* @return The value.
*/
public String getValue() {
return value;
}
}
/**
* <p>
* Used to transform a given {@link Token} value.
* </p>
* <p>
* For example; if the tokens were to be used to perform a case-insensitive lookup, you might use a
* {@code TokenTransformer} to transform the values to lower case up-front to reduce the cost of these lookups.
* </p>
* <pre>
* @Override
* protected TokenTransformer getTokenParameterTransformer() {
* return new TokenTransformer() {
* @Override
* public String transform(String value) {
* return value.toLowerCase();
* }
* };
* }
* </pre>
*/
public interface TokenTransformer {
/**
* <p>
* Transforms the token {@code value} provided.
* </p>
*
* @param value
* the {@link Token} value to be transformed
* @return The transformed {@code value}.
*/
String transform(String value);
}
}