/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3.0 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.dataplacement.c50.tree;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.io.BufferedReader;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a node in the decision tree
*
* @author Pedro Ruivo
* @since 5.2
*/
public class ParseTreeNode {
private static final Log log = LogFactory.getLog(ParseTreeNode.class);
private final RuleType[] ruleTypes = new RuleType[] {
new RuleType0V2(),
new RuleType0(),
new RuleType3(),
new RuleType2(),
new RuleType1()
};
private int type;
private String clazz;
private int[] frequency;
private String attribute;
private int numberOfForks;
private String cut;
private EltsValues[] elts;
private ParseTreeNode[] forks;
public ParseTreeNode() {
type = -1;
}
public int getType() {
return type;
}
public String getClazz() {
return clazz;
}
public int[] getFrequency() {
return frequency;
}
public String getAttribute() {
return attribute;
}
public int getNumberOfForks() {
return numberOfForks;
}
public String getCut() {
return cut;
}
public EltsValues[] getElts() {
return elts;
}
public ParseTreeNode[] getForks() {
return forks;
}
/**
* parses the current node from the file
*
* @param reader the reader for the *.tree file
* @throws Exception if some error occurs during the parse
*/
public void parse(BufferedReader reader) throws Exception {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || !line.startsWith("type")) {
if (log.isTraceEnabled()) {
log.tracef("Discarding line %s. it is not useful", line);
}
} else {
break;
}
}
if (line == null) {
throw new IllegalStateException("EOF not expected!");
}
if (log.isTraceEnabled()) {
log.tracef("Parsing line '%s'", line);
}
for (RuleType ruleType : ruleTypes) {
if (ruleType.matches(line)) {
ruleType.parse(reader);
return;
}
}
throw new IllegalStateException("The rule should match to one of the type");
}
/**
* pretty print the tree
*
* @param level the level of the node
* @param builder the builder to put the output
*/
public void toString(int level, StringBuilder builder) {
if (level > 0) {
for (int i = 0; i < level - 1; ++i) {
builder.append(" ");
}
builder.append(":...");
}
builder.append(toString()).append("\n");
if (forks != null) {
for (ParseTreeNode node : forks) {
node.toString(level + 1, builder);
}
}
}
@Override
public String toString() {
return "ParseTreeNode{" +
"type=" + type +
" class='" + clazz + '\'' +
(frequency == null ? "" : " freq=" + freqToString()) +
(attribute == null ? "" : " att='" + attribute + '\'') +
(numberOfForks == 0 ? "" : " fork=" + numberOfForks) +
(cut == null ? "" : " cut='" + cut + '\'') +
(elts == null ? "" : " " + eltsToString()) +
'}';
}
private String freqToString() {
if (frequency == null) {
return "";
} else if (frequency.length == 1) {
return Integer.toString(frequency[0]);
}
String s = Integer.toString(frequency[0]);
for (int i = 1; i < frequency.length; ++i) {
s += "," + Integer.toString(frequency[i]);
}
return s;
}
private String eltsToString() {
if (elts == null) {
return "";
} else if (elts.length == 1) {
return "elts='" + elts[0] + "'";
}
String s = "elts='" + elts[0] + "'";
for (int i = 1; i < elts.length; ++i) {
s += " elts='" + elts[i] + "'";
}
return s;
}
/**
* parses the frequencies
*
* @param value the frequencies
*/
private void parseFrequency(String value) {
String[] freqValues = value.split(",");
frequency = new int[freqValues.length];
for (int i = 0; i < frequency.length; ++i) {
frequency[i] = Integer.parseInt(freqValues[i]);
}
}
/**
* parses the elts list
*
* @param value the elts list
*/
private void parseElts(String value) {
int charIdx = 0;
if (log.isTraceEnabled()) {
log.tracef("Parsing elts values in '%s'", value);
}
for (int i = 0; i < elts.length; ++i) {
boolean escape = false;
boolean comma = false;
boolean endString = false;
elts[i] = new EltsValues();
if (log.isTraceEnabled()) {
log.tracef("Starting iterator %s. parsing from %s", i, value.substring(charIdx));
}
if (!value.startsWith("elts=", charIdx)) {
throw new IllegalStateException("Expected 'elts=' in string");
}
charIdx += 5;
if (value.charAt(charIdx) != '\"') {
throw new IllegalStateException("Expected '\"' in string");
}
charIdx++;
String etlsValue = "";
inner: while (true) {
if (log.isTraceEnabled()) {
log.tracef("consuming... %s", value.substring(charIdx));
}
switch (value.charAt(charIdx)) {
case '\\':
escape = true;
break;
case ',':
if (escape) {
etlsValue += "\\,";
escape = false;
} else if (endString) {
elts[i].addValue(etlsValue);
comma = true;
if (log.isTraceEnabled()) {
log.tracef("adding '%s'. State: escape=%s,comma=%s,end=%s", etlsValue, escape, comma, endString);
}
etlsValue = "";
} else {
comma = true;
}
break;
case '\"':
if (escape) {
etlsValue += "\\\"";
escape = false;
} else if (comma) {
comma = false; //ignore, starting a new value
} else {
endString = true;
}
break;
case ' ':
if (endString) {
if (log.isTraceEnabled()) {
log.tracef("adding '%s' and ending. State: escape=%s,comma=%s,end=%s", etlsValue, escape, comma, endString);
}
elts[i].addValue(etlsValue);
etlsValue = "";
charIdx++;
break inner;
}
etlsValue += " ";
break;
default:
if (escape) {
etlsValue += "\\";
}
escape = false;
comma = false;
endString = false;
etlsValue += value.charAt(charIdx);
}
charIdx++;
if (charIdx >= value.length()) {
break;
}
}
if (endString && !etlsValue.isEmpty()) {
if (log.isTraceEnabled()) {
log.tracef("adding '%s' and ending. State: escape=%s,comma=%s,end=%s", etlsValue, escape, comma, endString);
}
elts[i].addValue(etlsValue);
}
}
}
private abstract class RuleType {
private final Pattern pattern;
protected Matcher matcher;
protected RuleType(String rule) {
this.pattern = Pattern.compile(rule);
}
/**
* check if the rule matches with this pattern
*
* @param rule the rule
* @return true if the rule matches to this pattern
*/
public final boolean matches(String rule) {
matcher = pattern.matcher(rule);
if (log.isTraceEnabled()) {
log.tracef("Trying to match '%s' with pattern '%s%", rule, pattern.pattern());
}
return matcher.matches();
}
/**
* parses the rule
*
* @param reader the reader used to parse the child nodes (if any)
* @throws Exception if some error occurs
*/
abstract void parse(BufferedReader reader) throws Exception;
}
private class RuleType0 extends RuleType {
public RuleType0() {
super("type=\"0\" class=\"(.*)\"");
}
@Override
public void parse(BufferedReader reader) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("'%s' Matched with type 0: %s", matcher.group(0), matcher.pattern());
}
type = 0;
clazz = matcher.group(1);
}
}
private class RuleType0V2 extends RuleType {
public RuleType0V2() {
super("type=\"0\" class=\"(.*)\" freq=\"(.*)\"");
}
@Override
public void parse(BufferedReader reader) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("'%s' Matched with type 0: %s", matcher.group(0), matcher.pattern());
}
type = 0;
clazz = matcher.group(1);
parseFrequency(matcher.group(2));
}
}
private class RuleType1 extends RuleType {
public RuleType1() {
super("type=\"1\" class=\"(.*)\" freq=\"(.*)\" att=\"(.*)\" forks=\"(\\d+)\"");
}
@Override
public void parse(BufferedReader reader) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("'%s' Matched with type 1: %s", matcher.group(0), matcher.pattern());
}
type = 1;
clazz = matcher.group(1);
parseFrequency(matcher.group(2));
attribute = matcher.group(3);
numberOfForks = Integer.parseInt(matcher.group(4));
forks = new ParseTreeNode[numberOfForks];
for (int i = 0; i < numberOfForks; ++i) {
forks[i] = new ParseTreeNode();
forks[i].parse(reader);
}
}
}
private class RuleType2 extends RuleType {
public RuleType2() {
super("type=\"2\" class=\"(.*)\" freq=\"(.*)\" att=\"(.*)\" forks=\"(\\d+)\" cut=\"(.*)\"");
}
@Override
public void parse(BufferedReader reader) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("'%s' Matched with type 2: %s", matcher.group(0), matcher.pattern());
}
type = 2;
clazz = matcher.group(1);
parseFrequency(matcher.group(2));
attribute = matcher.group(3);
numberOfForks = Integer.parseInt(matcher.group(4));
cut = matcher.group(5);
forks = new ParseTreeNode[numberOfForks];
for (int i = 0; i < numberOfForks; ++i) {
forks[i] = new ParseTreeNode();
forks[i].parse(reader);
}
}
}
private class RuleType3 extends RuleType {
public RuleType3() {
super("type=\"3\" class=\"(.*)\" freq=\"(.*)\" att=\"(.*)\" forks=\"(\\d+)\" (elts=\"(.*)\")+");
}
@Override
public void parse(BufferedReader reader) throws Exception {
if (log.isTraceEnabled()) {
log.tracef("'%s' Matched with type 3: %s", matcher.group(0), matcher.pattern());
}
type = 3;
clazz = matcher.group(1);
parseFrequency(matcher.group(2));
attribute = matcher.group(3);
numberOfForks = Integer.parseInt(matcher.group(4));
elts = new EltsValues[numberOfForks];
parseElts(matcher.group(5));
forks = new ParseTreeNode[numberOfForks];
for (int i = 0; i < numberOfForks; ++i) {
forks[i] = new ParseTreeNode();
forks[i].parse(reader);
}
}
}
public static class EltsValues {
private final List<String> values;
public EltsValues() {
this.values = new LinkedList<String>();
}
private void addValue(String value) {
values.add(value);
}
public List<String> getValues() {
return Collections.unmodifiableList(values);
}
@Override
public String toString() {
return "EltsValues{" +
"values=" + values +
'}';
}
}
}