TokenSequence.java
/*
* (c) Copyright 2021 Hasan Selman Kara. All rights reserved.
*
* Licensed 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 li.selman.jpbe.dsl.token;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import li.selman.jpbe.dsl.DslElement;
/**
* Merges a list of tokens together to a new combined token.
* The whole TokenSequence must match.
* <p>
* If no tokens are past, the sequence <b>matches everything</b>
*
* @author Hasan Selman Kara
* @see TokenSequenceBuilder
*/
public final class TokenSequence implements DslElement, Iterable<Token> {
private final List<Token> tokens;
private final Pattern mergedPattern;
private TokenSequence(List<Token> tokens) {
this.tokens = tokens;
// 'and' merge all regex pattern together, i.e. the whole pattern must match
// 'or' merge with an '|' pipe is wrong!
final String regexPattern = tokens.stream().map(Token::getRegexPattern).collect(Collectors.joining());
this.mergedPattern = Pattern.compile(regexPattern);
}
public static TokenSequence of(Token... tokens) {
return new TokenSequence(Arrays.asList(tokens));
}
public static TokenSequence of(List<Token> tokens) {
return new TokenSequence(tokens);
}
public List<Token> getTokens() {
return tokens;
}
public int getNumberOfTokens() {
return tokens.size();
}
public boolean isEmpty() {
return tokens.isEmpty();
}
/**
* @return the last token or {@code null} if the sequence is empty
*/
public Token getLastToken() {
if (!tokens.isEmpty()) {
return tokens.get(tokens.size() - 1);
}
return null;
}
/**
* Is only called in {@link TokenSequence#union(TokenSequence)} and only if
* the size of sequence is >= 0.
* Therefore, returning null in the else case is not an issue.
*
* @return the first token in the sequence
*/
private Token getFirstElement() {
return tokens.size() >= 1 ? tokens.get(0) : null;
}
public Pattern getMergedPattern() {
return mergedPattern;
}
public TokenSequence union(TokenSequence other) {
if (sequenceLength() == 0 && other.sequenceLength() == 0) {
// empty token sequence
return TokenSequence.of();
}
List<Token> newSeq = new ArrayList<>(tokens);
if (sequenceLength() > 0
&& other.sequenceLength() > 0
&& Objects.equals(other.getFirstElement(), newSeq.get(newSeq.size() - 1))) {
newSeq.remove(newSeq.size() - 1);
}
newSeq.addAll(other.getTokens());
return new TokenSequence(newSeq);
}
/**
* @return the size of {@link TokenSequence#tokens}
*/
public int sequenceLength() {
return tokens.size();
}
/**
* Note that the size of the DslElement and the size of {@link TokenSequence#tokens} can differ!
* Do not use this method to get the number of tokens in the sequence.
*
* @return the weight of the token TokenSequence
*/
@Override
public int getDslWeight() {
// This implementation just happens to use the token list length as the DslElement size.
return tokens.size();
}
@Override
public Iterator<Token> iterator() {
return tokens.iterator();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TokenSequence tokens1 = (TokenSequence) o;
return tokens.equals(tokens1.tokens);
}
@Override
public int hashCode() {
return tokens.hashCode();
}
@Override
public String toString() {
return tokens.stream()
.map(Token::toString)
.collect(Collectors.joining(", ", "{", "}"));
}
}