package org.jbehave.eclipse.editor.step;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jbehave.eclipse.util.FJ;
import fj.F;
public class ParametrizedStep {
private static Pattern compileParameterPattern(String parameterPrefix) {
return Pattern.compile("(\\" + parameterPrefix + "\\p{L}*)(\\W|\\Z)", Pattern.DOTALL);
}
private List<Token> tokens = new ArrayList<Token>();
private final String content;
private final String parameterPrefix;
public ParametrizedStep(String content) {
this(content, "$");
}
public ParametrizedStep(String content, String parameterPrefix) {
if(content==null)
throw new IllegalArgumentException("Content cannot be null");
this.content = content;
this.parameterPrefix = parameterPrefix;
parse(compileParameterPattern(parameterPrefix));
}
@Override
public int hashCode() {
return content.hashCode();
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof ParametrizedStep))
return false;
return isSameAs((ParametrizedStep)obj);
}
public boolean isSameAs(ParametrizedStep other) {
return other.content.equals(content);
}
public String getContent() {
return content;
}
private void parse(Pattern parameterPattern) {
Matcher matcher = parameterPattern.matcher(content);
int prev = 0;
while (matcher.find()) {
int start = matcher.start();
int end = matcher.end();
if (start > 0) {
add(new Token(prev, start - prev, false));
}
end -= matcher.group(2).length();
start += parameterPrefix.length(); // remove prefix from the identifier
add(new Token(start, end - start, true));
prev = end;
}
if (prev < content.length()) {
add(new Token(prev, content.length() - prev, false));
}
}
private void add(Token token) {
tokens.add(token);
}
public class Token {
public final int offset;
public final int length;
public final boolean isIdentifier;
public Token(int offset, int length, boolean isIdentifier) {
this.offset = offset;
this.length = length;
this.isIdentifier = isIdentifier;
}
public String value () {
return content.substring(offset, offset + length);
}
@Override
public String toString() {
return "<<"+(isIdentifier?parameterPrefix:"")+value()+">>";
}
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
return content.regionMatches(offset + toffset, other, ooffset, len);
}
}
public static boolean isIdentifierChar(char c) {
return ('a' <= c && c <= 'z')
||('A' <= c && c <= 'Z')
||('0' <= c && c <= '9')
||(c=='-' || c=='_');
}
public Token getToken(int index) {
return tokens.get(index);
}
public List<Token> getTokens() {
return new ArrayList<Token>(tokens);
}
public List<String> getParameters() {
List<String> parameters = new ArrayList<String>();
for(Token token : tokens) {
if(token.isIdentifier)
parameters.add(token.value());
}
return parameters;
}
public int getTokenCount() {
return tokens.size();
}
private int parameterCount = -1;
public int getParameterCount() {
if(parameterCount == -1) {
parameterCount = parameterCount();
}
return parameterCount;
}
private int parameterCount() {
return FJ.count(tokens, isIdentifier());
}
public static fj.F<Token,Boolean> isIdentifier() {
return new F<ParametrizedStep.Token, Boolean>() {
@Override
public Boolean f(Token token) {
return token.isIdentifier;
}
};
}
public float weightOf(String input) {
return ((float)acceptsBeginning(input))/((float)getTokenCount());
}
public int acceptsBeginning(String input) {
WeightChain chain = calculateWeightChain(input);
return chain.getWeight();
}
public WeightChain calculateWeightChain(String input) {
WeightChain chain = acceptsBeginning(0, input, 0);
chain.input = input;
chain.collectWeights();
return chain;
}
private WeightChain acceptsBeginning(int inputIndex, String input, int tokenIndexStart) {
WeightChain pair = new WeightChain();
pair.inputIndex = inputIndex;
WeightChain current = pair;
List<Token> tokens = this.tokens;
for(int tokenIndex=tokenIndexStart,n=tokens.size(); tokenIndex<n; tokenIndex++) {
boolean isLastToken = (tokenIndex==n-1);
Token token = tokens.get(tokenIndex);
if(!token.isIdentifier) {
int remaining = input.length()-inputIndex;
if(remaining>token.length && isLastToken) {
// more data than the token itself
return WeightChain.zero();
}
int overlaping = Math.min(token.length, remaining);
if(overlaping>0) {
if(token.regionMatches(0, input, inputIndex, overlaping)) {
current.tokenIndex = tokenIndex;
current.weight++;
if(overlaping == token.length) // full token match
{
current.weight++;
if((inputIndex + overlaping)==input.length())
// no more data, break the loop now
return pair;
}
else // break looop
return pair;
inputIndex += overlaping;
WeightChain next = new WeightChain();
next.inputIndex = inputIndex;
current.next = next;
current = next;
}
else {
// no match
return WeightChain.zero();
}
}
else {
// not enough data, returns what has been collected
return pair;
}
}
else {
current.tokenIndex = tokenIndex;
current.weight++;
// not the most efficient part, but no other solution right now
WeightChain next = WeightChain.zero();
for(int j=inputIndex+1; j<input.length(); j++) {
WeightChain sub = acceptsBeginning(j, input, tokenIndex+1);
if(sub.isWeighterThan(next)) {
next = sub;
}
}
current.next = next;
return pair;
}
}
return pair;
}
public static class WeightChain {
public static final WeightChain zero() {
return new WeightChain();
}
private String input;
private int inputIndex;
private int weight;
private int tokenIndex = -1;
private WeightChain next;
public WeightChain last() {
WeightChain last = this;
WeightChain iter = this;
while(iter!=null) {
if(!iter.isZero())
last = iter;
iter = iter.next;
}
return last;
}
public boolean isZero() {
return weight==0 && tokenIndex==-1;
}
public int getInputIndex() {
return inputIndex;
}
public WeightChain getNext() {
return next;
}
public int getWeight() {
return weight;
}
public int getTokenIndex() {
return tokenIndex;
}
public boolean isWeighterThan(WeightChain pair) {
if(weight>pair.weight)
return true;
return false;
}
@Override
public String toString() {
return "WeightChain [inputIndex=" + inputIndex + ", weight=" + weight + ", tokenIndex=" + tokenIndex + "]";
}
public void collectWeights() {
int w = weight;
WeightChain n = next;
while(n!=null) {
if(!n.isZero()) {
w += n.weight;
}
n = n.next;
}
this.weight = w;
}
public List<String> tokenize() {
List<String> parts = new ArrayList<String>();
if(isZero())
return parts;
int indexBeg = inputIndex;
WeightChain n = next;
while(n!=null) {
if(!n.isZero()) {
parts.add(input.substring(indexBeg, n.inputIndex));
indexBeg = n.inputIndex;
}
n = n.next;
}
parts.add(input.substring(indexBeg));
return parts;
}
}
public boolean matches(String input) {
return acceptsBeginning(input) == (2*getTokenCount()-getParameterCount());
}
public String complete(String input) {
WeightChain chain = calculateWeightChain(input);
WeightChain last = chain.last();
if(last.isZero())
return "";
int inputIndex = last.inputIndex;
int tokenIndex = last.tokenIndex;
StringBuilder builder = new StringBuilder();
Token token = getToken(tokenIndex);
if(!token.isIdentifier) {
int consumed = input.length() - inputIndex;
builder.append(getToken(tokenIndex).value().substring(consumed));
}
tokenIndex++;
for(int i = tokenIndex; i< getTokenCount(); i++) {
token = getToken(i);
if(token.isIdentifier)
builder.append(parameterPrefix);
builder.append(token.value());
}
return builder.toString();
}
}