Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
D
dlink
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
zhaowei
dlink
Commits
ea875c21
Commit
ea875c21
authored
Jan 15, 2022
by
coderTomato
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/dev' into dev
parents
b69ac7bd
5c6f8d22
Changes
44
Show whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
5130 additions
and
60 deletions
+5130
-60
README.md
README.md
+5
-1
TaskService.java
dlink-admin/src/main/java/com/dlink/service/TaskService.java
+2
-0
CatalogueServiceImpl.java
...ain/java/com/dlink/service/impl/CatalogueServiceImpl.java
+1
-0
StudioServiceImpl.java
...c/main/java/com/dlink/service/impl/StudioServiceImpl.java
+14
-0
TaskServiceImpl.java
...src/main/java/com/dlink/service/impl/TaskServiceImpl.java
+14
-0
ExtractionUtils.java
.../apache/flink/table/types/extraction/ExtractionUtils.java
+947
-0
ExtractionUtils.java
.../apache/flink/table/types/extraction/ExtractionUtils.java
+947
-0
ExtractionUtils.java
.../apache/flink/table/types/extraction/ExtractionUtils.java
+985
-0
ExtractionUtils.java
.../apache/flink/table/types/extraction/ExtractionUtils.java
+986
-0
ClassEntity.java
dlink-common/src/main/java/com/dlink/pool/ClassEntity.java
+42
-0
ClassPool.java
dlink-common/src/main/java/com/dlink/pool/ClassPool.java
+61
-0
pom.xml
dlink-core/pom.xml
+5
-0
Explainer.java
dlink-core/src/main/java/com/dlink/explainer/Explainer.java
+2
-1
JobManager.java
dlink-core/src/main/java/com/dlink/job/JobManager.java
+23
-0
CustomStringJavaCompiler.java
...c/main/java/com/dlink/utils/CustomStringJavaCompiler.java
+166
-0
UDFUtil.java
dlink-core/src/main/java/com/dlink/utils/UDFUtil.java
+43
-0
FlinkSQLConstant.java
...or/src/main/java/com/dlink/constant/FlinkSQLConstant.java
+4
-0
SqlManager.java
...executor/src/main/java/com/dlink/executor/SqlManager.java
+11
-7
pom.xml
dlink-metadata/dlink-metadata-mysql/pom.xml
+1
-1
package.json
dlink-web/package.json
+1
-0
index.less
dlink-web/src/components/Chart/BarChartSetting/index.less
+9
-0
index.tsx
dlink-web/src/components/Chart/BarChartSetting/index.tsx
+153
-0
index.less
dlink-web/src/components/Chart/LineChartSetting/index.less
+9
-0
index.tsx
dlink-web/src/components/Chart/LineChartSetting/index.tsx
+145
-0
index.less
dlink-web/src/components/Chart/PieChartSetting/index.less
+9
-0
index.tsx
dlink-web/src/components/Chart/PieChartSetting/index.tsx
+109
-0
index.less
dlink-web/src/components/Chart/index.less
+9
-0
index.tsx
dlink-web/src/components/Chart/index.tsx
+138
-0
index.tsx
...c/components/Studio/StudioConsole/StudioHistory/index.tsx
+9
-3
index.tsx
...c/components/Studio/StudioConsole/StudioPreview/index.tsx
+1
-1
index.tsx
...src/components/Studio/StudioConsole/StudioTable/index.tsx
+2
-2
index.tsx
dlink-web/src/components/Studio/StudioConsole/index.tsx
+15
-1
DQL.ts
dlink-web/src/components/Studio/StudioEvent/DQL.ts
+3
-0
index.tsx
dlink-web/src/components/Studio/StudioMenu/index.tsx
+12
-9
index.less
...omponents/Studio/StudioRightTool/StudioUDFInfo/index.less
+9
-0
index.tsx
...components/Studio/StudioRightTool/StudioUDFInfo/index.tsx
+56
-0
index.tsx
dlink-web/src/components/Studio/StudioRightTool/index.tsx
+10
-1
index.tsx
dlink-web/src/components/Studio/StudioTabs/index.tsx
+63
-22
index.tsx
dlink-web/src/components/Studio/StudioTree/index.tsx
+2
-1
conf.ts
dlink-web/src/components/Studio/conf.ts
+20
-2
model.ts
dlink-web/src/pages/FlinkSqlStudio/model.ts
+62
-3
Welcome.tsx
dlink-web/src/pages/Welcome.tsx
+15
-3
quickstart.md
docs/en-US/guide/quickstart.md
+5
-1
quickstart.md
docs/guide/quickstart.md
+5
-1
No files found.
README.md
View file @
ea875c21
...
@@ -49,6 +49,7 @@ Dinky(原 Dlink):
...
@@ -49,6 +49,7 @@ Dinky(原 Dlink):
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 快捷键保存、校验、美化 | 0.5.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
...
@@ -56,16 +57,19 @@ Dinky(原 Dlink):
...
@@ -56,16 +57,19 @@ Dinky(原 Dlink):
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 UDF Java 方言Local模式在线编写、调试、动态加载 | 0.5.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 UDF java方言代码的开发 | 0.5.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | BI | 新增 折线图的渲染 | 0.5.0 |
| | | 新增 条形图图的渲染 | 0.5.0 |
| | | 新增 饼图的渲染 | 0.5.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
...
...
dlink-admin/src/main/java/com/dlink/service/TaskService.java
View file @
ea875c21
...
@@ -24,4 +24,6 @@ public interface TaskService extends ISuperService<Task> {
...
@@ -24,4 +24,6 @@ public interface TaskService extends ISuperService<Task> {
List
<
Task
>
listFlinkSQLEnv
();
List
<
Task
>
listFlinkSQLEnv
();
String
exportSql
(
Integer
id
);
String
exportSql
(
Integer
id
);
Task
getUDFByClassName
(
String
className
);
}
}
dlink-admin/src/main/java/com/dlink/service/impl/CatalogueServiceImpl.java
View file @
ea875c21
...
@@ -88,6 +88,7 @@ public class CatalogueServiceImpl extends SuperServiceImpl<CatalogueMapper, Cata
...
@@ -88,6 +88,7 @@ public class CatalogueServiceImpl extends SuperServiceImpl<CatalogueMapper, Cata
}
else
{
}
else
{
Task
task
=
new
Task
();
Task
task
=
new
Task
();
task
.
setId
(
oldCatalogue
.
getTaskId
());
task
.
setId
(
oldCatalogue
.
getTaskId
());
task
.
setName
(
catalogue
.
getName
());
task
.
setAlias
(
catalogue
.
getName
());
task
.
setAlias
(
catalogue
.
getName
());
taskService
.
updateById
(
task
);
taskService
.
updateById
(
task
);
this
.
updateById
(
catalogue
);
this
.
updateById
(
catalogue
);
...
...
dlink-admin/src/main/java/com/dlink/service/impl/StudioServiceImpl.java
View file @
ea875c21
...
@@ -27,6 +27,7 @@ import com.dlink.session.SessionConfig;
...
@@ -27,6 +27,7 @@ import com.dlink.session.SessionConfig;
import
com.dlink.session.SessionInfo
;
import
com.dlink.session.SessionInfo
;
import
com.dlink.session.SessionPool
;
import
com.dlink.session.SessionPool
;
import
com.dlink.utils.RunTimeUtil
;
import
com.dlink.utils.RunTimeUtil
;
import
com.dlink.utils.UDFUtil
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.core.JsonProcessingException
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.JsonNode
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
...
@@ -89,6 +90,7 @@ public class StudioServiceImpl implements StudioService {
...
@@ -89,6 +90,7 @@ public class StudioServiceImpl implements StudioService {
if
(!
config
.
isUseSession
())
{
if
(!
config
.
isUseSession
())
{
config
.
setAddress
(
clusterService
.
buildEnvironmentAddress
(
config
.
isUseRemote
(),
studioExecuteDTO
.
getClusterId
()));
config
.
setAddress
(
clusterService
.
buildEnvironmentAddress
(
config
.
isUseRemote
(),
studioExecuteDTO
.
getClusterId
()));
}
}
initUDF
(
config
,
studioExecuteDTO
.
getStatement
());
JobManager
jobManager
=
JobManager
.
build
(
config
);
JobManager
jobManager
=
JobManager
.
build
(
config
);
JobResult
jobResult
=
jobManager
.
executeSql
(
studioExecuteDTO
.
getStatement
());
JobResult
jobResult
=
jobManager
.
executeSql
(
studioExecuteDTO
.
getStatement
());
RunTimeUtil
.
recovery
(
jobManager
);
RunTimeUtil
.
recovery
(
jobManager
);
...
@@ -152,6 +154,7 @@ public class StudioServiceImpl implements StudioService {
...
@@ -152,6 +154,7 @@ public class StudioServiceImpl implements StudioService {
if
(!
config
.
isUseSession
())
{
if
(!
config
.
isUseSession
())
{
config
.
setAddress
(
clusterService
.
buildEnvironmentAddress
(
config
.
isUseRemote
(),
studioExecuteDTO
.
getClusterId
()));
config
.
setAddress
(
clusterService
.
buildEnvironmentAddress
(
config
.
isUseRemote
(),
studioExecuteDTO
.
getClusterId
()));
}
}
initUDF
(
config
,
studioExecuteDTO
.
getStatement
());
JobManager
jobManager
=
JobManager
.
buildPlanMode
(
config
);
JobManager
jobManager
=
JobManager
.
buildPlanMode
(
config
);
return
jobManager
.
explainSql
(
studioExecuteDTO
.
getStatement
()).
getSqlExplainResults
();
return
jobManager
.
explainSql
(
studioExecuteDTO
.
getStatement
()).
getSqlExplainResults
();
}
}
...
@@ -317,4 +320,15 @@ public class StudioServiceImpl implements StudioService {
...
@@ -317,4 +320,15 @@ public class StudioServiceImpl implements StudioService {
}
}
return
false
;
return
false
;
}
}
private
void
initUDF
(
JobConfig
config
,
String
statement
){
if
(!
GatewayType
.
LOCAL
.
equalsValue
(
config
.
getType
())){
return
;
}
List
<
String
>
udfClassNameList
=
JobManager
.
getUDFClassName
(
statement
);
for
(
String
item
:
udfClassNameList
){
Task
task
=
taskService
.
getUDFByClassName
(
item
);
JobManager
.
initUDF
(
item
,
task
.
getStatement
());
}
}
}
}
dlink-admin/src/main/java/com/dlink/service/impl/TaskServiceImpl.java
View file @
ea875c21
...
@@ -15,6 +15,7 @@ import com.dlink.job.JobResult;
...
@@ -15,6 +15,7 @@ import com.dlink.job.JobResult;
import
com.dlink.mapper.TaskMapper
;
import
com.dlink.mapper.TaskMapper
;
import
com.dlink.model.*
;
import
com.dlink.model.*
;
import
com.dlink.service.*
;
import
com.dlink.service.*
;
import
com.dlink.utils.CustomStringJavaCompiler
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
org.springframework.stereotype.Service
;
...
@@ -100,6 +101,11 @@ public class TaskServiceImpl extends SuperServiceImpl<TaskMapper, Task> implemen
...
@@ -100,6 +101,11 @@ public class TaskServiceImpl extends SuperServiceImpl<TaskMapper, Task> implemen
@Override
@Override
public
boolean
saveOrUpdateTask
(
Task
task
)
{
public
boolean
saveOrUpdateTask
(
Task
task
)
{
if
(
Asserts
.
isNotNullString
(
task
.
getDialect
())
&&
Dialect
.
JAVA
.
equalsVal
(
task
.
getDialect
())
&&
Asserts
.
isNotNullString
(
task
.
getStatement
())
){
CustomStringJavaCompiler
compiler
=
new
CustomStringJavaCompiler
(
task
.
getStatement
());
task
.
setSavePointPath
(
compiler
.
getFullClassName
());
}
if
(
task
.
getId
()
!=
null
)
{
if
(
task
.
getId
()
!=
null
)
{
this
.
updateById
(
task
);
this
.
updateById
(
task
);
if
(
task
.
getStatement
()
!=
null
)
{
if
(
task
.
getStatement
()
!=
null
)
{
...
@@ -151,6 +157,14 @@ public class TaskServiceImpl extends SuperServiceImpl<TaskMapper, Task> implemen
...
@@ -151,6 +157,14 @@ public class TaskServiceImpl extends SuperServiceImpl<TaskMapper, Task> implemen
}
}
}
}
@Override
public
Task
getUDFByClassName
(
String
className
)
{
Task
task
=
getOne
(
new
QueryWrapper
<
Task
>().
eq
(
"dialect"
,
"Java"
).
eq
(
"enabled"
,
1
).
eq
(
"save_point_path"
,
className
));
Assert
.
check
(
task
);
task
.
setStatement
(
statementService
.
getById
(
task
.
getId
()).
getStatement
());
return
task
;
}
private
JobConfig
buildJobConfig
(
Task
task
){
private
JobConfig
buildJobConfig
(
Task
task
){
boolean
isJarTask
=
isJarTask
(
task
);
boolean
isJarTask
=
isJarTask
(
task
);
if
(!
isJarTask
&&
Asserts
.
isNotNull
(
task
.
getEnvId
())){
if
(!
isJarTask
&&
Asserts
.
isNotNull
(
task
.
getEnvId
())){
...
...
dlink-client/dlink-client-1.11/src/main/java/org/apache/flink/table/types/extraction/ExtractionUtils.java
0 → 100644
View file @
ea875c21
/*
* 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
.
flink
.
table
.
types
.
extraction
;
import
com.dlink.pool.ClassPool
;
import
org.apache.flink.annotation.Internal
;
import
org.apache.flink.api.common.typeutils.TypeSerializer
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.ValidationException
;
import
org.apache.flink.table.catalog.DataTypeFactory
;
import
org.apache.flink.table.types.DataType
;
import
org.apache.flink.table.types.logical.StructuredType
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassReader
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Label
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.MethodVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Opcodes
;
import
javax.annotation.Nullable
;
import
java.io.IOException
;
import
java.lang.annotation.Annotation
;
import
java.lang.reflect.Constructor
;
import
java.lang.reflect.Executable
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.Modifier
;
import
java.lang.reflect.Parameter
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Type
;
import
java.lang.reflect.TypeVariable
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getConstructorDescriptor
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getMethodDescriptor
;
/** Utilities for performing reflection tasks. */
@Internal
public
final
class
ExtractionUtils
{
// --------------------------------------------------------------------------------------------
// Methods shared across packages
// --------------------------------------------------------------------------------------------
/** Collects methods of the given name. */
public
static
List
<
Method
>
collectMethods
(
Class
<?>
function
,
String
methodName
)
{
return
Arrays
.
stream
(
function
.
getMethods
())
.
filter
(
method
->
method
.
getName
().
equals
(
methodName
))
.
sorted
(
Comparator
.
comparing
(
Method:
:
toString
))
// for deterministic order
.
collect
(
Collectors
.
toList
());
}
/**
* Checks whether a method/constructor can be called with the given argument classes. This
* includes type widening and vararg. {@code null} is a wildcard.
*
* <p>E.g., {@code (int.class, int.class)} matches {@code f(Object...), f(int, int), f(Integer,
* Object)} and so forth.
*/
public
static
boolean
isInvokable
(
Executable
executable
,
Class
<?>...
classes
)
{
final
int
m
=
executable
.
getModifiers
();
if
(!
Modifier
.
isPublic
(
m
))
{
return
false
;
}
final
int
paramCount
=
executable
.
getParameterCount
();
final
int
classCount
=
classes
.
length
;
// check for enough classes for each parameter
if
((!
executable
.
isVarArgs
()
&&
classCount
!=
paramCount
)
||
(
executable
.
isVarArgs
()
&&
classCount
<
paramCount
-
1
))
{
return
false
;
}
int
currentClass
=
0
;
for
(
int
currentParam
=
0
;
currentParam
<
paramCount
;
currentParam
++)
{
final
Class
<?>
param
=
executable
.
getParameterTypes
()[
currentParam
];
// last parameter is a vararg that needs to consume remaining classes
if
(
currentParam
==
paramCount
-
1
&&
executable
.
isVarArgs
())
{
final
Class
<?>
paramComponent
=
executable
.
getParameterTypes
()[
currentParam
].
getComponentType
();
// we have more than 1 classes left so the vararg needs to consume them all
if
(
classCount
-
currentClass
>
1
)
{
while
(
currentClass
<
classCount
&&
ExtractionUtils
.
isAssignable
(
classes
[
currentClass
],
paramComponent
,
true
))
{
currentClass
++;
}
}
else
if
(
currentClass
<
classCount
&&
(
parameterMatches
(
classes
[
currentClass
],
param
)
||
parameterMatches
(
classes
[
currentClass
],
paramComponent
)))
{
currentClass
++;
}
}
// entire parameter matches
else
if
(
parameterMatches
(
classes
[
currentClass
],
param
))
{
currentClass
++;
}
}
// check if all classes have been consumed
return
currentClass
==
classCount
;
}
private
static
boolean
parameterMatches
(
Class
<?>
clz
,
Class
<?>
param
)
{
return
clz
==
null
||
ExtractionUtils
.
isAssignable
(
clz
,
param
,
true
);
}
/** Creates a method signature string like {@code int eval(Integer, String)}. */
public
static
String
createMethodSignatureString
(
String
methodName
,
Class
<?>[]
parameters
,
@Nullable
Class
<?>
returnType
)
{
final
StringBuilder
builder
=
new
StringBuilder
();
if
(
returnType
!=
null
)
{
builder
.
append
(
returnType
.
getCanonicalName
()).
append
(
" "
);
}
builder
.
append
(
methodName
)
.
append
(
Stream
.
of
(
parameters
)
.
map
(
parameter
->
{
// in case we don't know the parameter at this location
// (i.e. for accumulators)
if
(
parameter
==
null
)
{
return
"_"
;
}
else
{
return
parameter
.
getCanonicalName
();
}
})
.
collect
(
Collectors
.
joining
(
", "
,
"("
,
")"
)));
return
builder
.
toString
();
}
/**
* Validates the characteristics of a class for a {@link StructuredType} such as accessibility.
*/
public
static
void
validateStructuredClass
(
Class
<?>
clazz
)
{
final
int
m
=
clazz
.
getModifiers
();
if
(
Modifier
.
isAbstract
(
m
))
{
throw
extractionError
(
"Class '%s' must not be abstract."
,
clazz
.
getName
());
}
if
(!
Modifier
.
isPublic
(
m
))
{
throw
extractionError
(
"Class '%s' is not public."
,
clazz
.
getName
());
}
if
(
clazz
.
getEnclosingClass
()
!=
null
&&
(
clazz
.
getDeclaringClass
()
==
null
||
!
Modifier
.
isStatic
(
m
)))
{
throw
extractionError
(
"Class '%s' is a not a static, globally accessible class."
,
clazz
.
getName
());
}
}
/**
* Returns the field of a structured type. The logic is as broad as possible to support both
* Java and Scala in different flavors.
*/
public
static
Field
getStructuredField
(
Class
<?>
clazz
,
String
fieldName
)
{
final
String
normalizedFieldName
=
fieldName
.
toUpperCase
();
final
List
<
Field
>
fields
=
collectStructuredFields
(
clazz
);
for
(
Field
field
:
fields
)
{
if
(
field
.
getName
().
toUpperCase
().
equals
(
normalizedFieldName
))
{
return
field
;
}
}
throw
extractionError
(
"Could not to find a field named '%s' in class '%s' for structured type."
,
fieldName
,
clazz
.
getName
());
}
/**
* Checks for a field getter of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldGetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
field
.
getName
().
toUpperCase
();
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// get<Name>()
// is<Name>()
// <Name>() for Scala
final
String
normalizedMethodName
=
method
.
getName
().
toUpperCase
();
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"GET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
"IS"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// equal to field type
final
Type
returnType
=
method
.
getGenericReturnType
();
final
boolean
hasReturnType
=
returnType
.
equals
(
field
.
getGenericType
());
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// no parameters
final
boolean
hasNoParameters
=
method
.
getParameterCount
()
==
0
;
if
(!
hasNoParameters
)
{
continue
;
}
// matching getter found
return
Optional
.
of
(
method
);
}
// no getter found
return
Optional
.
empty
();
}
/**
* Checks for a field setters of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldSetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
field
.
getName
().
toUpperCase
();
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// set<Name>(type)
// <Name>(type)
// <Name>_$eq(type) for Scala
final
String
normalizedMethodName
=
method
.
getName
().
toUpperCase
();
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"SET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
+
"_$EQ"
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// void or the declaring class
final
Class
<?>
returnType
=
method
.
getReturnType
();
final
boolean
hasReturnType
=
returnType
==
Void
.
TYPE
||
returnType
==
clazz
;
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// one parameter that has the same (or primitive) type of the field
final
boolean
hasParameter
=
method
.
getParameterCount
()
==
1
&&
(
method
.
getGenericParameterTypes
()[
0
].
equals
(
field
.
getGenericType
())
||
primitiveToWrapper
(
method
.
getGenericParameterTypes
()[
0
])
.
equals
(
field
.
getGenericType
()));
if
(!
hasParameter
)
{
continue
;
}
// matching setter found
return
Optional
.
of
(
method
);
}
// no setter found
return
Optional
.
empty
();
}
/**
* Checks for an invokable constructor matching the given arguments.
*
* @see #isInvokable(Executable, Class[])
*/
public
static
boolean
hasInvokableConstructor
(
Class
<?>
clazz
,
Class
<?>...
classes
)
{
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
if
(
isInvokable
(
constructor
,
classes
))
{
return
true
;
}
}
return
false
;
}
/** Checks whether a field is directly readable without a getter. */
public
static
boolean
isStructuredFieldDirectlyReadable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is directly readable
return
Modifier
.
isPublic
(
m
);
}
/** Checks whether a field is directly writable without a setter or constructor. */
public
static
boolean
isStructuredFieldDirectlyWritable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly writable
return
Modifier
.
isPublic
(
m
);
}
// --------------------------------------------------------------------------------------------
// Methods intended for this package
// --------------------------------------------------------------------------------------------
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
String
message
,
Object
...
args
)
{
return
extractionError
(
null
,
message
,
args
);
}
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
Throwable
cause
,
String
message
,
Object
...
args
)
{
return
new
ValidationException
(
String
.
format
(
message
,
args
),
cause
);
}
/**
* Collects the partially ordered type hierarchy (i.e. all involved super classes and super
* interfaces) of the given type.
*/
static
List
<
Type
>
collectTypeHierarchy
(
Type
type
)
{
Type
currentType
=
type
;
Class
<?>
currentClass
=
toClass
(
type
);
final
List
<
Type
>
typeHierarchy
=
new
ArrayList
<>();
while
(
currentClass
!=
null
)
{
// collect type
typeHierarchy
.
add
(
currentType
);
// collect super interfaces
for
(
Type
genericInterface
:
currentClass
.
getGenericInterfaces
())
{
final
Class
<?>
interfaceClass
=
toClass
(
genericInterface
);
if
(
interfaceClass
!=
null
)
{
typeHierarchy
.
addAll
(
collectTypeHierarchy
(
genericInterface
));
}
}
currentType
=
currentClass
.
getGenericSuperclass
();
currentClass
=
toClass
(
currentType
);
}
return
typeHierarchy
;
}
/** Converts a {@link Type} to {@link Class} if possible, {@code null} otherwise. */
static
@Nullable
Class
<?>
toClass
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
(
Class
<?>)
type
;
}
else
if
(
type
instanceof
ParameterizedType
)
{
// this is always a class
return
(
Class
<?>)
((
ParameterizedType
)
type
).
getRawType
();
}
// unsupported: generic arrays, type variables, wildcard types
return
null
;
}
/** Creates a raw data type. */
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
static
DataType
createRawType
(
DataTypeFactory
typeFactory
,
@Nullable
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
,
@Nullable
Class
<?>
conversionClass
)
{
if
(
rawSerializer
!=
null
)
{
return
DataTypes
.
RAW
(
(
Class
)
createConversionClass
(
conversionClass
),
instantiateRawSerializer
(
rawSerializer
));
}
return
typeFactory
.
createRawDataType
(
createConversionClass
(
conversionClass
));
}
static
Class
<?>
createConversionClass
(
@Nullable
Class
<?>
conversionClass
)
{
if
(
conversionClass
!=
null
)
{
return
conversionClass
;
}
return
Object
.
class
;
}
private
static
TypeSerializer
<?>
instantiateRawSerializer
(
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
)
{
try
{
return
rawSerializer
.
newInstance
();
}
catch
(
Exception
e
)
{
throw
extractionError
(
e
,
"Cannot instantiate type serializer '%s' for RAW type. "
+
"Make sure the class is publicly accessible and has a default constructor."
,
rawSerializer
.
getName
());
}
}
/** Resolves a {@link TypeVariable} using the given type hierarchy if possible. */
static
Type
resolveVariable
(
List
<
Type
>
typeHierarchy
,
TypeVariable
<?>
variable
)
{
// iterate through hierarchy from top to bottom until type variable gets a non-variable
// assigned
for
(
int
i
=
typeHierarchy
.
size
()
-
1
;
i
>=
0
;
i
--)
{
final
Type
currentType
=
typeHierarchy
.
get
(
i
);
if
(
currentType
instanceof
ParameterizedType
)
{
final
Type
resolvedType
=
resolveVariableInParameterizedType
(
variable
,
(
ParameterizedType
)
currentType
);
if
(
resolvedType
instanceof
TypeVariable
)
{
// follow type variables transitively
variable
=
(
TypeVariable
<?>)
resolvedType
;
}
else
if
(
resolvedType
!=
null
)
{
return
resolvedType
;
}
}
}
// unresolved variable
return
variable
;
}
private
static
@Nullable
Type
resolveVariableInParameterizedType
(
TypeVariable
<?>
variable
,
ParameterizedType
currentType
)
{
final
Class
<?>
currentRaw
=
(
Class
<?>)
currentType
.
getRawType
();
final
TypeVariable
<?>[]
currentVariables
=
currentRaw
.
getTypeParameters
();
// search for matching type variable
for
(
int
paramPos
=
0
;
paramPos
<
currentVariables
.
length
;
paramPos
++)
{
if
(
typeVariableEquals
(
variable
,
currentVariables
[
paramPos
]))
{
return
currentType
.
getActualTypeArguments
()[
paramPos
];
}
}
return
null
;
}
private
static
boolean
typeVariableEquals
(
TypeVariable
<?>
variable
,
TypeVariable
<?>
currentVariable
)
{
return
currentVariable
.
getGenericDeclaration
().
equals
(
variable
.
getGenericDeclaration
())
&&
currentVariable
.
getName
().
equals
(
variable
.
getName
());
}
/**
* Validates if a given type is not already contained in the type hierarchy of a structured
* type.
*
* <p>Otherwise this would lead to infinite data type extraction cycles.
*/
static
void
validateStructuredSelfReference
(
Type
t
,
List
<
Type
>
typeHierarchy
)
{
final
Class
<?>
clazz
=
toClass
(
t
);
if
(
clazz
!=
null
&&
!
clazz
.
isInterface
()
&&
clazz
!=
Object
.
class
&&
typeHierarchy
.
contains
(
t
))
{
throw
extractionError
(
"Cyclic reference detected for class '%s'. Attributes of structured types must not "
+
"(transitively) reference the structured type itself."
,
clazz
.
getName
());
}
}
/** Returns the fields of a class for a {@link StructuredType}. */
static
List
<
Field
>
collectStructuredFields
(
Class
<?>
clazz
)
{
final
List
<
Field
>
fields
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Field
[]
declaredFields
=
clazz
.
getDeclaredFields
();
Stream
.
of
(
declaredFields
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
!
Modifier
.
isStatic
(
m
)
&&
!
Modifier
.
isTransient
(
m
);
})
.
forEach
(
fields:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
fields
;
}
/** Validates if a field is properly readable either directly or through a getter. */
static
void
validateStructuredFieldReadability
(
Class
<?>
clazz
,
Field
field
)
{
// field is accessible
if
(
isStructuredFieldDirectlyReadable
(
field
))
{
return
;
}
// field needs a getter
if
(!
getStructuredFieldGetter
(
clazz
,
field
).
isPresent
())
{
throw
extractionError
(
"Field '%s' of class '%s' is neither publicly accessible nor does it have "
+
"a corresponding getter method."
,
field
.
getName
(),
clazz
.
getName
());
}
}
/**
* Checks if a field is mutable or immutable. Returns {@code true} if the field is properly
* mutable. Returns {@code false} if it is properly immutable.
*/
static
boolean
isStructuredFieldMutable
(
Class
<?>
clazz
,
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly mutable
if
(
Modifier
.
isPublic
(
m
))
{
return
true
;
}
// field has setters by which it is mutable
if
(
getStructuredFieldSetter
(
clazz
,
field
).
isPresent
())
{
return
true
;
}
throw
extractionError
(
"Field '%s' of class '%s' is mutable but is neither publicly accessible nor does it have "
+
"a corresponding setter method."
,
field
.
getName
(),
clazz
.
getName
());
}
/** Returns the boxed type of a primitive type. */
static
Type
primitiveToWrapper
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
primitiveToWrapper
((
Class
<?>)
type
);
}
return
type
;
}
/** Collects all methods that qualify as methods of a {@link StructuredType}. */
static
List
<
Method
>
collectStructuredMethods
(
Class
<?>
clazz
)
{
final
List
<
Method
>
methods
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Method
[]
declaredMethods
=
clazz
.
getDeclaredMethods
();
Stream
.
of
(
declaredMethods
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
Modifier
.
isPublic
(
m
)
&&
!
Modifier
.
isNative
(
m
)
&&
!
Modifier
.
isAbstract
(
m
);
})
.
forEach
(
methods:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
methods
;
}
/**
* Collects all annotations of the given type defined in the current class or superclasses.
* Duplicates are ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfClass
(
Class
<
T
>
annotation
,
Class
<?>
annotatedClass
)
{
final
List
<
Class
<?>>
classHierarchy
=
new
ArrayList
<>();
Class
<?>
currentClass
=
annotatedClass
;
while
(
currentClass
!=
null
)
{
classHierarchy
.
add
(
currentClass
);
currentClass
=
currentClass
.
getSuperclass
();
}
// convert to top down
Collections
.
reverse
(
classHierarchy
);
return
classHierarchy
.
stream
()
.
flatMap
(
c
->
Stream
.
of
(
c
.
getAnnotationsByType
(
annotation
)))
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
}
/**
* Collects all annotations of the given type defined in the given method. Duplicates are
* ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfMethod
(
Class
<
T
>
annotation
,
Method
annotatedMethod
)
{
return
new
LinkedHashSet
<>(
Arrays
.
asList
(
annotatedMethod
.
getAnnotationsByType
(
annotation
)));
}
// --------------------------------------------------------------------------------------------
// Parameter Extraction Utilities
// --------------------------------------------------------------------------------------------
/** Result of the extraction in {@link #extractAssigningConstructor(Class, List)}. */
static
class
AssigningConstructor
{
public
final
Constructor
<?>
constructor
;
public
final
List
<
String
>
parameterNames
;
private
AssigningConstructor
(
Constructor
<?>
constructor
,
List
<
String
>
parameterNames
)
{
this
.
constructor
=
constructor
;
this
.
parameterNames
=
parameterNames
;
}
}
/**
* Checks whether the given constructor takes all of the given fields with matching (possibly
* primitive) type and name. An assigning constructor can define the order of fields.
*/
static
@Nullable
AssigningConstructor
extractAssigningConstructor
(
Class
<?>
clazz
,
List
<
Field
>
fields
)
{
AssigningConstructor
foundConstructor
=
null
;
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
final
boolean
qualifyingConstructor
=
Modifier
.
isPublic
(
constructor
.
getModifiers
())
&&
constructor
.
getParameterTypes
().
length
==
fields
.
size
();
if
(!
qualifyingConstructor
)
{
continue
;
}
final
List
<
String
>
parameterNames
=
extractConstructorParameterNames
(
constructor
,
fields
);
if
(
parameterNames
!=
null
)
{
if
(
foundConstructor
!=
null
)
{
throw
extractionError
(
"Multiple constructors found that assign all fields for class '%s'."
,
clazz
.
getName
());
}
foundConstructor
=
new
AssigningConstructor
(
constructor
,
parameterNames
);
}
}
return
foundConstructor
;
}
/** Extracts the parameter names of a method if possible. */
static
@Nullable
List
<
String
>
extractMethodParameterNames
(
Method
method
)
{
return
extractExecutableNames
(
method
);
}
/**
* Extracts ordered parameter names from a constructor that takes all of the given fields with
* matching (possibly primitive) type and name.
*/
private
static
@Nullable
List
<
String
>
extractConstructorParameterNames
(
Constructor
<?>
constructor
,
List
<
Field
>
fields
)
{
final
Type
[]
parameterTypes
=
constructor
.
getGenericParameterTypes
();
List
<
String
>
parameterNames
=
extractExecutableNames
(
constructor
);
if
(
parameterNames
==
null
)
{
return
null
;
}
final
Map
<
String
,
Type
>
fieldMap
=
fields
.
stream
().
collect
(
Collectors
.
toMap
(
Field:
:
getName
,
Field:
:
getGenericType
));
// check that all fields are represented in the parameters of the constructor
for
(
int
i
=
0
;
i
<
parameterNames
.
size
();
i
++)
{
final
String
parameterName
=
parameterNames
.
get
(
i
);
final
Type
fieldType
=
fieldMap
.
get
(
parameterName
);
// might be null
final
Type
parameterType
=
parameterTypes
[
i
];
// we are tolerant here because frameworks such as Avro accept a boxed type even though
// the field is primitive
if
(!
primitiveToWrapper
(
parameterType
).
equals
(
primitiveToWrapper
(
fieldType
)))
{
return
null
;
}
}
return
parameterNames
;
}
private
static
@Nullable
List
<
String
>
extractExecutableNames
(
Executable
executable
)
{
final
int
offset
;
if
(!
Modifier
.
isStatic
(
executable
.
getModifiers
()))
{
// remove "this" as first parameter
offset
=
1
;
}
else
{
offset
=
0
;
}
// by default parameter names are "arg0, arg1, arg2, ..." if compiler flag is not set
// so we need to extract them manually if possible
List
<
String
>
parameterNames
=
Stream
.
of
(
executable
.
getParameters
())
.
map
(
Parameter:
:
getName
)
.
collect
(
Collectors
.
toList
());
if
(
parameterNames
.
stream
().
allMatch
(
n
->
n
.
startsWith
(
"arg"
)))
{
final
ParameterExtractor
extractor
;
if
(
executable
instanceof
Constructor
)
{
extractor
=
new
ParameterExtractor
((
Constructor
<?>)
executable
);
}
else
{
extractor
=
new
ParameterExtractor
((
Method
)
executable
);
}
getClassReader
(
executable
.
getDeclaringClass
()).
accept
(
extractor
,
0
);
final
List
<
String
>
extractedNames
=
extractor
.
getParameterNames
();
if
(
extractedNames
.
size
()
==
0
)
{
return
null
;
}
// remove "this" and additional local variables
// select less names if class file has not the required information
parameterNames
=
extractedNames
.
subList
(
offset
,
Math
.
min
(
executable
.
getParameterCount
()
+
offset
,
extractedNames
.
size
()));
}
if
(
parameterNames
.
size
()
!=
executable
.
getParameterCount
())
{
return
null
;
}
return
parameterNames
;
}
private
static
ClassReader
getClassReader
(
Class
<?>
cls
)
{
final
String
className
=
cls
.
getName
().
replaceFirst
(
"^.*\\."
,
""
)
+
".class"
;
if
(
ClassPool
.
exist
(
cls
.
getName
())){
return
new
ClassReader
(
ClassPool
.
get
(
cls
.
getName
()).
getClassByte
());
}
try
{
return
new
ClassReader
(
cls
.
getResourceAsStream
(
className
));
}
catch
(
IOException
e
)
{
throw
new
IllegalStateException
(
"Could not instantiate ClassReader."
,
e
);
}
}
/**
* Extracts the parameter names and descriptors from a constructor or method. Assuming the
* existence of a local variable table.
*
* <p>For example:
*
* <pre>{@code
* public WC(java.lang.String arg0, long arg1) { // <init> //(Ljava/lang/String;J)V
* <localVar:index=0 , name=this , desc=Lorg/apache/flink/WC;, sig=null, start=L1, end=L2>
* <localVar:index=1 , name=word , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=frequency , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal2 , desc=J, sig=null, start=L1, end=L2>
* }
* }</pre>
*/
private
static
class
ParameterExtractor
extends
ClassVisitor
{
private
static
final
int
OPCODE
=
Opcodes
.
ASM7
;
private
final
String
methodDescriptor
;
private
final
List
<
String
>
parameterNames
=
new
ArrayList
<>();
ParameterExtractor
(
Constructor
<?>
constructor
)
{
super
(
OPCODE
);
methodDescriptor
=
getConstructorDescriptor
(
constructor
);
}
ParameterExtractor
(
Method
method
)
{
super
(
OPCODE
);
methodDescriptor
=
getMethodDescriptor
(
method
);
}
List
<
String
>
getParameterNames
()
{
return
parameterNames
;
}
@Override
public
MethodVisitor
visitMethod
(
int
access
,
String
name
,
String
descriptor
,
String
signature
,
String
[]
exceptions
)
{
if
(
descriptor
.
equals
(
methodDescriptor
))
{
return
new
MethodVisitor
(
OPCODE
)
{
@Override
public
void
visitLocalVariable
(
String
name
,
String
descriptor
,
String
signature
,
Label
start
,
Label
end
,
int
index
)
{
parameterNames
.
add
(
name
);
}
};
}
return
super
.
visitMethod
(
access
,
name
,
descriptor
,
signature
,
exceptions
);
}
}
// --------------------------------------------------------------------------------------------
// Class Assignment and Boxing
//
// copied from o.a.commons.lang3.ClassUtils (commons-lang3:3.3.2)
// --------------------------------------------------------------------------------------------
/**
* Checks if one {@code Class} can be assigned to a variable of another {@code Class}.
*
* <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into
* account widenings of primitive classes and {@code null}s.
*
* <p>Primitive widenings allow an int to be assigned to a long, float or double. This method
* returns the correct result for these cases.
*
* <p>{@code Null} may be assigned to any reference type. This method will return {@code true}
* if {@code null} is passed in and the toClass is non-primitive.
*
* <p>Specifically, this method tests whether the type represented by the specified {@code
* Class} parameter can be converted to the type represented by this {@code Class} object via an
* identity conversion widening primitive or widening reference conversion. See <em><a
* href="http://docs.oracle.com/javase/specs/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
*
* @param cls the Class to check, may be null
* @param toClass the Class to try to assign into, returns false if null
* @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
* @return {@code true} if assignment possible
*/
public
static
boolean
isAssignable
(
Class
<?>
cls
,
final
Class
<?>
toClass
,
final
boolean
autoboxing
)
{
if
(
toClass
==
null
)
{
return
false
;
}
// have to check for null, as isAssignableFrom doesn't
if
(
cls
==
null
)
{
return
!
toClass
.
isPrimitive
();
}
// autoboxing:
if
(
autoboxing
)
{
if
(
cls
.
isPrimitive
()
&&
!
toClass
.
isPrimitive
())
{
cls
=
primitiveToWrapper
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
if
(
toClass
.
isPrimitive
()
&&
!
cls
.
isPrimitive
())
{
cls
=
wrapperToPrimitive
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
}
if
(
cls
.
equals
(
toClass
))
{
return
true
;
}
if
(
cls
.
isPrimitive
())
{
if
(!
toClass
.
isPrimitive
())
{
return
false
;
}
if
(
Integer
.
TYPE
.
equals
(
cls
))
{
return
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Long
.
TYPE
.
equals
(
cls
))
{
return
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Boolean
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Double
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Float
.
TYPE
.
equals
(
cls
))
{
return
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Character
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Short
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Byte
.
TYPE
.
equals
(
cls
))
{
return
Short
.
TYPE
.
equals
(
toClass
)
||
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
// should never get here
return
false
;
}
return
toClass
.
isAssignableFrom
(
cls
);
}
/** Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
primitiveWrapperMap
=
new
HashMap
<>();
static
{
primitiveWrapperMap
.
put
(
Boolean
.
TYPE
,
Boolean
.
class
);
primitiveWrapperMap
.
put
(
Byte
.
TYPE
,
Byte
.
class
);
primitiveWrapperMap
.
put
(
Character
.
TYPE
,
Character
.
class
);
primitiveWrapperMap
.
put
(
Short
.
TYPE
,
Short
.
class
);
primitiveWrapperMap
.
put
(
Integer
.
TYPE
,
Integer
.
class
);
primitiveWrapperMap
.
put
(
Long
.
TYPE
,
Long
.
class
);
primitiveWrapperMap
.
put
(
Double
.
TYPE
,
Double
.
class
);
primitiveWrapperMap
.
put
(
Float
.
TYPE
,
Float
.
class
);
primitiveWrapperMap
.
put
(
Void
.
TYPE
,
Void
.
TYPE
);
}
/** Maps wrapper {@code Class}es to their corresponding primitive types. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
wrapperPrimitiveMap
=
new
HashMap
<>();
static
{
for
(
final
Class
<?>
primitiveClass
:
primitiveWrapperMap
.
keySet
())
{
final
Class
<?>
wrapperClass
=
primitiveWrapperMap
.
get
(
primitiveClass
);
if
(!
primitiveClass
.
equals
(
wrapperClass
))
{
wrapperPrimitiveMap
.
put
(
wrapperClass
,
primitiveClass
);
}
}
}
/**
* Converts the specified primitive Class object to its corresponding wrapper Class object.
*
* <p>NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
*
* @param cls the class to convert, may be null
* @return the wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive.
* {@code null} if null input.
* @since 2.1
*/
public
static
Class
<?>
primitiveToWrapper
(
final
Class
<?>
cls
)
{
Class
<?>
convertedClass
=
cls
;
if
(
cls
!=
null
&&
cls
.
isPrimitive
())
{
convertedClass
=
primitiveWrapperMap
.
get
(
cls
);
}
return
convertedClass
;
}
/**
* Converts the specified wrapper class to its corresponding primitive class.
*
* <p>This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is
* a wrapper class for a primitive type, this primitive type will be returned (e.g. {@code
* Integer.TYPE} for {@code Integer.class}). For other classes, or if the parameter is
* <b>null</b>, the return value is <b>null</b>.
*
* @param cls the class to convert, may be <b>null</b>
* @return the corresponding primitive type if {@code cls} is a wrapper class, <b>null</b>
* otherwise
* @see #primitiveToWrapper(Class)
* @since 2.4
*/
public
static
Class
<?>
wrapperToPrimitive
(
final
Class
<?>
cls
)
{
return
wrapperPrimitiveMap
.
get
(
cls
);
}
// --------------------------------------------------------------------------------------------
private
ExtractionUtils
()
{
// no instantiation
}
}
dlink-client/dlink-client-1.12/src/main/java/org/apache/flink/table/types/extraction/ExtractionUtils.java
0 → 100644
View file @
ea875c21
/*
* 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
.
flink
.
table
.
types
.
extraction
;
import
com.dlink.pool.ClassPool
;
import
org.apache.flink.annotation.Internal
;
import
org.apache.flink.api.common.typeutils.TypeSerializer
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.ValidationException
;
import
org.apache.flink.table.catalog.DataTypeFactory
;
import
org.apache.flink.table.types.DataType
;
import
org.apache.flink.table.types.logical.StructuredType
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassReader
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Label
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.MethodVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Opcodes
;
import
javax.annotation.Nullable
;
import
java.io.IOException
;
import
java.lang.annotation.Annotation
;
import
java.lang.reflect.Constructor
;
import
java.lang.reflect.Executable
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.Modifier
;
import
java.lang.reflect.Parameter
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Type
;
import
java.lang.reflect.TypeVariable
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getConstructorDescriptor
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getMethodDescriptor
;
/** Utilities for performing reflection tasks. */
@Internal
public
final
class
ExtractionUtils
{
// --------------------------------------------------------------------------------------------
// Methods shared across packages
// --------------------------------------------------------------------------------------------
/** Collects methods of the given name. */
public
static
List
<
Method
>
collectMethods
(
Class
<?>
function
,
String
methodName
)
{
return
Arrays
.
stream
(
function
.
getMethods
())
.
filter
(
method
->
method
.
getName
().
equals
(
methodName
))
.
sorted
(
Comparator
.
comparing
(
Method:
:
toString
))
// for deterministic order
.
collect
(
Collectors
.
toList
());
}
/**
* Checks whether a method/constructor can be called with the given argument classes. This
* includes type widening and vararg. {@code null} is a wildcard.
*
* <p>E.g., {@code (int.class, int.class)} matches {@code f(Object...), f(int, int), f(Integer,
* Object)} and so forth.
*/
public
static
boolean
isInvokable
(
Executable
executable
,
Class
<?>...
classes
)
{
final
int
m
=
executable
.
getModifiers
();
if
(!
Modifier
.
isPublic
(
m
))
{
return
false
;
}
final
int
paramCount
=
executable
.
getParameterCount
();
final
int
classCount
=
classes
.
length
;
// check for enough classes for each parameter
if
((!
executable
.
isVarArgs
()
&&
classCount
!=
paramCount
)
||
(
executable
.
isVarArgs
()
&&
classCount
<
paramCount
-
1
))
{
return
false
;
}
int
currentClass
=
0
;
for
(
int
currentParam
=
0
;
currentParam
<
paramCount
;
currentParam
++)
{
final
Class
<?>
param
=
executable
.
getParameterTypes
()[
currentParam
];
// last parameter is a vararg that needs to consume remaining classes
if
(
currentParam
==
paramCount
-
1
&&
executable
.
isVarArgs
())
{
final
Class
<?>
paramComponent
=
executable
.
getParameterTypes
()[
currentParam
].
getComponentType
();
// we have more than 1 classes left so the vararg needs to consume them all
if
(
classCount
-
currentClass
>
1
)
{
while
(
currentClass
<
classCount
&&
ExtractionUtils
.
isAssignable
(
classes
[
currentClass
],
paramComponent
,
true
))
{
currentClass
++;
}
}
else
if
(
currentClass
<
classCount
&&
(
parameterMatches
(
classes
[
currentClass
],
param
)
||
parameterMatches
(
classes
[
currentClass
],
paramComponent
)))
{
currentClass
++;
}
}
// entire parameter matches
else
if
(
parameterMatches
(
classes
[
currentClass
],
param
))
{
currentClass
++;
}
}
// check if all classes have been consumed
return
currentClass
==
classCount
;
}
private
static
boolean
parameterMatches
(
Class
<?>
clz
,
Class
<?>
param
)
{
return
clz
==
null
||
ExtractionUtils
.
isAssignable
(
clz
,
param
,
true
);
}
/** Creates a method signature string like {@code int eval(Integer, String)}. */
public
static
String
createMethodSignatureString
(
String
methodName
,
Class
<?>[]
parameters
,
@Nullable
Class
<?>
returnType
)
{
final
StringBuilder
builder
=
new
StringBuilder
();
if
(
returnType
!=
null
)
{
builder
.
append
(
returnType
.
getCanonicalName
()).
append
(
" "
);
}
builder
.
append
(
methodName
)
.
append
(
Stream
.
of
(
parameters
)
.
map
(
parameter
->
{
// in case we don't know the parameter at this location
// (i.e. for accumulators)
if
(
parameter
==
null
)
{
return
"_"
;
}
else
{
return
parameter
.
getCanonicalName
();
}
})
.
collect
(
Collectors
.
joining
(
", "
,
"("
,
")"
)));
return
builder
.
toString
();
}
/**
* Validates the characteristics of a class for a {@link StructuredType} such as accessibility.
*/
public
static
void
validateStructuredClass
(
Class
<?>
clazz
)
{
final
int
m
=
clazz
.
getModifiers
();
if
(
Modifier
.
isAbstract
(
m
))
{
throw
extractionError
(
"Class '%s' must not be abstract."
,
clazz
.
getName
());
}
if
(!
Modifier
.
isPublic
(
m
))
{
throw
extractionError
(
"Class '%s' is not public."
,
clazz
.
getName
());
}
if
(
clazz
.
getEnclosingClass
()
!=
null
&&
(
clazz
.
getDeclaringClass
()
==
null
||
!
Modifier
.
isStatic
(
m
)))
{
throw
extractionError
(
"Class '%s' is a not a static, globally accessible class."
,
clazz
.
getName
());
}
}
/**
* Returns the field of a structured type. The logic is as broad as possible to support both
* Java and Scala in different flavors.
*/
public
static
Field
getStructuredField
(
Class
<?>
clazz
,
String
fieldName
)
{
final
String
normalizedFieldName
=
fieldName
.
toUpperCase
();
final
List
<
Field
>
fields
=
collectStructuredFields
(
clazz
);
for
(
Field
field
:
fields
)
{
if
(
field
.
getName
().
toUpperCase
().
equals
(
normalizedFieldName
))
{
return
field
;
}
}
throw
extractionError
(
"Could not to find a field named '%s' in class '%s' for structured type."
,
fieldName
,
clazz
.
getName
());
}
/**
* Checks for a field getter of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldGetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
field
.
getName
().
toUpperCase
();
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// get<Name>()
// is<Name>()
// <Name>() for Scala
final
String
normalizedMethodName
=
method
.
getName
().
toUpperCase
();
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"GET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
"IS"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// equal to field type
final
Type
returnType
=
method
.
getGenericReturnType
();
final
boolean
hasReturnType
=
returnType
.
equals
(
field
.
getGenericType
());
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// no parameters
final
boolean
hasNoParameters
=
method
.
getParameterCount
()
==
0
;
if
(!
hasNoParameters
)
{
continue
;
}
// matching getter found
return
Optional
.
of
(
method
);
}
// no getter found
return
Optional
.
empty
();
}
/**
* Checks for a field setters of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldSetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
field
.
getName
().
toUpperCase
();
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// set<Name>(type)
// <Name>(type)
// <Name>_$eq(type) for Scala
final
String
normalizedMethodName
=
method
.
getName
().
toUpperCase
();
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"SET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
+
"_$EQ"
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// void or the declaring class
final
Class
<?>
returnType
=
method
.
getReturnType
();
final
boolean
hasReturnType
=
returnType
==
Void
.
TYPE
||
returnType
==
clazz
;
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// one parameter that has the same (or primitive) type of the field
final
boolean
hasParameter
=
method
.
getParameterCount
()
==
1
&&
(
method
.
getGenericParameterTypes
()[
0
].
equals
(
field
.
getGenericType
())
||
primitiveToWrapper
(
method
.
getGenericParameterTypes
()[
0
])
.
equals
(
field
.
getGenericType
()));
if
(!
hasParameter
)
{
continue
;
}
// matching setter found
return
Optional
.
of
(
method
);
}
// no setter found
return
Optional
.
empty
();
}
/**
* Checks for an invokable constructor matching the given arguments.
*
* @see #isInvokable(Executable, Class[])
*/
public
static
boolean
hasInvokableConstructor
(
Class
<?>
clazz
,
Class
<?>...
classes
)
{
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
if
(
isInvokable
(
constructor
,
classes
))
{
return
true
;
}
}
return
false
;
}
/** Checks whether a field is directly readable without a getter. */
public
static
boolean
isStructuredFieldDirectlyReadable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is directly readable
return
Modifier
.
isPublic
(
m
);
}
/** Checks whether a field is directly writable without a setter or constructor. */
public
static
boolean
isStructuredFieldDirectlyWritable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly writable
return
Modifier
.
isPublic
(
m
);
}
// --------------------------------------------------------------------------------------------
// Methods intended for this package
// --------------------------------------------------------------------------------------------
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
String
message
,
Object
...
args
)
{
return
extractionError
(
null
,
message
,
args
);
}
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
Throwable
cause
,
String
message
,
Object
...
args
)
{
return
new
ValidationException
(
String
.
format
(
message
,
args
),
cause
);
}
/**
* Collects the partially ordered type hierarchy (i.e. all involved super classes and super
* interfaces) of the given type.
*/
static
List
<
Type
>
collectTypeHierarchy
(
Type
type
)
{
Type
currentType
=
type
;
Class
<?>
currentClass
=
toClass
(
type
);
final
List
<
Type
>
typeHierarchy
=
new
ArrayList
<>();
while
(
currentClass
!=
null
)
{
// collect type
typeHierarchy
.
add
(
currentType
);
// collect super interfaces
for
(
Type
genericInterface
:
currentClass
.
getGenericInterfaces
())
{
final
Class
<?>
interfaceClass
=
toClass
(
genericInterface
);
if
(
interfaceClass
!=
null
)
{
typeHierarchy
.
addAll
(
collectTypeHierarchy
(
genericInterface
));
}
}
currentType
=
currentClass
.
getGenericSuperclass
();
currentClass
=
toClass
(
currentType
);
}
return
typeHierarchy
;
}
/** Converts a {@link Type} to {@link Class} if possible, {@code null} otherwise. */
static
@Nullable
Class
<?>
toClass
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
(
Class
<?>)
type
;
}
else
if
(
type
instanceof
ParameterizedType
)
{
// this is always a class
return
(
Class
<?>)
((
ParameterizedType
)
type
).
getRawType
();
}
// unsupported: generic arrays, type variables, wildcard types
return
null
;
}
/** Creates a raw data type. */
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
static
DataType
createRawType
(
DataTypeFactory
typeFactory
,
@Nullable
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
,
@Nullable
Class
<?>
conversionClass
)
{
if
(
rawSerializer
!=
null
)
{
return
DataTypes
.
RAW
(
(
Class
)
createConversionClass
(
conversionClass
),
instantiateRawSerializer
(
rawSerializer
));
}
return
typeFactory
.
createRawDataType
(
createConversionClass
(
conversionClass
));
}
static
Class
<?>
createConversionClass
(
@Nullable
Class
<?>
conversionClass
)
{
if
(
conversionClass
!=
null
)
{
return
conversionClass
;
}
return
Object
.
class
;
}
private
static
TypeSerializer
<?>
instantiateRawSerializer
(
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
)
{
try
{
return
rawSerializer
.
newInstance
();
}
catch
(
Exception
e
)
{
throw
extractionError
(
e
,
"Cannot instantiate type serializer '%s' for RAW type. "
+
"Make sure the class is publicly accessible and has a default constructor."
,
rawSerializer
.
getName
());
}
}
/** Resolves a {@link TypeVariable} using the given type hierarchy if possible. */
static
Type
resolveVariable
(
List
<
Type
>
typeHierarchy
,
TypeVariable
<?>
variable
)
{
// iterate through hierarchy from top to bottom until type variable gets a non-variable
// assigned
for
(
int
i
=
typeHierarchy
.
size
()
-
1
;
i
>=
0
;
i
--)
{
final
Type
currentType
=
typeHierarchy
.
get
(
i
);
if
(
currentType
instanceof
ParameterizedType
)
{
final
Type
resolvedType
=
resolveVariableInParameterizedType
(
variable
,
(
ParameterizedType
)
currentType
);
if
(
resolvedType
instanceof
TypeVariable
)
{
// follow type variables transitively
variable
=
(
TypeVariable
<?>)
resolvedType
;
}
else
if
(
resolvedType
!=
null
)
{
return
resolvedType
;
}
}
}
// unresolved variable
return
variable
;
}
private
static
@Nullable
Type
resolveVariableInParameterizedType
(
TypeVariable
<?>
variable
,
ParameterizedType
currentType
)
{
final
Class
<?>
currentRaw
=
(
Class
<?>)
currentType
.
getRawType
();
final
TypeVariable
<?>[]
currentVariables
=
currentRaw
.
getTypeParameters
();
// search for matching type variable
for
(
int
paramPos
=
0
;
paramPos
<
currentVariables
.
length
;
paramPos
++)
{
if
(
typeVariableEquals
(
variable
,
currentVariables
[
paramPos
]))
{
return
currentType
.
getActualTypeArguments
()[
paramPos
];
}
}
return
null
;
}
private
static
boolean
typeVariableEquals
(
TypeVariable
<?>
variable
,
TypeVariable
<?>
currentVariable
)
{
return
currentVariable
.
getGenericDeclaration
().
equals
(
variable
.
getGenericDeclaration
())
&&
currentVariable
.
getName
().
equals
(
variable
.
getName
());
}
/**
* Validates if a given type is not already contained in the type hierarchy of a structured
* type.
*
* <p>Otherwise this would lead to infinite data type extraction cycles.
*/
static
void
validateStructuredSelfReference
(
Type
t
,
List
<
Type
>
typeHierarchy
)
{
final
Class
<?>
clazz
=
toClass
(
t
);
if
(
clazz
!=
null
&&
!
clazz
.
isInterface
()
&&
clazz
!=
Object
.
class
&&
typeHierarchy
.
contains
(
t
))
{
throw
extractionError
(
"Cyclic reference detected for class '%s'. Attributes of structured types must not "
+
"(transitively) reference the structured type itself."
,
clazz
.
getName
());
}
}
/** Returns the fields of a class for a {@link StructuredType}. */
static
List
<
Field
>
collectStructuredFields
(
Class
<?>
clazz
)
{
final
List
<
Field
>
fields
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Field
[]
declaredFields
=
clazz
.
getDeclaredFields
();
Stream
.
of
(
declaredFields
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
!
Modifier
.
isStatic
(
m
)
&&
!
Modifier
.
isTransient
(
m
);
})
.
forEach
(
fields:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
fields
;
}
/** Validates if a field is properly readable either directly or through a getter. */
static
void
validateStructuredFieldReadability
(
Class
<?>
clazz
,
Field
field
)
{
// field is accessible
if
(
isStructuredFieldDirectlyReadable
(
field
))
{
return
;
}
// field needs a getter
if
(!
getStructuredFieldGetter
(
clazz
,
field
).
isPresent
())
{
throw
extractionError
(
"Field '%s' of class '%s' is neither publicly accessible nor does it have "
+
"a corresponding getter method."
,
field
.
getName
(),
clazz
.
getName
());
}
}
/**
* Checks if a field is mutable or immutable. Returns {@code true} if the field is properly
* mutable. Returns {@code false} if it is properly immutable.
*/
static
boolean
isStructuredFieldMutable
(
Class
<?>
clazz
,
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly mutable
if
(
Modifier
.
isPublic
(
m
))
{
return
true
;
}
// field has setters by which it is mutable
if
(
getStructuredFieldSetter
(
clazz
,
field
).
isPresent
())
{
return
true
;
}
throw
extractionError
(
"Field '%s' of class '%s' is mutable but is neither publicly accessible nor does it have "
+
"a corresponding setter method."
,
field
.
getName
(),
clazz
.
getName
());
}
/** Returns the boxed type of a primitive type. */
static
Type
primitiveToWrapper
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
primitiveToWrapper
((
Class
<?>)
type
);
}
return
type
;
}
/** Collects all methods that qualify as methods of a {@link StructuredType}. */
static
List
<
Method
>
collectStructuredMethods
(
Class
<?>
clazz
)
{
final
List
<
Method
>
methods
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Method
[]
declaredMethods
=
clazz
.
getDeclaredMethods
();
Stream
.
of
(
declaredMethods
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
Modifier
.
isPublic
(
m
)
&&
!
Modifier
.
isNative
(
m
)
&&
!
Modifier
.
isAbstract
(
m
);
})
.
forEach
(
methods:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
methods
;
}
/**
* Collects all annotations of the given type defined in the current class or superclasses.
* Duplicates are ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfClass
(
Class
<
T
>
annotation
,
Class
<?>
annotatedClass
)
{
final
List
<
Class
<?>>
classHierarchy
=
new
ArrayList
<>();
Class
<?>
currentClass
=
annotatedClass
;
while
(
currentClass
!=
null
)
{
classHierarchy
.
add
(
currentClass
);
currentClass
=
currentClass
.
getSuperclass
();
}
// convert to top down
Collections
.
reverse
(
classHierarchy
);
return
classHierarchy
.
stream
()
.
flatMap
(
c
->
Stream
.
of
(
c
.
getAnnotationsByType
(
annotation
)))
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
}
/**
* Collects all annotations of the given type defined in the given method. Duplicates are
* ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfMethod
(
Class
<
T
>
annotation
,
Method
annotatedMethod
)
{
return
new
LinkedHashSet
<>(
Arrays
.
asList
(
annotatedMethod
.
getAnnotationsByType
(
annotation
)));
}
// --------------------------------------------------------------------------------------------
// Parameter Extraction Utilities
// --------------------------------------------------------------------------------------------
/** Result of the extraction in {@link #extractAssigningConstructor(Class, List)}. */
static
class
AssigningConstructor
{
public
final
Constructor
<?>
constructor
;
public
final
List
<
String
>
parameterNames
;
private
AssigningConstructor
(
Constructor
<?>
constructor
,
List
<
String
>
parameterNames
)
{
this
.
constructor
=
constructor
;
this
.
parameterNames
=
parameterNames
;
}
}
/**
* Checks whether the given constructor takes all of the given fields with matching (possibly
* primitive) type and name. An assigning constructor can define the order of fields.
*/
static
@Nullable
AssigningConstructor
extractAssigningConstructor
(
Class
<?>
clazz
,
List
<
Field
>
fields
)
{
AssigningConstructor
foundConstructor
=
null
;
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
final
boolean
qualifyingConstructor
=
Modifier
.
isPublic
(
constructor
.
getModifiers
())
&&
constructor
.
getParameterTypes
().
length
==
fields
.
size
();
if
(!
qualifyingConstructor
)
{
continue
;
}
final
List
<
String
>
parameterNames
=
extractConstructorParameterNames
(
constructor
,
fields
);
if
(
parameterNames
!=
null
)
{
if
(
foundConstructor
!=
null
)
{
throw
extractionError
(
"Multiple constructors found that assign all fields for class '%s'."
,
clazz
.
getName
());
}
foundConstructor
=
new
AssigningConstructor
(
constructor
,
parameterNames
);
}
}
return
foundConstructor
;
}
/** Extracts the parameter names of a method if possible. */
static
@Nullable
List
<
String
>
extractMethodParameterNames
(
Method
method
)
{
return
extractExecutableNames
(
method
);
}
/**
* Extracts ordered parameter names from a constructor that takes all of the given fields with
* matching (possibly primitive) type and name.
*/
private
static
@Nullable
List
<
String
>
extractConstructorParameterNames
(
Constructor
<?>
constructor
,
List
<
Field
>
fields
)
{
final
Type
[]
parameterTypes
=
constructor
.
getGenericParameterTypes
();
List
<
String
>
parameterNames
=
extractExecutableNames
(
constructor
);
if
(
parameterNames
==
null
)
{
return
null
;
}
final
Map
<
String
,
Type
>
fieldMap
=
fields
.
stream
().
collect
(
Collectors
.
toMap
(
Field:
:
getName
,
Field:
:
getGenericType
));
// check that all fields are represented in the parameters of the constructor
for
(
int
i
=
0
;
i
<
parameterNames
.
size
();
i
++)
{
final
String
parameterName
=
parameterNames
.
get
(
i
);
final
Type
fieldType
=
fieldMap
.
get
(
parameterName
);
// might be null
final
Type
parameterType
=
parameterTypes
[
i
];
// we are tolerant here because frameworks such as Avro accept a boxed type even though
// the field is primitive
if
(!
primitiveToWrapper
(
parameterType
).
equals
(
primitiveToWrapper
(
fieldType
)))
{
return
null
;
}
}
return
parameterNames
;
}
private
static
@Nullable
List
<
String
>
extractExecutableNames
(
Executable
executable
)
{
final
int
offset
;
if
(!
Modifier
.
isStatic
(
executable
.
getModifiers
()))
{
// remove "this" as first parameter
offset
=
1
;
}
else
{
offset
=
0
;
}
// by default parameter names are "arg0, arg1, arg2, ..." if compiler flag is not set
// so we need to extract them manually if possible
List
<
String
>
parameterNames
=
Stream
.
of
(
executable
.
getParameters
())
.
map
(
Parameter:
:
getName
)
.
collect
(
Collectors
.
toList
());
if
(
parameterNames
.
stream
().
allMatch
(
n
->
n
.
startsWith
(
"arg"
)))
{
final
ParameterExtractor
extractor
;
if
(
executable
instanceof
Constructor
)
{
extractor
=
new
ParameterExtractor
((
Constructor
<?>)
executable
);
}
else
{
extractor
=
new
ParameterExtractor
((
Method
)
executable
);
}
getClassReader
(
executable
.
getDeclaringClass
()).
accept
(
extractor
,
0
);
final
List
<
String
>
extractedNames
=
extractor
.
getParameterNames
();
if
(
extractedNames
.
size
()
==
0
)
{
return
null
;
}
// remove "this" and additional local variables
// select less names if class file has not the required information
parameterNames
=
extractedNames
.
subList
(
offset
,
Math
.
min
(
executable
.
getParameterCount
()
+
offset
,
extractedNames
.
size
()));
}
if
(
parameterNames
.
size
()
!=
executable
.
getParameterCount
())
{
return
null
;
}
return
parameterNames
;
}
private
static
ClassReader
getClassReader
(
Class
<?>
cls
)
{
final
String
className
=
cls
.
getName
().
replaceFirst
(
"^.*\\."
,
""
)
+
".class"
;
if
(
ClassPool
.
exist
(
cls
.
getName
())){
return
new
ClassReader
(
ClassPool
.
get
(
cls
.
getName
()).
getClassByte
());
}
try
{
return
new
ClassReader
(
cls
.
getResourceAsStream
(
className
));
}
catch
(
IOException
e
)
{
throw
new
IllegalStateException
(
"Could not instantiate ClassReader."
,
e
);
}
}
/**
* Extracts the parameter names and descriptors from a constructor or method. Assuming the
* existence of a local variable table.
*
* <p>For example:
*
* <pre>{@code
* public WC(java.lang.String arg0, long arg1) { // <init> //(Ljava/lang/String;J)V
* <localVar:index=0 , name=this , desc=Lorg/apache/flink/WC;, sig=null, start=L1, end=L2>
* <localVar:index=1 , name=word , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=frequency , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal2 , desc=J, sig=null, start=L1, end=L2>
* }
* }</pre>
*/
private
static
class
ParameterExtractor
extends
ClassVisitor
{
private
static
final
int
OPCODE
=
Opcodes
.
ASM7
;
private
final
String
methodDescriptor
;
private
final
List
<
String
>
parameterNames
=
new
ArrayList
<>();
ParameterExtractor
(
Constructor
<?>
constructor
)
{
super
(
OPCODE
);
methodDescriptor
=
getConstructorDescriptor
(
constructor
);
}
ParameterExtractor
(
Method
method
)
{
super
(
OPCODE
);
methodDescriptor
=
getMethodDescriptor
(
method
);
}
List
<
String
>
getParameterNames
()
{
return
parameterNames
;
}
@Override
public
MethodVisitor
visitMethod
(
int
access
,
String
name
,
String
descriptor
,
String
signature
,
String
[]
exceptions
)
{
if
(
descriptor
.
equals
(
methodDescriptor
))
{
return
new
MethodVisitor
(
OPCODE
)
{
@Override
public
void
visitLocalVariable
(
String
name
,
String
descriptor
,
String
signature
,
Label
start
,
Label
end
,
int
index
)
{
parameterNames
.
add
(
name
);
}
};
}
return
super
.
visitMethod
(
access
,
name
,
descriptor
,
signature
,
exceptions
);
}
}
// --------------------------------------------------------------------------------------------
// Class Assignment and Boxing
//
// copied from o.a.commons.lang3.ClassUtils (commons-lang3:3.3.2)
// --------------------------------------------------------------------------------------------
/**
* Checks if one {@code Class} can be assigned to a variable of another {@code Class}.
*
* <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into
* account widenings of primitive classes and {@code null}s.
*
* <p>Primitive widenings allow an int to be assigned to a long, float or double. This method
* returns the correct result for these cases.
*
* <p>{@code Null} may be assigned to any reference type. This method will return {@code true}
* if {@code null} is passed in and the toClass is non-primitive.
*
* <p>Specifically, this method tests whether the type represented by the specified {@code
* Class} parameter can be converted to the type represented by this {@code Class} object via an
* identity conversion widening primitive or widening reference conversion. See <em><a
* href="http://docs.oracle.com/javase/specs/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
*
* @param cls the Class to check, may be null
* @param toClass the Class to try to assign into, returns false if null
* @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
* @return {@code true} if assignment possible
*/
public
static
boolean
isAssignable
(
Class
<?>
cls
,
final
Class
<?>
toClass
,
final
boolean
autoboxing
)
{
if
(
toClass
==
null
)
{
return
false
;
}
// have to check for null, as isAssignableFrom doesn't
if
(
cls
==
null
)
{
return
!
toClass
.
isPrimitive
();
}
// autoboxing:
if
(
autoboxing
)
{
if
(
cls
.
isPrimitive
()
&&
!
toClass
.
isPrimitive
())
{
cls
=
primitiveToWrapper
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
if
(
toClass
.
isPrimitive
()
&&
!
cls
.
isPrimitive
())
{
cls
=
wrapperToPrimitive
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
}
if
(
cls
.
equals
(
toClass
))
{
return
true
;
}
if
(
cls
.
isPrimitive
())
{
if
(!
toClass
.
isPrimitive
())
{
return
false
;
}
if
(
Integer
.
TYPE
.
equals
(
cls
))
{
return
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Long
.
TYPE
.
equals
(
cls
))
{
return
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Boolean
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Double
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Float
.
TYPE
.
equals
(
cls
))
{
return
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Character
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Short
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Byte
.
TYPE
.
equals
(
cls
))
{
return
Short
.
TYPE
.
equals
(
toClass
)
||
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
// should never get here
return
false
;
}
return
toClass
.
isAssignableFrom
(
cls
);
}
/** Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
primitiveWrapperMap
=
new
HashMap
<>();
static
{
primitiveWrapperMap
.
put
(
Boolean
.
TYPE
,
Boolean
.
class
);
primitiveWrapperMap
.
put
(
Byte
.
TYPE
,
Byte
.
class
);
primitiveWrapperMap
.
put
(
Character
.
TYPE
,
Character
.
class
);
primitiveWrapperMap
.
put
(
Short
.
TYPE
,
Short
.
class
);
primitiveWrapperMap
.
put
(
Integer
.
TYPE
,
Integer
.
class
);
primitiveWrapperMap
.
put
(
Long
.
TYPE
,
Long
.
class
);
primitiveWrapperMap
.
put
(
Double
.
TYPE
,
Double
.
class
);
primitiveWrapperMap
.
put
(
Float
.
TYPE
,
Float
.
class
);
primitiveWrapperMap
.
put
(
Void
.
TYPE
,
Void
.
TYPE
);
}
/** Maps wrapper {@code Class}es to their corresponding primitive types. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
wrapperPrimitiveMap
=
new
HashMap
<>();
static
{
for
(
final
Class
<?>
primitiveClass
:
primitiveWrapperMap
.
keySet
())
{
final
Class
<?>
wrapperClass
=
primitiveWrapperMap
.
get
(
primitiveClass
);
if
(!
primitiveClass
.
equals
(
wrapperClass
))
{
wrapperPrimitiveMap
.
put
(
wrapperClass
,
primitiveClass
);
}
}
}
/**
* Converts the specified primitive Class object to its corresponding wrapper Class object.
*
* <p>NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
*
* @param cls the class to convert, may be null
* @return the wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive.
* {@code null} if null input.
* @since 2.1
*/
public
static
Class
<?>
primitiveToWrapper
(
final
Class
<?>
cls
)
{
Class
<?>
convertedClass
=
cls
;
if
(
cls
!=
null
&&
cls
.
isPrimitive
())
{
convertedClass
=
primitiveWrapperMap
.
get
(
cls
);
}
return
convertedClass
;
}
/**
* Converts the specified wrapper class to its corresponding primitive class.
*
* <p>This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is
* a wrapper class for a primitive type, this primitive type will be returned (e.g. {@code
* Integer.TYPE} for {@code Integer.class}). For other classes, or if the parameter is
* <b>null</b>, the return value is <b>null</b>.
*
* @param cls the class to convert, may be <b>null</b>
* @return the corresponding primitive type if {@code cls} is a wrapper class, <b>null</b>
* otherwise
* @see #primitiveToWrapper(Class)
* @since 2.4
*/
public
static
Class
<?>
wrapperToPrimitive
(
final
Class
<?>
cls
)
{
return
wrapperPrimitiveMap
.
get
(
cls
);
}
// --------------------------------------------------------------------------------------------
private
ExtractionUtils
()
{
// no instantiation
}
}
dlink-client/dlink-client-1.13/src/main/java/org/apache/flink/table/types/extraction/ExtractionUtils.java
0 → 100644
View file @
ea875c21
/*
* 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
.
flink
.
table
.
types
.
extraction
;
import
com.dlink.pool.ClassPool
;
import
org.apache.flink.annotation.Internal
;
import
org.apache.flink.api.common.typeutils.TypeSerializer
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.ValidationException
;
import
org.apache.flink.table.catalog.DataTypeFactory
;
import
org.apache.flink.table.types.DataType
;
import
org.apache.flink.table.types.logical.StructuredType
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassReader
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Label
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.MethodVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Opcodes
;
import
javax.annotation.Nullable
;
import
java.io.IOException
;
import
java.lang.annotation.Annotation
;
import
java.lang.reflect.Constructor
;
import
java.lang.reflect.Executable
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.Modifier
;
import
java.lang.reflect.Parameter
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Type
;
import
java.lang.reflect.TypeVariable
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.function.Function
;
import
java.util.regex.Pattern
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getConstructorDescriptor
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getMethodDescriptor
;
/** Utilities for performing reflection tasks. */
@Internal
public
final
class
ExtractionUtils
{
// --------------------------------------------------------------------------------------------
// Methods shared across packages
// --------------------------------------------------------------------------------------------
/** Collects methods of the given name. */
public
static
List
<
Method
>
collectMethods
(
Class
<?>
function
,
String
methodName
)
{
return
Arrays
.
stream
(
function
.
getMethods
())
.
filter
(
method
->
method
.
getName
().
equals
(
methodName
))
.
sorted
(
Comparator
.
comparing
(
Method:
:
toString
))
// for deterministic order
.
collect
(
Collectors
.
toList
());
}
/**
* Checks whether a method/constructor can be called with the given argument classes. This
* includes type widening and vararg. {@code null} is a wildcard.
*
* <p>E.g., {@code (int.class, int.class)} matches {@code f(Object...), f(int, int), f(Integer,
* Object)} and so forth.
*/
public
static
boolean
isInvokable
(
Executable
executable
,
Class
<?>...
classes
)
{
final
int
m
=
executable
.
getModifiers
();
if
(!
Modifier
.
isPublic
(
m
))
{
return
false
;
}
final
int
paramCount
=
executable
.
getParameterCount
();
final
int
classCount
=
classes
.
length
;
// check for enough classes for each parameter
if
((!
executable
.
isVarArgs
()
&&
classCount
!=
paramCount
)
||
(
executable
.
isVarArgs
()
&&
classCount
<
paramCount
-
1
))
{
return
false
;
}
int
currentClass
=
0
;
for
(
int
currentParam
=
0
;
currentParam
<
paramCount
;
currentParam
++)
{
final
Class
<?>
param
=
executable
.
getParameterTypes
()[
currentParam
];
// last parameter is a vararg that needs to consume remaining classes
if
(
currentParam
==
paramCount
-
1
&&
executable
.
isVarArgs
())
{
final
Class
<?>
paramComponent
=
executable
.
getParameterTypes
()[
currentParam
].
getComponentType
();
// we have more than 1 classes left so the vararg needs to consume them all
if
(
classCount
-
currentClass
>
1
)
{
while
(
currentClass
<
classCount
&&
ExtractionUtils
.
isAssignable
(
classes
[
currentClass
],
paramComponent
,
true
))
{
currentClass
++;
}
}
else
if
(
currentClass
<
classCount
&&
(
parameterMatches
(
classes
[
currentClass
],
param
)
||
parameterMatches
(
classes
[
currentClass
],
paramComponent
)))
{
currentClass
++;
}
}
// entire parameter matches
else
if
(
parameterMatches
(
classes
[
currentClass
],
param
))
{
currentClass
++;
}
}
// check if all classes have been consumed
return
currentClass
==
classCount
;
}
private
static
boolean
parameterMatches
(
Class
<?>
clz
,
Class
<?>
param
)
{
return
clz
==
null
||
ExtractionUtils
.
isAssignable
(
clz
,
param
,
true
);
}
/** Creates a method signature string like {@code int eval(Integer, String)}. */
public
static
String
createMethodSignatureString
(
String
methodName
,
Class
<?>[]
parameters
,
@Nullable
Class
<?>
returnType
)
{
final
StringBuilder
builder
=
new
StringBuilder
();
if
(
returnType
!=
null
)
{
builder
.
append
(
returnType
.
getCanonicalName
()).
append
(
" "
);
}
builder
.
append
(
methodName
)
.
append
(
Stream
.
of
(
parameters
)
.
map
(
parameter
->
{
// in case we don't know the parameter at this location
// (i.e. for accumulators)
if
(
parameter
==
null
)
{
return
"_"
;
}
else
{
return
parameter
.
getCanonicalName
();
}
})
.
collect
(
Collectors
.
joining
(
", "
,
"("
,
")"
)));
return
builder
.
toString
();
}
/**
* Validates the characteristics of a class for a {@link StructuredType} such as accessibility.
*/
public
static
void
validateStructuredClass
(
Class
<?>
clazz
)
{
final
int
m
=
clazz
.
getModifiers
();
if
(
Modifier
.
isAbstract
(
m
))
{
throw
extractionError
(
"Class '%s' must not be abstract."
,
clazz
.
getName
());
}
if
(!
Modifier
.
isPublic
(
m
))
{
throw
extractionError
(
"Class '%s' is not public."
,
clazz
.
getName
());
}
if
(
clazz
.
getEnclosingClass
()
!=
null
&&
(
clazz
.
getDeclaringClass
()
==
null
||
!
Modifier
.
isStatic
(
m
)))
{
throw
extractionError
(
"Class '%s' is a not a static, globally accessible class."
,
clazz
.
getName
());
}
}
/**
* Returns the field of a structured type. The logic is as broad as possible to support both
* Java and Scala in different flavors.
*/
public
static
Field
getStructuredField
(
Class
<?>
clazz
,
String
fieldName
)
{
final
String
normalizedFieldName
=
fieldName
.
toUpperCase
();
final
List
<
Field
>
fields
=
collectStructuredFields
(
clazz
);
for
(
Field
field
:
fields
)
{
if
(
field
.
getName
().
toUpperCase
().
equals
(
normalizedFieldName
))
{
return
field
;
}
}
throw
extractionError
(
"Could not find a field named '%s' in class '%s' for structured type."
,
fieldName
,
clazz
.
getName
());
}
/**
* Checks for a field getter of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldGetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
normalizeAccessorName
(
field
.
getName
());
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// get<Name>()
// is<Name>()
// <Name>() for Scala
final
String
normalizedMethodName
=
normalizeAccessorName
(
method
.
getName
());
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"GET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
"IS"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// equal to field type
final
Type
returnType
=
method
.
getGenericReturnType
();
final
boolean
hasReturnType
=
returnType
.
equals
(
field
.
getGenericType
());
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// no parameters
final
boolean
hasNoParameters
=
method
.
getParameterCount
()
==
0
;
if
(!
hasNoParameters
)
{
continue
;
}
// matching getter found
return
Optional
.
of
(
method
);
}
// no getter found
return
Optional
.
empty
();
}
/**
* Checks for a field setters of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldSetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
normalizeAccessorName
(
field
.
getName
());
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// set<Name>(type)
// <Name>(type)
// <Name>_$eq(type) for Scala
final
String
normalizedMethodName
=
normalizeAccessorName
(
method
.
getName
());
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"SET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
+
"$EQ"
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// void or the declaring class
final
Class
<?>
returnType
=
method
.
getReturnType
();
final
boolean
hasReturnType
=
returnType
==
Void
.
TYPE
||
returnType
==
clazz
;
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// one parameter that has the same (or primitive) type of the field
final
boolean
hasParameter
=
method
.
getParameterCount
()
==
1
&&
(
method
.
getGenericParameterTypes
()[
0
].
equals
(
field
.
getGenericType
())
||
primitiveToWrapper
(
method
.
getGenericParameterTypes
()[
0
])
.
equals
(
field
.
getGenericType
()));
if
(!
hasParameter
)
{
continue
;
}
// matching setter found
return
Optional
.
of
(
method
);
}
// no setter found
return
Optional
.
empty
();
}
private
static
String
normalizeAccessorName
(
String
name
)
{
return
name
.
toUpperCase
().
replaceAll
(
Pattern
.
quote
(
"_"
),
""
);
}
/**
* Checks for an invokable constructor matching the given arguments.
*
* @see #isInvokable(Executable, Class[])
*/
public
static
boolean
hasInvokableConstructor
(
Class
<?>
clazz
,
Class
<?>...
classes
)
{
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
if
(
isInvokable
(
constructor
,
classes
))
{
return
true
;
}
}
return
false
;
}
/** Checks whether a field is directly readable without a getter. */
public
static
boolean
isStructuredFieldDirectlyReadable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is directly readable
return
Modifier
.
isPublic
(
m
);
}
/** Checks whether a field is directly writable without a setter or constructor. */
public
static
boolean
isStructuredFieldDirectlyWritable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly writable
return
Modifier
.
isPublic
(
m
);
}
/**
* A minimal version to extract a generic parameter from a given class.
*
* <p>This method should only be used for very specific use cases, in most cases {@link
* DataTypeExtractor#extractFromGeneric(DataTypeFactory, Class, int, Type)} should be more
* appropriate.
*/
public
static
Optional
<
Class
<?>>
extractSimpleGeneric
(
Class
<?>
baseClass
,
Class
<?>
clazz
,
int
pos
)
{
try
{
if
(
clazz
.
getSuperclass
()
!=
baseClass
)
{
return
Optional
.
empty
();
}
final
Type
t
=
((
ParameterizedType
)
clazz
.
getGenericSuperclass
())
.
getActualTypeArguments
()[
pos
];
return
Optional
.
ofNullable
(
toClass
(
t
));
}
catch
(
Exception
unused
)
{
return
Optional
.
empty
();
}
}
// --------------------------------------------------------------------------------------------
// Methods intended for this package
// --------------------------------------------------------------------------------------------
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
String
message
,
Object
...
args
)
{
return
extractionError
(
null
,
message
,
args
);
}
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
Throwable
cause
,
String
message
,
Object
...
args
)
{
return
new
ValidationException
(
String
.
format
(
message
,
args
),
cause
);
}
/**
* Collects the partially ordered type hierarchy (i.e. all involved super classes and super
* interfaces) of the given type.
*/
static
List
<
Type
>
collectTypeHierarchy
(
Type
type
)
{
Type
currentType
=
type
;
Class
<?>
currentClass
=
toClass
(
type
);
final
List
<
Type
>
typeHierarchy
=
new
ArrayList
<>();
while
(
currentClass
!=
null
)
{
// collect type
typeHierarchy
.
add
(
currentType
);
// collect super interfaces
for
(
Type
genericInterface
:
currentClass
.
getGenericInterfaces
())
{
final
Class
<?>
interfaceClass
=
toClass
(
genericInterface
);
if
(
interfaceClass
!=
null
)
{
typeHierarchy
.
addAll
(
collectTypeHierarchy
(
genericInterface
));
}
}
currentType
=
currentClass
.
getGenericSuperclass
();
currentClass
=
toClass
(
currentType
);
}
return
typeHierarchy
;
}
/** Converts a {@link Type} to {@link Class} if possible, {@code null} otherwise. */
static
@Nullable
Class
<?>
toClass
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
(
Class
<?>)
type
;
}
else
if
(
type
instanceof
ParameterizedType
)
{
// this is always a class
return
(
Class
<?>)
((
ParameterizedType
)
type
).
getRawType
();
}
// unsupported: generic arrays, type variables, wildcard types
return
null
;
}
/** Creates a raw data type. */
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
static
DataType
createRawType
(
DataTypeFactory
typeFactory
,
@Nullable
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
,
@Nullable
Class
<?>
conversionClass
)
{
if
(
rawSerializer
!=
null
)
{
return
DataTypes
.
RAW
(
(
Class
)
createConversionClass
(
conversionClass
),
instantiateRawSerializer
(
rawSerializer
));
}
return
typeFactory
.
createRawDataType
(
createConversionClass
(
conversionClass
));
}
static
Class
<?>
createConversionClass
(
@Nullable
Class
<?>
conversionClass
)
{
if
(
conversionClass
!=
null
)
{
return
conversionClass
;
}
return
Object
.
class
;
}
private
static
TypeSerializer
<?>
instantiateRawSerializer
(
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
)
{
try
{
return
rawSerializer
.
newInstance
();
}
catch
(
Exception
e
)
{
throw
extractionError
(
e
,
"Cannot instantiate type serializer '%s' for RAW type. "
+
"Make sure the class is publicly accessible and has a default constructor."
,
rawSerializer
.
getName
());
}
}
/** Resolves a {@link TypeVariable} using the given type hierarchy if possible. */
static
Type
resolveVariable
(
List
<
Type
>
typeHierarchy
,
TypeVariable
<?>
variable
)
{
// iterate through hierarchy from top to bottom until type variable gets a non-variable
// assigned
for
(
int
i
=
typeHierarchy
.
size
()
-
1
;
i
>=
0
;
i
--)
{
final
Type
currentType
=
typeHierarchy
.
get
(
i
);
if
(
currentType
instanceof
ParameterizedType
)
{
final
Type
resolvedType
=
resolveVariableInParameterizedType
(
variable
,
(
ParameterizedType
)
currentType
);
if
(
resolvedType
instanceof
TypeVariable
)
{
// follow type variables transitively
variable
=
(
TypeVariable
<?>)
resolvedType
;
}
else
if
(
resolvedType
!=
null
)
{
return
resolvedType
;
}
}
}
// unresolved variable
return
variable
;
}
private
static
@Nullable
Type
resolveVariableInParameterizedType
(
TypeVariable
<?>
variable
,
ParameterizedType
currentType
)
{
final
Class
<?>
currentRaw
=
(
Class
<?>)
currentType
.
getRawType
();
final
TypeVariable
<?>[]
currentVariables
=
currentRaw
.
getTypeParameters
();
// search for matching type variable
for
(
int
paramPos
=
0
;
paramPos
<
currentVariables
.
length
;
paramPos
++)
{
if
(
typeVariableEquals
(
variable
,
currentVariables
[
paramPos
]))
{
return
currentType
.
getActualTypeArguments
()[
paramPos
];
}
}
return
null
;
}
private
static
boolean
typeVariableEquals
(
TypeVariable
<?>
variable
,
TypeVariable
<?>
currentVariable
)
{
return
currentVariable
.
getGenericDeclaration
().
equals
(
variable
.
getGenericDeclaration
())
&&
currentVariable
.
getName
().
equals
(
variable
.
getName
());
}
/**
* Validates if a given type is not already contained in the type hierarchy of a structured
* type.
*
* <p>Otherwise this would lead to infinite data type extraction cycles.
*/
static
void
validateStructuredSelfReference
(
Type
t
,
List
<
Type
>
typeHierarchy
)
{
final
Class
<?>
clazz
=
toClass
(
t
);
if
(
clazz
!=
null
&&
!
clazz
.
isInterface
()
&&
clazz
!=
Object
.
class
&&
typeHierarchy
.
contains
(
t
))
{
throw
extractionError
(
"Cyclic reference detected for class '%s'. Attributes of structured types must not "
+
"(transitively) reference the structured type itself."
,
clazz
.
getName
());
}
}
/** Returns the fields of a class for a {@link StructuredType}. */
static
List
<
Field
>
collectStructuredFields
(
Class
<?>
clazz
)
{
final
List
<
Field
>
fields
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Field
[]
declaredFields
=
clazz
.
getDeclaredFields
();
Stream
.
of
(
declaredFields
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
!
Modifier
.
isStatic
(
m
)
&&
!
Modifier
.
isTransient
(
m
);
})
.
forEach
(
fields:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
fields
;
}
/** Validates if a field is properly readable either directly or through a getter. */
static
void
validateStructuredFieldReadability
(
Class
<?>
clazz
,
Field
field
)
{
// field is accessible
if
(
isStructuredFieldDirectlyReadable
(
field
))
{
return
;
}
// field needs a getter
if
(!
getStructuredFieldGetter
(
clazz
,
field
).
isPresent
())
{
throw
extractionError
(
"Field '%s' of class '%s' is neither publicly accessible nor does it have "
+
"a corresponding getter method."
,
field
.
getName
(),
clazz
.
getName
());
}
}
/**
* Checks if a field is mutable or immutable. Returns {@code true} if the field is properly
* mutable. Returns {@code false} if it is properly immutable.
*/
static
boolean
isStructuredFieldMutable
(
Class
<?>
clazz
,
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly mutable
if
(
Modifier
.
isPublic
(
m
))
{
return
true
;
}
// field has setters by which it is mutable
if
(
getStructuredFieldSetter
(
clazz
,
field
).
isPresent
())
{
return
true
;
}
throw
extractionError
(
"Field '%s' of class '%s' is mutable but is neither publicly accessible nor does it have "
+
"a corresponding setter method."
,
field
.
getName
(),
clazz
.
getName
());
}
/** Returns the boxed type of a primitive type. */
static
Type
primitiveToWrapper
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
primitiveToWrapper
((
Class
<?>)
type
);
}
return
type
;
}
/** Collects all methods that qualify as methods of a {@link StructuredType}. */
static
List
<
Method
>
collectStructuredMethods
(
Class
<?>
clazz
)
{
final
List
<
Method
>
methods
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Method
[]
declaredMethods
=
clazz
.
getDeclaredMethods
();
Stream
.
of
(
declaredMethods
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
Modifier
.
isPublic
(
m
)
&&
!
Modifier
.
isNative
(
m
)
&&
!
Modifier
.
isAbstract
(
m
);
})
.
forEach
(
methods:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
methods
;
}
/**
* Collects all annotations of the given type defined in the current class or superclasses.
* Duplicates are ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfClass
(
Class
<
T
>
annotation
,
Class
<?>
annotatedClass
)
{
final
List
<
Class
<?>>
classHierarchy
=
new
ArrayList
<>();
Class
<?>
currentClass
=
annotatedClass
;
while
(
currentClass
!=
null
)
{
classHierarchy
.
add
(
currentClass
);
currentClass
=
currentClass
.
getSuperclass
();
}
// convert to top down
Collections
.
reverse
(
classHierarchy
);
return
classHierarchy
.
stream
()
.
flatMap
(
c
->
Stream
.
of
(
c
.
getAnnotationsByType
(
annotation
)))
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
}
/**
* Collects all annotations of the given type defined in the given method. Duplicates are
* ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfMethod
(
Class
<
T
>
annotation
,
Method
annotatedMethod
)
{
return
new
LinkedHashSet
<>(
Arrays
.
asList
(
annotatedMethod
.
getAnnotationsByType
(
annotation
)));
}
// --------------------------------------------------------------------------------------------
// Parameter Extraction Utilities
// --------------------------------------------------------------------------------------------
/** Result of the extraction in {@link #extractAssigningConstructor(Class, List)}. */
public
static
class
AssigningConstructor
{
public
final
Constructor
<?>
constructor
;
public
final
List
<
String
>
parameterNames
;
private
AssigningConstructor
(
Constructor
<?>
constructor
,
List
<
String
>
parameterNames
)
{
this
.
constructor
=
constructor
;
this
.
parameterNames
=
parameterNames
;
}
}
/**
* Checks whether the given constructor takes all of the given fields with matching (possibly
* primitive) type and name. An assigning constructor can define the order of fields.
*/
public
static
@Nullable
AssigningConstructor
extractAssigningConstructor
(
Class
<?>
clazz
,
List
<
Field
>
fields
)
{
AssigningConstructor
foundConstructor
=
null
;
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
final
boolean
qualifyingConstructor
=
Modifier
.
isPublic
(
constructor
.
getModifiers
())
&&
constructor
.
getParameterTypes
().
length
==
fields
.
size
();
if
(!
qualifyingConstructor
)
{
continue
;
}
final
List
<
String
>
parameterNames
=
extractConstructorParameterNames
(
constructor
,
fields
);
if
(
parameterNames
!=
null
)
{
if
(
foundConstructor
!=
null
)
{
throw
extractionError
(
"Multiple constructors found that assign all fields for class '%s'."
,
clazz
.
getName
());
}
foundConstructor
=
new
AssigningConstructor
(
constructor
,
parameterNames
);
}
}
return
foundConstructor
;
}
/** Extracts the parameter names of a method if possible. */
static
@Nullable
List
<
String
>
extractMethodParameterNames
(
Method
method
)
{
return
extractExecutableNames
(
method
);
}
/**
* Extracts ordered parameter names from a constructor that takes all of the given fields with
* matching (possibly primitive and lenient) type and name.
*/
private
static
@Nullable
List
<
String
>
extractConstructorParameterNames
(
Constructor
<?>
constructor
,
List
<
Field
>
fields
)
{
final
Type
[]
parameterTypes
=
constructor
.
getGenericParameterTypes
();
List
<
String
>
parameterNames
=
extractExecutableNames
(
constructor
);
if
(
parameterNames
==
null
)
{
return
null
;
}
final
Map
<
String
,
Field
>
fieldMap
=
fields
.
stream
()
.
collect
(
Collectors
.
toMap
(
f
->
normalizeAccessorName
(
f
.
getName
()),
Function
.
identity
()));
// check that all fields are represented in the parameters of the constructor
final
List
<
String
>
fieldNames
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
parameterNames
.
size
();
i
++)
{
final
String
parameterName
=
normalizeAccessorName
(
parameterNames
.
get
(
i
));
final
Field
field
=
fieldMap
.
get
(
parameterName
);
if
(
field
==
null
)
{
return
null
;
}
final
Type
fieldType
=
field
.
getGenericType
();
final
Type
parameterType
=
parameterTypes
[
i
];
// we are tolerant here because frameworks such as Avro accept a boxed type even though
// the field is primitive
if
(!
primitiveToWrapper
(
parameterType
).
equals
(
primitiveToWrapper
(
fieldType
)))
{
return
null
;
}
fieldNames
.
add
(
field
.
getName
());
}
return
fieldNames
;
}
private
static
@Nullable
List
<
String
>
extractExecutableNames
(
Executable
executable
)
{
final
int
offset
;
if
(!
Modifier
.
isStatic
(
executable
.
getModifiers
()))
{
// remove "this" as first parameter
offset
=
1
;
}
else
{
offset
=
0
;
}
// by default parameter names are "arg0, arg1, arg2, ..." if compiler flag is not set
// so we need to extract them manually if possible
List
<
String
>
parameterNames
=
Stream
.
of
(
executable
.
getParameters
())
.
map
(
Parameter:
:
getName
)
.
collect
(
Collectors
.
toList
());
if
(
parameterNames
.
stream
().
allMatch
(
n
->
n
.
startsWith
(
"arg"
)))
{
final
ParameterExtractor
extractor
;
if
(
executable
instanceof
Constructor
)
{
extractor
=
new
ParameterExtractor
((
Constructor
<?>)
executable
);
}
else
{
extractor
=
new
ParameterExtractor
((
Method
)
executable
);
}
getClassReader
(
executable
.
getDeclaringClass
()).
accept
(
extractor
,
0
);
final
List
<
String
>
extractedNames
=
extractor
.
getParameterNames
();
if
(
extractedNames
.
size
()
==
0
)
{
return
null
;
}
// remove "this" and additional local variables
// select less names if class file has not the required information
parameterNames
=
extractedNames
.
subList
(
offset
,
Math
.
min
(
executable
.
getParameterCount
()
+
offset
,
extractedNames
.
size
()));
}
if
(
parameterNames
.
size
()
!=
executable
.
getParameterCount
())
{
return
null
;
}
return
parameterNames
;
}
private
static
ClassReader
getClassReader
(
Class
<?>
cls
)
{
final
String
className
=
cls
.
getName
().
replaceFirst
(
"^.*\\."
,
""
)
+
".class"
;
if
(
ClassPool
.
exist
(
cls
.
getName
())){
return
new
ClassReader
(
ClassPool
.
get
(
cls
.
getName
()).
getClassByte
());
}
try
{
return
new
ClassReader
(
cls
.
getResourceAsStream
(
className
));
}
catch
(
IOException
e
)
{
throw
new
IllegalStateException
(
"Could not instantiate ClassReader."
,
e
);
}
}
/**
* Extracts the parameter names and descriptors from a constructor or method. Assuming the
* existence of a local variable table.
*
* <p>For example:
*
* <pre>{@code
* public WC(java.lang.String arg0, long arg1) { // <init> //(Ljava/lang/String;J)V
* <localVar:index=0 , name=this , desc=Lorg/apache/flink/WC;, sig=null, start=L1, end=L2>
* <localVar:index=1 , name=word , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=frequency , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal2 , desc=J, sig=null, start=L1, end=L2>
* }
* }</pre>
*/
private
static
class
ParameterExtractor
extends
ClassVisitor
{
private
static
final
int
OPCODE
=
Opcodes
.
ASM7
;
private
final
String
methodDescriptor
;
private
final
List
<
String
>
parameterNames
=
new
ArrayList
<>();
ParameterExtractor
(
Constructor
<?>
constructor
)
{
super
(
OPCODE
);
methodDescriptor
=
getConstructorDescriptor
(
constructor
);
}
ParameterExtractor
(
Method
method
)
{
super
(
OPCODE
);
methodDescriptor
=
getMethodDescriptor
(
method
);
}
List
<
String
>
getParameterNames
()
{
return
parameterNames
;
}
@Override
public
MethodVisitor
visitMethod
(
int
access
,
String
name
,
String
descriptor
,
String
signature
,
String
[]
exceptions
)
{
if
(
descriptor
.
equals
(
methodDescriptor
))
{
return
new
MethodVisitor
(
OPCODE
)
{
@Override
public
void
visitLocalVariable
(
String
name
,
String
descriptor
,
String
signature
,
Label
start
,
Label
end
,
int
index
)
{
parameterNames
.
add
(
name
);
}
};
}
return
super
.
visitMethod
(
access
,
name
,
descriptor
,
signature
,
exceptions
);
}
}
// --------------------------------------------------------------------------------------------
// Class Assignment and Boxing
//
// copied from o.a.commons.lang3.ClassUtils (commons-lang3:3.3.2)
// --------------------------------------------------------------------------------------------
/**
* Checks if one {@code Class} can be assigned to a variable of another {@code Class}.
*
* <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into
* account widenings of primitive classes and {@code null}s.
*
* <p>Primitive widenings allow an int to be assigned to a long, float or double. This method
* returns the correct result for these cases.
*
* <p>{@code Null} may be assigned to any reference type. This method will return {@code true}
* if {@code null} is passed in and the toClass is non-primitive.
*
* <p>Specifically, this method tests whether the type represented by the specified {@code
* Class} parameter can be converted to the type represented by this {@code Class} object via an
* identity conversion widening primitive or widening reference conversion. See <em><a
* href="http://docs.oracle.com/javase/specs/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
*
* @param cls the Class to check, may be null
* @param toClass the Class to try to assign into, returns false if null
* @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
* @return {@code true} if assignment possible
*/
public
static
boolean
isAssignable
(
Class
<?>
cls
,
final
Class
<?>
toClass
,
final
boolean
autoboxing
)
{
if
(
toClass
==
null
)
{
return
false
;
}
// have to check for null, as isAssignableFrom doesn't
if
(
cls
==
null
)
{
return
!
toClass
.
isPrimitive
();
}
// autoboxing:
if
(
autoboxing
)
{
if
(
cls
.
isPrimitive
()
&&
!
toClass
.
isPrimitive
())
{
cls
=
primitiveToWrapper
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
if
(
toClass
.
isPrimitive
()
&&
!
cls
.
isPrimitive
())
{
cls
=
wrapperToPrimitive
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
}
if
(
cls
.
equals
(
toClass
))
{
return
true
;
}
if
(
cls
.
isPrimitive
())
{
if
(!
toClass
.
isPrimitive
())
{
return
false
;
}
if
(
Integer
.
TYPE
.
equals
(
cls
))
{
return
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Long
.
TYPE
.
equals
(
cls
))
{
return
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Boolean
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Double
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Float
.
TYPE
.
equals
(
cls
))
{
return
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Character
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Short
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Byte
.
TYPE
.
equals
(
cls
))
{
return
Short
.
TYPE
.
equals
(
toClass
)
||
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
// should never get here
return
false
;
}
return
toClass
.
isAssignableFrom
(
cls
);
}
/** Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
primitiveWrapperMap
=
new
HashMap
<>();
static
{
primitiveWrapperMap
.
put
(
Boolean
.
TYPE
,
Boolean
.
class
);
primitiveWrapperMap
.
put
(
Byte
.
TYPE
,
Byte
.
class
);
primitiveWrapperMap
.
put
(
Character
.
TYPE
,
Character
.
class
);
primitiveWrapperMap
.
put
(
Short
.
TYPE
,
Short
.
class
);
primitiveWrapperMap
.
put
(
Integer
.
TYPE
,
Integer
.
class
);
primitiveWrapperMap
.
put
(
Long
.
TYPE
,
Long
.
class
);
primitiveWrapperMap
.
put
(
Double
.
TYPE
,
Double
.
class
);
primitiveWrapperMap
.
put
(
Float
.
TYPE
,
Float
.
class
);
primitiveWrapperMap
.
put
(
Void
.
TYPE
,
Void
.
TYPE
);
}
/** Maps wrapper {@code Class}es to their corresponding primitive types. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
wrapperPrimitiveMap
=
new
HashMap
<>();
static
{
for
(
final
Class
<?>
primitiveClass
:
primitiveWrapperMap
.
keySet
())
{
final
Class
<?>
wrapperClass
=
primitiveWrapperMap
.
get
(
primitiveClass
);
if
(!
primitiveClass
.
equals
(
wrapperClass
))
{
wrapperPrimitiveMap
.
put
(
wrapperClass
,
primitiveClass
);
}
}
}
/**
* Converts the specified primitive Class object to its corresponding wrapper Class object.
*
* <p>NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
*
* @param cls the class to convert, may be null
* @return the wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive.
* {@code null} if null input.
* @since 2.1
*/
public
static
Class
<?>
primitiveToWrapper
(
final
Class
<?>
cls
)
{
Class
<?>
convertedClass
=
cls
;
if
(
cls
!=
null
&&
cls
.
isPrimitive
())
{
convertedClass
=
primitiveWrapperMap
.
get
(
cls
);
}
return
convertedClass
;
}
/**
* Converts the specified wrapper class to its corresponding primitive class.
*
* <p>This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is
* a wrapper class for a primitive type, this primitive type will be returned (e.g. {@code
* Integer.TYPE} for {@code Integer.class}). For other classes, or if the parameter is
* <b>null</b>, the return value is <b>null</b>.
*
* @param cls the class to convert, may be <b>null</b>
* @return the corresponding primitive type if {@code cls} is a wrapper class, <b>null</b>
* otherwise
* @see #primitiveToWrapper(Class)
* @since 2.4
*/
public
static
Class
<?>
wrapperToPrimitive
(
final
Class
<?>
cls
)
{
return
wrapperPrimitiveMap
.
get
(
cls
);
}
// --------------------------------------------------------------------------------------------
private
ExtractionUtils
()
{
// no instantiation
}
}
dlink-client/dlink-client-1.14/src/main/java/org/apache/flink/table/types/extraction/ExtractionUtils.java
0 → 100644
View file @
ea875c21
/*
* 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
.
flink
.
table
.
types
.
extraction
;
import
com.dlink.pool.ClassPool
;
import
org.apache.flink.annotation.Internal
;
import
org.apache.flink.api.common.typeutils.TypeSerializer
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.ValidationException
;
import
org.apache.flink.table.catalog.DataTypeFactory
;
import
org.apache.flink.table.types.DataType
;
import
org.apache.flink.table.types.logical.StructuredType
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassReader
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.ClassVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Label
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.MethodVisitor
;
import
org.apache.flink.shaded.asm7.org.objectweb.asm.Opcodes
;
import
javax.annotation.Nullable
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.lang.annotation.Annotation
;
import
java.lang.reflect.Constructor
;
import
java.lang.reflect.Executable
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.Modifier
;
import
java.lang.reflect.Parameter
;
import
java.lang.reflect.ParameterizedType
;
import
java.lang.reflect.Type
;
import
java.lang.reflect.TypeVariable
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.function.Function
;
import
java.util.regex.Pattern
;
import
java.util.stream.Collectors
;
import
java.util.stream.Stream
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getConstructorDescriptor
;
import
static
org
.
apache
.
flink
.
shaded
.
asm7
.
org
.
objectweb
.
asm
.
Type
.
getMethodDescriptor
;
/** Utilities for performing reflection tasks. */
@Internal
public
final
class
ExtractionUtils
{
// --------------------------------------------------------------------------------------------
// Methods shared across packages
// --------------------------------------------------------------------------------------------
/** Collects methods of the given name. */
public
static
List
<
Method
>
collectMethods
(
Class
<?>
function
,
String
methodName
)
{
return
Arrays
.
stream
(
function
.
getMethods
())
.
filter
(
method
->
method
.
getName
().
equals
(
methodName
))
.
sorted
(
Comparator
.
comparing
(
Method:
:
toString
))
// for deterministic order
.
collect
(
Collectors
.
toList
());
}
/**
* Checks whether a method/constructor can be called with the given argument classes. This
* includes type widening and vararg. {@code null} is a wildcard.
*
* <p>E.g., {@code (int.class, int.class)} matches {@code f(Object...), f(int, int), f(Integer,
* Object)} and so forth.
*/
public
static
boolean
isInvokable
(
Executable
executable
,
Class
<?>...
classes
)
{
final
int
m
=
executable
.
getModifiers
();
if
(!
Modifier
.
isPublic
(
m
))
{
return
false
;
}
final
int
paramCount
=
executable
.
getParameterCount
();
final
int
classCount
=
classes
.
length
;
// check for enough classes for each parameter
if
((!
executable
.
isVarArgs
()
&&
classCount
!=
paramCount
)
||
(
executable
.
isVarArgs
()
&&
classCount
<
paramCount
-
1
))
{
return
false
;
}
int
currentClass
=
0
;
for
(
int
currentParam
=
0
;
currentParam
<
paramCount
;
currentParam
++)
{
final
Class
<?>
param
=
executable
.
getParameterTypes
()[
currentParam
];
// last parameter is a vararg that needs to consume remaining classes
if
(
currentParam
==
paramCount
-
1
&&
executable
.
isVarArgs
())
{
final
Class
<?>
paramComponent
=
executable
.
getParameterTypes
()[
currentParam
].
getComponentType
();
// we have more than 1 classes left so the vararg needs to consume them all
if
(
classCount
-
currentClass
>
1
)
{
while
(
currentClass
<
classCount
&&
ExtractionUtils
.
isAssignable
(
classes
[
currentClass
],
paramComponent
,
true
))
{
currentClass
++;
}
}
else
if
(
currentClass
<
classCount
&&
(
parameterMatches
(
classes
[
currentClass
],
param
)
||
parameterMatches
(
classes
[
currentClass
],
paramComponent
)))
{
currentClass
++;
}
}
// entire parameter matches
else
if
(
parameterMatches
(
classes
[
currentClass
],
param
))
{
currentClass
++;
}
}
// check if all classes have been consumed
return
currentClass
==
classCount
;
}
private
static
boolean
parameterMatches
(
Class
<?>
clz
,
Class
<?>
param
)
{
return
clz
==
null
||
ExtractionUtils
.
isAssignable
(
clz
,
param
,
true
);
}
/** Creates a method signature string like {@code int eval(Integer, String)}. */
public
static
String
createMethodSignatureString
(
String
methodName
,
Class
<?>[]
parameters
,
@Nullable
Class
<?>
returnType
)
{
final
StringBuilder
builder
=
new
StringBuilder
();
if
(
returnType
!=
null
)
{
builder
.
append
(
returnType
.
getCanonicalName
()).
append
(
" "
);
}
builder
.
append
(
methodName
)
.
append
(
Stream
.
of
(
parameters
)
.
map
(
parameter
->
{
// in case we don't know the parameter at this location
// (i.e. for accumulators)
if
(
parameter
==
null
)
{
return
"_"
;
}
else
{
return
parameter
.
getCanonicalName
();
}
})
.
collect
(
Collectors
.
joining
(
", "
,
"("
,
")"
)));
return
builder
.
toString
();
}
/**
* Validates the characteristics of a class for a {@link StructuredType} such as accessibility.
*/
public
static
void
validateStructuredClass
(
Class
<?>
clazz
)
{
final
int
m
=
clazz
.
getModifiers
();
if
(
Modifier
.
isAbstract
(
m
))
{
throw
extractionError
(
"Class '%s' must not be abstract."
,
clazz
.
getName
());
}
if
(!
Modifier
.
isPublic
(
m
))
{
throw
extractionError
(
"Class '%s' is not public."
,
clazz
.
getName
());
}
if
(
clazz
.
getEnclosingClass
()
!=
null
&&
(
clazz
.
getDeclaringClass
()
==
null
||
!
Modifier
.
isStatic
(
m
)))
{
throw
extractionError
(
"Class '%s' is a not a static, globally accessible class."
,
clazz
.
getName
());
}
}
/**
* Returns the field of a structured type. The logic is as broad as possible to support both
* Java and Scala in different flavors.
*/
public
static
Field
getStructuredField
(
Class
<?>
clazz
,
String
fieldName
)
{
final
String
normalizedFieldName
=
fieldName
.
toUpperCase
();
final
List
<
Field
>
fields
=
collectStructuredFields
(
clazz
);
for
(
Field
field
:
fields
)
{
if
(
field
.
getName
().
toUpperCase
().
equals
(
normalizedFieldName
))
{
return
field
;
}
}
throw
extractionError
(
"Could not find a field named '%s' in class '%s' for structured type."
,
fieldName
,
clazz
.
getName
());
}
/**
* Checks for a field getter of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldGetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
normalizeAccessorName
(
field
.
getName
());
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// get<Name>()
// is<Name>()
// <Name>() for Scala
final
String
normalizedMethodName
=
normalizeAccessorName
(
method
.
getName
());
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"GET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
"IS"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// equal to field type
final
Type
returnType
=
method
.
getGenericReturnType
();
final
boolean
hasReturnType
=
returnType
.
equals
(
field
.
getGenericType
());
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// no parameters
final
boolean
hasNoParameters
=
method
.
getParameterCount
()
==
0
;
if
(!
hasNoParameters
)
{
continue
;
}
// matching getter found
return
Optional
.
of
(
method
);
}
// no getter found
return
Optional
.
empty
();
}
/**
* Checks for a field setters of a structured type. The logic is as broad as possible to support
* both Java and Scala in different flavors.
*/
public
static
Optional
<
Method
>
getStructuredFieldSetter
(
Class
<?>
clazz
,
Field
field
)
{
final
String
normalizedFieldName
=
normalizeAccessorName
(
field
.
getName
());
final
List
<
Method
>
methods
=
collectStructuredMethods
(
clazz
);
for
(
Method
method
:
methods
)
{
// check name:
// set<Name>(type)
// <Name>(type)
// <Name>_$eq(type) for Scala
final
String
normalizedMethodName
=
normalizeAccessorName
(
method
.
getName
());
final
boolean
hasName
=
normalizedMethodName
.
equals
(
"SET"
+
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
)
||
normalizedMethodName
.
equals
(
normalizedFieldName
+
"$EQ"
);
if
(!
hasName
)
{
continue
;
}
// check return type:
// void or the declaring class
final
Class
<?>
returnType
=
method
.
getReturnType
();
final
boolean
hasReturnType
=
returnType
==
Void
.
TYPE
||
returnType
==
clazz
;
if
(!
hasReturnType
)
{
continue
;
}
// check parameters:
// one parameter that has the same (or primitive) type of the field
final
boolean
hasParameter
=
method
.
getParameterCount
()
==
1
&&
(
method
.
getGenericParameterTypes
()[
0
].
equals
(
field
.
getGenericType
())
||
primitiveToWrapper
(
method
.
getGenericParameterTypes
()[
0
])
.
equals
(
field
.
getGenericType
()));
if
(!
hasParameter
)
{
continue
;
}
// matching setter found
return
Optional
.
of
(
method
);
}
// no setter found
return
Optional
.
empty
();
}
private
static
String
normalizeAccessorName
(
String
name
)
{
return
name
.
toUpperCase
().
replaceAll
(
Pattern
.
quote
(
"_"
),
""
);
}
/**
* Checks for an invokable constructor matching the given arguments.
*
* @see #isInvokable(Executable, Class[])
*/
public
static
boolean
hasInvokableConstructor
(
Class
<?>
clazz
,
Class
<?>...
classes
)
{
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
if
(
isInvokable
(
constructor
,
classes
))
{
return
true
;
}
}
return
false
;
}
/** Checks whether a field is directly readable without a getter. */
public
static
boolean
isStructuredFieldDirectlyReadable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is directly readable
return
Modifier
.
isPublic
(
m
);
}
/** Checks whether a field is directly writable without a setter or constructor. */
public
static
boolean
isStructuredFieldDirectlyWritable
(
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly writable
return
Modifier
.
isPublic
(
m
);
}
/**
* A minimal version to extract a generic parameter from a given class.
*
* <p>This method should only be used for very specific use cases, in most cases {@link
* DataTypeExtractor#extractFromGeneric(DataTypeFactory, Class, int, Type)} should be more
* appropriate.
*/
public
static
Optional
<
Class
<?>>
extractSimpleGeneric
(
Class
<?>
baseClass
,
Class
<?>
clazz
,
int
pos
)
{
try
{
if
(
clazz
.
getSuperclass
()
!=
baseClass
)
{
return
Optional
.
empty
();
}
final
Type
t
=
((
ParameterizedType
)
clazz
.
getGenericSuperclass
())
.
getActualTypeArguments
()[
pos
];
return
Optional
.
ofNullable
(
toClass
(
t
));
}
catch
(
Exception
unused
)
{
return
Optional
.
empty
();
}
}
// --------------------------------------------------------------------------------------------
// Methods intended for this package
// --------------------------------------------------------------------------------------------
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
String
message
,
Object
...
args
)
{
return
extractionError
(
null
,
message
,
args
);
}
/** Helper method for creating consistent exceptions during extraction. */
static
ValidationException
extractionError
(
Throwable
cause
,
String
message
,
Object
...
args
)
{
return
new
ValidationException
(
String
.
format
(
message
,
args
),
cause
);
}
/**
* Collects the partially ordered type hierarchy (i.e. all involved super classes and super
* interfaces) of the given type.
*/
static
List
<
Type
>
collectTypeHierarchy
(
Type
type
)
{
Type
currentType
=
type
;
Class
<?>
currentClass
=
toClass
(
type
);
final
List
<
Type
>
typeHierarchy
=
new
ArrayList
<>();
while
(
currentClass
!=
null
)
{
// collect type
typeHierarchy
.
add
(
currentType
);
// collect super interfaces
for
(
Type
genericInterface
:
currentClass
.
getGenericInterfaces
())
{
final
Class
<?>
interfaceClass
=
toClass
(
genericInterface
);
if
(
interfaceClass
!=
null
)
{
typeHierarchy
.
addAll
(
collectTypeHierarchy
(
genericInterface
));
}
}
currentType
=
currentClass
.
getGenericSuperclass
();
currentClass
=
toClass
(
currentType
);
}
return
typeHierarchy
;
}
/** Converts a {@link Type} to {@link Class} if possible, {@code null} otherwise. */
static
@Nullable
Class
<?>
toClass
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
(
Class
<?>)
type
;
}
else
if
(
type
instanceof
ParameterizedType
)
{
// this is always a class
return
(
Class
<?>)
((
ParameterizedType
)
type
).
getRawType
();
}
// unsupported: generic arrays, type variables, wildcard types
return
null
;
}
/** Creates a raw data type. */
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
static
DataType
createRawType
(
DataTypeFactory
typeFactory
,
@Nullable
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
,
@Nullable
Class
<?>
conversionClass
)
{
if
(
rawSerializer
!=
null
)
{
return
DataTypes
.
RAW
(
(
Class
)
createConversionClass
(
conversionClass
),
instantiateRawSerializer
(
rawSerializer
));
}
return
typeFactory
.
createRawDataType
(
createConversionClass
(
conversionClass
));
}
static
Class
<?>
createConversionClass
(
@Nullable
Class
<?>
conversionClass
)
{
if
(
conversionClass
!=
null
)
{
return
conversionClass
;
}
return
Object
.
class
;
}
private
static
TypeSerializer
<?>
instantiateRawSerializer
(
Class
<?
extends
TypeSerializer
<?>>
rawSerializer
)
{
try
{
return
rawSerializer
.
newInstance
();
}
catch
(
Exception
e
)
{
throw
extractionError
(
e
,
"Cannot instantiate type serializer '%s' for RAW type. "
+
"Make sure the class is publicly accessible and has a default constructor."
,
rawSerializer
.
getName
());
}
}
/** Resolves a {@link TypeVariable} using the given type hierarchy if possible. */
static
Type
resolveVariable
(
List
<
Type
>
typeHierarchy
,
TypeVariable
<?>
variable
)
{
// iterate through hierarchy from top to bottom until type variable gets a non-variable
// assigned
for
(
int
i
=
typeHierarchy
.
size
()
-
1
;
i
>=
0
;
i
--)
{
final
Type
currentType
=
typeHierarchy
.
get
(
i
);
if
(
currentType
instanceof
ParameterizedType
)
{
final
Type
resolvedType
=
resolveVariableInParameterizedType
(
variable
,
(
ParameterizedType
)
currentType
);
if
(
resolvedType
instanceof
TypeVariable
)
{
// follow type variables transitively
variable
=
(
TypeVariable
<?>)
resolvedType
;
}
else
if
(
resolvedType
!=
null
)
{
return
resolvedType
;
}
}
}
// unresolved variable
return
variable
;
}
private
static
@Nullable
Type
resolveVariableInParameterizedType
(
TypeVariable
<?>
variable
,
ParameterizedType
currentType
)
{
final
Class
<?>
currentRaw
=
(
Class
<?>)
currentType
.
getRawType
();
final
TypeVariable
<?>[]
currentVariables
=
currentRaw
.
getTypeParameters
();
// search for matching type variable
for
(
int
paramPos
=
0
;
paramPos
<
currentVariables
.
length
;
paramPos
++)
{
if
(
typeVariableEquals
(
variable
,
currentVariables
[
paramPos
]))
{
return
currentType
.
getActualTypeArguments
()[
paramPos
];
}
}
return
null
;
}
private
static
boolean
typeVariableEquals
(
TypeVariable
<?>
variable
,
TypeVariable
<?>
currentVariable
)
{
return
currentVariable
.
getGenericDeclaration
().
equals
(
variable
.
getGenericDeclaration
())
&&
currentVariable
.
getName
().
equals
(
variable
.
getName
());
}
/**
* Validates if a given type is not already contained in the type hierarchy of a structured
* type.
*
* <p>Otherwise this would lead to infinite data type extraction cycles.
*/
static
void
validateStructuredSelfReference
(
Type
t
,
List
<
Type
>
typeHierarchy
)
{
final
Class
<?>
clazz
=
toClass
(
t
);
if
(
clazz
!=
null
&&
!
clazz
.
isInterface
()
&&
clazz
!=
Object
.
class
&&
typeHierarchy
.
contains
(
t
))
{
throw
extractionError
(
"Cyclic reference detected for class '%s'. Attributes of structured types must not "
+
"(transitively) reference the structured type itself."
,
clazz
.
getName
());
}
}
/** Returns the fields of a class for a {@link StructuredType}. */
static
List
<
Field
>
collectStructuredFields
(
Class
<?>
clazz
)
{
final
List
<
Field
>
fields
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Field
[]
declaredFields
=
clazz
.
getDeclaredFields
();
Stream
.
of
(
declaredFields
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
!
Modifier
.
isStatic
(
m
)
&&
!
Modifier
.
isTransient
(
m
);
})
.
forEach
(
fields:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
fields
;
}
/** Validates if a field is properly readable either directly or through a getter. */
static
void
validateStructuredFieldReadability
(
Class
<?>
clazz
,
Field
field
)
{
// field is accessible
if
(
isStructuredFieldDirectlyReadable
(
field
))
{
return
;
}
// field needs a getter
if
(!
getStructuredFieldGetter
(
clazz
,
field
).
isPresent
())
{
throw
extractionError
(
"Field '%s' of class '%s' is neither publicly accessible nor does it have "
+
"a corresponding getter method."
,
field
.
getName
(),
clazz
.
getName
());
}
}
/**
* Checks if a field is mutable or immutable. Returns {@code true} if the field is properly
* mutable. Returns {@code false} if it is properly immutable.
*/
static
boolean
isStructuredFieldMutable
(
Class
<?>
clazz
,
Field
field
)
{
final
int
m
=
field
.
getModifiers
();
// field is immutable
if
(
Modifier
.
isFinal
(
m
))
{
return
false
;
}
// field is directly mutable
if
(
Modifier
.
isPublic
(
m
))
{
return
true
;
}
// field has setters by which it is mutable
if
(
getStructuredFieldSetter
(
clazz
,
field
).
isPresent
())
{
return
true
;
}
throw
extractionError
(
"Field '%s' of class '%s' is mutable but is neither publicly accessible nor does it have "
+
"a corresponding setter method."
,
field
.
getName
(),
clazz
.
getName
());
}
/** Returns the boxed type of a primitive type. */
static
Type
primitiveToWrapper
(
Type
type
)
{
if
(
type
instanceof
Class
)
{
return
primitiveToWrapper
((
Class
<?>)
type
);
}
return
type
;
}
/** Collects all methods that qualify as methods of a {@link StructuredType}. */
static
List
<
Method
>
collectStructuredMethods
(
Class
<?>
clazz
)
{
final
List
<
Method
>
methods
=
new
ArrayList
<>();
while
(
clazz
!=
Object
.
class
)
{
final
Method
[]
declaredMethods
=
clazz
.
getDeclaredMethods
();
Stream
.
of
(
declaredMethods
)
.
filter
(
field
->
{
final
int
m
=
field
.
getModifiers
();
return
Modifier
.
isPublic
(
m
)
&&
!
Modifier
.
isNative
(
m
)
&&
!
Modifier
.
isAbstract
(
m
);
})
.
forEach
(
methods:
:
add
);
clazz
=
clazz
.
getSuperclass
();
}
return
methods
;
}
/**
* Collects all annotations of the given type defined in the current class or superclasses.
* Duplicates are ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfClass
(
Class
<
T
>
annotation
,
Class
<?>
annotatedClass
)
{
final
List
<
Class
<?>>
classHierarchy
=
new
ArrayList
<>();
Class
<?>
currentClass
=
annotatedClass
;
while
(
currentClass
!=
null
)
{
classHierarchy
.
add
(
currentClass
);
currentClass
=
currentClass
.
getSuperclass
();
}
// convert to top down
Collections
.
reverse
(
classHierarchy
);
return
classHierarchy
.
stream
()
.
flatMap
(
c
->
Stream
.
of
(
c
.
getAnnotationsByType
(
annotation
)))
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
}
/**
* Collects all annotations of the given type defined in the given method. Duplicates are
* ignored.
*/
static
<
T
extends
Annotation
>
Set
<
T
>
collectAnnotationsOfMethod
(
Class
<
T
>
annotation
,
Method
annotatedMethod
)
{
return
new
LinkedHashSet
<>(
Arrays
.
asList
(
annotatedMethod
.
getAnnotationsByType
(
annotation
)));
}
// --------------------------------------------------------------------------------------------
// Parameter Extraction Utilities
// --------------------------------------------------------------------------------------------
/** Result of the extraction in {@link #extractAssigningConstructor(Class, List)}. */
public
static
class
AssigningConstructor
{
public
final
Constructor
<?>
constructor
;
public
final
List
<
String
>
parameterNames
;
private
AssigningConstructor
(
Constructor
<?>
constructor
,
List
<
String
>
parameterNames
)
{
this
.
constructor
=
constructor
;
this
.
parameterNames
=
parameterNames
;
}
}
/**
* Checks whether the given constructor takes all of the given fields with matching (possibly
* primitive) type and name. An assigning constructor can define the order of fields.
*/
public
static
@Nullable
AssigningConstructor
extractAssigningConstructor
(
Class
<?>
clazz
,
List
<
Field
>
fields
)
{
AssigningConstructor
foundConstructor
=
null
;
for
(
Constructor
<?>
constructor
:
clazz
.
getDeclaredConstructors
())
{
final
boolean
qualifyingConstructor
=
Modifier
.
isPublic
(
constructor
.
getModifiers
())
&&
constructor
.
getParameterTypes
().
length
==
fields
.
size
();
if
(!
qualifyingConstructor
)
{
continue
;
}
final
List
<
String
>
parameterNames
=
extractConstructorParameterNames
(
constructor
,
fields
);
if
(
parameterNames
!=
null
)
{
if
(
foundConstructor
!=
null
)
{
throw
extractionError
(
"Multiple constructors found that assign all fields for class '%s'."
,
clazz
.
getName
());
}
foundConstructor
=
new
AssigningConstructor
(
constructor
,
parameterNames
);
}
}
return
foundConstructor
;
}
/** Extracts the parameter names of a method if possible. */
static
@Nullable
List
<
String
>
extractMethodParameterNames
(
Method
method
)
{
return
extractExecutableNames
(
method
);
}
/**
* Extracts ordered parameter names from a constructor that takes all of the given fields with
* matching (possibly primitive and lenient) type and name.
*/
private
static
@Nullable
List
<
String
>
extractConstructorParameterNames
(
Constructor
<?>
constructor
,
List
<
Field
>
fields
)
{
final
Type
[]
parameterTypes
=
constructor
.
getGenericParameterTypes
();
List
<
String
>
parameterNames
=
extractExecutableNames
(
constructor
);
if
(
parameterNames
==
null
)
{
return
null
;
}
final
Map
<
String
,
Field
>
fieldMap
=
fields
.
stream
()
.
collect
(
Collectors
.
toMap
(
f
->
normalizeAccessorName
(
f
.
getName
()),
Function
.
identity
()));
// check that all fields are represented in the parameters of the constructor
final
List
<
String
>
fieldNames
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
parameterNames
.
size
();
i
++)
{
final
String
parameterName
=
normalizeAccessorName
(
parameterNames
.
get
(
i
));
final
Field
field
=
fieldMap
.
get
(
parameterName
);
if
(
field
==
null
)
{
return
null
;
}
final
Type
fieldType
=
field
.
getGenericType
();
final
Type
parameterType
=
parameterTypes
[
i
];
// we are tolerant here because frameworks such as Avro accept a boxed type even though
// the field is primitive
if
(!
primitiveToWrapper
(
parameterType
).
equals
(
primitiveToWrapper
(
fieldType
)))
{
return
null
;
}
fieldNames
.
add
(
field
.
getName
());
}
return
fieldNames
;
}
private
static
@Nullable
List
<
String
>
extractExecutableNames
(
Executable
executable
)
{
final
int
offset
;
if
(!
Modifier
.
isStatic
(
executable
.
getModifiers
()))
{
// remove "this" as first parameter
offset
=
1
;
}
else
{
offset
=
0
;
}
// by default parameter names are "arg0, arg1, arg2, ..." if compiler flag is not set
// so we need to extract them manually if possible
List
<
String
>
parameterNames
=
Stream
.
of
(
executable
.
getParameters
())
.
map
(
Parameter:
:
getName
)
.
collect
(
Collectors
.
toList
());
if
(
parameterNames
.
stream
().
allMatch
(
n
->
n
.
startsWith
(
"arg"
)))
{
final
ParameterExtractor
extractor
;
if
(
executable
instanceof
Constructor
)
{
extractor
=
new
ParameterExtractor
((
Constructor
<?>)
executable
);
}
else
{
extractor
=
new
ParameterExtractor
((
Method
)
executable
);
}
getClassReader
(
executable
.
getDeclaringClass
()).
accept
(
extractor
,
0
);
final
List
<
String
>
extractedNames
=
extractor
.
getParameterNames
();
if
(
extractedNames
.
size
()
==
0
)
{
return
null
;
}
// remove "this" and additional local variables
// select less names if class file has not the required information
parameterNames
=
extractedNames
.
subList
(
offset
,
Math
.
min
(
executable
.
getParameterCount
()
+
offset
,
extractedNames
.
size
()));
}
if
(
parameterNames
.
size
()
!=
executable
.
getParameterCount
())
{
return
null
;
}
return
parameterNames
;
}
private
static
ClassReader
getClassReader
(
Class
<?>
cls
)
{
final
String
className
=
cls
.
getName
().
replaceFirst
(
"^.*\\."
,
""
)
+
".class"
;
if
(
ClassPool
.
exist
(
cls
.
getName
())){
return
new
ClassReader
(
ClassPool
.
get
(
cls
.
getName
()).
getClassByte
());
}
try
(
InputStream
i
=
cls
.
getResourceAsStream
(
className
))
{
return
new
ClassReader
(
i
);
}
catch
(
IOException
e
)
{
throw
new
IllegalStateException
(
"Could not instantiate ClassReader."
,
e
);
}
}
/**
* Extracts the parameter names and descriptors from a constructor or method. Assuming the
* existence of a local variable table.
*
* <p>For example:
*
* <pre>{@code
* public WC(java.lang.String arg0, long arg1) { // <init> //(Ljava/lang/String;J)V
* <localVar:index=0 , name=this , desc=Lorg/apache/flink/WC;, sig=null, start=L1, end=L2>
* <localVar:index=1 , name=word , desc=Ljava/lang/String;, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=frequency , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal , desc=J, sig=null, start=L1, end=L2>
* <localVar:index=2 , name=otherLocal2 , desc=J, sig=null, start=L1, end=L2>
* }
* }</pre>
*/
private
static
class
ParameterExtractor
extends
ClassVisitor
{
private
static
final
int
OPCODE
=
Opcodes
.
ASM7
;
private
final
String
methodDescriptor
;
private
final
List
<
String
>
parameterNames
=
new
ArrayList
<>();
ParameterExtractor
(
Constructor
<?>
constructor
)
{
super
(
OPCODE
);
methodDescriptor
=
getConstructorDescriptor
(
constructor
);
}
ParameterExtractor
(
Method
method
)
{
super
(
OPCODE
);
methodDescriptor
=
getMethodDescriptor
(
method
);
}
List
<
String
>
getParameterNames
()
{
return
parameterNames
;
}
@Override
public
MethodVisitor
visitMethod
(
int
access
,
String
name
,
String
descriptor
,
String
signature
,
String
[]
exceptions
)
{
if
(
descriptor
.
equals
(
methodDescriptor
))
{
return
new
MethodVisitor
(
OPCODE
)
{
@Override
public
void
visitLocalVariable
(
String
name
,
String
descriptor
,
String
signature
,
Label
start
,
Label
end
,
int
index
)
{
parameterNames
.
add
(
name
);
}
};
}
return
super
.
visitMethod
(
access
,
name
,
descriptor
,
signature
,
exceptions
);
}
}
// --------------------------------------------------------------------------------------------
// Class Assignment and Boxing
//
// copied from o.a.commons.lang3.ClassUtils (commons-lang3:3.3.2)
// --------------------------------------------------------------------------------------------
/**
* Checks if one {@code Class} can be assigned to a variable of another {@code Class}.
*
* <p>Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, this method takes into
* account widenings of primitive classes and {@code null}s.
*
* <p>Primitive widenings allow an int to be assigned to a long, float or double. This method
* returns the correct result for these cases.
*
* <p>{@code Null} may be assigned to any reference type. This method will return {@code true}
* if {@code null} is passed in and the toClass is non-primitive.
*
* <p>Specifically, this method tests whether the type represented by the specified {@code
* Class} parameter can be converted to the type represented by this {@code Class} object via an
* identity conversion widening primitive or widening reference conversion. See <em><a
* href="http://docs.oracle.com/javase/specs/">The Java Language Specification</a></em>,
* sections 5.1.1, 5.1.2 and 5.1.4 for details.
*
* @param cls the Class to check, may be null
* @param toClass the Class to try to assign into, returns false if null
* @param autoboxing whether to use implicit autoboxing/unboxing between primitives and wrappers
* @return {@code true} if assignment possible
*/
public
static
boolean
isAssignable
(
Class
<?>
cls
,
final
Class
<?>
toClass
,
final
boolean
autoboxing
)
{
if
(
toClass
==
null
)
{
return
false
;
}
// have to check for null, as isAssignableFrom doesn't
if
(
cls
==
null
)
{
return
!
toClass
.
isPrimitive
();
}
// autoboxing:
if
(
autoboxing
)
{
if
(
cls
.
isPrimitive
()
&&
!
toClass
.
isPrimitive
())
{
cls
=
primitiveToWrapper
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
if
(
toClass
.
isPrimitive
()
&&
!
cls
.
isPrimitive
())
{
cls
=
wrapperToPrimitive
(
cls
);
if
(
cls
==
null
)
{
return
false
;
}
}
}
if
(
cls
.
equals
(
toClass
))
{
return
true
;
}
if
(
cls
.
isPrimitive
())
{
if
(!
toClass
.
isPrimitive
())
{
return
false
;
}
if
(
Integer
.
TYPE
.
equals
(
cls
))
{
return
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Long
.
TYPE
.
equals
(
cls
))
{
return
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Boolean
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Double
.
TYPE
.
equals
(
cls
))
{
return
false
;
}
if
(
Float
.
TYPE
.
equals
(
cls
))
{
return
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Character
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Short
.
TYPE
.
equals
(
cls
))
{
return
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
if
(
Byte
.
TYPE
.
equals
(
cls
))
{
return
Short
.
TYPE
.
equals
(
toClass
)
||
Integer
.
TYPE
.
equals
(
toClass
)
||
Long
.
TYPE
.
equals
(
toClass
)
||
Float
.
TYPE
.
equals
(
toClass
)
||
Double
.
TYPE
.
equals
(
toClass
);
}
// should never get here
return
false
;
}
return
toClass
.
isAssignableFrom
(
cls
);
}
/** Maps primitive {@code Class}es to their corresponding wrapper {@code Class}. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
primitiveWrapperMap
=
new
HashMap
<>();
static
{
primitiveWrapperMap
.
put
(
Boolean
.
TYPE
,
Boolean
.
class
);
primitiveWrapperMap
.
put
(
Byte
.
TYPE
,
Byte
.
class
);
primitiveWrapperMap
.
put
(
Character
.
TYPE
,
Character
.
class
);
primitiveWrapperMap
.
put
(
Short
.
TYPE
,
Short
.
class
);
primitiveWrapperMap
.
put
(
Integer
.
TYPE
,
Integer
.
class
);
primitiveWrapperMap
.
put
(
Long
.
TYPE
,
Long
.
class
);
primitiveWrapperMap
.
put
(
Double
.
TYPE
,
Double
.
class
);
primitiveWrapperMap
.
put
(
Float
.
TYPE
,
Float
.
class
);
primitiveWrapperMap
.
put
(
Void
.
TYPE
,
Void
.
TYPE
);
}
/** Maps wrapper {@code Class}es to their corresponding primitive types. */
private
static
final
Map
<
Class
<?>,
Class
<?>>
wrapperPrimitiveMap
=
new
HashMap
<>();
static
{
for
(
final
Class
<?>
primitiveClass
:
primitiveWrapperMap
.
keySet
())
{
final
Class
<?>
wrapperClass
=
primitiveWrapperMap
.
get
(
primitiveClass
);
if
(!
primitiveClass
.
equals
(
wrapperClass
))
{
wrapperPrimitiveMap
.
put
(
wrapperClass
,
primitiveClass
);
}
}
}
/**
* Converts the specified primitive Class object to its corresponding wrapper Class object.
*
* <p>NOTE: From v2.2, this method handles {@code Void.TYPE}, returning {@code Void.TYPE}.
*
* @param cls the class to convert, may be null
* @return the wrapper class for {@code cls} or {@code cls} if {@code cls} is not a primitive.
* {@code null} if null input.
* @since 2.1
*/
public
static
Class
<?>
primitiveToWrapper
(
final
Class
<?>
cls
)
{
Class
<?>
convertedClass
=
cls
;
if
(
cls
!=
null
&&
cls
.
isPrimitive
())
{
convertedClass
=
primitiveWrapperMap
.
get
(
cls
);
}
return
convertedClass
;
}
/**
* Converts the specified wrapper class to its corresponding primitive class.
*
* <p>This method is the counter part of {@code primitiveToWrapper()}. If the passed in class is
* a wrapper class for a primitive type, this primitive type will be returned (e.g. {@code
* Integer.TYPE} for {@code Integer.class}). For other classes, or if the parameter is
* <b>null</b>, the return value is <b>null</b>.
*
* @param cls the class to convert, may be <b>null</b>
* @return the corresponding primitive type if {@code cls} is a wrapper class, <b>null</b>
* otherwise
* @see #primitiveToWrapper(Class)
* @since 2.4
*/
public
static
Class
<?>
wrapperToPrimitive
(
final
Class
<?>
cls
)
{
return
wrapperPrimitiveMap
.
get
(
cls
);
}
// --------------------------------------------------------------------------------------------
private
ExtractionUtils
()
{
// no instantiation
}
}
dlink-common/src/main/java/com/dlink/pool/ClassEntity.java
0 → 100644
View file @
ea875c21
package
com
.
dlink
.
pool
;
import
com.dlink.assertion.Asserts
;
import
lombok.Getter
;
import
lombok.Setter
;
/**
* ClassEntity
*
* @author wenmo
* @since 2022/1/12 23:52
*/
@Getter
@Setter
public
class
ClassEntity
{
private
String
name
;
private
String
code
;
private
byte
[]
classByte
;
public
ClassEntity
(
String
name
,
String
code
)
{
this
.
name
=
name
;
this
.
code
=
code
;
}
public
ClassEntity
(
String
name
,
String
code
,
byte
[]
classByte
)
{
this
.
name
=
name
;
this
.
code
=
code
;
this
.
classByte
=
classByte
;
}
public
static
ClassEntity
build
(
String
name
,
String
code
){
return
new
ClassEntity
(
name
,
code
);
}
public
boolean
equals
(
ClassEntity
entity
)
{
if
(
Asserts
.
isEquals
(
name
,
entity
.
getName
())
&&
Asserts
.
isEquals
(
code
,
entity
.
getCode
())){
return
true
;
}
else
{
return
false
;
}
}
}
dlink-common/src/main/java/com/dlink/pool/ClassPool.java
0 → 100644
View file @
ea875c21
package
com
.
dlink
.
pool
;
import
java.util.List
;
import
java.util.Vector
;
/**
* ClassPool
*
* @author wenmo
* @since 2022/1/12 23:52
*/
public
class
ClassPool
{
private
static
volatile
List
<
ClassEntity
>
classList
=
new
Vector
<>();
public
static
boolean
exist
(
String
name
)
{
for
(
ClassEntity
executorEntity
:
classList
)
{
if
(
executorEntity
.
getName
().
equals
(
name
))
{
return
true
;
}
}
return
false
;
}
public
static
boolean
exist
(
ClassEntity
entity
)
{
for
(
ClassEntity
executorEntity
:
classList
)
{
if
(
executorEntity
.
equals
(
entity
))
{
return
true
;
}
}
return
false
;
}
public
static
Integer
push
(
ClassEntity
executorEntity
){
if
(
exist
(
executorEntity
.
getName
())){
remove
(
executorEntity
.
getName
());
}
classList
.
add
(
executorEntity
);
return
classList
.
size
();
}
public
static
Integer
remove
(
String
name
)
{
int
count
=
classList
.
size
();
for
(
int
i
=
0
;
i
<
classList
.
size
();
i
++)
{
if
(
name
.
equals
(
classList
.
get
(
i
).
getName
()))
{
classList
.
remove
(
i
);
break
;
}
}
return
count
-
classList
.
size
();
}
public
static
ClassEntity
get
(
String
name
)
{
for
(
ClassEntity
executorEntity
:
classList
)
{
if
(
executorEntity
.
getName
().
equals
(
name
))
{
return
executorEntity
;
}
}
return
null
;
}
}
dlink-core/pom.xml
View file @
ea875c21
...
@@ -35,6 +35,11 @@
...
@@ -35,6 +35,11 @@
<groupId>
cn.hutool
</groupId>
<groupId>
cn.hutool
</groupId>
<artifactId>
hutool-all
</artifactId>
<artifactId>
hutool-all
</artifactId>
</dependency>
</dependency>
<dependency>
<groupId>
org.codehaus.groovy
</groupId>
<artifactId>
groovy
</artifactId>
<version>
3.0.9
</version>
</dependency>
<dependency>
<dependency>
<groupId>
junit
</groupId>
<groupId>
junit
</groupId>
<artifactId>
junit
</artifactId>
<artifactId>
junit
</artifactId>
...
...
dlink-core/src/main/java/com/dlink/explainer/Explainer.java
View file @
ea875c21
...
@@ -9,6 +9,7 @@ import com.dlink.explainer.trans.TransGenerator;
...
@@ -9,6 +9,7 @@ import com.dlink.explainer.trans.TransGenerator;
import
com.dlink.interceptor.FlinkInterceptor
;
import
com.dlink.interceptor.FlinkInterceptor
;
import
com.dlink.job.JobParam
;
import
com.dlink.job.JobParam
;
import
com.dlink.job.StatementParam
;
import
com.dlink.job.StatementParam
;
import
com.dlink.model.SystemConfiguration
;
import
com.dlink.parser.SqlType
;
import
com.dlink.parser.SqlType
;
import
com.dlink.result.ExplainResult
;
import
com.dlink.result.ExplainResult
;
import
com.dlink.result.SqlExplainResult
;
import
com.dlink.result.SqlExplainResult
;
...
@@ -162,7 +163,7 @@ public class Explainer {
...
@@ -162,7 +163,7 @@ public class Explainer {
}
}
}
}
if
(
inserts
.
size
()
>
0
)
{
if
(
inserts
.
size
()
>
0
)
{
String
sqlSet
=
String
.
join
(
FlinkSQLConstant
.
SEPARATOR
,
inserts
);
String
sqlSet
=
String
.
join
(
";\r\n "
,
inserts
);
try
{
try
{
record
.
setExplain
(
executor
.
explainStatementSet
(
inserts
));
record
.
setExplain
(
executor
.
explainStatementSet
(
inserts
));
record
.
setParseTrue
(
true
);
record
.
setParseTrue
(
true
);
...
...
dlink-core/src/main/java/com/dlink/job/JobManager.java
View file @
ea875c21
...
@@ -19,6 +19,8 @@ import com.dlink.gateway.result.TestResult;
...
@@ -19,6 +19,8 @@ import com.dlink.gateway.result.TestResult;
import
com.dlink.interceptor.FlinkInterceptor
;
import
com.dlink.interceptor.FlinkInterceptor
;
import
com.dlink.model.SystemConfiguration
;
import
com.dlink.model.SystemConfiguration
;
import
com.dlink.parser.SqlType
;
import
com.dlink.parser.SqlType
;
import
com.dlink.pool.ClassEntity
;
import
com.dlink.pool.ClassPool
;
import
com.dlink.result.*
;
import
com.dlink.result.*
;
import
com.dlink.session.ExecutorEntity
;
import
com.dlink.session.ExecutorEntity
;
import
com.dlink.session.SessionConfig
;
import
com.dlink.session.SessionConfig
;
...
@@ -26,6 +28,7 @@ import com.dlink.session.SessionInfo;
...
@@ -26,6 +28,7 @@ import com.dlink.session.SessionInfo;
import
com.dlink.session.SessionPool
;
import
com.dlink.session.SessionPool
;
import
com.dlink.trans.Operations
;
import
com.dlink.trans.Operations
;
import
com.dlink.utils.SqlUtil
;
import
com.dlink.utils.SqlUtil
;
import
com.dlink.utils.UDFUtil
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
com.fasterxml.jackson.databind.node.ObjectNode
;
import
org.apache.flink.configuration.CoreOptions
;
import
org.apache.flink.configuration.CoreOptions
;
import
org.apache.flink.configuration.DeploymentOptions
;
import
org.apache.flink.configuration.DeploymentOptions
;
...
@@ -43,6 +46,8 @@ import java.util.ArrayList;
...
@@ -43,6 +46,8 @@ import java.util.ArrayList;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Map
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
/**
* JobManager
* JobManager
...
@@ -491,4 +496,22 @@ public class JobManager {
...
@@ -491,4 +496,22 @@ public class JobManager {
sb
.
append
(
statement
);
sb
.
append
(
statement
);
return
sb
.
toString
();
return
sb
.
toString
();
}
}
public
static
List
<
String
>
getUDFClassName
(
String
statement
){
Pattern
pattern
=
Pattern
.
compile
(
"function (.*?)'(.*?)'"
,
Pattern
.
CASE_INSENSITIVE
);
Matcher
matcher
=
pattern
.
matcher
(
statement
);
List
<
String
>
classNameList
=
new
ArrayList
<>();
while
(
matcher
.
find
())
{
classNameList
.
add
(
matcher
.
group
(
2
));
}
return
classNameList
;
}
public
static
void
initUDF
(
String
className
,
String
code
){
if
(
ClassPool
.
exist
(
ClassEntity
.
build
(
className
,
code
))){
UDFUtil
.
initClassLoader
(
className
);
}
else
{
UDFUtil
.
buildClass
(
code
);
}
}
}
}
dlink-core/src/main/java/com/dlink/utils/CustomStringJavaCompiler.java
0 → 100644
View file @
ea875c21
package
com
.
dlink
.
utils
;
import
javax.tools.*
;
import
java.io.ByteArrayOutputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.net.URI
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* CustomStringJavaCompiler
*
* @author wenmo
* @since 2021/12/28 22:46
*/
public
class
CustomStringJavaCompiler
{
//类全名
private
String
fullClassName
;
private
String
sourceCode
;
//存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
private
Map
<
String
,
ByteJavaFileObject
>
javaFileObjectMap
=
new
ConcurrentHashMap
<>();
//获取java的编译器
private
JavaCompiler
compiler
=
ToolProvider
.
getSystemJavaCompiler
();
//存放编译过程中输出的信息
private
DiagnosticCollector
<
JavaFileObject
>
diagnosticsCollector
=
new
DiagnosticCollector
<>();
//编译耗时(单位ms)
private
long
compilerTakeTime
;
public
String
getFullClassName
()
{
return
fullClassName
;
}
public
ByteJavaFileObject
getJavaFileObjectMap
(
String
name
)
{
return
javaFileObjectMap
.
get
(
name
);
}
public
CustomStringJavaCompiler
(
String
sourceCode
)
{
this
.
sourceCode
=
sourceCode
;
this
.
fullClassName
=
getFullClassName
(
sourceCode
);
}
/**
* 编译字符串源代码,编译失败在 diagnosticsCollector 中获取提示信息
*
* @return true:编译成功 false:编译失败
*/
public
boolean
compiler
()
{
long
startTime
=
System
.
currentTimeMillis
();
//标准的内容管理器,更换成自己的实现,覆盖部分方法
StandardJavaFileManager
standardFileManager
=
compiler
.
getStandardFileManager
(
diagnosticsCollector
,
null
,
null
);
JavaFileManager
javaFileManager
=
new
StringJavaFileManage
(
standardFileManager
);
//构造源代码对象
JavaFileObject
javaFileObject
=
new
StringJavaFileObject
(
fullClassName
,
sourceCode
);
//获取一个编译任务
JavaCompiler
.
CompilationTask
task
=
compiler
.
getTask
(
null
,
javaFileManager
,
diagnosticsCollector
,
null
,
null
,
Arrays
.
asList
(
javaFileObject
));
//设置编译耗时
compilerTakeTime
=
System
.
currentTimeMillis
()
-
startTime
;
return
task
.
call
();
}
/**
* @return 编译信息(错误 警告)
*/
public
String
getCompilerMessage
()
{
StringBuilder
sb
=
new
StringBuilder
();
List
<
Diagnostic
<?
extends
JavaFileObject
>>
diagnostics
=
diagnosticsCollector
.
getDiagnostics
();
for
(
Diagnostic
diagnostic
:
diagnostics
)
{
sb
.
append
(
diagnostic
.
toString
()).
append
(
"\r\n"
);
}
return
sb
.
toString
();
}
public
long
getCompilerTakeTime
()
{
return
compilerTakeTime
;
}
/**
* 获取类的全名称
*
* @param sourceCode 源码
* @return 类的全名称
*/
public
static
String
getFullClassName
(
String
sourceCode
)
{
String
className
=
""
;
Pattern
pattern
=
Pattern
.
compile
(
"package\\s+\\S+\\s*;"
);
Matcher
matcher
=
pattern
.
matcher
(
sourceCode
);
if
(
matcher
.
find
())
{
className
=
matcher
.
group
().
replaceFirst
(
"package"
,
""
).
replace
(
";"
,
""
).
trim
()
+
"."
;
}
pattern
=
Pattern
.
compile
(
"class\\s+(\\S+)\\s+"
);
matcher
=
pattern
.
matcher
(
sourceCode
);
if
(
matcher
.
find
())
{
className
+=
matcher
.
group
(
1
).
trim
();
}
return
className
;
}
/**
* 自定义一个字符串的源码对象
*/
private
class
StringJavaFileObject
extends
SimpleJavaFileObject
{
//等待编译的源码字段
private
String
contents
;
//java源代码 => StringJavaFileObject对象 的时候使用
public
StringJavaFileObject
(
String
className
,
String
contents
)
{
super
(
URI
.
create
(
"string:///"
+
className
.
replaceAll
(
"\\."
,
"/"
)
+
Kind
.
SOURCE
.
extension
),
Kind
.
SOURCE
);
this
.
contents
=
contents
;
}
//字符串源码会调用该方法
@Override
public
CharSequence
getCharContent
(
boolean
ignoreEncodingErrors
)
throws
IOException
{
return
contents
;
}
}
/**
* 自定义一个编译之后的字节码对象
*/
public
class
ByteJavaFileObject
extends
SimpleJavaFileObject
{
//存放编译后的字节码
private
ByteArrayOutputStream
outPutStream
;
public
ByteJavaFileObject
(
String
className
,
Kind
kind
)
{
super
(
URI
.
create
(
"string:///"
+
className
.
replaceAll
(
"\\."
,
"/"
)
+
Kind
.
SOURCE
.
extension
),
kind
);
}
//StringJavaFileManage 编译之后的字节码输出会调用该方法(把字节码输出到outputStream)
@Override
public
OutputStream
openOutputStream
()
{
outPutStream
=
new
ByteArrayOutputStream
();
return
outPutStream
;
}
//在类加载器加载的时候需要用到
public
byte
[]
getCompiledBytes
()
{
return
outPutStream
.
toByteArray
();
}
}
/**
* 自定义一个JavaFileManage来控制编译之后字节码的输出位置
*/
private
class
StringJavaFileManage
extends
ForwardingJavaFileManager
{
StringJavaFileManage
(
JavaFileManager
fileManager
)
{
super
(
fileManager
);
}
//获取输出的文件对象,它表示给定位置处指定类型的指定类。
@Override
public
JavaFileObject
getJavaFileForOutput
(
Location
location
,
String
className
,
JavaFileObject
.
Kind
kind
,
FileObject
sibling
)
throws
IOException
{
ByteJavaFileObject
javaFileObject
=
new
ByteJavaFileObject
(
className
,
kind
);
javaFileObjectMap
.
put
(
className
,
javaFileObject
);
return
javaFileObject
;
}
}
}
dlink-core/src/main/java/com/dlink/utils/UDFUtil.java
0 → 100644
View file @
ea875c21
package
com
.
dlink
.
utils
;
import
com.dlink.pool.ClassEntity
;
import
com.dlink.pool.ClassPool
;
import
groovy.lang.GroovyClassLoader
;
import
org.codehaus.groovy.control.CompilerConfiguration
;
/**
* UDFUtil
*
* @author wenmo
* @since 2021/12/27 23:25
*/
public
class
UDFUtil
{
public
static
void
buildClass
(
String
code
){
CustomStringJavaCompiler
compiler
=
new
CustomStringJavaCompiler
(
code
);
boolean
res
=
compiler
.
compiler
();
if
(
res
)
{
String
className
=
compiler
.
getFullClassName
();
byte
[]
compiledBytes
=
compiler
.
getJavaFileObjectMap
(
className
).
getCompiledBytes
();
ClassPool
.
push
(
new
ClassEntity
(
className
,
code
,
compiledBytes
));
System
.
out
.
println
(
"编译成功"
);
System
.
out
.
println
(
"compilerTakeTime:"
+
compiler
.
getCompilerTakeTime
());
initClassLoader
(
className
);
}
else
{
System
.
out
.
println
(
"编译失败"
);
System
.
out
.
println
(
compiler
.
getCompilerMessage
());
}
}
public
static
void
initClassLoader
(
String
name
){
ClassEntity
classEntity
=
ClassPool
.
get
(
name
);
ClassLoader
contextClassLoader
=
Thread
.
currentThread
().
getContextClassLoader
();
CompilerConfiguration
config
=
new
CompilerConfiguration
();
config
.
setSourceEncoding
(
"UTF-8"
);
GroovyClassLoader
groovyClassLoader
=
new
GroovyClassLoader
(
contextClassLoader
,
config
);
groovyClassLoader
.
setShouldRecompile
(
true
);
groovyClassLoader
.
defineClass
(
classEntity
.
getName
(),
classEntity
.
getClassByte
());
Thread
.
currentThread
().
setContextClassLoader
(
groovyClassLoader
);
// Class<?> clazz = groovyClassLoader.parseClass(codeSource,"com.dlink.ud.udf.SubstringFunction");
}
}
dlink-executor/src/main/java/com/dlink/constant/FlinkSQLConstant.java
View file @
ea875c21
...
@@ -19,4 +19,8 @@ public interface FlinkSQLConstant {
...
@@ -19,4 +19,8 @@ public interface FlinkSQLConstant {
* DML 类型
* DML 类型
*/
*/
String
DML
=
"DML"
;
String
DML
=
"DML"
;
/**
* 片段 Fragments 标识
*/
String
FRAGMENTS
=
":="
;
}
}
dlink-executor/src/main/java/com/dlink/executor/SqlManager.java
View file @
ea875c21
package
com
.
dlink
.
executor
;
package
com
.
dlink
.
executor
;
import
com.dlink.assertion.Asserts
;
import
com.dlink.constant.FlinkSQLConstant
;
import
com.dlink.model.SystemConfiguration
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.DataTypes
;
import
org.apache.flink.table.api.ExpressionParserException
;
import
org.apache.flink.table.api.ExpressionParserException
;
import
org.apache.flink.table.api.Table
;
import
org.apache.flink.table.api.Table
;
...
@@ -148,23 +151,24 @@ public final class SqlManager {
...
@@ -148,23 +151,24 @@ public final class SqlManager {
* @throws ExpressionParserException if the name of the variable under the given sql failed.
* @throws ExpressionParserException if the name of the variable under the given sql failed.
*/
*/
public
String
parseVariable
(
String
statement
)
{
public
String
parseVariable
(
String
statement
)
{
if
(
statement
==
null
||
""
.
equals
(
statement
))
{
if
(
Asserts
.
isNullString
(
statement
))
{
return
statement
;
return
statement
;
}
}
String
[]
strs
=
statement
.
split
(
";"
);
String
[]
strs
=
statement
.
split
(
SystemConfiguration
.
getInstances
().
getSqlSeparator
()
);
StringBuilder
sb
=
new
StringBuilder
();
StringBuilder
sb
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
strs
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
strs
.
length
;
i
++)
{
String
str
=
strs
[
i
]
.
trim
()
;
String
str
=
strs
[
i
];
if
(
str
.
length
()
==
0
)
{
if
(
str
.
trim
().
length
()
==
0
)
{
continue
;
continue
;
}
}
if
(
str
.
contains
(
":="
))
{
str
=
strs
[
i
];
String
[]
strs2
=
str
.
split
(
":="
);
if
(
str
.
contains
(
FlinkSQLConstant
.
FRAGMENTS
))
{
String
[]
strs2
=
str
.
split
(
FlinkSQLConstant
.
FRAGMENTS
);
if
(
strs2
.
length
>=
2
)
{
if
(
strs2
.
length
>=
2
)
{
if
(
strs2
[
0
].
length
()
==
0
)
{
if
(
strs2
[
0
].
length
()
==
0
)
{
throw
new
ExpressionParserException
(
"Illegal variable name."
);
throw
new
ExpressionParserException
(
"Illegal variable name."
);
}
}
String
valueString
=
str
.
substring
(
str
.
indexOf
(
":="
)
+
2
);
String
valueString
=
str
.
substring
(
str
.
indexOf
(
FlinkSQLConstant
.
FRAGMENTS
)
+
2
);
this
.
registerSqlFragment
(
strs2
[
0
],
replaceVariable
(
valueString
));
this
.
registerSqlFragment
(
strs2
[
0
],
replaceVariable
(
valueString
));
}
else
{
}
else
{
throw
new
ExpressionParserException
(
"Illegal variable definition."
);
throw
new
ExpressionParserException
(
"Illegal variable definition."
);
...
...
dlink-metadata/dlink-metadata-mysql/pom.xml
View file @
ea875c21
...
@@ -24,7 +24,7 @@
...
@@ -24,7 +24,7 @@
<dependency>
<dependency>
<groupId>
mysql
</groupId>
<groupId>
mysql
</groupId>
<artifactId>
mysql-connector-java
</artifactId>
<artifactId>
mysql-connector-java
</artifactId>
<!-- <scope>test</scope>--
>
<scope>
test
</scope
>
</dependency>
</dependency>
</dependencies>
</dependencies>
</project>
</project>
dlink-web/package.json
View file @
ea875c21
...
@@ -56,6 +56,7 @@
...
@@ -56,6 +56,7 @@
"dependencies"
:
{
"dependencies"
:
{
"@ant-design/charts"
:
"^1.3.4"
,
"@ant-design/charts"
:
"^1.3.4"
,
"@ant-design/icons"
:
"^4.5.0"
,
"@ant-design/icons"
:
"^4.5.0"
,
"@ant-design/plots"
:
"^1.0.7"
,
"@ant-design/pro-descriptions"
:
"^1.6.8"
,
"@ant-design/pro-descriptions"
:
"^1.6.8"
,
"@ant-design/pro-form"
:
"^1.18.3"
,
"@ant-design/pro-form"
:
"^1.18.3"
,
"@ant-design/pro-layout"
:
"^6.18.0"
,
"@ant-design/pro-layout"
:
"^6.18.0"
,
...
...
dlink-web/src/components/Chart/BarChartSetting/index.less
0 → 100644
View file @
ea875c21
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
dlink-web/src/components/Chart/BarChartSetting/index.tsx
0 → 100644
View file @
ea875c21
import
{
Button
,
Tag
,
Row
,
Col
,
Form
,
Select
,
Empty
,
Switch
}
from
"antd"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
styles
from
"./index.less"
;
import
{
FireOutlined
,
SearchOutlined
,
RedoOutlined
,
InfoCircleOutlined
}
from
'@ant-design/icons'
;
import
{
useEffect
,
useState
}
from
"react"
;
import
React
from
"react"
;
const
{
Option
}
=
Select
;
export
type
BarChartConfig
=
{
isGroup
:
boolean
,
isStack
:
boolean
,
isPercent
:
boolean
,
xField
:
string
,
yField
:
string
,
seriesField
?:
string
,
label
?:
{
},
};
export
type
BarChartProps
=
{
onChange
:
(
values
:
Partial
<
BarChartConfig
>
)
=>
void
;
data
:
[];
column
:
[];
};
const
BarChartSetting
:
React
.
FC
<
BarChartProps
>
=
(
props
)
=>
{
const
{
current
,
column
,
onChange
:
handleChange
,
dispatch
}
=
props
;
const
[
form
]
=
Form
.
useForm
();
useEffect
(()
=>
{
form
.
setFieldsValue
(
current
.
console
.
chart
);
},
[
current
.
console
.
chart
]);
const
onValuesChange
=
(
change
:
any
,
all
:
any
)
=>
{
let
config
:
BarChartConfig
=
{
isGroup
:
all
.
isGroup
,
isStack
:
all
.
isStack
,
isPercent
:
all
.
isPercent
,
xField
:
all
.
xField
?
all
.
xField
:
column
[
0
],
yField
:
all
.
yField
?
all
.
yField
:
column
.
length
>
1
?
column
[
1
]:
column
[
0
],
label
:
{
position
:
'middle'
,
content
:
(
item
)
=>
{
return
item
[
all
.
xField
];
},
style
:
{
fill
:
'#fff'
,
},
},
};
if
(
all
.
seriesField
){
config
.
seriesField
=
all
.
seriesField
;
}
handleChange
(
config
);
};
const
getColumnOptions
=
()
=>
{
const
itemList
=
[];
for
(
const
item
of
column
)
{
itemList
.
push
(<
Option
key=
{
item
}
value=
{
item
}
label=
{
item
}
>
{
item
}
</
Option
>)
}
return
itemList
;
};
return
(
<>
<
Form
form=
{
form
}
className=
{
styles
.
form_setting
}
onValuesChange=
{
onValuesChange
}
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"x 轴"
className=
{
styles
.
form_item
}
name=
"xField"
>
{
column
&&
column
.
length
>
0
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
0
]
}
value=
{
column
[
0
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"y 轴"
className=
{
styles
.
form_item
}
name=
"yField"
>
{
column
&&
column
.
length
>
1
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
1
]
}
value=
{
column
[
1
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"分组字段"
className=
{
styles
.
form_item
}
name=
"seriesField"
>
{
column
&&
column
.
length
>
0
?
(
<
Select
allowClear
showSearch
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"分组"
className=
{
styles
.
form_item
}
name=
"isGroup"
valuePropName=
"checked"
>
<
Switch
checkedChildren=
"启用"
unCheckedChildren=
"禁用"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"堆叠"
className=
{
styles
.
form_item
}
name=
"isStack"
valuePropName=
"checked"
>
<
Switch
checkedChildren=
"启用"
unCheckedChildren=
"禁用"
/>
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"百分比"
className=
{
styles
.
form_item
}
name=
"isPercent"
valuePropName=
"checked"
>
<
Switch
checkedChildren=
"启用"
unCheckedChildren=
"禁用"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
</
Form
>
</>
);
};
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
result
:
Studio
.
result
,
}))(
BarChartSetting
);
dlink-web/src/components/Chart/LineChartSetting/index.less
0 → 100644
View file @
ea875c21
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
dlink-web/src/components/Chart/LineChartSetting/index.tsx
0 → 100644
View file @
ea875c21
import
{
Button
,
Tag
,
Row
,
Col
,
Form
,
Select
,
Empty
,
Switch
}
from
"antd"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
styles
from
"./index.less"
;
import
{
FireOutlined
,
SearchOutlined
,
RedoOutlined
,
InfoCircleOutlined
}
from
'@ant-design/icons'
;
import
{
useEffect
,
useState
}
from
"react"
;
import
React
from
"react"
;
const
{
Option
}
=
Select
;
export
type
LineChartConfig
=
{
padding
:
string
,
xField
:
string
,
yField
:
string
,
seriesField
?:
string
,
stepType
?:
string
,
xAxis
?:
{
type
?:
string
,
},
slider
?:
{},
};
export
type
LineChartProps
=
{
onChange
:
(
values
:
Partial
<
LineChartConfig
>
)
=>
void
;
data
:
[];
column
:
[];
};
const
LineChartSetting
:
React
.
FC
<
LineChartProps
>
=
(
props
)
=>
{
const
{
current
,
column
,
onChange
:
handleChange
,
dispatch
}
=
props
;
const
[
form
]
=
Form
.
useForm
();
useEffect
(()
=>
{
form
.
setFieldsValue
(
current
.
console
.
chart
);
},
[
current
.
console
.
chart
]);
const
onValuesChange
=
(
change
:
any
,
all
:
any
)
=>
{
let
config
:
LineChartConfig
=
{
padding
:
'auto'
,
xField
:
all
.
xField
?
all
.
xField
:
column
[
0
],
yField
:
all
.
yField
?
all
.
yField
:
column
.
length
>
1
?
column
[
1
]:
column
[
0
],
};
if
(
all
.
seriesField
){
config
.
seriesField
=
all
.
seriesField
;
}
if
(
all
.
openStepType
){
config
.
stepType
=
'hv'
;
}
if
(
all
.
openSlider
){
config
.
slider
=
{
start
:
0
,
end
:
0.5
,
};
}
handleChange
(
config
);
};
const
getColumnOptions
=
()
=>
{
const
itemList
=
[];
for
(
const
item
of
column
)
{
itemList
.
push
(<
Option
key=
{
item
}
value=
{
item
}
label=
{
item
}
>
{
item
}
</
Option
>)
}
return
itemList
;
};
return
(
<>
<
Form
form=
{
form
}
className=
{
styles
.
form_setting
}
onValuesChange=
{
onValuesChange
}
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"x 轴"
className=
{
styles
.
form_item
}
name=
"xField"
>
{
column
&&
column
.
length
>
0
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
0
]
}
value=
{
column
[
0
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"y 轴"
className=
{
styles
.
form_item
}
name=
"yField"
>
{
column
&&
column
.
length
>
1
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
1
]
}
value=
{
column
[
1
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"分组字段"
className=
{
styles
.
form_item
}
name=
"seriesField"
>
{
column
&&
column
.
length
>
0
?
(
<
Select
allowClear
showSearch
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"缩略轴"
className=
{
styles
.
form_item
}
name=
"openSlider"
valuePropName=
"checked"
>
<
Switch
checkedChildren=
"启用"
unCheckedChildren=
"禁用"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"阶梯线"
className=
{
styles
.
form_item
}
name=
"openStepType"
valuePropName=
"checked"
>
<
Switch
checkedChildren=
"启用"
unCheckedChildren=
"禁用"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
</
Form
>
</>
);
};
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
result
:
Studio
.
result
,
}))(
LineChartSetting
);
dlink-web/src/components/Chart/PieChartSetting/index.less
0 → 100644
View file @
ea875c21
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
dlink-web/src/components/Chart/PieChartSetting/index.tsx
0 → 100644
View file @
ea875c21
import
{
Button
,
Tag
,
Row
,
Col
,
Form
,
Select
,
Empty
,
Switch
}
from
"antd"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
styles
from
"./index.less"
;
import
{
FireOutlined
,
SearchOutlined
,
RedoOutlined
,
InfoCircleOutlined
}
from
'@ant-design/icons'
;
import
{
useEffect
,
useState
}
from
"react"
;
import
React
from
"react"
;
const
{
Option
}
=
Select
;
export
type
PieChartConfig
=
{
angleField
:
string
,
colorField
:
string
,
label
:
{},
interactions
:
[],
};
export
type
PieChartProps
=
{
onChange
:
(
values
:
Partial
<
PieChartConfig
>
)
=>
void
;
data
:
[];
column
:
[];
};
const
PieChartSetting
:
React
.
FC
<
PieChartProps
>
=
(
props
)
=>
{
const
{
current
,
column
,
onChange
:
handleChange
,
dispatch
}
=
props
;
const
[
form
]
=
Form
.
useForm
();
useEffect
(()
=>
{
form
.
setFieldsValue
(
current
.
console
.
chart
);
},
[
current
.
console
.
chart
]);
const
onValuesChange
=
(
change
:
any
,
all
:
any
)
=>
{
let
config
:
PieChartConfig
=
{
angleField
:
all
.
angleField
?
all
.
angleField
:
column
[
0
],
colorField
:
all
.
colorField
?
all
.
colorField
:
column
.
length
>
1
?
column
[
1
]:
column
[
0
],
label
:
{
type
:
'inner'
,
offset
:
'-30%'
,
content
:
({
percent
})
=>
`
${(
percent
*
100
).
toFixed
(
0
)}
%`
,
style
:
{
fontSize
:
14
,
textAlign
:
'center'
,
},
},
interactions
:
[
{
type
:
'element-active'
,
},
],
};
handleChange
(
config
);
};
const
getColumnOptions
=
()
=>
{
const
itemList
=
[];
for
(
const
item
of
column
)
{
itemList
.
push
(<
Option
key=
{
item
}
value=
{
item
}
label=
{
item
}
>
{
item
}
</
Option
>)
}
return
itemList
;
};
return
(
<>
<
Form
form=
{
form
}
className=
{
styles
.
form_setting
}
onValuesChange=
{
onValuesChange
}
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"弧轴"
className=
{
styles
.
form_item
}
name=
"angleField"
>
{
column
&&
column
.
length
>
0
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
0
]
}
value=
{
column
[
0
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"颜色"
className=
{
styles
.
form_item
}
name=
"colorField"
>
{
column
&&
column
.
length
>
1
?
(
<
Select
allowClear
showSearch
defaultValue=
{
column
[
1
]
}
value=
{
column
[
1
]
}
>
{
getColumnOptions
()
}
</
Select
>):(<
Select
allowClear
showSearch
>
{
column
&&
getColumnOptions
()
}
</
Select
>)
}
</
Form
.
Item
>
</
Col
>
</
Row
>
</
Form
>
</>
);
};
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
result
:
Studio
.
result
,
}))(
PieChartSetting
);
dlink-web/src/components/Chart/index.less
0 → 100644
View file @
ea875c21
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
dlink-web/src/components/Chart/index.tsx
0 → 100644
View file @
ea875c21
import
{
Button
,
Tag
,
Row
,
Col
,
Form
,
Select
,
Empty
}
from
"antd"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
styles
from
"./index.less"
;
import
{
RedoOutlined
}
from
'@ant-design/icons'
;
import
{
CHART
,
isSql
}
from
"@/components/Studio/conf"
;
import
{
Line
,
Bar
,
Pie
}
from
'@ant-design/plots'
;
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
LineChartSetting
from
"./LineChartSetting"
;
import
BarChartSetting
from
"./BarChartSetting"
;
import
PieChartSetting
from
"./PieChartSetting"
;
import
{
showJobData
}
from
"@/components/Studio/StudioEvent/DQL"
;
import
{
Dispatch
}
from
"@@/plugin-dva/connect"
;
const
{
Option
}
=
Select
;
const
Chart
=
(
props
:
any
)
=>
{
const
{
current
,
result
,
height
,
dispatch
}
=
props
;
const
[
config
,
setConfig
]
=
useState
({});
const
[
type
,
setType
]
=
useState
<
string
>
(
CHART
.
LINE
);
const
[
form
]
=
Form
.
useForm
();
useEffect
(()
=>
{
form
.
setFieldsValue
(
current
.
console
.
chart
);
},
[
current
.
console
.
chart
]);
const
toRebuild
=
()
=>
{
if
(
!
isSql
(
current
.
task
.
dialect
)){
showJobData
(
current
.
console
.
result
.
jobId
,
dispatch
);
}
};
const
onValuesChange
=
(
change
:
any
,
all
:
any
)
=>
{
if
(
change
.
type
){
setType
(
change
.
type
);
props
.
saveChart
({
type
:
change
.
type
});
}
};
const
renderChartSetting
=
()
=>
{
if
(
!
current
.
console
.
chart
||!
current
.
console
.
result
.
result
){
return
undefined
;
}
switch
(
type
){
case
CHART
.
LINE
:
return
<
LineChartSetting
column=
{
current
.
console
.
result
.
result
.
columns
}
onChange=
{
(
value
)
=>
{
setConfig
(
value
);
props
.
saveChart
({...
value
,
type
:
current
.
console
.
chart
.
type
});
}
}
/>;
case
CHART
.
BAR
:
return
<
BarChartSetting
column=
{
current
.
console
.
result
.
result
.
columns
}
onChange=
{
(
value
)
=>
{
setConfig
(
value
);
props
.
saveChart
({...
value
,
type
:
current
.
console
.
chart
.
type
});
}
}
/>;
case
CHART
.
PIE
:
return
<
PieChartSetting
column=
{
current
.
console
.
result
.
result
.
columns
}
onChange=
{
(
value
)
=>
{
setConfig
(
value
);
props
.
saveChart
({...
value
,
type
:
current
.
console
.
chart
.
type
});
}
}
/>;
default
:
return
<
LineChartSetting
column=
{
current
.
console
.
result
.
result
.
columns
}
onChange=
{
(
value
)
=>
{
setConfig
(
value
);
props
.
saveChart
({...
value
,
type
:
current
.
console
.
chart
.
type
});
}
}
/>
}
};
const
renderChartContent
=
()
=>
{
if
(
!
current
.
console
.
result
.
result
||!
current
.
console
.
result
.
result
.
columns
){
return
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>;
}
switch
(
current
.
console
.
chart
.
type
){
case
CHART
.
LINE
:
return
<
Line
data=
{
current
.
console
.
result
.
result
.
rowData
}
{
...
config
}
/>;
case
CHART
.
BAR
:
return
<
Bar
data=
{
current
.
console
.
result
.
result
.
rowData
}
{
...
config
}
/>;
case
CHART
.
PIE
:
if
(
config
.
angleField
){
return
<
Pie
data=
{
current
.
console
.
result
.
result
.
rowData
}
{
...
config
}
/>;
}
else
{
return
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>;
}
default
:
return
<
Line
data=
{
current
.
console
.
result
.
result
.
rowData
}
{
...
config
}
/>;
}
};
return
(
<
div
style=
{
{
width
:
'100%'
}
}
>
<
Row
>
<
Col
span=
{
16
}
style=
{
{
padding
:
'20px'
}
}
>
{
renderChartContent
()
}
</
Col
>
<
Col
span=
{
8
}
>
<
Form
form=
{
form
}
className=
{
styles
.
form_setting
}
onValuesChange=
{
onValuesChange
}
>
<
Row
>
<
Col
span=
{
12
}
>
<
Form
.
Item
label=
"图形类型"
className=
{
styles
.
form_item
}
name=
"type"
>
<
Select
defaultValue=
{
CHART
.
LINE
}
value=
{
CHART
.
LINE
}
>
<
Option
value=
{
CHART
.
LINE
}
>
{
CHART
.
LINE
}
</
Option
>
<
Option
value=
{
CHART
.
BAR
}
>
{
CHART
.
BAR
}
</
Option
>
<
Option
value=
{
CHART
.
PIE
}
>
{
CHART
.
PIE
}
</
Option
>
</
Select
>
</
Form
.
Item
>
</
Col
>
{
!
isSql
(
current
.
task
.
dialect
)
?
<
Col
span=
{
12
}
>
<
Button
type=
"primary"
onClick=
{
toRebuild
}
icon=
{
<
RedoOutlined
/>
}
>
刷新数据
</
Button
>
</
Col
>:
undefined
}
</
Row
>
</
Form
>
{
renderChartSetting
()
}
</
Col
>
</
Row
>
</
div
>
);
};
const
mapDispatchToProps
=
(
dispatch
:
Dispatch
)
=>
({
saveChart
:(
chart
:
any
)
=>
dispatch
({
type
:
"Studio/saveChart"
,
payload
:
chart
,
}),
})
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
result
:
Studio
.
result
,
}),
mapDispatchToProps
)(
Chart
);
dlink-web/src/components/Studio/StudioConsole/StudioHistory/index.tsx
View file @
ea875c21
...
@@ -12,6 +12,7 @@ import {
...
@@ -12,6 +12,7 @@ import {
import
styles
from
"./index.less"
;
import
styles
from
"./index.less"
;
import
{
showJobData
}
from
"@/components/Studio/StudioEvent/DQL"
;
import
{
showJobData
}
from
"@/components/Studio/StudioEvent/DQL"
;
import
StudioPreview
from
"../StudioPreview"
;
import
StudioPreview
from
"../StudioPreview"
;
import
{
getJobData
}
from
"@/pages/FlinkSqlStudio/service"
;
const
{
Title
,
Paragraph
,
Text
,
Link
}
=
Typography
;
const
{
Title
,
Paragraph
,
Text
,
Link
}
=
Typography
;
...
@@ -61,14 +62,19 @@ const StudioHistory = (props: any) => {
...
@@ -61,14 +62,19 @@ const StudioHistory = (props: any) => {
const
[
row
,
setRow
]
=
useState
<
HistoryItem
>
();
const
[
row
,
setRow
]
=
useState
<
HistoryItem
>
();
const
[
config
,
setConfig
]
=
useState
<
HistoryConfig
>
();
const
[
config
,
setConfig
]
=
useState
<
HistoryConfig
>
();
const
[
type
,
setType
]
=
useState
<
number
>
();
const
[
type
,
setType
]
=
useState
<
number
>
();
const
[
result
,
setResult
]
=
useState
<
{}
>
();
const
showDetail
=
(
row
:
HistoryItem
,
type
:
number
)
=>
{
const
showDetail
=
(
row
:
HistoryItem
,
type
:
number
)
=>
{
setRow
(
row
);
setRow
(
row
);
setModalVisit
(
true
);
setModalVisit
(
true
);
setType
(
type
);
setType
(
type
);
setConfig
(
JSON
.
parse
(
row
.
config
));
setConfig
(
JSON
.
parse
(
row
.
config
));
if
(
type
==
3
){
if
(
type
===
3
){
showJobData
(
row
.
jobId
,
dispatch
)
// showJobData(row.jobId,dispatch)
const
res
=
getJobData
(
row
.
jobId
);
res
.
then
((
resd
)
=>
{
setResult
(
resd
.
datas
);
});
}
}
};
};
...
@@ -341,7 +347,7 @@ const StudioHistory = (props: any) => {
...
@@ -341,7 +347,7 @@ const StudioHistory = (props: any) => {
</
Tag
>
</
Tag
>
</
ProDescriptions
.
Item
>
</
ProDescriptions
.
Item
>
<
ProDescriptions
.
Item
span=
{
2
}
>
<
ProDescriptions
.
Item
span=
{
2
}
>
<
StudioPreview
style=
{
{
width
:
'100%'
}
}
/>
<
StudioPreview
result=
{
result
}
style=
{
{
width
:
'100%'
}
}
/>
</
ProDescriptions
.
Item
>
</
ProDescriptions
.
Item
>
</
ProDescriptions
>
</
ProDescriptions
>
)
}
)
}
...
...
dlink-web/src/components/Studio/StudioConsole/StudioPreview/index.tsx
View file @
ea875c21
...
@@ -34,5 +34,5 @@ const StudioPreview = (props:any) => {
...
@@ -34,5 +34,5 @@ const StudioPreview = (props:any) => {
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
current
:
Studio
.
current
,
current
:
Studio
.
current
,
result
:
Studio
.
result
,
//
result: Studio.result,
}))(
StudioPreview
);
}))(
StudioPreview
);
dlink-web/src/components/Studio/StudioConsole/StudioTable/index.tsx
View file @
ea875c21
...
@@ -32,8 +32,8 @@ const StudioTable = (props:any) => {
...
@@ -32,8 +32,8 @@ const StudioTable = (props:any) => {
{
current
.
console
.
result
.
jobId
&&
(<
Tag
color=
"blue"
key=
{
current
.
console
.
result
.
jobId
}
>
{
current
.
console
.
result
.
jobId
&&
(<
Tag
color=
"blue"
key=
{
current
.
console
.
result
.
jobId
}
>
<
FireOutlined
/>
{
current
.
console
.
result
.
jobId
}
<
FireOutlined
/>
{
current
.
console
.
result
.
jobId
}
</
Tag
>)
}
</
Tag
>)
}
{
result
.
columns
?
{
current
.
console
.
result
.
result
&&
current
.
console
.
result
.
result
.
columns
?
<
DTable
dataSource=
{
result
.
rowData
}
columns=
{
getColumns
(
result
.
columns
)
}
/>
<
DTable
dataSource=
{
current
.
console
.
result
.
result
.
rowData
}
columns=
{
getColumns
(
current
.
console
.
result
.
result
.
columns
)
}
/>
:(<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>)
:(<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
/>)
}
}
</>)
</>)
...
...
dlink-web/src/components/Studio/StudioConsole/index.tsx
View file @
ea875c21
import
{
Tabs
,
Empty
}
from
"antd"
;
import
{
Tabs
,
Empty
}
from
"antd"
;
import
{
import
{
CodeOutlined
,
TableOutlined
,
RadarChartOutlined
,
CalendarOutlined
,
FileSearchOutlined
,
DesktopOutlined
CodeOutlined
,
TableOutlined
,
RadarChartOutlined
,
CalendarOutlined
,
FileSearchOutlined
,
DesktopOutlined
,
FunctionOutlined
,
ApartmentOutlined
,
FunctionOutlined
,
ApartmentOutlined
,
BarChartOutlined
}
from
"@ant-design/icons"
;
}
from
"@ant-design/icons"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
{
connect
}
from
"umi"
;
...
@@ -13,6 +13,7 @@ import StudioFX from "./StudioFX";
...
@@ -13,6 +13,7 @@ import StudioFX from "./StudioFX";
import
StudioCA
from
"./StudioCA"
;
import
StudioCA
from
"./StudioCA"
;
import
StudioProcess
from
"./StudioProcess"
;
import
StudioProcess
from
"./StudioProcess"
;
import
{
Scrollbars
}
from
'react-custom-scrollbars'
;
import
{
Scrollbars
}
from
'react-custom-scrollbars'
;
import
Chart
from
"@/components/Chart"
;
const
{
TabPane
}
=
Tabs
;
const
{
TabPane
}
=
Tabs
;
...
@@ -50,6 +51,19 @@ const StudioConsole = (props: any) => {
...
@@ -50,6 +51,19 @@ const StudioConsole = (props: any) => {
<
StudioTable
/>
<
StudioTable
/>
</
Scrollbars
>
</
Scrollbars
>
</
TabPane
>
</
TabPane
>
<
TabPane
tab=
{
<
span
>
<
BarChartOutlined
/>
BI
</
span
>
}
key=
"StudioChart"
>
<
Scrollbars
style=
{
{
height
:
consoleHeight
}
}
>
<
Chart
height=
{
consoleHeight
}
/>
</
Scrollbars
>
</
TabPane
>
<
TabPane
<
TabPane
tab=
{
tab=
{
<
span
>
<
span
>
...
...
dlink-web/src/components/Studio/StudioEvent/DQL.ts
View file @
ea875c21
import
{
getJobData
}
from
"@/pages/FlinkSqlStudio/service"
;
import
{
getJobData
}
from
"@/pages/FlinkSqlStudio/service"
;
export
function
showJobData
(
jobId
:
string
,
dispatch
:
any
)
{
export
function
showJobData
(
jobId
:
string
,
dispatch
:
any
)
{
if
(
!
jobId
){
return
;
}
const
res
=
getJobData
(
jobId
);
const
res
=
getJobData
(
jobId
);
res
.
then
((
result
)
=>
{
res
.
then
((
result
)
=>
{
dispatch
&&
dispatch
({
dispatch
&&
dispatch
({
...
...
dlink-web/src/components/Studio/StudioMenu/index.tsx
View file @
ea875c21
...
@@ -11,14 +11,14 @@ import Button from "antd/es/button/button";
...
@@ -11,14 +11,14 @@ import Button from "antd/es/button/button";
import
Breadcrumb
from
"antd/es/breadcrumb/Breadcrumb"
;
import
Breadcrumb
from
"antd/es/breadcrumb/Breadcrumb"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
connect
}
from
"umi"
;
import
{
connect
}
from
"umi"
;
import
{
handleAddOrUpdate
,
postDataArray
}
from
"@/components/Common/crud"
;
import
{
postDataArray
}
from
"@/components/Common/crud"
;
import
{
executeSql
,
explainSql
,
getJobPlan
}
from
"@/pages/FlinkSqlStudio/service"
;
import
{
executeSql
,
getJobPlan
}
from
"@/pages/FlinkSqlStudio/service"
;
import
StudioHelp
from
"./StudioHelp"
;
import
StudioHelp
from
"./StudioHelp"
;
import
StudioGraph
from
"./StudioGraph"
;
import
StudioGraph
from
"./StudioGraph"
;
import
{
showCluster
,
showTables
,
saveTask
}
from
"@/components/Studio/StudioEvent/DDL"
;
import
{
showCluster
,
showTables
,
saveTask
}
from
"@/components/Studio/StudioEvent/DDL"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
StudioExplain
from
"../StudioConsole/StudioExplain"
;
import
StudioExplain
from
"../StudioConsole/StudioExplain"
;
import
{
DIALECT
,
isSql
}
from
"@/components/Studio/conf"
;
import
{
DIALECT
,
is
Online
,
is
Sql
}
from
"@/components/Studio/conf"
;
import
{
import
{
ModalForm
,
ModalForm
,
}
from
'@ant-design/pro-form'
;
}
from
'@ant-design/pro-form'
;
...
@@ -37,10 +37,13 @@ const StudioMenu = (props: any) => {
...
@@ -37,10 +37,13 @@ const StudioMenu = (props: any) => {
const
[
modalVisible
,
handleModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
modalVisible
,
handleModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
exportModalVisible
,
handleExportModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
exportModalVisible
,
handleExportModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
graphModalVisible
,
handleGraphModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
graphModalVisible
,
handleGraphModalVisible
]
=
useState
<
boolean
>
(
false
);
const
[
explainData
,
setExplainData
]
=
useState
([]);
const
[
graphData
,
setGraphData
]
=
useState
();
const
[
graphData
,
setGraphData
]
=
useState
();
const
execute
=
()
=>
{
const
execute
=
()
=>
{
if
(
!
isSql
(
current
.
task
.
dialect
)
&&!
isOnline
(
current
.
task
.
type
)){
message
.
warn
(
`该任务执行模式为【
${
current
.
task
.
type
}
】,不支持 SQL 查询,请手动保存后使用右侧按钮——作业提交`
);
return
;
}
let
selectsql
=
null
;
let
selectsql
=
null
;
if
(
current
.
monaco
.
current
)
{
if
(
current
.
monaco
.
current
)
{
let
selection
=
current
.
monaco
.
current
.
editor
.
getSelection
();
let
selection
=
current
.
monaco
.
current
.
editor
.
getSelection
();
...
@@ -75,7 +78,7 @@ const StudioMenu = (props: any) => {
...
@@ -75,7 +78,7 @@ const StudioMenu = (props: any) => {
if
(
res
.
datas
.
success
)
{
if
(
res
.
datas
.
success
)
{
message
.
success
(
'执行成功'
);
message
.
success
(
'执行成功'
);
}
else
{
}
else
{
message
.
success
(
'执行失败'
);
message
.
error
(
'执行失败'
);
}
}
let
newTabs
=
tabs
;
let
newTabs
=
tabs
;
for
(
let
i
=
0
;
i
<
newTabs
.
panes
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
newTabs
.
panes
.
length
;
i
++
)
{
...
@@ -230,8 +233,8 @@ const StudioMenu = (props: any) => {
...
@@ -230,8 +233,8 @@ const StudioMenu = (props: any) => {
const
runMenu
=
(
const
runMenu
=
(
<
Menu
>
<
Menu
>
<
Menu
.
Item
onClick=
{
execute
}
>
同步执行
</
Menu
.
Item
>
<
Menu
.
Item
onClick=
{
execute
}
>
SQL 查询
</
Menu
.
Item
>
<
Menu
.
Item
onClick=
{
submit
}
>
异步提交
</
Menu
.
Item
>
<
Menu
.
Item
onClick=
{
submit
}
>
提交作业
</
Menu
.
Item
>
</
Menu
>
</
Menu
>
);
);
...
@@ -348,7 +351,7 @@ const StudioMenu = (props: any) => {
...
@@ -348,7 +351,7 @@ const StudioMenu = (props: any) => {
/>
/>
</
Tooltip
>)
}
</
Tooltip
>)
}
{
(
!
current
.
task
.
dialect
||
current
.
task
.
dialect
===
DIALECT
.
FLINKSQL
||
isSql
(
current
.
task
.
dialect
))
&&
(
{
(
!
current
.
task
.
dialect
||
current
.
task
.
dialect
===
DIALECT
.
FLINKSQL
||
isSql
(
current
.
task
.
dialect
))
&&
(
<
Tooltip
title=
"执行当前的
FlinkSql
"
>
<
Tooltip
title=
"执行当前的
SQL
"
>
<
Button
<
Button
type=
"text"
type=
"text"
icon=
{
<
PlayCircleTwoTone
/>
}
icon=
{
<
PlayCircleTwoTone
/>
}
...
@@ -357,7 +360,7 @@ const StudioMenu = (props: any) => {
...
@@ -357,7 +360,7 @@ const StudioMenu = (props: any) => {
/>
/>
</
Tooltip
>)
}
</
Tooltip
>)
}
{
(
!
current
.
task
.
dialect
||
current
.
task
.
dialect
===
DIALECT
.
FLINKSQL
||
isSql
(
current
.
task
.
dialect
))
&&
(<>
{
(
!
current
.
task
.
dialect
||
current
.
task
.
dialect
===
DIALECT
.
FLINKSQL
||
isSql
(
current
.
task
.
dialect
))
&&
(<>
<
Tooltip
title=
"提交当前的作业到集群"
>
<
Tooltip
title=
"提交当前的作业到集群
,提交前请手动保存
"
>
<
Button
<
Button
type=
"text"
type=
"text"
icon=
{
<
RocketTwoTone
/>
}
icon=
{
<
RocketTwoTone
/>
}
...
...
dlink-web/src/components/Studio/StudioRightTool/StudioUDFInfo/index.less
0 → 100644
View file @
ea875c21
@import '~antd/es/style/themes/default.less';
.form_setting{
padding-left: 10px;
}
.form_item{
margin-bottom: 5px;
}
dlink-web/src/components/Studio/StudioRightTool/StudioUDFInfo/index.tsx
0 → 100644
View file @
ea875c21
import
{
connect
}
from
"umi"
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
Form
,
Switch
,
Row
,
Col
,
Tooltip
,
Button
,
Input
}
from
"antd"
;
import
{
InfoCircleOutlined
,
MinusSquareOutlined
}
from
"@ant-design/icons"
;
import
styles
from
"./index.less"
;
import
{
useEffect
}
from
"react"
;
import
{
JarStateType
}
from
"@/pages/Jar/model"
;
import
{
Scrollbars
}
from
"react-custom-scrollbars"
;
const
StudioUDFInfo
=
(
props
:
any
)
=>
{
const
{
current
,
form
,
toolHeight
}
=
props
;
useEffect
(()
=>
{
form
.
setFieldsValue
(
current
.
task
);
},
[
current
.
task
]);
return
(
<>
<
Row
>
<
Col
span=
{
24
}
>
<
div
style=
{
{
float
:
"right"
}
}
>
<
Tooltip
title=
"最小化"
>
<
Button
type=
"text"
icon=
{
<
MinusSquareOutlined
/>
}
/>
</
Tooltip
>
</
div
>
</
Col
>
</
Row
>
<
Scrollbars
style=
{
{
height
:
(
toolHeight
-
32
)}
}
>
<
Form
form=
{
form
}
layout=
"vertical"
className=
{
styles
.
form_setting
}
>
<
Row
>
<
Col
span=
{
24
}
>
<
Form
.
Item
label=
"类名"
className=
{
styles
.
form_item
}
name=
"savePointPath"
>
<
Input
readOnly=
{
true
}
placeholder=
"自动识别"
/>
</
Form
.
Item
>
</
Col
>
</
Row
>
</
Form
>
</
Scrollbars
>
</>
);
};
export
default
connect
(({
Studio
,
Jar
}:
{
Studio
:
StateType
,
Jar
:
JarStateType
})
=>
({
current
:
Studio
.
current
,
toolHeight
:
Studio
.
toolHeight
,
}))(
StudioUDFInfo
);
dlink-web/src/components/Studio/StudioRightTool/index.tsx
View file @
ea875c21
...
@@ -8,6 +8,7 @@ import StudioSetting from "./StudioSetting";
...
@@ -8,6 +8,7 @@ import StudioSetting from "./StudioSetting";
import
StudioSavePoint
from
"./StudioSavePoint"
;
import
StudioSavePoint
from
"./StudioSavePoint"
;
import
StudioEnvSetting
from
"./StudioEnvSetting"
;
import
StudioEnvSetting
from
"./StudioEnvSetting"
;
import
StudioSqlConfig
from
"./StudioSqlConfig"
;
import
StudioSqlConfig
from
"./StudioSqlConfig"
;
import
StudioUDFInfo
from
"./StudioUDFInfo"
;
import
{
DIALECT
,
isSql
}
from
"@/components/Studio/conf"
;
import
{
DIALECT
,
isSql
}
from
"@/components/Studio/conf"
;
const
{
TabPane
}
=
Tabs
;
const
{
TabPane
}
=
Tabs
;
...
@@ -26,7 +27,7 @@ const StudioRightTool = (props:any) => {
...
@@ -26,7 +27,7 @@ const StudioRightTool = (props:any) => {
return
renderEnvContent
();
return
renderEnvContent
();
}
}
if
(
DIALECT
.
JAVA
===
current
.
task
.
dialect
){
if
(
DIALECT
.
JAVA
===
current
.
task
.
dialect
){
return
undefined
;
return
renderUDFContent
()
;
}
}
return
renderFlinkSqlContent
();
return
renderFlinkSqlContent
();
};
};
...
@@ -47,6 +48,14 @@ const StudioRightTool = (props:any) => {
...
@@ -47,6 +48,14 @@ const StudioRightTool = (props:any) => {
</>)
</>)
};
};
const
renderUDFContent
=
()
=>
{
return
(<>
<
TabPane
tab=
{
<
span
><
SettingOutlined
/>
UDF信息
</
span
>
}
key=
"StudioUDFInfo"
>
<
StudioUDFInfo
form=
{
form
}
/>
</
TabPane
>
</>)
};
const
renderFlinkSqlContent
=
()
=>
{
const
renderFlinkSqlContent
=
()
=>
{
return
(<><
TabPane
tab=
{
<
span
><
SettingOutlined
/>
作业配置
</
span
>
}
key=
"StudioSetting"
>
return
(<><
TabPane
tab=
{
<
span
><
SettingOutlined
/>
作业配置
</
span
>
}
key=
"StudioSetting"
>
<
StudioSetting
form=
{
form
}
/>
<
StudioSetting
form=
{
form
}
/>
...
...
dlink-web/src/components/Studio/StudioTabs/index.tsx
View file @
ea875c21
import
{
message
,
Tabs
}
from
'antd'
;
import
{
message
,
Tabs
,
Menu
,
Dropdown
}
from
'antd'
;
import
React
,
{
useState
}
from
'react'
;
import
React
,
{
useState
}
from
'react'
;
import
{
connect
}
from
"umi"
;
import
{
connect
}
from
'umi'
;
import
{
StateType
}
from
"@/pages/FlinkSqlStudio/model"
;
import
{
StateType
}
from
'@/pages/FlinkSqlStudio/model'
;
import
styles
from
'./index.less'
;
import
styles
from
'./index.less'
;
import
StudioEdit
from
'../StudioEdit'
;
import
StudioEdit
from
'../StudioEdit'
;
import
{
saveTask
}
from
"@/components/Studio/StudioEvent/DDL"
;
import
{
saveTask
}
from
'@/components/Studio/StudioEvent/DDL'
;
import
{
DIALECT
}
from
'../conf'
;
import
{
DIALECT
}
from
'../conf'
;
const
{
TabPane
}
=
Tabs
;
const
{
TabPane
}
=
Tabs
;
const
EditorTabs
=
(
props
:
any
)
=>
{
const
EditorTabs
=
(
props
:
any
)
=>
{
const
{
tabs
,
dispatch
,
current
,
toolHeight
,
width
}
=
props
;
const
{
tabs
,
dispatch
,
current
,
toolHeight
,
width
}
=
props
;
const
onChange
=
(
activeKey
:
any
)
=>
{
const
onChange
=
(
activeKey
:
any
)
=>
{
dispatch
&&
dispatch
({
dispatch
&&
type
:
"Studio/saveToolHeight"
,
dispatch
({
payload
:
toolHeight
-
0.0001
,
type
:
'Studio/saveToolHeight'
,
payload
:
toolHeight
-
0.0001
,
});
});
dispatch
({
dispatch
({
type
:
"Studio/changeActiveKey"
,
type
:
'Studio/changeActiveKey'
,
payload
:
activeKey
,
payload
:
activeKey
,
});
});
};
};
...
@@ -27,9 +28,10 @@ const EditorTabs = (props: any) => {
...
@@ -27,9 +28,10 @@ const EditorTabs = (props: any) => {
if
(
action
==
'add'
)
{
if
(
action
==
'add'
)
{
add
();
add
();
}
else
if
(
action
==
'remove'
)
{
}
else
if
(
action
==
'remove'
)
{
dispatch
&&
dispatch
({
dispatch
&&
type
:
"Studio/saveToolHeight"
,
dispatch
({
payload
:
toolHeight
-
0.0001
,
type
:
'Studio/saveToolHeight'
,
payload
:
toolHeight
-
0.0001
,
});
});
if
(
current
.
isModified
)
{
if
(
current
.
isModified
)
{
saveTask
(
current
,
dispatch
);
saveTask
(
current
,
dispatch
);
...
@@ -51,7 +53,7 @@ const EditorTabs = (props: any) => {
...
@@ -51,7 +53,7 @@ const EditorTabs = (props: any) => {
}
}
});
});
let
panes
=
tabs
.
panes
;
let
panes
=
tabs
.
panes
;
const
newPanes
=
panes
.
filter
(
pane
=>
pane
.
key
.
toString
()
!=
targetKey
);
const
newPanes
=
panes
.
filter
(
(
pane
)
=>
pane
.
key
.
toString
()
!=
targetKey
);
if
(
newPanes
.
length
&&
newActiveKey
.
toString
()
===
targetKey
)
{
if
(
newPanes
.
length
&&
newActiveKey
.
toString
()
===
targetKey
)
{
if
(
lastIndex
>
0
)
{
if
(
lastIndex
>
0
)
{
newActiveKey
=
newPanes
[
lastIndex
].
key
;
newActiveKey
=
newPanes
[
lastIndex
].
key
;
...
@@ -60,13 +62,49 @@ const EditorTabs = (props: any) => {
...
@@ -60,13 +62,49 @@ const EditorTabs = (props: any) => {
}
}
}
}
dispatch
({
dispatch
({
type
:
"Studio/saveTabs"
,
type
:
'Studio/saveTabs'
,
payload
:
{
payload
:
{
activeKey
:
newActiveKey
,
activeKey
:
newActiveKey
,
panes
:
newPanes
,
panes
:
newPanes
,
},
},
});
});
};
};
const
handleClickMenu
=
(
e
:
any
,
current
)
=>
{
dispatch
({
type
:
'Studio/closeTabs'
,
payload
:
{
deleteType
:
e
.
key
,
current
},
});
};
const
menu
=
(
pane
)
=>
(
<
Menu
onClick=
{
(
e
)
=>
handleClickMenu
(
e
,
pane
)
}
>
<
Menu
.
Item
key=
"CLOSE_OTHER"
>
<
span
>
关闭其他
</
span
>
</
Menu
.
Item
>
<
Menu
.
Item
key=
"CLOSE_ALL"
>
<
span
>
关闭所有
</
span
>
</
Menu
.
Item
>
</
Menu
>
);
const
Tab
=
(
pane
:
any
)
=>
(
<
span
>
{
pane
.
key
===
0
?
(
pane
.
title
)
:
(
<
Dropdown
overlay=
{
menu
(
pane
)
}
trigger=
{
[
'contextMenu'
]
}
>
<
span
className=
"ant-dropdown-link"
>
{
pane
.
title
}
</
span
>
</
Dropdown
>
)
}
</
span
>
);
return
(
return
(
<
Tabs
<
Tabs
hideAdd
hideAdd
...
@@ -75,18 +113,21 @@ const EditorTabs = (props: any) => {
...
@@ -75,18 +113,21 @@ const EditorTabs = (props: any) => {
onChange=
{
onChange
}
onChange=
{
onChange
}
activeKey=
{
tabs
.
activeKey
+
''
}
activeKey=
{
tabs
.
activeKey
+
''
}
onEdit=
{
onEdit
}
onEdit=
{
onEdit
}
className=
{
styles
[
"edit-tabs"
]
}
className=
{
styles
[
'edit-tabs'
]
}
style=
{
{
height
:
toolHeight
}
}
style=
{
{
height
:
toolHeight
}
}
>
>
{
tabs
.
panes
.
map
(
pane
=>
(
{
tabs
.
panes
.
map
((
pane
)
=>
(
<
TabPane
tab=
{
pane
.
title
}
key=
{
pane
.
key
}
closable=
{
pane
.
closable
}
>
<
TabPane
tab=
{
Tab
(
pane
)
}
key=
{
pane
.
key
}
closable=
{
pane
.
closable
}
>
<
StudioEdit
tabsKey=
{
pane
.
key
}
height=
{
(
toolHeight
-
32
)
}
width=
{
width
}
<
StudioEdit
language=
{
current
.
task
.
dialect
===
DIALECT
.
JAVA
?
'java'
:
'sql'
}
/>
tabsKey=
{
pane
.
key
}
height=
{
toolHeight
-
32
}
width=
{
width
}
language=
{
current
.
task
.
dialect
===
DIALECT
.
JAVA
?
'java'
:
'sql'
}
/>
</
TabPane
>
</
TabPane
>
))
}
))
}
</
Tabs
>
</
Tabs
>
);
)
};
};
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
export
default
connect
(({
Studio
}:
{
Studio
:
StateType
})
=>
({
...
...
dlink-web/src/components/Studio/StudioTree/index.tsx
View file @
ea875c21
...
@@ -161,7 +161,8 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
...
@@ -161,7 +161,8 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
...
result
.
datas
,
...
result
.
datas
,
},
},
console
:{
console
:{
result
:[],
result
:
{},
chart
:
{},
},
},
monaco
:
React
.
createRef
(),
monaco
:
React
.
createRef
(),
};
};
...
...
dlink-web/src/components/Studio/conf.ts
View file @
ea875c21
...
@@ -20,8 +20,14 @@ export const DIALECT = {
...
@@ -20,8 +20,14 @@ export const DIALECT = {
JAVA
:
'Java'
,
JAVA
:
'Java'
,
};
};
export
const
isSql
=
(
type
:
string
)
=>
{
export
const
CHART
=
{
switch
(
type
){
LINE
:
'折线图'
,
BAR
:
'条形图'
,
PIE
:
'饼图'
,
};
export
const
isSql
=
(
dialect
:
string
)
=>
{
switch
(
dialect
){
case
DIALECT
.
SQL
:
case
DIALECT
.
SQL
:
case
DIALECT
.
MYSQL
:
case
DIALECT
.
MYSQL
:
case
DIALECT
.
ORACLE
:
case
DIALECT
.
ORACLE
:
...
@@ -32,4 +38,16 @@ export const isSql = (type: string)=>{
...
@@ -32,4 +38,16 @@ export const isSql = (type: string)=>{
default
:
default
:
return
false
;
return
false
;
}
}
};
export
const
isOnline
=
(
type
:
string
)
=>
{
switch
(
type
){
case
RUN_MODE
.
LOCAL
:
case
RUN_MODE
.
STANDALONE
:
case
RUN_MODE
.
YARN_SESSION
:
case
RUN_MODE
.
KUBERNETES_SESSION
:
return
true
;
default
:
return
false
;
}
}
}
dlink-web/src/pages/FlinkSqlStudio/model.ts
View file @
ea875c21
...
@@ -96,6 +96,7 @@ export type TaskType = {
...
@@ -96,6 +96,7 @@ export type TaskType = {
export
type
ConsoleType
=
{
export
type
ConsoleType
=
{
result
:
{};
result
:
{};
chart
:
{};
}
}
export
type
TabsItemType
=
{
export
type
TabsItemType
=
{
...
@@ -172,6 +173,7 @@ export type ModelType = {
...
@@ -172,6 +173,7 @@ export type ModelType = {
saveMonaco
:
Reducer
<
StateType
>
;
saveMonaco
:
Reducer
<
StateType
>
;
saveSqlMetaData
:
Reducer
<
StateType
>
;
saveSqlMetaData
:
Reducer
<
StateType
>
;
saveTabs
:
Reducer
<
StateType
>
;
saveTabs
:
Reducer
<
StateType
>
;
closeTabs
:
Reducer
<
StateType
>
;
changeActiveKey
:
Reducer
<
StateType
>
;
changeActiveKey
:
Reducer
<
StateType
>
;
saveTaskData
:
Reducer
<
StateType
>
;
saveTaskData
:
Reducer
<
StateType
>
;
saveSession
:
Reducer
<
StateType
>
;
saveSession
:
Reducer
<
StateType
>
;
...
@@ -184,6 +186,7 @@ export type ModelType = {
...
@@ -184,6 +186,7 @@ export type ModelType = {
saveClusterConfiguration
:
Reducer
<
StateType
>
;
saveClusterConfiguration
:
Reducer
<
StateType
>
;
saveDataBase
:
Reducer
<
StateType
>
;
saveDataBase
:
Reducer
<
StateType
>
;
saveEnv
:
Reducer
<
StateType
>
;
saveEnv
:
Reducer
<
StateType
>
;
saveChart
:
Reducer
<
StateType
>
;
};
};
};
};
...
@@ -238,6 +241,7 @@ const Model: ModelType = {
...
@@ -238,6 +241,7 @@ const Model: ModelType = {
},
},
console
:
{
console
:
{
result
:
{},
result
:
{},
chart
:
{},
},
},
monaco
:
{},
monaco
:
{},
sqlMetaData
:
undefined
,
sqlMetaData
:
undefined
,
...
@@ -284,6 +288,7 @@ const Model: ModelType = {
...
@@ -284,6 +288,7 @@ const Model: ModelType = {
},
},
console
:
{
console
:
{
result
:
{},
result
:
{},
chart
:
{},
},
},
monaco
:
{},
monaco
:
{},
sqlMetaData
:
undefined
,
sqlMetaData
:
undefined
,
...
@@ -420,6 +425,31 @@ const Model: ModelType = {
...
@@ -420,6 +425,31 @@ const Model: ModelType = {
},
},
};
};
},
},
closeTabs
(
state
,
{
payload
})
{
const
{
deleteType
,
current
}
=
payload
;
const
newTabs
=
state
.
tabs
;
const
firstKey
=
newTabs
.
panes
[
0
].
key
;
let
newCurrent
=
newTabs
.
panes
[
0
];
if
(
deleteType
===
'CLOSE_OTHER'
)
{
const
keys
=
[
firstKey
,
current
.
key
];
newCurrent
=
{...
current
};
newTabs
.
activeKey
=
current
.
key
;
newTabs
.
panes
=
newTabs
.
panes
.
filter
(
item
=>
keys
.
includes
(
item
.
key
));
}
else
{
newTabs
.
panes
=
[];
newTabs
.
activeKey
=
firstKey
}
return
{
...
state
,
current
:
{
...
newCurrent
},
tabs
:
{
...
newTabs
,
}
};
},
changeActiveKey
(
state
,
{
payload
})
{
changeActiveKey
(
state
,
{
payload
})
{
const
{
tabs
}
=
state
;
const
{
tabs
}
=
state
;
tabs
.
activeKey
=
payload
;
tabs
.
activeKey
=
payload
;
...
@@ -484,11 +514,25 @@ const Model: ModelType = {
...
@@ -484,11 +514,25 @@ const Model: ModelType = {
};
};
},
},
saveResult
(
state
,
{
payload
})
{
saveResult
(
state
,
{
payload
})
{
// return {
// ...state,
// result: {
// ...payload
// },
// };
let
newTabs
=
state
?.
tabs
;
let
newCurrent
=
state
?.
current
;
for
(
let
i
=
0
;
i
<
newTabs
.
panes
.
length
;
i
++
)
{
if
(
newTabs
.
panes
[
i
].
key
===
newTabs
.
activeKey
)
{
newTabs
.
panes
[
i
].
console
.
result
.
result
=
payload
;
newCurrent
=
newTabs
.
panes
[
i
];
break
;
}
}
return
{
return
{
...
state
,
...
state
,
result
:
{
current
:
newCurrent
,
...
payload
tabs
:
newTabs
,
},
};
};
},
},
saveCluster
(
state
,
{
payload
})
{
saveCluster
(
state
,
{
payload
})
{
...
@@ -516,6 +560,21 @@ const Model: ModelType = {
...
@@ -516,6 +560,21 @@ const Model: ModelType = {
...
state
,
...
state
,
env
:
payload
,
env
:
payload
,
};
};
},
saveChart
(
state
,
{
payload
})
{
let
newTabs
=
state
?.
tabs
;
let
newCurrent
=
state
?.
current
;
for
(
let
i
=
0
;
i
<
newTabs
.
panes
.
length
;
i
++
)
{
if
(
newTabs
.
panes
[
i
].
key
===
newTabs
.
activeKey
)
{
newTabs
.
panes
[
i
].
console
.
chart
=
payload
;
newCurrent
=
newTabs
.
panes
[
i
];
break
;
}
}
return
{
...
state
,
current
:
newCurrent
,
tabs
:
newTabs
,
};
},
},
},
},
};
};
...
...
dlink-web/src/pages/Welcome.tsx
View file @
ea875c21
...
@@ -530,7 +530,7 @@ export default (): React.ReactNode => {
...
@@ -530,7 +530,7 @@ export default (): React.ReactNode => {
<
Link
>
修复 set 语法在1.11和1.12的兼容问题
</
Link
>
<
Link
>
修复 set 语法在1.11和1.12的兼容问题
</
Link
>
</
li
>
</
li
>
<
li
>
<
li
>
<
Link
>
升级各版本 Flink 依赖至最新版本以解决核弹问题
</
Link
>
<
Link
>
升级
各版本 Flink 依赖至最新版本以解决核弹问题
</
Link
>
</
li
>
</
li
>
<
li
>
<
li
>
<
Link
>
新增 Yarn 的 Kerboros 验证
</
Link
>
<
Link
>
新增 Yarn 的 Kerboros 验证
</
Link
>
...
@@ -539,10 +539,22 @@ export default (): React.ReactNode => {
...
@@ -539,10 +539,22 @@ export default (): React.ReactNode => {
<
Link
>
新增 ChangLog 和 Table 的查询及自动停止实现
</
Link
>
<
Link
>
新增 ChangLog 和 Table 的查询及自动停止实现
</
Link
>
</
li
>
</
li
>
<
li
>
<
li
>
<
Link
>
修改项目名为 Dinky 以及图标
</
Link
>
<
Link
>
修改
项目名为 Dinky 以及图标
</
Link
>
</
li
>
</
li
>
<
li
>
<
li
>
<
Link
>
优化血缘分析图
</
Link
>
<
Link
>
优化 血缘分析图
</
Link
>
</
li
>
<
li
>
<
Link
>
新增 快捷键保存、校验、美化
</
Link
>
</
li
>
<
li
>
<
Link
>
新增 UDF Java方言的Local模式的在线编写、调试、动态加载
</
Link
>
</
li
>
<
li
>
<
Link
>
新增 编辑器选项卡右键关闭其他和关闭所有
</
Link
>
</
li
>
<
li
>
<
Link
>
新增 BI选项卡的折线图、条形图、饼图
</
Link
>
</
li
>
</
li
>
</
ul
>
</
ul
>
</
Paragraph
>
</
Paragraph
>
...
...
docs/en-US/guide/quickstart.md
View file @
ea875c21
...
@@ -87,6 +87,7 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
...
@@ -87,6 +87,7 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 快捷键保存、校验、美化 | 0.5.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
...
@@ -94,16 +95,19 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
...
@@ -94,16 +95,19 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 UDF Java 方言Local模式在线编写、调试、动态加载 | 0.5.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 UDF java方言代码的开发 | 0.5.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | BI | 新增 折线图的渲染 | 0.5.0 |
| | | 新增 条形图图的渲染 | 0.5.0 |
| | | 新增 饼图的渲染 | 0.5.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
...
...
docs/guide/quickstart.md
View file @
ea875c21
...
@@ -87,6 +87,7 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
...
@@ -87,6 +87,7 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 选中片段执行 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 布局拖拽 | 0.4.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 SQL导出 | 0.5.0 |
| | | 新增 快捷键保存、校验、美化 | 0.5.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 local 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 standalone 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn session 模式下 FlinkSQL 提交 | 0.4.0 |
...
@@ -94,16 +95,19 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
...
@@ -94,16 +95,19 @@ Dinky 通过已注册的集群配置来获取对应的 YarnClient 实例。对
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 yarn application 模式下 FlinkSQL 提交 | 0.4.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes session 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 kubernetes application 模式下 FlinkSQL 提交 | 0.5.0 |
| | | 支持 UDF Java 方言Local模式在线编写、调试、动态加载 | 0.5.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | Flink 作业 | 支持 yarn application 模式下 Jar 提交 | 0.4.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 k8s application 模式下 Jar 提交 | 0.5.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 Cancel | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 支持 作业 SavePoint 的 Cancel、Stop、Trigger | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 作业自动从 SavePoint 恢复机制(包含最近、最早、指定一次) | 0.4.0 |
| | | 新增 UDF java方言代码的开发 | 0.5.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | Flink 集群 | 支持 查看已注册集群的作业列表与运维 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | | 新增 自动注册 Yarn 创建的集群 | 0.4.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | SQL | 新增 外部数据源的 SQL 校验 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | | 新增 外部数据源的 SQL 执行与预览 | 0.5.0 |
| | BI | 新增 折线图的渲染 | 0.5.0 |
| | | 新增 条形图图的渲染 | 0.5.0 |
| | | 新增 饼图的渲染 | 0.5.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 元数据 | 新增 查询外部数据源的元数据信息 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| | 归档 | 新增 执行与提交历史 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
| 运维中心 | 暂无 | 暂无 | 0.4.0 |
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment