You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kyuubi.apache.org by ch...@apache.org on 2022/08/15 07:38:44 UTC
[incubator-kyuubi] branch master updated: [KYUUBI #3230] Flink SQL engine supports run across versions
This is an automated email from the ASF dual-hosted git repository.
chengpan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-kyuubi.git
The following commit(s) were added to refs/heads/master by this push:
new db0047d51 [KYUUBI #3230] Flink SQL engine supports run across versions
db0047d51 is described below
commit db0047d51c1d734c288635866934f2f316f2e5d0
Author: Cheng Pan <ch...@apache.org>
AuthorDate: Mon Aug 15 15:38:35 2022 +0800
[KYUUBI #3230] Flink SQL engine supports run across versions
### _Why are the changes needed?_
To make sure flink sql engine build against flink-1.15 can run on flink-1.14.
### _How was this patch tested?_
- [ ] Add some test cases that check the changes thoroughly including negative and positive cases if possible
- [ ] Add screenshots for manual tests if appropriate
- [x] [Run test](https://kyuubi.apache.org/docs/latest/develop_tools/testing.html#running-tests) locally before make a pull request
Closes #3230 from pan3793/flink.
Closes #3230
5c3f859c [Cheng Pan] updated
e776ee63 [Cheng Pan] review
a5cfb0c8 [Cheng Pan] review
004c05d9 [Cheng Pan] nit
9c019554 [Cheng Pan] nit
7c3f553c [Cheng Pan] nit
8e1fb04b [Cheng Pan] nit
6adc7dbe [Cheng Pan] nit
bc10739c [Cheng Pan] fix
cf98111a [Cheng Pan] workflow
a8895e68 [Cheng Pan] nit
8cab5188 [Cheng Pan] Flink SQL engine supports run across versions
Authored-by: Cheng Pan <ch...@apache.org>
Signed-off-by: Cheng Pan <ch...@apache.org>
---
.github/workflows/master.yml | 31 +-
.../plugin/spark/authz/util/SemanticVersion.scala | 2 +
.../spark/connector/common/SemanticVersion.scala | 2 +
.../kyuubi/engine/flink/FlinkEngineUtils.scala | 25 +-
.../engine/flink/operation/ExecuteStatement.scala | 35 +-
.../flink/operation/FlinkOperationSuite.scala | 40 +-
.../org/apache/kyuubi/reflection/DynClasses.java | 120 +++++
.../apache/kyuubi/reflection/DynConstructors.java | 306 ++++++++++++
.../org/apache/kyuubi/reflection/DynFields.java | 430 +++++++++++++++++
.../org/apache/kyuubi/reflection/DynMethods.java | 529 +++++++++++++++++++++
.../org/apache/kyuubi/engine/SemanticVersion.scala | 2 +
11 files changed, 1469 insertions(+), 53 deletions(-)
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index 0c6ed989b..8857d22d4 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -56,12 +56,12 @@ jobs:
spark: '3.2'
spark-archive: '-Dspark.archive.mirror=https://archive.apache.org/dist/spark/spark-3.1.3 -Dspark.archive.name=spark-3.1.3-bin-hadoop3.2.tgz'
exclude-tags: '-Dmaven.plugin.scalatest.exclude.tags=org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.IcebergTest'
- comment: 'verify-spark-3.1'
+ comment: 'verify-on-spark-3.1-binary'
- java: 8
spark: '3.2'
spark-archive: '-Dspark.archive.mirror=https://archive.apache.org/dist/spark/spark-3.3.0 -Dspark.archive.name=spark-3.3.0-bin-hadoop3.tgz'
exclude-tags: '-Dmaven.plugin.scalatest.exclude.tags=org.scalatest.tags.Slow,org.apache.kyuubi.tags.DeltaTest,org.apache.kyuubi.tags.HudiTest,org.apache.kyuubi.tags.IcebergTest'
- comment: 'verify-spark-3.3'
+ comment: 'verify-on-spark-3.3-binary'
env:
SPARK_LOCAL_IP: localhost
steps:
@@ -111,12 +111,11 @@ jobs:
- '1.15'
flink-archive: [ "" ]
comment: [ "normal" ]
- # FIXME: Cross Flink versions verification is not supported yet
- # include:
- # - java: 8
- # flink: '1.15'
- # flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.5 -Dflink.archive.name=flink-1.14.5-bin-scala_2.12.tgz'
- # comment: 'verify-flink-1.14'
+ include:
+ - java: 8
+ flink: '1.15'
+ flink-archive: '-Dflink.archive.mirror=https://archive.apache.org/dist/flink/flink-1.14.5 -Dflink.archive.name=flink-1.14.5-bin-scala_2.12.tgz'
+ comment: 'verify-on-flink-1.14-binary'
steps:
- uses: actions/checkout@v2
- name: Tune Runner VM
@@ -128,11 +127,21 @@ jobs:
java-version: ${{ matrix.java }}
cache: 'maven'
check-latest: false
- - name: Build and test Flink with maven w/o linters
- run: |
+ - name: Build Flink with maven w/o linters
+ run: |
+ TEST_MODULES="externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it"
+ ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} -am clean install -DskipTests
+ - name: Test Flink
+ if: matrix.flink-archive == ''
+ run: |
TEST_MODULES="externals/kyuubi-flink-sql-engine,integration-tests/kyuubi-flink-it"
- ./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} -am clean install -DskipTests
./build/mvn ${MVN_OPT} -pl ${TEST_MODULES} -Pflink-${{ matrix.flink }} ${{ matrix.flink-archive }} test
+ - name: Cross-version test Flink
+ if: matrix.flink-archive != ''
+ run: |
+ IT_FLINK=`echo "${{ matrix.flink-archive }}" | grep -E 'flink\-([0-9]+\.[0-9]+.[0-9]+)\-bin' -o | grep -E '[0-9]+\.[0-9]+' -o`
+ IT_MODULE="integration-tests/kyuubi-flink-it"
+ ./build/mvn ${MVN_OPT} -pl ${IT_MODULE} -Pflink-${IT_FLINK} ${{ matrix.flink-archive }} test
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v2
diff --git a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
index ab4f95836..4d7e89725 100644
--- a/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
+++ b/extensions/spark/kyuubi-spark-authz/src/main/scala/org/apache/kyuubi/plugin/spark/authz/util/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
val targetMinor = targetVersion.minorVersion
callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
}
+
+ override def toString: String = s"$majorVersion.$minorVersion"
}
object SemanticVersion {
diff --git a/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala b/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
index ee6936ebf..200937ca6 100644
--- a/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
+++ b/extensions/spark/kyuubi-spark-connector-common/src/main/scala/org/apache/kyuubi/spark/connector/common/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
val targetMinor = targetVersion.minorVersion
callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
}
+
+ override def toString: String = s"$majorVersion.$minorVersion"
}
object SemanticVersion {
diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
index a57442d42..a3f60192f 100644
--- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
+++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/FlinkEngineUtils.scala
@@ -39,18 +39,29 @@ object FlinkEngineUtils extends Logging {
val MODE_EMBEDDED = "embedded"
val EMBEDDED_MODE_CLIENT_OPTIONS: Options = getEmbeddedModeClientOptions(new Options);
+ val SUPPORTED_FLINK_VERSIONS: Array[SemanticVersion] =
+ Array("1.14", "1.15").map(SemanticVersion.apply)
+
def checkFlinkVersion(): Unit = {
val flinkVersion = EnvironmentInformation.getVersion
- SemanticVersion(flinkVersion) match {
- case SemanticVersion(1, 14 | 15) =>
- logger.info(s"The current Flink version is $flinkVersion")
- case _ =>
- throw new UnsupportedOperationException(
- s"The current Flink version is $flinkVersion, " +
- s"Only Flink 1.14.x and 1.15 are supported, not supported in other versions")
+ if (SUPPORTED_FLINK_VERSIONS.contains(SemanticVersion(flinkVersion))) {
+ info(s"The current Flink version is $flinkVersion")
+ } else {
+ throw new UnsupportedOperationException(
+ s"You are using unsupported Flink version $flinkVersion, " +
+ s"only Flink ${SUPPORTED_FLINK_VERSIONS.mkString(", ")} are supported now.")
}
}
+ def isFlinkVersionAtMost(targetVersionString: String): Boolean =
+ SemanticVersion(EnvironmentInformation.getVersion).isVersionAtMost(targetVersionString)
+
+ def isFlinkVersionAtLeast(targetVersionString: String): Boolean =
+ SemanticVersion(EnvironmentInformation.getVersion).isVersionAtLeast(targetVersionString)
+
+ def isFlinkVersionEqualTo(targetVersionString: String): Boolean =
+ SemanticVersion(EnvironmentInformation.getVersion).isVersionEqualTo(targetVersionString)
+
def parseCliOptions(args: Array[String]): CliOptions = {
val (mode, modeArgs) =
if (args.isEmpty || args(0).startsWith("-")) (MODE_EMBEDDED, args)
diff --git a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
index fe3c29f8d..47218276d 100644
--- a/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
+++ b/externals/kyuubi-flink-sql-engine/src/main/scala/org/apache/kyuubi/engine/flink/operation/ExecuteStatement.scala
@@ -34,10 +34,12 @@ import org.apache.flink.table.types.logical._
import org.apache.flink.types.Row
import org.apache.kyuubi.Logging
+import org.apache.kyuubi.engine.flink.FlinkEngineUtils._
import org.apache.kyuubi.engine.flink.result.ResultSet
import org.apache.kyuubi.engine.flink.schema.RowSet.toHiveString
import org.apache.kyuubi.operation.OperationState
import org.apache.kyuubi.operation.log.OperationLog
+import org.apache.kyuubi.reflection.DynMethods
import org.apache.kyuubi.session.Session
import org.apache.kyuubi.util.RowSetUtils
@@ -116,17 +118,16 @@ class ExecuteStatement(
(1 to result.getPayload).foreach { page =>
if (rows.size < resultMaxRows) {
// FLINK-24461 retrieveResultPage method changes the return type from Row to RowData
- val result = executor.retrieveResultPage(resultId, page).asScala.toList
- result.headOption match {
- case None =>
- case Some(r) =>
- // for flink 1.14
- if (r.getClass == classOf[Row]) {
- rows ++= result.asInstanceOf[List[Row]]
- } else {
- // for flink 1.15+
- rows ++= result.map(r => convertToRow(r.asInstanceOf[RowData], dataTypes))
- }
+ val retrieveResultPage = DynMethods.builder("retrieveResultPage")
+ .impl(executor.getClass, classOf[String], classOf[Int])
+ .build(executor)
+ val _page = Integer.valueOf(page)
+ if (isFlinkVersionEqualTo("1.14")) {
+ val result = retrieveResultPage.invoke[util.List[Row]](resultId, _page)
+ rows ++= result.asScala
+ } else if (isFlinkVersionAtLeast("1.15")) {
+ val result = retrieveResultPage.invoke[util.List[RowData]](resultId, _page)
+ rows ++= result.asScala.map(r => convertToRow(r, dataTypes))
}
} else {
loop = false
@@ -216,7 +217,7 @@ class ExecuteStatement(
case d: BinaryMapData =>
val kvArray = toArray(keyType, valueType, d)
val map: util.Map[Any, Any] = new util.HashMap[Any, Any]
- for (i <- 0 until kvArray._1.length) {
+ for (i <- kvArray._1.indices) {
val value: Any = kvArray._2(i)
map.put(kvArray._1(i), value)
}
@@ -229,11 +230,13 @@ class ExecuteStatement(
case _: DoubleType =>
row.setField(i, r.getDouble(i))
case t: RowType =>
- val clazz = Class.forName("org.apache.flink.table.types.DataType")
- val fieldDataTypes = clazz.getDeclaredMethod("getFieldDataTypes", classOf[DataType])
- .invoke(null, dataType).asInstanceOf[java.util.List[DataType]]
+ val fieldDataTypes = DynMethods.builder("getFieldDataTypes")
+ .impl(classOf[DataType], classOf[DataType])
+ .buildStatic
+ .invoke[util.List[DataType]](dataType)
+ .asScala.toList
val internalRowData = r.getRow(i, t.getFieldCount)
- val internalRow = convertToRow(internalRowData, fieldDataTypes.asScala.toList)
+ val internalRow = convertToRow(internalRowData, fieldDataTypes)
row.setField(i, internalRow)
case t =>
val hiveString = toHiveString((row.getField(i), t))
diff --git a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
index 7dd77b3dd..c402d5cd5 100644
--- a/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
+++ b/externals/kyuubi-flink-sql-engine/src/test/scala/org/apache/kyuubi/engine/flink/operation/FlinkOperationSuite.scala
@@ -23,7 +23,6 @@ import java.util.UUID
import scala.collection.JavaConverters._
-import org.apache.flink.runtime.util.EnvironmentInformation
import org.apache.flink.table.types.logical.LogicalTypeRoot
import org.apache.hive.service.rpc.thrift._
import org.scalatest.concurrent.PatienceConfiguration.Timeout
@@ -31,7 +30,7 @@ import org.scalatest.time.SpanSugar._
import org.apache.kyuubi.config.KyuubiConf._
import org.apache.kyuubi.config.KyuubiConf.OperationModes.NONE
-import org.apache.kyuubi.engine.SemanticVersion
+import org.apache.kyuubi.engine.flink.FlinkEngineUtils._
import org.apache.kyuubi.engine.flink.WithFlinkSQLEngine
import org.apache.kyuubi.engine.flink.result.Constants
import org.apache.kyuubi.engine.flink.util.TestUserClassLoaderJar
@@ -46,8 +45,6 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
override protected def jdbcUrl: String =
s"jdbc:hive2://${engine.frontendServices.head.connectionUrl}/;"
- val runtimeVersion = SemanticVersion(EnvironmentInformation.getVersion)
-
ignore("release session if shared level is CONNECTION") {
logger.info(s"jdbc url is $jdbcUrl")
assert(engine.getServiceState == STARTED)
@@ -579,10 +576,13 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
val metaData = resultSet.getMetaData
assert(metaData.getColumnType(1) === java.sql.Types.ARRAY)
assert(resultSet.next())
- runtimeVersion.minorVersion match {
- case 14 =>
- assert(resultSet.getObject(1).toString == """["v1","v2","v3"]""")
- case _ => assert(resultSet.getObject(1).toString == "[v1,v2,v3]")
+ if (isFlinkVersionEqualTo("1.14")) {
+ val expected = """["v1","v2","v3"]"""
+ assert(resultSet.getObject(1).toString == expected)
+ }
+ if (isFlinkVersionAtLeast("1.15")) {
+ val expected = "[v1,v2,v3]"
+ assert(resultSet.getObject(1).toString == expected)
}
}
}
@@ -603,12 +603,13 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
val resultSet =
statement.executeQuery("select (1, '2', true)")
assert(resultSet.next())
- runtimeVersion.minorVersion match {
- case 14 => assert(resultSet.getString(
- 1) == "{INT NOT NULL:1,CHAR(1) NOT NULL:\"2\",BOOLEAN NOT NULL:true}")
- case _ =>
- assert(
- resultSet.getString(1) == "{INT NOT NULL:1,CHAR(1) NOT NULL:2,BOOLEAN NOT NULL:true}")
+ if (isFlinkVersionEqualTo("1.14")) {
+ val expected = """{INT NOT NULL:1,CHAR(1) NOT NULL:"2",BOOLEAN NOT NULL:true}"""
+ assert(resultSet.getString(1) == expected)
+ }
+ if (isFlinkVersionAtLeast("1.15")) {
+ val expected = """{INT NOT NULL:1,CHAR(1) NOT NULL:2,BOOLEAN NOT NULL:true}"""
+ assert(resultSet.getString(1) == expected)
}
val metaData = resultSet.getMetaData
assert(metaData.getColumnType(1) === java.sql.Types.STRUCT)
@@ -619,11 +620,12 @@ class FlinkOperationSuite extends WithFlinkSQLEngine with HiveJDBCTestHelper {
withJdbcStatement() { statement =>
val resultSet = statement.executeQuery("select encode('kyuubi', 'UTF-8')")
assert(resultSet.next())
- runtimeVersion.minorVersion match {
- case 14 => assert(resultSet.getString(1) == "kyuubi")
- case _ =>
- // TODO: validate table results after FLINK-28882 is resolved
- assert(resultSet.getString(1) == "k")
+ if (isFlinkVersionEqualTo("1.14")) {
+ assert(resultSet.getString(1) == "kyuubi")
+ }
+ if (isFlinkVersionAtLeast("1.15")) {
+ // TODO: validate table results after FLINK-28882 is resolved
+ assert(resultSet.getString(1) == "k")
}
val metaData = resultSet.getMetaData
assert(metaData.getColumnType(1) === java.sql.Types.BINARY)
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java
new file mode 100644
index 000000000..05661e6a6
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynClasses.java
@@ -0,0 +1,120 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/** Copied from iceberg-common */
+public class DynClasses {
+
+ private DynClasses() {}
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ private Class<?> foundClass = null;
+ private boolean nullOk = false;
+ private Set<String> classNames = new LinkedHashSet<>();
+
+ private Builder() {}
+
+ /**
+ * Set the {@link ClassLoader} used to lookup classes by name.
+ *
+ * <p>If not set, the current thread's ClassLoader is used.
+ *
+ * @param newLoader a ClassLoader
+ * @return this Builder for method chaining
+ */
+ public Builder loader(ClassLoader newLoader) {
+ this.loader = newLoader;
+ return this;
+ }
+
+ /**
+ * Checks for an implementation of the class by name.
+ *
+ * @param className name of a class
+ * @return this Builder for method chaining
+ */
+ public Builder impl(String className) {
+ classNames.add(className);
+
+ if (foundClass != null) {
+ return this;
+ }
+
+ try {
+ this.foundClass = Class.forName(className, true, loader);
+ } catch (ClassNotFoundException e) {
+ // not the right implementation
+ }
+
+ return this;
+ }
+
+ /**
+ * Instructs this builder to return null if no class is found, rather than throwing an
+ * Exception.
+ *
+ * @return this Builder for method chaining
+ */
+ public Builder orNull() {
+ this.nullOk = true;
+ return this;
+ }
+
+ /**
+ * Returns the first implementation or throws ClassNotFoundException if one was not found.
+ *
+ * @param <S> Java superclass
+ * @return a {@link Class} for the first implementation found
+ * @throws ClassNotFoundException if no implementation was found
+ */
+ @SuppressWarnings("unchecked")
+ public <S> Class<? extends S> buildChecked() throws ClassNotFoundException {
+ if (!nullOk && foundClass == null) {
+ throw new ClassNotFoundException(
+ "Cannot find class; alternatives: " + String.join(", ", classNames));
+ }
+ return (Class<? extends S>) foundClass;
+ }
+
+ /**
+ * Returns the first implementation or throws RuntimeException if one was not found.
+ *
+ * @param <S> Java superclass
+ * @return a {@link Class} for the first implementation found
+ * @throws RuntimeException if no implementation was found
+ */
+ @SuppressWarnings("unchecked")
+ public <S> Class<? extends S> build() {
+ if (!nullOk && foundClass == null) {
+ throw new RuntimeException(
+ "Cannot find class; alternatives: " + String.join(", ", classNames));
+ }
+ return (Class<? extends S>) foundClass;
+ }
+ }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java
new file mode 100644
index 000000000..7495ce0ff
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynConstructors.java
@@ -0,0 +1,306 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Copied from parquet-common */
+public class DynConstructors {
+
+ private DynConstructors() {}
+
+ public static class Ctor<C> extends DynMethods.UnboundMethod {
+ private final Constructor<C> ctor;
+ private final Class<? extends C> constructed;
+
+ private Ctor(Constructor<C> constructor, Class<? extends C> constructed) {
+ super(null, "newInstance");
+ this.ctor = constructor;
+ this.constructed = constructed;
+ }
+
+ public Class<? extends C> getConstructedClass() {
+ return constructed;
+ }
+
+ public C newInstanceChecked(Object... args) throws Exception {
+ try {
+ if (args.length > ctor.getParameterCount()) {
+ return ctor.newInstance(Arrays.copyOfRange(args, 0, ctor.getParameterCount()));
+ } else {
+ return ctor.newInstance(args);
+ }
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw e;
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof Exception) {
+ throw (Exception) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+ public C newInstance(Object... args) {
+ try {
+ return newInstanceChecked(args);
+ } catch (Exception e) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <R> R invoke(Object target, Object... args) {
+ if (target != null) {
+ throw new IllegalArgumentException("Invalid call to constructor: target must be null");
+ }
+ return (R) newInstance(args);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <R> R invokeChecked(Object target, Object... args) throws Exception {
+ if (target != null) {
+ throw new IllegalArgumentException("Invalid call to constructor: target must be null");
+ }
+ return (R) newInstanceChecked(args);
+ }
+
+ @Override
+ public DynMethods.BoundMethod bind(Object receiver) {
+ throw new IllegalStateException("Cannot bind constructors");
+ }
+
+ @Override
+ public boolean isStatic() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "(constructor=" + ctor + ", class=" + constructed + ")";
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static Builder builder(Class<?> baseClass) {
+ return new Builder(baseClass);
+ }
+
+ public static class Builder {
+ private final Class<?> baseClass;
+ private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ private Ctor ctor = null;
+ private Map<String, Throwable> problems = new HashMap<>();
+
+ public Builder(Class<?> baseClass) {
+ this.baseClass = baseClass;
+ }
+
+ public Builder() {
+ this.baseClass = null;
+ }
+
+ /**
+ * Set the {@link ClassLoader} used to lookup classes by name.
+ *
+ * <p>If not set, the current thread's ClassLoader is used.
+ *
+ * @param newLoader a ClassLoader
+ * @return this Builder for method chaining
+ */
+ public Builder loader(ClassLoader newLoader) {
+ this.loader = newLoader;
+ return this;
+ }
+
+ public Builder impl(String className, Class<?>... types) {
+ // don't do any work if an implementation has been found
+ if (ctor != null) {
+ return this;
+ }
+
+ try {
+ Class<?> targetClass = Class.forName(className, true, loader);
+ impl(targetClass, types);
+ } catch (NoClassDefFoundError | ClassNotFoundException e) {
+ // cannot load this implementation
+ problems.put(className, e);
+ }
+ return this;
+ }
+
+ public <T> Builder impl(Class<T> targetClass, Class<?>... types) {
+ // don't do any work if an implementation has been found
+ if (ctor != null) {
+ return this;
+ }
+
+ try {
+ ctor = new Ctor<T>(targetClass.getConstructor(types), targetClass);
+ } catch (NoSuchMethodException e) {
+ // not the right implementation
+ problems.put(methodName(targetClass, types), e);
+ }
+ return this;
+ }
+
+ public Builder hiddenImpl(Class<?>... types) {
+ hiddenImpl(baseClass, types);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Builder hiddenImpl(String className, Class<?>... types) {
+ // don't do any work if an implementation has been found
+ if (ctor != null) {
+ return this;
+ }
+
+ try {
+ Class targetClass = Class.forName(className, true, loader);
+ hiddenImpl(targetClass, types);
+ } catch (NoClassDefFoundError | ClassNotFoundException e) {
+ // cannot load this implementation
+ problems.put(className, e);
+ }
+ return this;
+ }
+
+ public <T> Builder hiddenImpl(Class<T> targetClass, Class<?>... types) {
+ // don't do any work if an implementation has been found
+ if (ctor != null) {
+ return this;
+ }
+
+ try {
+ Constructor<T> hidden = targetClass.getDeclaredConstructor(types);
+ AccessController.doPrivileged(new MakeAccessible(hidden));
+ ctor = new Ctor<T>(hidden, targetClass);
+ } catch (SecurityException e) {
+ // unusable
+ problems.put(methodName(targetClass, types), e);
+ } catch (NoSuchMethodException e) {
+ // not the right implementation
+ problems.put(methodName(targetClass, types), e);
+ }
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <C> Ctor<C> buildChecked() throws NoSuchMethodException {
+ if (ctor != null) {
+ return ctor;
+ }
+ throw buildCheckedException(baseClass, problems);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <C> Ctor<C> build() {
+ if (ctor != null) {
+ return ctor;
+ }
+ throw buildRuntimeException(baseClass, problems);
+ }
+ }
+
+ private static class MakeAccessible implements PrivilegedAction<Void> {
+ private Constructor<?> hidden;
+
+ MakeAccessible(Constructor<?> hidden) {
+ this.hidden = hidden;
+ }
+
+ @Override
+ public Void run() {
+ hidden.setAccessible(true);
+ return null;
+ }
+ }
+
+ private static NoSuchMethodException buildCheckedException(
+ Class<?> baseClass, Map<String, Throwable> problems) {
+ NoSuchMethodException exc =
+ new NoSuchMethodException(
+ "Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
+ problems.values().forEach(exc::addSuppressed);
+ return exc;
+ }
+
+ private static RuntimeException buildRuntimeException(
+ Class<?> baseClass, Map<String, Throwable> problems) {
+ RuntimeException exc =
+ new RuntimeException(
+ "Cannot find constructor for " + baseClass + "\n" + formatProblems(problems));
+ problems.values().forEach(exc::addSuppressed);
+ return exc;
+ }
+
+ private static String formatProblems(Map<String, Throwable> problems) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (Map.Entry<String, Throwable> problem : problems.entrySet()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append("\n");
+ }
+ sb.append("\tMissing ")
+ .append(problem.getKey())
+ .append(" [")
+ .append(problem.getValue().getClass().getName())
+ .append(": ")
+ .append(problem.getValue().getMessage())
+ .append("]");
+ }
+ return sb.toString();
+ }
+
+ private static String methodName(Class<?> targetClass, Class<?>... types) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(targetClass.getName()).append("(");
+ boolean first = true;
+ for (Class<?> type : types) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(type.getName());
+ }
+ sb.append(")");
+ return sb.toString();
+ }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java
new file mode 100644
index 000000000..39c83b162
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynFields.java
@@ -0,0 +1,430 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+/** Copied from iceberg-common */
+public class DynFields {
+
+ private DynFields() {}
+
+ /**
+ * Convenience wrapper class around {@link Field}.
+ *
+ * <p>Allows callers to invoke the wrapped method with all Exceptions wrapped by RuntimeException,
+ * or with a single Exception catch block.
+ */
+ public static class UnboundField<T> {
+ private final Field field;
+ private final String name;
+
+ private UnboundField(Field field, String name) {
+ this.field = field;
+ this.name = name;
+ }
+
+ @SuppressWarnings("unchecked")
+ public T get(Object target) {
+ try {
+ return (T) field.get(target);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void set(Object target, T value) {
+ try {
+ field.set(target, value);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("class", field.getDeclaringClass().toString())
+ .append("name", name)
+ .append("type", field.getType())
+ .toString();
+ }
+
+ /**
+ * Returns this method as a BoundMethod for the given receiver.
+ *
+ * @param target an Object on which to get or set this field
+ * @return a {@link BoundField} for this field and the target
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ */
+ public BoundField<T> bind(Object target) {
+ if (isStatic() && this != AlwaysNull.INSTANCE) {
+ throw new IllegalStateException("Cannot bind static field " + name);
+ }
+ if (!field.getDeclaringClass().isAssignableFrom(target.getClass())) {
+ throw new IllegalArgumentException(
+ "Cannot bind field " + name + " to instance of " + target.getClass());
+ }
+
+ return new BoundField<>(this, target);
+ }
+
+ /**
+ * Returns this field as a StaticField.
+ *
+ * @return a {@link StaticField} for this field
+ * @throws IllegalStateException if the method is not static
+ */
+ public StaticField<T> asStatic() {
+ if (!isStatic()) {
+ throw new IllegalStateException("Field " + name + " is not static");
+ }
+ return new StaticField<>(this);
+ }
+
+ /** Returns whether the field is a static field. */
+ public boolean isStatic() {
+ return Modifier.isStatic(field.getModifiers());
+ }
+
+ /** Returns whether the field is always null. */
+ public boolean isAlwaysNull() {
+ return this == AlwaysNull.INSTANCE;
+ }
+ }
+
+ private static class AlwaysNull extends UnboundField<Void> {
+ private static final AlwaysNull INSTANCE = new AlwaysNull();
+
+ private AlwaysNull() {
+ super(null, "AlwaysNull");
+ }
+
+ @Override
+ public Void get(Object target) {
+ return null;
+ }
+
+ @Override
+ public void set(Object target, Void value) {}
+
+ @Override
+ public String toString() {
+ return "Field(AlwaysNull)";
+ }
+
+ @Override
+ public boolean isStatic() {
+ return true;
+ }
+
+ @Override
+ public boolean isAlwaysNull() {
+ return true;
+ }
+ }
+
+ public static class StaticField<T> {
+ private final UnboundField<T> field;
+
+ private StaticField(UnboundField<T> field) {
+ this.field = field;
+ }
+
+ public T get() {
+ return field.get(null);
+ }
+
+ public void set(T value) {
+ field.set(null, value);
+ }
+ }
+
+ public static class BoundField<T> {
+ private final UnboundField<T> field;
+ private final Object target;
+
+ private BoundField(UnboundField<T> field, Object target) {
+ this.field = field;
+ this.target = target;
+ }
+
+ public T get() {
+ return field.get(target);
+ }
+
+ public void set(T value) {
+ field.set(target, value);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ private UnboundField<?> field = null;
+ private final Set<String> candidates = new HashSet<>();
+ private boolean defaultAlwaysNull = false;
+
+ private Builder() {}
+
+ /**
+ * Set the {@link ClassLoader} used to lookup classes by name.
+ *
+ * <p>If not set, the current thread's ClassLoader is used.
+ *
+ * @param newLoader a ClassLoader
+ * @return this Builder for method chaining
+ */
+ public Builder loader(ClassLoader newLoader) {
+ this.loader = newLoader;
+ return this;
+ }
+
+ /**
+ * Instructs this builder to return AlwaysNull if no implementation is found.
+ *
+ * @return this Builder for method chaining
+ */
+ public Builder defaultAlwaysNull() {
+ this.defaultAlwaysNull = true;
+ return this;
+ }
+
+ /**
+ * Checks for an implementation, first finding the class by name.
+ *
+ * @param className name of a class
+ * @param fieldName name of the field
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getField(String)
+ */
+ public Builder impl(String className, String fieldName) {
+ // don't do any work if an implementation has been found
+ if (field != null) {
+ return this;
+ }
+
+ try {
+ Class<?> targetClass = Class.forName(className, true, loader);
+ impl(targetClass, fieldName);
+ } catch (ClassNotFoundException e) {
+ // not the right implementation
+ candidates.add(className + "." + fieldName);
+ }
+ return this;
+ }
+
+ /**
+ * Checks for an implementation.
+ *
+ * @param targetClass a class instance
+ * @param fieldName name of a field (different from constructor)
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getField(String)
+ */
+ public Builder impl(Class<?> targetClass, String fieldName) {
+ // don't do any work if an implementation has been found
+ if (field != null || targetClass == null) {
+ return this;
+ }
+
+ try {
+ this.field = new UnboundField<>(targetClass.getField(fieldName), fieldName);
+ } catch (NoSuchFieldException e) {
+ // not the right implementation
+ candidates.add(targetClass.getName() + "." + fieldName);
+ }
+ return this;
+ }
+
+ /**
+ * Checks for a hidden implementation, first finding the class by name.
+ *
+ * @param className name of a class
+ * @param fieldName name of a field (different from constructor)
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getField(String)
+ */
+ public Builder hiddenImpl(String className, String fieldName) {
+ // don't do any work if an implementation has been found
+ if (field != null) {
+ return this;
+ }
+
+ try {
+ Class<?> targetClass = Class.forName(className, true, loader);
+ hiddenImpl(targetClass, fieldName);
+ } catch (ClassNotFoundException e) {
+ // not the right implementation
+ candidates.add(className + "." + fieldName);
+ }
+ return this;
+ }
+
+ /**
+ * Checks for a hidden implementation.
+ *
+ * @param targetClass a class instance
+ * @param fieldName name of a field (different from constructor)
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getField(String)
+ */
+ public Builder hiddenImpl(Class<?> targetClass, String fieldName) {
+ // don't do any work if an implementation has been found
+ if (field != null || targetClass == null) {
+ return this;
+ }
+
+ try {
+ Field hidden = targetClass.getDeclaredField(fieldName);
+ AccessController.doPrivileged(new MakeFieldAccessible(hidden));
+ this.field = new UnboundField(hidden, fieldName);
+ } catch (SecurityException | NoSuchFieldException e) {
+ // unusable
+ candidates.add(targetClass.getName() + "." + fieldName);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the first valid implementation as a UnboundField or throws a NoSuchFieldException if
+ * there is none.
+ *
+ * @param <T> Java class stored in the field
+ * @return a {@link UnboundField} with a valid implementation
+ * @throws NoSuchFieldException if no implementation was found
+ */
+ @SuppressWarnings("unchecked")
+ public <T> UnboundField<T> buildChecked() throws NoSuchFieldException {
+ if (field != null) {
+ return (UnboundField<T>) field;
+ } else if (defaultAlwaysNull) {
+ return (UnboundField<T>) AlwaysNull.INSTANCE;
+ } else {
+ throw new NoSuchFieldException(
+ "Cannot find field from candidates: " + String.join(", ", candidates));
+ }
+ }
+
+ /**
+ * Returns the first valid implementation as a BoundMethod or throws a NoSuchMethodException if
+ * there is none.
+ *
+ * @param target an Object on which to get and set the field
+ * @param <T> Java class stored in the field
+ * @return a {@link BoundField} with a valid implementation and target
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ * @throws NoSuchFieldException if no implementation was found
+ */
+ public <T> BoundField<T> buildChecked(Object target) throws NoSuchFieldException {
+ return this.<T>buildChecked().bind(target);
+ }
+
+ /**
+ * Returns the first valid implementation as a UnboundField or throws a NoSuchFieldException if
+ * there is none.
+ *
+ * @param <T> Java class stored in the field
+ * @return a {@link UnboundField} with a valid implementation
+ * @throws RuntimeException if no implementation was found
+ */
+ @SuppressWarnings("unchecked")
+ public <T> UnboundField<T> build() {
+ if (field != null) {
+ return (UnboundField<T>) field;
+ } else if (defaultAlwaysNull) {
+ return (UnboundField<T>) AlwaysNull.INSTANCE;
+ } else {
+ throw new RuntimeException(
+ "Cannot find field from candidates: " + String.join(", ", candidates));
+ }
+ }
+
+ /**
+ * Returns the first valid implementation as a BoundMethod or throws a RuntimeException if there
+ * is none.
+ *
+ * @param target an Object on which to get and set the field
+ * @param <T> Java class stored in the field
+ * @return a {@link BoundField} with a valid implementation and target
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ * @throws RuntimeException if no implementation was found
+ */
+ public <T> BoundField<T> build(Object target) {
+ return this.<T>build().bind(target);
+ }
+
+ /**
+ * Returns the first valid implementation as a StaticField or throws a NoSuchFieldException if
+ * there is none.
+ *
+ * @param <T> Java class stored in the field
+ * @return a {@link StaticField} with a valid implementation
+ * @throws IllegalStateException if the method is not static
+ * @throws NoSuchFieldException if no implementation was found
+ */
+ public <T> StaticField<T> buildStaticChecked() throws NoSuchFieldException {
+ return this.<T>buildChecked().asStatic();
+ }
+
+ /**
+ * Returns the first valid implementation as a StaticField or throws a RuntimeException if there
+ * is none.
+ *
+ * @param <T> Java class stored in the field
+ * @return a {@link StaticField} with a valid implementation
+ * @throws IllegalStateException if the method is not static
+ * @throws RuntimeException if no implementation was found
+ */
+ public <T> StaticField<T> buildStatic() {
+ return this.<T>build().asStatic();
+ }
+ }
+
+ private static class MakeFieldAccessible implements PrivilegedAction<Void> {
+ private Field hidden;
+
+ MakeFieldAccessible(Field hidden) {
+ this.hidden = hidden;
+ }
+
+ @Override
+ public Void run() {
+ hidden.setAccessible(true);
+ return null;
+ }
+ }
+}
diff --git a/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java
new file mode 100644
index 000000000..85264191c
--- /dev/null
+++ b/kyuubi-common/src/main/java/org/apache/kyuubi/reflection/DynMethods.java
@@ -0,0 +1,529 @@
+/*
+ * 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.kyuubi.reflection;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+
+/** Copied from parquet-common */
+public class DynMethods {
+
+ private DynMethods() {}
+
+ /**
+ * Convenience wrapper class around {@link Method}.
+ *
+ * <p>Allows callers to invoke the wrapped method with all Exceptions wrapped by RuntimeException,
+ * or with a single Exception catch block.
+ */
+ public static class UnboundMethod {
+
+ private final Method method;
+ private final String name;
+ private final int argLength;
+
+ UnboundMethod(Method method, String name) {
+ this.method = method;
+ this.name = name;
+ this.argLength =
+ (method == null || method.isVarArgs()) ? -1 : method.getParameterTypes().length;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <R> R invokeChecked(Object target, Object... args) throws Exception {
+ try {
+ if (argLength < 0) {
+ return (R) method.invoke(target, args);
+ } else {
+ return (R) method.invoke(target, Arrays.copyOfRange(args, 0, argLength));
+ }
+
+ } catch (InvocationTargetException e) {
+ if (e.getCause() instanceof Exception) {
+ throw (Exception) e.getCause();
+ }
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+ public <R> R invoke(Object target, Object... args) {
+ try {
+ return this.invokeChecked(target, args);
+ } catch (Exception e) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new RuntimeException(e.getCause());
+ }
+ }
+
+ /**
+ * Returns this method as a BoundMethod for the given receiver.
+ *
+ * @param receiver an Object to receive the method invocation
+ * @return a {@link BoundMethod} for this method and the receiver
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ */
+ public BoundMethod bind(Object receiver) {
+ if (isStatic()) {
+ throw new IllegalStateException("Cannot bind static method " + method.toGenericString());
+ }
+ if (!method.getDeclaringClass().isAssignableFrom(receiver.getClass())) {
+ throw new IllegalArgumentException(
+ "Cannot bind" + method.toGenericString() + "to instance of " + receiver.getClass());
+ }
+ return new BoundMethod(this, receiver);
+ }
+
+ /** Returns whether the method is a static method. */
+ public boolean isStatic() {
+ return Modifier.isStatic(method.getModifiers());
+ }
+
+ /** Returns whether the method is a noop. */
+ public boolean isNoop() {
+ return this == NOOP;
+ }
+
+ /**
+ * Returns this method as a StaticMethod.
+ *
+ * @return a {@link StaticMethod} for this method
+ * @throws IllegalStateException if the method is not static
+ */
+ public StaticMethod asStatic() {
+ if (!isStatic()) {
+ throw new IllegalStateException("Method is not static");
+ }
+ return new StaticMethod(this);
+ }
+
+ @Override
+ public String toString() {
+ return "DynMethods.UnboundMethod(name=" + name + " method=" + method.toGenericString() + ")";
+ }
+
+ /** Singleton {@link UnboundMethod}, performs no operation and returns null. */
+ private static final UnboundMethod NOOP =
+ new UnboundMethod(null, "NOOP") {
+ @Override
+ public <R> R invokeChecked(Object target, Object... args) throws Exception {
+ return null;
+ }
+
+ @Override
+ public BoundMethod bind(Object receiver) {
+ return new BoundMethod(this, receiver);
+ }
+
+ @Override
+ public StaticMethod asStatic() {
+ return new StaticMethod(this);
+ }
+
+ @Override
+ public boolean isStatic() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "DynMethods.UnboundMethod(NOOP)";
+ }
+ };
+ }
+
+ public static class BoundMethod {
+ private final UnboundMethod method;
+ private final Object receiver;
+
+ private BoundMethod(UnboundMethod method, Object receiver) {
+ this.method = method;
+ this.receiver = receiver;
+ }
+
+ public <R> R invokeChecked(Object... args) throws Exception {
+ return method.invokeChecked(receiver, args);
+ }
+
+ public <R> R invoke(Object... args) {
+ return method.invoke(receiver, args);
+ }
+ }
+
+ public static class StaticMethod {
+ private final UnboundMethod method;
+
+ private StaticMethod(UnboundMethod method) {
+ this.method = method;
+ }
+
+ public <R> R invokeChecked(Object... args) throws Exception {
+ return method.invokeChecked(null, args);
+ }
+
+ public <R> R invoke(Object... args) {
+ return method.invoke(null, args);
+ }
+ }
+
+ /**
+ * Constructs a new builder for calling methods dynamically.
+ *
+ * @param methodName name of the method the builder will locate
+ * @return a Builder for finding a method
+ */
+ public static Builder builder(String methodName) {
+ return new Builder(methodName);
+ }
+
+ public static class Builder {
+ private final String name;
+ private ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ private UnboundMethod method = null;
+
+ public Builder(String methodName) {
+ this.name = methodName;
+ }
+
+ /**
+ * Set the {@link ClassLoader} used to lookup classes by name.
+ *
+ * <p>If not set, the current thread's ClassLoader is used.
+ *
+ * @param newLoader a ClassLoader
+ * @return this Builder for method chaining
+ */
+ public Builder loader(ClassLoader newLoader) {
+ this.loader = newLoader;
+ return this;
+ }
+
+ /**
+ * If no implementation has been found, adds a NOOP method.
+ *
+ * <p>Note: calls to impl will not match after this method is called!
+ *
+ * @return this Builder for method chaining
+ */
+ public Builder orNoop() {
+ if (method == null) {
+ this.method = UnboundMethod.NOOP;
+ }
+ return this;
+ }
+
+ /**
+ * Checks for an implementation, first finding the given class by name.
+ *
+ * @param className name of a class
+ * @param methodName name of a method (different from constructor)
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder impl(String className, String methodName, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ Class<?> targetClass = Class.forName(className, true, loader);
+ impl(targetClass, methodName, argClasses);
+ } catch (ClassNotFoundException e) {
+ // not the right implementation
+ }
+ return this;
+ }
+
+ /**
+ * Checks for an implementation, first finding the given class by name.
+ *
+ * <p>The name passed to the constructor is the method name used.
+ *
+ * @param className name of a class
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder impl(String className, Class<?>... argClasses) {
+ impl(className, name, argClasses);
+ return this;
+ }
+
+ /**
+ * Checks for a method implementation.
+ *
+ * @param targetClass a class instance
+ * @param methodName name of a method (different from constructor)
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder impl(Class<?> targetClass, String methodName, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ this.method = new UnboundMethod(targetClass.getMethod(methodName, argClasses), name);
+ } catch (NoSuchMethodException e) {
+ // not the right implementation
+ }
+ return this;
+ }
+
+ /**
+ * Checks for a method implementation.
+ *
+ * <p>The name passed to the constructor is the method name used.
+ *
+ * @param targetClass a class instance
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder impl(Class<?> targetClass, Class<?>... argClasses) {
+ impl(targetClass, name, argClasses);
+ return this;
+ }
+
+ public Builder ctorImpl(Class<?> targetClass, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ this.method = new DynConstructors.Builder().impl(targetClass, argClasses).buildChecked();
+ } catch (NoSuchMethodException e) {
+ // not the right implementation
+ }
+ return this;
+ }
+
+ public Builder ctorImpl(String className, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ this.method = new DynConstructors.Builder().impl(className, argClasses).buildChecked();
+ } catch (NoSuchMethodException e) {
+ // not the right implementation
+ }
+ return this;
+ }
+
+ /**
+ * Checks for an implementation, first finding the given class by name.
+ *
+ * @param className name of a class
+ * @param methodName name of a method (different from constructor)
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder hiddenImpl(String className, String methodName, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ Class<?> targetClass = Class.forName(className, true, loader);
+ hiddenImpl(targetClass, methodName, argClasses);
+ } catch (ClassNotFoundException e) {
+ // not the right implementation
+ }
+ return this;
+ }
+
+ /**
+ * Checks for an implementation, first finding the given class by name.
+ *
+ * <p>The name passed to the constructor is the method name used.
+ *
+ * @param className name of a class
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder hiddenImpl(String className, Class<?>... argClasses) {
+ hiddenImpl(className, name, argClasses);
+ return this;
+ }
+
+ /**
+ * Checks for a method implementation.
+ *
+ * @param targetClass a class instance
+ * @param methodName name of a method (different from constructor)
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder hiddenImpl(Class<?> targetClass, String methodName, Class<?>... argClasses) {
+ // don't do any work if an implementation has been found
+ if (method != null) {
+ return this;
+ }
+
+ try {
+ Method hidden = targetClass.getDeclaredMethod(methodName, argClasses);
+ AccessController.doPrivileged(new MakeAccessible(hidden));
+ this.method = new UnboundMethod(hidden, name);
+ } catch (SecurityException | NoSuchMethodException e) {
+ // unusable or not the right implementation
+ }
+ return this;
+ }
+
+ /**
+ * Checks for a method implementation.
+ *
+ * <p>The name passed to the constructor is the method name used.
+ *
+ * @param targetClass a class instance
+ * @param argClasses argument classes for the method
+ * @return this Builder for method chaining
+ * @see Class#forName(String)
+ * @see Class#getMethod(String, Class[])
+ */
+ public Builder hiddenImpl(Class<?> targetClass, Class<?>... argClasses) {
+ hiddenImpl(targetClass, name, argClasses);
+ return this;
+ }
+
+ /**
+ * Returns the first valid implementation as a UnboundMethod or throws a RuntimeError if there
+ * is none.
+ *
+ * @return a {@link UnboundMethod} with a valid implementation
+ * @throws RuntimeException if no implementation was found
+ */
+ public UnboundMethod build() {
+ if (method != null) {
+ return method;
+ } else {
+ throw new RuntimeException("Cannot find method: " + name);
+ }
+ }
+
+ /**
+ * Returns the first valid implementation as a BoundMethod or throws a RuntimeError if there is
+ * none.
+ *
+ * @param receiver an Object to receive the method invocation
+ * @return a {@link BoundMethod} with a valid implementation and receiver
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ * @throws RuntimeException if no implementation was found
+ */
+ public BoundMethod build(Object receiver) {
+ return build().bind(receiver);
+ }
+
+ /**
+ * Returns the first valid implementation as a UnboundMethod or throws a NoSuchMethodException
+ * if there is none.
+ *
+ * @return a {@link UnboundMethod} with a valid implementation
+ * @throws NoSuchMethodException if no implementation was found
+ */
+ public UnboundMethod buildChecked() throws NoSuchMethodException {
+ if (method != null) {
+ return method;
+ } else {
+ throw new NoSuchMethodException("Cannot find method: " + name);
+ }
+ }
+
+ /**
+ * Returns the first valid implementation as a BoundMethod or throws a NoSuchMethodException if
+ * there is none.
+ *
+ * @param receiver an Object to receive the method invocation
+ * @return a {@link BoundMethod} with a valid implementation and receiver
+ * @throws IllegalStateException if the method is static
+ * @throws IllegalArgumentException if the receiver's class is incompatible
+ * @throws NoSuchMethodException if no implementation was found
+ */
+ public BoundMethod buildChecked(Object receiver) throws NoSuchMethodException {
+ return buildChecked().bind(receiver);
+ }
+
+ /**
+ * Returns the first valid implementation as a StaticMethod or throws a NoSuchMethodException if
+ * there is none.
+ *
+ * @return a {@link StaticMethod} with a valid implementation
+ * @throws IllegalStateException if the method is not static
+ * @throws NoSuchMethodException if no implementation was found
+ */
+ public StaticMethod buildStaticChecked() throws NoSuchMethodException {
+ return buildChecked().asStatic();
+ }
+
+ /**
+ * Returns the first valid implementation as a StaticMethod or throws a RuntimeException if
+ * there is none.
+ *
+ * @return a {@link StaticMethod} with a valid implementation
+ * @throws IllegalStateException if the method is not static
+ * @throws RuntimeException if no implementation was found
+ */
+ public StaticMethod buildStatic() {
+ return build().asStatic();
+ }
+ }
+
+ private static class MakeAccessible implements PrivilegedAction<Void> {
+ private Method hidden;
+
+ MakeAccessible(Method hidden) {
+ this.hidden = hidden;
+ }
+
+ @Override
+ public Void run() {
+ hidden.setAccessible(true);
+ return null;
+ }
+ }
+}
diff --git a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
index d22c993d2..7eedf0bb8 100644
--- a/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
+++ b/kyuubi-common/src/main/scala/org/apache/kyuubi/engine/SemanticVersion.scala
@@ -56,6 +56,8 @@ case class SemanticVersion(majorVersion: Int, minorVersion: Int) {
val targetMinor = targetVersion.minorVersion
callback(targetMajor, targetMinor, this.majorVersion, this.minorVersion)
}
+
+ override def toString: String = s"$majorVersion.$minorVersion"
}
object SemanticVersion {