blob: 2ffeca565963c2e7e340ba18ab2055b2f3d97a75 [file] [log] [blame]
/*
* 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.iotdb.commons.path;
import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.exception.MetadataException;
import org.apache.iotdb.commons.utils.PathUtils;
import org.apache.iotdb.commons.utils.TestOnly;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.apache.tsfile.common.constant.TsFileConstant;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.IDeviceID;
import org.apache.tsfile.file.metadata.PlainDeviceID;
import org.apache.tsfile.read.common.Path;
import org.apache.tsfile.utils.PublicBAOS;
import org.apache.tsfile.utils.ReadWriteIOUtils;
import org.apache.tsfile.write.schema.IMeasurementSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.apache.iotdb.commons.conf.IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD;
import static org.apache.iotdb.commons.conf.IoTDBConstant.ONE_LEVEL_PATH_WILDCARD;
/**
* A prefix path, suffix path or fullPath generated from SQL. Usually used in the IoTDB server
* module
*/
public class PartialPath extends Path implements Comparable<Path>, Cloneable {
private static final Logger logger = LoggerFactory.getLogger(PartialPath.class);
private static final PartialPath ALL_MATCH_PATTERN = new PartialPath(new String[] {"root", "**"});
protected String[] nodes;
public PartialPath() {}
public PartialPath(IDeviceID device) throws IllegalPathException {
this(((PlainDeviceID) device).toStringID());
}
/**
* Construct the PartialPath using a String, will split the given String into String[] E.g., path
* = "root.sg.`d.1`.`s.1`" nodes = {"root", "sg", "`d.1`", "`s.1`"}
*
* @param path a full String of a time series path
*/
public PartialPath(String path) throws IllegalPathException {
this.nodes = PathUtils.splitPathToDetachedNodes(path);
if (nodes.length == 0) {
throw new IllegalPathException(path);
}
// path is root.sg.`abc`, fullPath is root.sg.abc
// path is root.sg.`select`, fullPath is root.sg.select
// path is root.sg.`111`, fullPath is root.sg.`111`
// path is root.sg.`a.b`, fullPath is root.sg.`a.b`
// path is root.sg.`a``b`, fullPath is root.sg.`a``b`
this.fullPath = getFullPath();
}
public PartialPath(IDeviceID device, String measurement) throws IllegalPathException {
this(((PlainDeviceID) device).toStringID(), measurement);
}
public PartialPath(String device, String measurement) throws IllegalPathException {
String path = device + TsFileConstant.PATH_SEPARATOR + measurement;
this.nodes = PathUtils.splitPathToDetachedNodes(path);
this.fullPath = getFullPath();
}
/** @param partialNodes nodes of a time series path */
public PartialPath(String[] partialNodes) {
nodes = partialNodes;
}
/**
* only use this method in following situations: 1. you are sure you do not want to split the
* path. 2. you are sure path is correct.
*
* @param path path
* @param needSplit whether to split path to nodes, needSplit can only be false.
*/
public PartialPath(String path, boolean needSplit) {
Validate.isTrue(!needSplit);
fullPath = path;
if ("".equals(path)) {
this.nodes = new String[] {};
} else {
this.nodes = new String[] {path};
}
}
public boolean hasWildcard() {
for (String node : nodes) {
// *, ** , d*, *d*
if (PathPatternUtil.hasWildcard(node)) {
return true;
}
}
return false;
}
public boolean hasMultiLevelMatchWildcard() {
for (String node : nodes) {
if (PathPatternUtil.isMultiLevelMatchWildcard(node)) {
return true;
}
}
return false;
}
public boolean endWithMultiLevelWildcard() {
return PathPatternUtil.isMultiLevelMatchWildcard(nodes[nodes.length - 1]);
}
// e.g. root.db.d.s, root.db.d.*, root.db.d.s*, not include patterns like root.db.d.**
public boolean hasExplicitDevice() {
if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
return false;
}
for (int i = 0; i < nodes.length - 1; i++) {
// *, ** , d*, *d*
if (PathPatternUtil.hasWildcard(nodes[i])) {
return false;
}
}
return true;
}
/**
* it will return a new partial path
*
* @param partialPath the path you want to concat
* @return new partial path
*/
public PartialPath concatPath(PartialPath partialPath) {
int len = nodes.length;
String[] newNodes = Arrays.copyOf(nodes, nodes.length + partialPath.nodes.length);
System.arraycopy(partialPath.nodes, 0, newNodes, len, partialPath.nodes.length);
return new PartialPath(newNodes);
}
/**
* It will change nodes in this partial path
*
* @param otherNodes nodes
*/
public void concatPath(String[] otherNodes) {
int len = nodes.length;
this.nodes = Arrays.copyOf(nodes, nodes.length + otherNodes.length);
System.arraycopy(otherNodes, 0, nodes, len, otherNodes.length);
fullPath = String.join(TsFileConstant.PATH_SEPARATOR, nodes);
}
/**
* only use this method in following situations: 1. you are sure node is allowed in syntax
* convention. 2. you are sure node needs not to be checked.
*/
public PartialPath concatNode(String node) {
String[] newPathNodes = Arrays.copyOf(nodes, nodes.length + 1);
newPathNodes[newPathNodes.length - 1] = node;
return new PartialPath(newPathNodes);
}
public String[] getNodes() {
return nodes;
}
public int getNodeLength() {
return nodes.length;
}
public String getTailNode() {
if (nodes.length <= 0) {
return "";
}
return nodes[nodes.length - 1];
}
/**
* Return the intersection of paths starting with the given prefix and paths matching this path
* pattern.
*
* <p>For example, minimizing "root.**.b.c" with prefix "root.a.b" produces "root.a.b.c",
* "root.a.b.b.c", "root.a.b.**.b.c", since the multi-level wildcard can match 'a', 'a.b', and any
* other sub paths start with 'a.b'.
*
* <p>The goal of this method is to reduce the search space when querying a database with a path
* with wildcard.
*
* <p>If this path or path pattern doesn't start with given prefix, return empty list. For
* example, "root.a.b.c".alterPrefixPath("root.b") or "root.a.**".alterPrefixPath("root.b")
* returns [].
*
* @param prefix The prefix. Cannot be null and cannot contain any wildcard.
*/
public List<PartialPath> alterPrefixPath(PartialPath prefix) {
Validate.notNull(prefix);
// Make sure the prefix path doesn't contain any wildcard.
for (String node : prefix.getNodes()) {
if (MULTI_LEVEL_PATH_WILDCARD.equals(node) || ONE_LEVEL_PATH_WILDCARD.equals(node)) {
throw new IllegalArgumentException(
"Wildcards are not allowed in the prefix path: " + prefix.getFullPath());
}
}
List<List<String>> results = new ArrayList<>();
alterPrefixPathInternal(
Arrays.asList(prefix.getNodes()), Arrays.asList(nodes), new ArrayList<>(), results);
return results.stream()
.map(r -> new PartialPath(r.toArray(new String[0])))
.collect(Collectors.toList());
}
/**
* Let PRE stands for the remaining prefix nodes, PATH for the remaining path nodes, and C for the
* path being processed. And assume the size of the prefix is i and that of this path is j.
*
* <p>If the first node of the path is not '**' and it matches the first node of the path. The
* optimality equation is,
*
* <p>O(PRE(0, i), PATH(0, j), C) = O(PRE(1, i), PATH(1, j), [PRE(0)])
*
* <p>If the first node of the path is '**', it may match the first 1, 2, ... i nodes of the
* prefix. '**' may match the all the remaining nodes of the prefix, as well as some additional
* levels. Thus, the optimality equation is,
*
* <p>O(PRE(0, i), PATH(0, j), C) = { O(PRE(1, i), PATH(1, j), [PRE(0)]), ... , O([], PATH(1, j),
* C + PRE(0) + ... + PRE(i-1)), O([], PATH(0, j), C + PRE(0) + ... + PRE(i-1))}
*
* <p>And when all the prefix nodes are matched,
*
* <p>O([], PATH, C) = C + PATH
*
* <p>The algorithm above takes all the possible matches of a multi-level wildcard into account.
* Thus, it produces the correct result.
*
* <p>To prevent overlapping among the final results, this algorithm stops when the prefix has no
* remaining node and the first remaining node of the path is '**'. This strategy works because
* this algorithm will always find the path with the least levels in the search space.
*
* @param prefix The remaining nodes of the prefix.
* @param path The remaining nodes of the path.
* @param current The path being processed.
* @param results The final results.
* @return True if the search should be stopped, else false.
*/
private boolean alterPrefixPathInternal(
List<String> prefix, List<String> path, List<String> current, List<List<String>> results) {
if (prefix.isEmpty()) {
current.addAll(path);
results.add(current);
return !path.isEmpty() && MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0));
}
if (path.isEmpty()) {
return false;
}
if (MULTI_LEVEL_PATH_WILDCARD.equals(path.get(0))) {
// Wildcard matches part of or the whole the prefix.
for (int j = 1; j <= prefix.size(); j++) {
List<String> copy = new ArrayList<>(current);
copy.addAll(prefix.subList(0, j));
if (alterPrefixPathInternal(
prefix.subList(j, prefix.size()), path.subList(1, path.size()), copy, results)) {
return true;
}
}
// Wildcard matches the prefix and some more levels.
List<String> copy = new ArrayList<>(current);
copy.addAll(prefix);
return alterPrefixPathInternal(
Collections.emptyList(), path.subList(0, path.size()), copy, results);
} else if (ONE_LEVEL_PATH_WILDCARD.equals(path.get(0)) || prefix.get(0).equals(path.get(0))) {
// No need to make a copy.
current.add(prefix.get(0));
return alterPrefixPathInternal(
prefix.subList(1, prefix.size()), path.subList(1, path.size()), current, results);
}
return false;
}
/**
* Test if current PartialPath matches a full path. Current partialPath acts as a full path
* pattern. rPath is supposed to be a full timeseries path without wildcards. e.g.
* "root.sg.device.*" matches path "root.sg.device.s1" whereas it does not match "root.sg.device"
* and "root.sg.vehicle.s1"
*
* @param rPath a plain full path of a timeseries
* @return true if a successful match, otherwise return false
*/
public boolean matchFullPath(PartialPath rPath) {
return matchPath(rPath.getNodes(), 0, 0, false, false);
}
/**
* Check if current pattern PartialPath can match 1 prefix path.
*
* <p>1) Current partialPath acts as a full path pattern.
*
* <p>2) Input parameter prefixPath is 1 prefix of time-series path.
*
* <p>For example:
*
* <p>1) Pattern "root.sg1.d1.*" can match prefix path "root.sg1.d1.s1", "root.sg1.d1",
* "root.sg1", "root" etc.
*
* <p>1) Pattern "root.sg1.d1.*" does not match prefix path "root.sg2", "root.sg1.d2".
*
* @param prefixPath
* @return true if a successful match, otherwise return false
*/
public boolean matchPrefixPath(PartialPath prefixPath) {
return matchPath(prefixPath.getNodes(), 0, 0, false, true);
}
private boolean matchPath(
String[] pathNodes,
int pathIndex,
int patternIndex,
boolean multiLevelWild,
boolean pathIsPrefix) {
if (pathIndex == pathNodes.length && patternIndex == nodes.length) {
return true;
} else if (patternIndex == nodes.length && multiLevelWild) {
return matchPath(pathNodes, pathIndex + 1, patternIndex, true, pathIsPrefix);
} else if (pathIndex >= pathNodes.length) {
return pathIsPrefix;
} else if (patternIndex >= nodes.length) {
return false;
}
String pathNode = pathNodes[pathIndex];
String patternNode = nodes[patternIndex];
boolean isMatch = false;
if (patternNode.equals(MULTI_LEVEL_PATH_WILDCARD)) {
isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, true, pathIsPrefix);
} else {
if (PathPatternUtil.hasWildcard(patternNode)) {
if (PathPatternUtil.isNodeMatch(patternNode, pathNode)) {
isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, false, pathIsPrefix);
}
} else {
if (patternNode.equals(pathNode)) {
isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex + 1, false, pathIsPrefix);
}
}
if (!isMatch && multiLevelWild) {
isMatch = matchPath(pathNodes, pathIndex + 1, patternIndex, true, pathIsPrefix);
}
}
return isMatch;
}
/**
* Test if current PartialPath matches a full path's prefix. Current partialPath acts as a prefix
* path pattern. rPath is supposed to be a full time-series path without wildcards. e.g. Current
* PartialPath "root.sg" or "root.*" can match rPath "root.sg.device.s1", "root.sg.device" or
* "root.sg.vehicle.s1".
*
* @param rPath a plain full path of a time-series
* @return true if a successful match, otherwise return false.
*/
public boolean prefixMatchFullPath(PartialPath rPath) {
String[] rNodes = rPath.getNodes();
if (this.nodes.length > rNodes.length) {
return false;
}
for (int i = 0; i < this.nodes.length; i++) {
if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
return true;
}
if (nodes[i].equals(ONE_LEVEL_PATH_WILDCARD)) {
continue;
}
if (PathPatternUtil.hasWildcard(nodes[i])) {
if (PathPatternUtil.isNodeMatch(nodes[i], rNodes[i])) {
continue;
} else {
return false;
}
}
if (!nodes[i].equals(rNodes[i])) {
return false;
}
}
return true;
}
/**
* Test if this path pattern includes input path pattern. e.g. "root.**" includes "root.sg.**",
* "root.*.d.s" includes "root.sg.d.s", "root.sg.**" does not include "root.**.s", "root.*.d.s"
* does not include "root.sg.d1.*"
*
* @param rPath a pattern path of a timeseries
* @return true if this path pattern includes input path pattern, otherwise return false
*/
public boolean include(PartialPath rPath) {
String[] rNodes = rPath.getNodes();
String[] lNodes = nodes.clone();
// Replace * with ** if they are adjacent to each other
for (int i = 1; i < lNodes.length; i++) {
if (MULTI_LEVEL_PATH_WILDCARD.equals(lNodes[i - 1])
&& ONE_LEVEL_PATH_WILDCARD.equals(lNodes[i])) {
lNodes[i] = MULTI_LEVEL_PATH_WILDCARD;
}
if (MULTI_LEVEL_PATH_WILDCARD.equals(lNodes[lNodes.length - i])
&& ONE_LEVEL_PATH_WILDCARD.equals(lNodes[lNodes.length - 1 - i])) {
lNodes[lNodes.length - 1 - i] = MULTI_LEVEL_PATH_WILDCARD;
}
}
// dp[i][j] means if nodes1[0:i) includes nodes[0:j)
// for example: "root.sg.**" includes "root.sg.d1.*"
// 1 0 0 0 0 |→| 1 0 0 0 0 |→| 1 0 0 0 0 |→| 1 0 0 0 0
// 0 0 0 0 0 |↓| 0 1 0 0 0 |→| 0 1 0 0 0 |→| 0 1 0 0 0
// 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 1 0 0 |→| 0 0 1 0 0
// 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 0 0 0 |↓| 0 0 0 1 1
// Since the derivation of the next row depends only on the previous row, the calculation can
// be performed using a one-dimensional array
boolean[] dp = new boolean[rNodes.length + 1];
dp[0] = true;
for (int i = 1; i <= lNodes.length; i++) {
boolean[] newDp = new boolean[rNodes.length + 1];
for (int j = i; j <= rNodes.length; j++) {
if (lNodes[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
// if encounter MULTI_LEVEL_PATH_WILDCARD
if (dp[j - 1]) {
for (int k = j; k <= rNodes.length; k++) {
newDp[k] = true;
}
break;
}
} else {
// if without MULTI_LEVEL_PATH_WILDCARD, scan and check
if (!rNodes[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)
&& ((PathPatternUtil.hasWildcard(lNodes[i - 1])
&& PathPatternUtil.isNodeMatch(lNodes[i - 1], rNodes[j - 1]))
|| lNodes[i - 1].equals(rNodes[j - 1]))) {
// if nodes1[i-1] includes rNodes[j-1], dp[i][j] = dp[i-1][j-1]
newDp[j] |= dp[j - 1];
}
}
}
dp = newDp;
}
return dp[rNodes.length];
}
/**
* Test if this path pattern overlaps with input path pattern. Overlap means the result sets
* generated by two path pattern share some common elements. e.g. "root.sg.**" overlaps with
* "root.**", "root.*.d.s" overlaps with "root.sg.d.s", "root.sg.**" overlaps with "root.**.s",
* "root.*.d.s" doesn't overlap with "root.sg.d1.*"
*
* @param rPath a pattern path of a timeseries
* @return true if overlapping otherwise return false
*/
public boolean overlapWith(PartialPath rPath) {
String[] rNodes = rPath.getNodes();
for (int i = 0; i < this.nodes.length && i < rNodes.length; i++) {
// if encounter MULTI_LEVEL_PATH_WILDCARD
if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)
|| rNodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
return checkOverlapWithMultiLevelWildcard(nodes, rNodes);
}
// if without MULTI_LEVEL_PATH_WILDCARD, scan and check
if ((PathPatternUtil.hasWildcard(nodes[i])
&& PathPatternUtil.isNodeMatch(nodes[i], rNodes[i]))
|| (PathPatternUtil.hasWildcard(rNodes[i])
&& PathPatternUtil.isNodeMatch(rNodes[i], nodes[i]))) {
continue;
}
if (!nodes[i].equals(rNodes[i])) {
return false;
}
}
return this.nodes.length == rNodes.length;
}
/**
* Try to check overlap between nodes1 and nodes2 with MULTI_LEVEL_PATH_WILDCARD. Time complexity
* O(n^2).
*
* @return true if overlapping, otherwise return false
*/
private boolean checkOverlapWithMultiLevelWildcard(String[] nodes1, String[] nodes2) {
// dp[i][j] means if nodes1[0:i) and nodes[0:j) overlapping
boolean[][] dp = new boolean[nodes1.length + 1][nodes2.length + 1];
dp[0][0] = true;
for (int i = 1; i <= nodes1.length; i++) {
boolean prune = false; //
// prune if we've already found that these two path are never overlapped in the beginning
// steps.
// e.g. root.db1.**.s1 and root.db2.**.s1
for (int j = 1; j <= nodes2.length; j++) {
if (nodes1[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)
|| nodes2[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
// if encounter MULTI_LEVEL_PATH_WILDCARD
if (nodes1[i - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
// if nodes1[i-1] is MULTI_LEVEL_PATH_WILDCARD, dp[i][k(k>=j)]=dp[i-1][j-1]
if (dp[i - 1][j - 1]) {
for (int k = j; k <= nodes2.length; k++) {
dp[i][k] = true;
}
}
}
if (nodes2[j - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
// if nodes2[j-1] is MULTI_LEVEL_PATH_WILDCARD, dp[k(k>=i)][j]=dp[i-1][j-1]
if (dp[i - 1][j - 1]) {
for (int k = i; k <= nodes1.length; k++) {
dp[k][j] = true;
}
}
}
} else {
// if without MULTI_LEVEL_PATH_WILDCARD, scan and check
if ((PathPatternUtil.hasWildcard(nodes1[i - 1])
&& PathPatternUtil.isNodeMatch(nodes1[i - 1], nodes2[j - 1]))
|| (PathPatternUtil.hasWildcard(nodes2[j - 1])
&& PathPatternUtil.isNodeMatch(nodes2[j - 1], nodes1[i - 1]))
|| nodes1[i - 1].equals(nodes2[j - 1])) {
// if nodes1[i-1] and nodes[j-1] is matched, dp[i][j] = dp[i-1][j-1]
dp[i][j] |= dp[i - 1][j - 1];
}
}
prune |= dp[i][j];
}
if (!prune) {
break;
}
}
return dp[nodes1.length][nodes2.length];
}
/**
* Test if this path pattern overlaps with input prefix full path. Overlap means the result sets
* generated by two path pattern share some common elements. e.g.
*
* <ul>
* <li>"root.sg.**" overlaps with prefix full path "root" because "root.sg.**" share some common
* element "root.sg.d" with "root.**"
* <li>"root.*.d*.s" overlaps with prefix full path "root.sg" because "root.*.d*.s" share some
* common element "root.sg.d1.s" with "root.sg.**"
* <li>"root.*.d.s" doesn't overlap with prefix full path "root.sg.d1" because there is no
* common element between "root.*.d.s" and "root.sg.d1.**"
* </ul>
*
* @param prefixFullPath prefix full path
* @return true if overlapping otherwise return false
*/
public boolean overlapWithFullPathPrefix(PartialPath prefixFullPath) {
String[] rNodes = prefixFullPath.getNodes();
int rNodesIndex = 0;
for (int i = 0; i < this.nodes.length && rNodesIndex < rNodes.length; i++) {
// if encounter MULTI_LEVEL_PATH_WILDCARD
if (nodes[i].equals(MULTI_LEVEL_PATH_WILDCARD)) {
return true;
} else if (nodes[i].equals(ONE_LEVEL_PATH_WILDCARD)) {
rNodesIndex++;
} else if (PathPatternUtil.hasWildcard(nodes[i])) {
if (!PathPatternUtil.isNodeMatch(nodes[i], rNodes[rNodesIndex])) {
return false;
} else {
rNodesIndex++;
}
} else if (nodes[i].equals(rNodes[rNodesIndex])) {
rNodesIndex++;
} else {
return false;
}
}
return true;
}
/**
* Generate a list of partial paths which are the intersection of this path pattern and input
* prefix pattern.
*
* @param prefixPattern must be a prefix full path ending with one "**", like "root.sg.**"
* @return a list of partial paths which are the intersection of this path pattern and input
* prefix pattern.
*/
public List<PartialPath> intersectWithPrefixPattern(PartialPath prefixPattern) {
if (prefixPattern.equals(ALL_MATCH_PATTERN)) {
return Collections.singletonList(this);
}
String[] prefixFullPath =
Arrays.copyOf(prefixPattern.getNodes(), prefixPattern.getNodeLength() - 1);
// init dp array
int thisLength = this.getNodeLength();
boolean[] matchIndex = new boolean[thisLength];
matchIndex[0] = true; // "root" must match "root"
// dp[i][j] means if nodes[0:i] matches prefixFullPath[0:j]
// for example: "root.**.d1.**" intersect "root.sg1.d1(.**)"
// dp[i][j] = (nodes[i]=="**"&&dp[i][j-1]) || (nodes[i] matches prefixFullPath[j]&&dp[i-1][j-1])
// 1 0 0 0 |→| 1 0 0 0 |→| 1 0 0 0
// 0 0 0 0 |↓| 0 1 0 0 |→| 0 1 0 0
// 0 0 0 0 |↓| 0 0 0 0 |↓| 0 1 1 0
// Since the derivation of the next row depends only on the previous row, the calculation can
// be performed using a one-dimensional array named "matchIndex"
for (int i = 1; i < prefixFullPath.length; i++) {
for (int j = thisLength - 1; j >= 1; j--) {
if (nodes[j].equals(MULTI_LEVEL_PATH_WILDCARD)) {
matchIndex[j] = matchIndex[j] || matchIndex[j - 1];
} else if (PathPatternUtil.isNodeMatch(nodes[j], prefixFullPath[i])) {
matchIndex[j] = matchIndex[j - 1];
} else {
matchIndex[j] = false;
}
}
}
// Scan in reverse order to construct the result set.
// The structure of the result set is prefixFullPath+remaining nodes. 【E.g.root.sg1.d1 + **】
// It can be pruned if the remaining nodes start with **.
List<PartialPath> res = new ArrayList<>();
if (matchIndex[thisLength - 1] && nodes[thisLength - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
res.add(new PartialPath(ArrayUtils.addAll(prefixFullPath, nodes[thisLength - 1])));
} else {
for (int j = thisLength - 2; j > 0; j--) {
if (matchIndex[j]) {
res.add(
new PartialPath(
ArrayUtils.addAll(prefixFullPath, Arrays.copyOfRange(nodes, j + 1, thisLength))));
if (nodes[j + 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
break;
}
if (nodes[j].equals(MULTI_LEVEL_PATH_WILDCARD)) {
res.add(
new PartialPath(
ArrayUtils.addAll(prefixFullPath, Arrays.copyOfRange(nodes, j, thisLength))));
break;
}
}
}
}
return res;
}
@Override
public String getFullPath() {
if (fullPath == null) {
StringBuilder s = new StringBuilder(nodes[0]);
for (int i = 1; i < nodes.length; i++) {
s.append(TsFileConstant.PATH_SEPARATOR).append(nodes[i]);
}
fullPath = s.toString();
}
return fullPath;
}
public PartialPath copy() {
PartialPath result = new PartialPath();
result.nodes = nodes;
result.fullPath = fullPath;
result.device = device;
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PartialPath)) {
return false;
}
String[] otherNodes = ((PartialPath) obj).getNodes();
if (this.nodes.length != otherNodes.length) {
return false;
} else {
for (int i = 0; i < this.nodes.length; i++) {
if (!nodes[i].equals(otherNodes[i])) {
return false;
}
}
}
return true;
}
@Override
public boolean equals(String obj) {
return this.getFullPath().equals(obj);
}
@Override
public int hashCode() {
int h = 0;
for (String node : nodes) {
h += 31 * h + node.hashCode();
}
return h;
}
@Override
public String getMeasurement() {
return nodes[nodes.length - 1];
}
public String getFirstNode() {
return nodes[0];
}
@Override
public String getDevice() {
if (device != null) {
return device;
} else {
if (nodes.length == 1) {
return "";
}
StringBuilder s = new StringBuilder(nodes[0]);
for (int i = 1; i < nodes.length - 1; i++) {
s.append(TsFileConstant.PATH_SEPARATOR);
s.append(nodes[i]);
}
device = s.toString();
}
return device;
}
// todo remove measurement related interface after invoker using MeasurementPath explicitly
public String getMeasurementAlias() {
throw new RuntimeException("Only MeasurementPath support alias");
}
public void setMeasurementAlias(String measurementAlias) {
throw new RuntimeException("Only MeasurementPath support alias");
}
public boolean isMeasurementAliasExists() {
return false;
}
@Override
public String getFullPathWithAlias() {
throw new RuntimeException("Only MeasurementPath support alias");
}
public IMeasurementSchema getMeasurementSchema() throws MetadataException {
throw new MetadataException("This path doesn't represent a measurement");
}
public TSDataType getSeriesType() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This path doesn't represent a measurement");
}
@Override
public int compareTo(Path path) {
PartialPath partialPath = (PartialPath) path;
return this.getFullPath().compareTo(partialPath.getFullPath());
}
public boolean startsWithOrPrefixOf(String[] otherNodes) {
for (int i = 0; i < otherNodes.length && i < nodes.length; i++) {
if (!nodes[i].equals(otherNodes[i])) {
return false;
}
}
return true;
}
public boolean startsWith(String prefix) {
return getFullPath().startsWith(prefix);
}
public boolean containNode(String otherNode) {
for (String node : nodes) {
if (node.equals(otherNode)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return getFullPath();
}
public PartialPath getDevicePath() {
return new PartialPath(Arrays.copyOf(nodes, nodes.length - 1));
}
public List<PartialPath> getDevicePathPattern() {
List<PartialPath> result = new ArrayList<>();
result.add(getDevicePath());
if (nodes[nodes.length - 1].equals(MULTI_LEVEL_PATH_WILDCARD)) {
result.add(new PartialPath(nodes));
}
return result;
}
@TestOnly
public Path toTSFilePath() {
return new Path(getDevice(), getMeasurement(), true);
}
public static List<String> toStringList(List<PartialPath> pathList) {
List<String> ret = new ArrayList<>();
for (PartialPath path : pathList) {
ret.add(path.getFullPath());
}
return ret;
}
/**
* Convert a list of Strings to a list of PartialPaths, ignoring all illegal paths
*
* @param pathList
* @return
*/
public static List<PartialPath> fromStringList(List<String> pathList) {
if (pathList == null || pathList.isEmpty()) {
return Collections.emptyList();
}
List<PartialPath> ret = new ArrayList<>();
for (String s : pathList) {
try {
ret.add(new PartialPath(s));
} catch (IllegalPathException e) {
logger.warn("Encountered an illegal path {}", s);
}
}
return ret;
}
@Override
public PartialPath clone() {
return new PartialPath(this.getNodes().clone());
}
public ByteBuffer serialize() throws IOException {
PublicBAOS publicBAOS = new PublicBAOS();
serialize((OutputStream) publicBAOS);
return ByteBuffer.wrap(publicBAOS.getBuf(), 0, publicBAOS.size());
}
@Override
public void serialize(OutputStream stream) throws IOException {
PathType.Partial.serialize(stream);
serializeWithoutType(stream);
}
@Override
public void serialize(ByteBuffer byteBuffer) {
PathType.Partial.serialize(byteBuffer);
serializeWithoutType(byteBuffer);
}
@Override
public void serialize(PublicBAOS stream) throws IOException {
PathType.Partial.serialize(stream);
serializeWithoutType(stream);
}
@Override
protected void serializeWithoutType(ByteBuffer byteBuffer) {
super.serializeWithoutType(byteBuffer);
ReadWriteIOUtils.write(nodes.length, byteBuffer);
for (String node : nodes) {
ReadWriteIOUtils.write(node, byteBuffer);
}
}
@Override
protected void serializeWithoutType(OutputStream stream) throws IOException {
super.serializeWithoutType(stream);
ReadWriteIOUtils.write(nodes.length, stream);
for (String node : nodes) {
ReadWriteIOUtils.write(node, stream);
}
}
// Attention!!! If you want to use serialize and deserialize of partialPath, must invoke
// PathDeserializeUtil.deserialize
public static PartialPath deserialize(ByteBuffer byteBuffer) {
Path path = Path.deserialize(byteBuffer);
PartialPath partialPath = new PartialPath();
int nodeSize = ReadWriteIOUtils.readInt(byteBuffer);
String[] nodes = new String[nodeSize];
for (int i = 0; i < nodeSize; i++) {
nodes[i] = ReadWriteIOUtils.readString(byteBuffer);
}
partialPath.nodes = nodes;
partialPath.setMeasurement(path.getMeasurement());
partialPath.device = path.getDevice();
partialPath.fullPath = path.getFullPath();
return partialPath;
}
public PartialPath transformToPartialPath() {
return this;
}
/**
* PartialPath basic total, 52B
*
* <ul>
* <li>Object header, 8B
* <li>String[] reference + header + length, 8 + 4 + 8= 20B
* <li>Path attributes' references, 8 * 3 = 24B
* </ul>
*/
public static int estimateSize(PartialPath partialPath) {
int size = 52;
for (String node : partialPath.getNodes()) {
size += estimateStringSize(node);
}
size += estimateStringSize(partialPath.getFullPath());
return size;
}
/**
* String basic total, 32B
*
* <ul>
* <li>Object header, 8B
* <li>char[] reference + header + length, 8 + 4 + 8= 20B
* <li>hash code, 4B
* </ul>
*/
private static int estimateStringSize(String string) {
// each char takes 2B in Java
return string == null ? 0 : 32 + 2 * string.length();
}
}