EnhancedRegexParameter.java
package com.github.dakusui.jcunitx.metamodel.parameters;
import com.github.dakusui.jcunitx.core.AArray;
import com.github.dakusui.jcunitx.factorspace.Constraint;
import com.github.dakusui.jcunitx.factorspace.Factor;
import com.github.dakusui.jcunitx.factorspace.FactorSpace;
import com.github.dakusui.jcunitx.metamodel.Parameter;
import com.github.dakusui.jcunitx.metamodel.parameters.regex.RegexComposer;
import com.github.dakusui.jcunitx.metamodel.parameters.regex.RegexDecomposer;
import com.github.dakusui.jcunitx.regex.Expr;
import com.github.dakusui.jcunitx.regex.Parser;
import com.github.dakusui.jcunitx.runners.helpers.ParameterUtils;
import com.github.dakusui.jcunitx.utils.Utils;
import com.github.dakusui.pcond.functions.Printables;
import java.util.*;
import java.util.function.Function;
import static com.github.dakusui.jcunitx.utils.AssertionUtils.isKeyOf;
import static com.github.dakusui.pcond.Assertions.that;
import static com.github.dakusui.pcond.Preconditions.require;
import static com.github.dakusui.pcond.Preconditions.requireArgument;
import static com.github.dakusui.pcond.functions.Predicates.*;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
/**
* `ParameterizedRegex` is a meta-model designed to model a sequence of method calls.
*/
public interface EnhancedRegexParameter extends Parameter<List<EnhancedRegexParameter.MethodCallDescriptor>> {
@SafeVarargs
static <T> Parameter<Function<ExecutionContext, T>> parameter(String parameterName, Function<ExecutionContext, T>... args) {
return ParameterUtils.simple(args).create(parameterName);
}
static <T extends Enum<T>> Function<ExecutionContext, T>[] immediateValuesFromEnum(Class<T> enumClass) {
return immediateValues(enumClass.getEnumConstants());
}
@SuppressWarnings("unchecked")
@SafeVarargs
static <T> Function<ExecutionContext, T>[] immediateValues(T... values) {
return Arrays.stream(values)
.map(EnhancedRegexParameter::immediateValue)
.toArray(Function[]::new);
}
static <T> Function<ExecutionContext, T> immediateValue(T value) {
return Printables.function(() -> String.format("immediateValue:'%s'", value), c -> value);
}
static <T> Function<ExecutionContext, T> valueFrom(String methodName) {
return valueFrom(methodName, -1);
}
static <T> Function<ExecutionContext, T> valueFrom(String methodName, int index) {
return Printables.function(
() -> String.format("valueFrom:%s[%s]", methodName, index),
c -> c.<T>resultOf(methodName, index).orElseThrow(RuntimeException::new));
}
static <T> Function<ExecutionContext, T> argumentValueFrom(String methodName, int index, int argumentIndex) {
return c -> null;
}
static MethodDescriptor.Builder method(String methodName) {
return new MethodDescriptor.Builder(methodName);
}
/**
* An interface to define how calls of a method should be.
* That is, it specifies a name of a method and "parameters" of it.
* Note that a parameter defines all the possible values for a variable passed to a method.
*/
interface MethodDescriptor {
String name();
List<Parameter<?>> parameters();
List<Constraint> constraints();
class Builder {
private final List<Parameter<?>> parameters = new LinkedList<>();
private final List<Constraint> constraints = new LinkedList<>();
private final String methodName;
public Builder(String methodName) {
this.methodName = requireNonNull(methodName);
}
@SafeVarargs
public final <T> Builder parameter(String parameterName, Function<ExecutionContext, T>... values) {
return this.parameter(EnhancedRegexParameter.parameter(parameterName, values));
}
public <T> Builder parameter(Parameter<? extends Function<? extends ExecutionContext, T>> parameter) {
parameters.add(requireNonNull(parameter));
return this;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SafeVarargs
public final Builder parameters(Parameter<Function<ExecutionContext, ?>>... parameters) {
Builder ret = this;
for (Parameter<Function<ExecutionContext, ?>> each : parameters) {
ret = ret.parameter(((Parameter) each));
}
return ret;
}
public Builder constraint(Constraint constraint) {
this.constraints.add(requireNonNull(constraint));
return this;
}
public Builder constraints(Constraint... constraints) {
Builder ret = this;
for (Constraint each : constraints) {
ret = ret.constraint(each);
}
return ret;
}
public MethodDescriptor build() {
final String name = this.methodName;
final List<Parameter<?>> parameters = unmodifiableList(this.parameters);
return new MethodDescriptor() {
@Override
public String name() {
return name;
}
@Override
public List<Parameter<?>> parameters() {
return parameters;
}
@Override
public List<Constraint> constraints() {
return constraints;
}
};
}
public MethodDescriptor $() {
return build();
}
}
}
/**
* A class to model a single method call.
*/
interface MethodCallDescriptor {
String methodName();
List<Object> arguments();
}
class Impl extends Base<List<MethodCallDescriptor>> implements EnhancedRegexParameter {
private final FactorSpace factorSpace;
private final RegexComposer regexComposer;
public Impl(String name, EnhancedRegexParameter.Descriptor descriptor) {
super(name, descriptor);
Expr expr = new Parser().parse(descriptor.regex());
RegexDecomposer decomposer = new RegexDecomposer(name, expr);
this.regexComposer = new RegexComposer(name, expr);
FactorSpace decomposed = decomposer.decompose();
this.factorSpace = decomposed.extend(
descriptor.methodNames()
.stream()
.map(descriptor::parametersFor)
.flatMap(
each -> each.stream()
.map(Parameter::toFactorSpace)
.map(p -> p.getFactors().get(0)))
.collect(toList()),
descriptor.methodNames()
.stream().map(descriptor::constraintsFor)
.flatMap(Collection::stream)
.collect(toList()));
}
public Optional<AArray> decomposeValue(List<MethodCallDescriptor> value) {
return RegexParameter._decomposeValue(
value,
this.factorSpace.streamAllPossibleRows(),
this.regexComposer::compose,
Utils.conjunct(this.factorSpace.getConstraints())
);
}
@Override
public List<MethodCallDescriptor> composeValue(AArray tuple) {
return new ArrayList<>(composeMethodCallDescriptorSequenceFrom(tuple));
}
@Override
protected List<Factor> decompose() {
return factorSpace.getFactors();
}
@Override
protected List<Constraint> generateConstraints() {
return factorSpace.getConstraints();
}
/**
* NAME:
* REGEX:regexExample:cat-8
* LEVELS:
* (VOID)
* [[openForWrite], Reference:<REGEX:regexExample:rep-4>, [], [close], [openForWrite], Reference:<REGEX:regexExample:rep-4>, [], [close]]
* NAME:
* REGEX:regexExample:rep-9
* LEVELS:
* [(VOID), [Reference:<REGEX:regexExample:cat-7>], [Reference:<REGEX:regexExample:cat-8>]]
* NAME:
* REGEX:regexExample:rep-14
* LEVELS
* (VOID),
* [Reference:<REGEX:regexExample:empty-0>],
* [readLine],
* [Reference:<REGEX:regexExample:cat-13>]
*/
private List<MethodCallDescriptor> composeMethodCallDescriptorSequenceFrom(AArray aarray) {
// TODO regexComposer.compose(tuple);
return emptyList();
}
private static List<Factor> expandParameterFor(Factor factor, String methodName, Parameter<?> parameter) {
assert that(parameter, isInstanceOf(SimpleParameter.class));
return null;
}
private static Parameter<?> renameParameterFor(Factor factor, String methodName, Parameter<?> parameter) {
return parameter.descriptor().create(factor.getName() + ":" + methodName + "." + parameter.getName());
}
}
/**
* A factory class for `ParameterizedRegex` parameter.
*/
class Descriptor extends Parameter.Descriptor.Base<List<MethodCallDescriptor>> {
private final String regex;
private final Map<String, MethodDescriptor> methodDescriptors = new HashMap<>();
private int asteriskMax;
private int plusMax;
private Descriptor(String regex) {
this.regex = requireNonNull(regex);
this.asterisk(2).plus(2);
}
public static Descriptor of(String regex) {
return new Descriptor(regex);
}
private static EnhancedRegexParameter create(String name, Descriptor descriptor) {
return new Impl(name, descriptor);
}
@Override
public EnhancedRegexParameter create(String name) {
return create(name, this);
}
/**
* A method to specify parameters for a method call specified by `element`.
* `element` can be in the following format.
*
* - `elementName`: All the occurrences of the element whose name is `elementName`.
* - `elementName[0]`, `elementName[1]`, ..., `elementName[i]` ,... `elementName[n-1]`: The *i*th occurrence of the element whose name is `elementName`.
*
* @param methodName A string to specify element. By default, used as a name of method.
* @param parameters Parameters passed to a method specified by `element`.
* @return This object
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Descriptor describe(String methodName, List<Parameter<? extends Function<? extends ExecutionContext, ?>>> parameters) {
MethodDescriptor.Builder b = new MethodDescriptor.Builder(methodName);
for (Parameter<? extends Function<? extends ExecutionContext, ?>> each : parameters) {
b.parameter((Parameter) each);
}
return this.describe(b.$());
}
public Descriptor describe(MethodDescriptor methodDescriptor) {
this.methodDescriptors.put(methodDescriptor.name(), requireNonNull(methodDescriptor));
return this;
}
public Descriptor plus(int max) {
this.plusMax = require(max, greaterThanOrEqualTo(1));
return this;
}
public Descriptor asterisk(int max) {
this.asteriskMax = require(max, greaterThanOrEqualTo(0));
return this;
}
public String regex() {
return this.regex;
}
public int plusMax() {
return this.plusMax;
}
public int asteriskMax() {
return this.asteriskMax;
}
public List<String> methodNames() {
return this.methodDescriptors.keySet().stream().sorted().collect(toList());
}
public List<Parameter<?>> parametersFor(String methodName) {
requireArgument(methodName, allOf(isNotNull(), isKeyOf(this.methodDescriptors)));
return this.methodDescriptors.get(methodName).parameters();
}
public List<Constraint> constraintsFor(String methodName) {
requireArgument(methodName, allOf(isNotNull(), isKeyOf(this.methodDescriptors)));
return this.methodDescriptors.get(methodName).constraints();
}
}
}