/** * 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; public abstract class PathSegment{ PathSegment child; int hash; public abstract PathSegment cloneWithNewChild(PathSegment segment); @Override public abstract PathSegment clone(); public static final class ArraySegment extends PathSegment { private final int index; public ArraySegment(String numberAsText, PathSegment child) { this(Integer.parseInt(numberAsText), child); } public ArraySegment(int index, PathSegment child) { this.child = child; this.index = index; assert index >=0; } public ArraySegment(PathSegment child) { this.child = child; this.index = -1; } public boolean hasIndex() { return index != -1; } public ArraySegment(int index) { if (index < 0 ) { throw new IllegalArgumentException(); } this.index = index; } public int getIndex() { return index; } @Override public boolean isArray() { return true; } @Override public boolean isNamed() { return false; } @Override public ArraySegment getArraySegment() { return this; } @Override public String toString() { return "ArraySegment [index=" + index + ", getChild()=" + getChild() + "]"; } @Override public int segmentHashCode() { return index; } @Override public boolean segmentEquals(PathSegment obj) { if (this == obj) { return true; } else if (obj == null) { return false; } else if (obj instanceof ArraySegment) { return index == ((ArraySegment)obj).getIndex(); } return false; } @Override public PathSegment clone() { PathSegment seg = index < 0 ? new ArraySegment(null) : new ArraySegment(index); if (child != null) { seg.setChild(child.clone()); } return seg; } @Override public ArraySegment cloneWithNewChild(PathSegment newChild) { ArraySegment seg = index < 0 ? new ArraySegment(null) : new ArraySegment(index); if (child != null) { seg.setChild(child.cloneWithNewChild(newChild)); } else { seg.setChild(newChild); } return seg; } } public static final class NameSegment extends PathSegment { private final String path; public NameSegment(CharSequence n, PathSegment child) { this.child = child; this.path = n.toString(); } public NameSegment(CharSequence n) { this.path = n.toString(); } public String getPath() { return path; } @Override public boolean isArray() { return false; } @Override public boolean isNamed() { return true; } @Override public NameSegment getNameSegment() { return this; } @Override public String toString() { return "NameSegment [path=" + path + ", getChild()=" + getChild() + "]"; } @Override public int segmentHashCode() { return ((path == null) ? 0 : path.toLowerCase().hashCode()); } @Override public boolean segmentEquals(PathSegment obj) { if (this == obj) { return true; } else if (obj == null) { return false; } else if (getClass() != obj.getClass()) { return false; } NameSegment other = (NameSegment) obj; if (path == null) { return other.path == null; } return path.equalsIgnoreCase(other.path); } @Override public NameSegment clone() { NameSegment s = new NameSegment(this.path); if (child != null) { s.setChild(child.clone()); } return s; } @Override public NameSegment cloneWithNewChild(PathSegment newChild) { NameSegment s = new NameSegment(this.path); if (child != null) { s.setChild(child.cloneWithNewChild(newChild)); } else { s.setChild(newChild); } return s; } } public NameSegment getNameSegment() { throw new UnsupportedOperationException(); } public ArraySegment getArraySegment() { throw new UnsupportedOperationException(); } public abstract boolean isArray(); public abstract boolean isNamed(); public boolean isLastPath() { return child == null; } public PathSegment getChild() { return child; } void setChild(PathSegment child) { this.child = child; } protected abstract int segmentHashCode(); protected abstract boolean segmentEquals(PathSegment other); @Override public int hashCode() { int h = hash; if (h == 0) { h = segmentHashCode(); h = 31*h + ((child == null) ? 0 : child.hashCode()); hash = h; } return h; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } PathSegment other = (PathSegment) obj; if (!segmentEquals(other)) { return false; } else if (child == null) { return (other.child == null); } else { return child.equals(other.child); } } /** * Check if another path is contained in this one. This is useful for 2 cases. The first * is checking if the other is lower down in the tree, below this path. The other is if * a path is actually contained above the current one. * * Examples: * [a] . contains( [a.b.c] ) returns true * [a.b.c] . contains( [a] ) returns true * * This behavior is used for cases like scanning json in an event based fashion, when we arrive at * a node in a complex type, we will know the complete path back to the root. This method can * be used to determine if we need the data below. This is true in both the cases where the * column requested from the user is below the current node (in which case we may ignore other nodes * further down the tree, while keeping others). This is also the case if the requested path is further * up the tree, if we know we are at position a.b.c and a.b was a requested column, we need to scan * all of the data at and below the current a.b.c node. * * @param otherSeg - path segment to check if it is contained below this one. * @return - is this a match */ public boolean contains(PathSegment otherSeg) { if (this == otherSeg) { return true; } if (otherSeg == null) { return false; } // TODO - fix this in the future to match array segments are part of the path // the current behavior to always return true when we hit an array may be useful in some cases, // but we can get better performance in the JSON reader if we avoid reading unwanted elements in arrays if (otherSeg.isArray() || this.isArray()) { return true; } if (getClass() != otherSeg.getClass()) { return false; } if (!segmentEquals(otherSeg)) { return false; } else if (child == null || otherSeg.child == null) { return true; } else { return child.contains(otherSeg.child); } } }