/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flink.api.java.typeutils;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.flink.api.common.operators.Keys.ExpressionKeys;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.common.typeutils.CompositeType;
import static org.apache.flink.util.Preconditions.checkNotNull;
public abstract class TupleTypeInfoBase<T> extends CompositeType<T> {
private static final long serialVersionUID = 1L;
private final static String REGEX_FIELD = "(f?)([0-9]+)";
private final static String REGEX_NESTED_FIELDS = "("+REGEX_FIELD+")(\\.(.+))?";
private final static String REGEX_NESTED_FIELDS_WILDCARD = REGEX_NESTED_FIELDS
+"|\\"+ ExpressionKeys.SELECT_ALL_CHAR
+"|\\"+ExpressionKeys.SELECT_ALL_CHAR_SCALA;
private static final Pattern PATTERN_FIELD = Pattern.compile(REGEX_FIELD);
private static final Pattern PATTERN_NESTED_FIELDS = Pattern.compile(REGEX_NESTED_FIELDS);
private static final Pattern PATTERN_NESTED_FIELDS_WILDCARD = Pattern.compile(REGEX_NESTED_FIELDS_WILDCARD);
// --------------------------------------------------------------------------------------------
protected final TypeInformation<?>[] types;
private final int totalFields;
public TupleTypeInfoBase(Class<T> tupleType, TypeInformation<?>... types) {
super(tupleType);
this.types = checkNotNull(types);
int fieldCounter = 0;
for(TypeInformation<?> type : types) {
fieldCounter += type.getTotalFields();
}
totalFields = fieldCounter;
}
@Override
public boolean isBasicType() {
return false;
}
@Override
public boolean isTupleType() {
return true;
}
public boolean isCaseClass() {
return false;
}
@Override
public int getArity() {
return types.length;
}
@Override
public int getTotalFields() {
return totalFields;
}
@Override
public void getFlatFields(String fieldExpression, int offset, List<FlatFieldDescriptor> result) {
Matcher matcher = PATTERN_NESTED_FIELDS_WILDCARD.matcher(fieldExpression);
if (!matcher.matches()) {
throw new InvalidFieldReferenceException("Invalid tuple field reference \""+fieldExpression+"\".");
}
String field = matcher.group(0);
if (field.equals(ExpressionKeys.SELECT_ALL_CHAR) || field.equals(ExpressionKeys.SELECT_ALL_CHAR_SCALA)) {
// handle select all
int keyPosition = 0;
for (TypeInformation<?> type : types) {
if (type instanceof CompositeType) {
CompositeType<?> cType = (CompositeType<?>) type;
cType.getFlatFields(String.valueOf(ExpressionKeys.SELECT_ALL_CHAR), offset + keyPosition, result);
keyPosition += cType.getTotalFields() - 1;
} else {
result.add(new FlatFieldDescriptor(offset + keyPosition, type));
}
keyPosition++;
}
} else {
String fieldStr = matcher.group(1);
Matcher fieldMatcher = PATTERN_FIELD.matcher(fieldStr);
if (!fieldMatcher.matches()) {
throw new RuntimeException("Invalid matcher pattern");
}
field = fieldMatcher.group(2);
int fieldPos = Integer.valueOf(field);
if (fieldPos >= this.getArity()) {
throw new InvalidFieldReferenceException("Tuple field expression \"" + fieldStr + "\" out of bounds of " + this.toString() + ".");
}
TypeInformation<?> fieldType = this.getTypeAt(fieldPos);
String tail = matcher.group(5);
if (tail == null) {
if (fieldType instanceof CompositeType) {
// forward offsets
for (int i = 0; i < fieldPos; i++) {
offset += this.getTypeAt(i).getTotalFields();
}
// add all fields of composite type
((CompositeType<?>) fieldType).getFlatFields("*", offset, result);
} else {
// we found the field to add
// compute flat field position by adding skipped fields
int flatFieldPos = offset;
for (int i = 0; i < fieldPos; i++) {
flatFieldPos += this.getTypeAt(i).getTotalFields();
}
result.add(new FlatFieldDescriptor(flatFieldPos, fieldType));
}
} else {
if (fieldType instanceof CompositeType<?>) {
// forward offset
for (int i = 0; i < fieldPos; i++) {
offset += this.getTypeAt(i).getTotalFields();
}
((CompositeType<?>) fieldType).getFlatFields(tail, offset, result);
} else {
throw new InvalidFieldReferenceException("Nested field expression \"" + tail + "\" not possible on atomic type " + fieldType + ".");
}
}
}
}
@Override
public <X> TypeInformation<X> getTypeAt(String fieldExpression) {
Matcher matcher = PATTERN_NESTED_FIELDS.matcher(fieldExpression);
if(!matcher.matches()) {
if (fieldExpression.equals(ExpressionKeys.SELECT_ALL_CHAR) || fieldExpression.equals(ExpressionKeys.SELECT_ALL_CHAR_SCALA)) {
throw new InvalidFieldReferenceException("Wildcard expressions are not allowed here.");
} else {
throw new InvalidFieldReferenceException("Invalid format of tuple field expression \""+fieldExpression+"\".");
}
}
String fieldStr = matcher.group(1);
Matcher fieldMatcher = PATTERN_FIELD.matcher(fieldStr);
if(!fieldMatcher.matches()) {
throw new RuntimeException("Invalid matcher pattern");
}
String field = fieldMatcher.group(2);
int fieldPos = Integer.valueOf(field);
if(fieldPos >= this.getArity()) {
throw new InvalidFieldReferenceException("Tuple field expression \""+fieldStr+"\" out of bounds of "+this.toString()+".");
}
TypeInformation<X> fieldType = this.getTypeAt(fieldPos);
String tail = matcher.group(5);
if(tail == null) {
// we found the type
return fieldType;
} else {
if(fieldType instanceof CompositeType<?>) {
return ((CompositeType<?>) fieldType).getTypeAt(tail);
} else {
throw new InvalidFieldReferenceException("Nested field expression \""+tail+"\" not possible on atomic type "+fieldType+".");
}
}
}
@Override
public <X> TypeInformation<X> getTypeAt(int pos) {
if (pos < 0 || pos >= this.types.length) {
throw new IndexOutOfBoundsException();
}
@SuppressWarnings("unchecked")
TypeInformation<X> typed = (TypeInformation<X>) this.types[pos];
return typed;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TupleTypeInfoBase) {
@SuppressWarnings("unchecked")
TupleTypeInfoBase<T> other = (TupleTypeInfoBase<T>) obj;
return other.canEqual(this) &&
super.equals(other) &&
Arrays.equals(types, other.types) &&
totalFields == other.totalFields;
} else {
return false;
}
}
@Override
public boolean canEqual(Object obj) {
return obj instanceof TupleTypeInfoBase;
}
@Override
public int hashCode() {
return 31 * (31 * super.hashCode() + Arrays.hashCode(types)) + totalFields;
}
@Override
public String toString() {
StringBuilder bld = new StringBuilder("Tuple");
bld.append(types.length);
if (types.length > 0) {
bld.append('<').append(types[0]);
for (int i = 1; i < types.length; i++) {
bld.append(", ").append(types[i]);
}
bld.append('>');
}
return bld.toString();
}
@Override
public boolean hasDeterministicFieldOrder() {
return true;
}
}