/***********************************************************************************************************************
* Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.api.java.typeutils;
import eu.stratosphere.types.Value;
import eu.stratosphere.types.TypeInformation;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TypeInfoParser {
private static final String TUPLE_PACKAGE = "eu.stratosphere.api.java.tuple";
private static final String VALUE_PACKAGE = "eu.stratosphere.types";
private static final String WRITABLE_PACKAGE = "org.apache.hadoop.io";
private static final Pattern tuplePattern = Pattern.compile("^((" + TUPLE_PACKAGE.replaceAll("\\.", "\\\\.") + "\\.)?Tuple[0-9]+)<");
private static final Pattern writablePattern = Pattern.compile("^((" + WRITABLE_PACKAGE.replaceAll("\\.", "\\\\.") + "\\.)?Writable)<([^\\s,>]*)(,|>|$)");
private static final Pattern basicTypePattern = Pattern
.compile("^((java\\.lang\\.)?(String|Integer|Byte|Short|Character|Double|Float|Long|Boolean))(,|>|$)");
private static final Pattern basicType2Pattern = Pattern.compile("^(int|byte|short|char|double|float|long|boolean)(,|>|$)");
private static final Pattern valueTypePattern = Pattern.compile("^((" + VALUE_PACKAGE.replaceAll("\\.", "\\\\.")
+ "\\.)?(String|Int|Byte|Short|Char|Double|Float|Long|Boolean|List|Map|Null))Value(,|>|$)");
private static final Pattern basicArrayTypePattern = Pattern
.compile("^((java\\.lang\\.)?(String|Integer|Byte|Short|Character|Double|Float|Long|Boolean))\\[\\](,|>|$)");
private static final Pattern basicArrayType2Pattern = Pattern.compile("^(int|byte|short|char|double|float|long|boolean)\\[\\](,|>|$)");
private static final Pattern customObjectPattern = Pattern.compile("^([^\\s,>]+)(,|>|$)");
/**
* Generates an instance of <code>TypeInformation</code> by parsing a type
* information string. A type information string can contain the following
* types:
*
* <ul>
* <li>Basic types such as <code>Integer</code>, <code>String</code>, etc.
* <li>Basic type arrays such as <code>Integer[]</code>,
* <code>String[]</code>, etc.
* <li>Tuple types such as <code>Tuple1<TYPE0></code>,
* <code>Tuple2<TYPE0, TYPE1></code>, etc.</li>
* <li>Custom types such as <code>org.my.CustomClass</code>,
* <code>org.my.CustomClass$StaticInnerClass</code>, etc.
* <li>Custom type arrays such as <code>org.my.CustomClass[]</code>,
* <code>org.my.CustomClass$StaticInnerClass[]</code>, etc.
* <li>Value types such as <code>DoubleValue</code>,
* <code>StringValue</code>, <code>IntegerValue</code>, etc.</li>
* <li>Tuple array types such as <code>Tuple2<TYPE0,TYPE1>[], etc.</code></li>
* <li>Writable types such as <code>Writable<org.my.CustomWritable></code></li>
* </ul>
*
* Example:
* <code>"Tuple2<String,Tuple2<Integer,org.my.MyClass>>"</code>
*
* @param infoString
* type information string to be parsed
* @return <code>TypeInformation</code> representation of the string
*/
@SuppressWarnings("unchecked")
public static <X> TypeInformation<X> parse(String infoString) {
try {
if (infoString == null) {
throw new IllegalArgumentException("String is null.");
}
String clearedString = infoString.replaceAll("\\s", "");
if (clearedString.length() == 0) {
throw new IllegalArgumentException("String must not be empty.");
}
return (TypeInformation<X>) parse(new StringBuilder(clearedString));
} catch (Exception e) {
throw new IllegalArgumentException("String could not be parsed: " + e.getMessage());
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static TypeInformation<?> parse(StringBuilder sb) throws ClassNotFoundException {
String infoString = sb.toString();
final Matcher tupleMatcher = tuplePattern.matcher(infoString);
final Matcher writableMatcher = writablePattern.matcher(infoString);
final Matcher basicTypeMatcher = basicTypePattern.matcher(infoString);
final Matcher basicType2Matcher = basicType2Pattern.matcher(infoString);
final Matcher valueTypeMatcher = valueTypePattern.matcher(infoString);
final Matcher basicArrayTypeMatcher = basicArrayTypePattern.matcher(infoString);
final Matcher basicArrayType2Matcher = basicArrayType2Pattern.matcher(infoString);
final Matcher customObjectMatcher = customObjectPattern.matcher(infoString);
if (infoString.length() == 0) {
return null;
}
TypeInformation<?> returnType = null;
// tuples
if (tupleMatcher.find()) {
String className = tupleMatcher.group(1);
sb.delete(0, className.length() + 1);
int arity = Integer.parseInt(className.replaceAll("\\D", ""));
Class<?> clazz = null;
// check if fully qualified
if (className.startsWith(TUPLE_PACKAGE)) {
clazz = Class.forName(className);
} else {
clazz = Class.forName(TUPLE_PACKAGE + "." + className);
}
TypeInformation<?>[] types = new TypeInformation<?>[arity];
for (int i = 0; i < arity; i++) {
types[i] = parse(sb);
if (types[i] == null) {
throw new IllegalArgumentException("Tuple arity does not match given parameters.");
}
}
if (sb.charAt(0) != '>') {
throw new IllegalArgumentException("Tuple arity does not match given parameters.");
}
// remove '>'
sb.deleteCharAt(0);
// tuple arrays
if (sb.length() > 0) {
if (sb.length() >= 2 && sb.charAt(0) == '[' && sb.charAt(1) == ']') {
Class<?> arrayClazz = null;
// check if fully qualified
if (className.startsWith(TUPLE_PACKAGE)) {
arrayClazz = Class.forName("[L" + className + ";");
} else {
arrayClazz = Class.forName("[L" + TUPLE_PACKAGE + "." + className + ";");
}
returnType = ObjectArrayTypeInfo.getInfoFor(arrayClazz, new TupleTypeInfo(clazz, types));
} else if (sb.length() >= 1 && sb.charAt(0) == '[') {
// no return type -> exception instead
} else {
returnType = new TupleTypeInfo(clazz, types);
}
} else {
returnType = new TupleTypeInfo(clazz, types);
}
}
// writable types
else if (writableMatcher.find()) {
String className = writableMatcher.group(1);
String fullyQualifiedName = writableMatcher.group(3);
sb.delete(0, className.length() + 1 + fullyQualifiedName.length());
try {
Class<?> clazz = Class.forName(fullyQualifiedName);
returnType = WritableTypeInfo.getWritableTypeInfo((Class) clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class '" + fullyQualifiedName
+ "' could not be found for use as writable type. Please note that inner classes must be declared static.");
}
}
// basic types of classes
else if (basicTypeMatcher.find()) {
String className = basicTypeMatcher.group(1);
sb.delete(0, className.length());
Class<?> clazz = null;
// check if fully qualified
if (className.startsWith("java.lang")) {
clazz = Class.forName(className);
} else {
clazz = Class.forName("java.lang." + className);
}
returnType = BasicTypeInfo.getInfoFor(clazz);
}
// basic type of primitives
else if (basicType2Matcher.find()) {
String className = basicType2Matcher.group(1);
sb.delete(0, className.length());
Class<?> clazz = null;
if (className.equals("int")) {
clazz = Integer.class;
} else if (className.equals("byte")) {
clazz = Byte.class;
} else if (className.equals("short")) {
clazz = Short.class;
} else if (className.equals("char")) {
clazz = Character.class;
} else if (className.equals("double")) {
clazz = Double.class;
} else if (className.equals("float")) {
clazz = Float.class;
} else if (className.equals("long")) {
clazz = Long.class;
} else if (className.equals("boolean")) {
clazz = Boolean.class;
}
returnType = BasicTypeInfo.getInfoFor(clazz);
}
// values
else if (valueTypeMatcher.find()) {
String className = valueTypeMatcher.group(1);
sb.delete(0, className.length() + 5);
Class<?> clazz = null;
// check if fully qualified
if (className.startsWith(VALUE_PACKAGE)) {
clazz = Class.forName(className + "Value");
} else {
clazz = Class.forName(VALUE_PACKAGE + "." + className + "Value");
}
returnType = ValueTypeInfo.getValueTypeInfo((Class<Value>) clazz);
}
// array of classes
else if (basicArrayTypeMatcher.find()) {
String className = basicArrayTypeMatcher.group(1);
sb.delete(0, className.length() + 2);
Class<?> clazz = null;
if (className.startsWith("java.lang")) {
clazz = Class.forName("[L" + className + ";");
} else {
clazz = Class.forName("[Ljava.lang." + className + ";");
}
returnType = BasicArrayTypeInfo.getInfoFor(clazz);
}
// array of primitives
else if (basicArrayType2Matcher.find()) {
String className = basicArrayType2Matcher.group(1);
sb.delete(0, className.length() + 2);
Class<?> clazz = null;
if (className.equals("int")) {
clazz = Integer[].class;
} else if (className.equals("byte")) {
clazz = Byte[].class;
} else if (className.equals("short")) {
clazz = Short[].class;
} else if (className.equals("char")) {
clazz = Character[].class;
} else if (className.equals("double")) {
clazz = Double[].class;
} else if (className.equals("float")) {
clazz = Float[].class;
} else if (className.equals("long")) {
clazz = Long[].class;
} else if (className.equals("boolean")) {
clazz = Boolean[].class;
}
returnType = BasicArrayTypeInfo.getInfoFor(clazz);
}
// custom objects
else if (customObjectMatcher.find()) {
String fullyQualifiedName = customObjectMatcher.group(1);
sb.delete(0, fullyQualifiedName.length());
if (fullyQualifiedName.contains("<")) {
throw new IllegalArgumentException("Parameterized custom classes are not supported by parser.");
}
// custom object array
if (fullyQualifiedName.endsWith("[]")) {
fullyQualifiedName = fullyQualifiedName.substring(0, fullyQualifiedName.length() - 2);
try {
Class<?> clazz = Class.forName("[L" + fullyQualifiedName + ";");
returnType = ObjectArrayTypeInfo.getInfoFor(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class '" + fullyQualifiedName
+ "' could not be found for use as object array. Please note that inner classes must be declared static.");
}
} else {
try {
Class<?> clazz = Class.forName(fullyQualifiedName);
returnType = new GenericTypeInfo(clazz);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class '" + fullyQualifiedName
+ "' could not be found for use as custom object. Please note that inner classes must be declared static.");
}
}
}
if (returnType == null) {
throw new IllegalArgumentException("Error at '" + infoString + "'");
} else {
// remove possible ','
if (sb.length() > 0 && sb.charAt(0) == ',') {
sb.deleteCharAt(0);
}
return returnType;
}
}
}