/**
* 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.drill.common.expression;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.common.expression.PathSegment.ArraySegment;
import org.apache.drill.common.expression.PathSegment.NameSegment;
import org.apache.drill.common.expression.parser.ExprLexer;
import org.apache.drill.common.expression.parser.ExprParser;
import org.apache.drill.common.expression.parser.ExprParser.parse_return;
import org.apache.drill.common.expression.visitors.ExprVisitor;
import org.apache.drill.common.types.TypeProtos.MajorType;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.proto.UserBitShared.NamePart;
import org.apache.drill.exec.proto.UserBitShared.NamePart.Type;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
public class SchemaPath extends LogicalExpressionBase {
private final NameSegment rootSegment;
public static SchemaPath getSimplePath(String name){
return getCompoundPath(name);
}
public static SchemaPath getCompoundPath(String... strings){
List<String> paths = Arrays.asList(strings);
Collections.reverse(paths);
NameSegment s = null;
for(String p : paths){
s = new NameSegment(p, s);
}
return new SchemaPath(s);
}
public PathSegment getLastSegment(){
PathSegment s= rootSegment;
while(s.getChild() != null){
s = s.getChild();
}
return s;
}
/**
*
* @param simpleName
*/
@Deprecated
public SchemaPath(String simpleName, ExpressionPosition pos){
super(pos);
this.rootSegment = new NameSegment(simpleName);
if(simpleName.contains(".")) throw new IllegalStateException("This is deprecated and only supports simpe paths.");
}
public NamePart getAsNamePart(){
return getNamePart(rootSegment);
}
private static NamePart getNamePart(PathSegment s){
if(s == null) return null;
NamePart.Builder b = NamePart.newBuilder();
if(s.getChild() != null){
b.setChild(getNamePart(s.getChild()));
}
if(s.isArray()){
if(s.getArraySegment().hasIndex()) throw new IllegalStateException("You cannot convert a indexed schema path to a NamePart. NameParts can only reference Vectors, not individual records or values.");
b.setType(Type.ARRAY);
}else{
b.setType(Type.NAME);
b.setName(s.getNameSegment().getPath());
}
return b.build();
}
private static PathSegment getPathSegment(NamePart n){
PathSegment child = n.hasChild() ? getPathSegment(n.getChild()) : null;
if(n.getType() == Type.ARRAY){
return new ArraySegment(child);
}else{
return new NameSegment(n.getName(), child);
}
}
public static SchemaPath create(NamePart namePart){
Preconditions.checkArgument(namePart.getType() == NamePart.Type.NAME);
return new SchemaPath((NameSegment) getPathSegment(namePart));
}
/**
* A simple is a path where there are no repeated elements outside the lowest level of the path.
* @return Whether this path is a simple path.
*/
public boolean isSimplePath(){
PathSegment seg = rootSegment;
while(seg != null){
if(seg.isArray() && !seg.isLastPath()) return false;
seg = seg.getChild();
}
return true;
}
public SchemaPath(SchemaPath path){
super(path.getPosition());
this.rootSegment = path.rootSegment;
}
public SchemaPath(NameSegment rootSegment){
super(ExpressionPosition.UNKNOWN);
this.rootSegment = rootSegment;
}
public SchemaPath(NameSegment rootSegment, ExpressionPosition pos){
super(pos);
this.rootSegment = rootSegment;
}
@Override
public <T, V, E extends Exception> T accept(ExprVisitor<T, V, E> visitor, V value) throws E {
return visitor.visitSchemaPath(this, value);
}
public SchemaPath getChild(String childPath){
NameSegment newRoot = rootSegment.cloneWithNewChild(new NameSegment(childPath));
return new SchemaPath(newRoot);
}
public SchemaPath getUnindexedArrayChild(){
NameSegment newRoot = rootSegment.cloneWithNewChild(new ArraySegment(null));
return new SchemaPath(newRoot);
}
public SchemaPath getChild(int index){
NameSegment newRoot = rootSegment.cloneWithNewChild(new ArraySegment(index));
return new SchemaPath(newRoot);
}
public NameSegment getRootSegment() {
return rootSegment;
}
@Override
public MajorType getMajorType() {
return Types.LATE_BIND_TYPE;
}
@Override
public int hashCode() {
return ((rootSegment == null) ? 0 : rootSegment.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof SchemaPath))
return false;
SchemaPath other = (SchemaPath) obj;
if (rootSegment == null) {
return (other.rootSegment == null);
}
return rootSegment.equals(other.rootSegment);
}
public boolean contains(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof SchemaPath))
return false;
SchemaPath other = (SchemaPath) obj;
if (rootSegment == null) {
return true;
}
return rootSegment.contains(other.rootSegment);
}
@Override
public Iterator<LogicalExpression> iterator() {
return Iterators.emptyIterator();
}
@Override
public String toString() {
String expr = ExpressionStringBuilder.toString(this);
return "SchemaPath ["+ expr + "]";
}
public String toExpr(){
return ExpressionStringBuilder.toString(this);
}
public String getAsUnescapedPath(){
StringBuilder sb = new StringBuilder();
PathSegment seg = getRootSegment();
if(seg.isArray()) throw new IllegalStateException("Drill doesn't currently support top level arrays");
sb.append(seg.getNameSegment().getPath());
while( (seg = seg.getChild()) != null){
if(seg.isNamed()){
sb.append('.');
sb.append(seg.getNameSegment().getPath());
}else{
sb.append('[');
sb.append(seg.getArraySegment().getIndex());
sb.append(']');
}
}
return sb.toString();
}
public static class De extends StdDeserializer<SchemaPath> {
DrillConfig config;
public De(DrillConfig config) {
super(LogicalExpression.class);
this.config = config;
}
@Override
public SchemaPath deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException {
String expr = jp.getText();
if (expr == null || expr.isEmpty())
return null;
try {
// logger.debug("Parsing expression string '{}'", expr);
ExprLexer lexer = new ExprLexer(new ANTLRStringStream(expr));
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExprParser parser = new ExprParser(tokens);
//TODO: move functionregistry and error collector to injectables.
//ctxt.findInjectableValue(valueId, forProperty, beanInstance)
parse_return ret = parser.parse();
// ret.e.resolveAndValidate(expr, errorCollector);
if(ret.e instanceof SchemaPath){
return (SchemaPath) ret.e;
}else{
throw new IllegalStateException("Schema path is not a valid format.");
}
} catch (RecognitionException e) {
throw new RuntimeException(e);
}
}
}
}