package com.aliyun.odps.type; import java.util.ArrayList; import com.aliyun.odps.OdpsType; /** * Created by zhenhong.gzh on 16/7/18. */ public class TypeInfoParser { private ArrayList<String> tokens = new ArrayList<String>(); private String typeInfoName; private int index = 0; TypeInfoParser(String name) { typeInfoName = name.toUpperCase(); tokenize(typeInfoName); } private boolean isTypeInfoChar(char c) { return Character.isLetterOrDigit(c) || (c == '_') || (c == '.'); } // Tokenize the typeInfoString. The rule is simple: all consecutive // letter, digit and '_', '.' are in one token, and all other characters are // one character per token. // // tokenize("map<int,decimal(10,2)>") should return // ["map", "<" , "int", ",", "decimal", "(", "10", "," ,"2", ")", ">"] private void tokenize(String name) { int begin = 0; int end = 1; while (end <= name.length()) { if (end == name.length() || !isTypeInfoChar(name.charAt(end)) || !isTypeInfoChar( name.charAt(end - 1))) { String token = name.substring(begin, end).trim(); if (!token.isEmpty()) { tokens.add(token); } begin = end; } end++; } } private TypeInfo parseTypeInfo() { TypeInfo type = parseTypeInfoInternal(); if (index != tokens.size()) { throw new IllegalArgumentException("Parse type info failed, pls check: " + typeInfoName); } return type; } private TypeInfo parseTypeInfoInternal() { OdpsType typeCategory = OdpsType.valueOf(peek()); switch (typeCategory) { case ARRAY: { return parseArrayTypeInfo(); } case MAP: { return parseMapTypeInfo(); } case STRUCT: { return parseStructTypeInfo(); } case CHAR: { return parseCharTypeInfo(); } case VARCHAR: { return parseVarcharTypeInfo(); } case DECIMAL: { return parseComplexDecimalTypeInfo(); } default: { return TypeInfoFactory.getPrimitiveTypeInfo(typeCategory); } } } private String peek() { if (index >= tokens.size()) { throw new IllegalArgumentException("Parse type info failed, pls check: " + typeInfoName); } return tokens.get(index++); } private int getPosition(int tokenIndex) { int pos = 0; for (int i = 0; i < tokenIndex; i++) { pos += tokens.get(i).length(); } return pos; } private void expect(String expected) { if (!peek().equals(expected)) { throw new IllegalArgumentException( "Error parse type info: " + typeInfoName + ", expect \'" + expected + "\' in position: " + getPosition(index - 1)); } } private int getInteger() { try { return Integer.parseInt(peek()); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Error parse type info: " + typeInfoName + ", expect integer in position: " + getPosition( index - 1), e); } } private int getCharParam() { expect("("); int length = getInteger(); expect(")"); return length; } private int[] getDecimalParams() { // no param decimal. if ((index >= tokens.size()) || !tokens.get(index).equals("(")) { return null; } expect("("); int precision = getInteger(); expect(","); int scale = getInteger(); expect(")"); return new int[]{precision, scale}; } private TypeInfo parseArrayTypeInfo() { expect("<"); TypeInfo elementTypeInfo = parseTypeInfoInternal(); expect(">"); return TypeInfoFactory.getArrayTypeInfo(elementTypeInfo); } private TypeInfo parseMapTypeInfo() { expect("<"); TypeInfo keyTypeInfo = parseTypeInfoInternal(); expect(","); TypeInfo valueTypeInfo = parseTypeInfoInternal(); expect(">"); return TypeInfoFactory.getMapTypeInfo(keyTypeInfo, valueTypeInfo); } private TypeInfo parseStructTypeInfo() { ArrayList<String> names = new ArrayList<String>(); ArrayList<TypeInfo> typeInfos = new ArrayList<TypeInfo>(); boolean first = true; while (true) { if (first) { expect("<"); first = false; } else { String delimiter = peek(); if (delimiter.equals(">")) { break; } else { index--; expect(","); } } names.add(peek()); expect(":"); typeInfos.add(parseTypeInfoInternal()); } return TypeInfoFactory.getStructTypeInfo(names, typeInfos); } private TypeInfo parseVarcharTypeInfo() { return TypeInfoFactory.getVarcharTypeInfo(getCharParam()); } private TypeInfo parseCharTypeInfo() { return TypeInfoFactory.getCharTypeInfo(getCharParam()); } private TypeInfo parseComplexDecimalTypeInfo() { int[] params = getDecimalParams(); if (params == null) { return TypeInfoFactory.DECIMAL; } return TypeInfoFactory.getDecimalTypeInfo(params[0], params[1]); } public static TypeInfo getTypeInfoFromTypeString(String name) { return new TypeInfoParser(name).parseTypeInfo(); } }