From 282631e7e7521bc5c8736e93096bb0b59dfd0a00 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Sun, 18 Sep 2022 16:24:40 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=89=8B=E5=8A=A8=E5=87=BA=E5=8F=91=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=9B=B8=E5=85=B3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asgc/neutrino/core/quartz/DefaultJobSource.java | 1 + .../fun/asgc/neutrino/core/quartz/IJobCallback.java | 3 ++- .../fun/asgc/neutrino/core/quartz/JobExecutor.java | 12 +++++++----- .../java/fun/asgc/neutrino/core/quartz/JobInfo.java | 1 + .../asgc/neutrino/core/quartz/test2/JobCallback.java | 2 +- .../neutrino/proxy/server/dal/DataCleanMapper.java | 2 +- .../neutrino/proxy/server/dal/JobInfoMapper.java | 4 ++-- .../asgc/neutrino/proxy/server/job/DataCleanJob.java | 9 ++++++--- .../proxy/server/service/JobInfoService.java | 4 +++- .../neutrino/proxy/server/service/JobLogService.java | 4 ++-- 10 files changed, 26 insertions(+), 16 deletions(-) diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/DefaultJobSource.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/DefaultJobSource.java index afbe005c..6c4ad85b 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/DefaultJobSource.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/DefaultJobSource.java @@ -54,6 +54,7 @@ public class DefaultJobSource implements IJobSource { .setDesc(handler.desc()) .setCron(handler.cron()) .setParam(handler.param()) + .setEnable(true) ); } return jobInfoList; diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/IJobCallback.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/IJobCallback.java index 1c4a232f..402badd1 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/IJobCallback.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/IJobCallback.java @@ -31,7 +31,8 @@ public interface IJobCallback { /** * 执行日志 * @param jobInfo + * @param param * @param throwable */ - void executeLog(JobInfo jobInfo, Throwable throwable); + void executeLog(JobInfo jobInfo, String param, Throwable throwable); } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java index 0e14ed57..bb6e42a9 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java @@ -112,7 +112,7 @@ public class JobExecutor implements ApplicationRunner, IJobExecutor { @Override public void add(JobInfo jobInfo) throws JobException { if (null == jobInfo || StringUtil.isEmpty(jobInfo.getId()) || StringUtil.isEmpty(jobInfo.getName()) || - StringUtil.isEmpty(jobInfo.getCron()) || runJobSet.contains(jobInfo.getName())) { + StringUtil.isEmpty(jobInfo.getCron())) { return; } synchronized (jobInfo.getId()) { @@ -128,8 +128,10 @@ public class JobExecutor implements ApplicationRunner, IJobExecutor { JobDetail jobDetail = JobBuilder.newJob(JobBean.class).withIdentity(jobKey).build(); try { - scheduler.scheduleJob(jobDetail, cronTrigger); - scheduler.start(); + if (jobInfo.isEnable()) { + scheduler.scheduleJob(jobDetail, cronTrigger); + scheduler.start(); + } } catch (Exception e) { throw new RuntimeException(String.format("新增job[name=%s]异常", jobInfo.getName())); } @@ -190,10 +192,10 @@ public class JobExecutor implements ApplicationRunner, IJobExecutor { try { jobHandler.execute(param); if (null != jobCallback) { - jobCallback.executeLog(jobInfo, null); + jobCallback.executeLog(jobInfo, param, null); } } catch (Throwable e) { - jobCallback.executeLog(jobInfo, e); + jobCallback.executeLog(jobInfo, param, e); } }); } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobInfo.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobInfo.java index e5ace0d2..c7055f0a 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobInfo.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobInfo.java @@ -39,5 +39,6 @@ public class JobInfo { private String desc; private String cron; private String param; + private boolean enable; private Map extension; } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/quartz/test2/JobCallback.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/quartz/test2/JobCallback.java index 639abf4d..1bd14b1e 100644 --- a/neutrino-core/src/test/java/fun/asgc/neutrino/core/quartz/test2/JobCallback.java +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/quartz/test2/JobCallback.java @@ -34,7 +34,7 @@ import lombok.extern.slf4j.Slf4j; public class JobCallback implements IJobCallback { @Override - public void executeLog(JobInfo jobInfo, Throwable throwable) { + public void executeLog(JobInfo jobInfo, String param, Throwable throwable) { if (null == throwable) { log.info("job[name={}]执行完毕", jobInfo.getId(), jobInfo.getName()); } else { diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/DataCleanMapper.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/DataCleanMapper.java index 87dbc372..1b77d255 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/DataCleanMapper.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/DataCleanMapper.java @@ -16,6 +16,6 @@ import java.util.Date; public interface DataCleanMapper extends SqlMapper { @Delete("delete from `job_log` where create_time < ?") - void cleanJobLog(Date date); + void cleanJobLog(long date); } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobInfoMapper.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobInfoMapper.java index 8f164f53..7ab06ec6 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobInfoMapper.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobInfoMapper.java @@ -56,8 +56,8 @@ public interface JobInfoMapper extends SqlMapper { void updateEnableStatus(@Param("id") Integer id, @Param("enable") Integer enable, @Param("updateTime") Date updateTime); @ResultType(JobInfoDO.class) - @Select("select * from job_info where enable = 1") - List findEnableList(); + @Select("select * from job_info") + List findList(); void update(JobInfoDO jobInfoDO); } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/job/DataCleanJob.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/job/DataCleanJob.java index 094ab87a..dc01a175 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/job/DataCleanJob.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/job/DataCleanJob.java @@ -11,6 +11,7 @@ import fun.asgc.neutrino.proxy.server.dal.DataCleanMapper; import lombok.Data; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -38,14 +39,16 @@ public class DataCleanJob implements IJobHandler { public void execute(String s) throws Exception { JobParams jobParams = getParams(s); - Date date = DateUtil.addDate(new Date(), Calendar.DATE, jobParams.getJobLogKeepDays()); + Date date = DateUtil.addDate(new Date(), Calendar.DATE, -1 * jobParams.getJobLogKeepDays()); log.info("清理调度管理日志 date:{}", sdf.format(date)); - dataCleanMapper.cleanJobLog(date); + dataCleanMapper.cleanJobLog(date.getTime()); } public static JobParams getParams(String s) { try { - return JSONObject.parseObject(s, JobParams.class); + if (StringUtils.isNotBlank(s)) { + return JSONObject.parseObject(s, JobParams.class); + } } catch (Exception e) { // ignore } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java index 45f3a043..da1691b7 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java @@ -74,6 +74,7 @@ public class JobInfoService implements IJobSource { .setDesc(jobInfoDO.getDesc()) .setCron(jobInfoDO.getCron()) .setParam(jobInfoDO.getParam()) + .setEnable(true) ); } else { BeanManager.getBean(JobExecutor.class).remove(String.valueOf(req.getId())); @@ -89,7 +90,7 @@ public class JobInfoService implements IJobSource { @Override public List sourceList() { List jobInfoList = Lists.newArrayList(); - List jobInfoDOList = jobInfoMapper.findEnableList(); + List jobInfoDOList = jobInfoMapper.findList(); if (CollectionUtil.isEmpty(jobInfoDOList)) { return jobInfoList; } @@ -100,6 +101,7 @@ public class JobInfoService implements IJobSource { .setDesc(item.getDesc()) .setCron(item.getCron()) .setParam(item.getParam()) + .setEnable(EnableStatusEnum.ENABLE.getStatus().equals(item.getEnable())) ); } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java index 250300ac..3c9c536c 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java @@ -46,7 +46,7 @@ public class JobLogService implements IJobCallback { private JobLogMapper jobLogMapper; @Override - public void executeLog(JobInfo jobInfo, Throwable throwable) { + public void executeLog(JobInfo jobInfo, String param, Throwable throwable) { Integer code = 0; String msg = ""; if (null == throwable) { @@ -60,7 +60,7 @@ public class JobLogService implements IJobCallback { jobLogMapper.add(new JobLogDO() .setJobId(Integer.valueOf(jobInfo.getId())) .setHandler(jobInfo.getName()) - .setParam(jobInfo.getParam()) + .setParam(param) .setCode(code) .setMsg(msg) .setAlarmStatus(0) From 023d33c416acf33b00d482cc742f5da37f2a5c84 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Tue, 20 Sep 2022 23:18:39 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0todolist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todolist.MD | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 todolist.MD diff --git a/todolist.MD b/todolist.MD new file mode 100644 index 00000000..48f41c04 --- /dev/null +++ b/todolist.MD @@ -0,0 +1,6 @@ +- 测试及优化代理稳定性 +- 完成剩余的调度管理日志功能 +- 增加日志管理(登录日志、客户端连接日志、调度执行日志) +- 增加简单的流量统计 + - 基于用户粒度的上下行流量累计 +- 完善补充代码文档 \ No newline at end of file From 563704d5883df0943666682808e6d481cd76cd6a Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Wed, 21 Sep 2022 22:20:31 +0800 Subject: [PATCH 03/28] =?UTF-8?q?JobExecutor=E4=BD=BF=E7=94=A8=E7=AE=80?= =?UTF-8?q?=E5=8C=96=EF=BC=8C=E5=9C=A8=E6=B2=A1=E6=9C=89=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E6=B1=A0=E6=97=B6=EF=BC=8C=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E7=9A=84=E7=BA=BF=E7=A8=8B=E6=B1=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/fun/asgc/neutrino/core/quartz/JobExecutor.java | 9 ++++++++- .../proxy/server/base/rest/config/JobConfig.java | 2 -- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java index bb6e42a9..a50f2ab0 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/quartz/JobExecutor.java @@ -23,6 +23,7 @@ package fun.asgc.neutrino.core.quartz; import com.google.common.collect.Sets; import fun.asgc.neutrino.core.annotation.Autowired; +import fun.asgc.neutrino.core.base.CustomThreadFactory; import fun.asgc.neutrino.core.context.ApplicationRunner; import fun.asgc.neutrino.core.context.Environment; import fun.asgc.neutrino.core.quartz.annotation.JobHandler; @@ -37,7 +38,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * Job执行器 @@ -60,9 +63,13 @@ public class JobExecutor implements ApplicationRunner, IJobExecutor { @Override public void run(String[] args) throws JobException { - if (!environment.isEnableJob() || null == jobSource || null == threadPoolExecutor) { + if (!environment.isEnableJob() || null == jobSource) { return; } + if (null == threadPoolExecutor) { + threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new CustomThreadFactory("DefaultJobPool")); + } List jobHandlerList = BeanManager.getBeanListBySuperClass(IJobHandler.class); if (!CollectionUtil.isEmpty(jobHandlerList)) { diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/config/JobConfig.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/config/JobConfig.java index 749de7c9..e4e78372 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/config/JobConfig.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/base/rest/config/JobConfig.java @@ -49,8 +49,6 @@ public class JobConfig { public JobExecutor jobExecutor() { JobExecutor executor = new JobExecutor(); executor.setJobSource(jobInfoService); - executor.setThreadPoolExecutor(new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>(), new CustomThreadFactory("JobPool"))); executor.setJobCallback(jobLogService); return executor; } From 61660c8e307427dbbcf383aed504c9f0c784e7c4 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Thu, 22 Sep 2022 23:59:35 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0todolist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- todolist.MD | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/todolist.MD b/todolist.MD index 48f41c04..149ba0e1 100644 --- a/todolist.MD +++ b/todolist.MD @@ -3,4 +3,5 @@ - 增加日志管理(登录日志、客户端连接日志、调度执行日志) - 增加简单的流量统计 - 基于用户粒度的上下行流量累计 -- 完善补充代码文档 \ No newline at end of file +- 完善补充代码文档 +- 优化底层框架 \ No newline at end of file From 90dd11e509db5cb0ef50b9077f8f1e392175fed7 Mon Sep 17 00:00:00 2001 From: zCans <1224895921@qq.com> Date: Fri, 23 Sep 2022 22:43:48 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- neutrino-proxy-admin/src/api/jobLog.js | 9 ++ neutrino-proxy-admin/src/lang/zh.js | 8 +- neutrino-proxy-admin/src/router/index.js | 3 +- .../src/views/system/jobLog.vue | 121 ++++++++++++++++++ .../src/views/system/jobManager.vue | 8 ++ 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 neutrino-proxy-admin/src/api/jobLog.js create mode 100644 neutrino-proxy-admin/src/views/system/jobLog.vue diff --git a/neutrino-proxy-admin/src/api/jobLog.js b/neutrino-proxy-admin/src/api/jobLog.js new file mode 100644 index 00000000..016785a9 --- /dev/null +++ b/neutrino-proxy-admin/src/api/jobLog.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function fetchList(query) { + return request({ + url: '/job-Log/page', + method: 'get', + params: query + }) +} diff --git a/neutrino-proxy-admin/src/lang/zh.js b/neutrino-proxy-admin/src/lang/zh.js index 555dd24b..63096fa8 100644 --- a/neutrino-proxy-admin/src/lang/zh.js +++ b/neutrino-proxy-admin/src/lang/zh.js @@ -51,7 +51,8 @@ export default { proxy: '代理配置', license: 'License管理', portMapping: '端口映射', - jobManager: '调度管理' + jobManager: '调度管理', + jobLog: '调度日志' }, navbar: { logOut: '退出登录', @@ -131,7 +132,10 @@ export default { cron: 'cron', jobParam: '任务参数', alarmEmail: '任务报警邮箱', - alarmDing: '任务报警钉钉' + alarmDing: '任务报警钉钉', + jobLogCode: '执行结果', + jobLogMsg: '执行日志', + alarmStatus: '报警状态' }, errorLog: { tips: '请点击右上角bug小图标', diff --git a/neutrino-proxy-admin/src/router/index.js b/neutrino-proxy-admin/src/router/index.js index 4ea90863..fd920bdd 100644 --- a/neutrino-proxy-admin/src/router/index.js +++ b/neutrino-proxy-admin/src/router/index.js @@ -277,7 +277,8 @@ export const asyncRouterMap = [ children: [ { path: 'user', component: _import('system/user'), name: 'user', meta: { title: 'user' }}, { path: 'portPool', component: _import('system/portPool'), name: 'portPool', meta: { title: 'portPool' }}, - { path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }} + { path: 'jobManager', component: _import('system/jobManager'), name: 'jobManager', meta: { title: 'jobManager' }}, + { path: 'jobLog', component: _import('system/jobLog'), name: 'jobLog', meta: { title: 'jobLog' }} ] } ] diff --git a/neutrino-proxy-admin/src/views/system/jobLog.vue b/neutrino-proxy-admin/src/views/system/jobLog.vue new file mode 100644 index 00000000..3fda1742 --- /dev/null +++ b/neutrino-proxy-admin/src/views/system/jobLog.vue @@ -0,0 +1,121 @@ + + + diff --git a/neutrino-proxy-admin/src/views/system/jobManager.vue b/neutrino-proxy-admin/src/views/system/jobManager.vue index 94a4fb53..83266050 100644 --- a/neutrino-proxy-admin/src/views/system/jobManager.vue +++ b/neutrino-proxy-admin/src/views/system/jobManager.vue @@ -40,6 +40,11 @@ {{scope.row.enable | statusName}} + + + - + @@ -40,7 +40,7 @@ {{scope.row.createTime | parseTime('{y}-{m}-{d} {h}:{i}')}} - + From b40e947b176217eb857c3ae0760af6b85641813e Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Sat, 24 Sep 2022 00:05:26 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0PropertyDescriptor?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asgc/neutrino/core/bean/test2/Test1.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/bean/test2/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/bean/test2/Test1.java index 985879c0..0cce39f6 100644 --- a/neutrino-core/src/test/java/fun/asgc/neutrino/core/bean/test2/Test1.java +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/bean/test2/Test1.java @@ -21,13 +21,11 @@ */ package fun.asgc.neutrino.core.bean.test2; -import lombok.Data; import org.junit.Test; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; /** * @@ -38,27 +36,38 @@ public class Test1 { @Test public void test1() throws IntrospectionException, InvocationTargetException, IllegalAccessException { - PropertyDescriptor descriptor = new PropertyDescriptor("age", Student.class); - Student student = new Student(); - Method method = descriptor.getWriteMethod(); - method.invoke(student, 30); - System.out.println(student); + PropertyDescriptor pd1= new PropertyDescriptor("name", Person.class); + PropertyDescriptor pd2= new PropertyDescriptor("age", Person.class, "getAge", "setAge"); + + Person person = new Person(); + person.setName("张三"); + + pd1.getWriteMethod().invoke(person, "李四"); + + System.out.println(pd1.getReadMethod().invoke(person)); + + pd2.getWriteMethod().invoke(person, 20); + System.out.println(pd2.getReadMethod().invoke(person)); } - public static class Student { + public static class Person { private String name; private int age; - private int score; + + public String isName() { + return name; + } public int getAge() { return age; } -// public void setAge(int age) { -// this.age = age; -// } + public void setName(String name) { + this.name = name; + } + public void setAge(int age) { - System.out.println("111"); + this.age = age; } } } From f3322fdf383f9704aa2c59c8ee857a72007f2838 Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Sun, 25 Sep 2022 00:25:11 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E6=96=B0=E5=A2=9EResolvableType=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=9F=BA=E7=A1=80=E7=B1=BB=E5=8F=8A=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/base/type/MethodParameter.java | 654 +++++++ .../base/type/ParameterNameDiscoverer.java | 51 + .../base/type/ParameterizedTypeReference.java | 100 ++ .../core/base/type/ResolvableType.java | 1556 +++++++++++++++++ .../base/type/ResolvableTypeProvider.java | 36 + .../base/type/SerializableTypeWrapper.java | 399 +++++ .../core/util/ConcurrentReferenceHashMap.java | 1017 +++++++++++ .../asgc/neutrino/core/util/ReflectUtil.java | 131 ++ .../fun/asgc/neutrino/core/type/Test1.java | 55 + .../fun/asgc/neutrino/core/type/Test2.java | 50 + 10 files changed, 4049 insertions(+) create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java create mode 100644 neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java create mode 100644 neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java create mode 100644 neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java new file mode 100644 index 00000000..8dc48afc --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/MethodParameter.java @@ -0,0 +1,654 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +import fun.asgc.neutrino.core.util.Assert; +import fun.asgc.neutrino.core.util.ObjectUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.util.HashMap; +import java.util.Map; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public class MethodParameter { + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + private static final Class javaUtilOptionalClass; + + static { + Class clazz; + try { + clazz = Class.forName("java.util.Optional"); + } + catch (ClassNotFoundException ex) { + // Java 8 not available - Optional references simply not supported then. + clazz = null; + } + javaUtilOptionalClass = clazz; + } + + + private final Method method; + + private final Constructor constructor; + + private final int parameterIndex; + + private int nestingLevel; + + /** Map from Integer level to Integer type index */ + Map typeIndexesPerLevel; + + /** The containing class. Could also be supplied by overriding {@link #getContainingClass()} */ + private volatile Class containingClass; + + private volatile Class parameterType; + + private volatile Type genericParameterType; + + private volatile Annotation[] parameterAnnotations; + + private volatile ParameterNameDiscoverer parameterNameDiscoverer; + + private volatile String parameterName; + + private volatile MethodParameter nestedMethodParameter; + + + /** + * Create a new {@code MethodParameter} for the given method, with nesting level 1. + * @param method the Method to specify a parameter for + * @param parameterIndex the index of the parameter: -1 for the method + * return type; 0 for the first method parameter; 1 for the second method + * parameter, etc. + */ + public MethodParameter(Method method, int parameterIndex) { + this(method, parameterIndex, 1); + } + + /** + * Create a new {@code MethodParameter} for the given method. + * @param method the Method to specify a parameter for + * @param parameterIndex the index of the parameter: -1 for the method + * return type; 0 for the first method parameter; 1 for the second method + * parameter, etc. + * @param nestingLevel the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List) + */ + public MethodParameter(Method method, int parameterIndex, int nestingLevel) { + Assert.notNull(method, "Method must not be null"); + this.method = method; + this.parameterIndex = parameterIndex; + this.nestingLevel = nestingLevel; + this.constructor = null; + } + + /** + * Create a new MethodParameter for the given constructor, with nesting level 1. + * @param constructor the Constructor to specify a parameter for + * @param parameterIndex the index of the parameter + */ + public MethodParameter(Constructor constructor, int parameterIndex) { + this(constructor, parameterIndex, 1); + } + + /** + * Create a new MethodParameter for the given constructor. + * @param constructor the Constructor to specify a parameter for + * @param parameterIndex the index of the parameter + * @param nestingLevel the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List) + */ + public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel) { + Assert.notNull(constructor, "Constructor must not be null"); + this.constructor = constructor; + this.parameterIndex = parameterIndex; + this.nestingLevel = nestingLevel; + this.method = null; + } + + /** + * Copy constructor, resulting in an independent MethodParameter object + * based on the same metadata and cache state that the original object was in. + * @param original the original MethodParameter object to copy from + */ + public MethodParameter(MethodParameter original) { + Assert.notNull(original, "Original must not be null"); + this.method = original.method; + this.constructor = original.constructor; + this.parameterIndex = original.parameterIndex; + this.nestingLevel = original.nestingLevel; + this.typeIndexesPerLevel = original.typeIndexesPerLevel; + this.containingClass = original.containingClass; + this.parameterType = original.parameterType; + this.genericParameterType = original.genericParameterType; + this.parameterAnnotations = original.parameterAnnotations; + this.parameterNameDiscoverer = original.parameterNameDiscoverer; + this.parameterName = original.parameterName; + } + + + /** + * Return the wrapped Method, if any. + *

Note: Either Method or Constructor is available. + * @return the Method, or {@code null} if none + */ + public Method getMethod() { + return this.method; + } + + /** + * Return the wrapped Constructor, if any. + *

Note: Either Method or Constructor is available. + * @return the Constructor, or {@code null} if none + */ + public Constructor getConstructor() { + return this.constructor; + } + + /** + * Return the class that declares the underlying Method or Constructor. + */ + public Class getDeclaringClass() { + return getMember().getDeclaringClass(); + } + + /** + * Return the wrapped member. + * @return the Method or Constructor as Member + */ + public Member getMember() { + // NOTE: no ternary expression to retain JDK <8 compatibility even when using + // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable + // as common type, with that new base class not available on older JDKs) + if (this.method != null) { + return this.method; + } + else { + return this.constructor; + } + } + + /** + * Return the wrapped annotated element. + *

Note: This method exposes the annotations declared on the method/constructor + * itself (i.e. at the method/constructor level, not at the parameter level). + * @return the Method or Constructor as AnnotatedElement + */ + public AnnotatedElement getAnnotatedElement() { + // NOTE: no ternary expression to retain JDK <8 compatibility even when using + // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable + // as common type, with that new base class not available on older JDKs) + if (this.method != null) { + return this.method; + } + else { + return this.constructor; + } + } + + /** + * Return the index of the method/constructor parameter. + * @return the parameter index (-1 in case of the return type) + */ + public int getParameterIndex() { + return this.parameterIndex; + } + + /** + * Increase this parameter's nesting level. + * @see #getNestingLevel() + */ + public void increaseNestingLevel() { + this.nestingLevel++; + } + + /** + * Decrease this parameter's nesting level. + * @see #getNestingLevel() + */ + public void decreaseNestingLevel() { + getTypeIndexesPerLevel().remove(this.nestingLevel); + this.nestingLevel--; + } + + /** + * Return the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List). + */ + public int getNestingLevel() { + return this.nestingLevel; + } + + /** + * Set the type index for the current nesting level. + * @param typeIndex the corresponding type index + * (or {@code null} for the default type index) + * @see #getNestingLevel() + */ + public void setTypeIndexForCurrentLevel(int typeIndex) { + getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex); + } + + /** + * Return the type index for the current nesting level. + * @return the corresponding type index, or {@code null} + * if none specified (indicating the default type index) + * @see #getNestingLevel() + */ + public Integer getTypeIndexForCurrentLevel() { + return getTypeIndexForLevel(this.nestingLevel); + } + + /** + * Return the type index for the specified nesting level. + * @param nestingLevel the nesting level to check + * @return the corresponding type index, or {@code null} + * if none specified (indicating the default type index) + */ + public Integer getTypeIndexForLevel(int nestingLevel) { + return getTypeIndexesPerLevel().get(nestingLevel); + } + + /** + * Obtain the (lazily constructed) type-indexes-per-level Map. + */ + private Map getTypeIndexesPerLevel() { + if (this.typeIndexesPerLevel == null) { + this.typeIndexesPerLevel = new HashMap(4); + } + return this.typeIndexesPerLevel; + } + + /** + * Return a variant of this {@code MethodParameter} which points to the + * same parameter but one nesting level deeper. This is effectively the + * same as {@link #increaseNestingLevel()}, just with an independent + * {@code MethodParameter} object (e.g. in case of the original being cached). + * @since 4.3 + */ + public MethodParameter nested() { + if (this.nestedMethodParameter != null) { + return this.nestedMethodParameter; + } + MethodParameter nestedParam = clone(); + nestedParam.nestingLevel = this.nestingLevel + 1; + this.nestedMethodParameter = nestedParam; + return nestedParam; + } + + /** + * Return whether this method parameter is declared as optional + * in the form of Java 8's {@link java.util.Optional}. + * @since 4.3 + */ + public boolean isOptional() { + return (getParameterType() == javaUtilOptionalClass); + } + + /** + * Return a variant of this {@code MethodParameter} which points to + * the same parameter but one nesting level deeper in case of a + * {@link java.util.Optional} declaration. + * @since 4.3 + * @see #isOptional() + * @see #nested() + */ + public MethodParameter nestedIfOptional() { + return (isOptional() ? nested() : this); + } + + /** + * Set a containing class to resolve the parameter type against. + */ + void setContainingClass(Class containingClass) { + this.containingClass = containingClass; + } + + /** + * Return the containing class for this method parameter. + * @return a specific containing class (potentially a subclass of the + * declaring class), or otherwise simply the declaring class itself + * @see #getDeclaringClass() + */ + public Class getContainingClass() { + return (this.containingClass != null ? this.containingClass : getDeclaringClass()); + } + + /** + * Set a resolved (generic) parameter type. + */ + void setParameterType(Class parameterType) { + this.parameterType = parameterType; + } + + /** + * Return the type of the method/constructor parameter. + * @return the parameter type (never {@code null}) + */ + public Class getParameterType() { + Class paramType = this.parameterType; + if (paramType == null) { + if (this.parameterIndex < 0) { + Method method = getMethod(); + paramType = (method != null ? method.getReturnType() : void.class); + } + else { + paramType = (this.method != null ? + this.method.getParameterTypes()[this.parameterIndex] : + this.constructor.getParameterTypes()[this.parameterIndex]); + } + this.parameterType = paramType; + } + return paramType; + } + + /** + * Return the generic type of the method/constructor parameter. + * @return the parameter type (never {@code null}) + * @since 3.0 + */ + public Type getGenericParameterType() { + Type paramType = this.genericParameterType; + if (paramType == null) { + if (this.parameterIndex < 0) { + Method method = getMethod(); + paramType = (method != null ? method.getGenericReturnType() : void.class); + } + else { + Type[] genericParameterTypes = (this.method != null ? + this.method.getGenericParameterTypes() : this.constructor.getGenericParameterTypes()); + int index = this.parameterIndex; + if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() && + !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) && + genericParameterTypes.length == this.constructor.getParameterTypes().length - 1) { + // Bug in javac: type array excludes enclosing instance parameter + // for inner classes with at least one generic constructor parameter, + // so access it with the actual parameter index lowered by 1 + index = this.parameterIndex - 1; + } + paramType = (index >= 0 && index < genericParameterTypes.length ? + genericParameterTypes[index] : getParameterType()); + } + this.genericParameterType = paramType; + } + return paramType; + } + + /** + * Return the nested type of the method/constructor parameter. + * @return the parameter type (never {@code null}) + * @since 3.1 + * @see #getNestingLevel() + */ + public Class getNestedParameterType() { + if (this.nestingLevel > 1) { + Type type = getGenericParameterType(); + for (int i = 2; i <= this.nestingLevel; i++) { + if (type instanceof ParameterizedType) { + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Integer index = getTypeIndexForLevel(i); + type = args[index != null ? index : args.length - 1]; + } + // TODO: Object.class if unresolvable + } + if (type instanceof Class) { + return (Class) type; + } + else if (type instanceof ParameterizedType) { + Type arg = ((ParameterizedType) type).getRawType(); + if (arg instanceof Class) { + return (Class) arg; + } + } + return Object.class; + } + else { + return getParameterType(); + } + } + + /** + * Return the nested generic type of the method/constructor parameter. + * @return the parameter type (never {@code null}) + * @since 4.2 + * @see #getNestingLevel() + */ + public Type getNestedGenericParameterType() { + if (this.nestingLevel > 1) { + Type type = getGenericParameterType(); + for (int i = 2; i <= this.nestingLevel; i++) { + if (type instanceof ParameterizedType) { + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Integer index = getTypeIndexForLevel(i); + type = args[index != null ? index : args.length - 1]; + } + } + return type; + } + else { + return getGenericParameterType(); + } + } + + /** + * Return the annotations associated with the target method/constructor itself. + */ + public Annotation[] getMethodAnnotations() { + return adaptAnnotationArray(getAnnotatedElement().getAnnotations()); + } + + /** + * Return the method/constructor annotation of the given type, if available. + * @param annotationType the annotation type to look for + * @return the annotation object, or {@code null} if not found + */ + public A getMethodAnnotation(Class annotationType) { + return adaptAnnotation(getAnnotatedElement().getAnnotation(annotationType)); + } + + /** + * Return whether the method/constructor is annotated with the given type. + * @param annotationType the annotation type to look for + * @since 4.3 + * @see #getMethodAnnotation(Class) + */ + public boolean hasMethodAnnotation(Class annotationType) { + return getAnnotatedElement().isAnnotationPresent(annotationType); + } + + /** + * Return the annotations associated with the specific method/constructor parameter. + */ + public Annotation[] getParameterAnnotations() { + Annotation[] paramAnns = this.parameterAnnotations; + if (paramAnns == null) { + Annotation[][] annotationArray = (this.method != null ? + this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations()); + int index = this.parameterIndex; + if (this.constructor != null && this.constructor.getDeclaringClass().isMemberClass() && + !Modifier.isStatic(this.constructor.getDeclaringClass().getModifiers()) && + annotationArray.length == this.constructor.getParameterTypes().length - 1) { + // Bug in javac in JDK <9: annotation array excludes enclosing instance parameter + // for inner classes, so access it with the actual parameter index lowered by 1 + index = this.parameterIndex - 1; + } + paramAnns = (index >= 0 && index < annotationArray.length ? + adaptAnnotationArray(annotationArray[index]) : EMPTY_ANNOTATION_ARRAY); + this.parameterAnnotations = paramAnns; + } + return paramAnns; + } + + /** + * Return {@code true} if the parameter has at least one annotation, + * {@code false} if it has none. + * @see #getParameterAnnotations() + */ + public boolean hasParameterAnnotations() { + return (getParameterAnnotations().length != 0); + } + + /** + * Return the parameter annotation of the given type, if available. + * @param annotationType the annotation type to look for + * @return the annotation object, or {@code null} if not found + */ + @SuppressWarnings("unchecked") + public A getParameterAnnotation(Class annotationType) { + Annotation[] anns = getParameterAnnotations(); + for (Annotation ann : anns) { + if (annotationType.isInstance(ann)) { + return (A) ann; + } + } + return null; + } + + /** + * Return whether the parameter is declared with the given annotation type. + * @param annotationType the annotation type to look for + * @see #getParameterAnnotation(Class) + */ + public boolean hasParameterAnnotation(Class annotationType) { + return (getParameterAnnotation(annotationType) != null); + } + + /** + * Initialize parameter name discovery for this method parameter. + *

This method does not actually try to retrieve the parameter name at + * this point; it just allows discovery to happen when the application calls + * {@link #getParameterName()} (if ever). + */ + public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + /** + * Return the name of the method/constructor parameter. + * @return the parameter name (may be {@code null} if no + * parameter name metadata is contained in the class file or no + * {@link #initParameterNameDiscovery ParameterNameDiscoverer} + * has been set to begin with) + */ + public String getParameterName() { + ParameterNameDiscoverer discoverer = this.parameterNameDiscoverer; + if (discoverer != null) { + String[] parameterNames = (this.method != null ? + discoverer.getParameterNames(this.method) : discoverer.getParameterNames(this.constructor)); + if (parameterNames != null) { + this.parameterName = parameterNames[this.parameterIndex]; + } + this.parameterNameDiscoverer = null; + } + return this.parameterName; + } + + + /** + * A template method to post-process a given annotation instance before + * returning it to the caller. + *

The default implementation simply returns the given annotation as-is. + * @param annotation the annotation about to be returned + * @return the post-processed annotation (or simply the original one) + * @since 4.2 + */ + protected A adaptAnnotation(A annotation) { + return annotation; + } + + /** + * A template method to post-process a given annotation array before + * returning it to the caller. + *

The default implementation simply returns the given annotation array as-is. + * @param annotations the annotation array about to be returned + * @return the post-processed annotation array (or simply the original one) + * @since 4.2 + */ + protected Annotation[] adaptAnnotationArray(Annotation[] annotations) { + return annotations; + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof MethodParameter)) { + return false; + } + MethodParameter otherParam = (MethodParameter) other; + return (getContainingClass() == otherParam.getContainingClass() && + ObjectUtil.nullSafeEquals(this.typeIndexesPerLevel, otherParam.typeIndexesPerLevel) && + this.nestingLevel == otherParam.nestingLevel && + this.parameterIndex == otherParam.parameterIndex && + getMember().equals(otherParam.getMember())); + } + + @Override + public int hashCode() { + return (getMember().hashCode() * 31 + this.parameterIndex); + } + + @Override + public String toString() { + return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") + + " parameter " + this.parameterIndex; + } + + @Override + public MethodParameter clone() { + return new MethodParameter(this); + } + + + /** + * Create a new MethodParameter for the given method or constructor. + *

This is a convenience constructor for scenarios where a + * Method or Constructor reference is treated in a generic fashion. + * @param methodOrConstructor the Method or Constructor to specify a parameter for + * @param parameterIndex the index of the parameter + * @return the corresponding MethodParameter instance + */ + public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) { + if (methodOrConstructor instanceof Method) { + return new MethodParameter((Method) methodOrConstructor, parameterIndex); + } + else if (methodOrConstructor instanceof Constructor) { + return new MethodParameter((Constructor) methodOrConstructor, parameterIndex); + } + else { + throw new IllegalArgumentException( + "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor"); + } + } + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java new file mode 100644 index 00000000..4d713d97 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterNameDiscoverer.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public interface ParameterNameDiscoverer { + + /** + * Return parameter names for this method, + * or {@code null} if they cannot be determined. + * @param method method to find parameter names for + * @return an array of parameter names if the names can be resolved, + * or {@code null} if they cannot + */ + String[] getParameterNames(Method method); + + /** + * Return parameter names for this constructor, + * or {@code null} if they cannot be determined. + * @param ctor constructor to find parameter names for + * @return an array of parameter names if the names can be resolved, + * or {@code null} if they cannot + */ + String[] getParameterNames(Constructor ctor); + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java new file mode 100644 index 00000000..279d8322 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ParameterizedTypeReference.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +import fun.asgc.neutrino.core.util.Assert; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public abstract class ParameterizedTypeReference { + + private final Type type; + + + protected ParameterizedTypeReference() { + Class parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass()); + Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass(); + Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type"); + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + Assert.isTrue(actualTypeArguments.length == 1, "Number of type arguments must be 1"); + this.type = actualTypeArguments[0]; + } + + private ParameterizedTypeReference(Type type) { + this.type = type; + } + + + public Type getType() { + return this.type; + } + + @Override + public boolean equals(Object obj) { + return (this == obj || (obj instanceof ParameterizedTypeReference && + this.type.equals(((ParameterizedTypeReference) obj).type))); + } + + @Override + public int hashCode() { + return this.type.hashCode(); + } + + @Override + public String toString() { + return "ParameterizedTypeReference<" + this.type + ">"; + } + + + /** + * Build a {@code ParameterizedTypeReference} wrapping the given type. + * @param type a generic type (possibly obtained via reflection, + * e.g. from {@link java.lang.reflect.Method#getGenericReturnType()}) + * @return a corresponding reference which may be passed into + * {@code ParameterizedTypeReference}-accepting methods + * @since 4.3.12 + */ + public static ParameterizedTypeReference forType(Type type) { + return new ParameterizedTypeReference(type) { + }; + } + + private static Class findParameterizedTypeReferenceSubclass(Class child) { + Class parent = child.getSuperclass(); + if (Object.class == parent) { + throw new IllegalStateException("Expected ParameterizedTypeReference superclass"); + } + else if (ParameterizedTypeReference.class == parent) { + return child; + } + else { + return findParameterizedTypeReferenceSubclass(parent); + } + } + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java new file mode 100644 index 00000000..5792cb80 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableType.java @@ -0,0 +1,1556 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.FieldTypeProvider; +import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.MethodParameterTypeProvider; +import fun.asgc.neutrino.core.base.type.SerializableTypeWrapper.TypeProvider; +import fun.asgc.neutrino.core.util.Assert; +import fun.asgc.neutrino.core.util.ConcurrentReferenceHashMap; +import fun.asgc.neutrino.core.util.ObjectUtil; +import fun.asgc.neutrino.core.util.StringUtil; +import org.apache.commons.lang3.ClassUtils; + +import java.io.Serializable; +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * @author: aoshiguchen + * @date: 2022/9/24 + */ +@SuppressWarnings("serial") +public class ResolvableType implements Serializable { + + /** + * {@code ResolvableType} returned when no value is available. {@code NONE} is used + * in preference to {@code null} so that multiple method calls can be safely chained. + */ + public static final ResolvableType NONE = new ResolvableType(null, null, null, 0); + + private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0]; + + private static final ConcurrentReferenceHashMap cache = + new ConcurrentReferenceHashMap(256); + + + /** + * The underlying Java type being managed (only ever {@code null} for {@link #NONE}). + */ + private final Type type; + + /** + * Optional provider for the type. + */ + private final TypeProvider typeProvider; + + /** + * The {@code VariableResolver} to use or {@code null} if no resolver is available. + */ + private final VariableResolver variableResolver; + + /** + * The component type for an array or {@code null} if the type should be deduced. + */ + private final ResolvableType componentType; + + /** + * Copy of the resolved value. + */ + private final Class resolved; + + private final Integer hash; + + private ResolvableType superType; + + private ResolvableType[] interfaces; + + private ResolvableType[] generics; + + + /** + * Private constructor used to create a new {@link ResolvableType} for cache key purposes, + * with no upfront resolution. + */ + private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = null; + this.resolved = null; + this.hash = calculateHashCode(); + } + + /** + * Private constructor used to create a new {@link ResolvableType} for cache value purposes, + * with upfront resolution and a pre-calculated hash. + * @since 4.2 + */ + private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver, Integer hash) { + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = null; + this.resolved = resolveClass(); + this.hash = hash; + } + + /** + * Private constructor used to create a new {@link ResolvableType} for uncached purposes, + * with upfront resolution but lazily calculated hash. + */ + private ResolvableType( + Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) { + + this.type = type; + this.typeProvider = typeProvider; + this.variableResolver = variableResolver; + this.componentType = componentType; + this.resolved = resolveClass(); + this.hash = null; + } + + /** + * Private constructor used to create a new {@link ResolvableType} on a {@link Class} basis. + * Avoids all {@code instanceof} checks in order to create a straight {@link Class} wrapper. + * @since 4.2 + */ + private ResolvableType(Class clazz) { + this.resolved = (clazz != null ? clazz : Object.class); + this.type = this.resolved; + this.typeProvider = null; + this.variableResolver = null; + this.componentType = null; + this.hash = null; + } + + + /** + * Return the underling Java {@link Type} being managed. With the exception of + * the {@link #NONE} constant, this method will never return {@code null}. + */ + public Type getType() { + return SerializableTypeWrapper.unwrap(this.type); + } + + /** + * Return the underlying Java {@link Class} being managed, if available; + * otherwise {@code null}. + */ + public Class getRawClass() { + if (this.type == this.resolved) { + return this.resolved; + } + Type rawType = this.type; + if (rawType instanceof ParameterizedType) { + rawType = ((ParameterizedType) rawType).getRawType(); + } + return (rawType instanceof Class ? (Class) rawType : null); + } + + /** + * Return the underlying source of the resolvable type. Will return a {@link Field}, + * {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType} + * was constructed. With the exception of the {@link #NONE} constant, this method will + * never return {@code null}. This method is primarily to provide access to additional + * type information or meta-data that alternative JVM languages may provide. + */ + public Object getSource() { + Object source = (this.typeProvider != null ? this.typeProvider.getSource() : null); + return (source != null ? source : this.type); + } + + /** + * Determine whether the given object is an instance of this {@code ResolvableType}. + * @param obj the object to check + * @since 4.2 + * @see #isAssignableFrom(Class) + */ + public boolean isInstance(Object obj) { + return (obj != null && isAssignableFrom(obj.getClass())); + } + + /** + * Determine whether this {@code ResolvableType} is assignable from the + * specified other type. + * @param other the type to be checked against (as a {@code Class}) + * @since 4.2 + * @see #isAssignableFrom(ResolvableType) + */ + public boolean isAssignableFrom(Class other) { + return isAssignableFrom(forClass(other), null); + } + + /** + * Determine whether this {@code ResolvableType} is assignable from the + * specified other type. + *

Attempts to follow the same rules as the Java compiler, considering + * whether both the {@link #resolve() resolved} {@code Class} is + * {@link Class#isAssignableFrom(Class) assignable from} the given type + * as well as whether all {@link #getGenerics() generics} are assignable. + * @param other the type to be checked against (as a {@code ResolvableType}) + * @return {@code true} if the specified other type can be assigned to this + * {@code ResolvableType}; {@code false} otherwise + */ + public boolean isAssignableFrom(ResolvableType other) { + return isAssignableFrom(other, null); + } + + private boolean isAssignableFrom(ResolvableType other, Map matchedBefore) { + Assert.notNull(other, "ResolvableType must not be null"); + + // If we cannot resolve types, we are not assignable + if (this == NONE || other == NONE) { + return false; + } + + // Deal with array by delegating to the component type + if (isArray()) { + return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType())); + } + + if (matchedBefore != null && matchedBefore.get(this.type) == other.type) { + return true; + } + + // Deal with wildcard bounds + WildcardBounds ourBounds = WildcardBounds.get(this); + WildcardBounds typeBounds = WildcardBounds.get(other); + + // In the form X is assignable to + if (typeBounds != null) { + return (ourBounds != null && ourBounds.isSameKind(typeBounds) && + ourBounds.isAssignableFrom(typeBounds.getBounds())); + } + + // In the form is assignable to X... + if (ourBounds != null) { + return ourBounds.isAssignableFrom(other); + } + + // Main assignability check about to follow + boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now... + boolean checkGenerics = true; + Class ourResolved = null; + if (this.type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) this.type; + // Try default variable resolution + if (this.variableResolver != null) { + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved != null) { + ourResolved = resolved.resolve(); + } + } + if (ourResolved == null) { + // Try variable resolution against target type + if (other.variableResolver != null) { + ResolvableType resolved = other.variableResolver.resolveVariable(variable); + if (resolved != null) { + ourResolved = resolved.resolve(); + checkGenerics = false; + } + } + } + if (ourResolved == null) { + // Unresolved type variable, potentially nested -> never insist on exact match + exactMatch = false; + } + } + if (ourResolved == null) { + ourResolved = resolve(Object.class); + } + Class otherResolved = other.resolve(Object.class); + + // We need an exact type match for generics + // List is not assignable from List + if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) { + return false; + } + + if (checkGenerics) { + // Recursively check each generic + ResolvableType[] ourGenerics = getGenerics(); + ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics(); + if (ourGenerics.length != typeGenerics.length) { + return false; + } + if (matchedBefore == null) { + matchedBefore = new IdentityHashMap(1); + } + matchedBefore.put(this.type, other.type); + for (int i = 0; i < ourGenerics.length; i++) { + if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) { + return false; + } + } + } + + return true; + } + + /** + * Return {@code true} if this type resolves to a Class that represents an array. + * @see #getComponentType() + */ + public boolean isArray() { + if (this == NONE) { + return false; + } + return ((this.type instanceof Class && ((Class) this.type).isArray()) || + this.type instanceof GenericArrayType || resolveType().isArray()); + } + + /** + * Return the ResolvableType representing the component type of the array or + * {@link #NONE} if this type does not represent an array. + * @see #isArray() + */ + public ResolvableType getComponentType() { + if (this == NONE) { + return NONE; + } + if (this.componentType != null) { + return this.componentType; + } + if (this.type instanceof Class) { + Class componentType = ((Class) this.type).getComponentType(); + return forType(componentType, this.variableResolver); + } + if (this.type instanceof GenericArrayType) { + return forType(((GenericArrayType) this.type).getGenericComponentType(), this.variableResolver); + } + return resolveType().getComponentType(); + } + + /** + * Convenience method to return this type as a resolvable {@link Collection} type. + * Returns {@link #NONE} if this type does not implement or extend + * {@link Collection}. + * @see #as(Class) + * @see #asMap() + */ + public ResolvableType asCollection() { + return as(Collection.class); + } + + /** + * Convenience method to return this type as a resolvable {@link Map} type. + * Returns {@link #NONE} if this type does not implement or extend + * {@link Map}. + * @see #as(Class) + * @see #asCollection() + */ + public ResolvableType asMap() { + return as(Map.class); + } + + /** + * Return this type as a {@link ResolvableType} of the specified class. Searches + * {@link #getSuperType() supertype} and {@link #getInterfaces() interface} + * hierarchies to find a match, returning {@link #NONE} if this type does not + * implement or extend the specified class. + * @param type the required type (typically narrowed) + * @return a {@link ResolvableType} representing this object as the specified + * type, or {@link #NONE} if not resolvable as that type + * @see #asCollection() + * @see #asMap() + * @see #getSuperType() + * @see #getInterfaces() + */ + public ResolvableType as(Class type) { + if (this == NONE) { + return NONE; + } + if (ObjectUtil.nullSafeEquals(resolve(), type)) { + return this; + } + for (ResolvableType interfaceType : getInterfaces()) { + ResolvableType interfaceAsType = interfaceType.as(type); + if (interfaceAsType != NONE) { + return interfaceAsType; + } + } + return getSuperType().as(type); + } + + /** + * Return a {@link ResolvableType} representing the direct supertype of this type. + * If no supertype is available this method returns {@link #NONE}. + * @see #getInterfaces() + */ + public ResolvableType getSuperType() { + Class resolved = resolve(); + if (resolved == null || resolved.getGenericSuperclass() == null) { + return NONE; + } + if (this.superType == null) { + this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver()); + } + return this.superType; + } + + /** + * Return a {@link ResolvableType} array representing the direct interfaces + * implemented by this type. If this type does not implement any interfaces an + * empty array is returned. + * @see #getSuperType() + */ + public ResolvableType[] getInterfaces() { + Class resolved = resolve(); + if (resolved == null || ObjectUtil.isEmpty(resolved.getGenericInterfaces())) { + return EMPTY_TYPES_ARRAY; + } + if (this.interfaces == null) { + this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver()); + } + return this.interfaces; + } + + /** + * Return {@code true} if this type contains generic parameters. + * @see #getGeneric(int...) + * @see #getGenerics() + */ + public boolean hasGenerics() { + return (getGenerics().length > 0); + } + + /** + * Return {@code true} if this type contains unresolvable generics only, + * that is, no substitute for any of its declared type variables. + */ + boolean isEntirelyUnresolvable() { + if (this == NONE) { + return false; + } + ResolvableType[] generics = getGenerics(); + for (ResolvableType generic : generics) { + if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) { + return false; + } + } + return true; + } + + /** + * Determine whether the underlying type has any unresolvable generics: + * either through an unresolvable type variable on the type itself + * or through implementing a generic interface in a raw fashion, + * i.e. without substituting that interface's type variables. + * The result will be {@code true} only in those two scenarios. + */ + public boolean hasUnresolvableGenerics() { + if (this == NONE) { + return false; + } + ResolvableType[] generics = getGenerics(); + for (ResolvableType generic : generics) { + if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds()) { + return true; + } + } + Class resolved = resolve(); + if (resolved != null) { + for (Type genericInterface : resolved.getGenericInterfaces()) { + if (genericInterface instanceof Class) { + if (forClass((Class) genericInterface).hasGenerics()) { + return true; + } + } + } + return getSuperType().hasUnresolvableGenerics(); + } + return false; + } + + /** + * Determine whether the underlying type is a type variable that + * cannot be resolved through the associated variable resolver. + */ + private boolean isUnresolvableTypeVariable() { + if (this.type instanceof TypeVariable) { + if (this.variableResolver == null) { + return true; + } + TypeVariable variable = (TypeVariable) this.type; + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved == null || resolved.isUnresolvableTypeVariable()) { + return true; + } + } + return false; + } + + /** + * Determine whether the underlying type represents a wildcard + * without specific bounds (i.e., equal to {@code ? extends Object}). + */ + private boolean isWildcardWithoutBounds() { + if (this.type instanceof WildcardType) { + WildcardType wt = (WildcardType) this.type; + if (wt.getLowerBounds().length == 0) { + Type[] upperBounds = wt.getUpperBounds(); + if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class == upperBounds[0])) { + return true; + } + } + } + return false; + } + + /** + * Return a {@link ResolvableType} for the specified nesting level. + * See {@link #getNested(int, Map)} for details. + * @param nestingLevel the nesting level + * @return the {@link ResolvableType} type, or {@code #NONE} + */ + public ResolvableType getNested(int nestingLevel) { + return getNested(nestingLevel, null); + } + + /** + * Return a {@link ResolvableType} for the specified nesting level. + *

The nesting level refers to the specific generic parameter that should be returned. + * A nesting level of 1 indicates this type; 2 indicates the first nested generic; + * 3 the second; and so on. For example, given {@code List>} level 1 refers + * to the {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}. + *

The {@code typeIndexesPerLevel} map can be used to reference a specific generic + * for the given level. For example, an index of 0 would refer to a {@code Map} key; + * whereas, 1 would refer to the value. If the map does not contain a value for a + * specific level the last generic will be used (e.g. a {@code Map} value). + *

Nesting levels may also apply to array types; for example given + * {@code String[]}, a nesting level of 2 refers to {@code String}. + *

If a type does not {@link #hasGenerics() contain} generics the + * {@link #getSuperType() supertype} hierarchy will be considered. + * @param nestingLevel the required nesting level, indexed from 1 for the + * current type, 2 for the first nested generic, 3 for the second and so on + * @param typeIndexesPerLevel a map containing the generic index for a given + * nesting level (may be {@code null}) + * @return a {@link ResolvableType} for the nested level, or {@link #NONE} + */ + public ResolvableType getNested(int nestingLevel, Map typeIndexesPerLevel) { + ResolvableType result = this; + for (int i = 2; i <= nestingLevel; i++) { + if (result.isArray()) { + result = result.getComponentType(); + } + else { + // Handle derived types + while (result != ResolvableType.NONE && !result.hasGenerics()) { + result = result.getSuperType(); + } + Integer index = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(i) : null); + index = (index == null ? result.getGenerics().length - 1 : index); + result = result.getGeneric(index); + } + } + return result; + } + + /** + * Return a {@link ResolvableType} representing the generic parameter for the + * given indexes. Indexes are zero based; for example given the type + * {@code Map>}, {@code getGeneric(0)} will access the + * {@code Integer}. Nested generics can be accessed by specifying multiple indexes; + * for example {@code getGeneric(1, 0)} will access the {@code String} from the + * nested {@code List}. For convenience, if no indexes are specified the first + * generic is returned. + *

If no generic is available at the specified indexes {@link #NONE} is returned. + * @param indexes the indexes that refer to the generic parameter + * (may be omitted to return the first generic) + * @return a {@link ResolvableType} for the specified generic, or {@link #NONE} + * @see #hasGenerics() + * @see #getGenerics() + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public ResolvableType getGeneric(int... indexes) { + ResolvableType[] generics = getGenerics(); + if (indexes == null || indexes.length == 0) { + return (generics.length == 0 ? NONE : generics[0]); + } + ResolvableType generic = this; + for (int index : indexes) { + generics = generic.getGenerics(); + if (index < 0 || index >= generics.length) { + return NONE; + } + generic = generics[index]; + } + return generic; + } + + /** + * Return an array of {@link ResolvableType}s representing the generic parameters of + * this type. If no generics are available an empty array is returned. If you need to + * access a specific generic consider using the {@link #getGeneric(int...)} method as + * it allows access to nested generics and protects against + * {@code IndexOutOfBoundsExceptions}. + * @return an array of {@link ResolvableType}s representing the generic parameters + * (never {@code null}) + * @see #hasGenerics() + * @see #getGeneric(int...) + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public ResolvableType[] getGenerics() { + if (this == NONE) { + return EMPTY_TYPES_ARRAY; + } + if (this.generics == null) { + if (this.type instanceof Class) { + Class typeClass = (Class) this.type; + this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver); + } + else if (this.type instanceof ParameterizedType) { + Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments(); + ResolvableType[] generics = new ResolvableType[actualTypeArguments.length]; + for (int i = 0; i < actualTypeArguments.length; i++) { + generics[i] = forType(actualTypeArguments[i], this.variableResolver); + } + this.generics = generics; + } + else { + this.generics = resolveType().getGenerics(); + } + } + return this.generics; + } + + /** + * Convenience method that will {@link #getGenerics() get} and + * {@link #resolve() resolve} generic parameters. + * @return an array of resolved generic parameters (the resulting array + * will never be {@code null}, but it may contain {@code null} elements}) + * @see #getGenerics() + * @see #resolve() + */ + public Class[] resolveGenerics() { + return resolveGenerics(null); + } + + /** + * Convenience method that will {@link #getGenerics() get} and {@link #resolve() + * resolve} generic parameters, using the specified {@code fallback} if any type + * cannot be resolved. + * @param fallback the fallback class to use if resolution fails + * @return an array of resolved generic parameters + * @see #getGenerics() + * @see #resolve() + */ + public Class[] resolveGenerics(Class fallback) { + ResolvableType[] generics = getGenerics(); + Class[] resolvedGenerics = new Class[generics.length]; + for (int i = 0; i < generics.length; i++) { + resolvedGenerics[i] = generics[i].resolve(fallback); + } + return resolvedGenerics; + } + + /** + * Convenience method that will {@link #getGeneric(int...) get} and + * {@link #resolve() resolve} a specific generic parameters. + * @param indexes the indexes that refer to the generic parameter + * (may be omitted to return the first generic) + * @return a resolved {@link Class} or {@code null} + * @see #getGeneric(int...) + * @see #resolve() + */ + public Class resolveGeneric(int... indexes) { + return getGeneric(indexes).resolve(); + } + + /** + * Resolve this type to a {@link java.lang.Class}, returning {@code null} + * if the type cannot be resolved. This method will consider bounds of + * {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails; + * however, bounds of {@code Object.class} will be ignored. + * @return the resolved {@link Class}, or {@code null} if not resolvable + * @see #resolve(Class) + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public Class resolve() { + return resolve(null); + } + + /** + * Resolve this type to a {@link java.lang.Class}, returning the specified + * {@code fallback} if the type cannot be resolved. This method will consider bounds + * of {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails; + * however, bounds of {@code Object.class} will be ignored. + * @param fallback the fallback class to use if resolution fails + * @return the resolved {@link Class} or the {@code fallback} + * @see #resolve() + * @see #resolveGeneric(int...) + * @see #resolveGenerics() + */ + public Class resolve(Class fallback) { + return (this.resolved != null ? this.resolved : fallback); + } + + private Class resolveClass() { + if (this.type instanceof Class || this.type == null) { + return (Class) this.type; + } + if (this.type instanceof GenericArrayType) { + Class resolvedComponent = getComponentType().resolve(); + return (resolvedComponent != null ? Array.newInstance(resolvedComponent, 0).getClass() : null); + } + return resolveType().resolve(); + } + + /** + * Resolve this type by a single level, returning the resolved value or {@link #NONE}. + *

Note: The returned {@link ResolvableType} should only be used as an intermediary + * as it cannot be serialized. + */ + ResolvableType resolveType() { + if (this.type instanceof ParameterizedType) { + return forType(((ParameterizedType) this.type).getRawType(), this.variableResolver); + } + if (this.type instanceof WildcardType) { + Type resolved = resolveBounds(((WildcardType) this.type).getUpperBounds()); + if (resolved == null) { + resolved = resolveBounds(((WildcardType) this.type).getLowerBounds()); + } + return forType(resolved, this.variableResolver); + } + if (this.type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) this.type; + // Try default variable resolution + if (this.variableResolver != null) { + ResolvableType resolved = this.variableResolver.resolveVariable(variable); + if (resolved != null) { + return resolved; + } + } + // Fallback to bounds + return forType(resolveBounds(variable.getBounds()), this.variableResolver); + } + return NONE; + } + + private Type resolveBounds(Type[] bounds) { + if (ObjectUtil.isEmpty(bounds) || Object.class == bounds[0]) { + return null; + } + return bounds[0]; + } + + private ResolvableType resolveVariable(TypeVariable variable) { + if (this.type instanceof TypeVariable) { + return resolveType().resolveVariable(variable); + } + if (this.type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) this.type; + TypeVariable[] variables = resolve().getTypeParameters(); + for (int i = 0; i < variables.length; i++) { + if (ObjectUtil.nullSafeEquals(variables[i].getName(), variable.getName())) { + Type actualType = parameterizedType.getActualTypeArguments()[i]; + return forType(actualType, this.variableResolver); + } + } + if (parameterizedType.getOwnerType() != null) { + return forType(parameterizedType.getOwnerType(), this.variableResolver).resolveVariable(variable); + } + } + if (this.variableResolver != null) { + return this.variableResolver.resolveVariable(variable); + } + return null; + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ResolvableType)) { + return false; + } + + ResolvableType otherType = (ResolvableType) other; + if (!ObjectUtil.nullSafeEquals(this.type, otherType.type)) { + return false; + } + if (this.typeProvider != otherType.typeProvider && + (this.typeProvider == null || otherType.typeProvider == null || + !ObjectUtil.nullSafeEquals(this.typeProvider.getType(), otherType.typeProvider.getType()))) { + return false; + } + if (this.variableResolver != otherType.variableResolver && + (this.variableResolver == null || otherType.variableResolver == null || + !ObjectUtil.nullSafeEquals(this.variableResolver.getSource(), otherType.variableResolver.getSource()))) { + return false; + } + if (!ObjectUtil.nullSafeEquals(this.componentType, otherType.componentType)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return (this.hash != null ? this.hash : calculateHashCode()); + } + + private int calculateHashCode() { + int hashCode = ObjectUtil.nullSafeHashCode(this.type); + if (this.typeProvider != null) { + hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.typeProvider.getType()); + } + if (this.variableResolver != null) { + hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.variableResolver.getSource()); + } + if (this.componentType != null) { + hashCode = 31 * hashCode + ObjectUtil.nullSafeHashCode(this.componentType); + } + return hashCode; + } + + /** + * Adapts this {@link ResolvableType} to a {@link VariableResolver}. + */ + VariableResolver asVariableResolver() { + if (this == NONE) { + return null; + } + return new DefaultVariableResolver(); + } + + /** + * Custom serialization support for {@link #NONE}. + */ + private Object readResolve() { + return (this.type == null ? NONE : this); + } + + /** + * Return a String representation of this type in its fully resolved form + * (including any generic parameters). + */ + @Override + public String toString() { + if (isArray()) { + return getComponentType() + "[]"; + } + if (this.resolved == null) { + return "?"; + } + if (this.type instanceof TypeVariable) { + TypeVariable variable = (TypeVariable) this.type; + if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) { + // Don't bother with variable boundaries for toString()... + // Can cause infinite recursions in case of self-references + return "?"; + } + } + StringBuilder result = new StringBuilder(this.resolved.getName()); + if (hasGenerics()) { + result.append('<'); + result.append(StringUtil.arrayToDelimitedString(getGenerics(), ", ")); + result.append('>'); + } + return result.toString(); + } + + + // Factory methods + + /** + * Return a {@link ResolvableType} for the specified {@link Class}, + * using the full generic type information for assignability checks. + * For example: {@code ResolvableType.forClass(MyArrayList.class)}. + * @param clazz the class to introspect ({@code null} is semantically + * equivalent to {@code Object.class} for typical use cases here) + * @return a {@link ResolvableType} for the specified class + * @see #forClass(Class, Class) + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClass(Class clazz) { + return new ResolvableType(clazz); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class}, + * doing assignability checks against the raw class only (analogous to + * {@link Class#isAssignableFrom}, which this serves as a wrapper for. + * For example: {@code ResolvableType.forRawClass(List.class)}. + * @param clazz the class to introspect ({@code null} is semantically + * equivalent to {@code Object.class} for typical use cases here) + * @return a {@link ResolvableType} for the specified class + * @since 4.2 + * @see #forClass(Class) + * @see #getRawClass() + */ + public static ResolvableType forRawClass(Class clazz) { + return new ResolvableType(clazz) { + @Override + public ResolvableType[] getGenerics() { + return EMPTY_TYPES_ARRAY; + } + @Override + public boolean isAssignableFrom(Class other) { + return ClassUtils.isAssignable(getRawClass(), other); + } + @Override + public boolean isAssignableFrom(ResolvableType other) { + Class otherClass = other.getRawClass(); + return (otherClass != null && ClassUtils.isAssignable(getRawClass(), otherClass)); + } + }; + } + + /** + * Return a {@link ResolvableType} for the specified base type + * (interface or base class) with a given implementation class. + * For example: {@code ResolvableType.forClass(List.class, MyArrayList.class)}. + * @param baseType the base type (must not be {@code null}) + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified base type backed by the + * given implementation class + * @see #forClass(Class) + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClass(Class baseType, Class implementationClass) { + Assert.notNull(baseType, "Base type must not be null"); + ResolvableType asType = forType(implementationClass).as(baseType); + return (asType == NONE ? forType(baseType) : asType); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param clazz the class (or interface) to introspect + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, ResolvableType...) + */ + public static ResolvableType forClassWithGenerics(Class clazz, Class... generics) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(generics, "Generics array must not be null"); + ResolvableType[] resolvableGenerics = new ResolvableType[generics.length]; + for (int i = 0; i < generics.length; i++) { + resolvableGenerics[i] = forClass(generics[i]); + } + return forClassWithGenerics(clazz, resolvableGenerics); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics. + * @param clazz the class (or interface) to introspect + * @param generics the generics of the class + * @return a {@link ResolvableType} for the specific class and generics + * @see #forClassWithGenerics(Class, Class...) + */ + public static ResolvableType forClassWithGenerics(Class clazz, ResolvableType... generics) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(generics, "Generics array must not be null"); + TypeVariable[] variables = clazz.getTypeParameters(); + Assert.isTrue(variables.length == generics.length, "Mismatched number of generics specified"); + + Type[] arguments = new Type[generics.length]; + for (int i = 0; i < generics.length; i++) { + ResolvableType generic = generics[i]; + Type argument = (generic != null ? generic.getType() : null); + arguments[i] = (argument != null ? argument : variables[i]); + } + + ParameterizedType syntheticType = new SyntheticParameterizedType(clazz, arguments); + return forType(syntheticType, new TypeVariablesVariableResolver(variables, generics)); + } + + /** + * Return a {@link ResolvableType} for the specified instance. The instance does not + * convey generic information but if it implements {@link ResolvableTypeProvider} a + * more precise {@link ResolvableType} can be used than the simple one based on + * the {@link #forClass(Class) Class instance}. + * @param instance the instance + * @return a {@link ResolvableType} for the specified instance + * @since 4.2 + * @see ResolvableTypeProvider + */ + public static ResolvableType forInstance(Object instance) { + Assert.notNull(instance, "Instance must not be null"); + if (instance instanceof ResolvableTypeProvider) { + ResolvableType type = ((ResolvableTypeProvider) instance).getResolvableType(); + if (type != null) { + return type; + } + } + return ResolvableType.forClass(instance.getClass()); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field}. + * @param field the source field + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field, Class) + */ + public static ResolvableType forField(Field field) { + Assert.notNull(field, "Field must not be null"); + return forType(null, new FieldTypeProvider(field), null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation. + *

Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. + * @param field the source field + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, Class implementationClass) { + Assert.notNull(field, "Field must not be null"); + ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation. + *

Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation type. + * @param field the source field + * @param implementationType the implementation type + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, ResolvableType implementationType) { + Assert.notNull(field, "Field must not be null"); + ResolvableType owner = (implementationType != null ? implementationType : NONE); + owner = owner.as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with the + * given nesting level. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel) { + Assert.notNull(field, "Field must not be null"); + return forType(null, new FieldTypeProvider(field), null).getNested(nestingLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Field} with a given + * implementation and the given nesting level. + *

Use this variant when the class that declares the field includes generic + * parameter variables that are satisfied by the implementation class. + * @param field the source field + * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested + * generic type; etc) + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified field + * @see #forField(Field) + */ + public static ResolvableType forField(Field field, int nestingLevel, Class implementationClass) { + Assert.notNull(field, "Field must not be null"); + ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Constructor} parameter. + * @param constructor the source constructor (must not be {@code null}) + * @param parameterIndex the parameter index + * @return a {@link ResolvableType} for the specified constructor parameter + * @see #forConstructorParameter(Constructor, int, Class) + */ + public static ResolvableType forConstructorParameter(Constructor constructor, int parameterIndex) { + Assert.notNull(constructor, "Constructor must not be null"); + return forMethodParameter(new MethodParameter(constructor, parameterIndex)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Constructor} parameter + * with a given implementation. Use this variant when the class that declares the + * constructor includes generic parameter variables that are satisfied by the + * implementation class. + * @param constructor the source constructor (must not be {@code null}) + * @param parameterIndex the parameter index + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified constructor parameter + * @see #forConstructorParameter(Constructor, int) + */ + public static ResolvableType forConstructorParameter(Constructor constructor, int parameterIndex, + Class implementationClass) { + + Assert.notNull(constructor, "Constructor must not be null"); + MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * @param method the source for the method return type + * @return a {@link ResolvableType} for the specified method return + * @see #forMethodReturnType(Method, Class) + */ + public static ResolvableType forMethodReturnType(Method method) { + Assert.notNull(method, "Method must not be null"); + return forMethodParameter(new MethodParameter(method, -1)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} return type. + * Use this variant when the class that declares the method includes generic + * parameter variables that are satisfied by the implementation class. + * @param method the source for the method return type + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified method return + * @see #forMethodReturnType(Method) + */ + public static ResolvableType forMethodReturnType(Method method, Class implementationClass) { + Assert.notNull(method, "Method must not be null"); + MethodParameter methodParameter = new MethodParameter(method, -1); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} parameter. + * @param method the source method (must not be {@code null}) + * @param parameterIndex the parameter index + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int, Class) + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(Method method, int parameterIndex) { + Assert.notNull(method, "Method must not be null"); + return forMethodParameter(new MethodParameter(method, parameterIndex)); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Method} parameter with a + * given implementation. Use this variant when the class that declares the method + * includes generic parameter variables that are satisfied by the implementation class. + * @param method the source method (must not be {@code null}) + * @param parameterIndex the parameter index + * @param implementationClass the implementation class + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int, Class) + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class implementationClass) { + Assert.notNull(method, "Method must not be null"); + MethodParameter methodParameter = new MethodParameter(method, parameterIndex); + methodParameter.setContainingClass(implementationClass); + return forMethodParameter(methodParameter); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter}. + * @param methodParameter the source method parameter (must not be {@code null}) + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter) { + return forMethodParameter(methodParameter, (Type) null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter} with a + * given implementation type. Use this variant when the class that declares the method + * includes generic parameter variables that are satisfied by the implementation type. + * @param methodParameter the source method parameter (must not be {@code null}) + * @param implementationType the implementation type + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(MethodParameter) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter, ResolvableType implementationType) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + implementationType = (implementationType != null ? implementationType : + forType(methodParameter.getContainingClass())); + ResolvableType owner = implementationType.as(methodParameter.getDeclaringClass()); + return forType(null, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()). + getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); + } + + /** + * Return a {@link ResolvableType} for the specified {@link MethodParameter}, + * overriding the target type to resolve with a specific given type. + * @param methodParameter the source method parameter (must not be {@code null}) + * @param targetType the type to resolve (a part of the method parameter's type) + * @return a {@link ResolvableType} for the specified method parameter + * @see #forMethodParameter(Method, int) + */ + public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); + return forType(targetType, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()). + getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); + } + + /** + * Resolve the top-level parameter type of the given {@code MethodParameter}. + * @param methodParameter the method parameter to resolve + * @since 4.1.9 + * @see MethodParameter#setParameterType + */ + static void resolveMethodParameter(MethodParameter methodParameter) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); + methodParameter.setParameterType( + forType(null, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).resolve()); + } + + /** + * Return a {@link ResolvableType} as a array of the specified {@code componentType}. + * @param componentType the component type + * @return a {@link ResolvableType} as an array of the specified component type + */ + public static ResolvableType forArrayComponent(ResolvableType componentType) { + Assert.notNull(componentType, "Component type must not be null"); + Class arrayClass = Array.newInstance(componentType.resolve(), 0).getClass(); + return new ResolvableType(arrayClass, null, null, componentType); + } + + private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) { + ResolvableType[] result = new ResolvableType[types.length]; + for (int i = 0; i < types.length; i++) { + result[i] = forType(types[i], owner); + } + return result; + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type}. + *

Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}. + * @param type the source type (potentially {@code null}) + * @return a {@link ResolvableType} for the specified {@link Type} + * @see #forType(Type, ResolvableType) + */ + public static ResolvableType forType(Type type) { + return forType(type, null, null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by the given + * owner type. + *

Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}. + * @param type the source type or {@code null} + * @param owner the owner type used to resolve variables + * @return a {@link ResolvableType} for the specified {@link Type} and owner + * @see #forType(Type) + */ + public static ResolvableType forType(Type type, ResolvableType owner) { + VariableResolver variableResolver = null; + if (owner != null) { + variableResolver = owner.asVariableResolver(); + } + return forType(type, variableResolver); + } + + + /** + * Return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference}. + *

Note: The resulting {@link ResolvableType} instance may not be {@link Serializable}. + * @param typeReference the reference to obtain the source type from + * @return a {@link ResolvableType} for the specified {@link ParameterizedTypeReference} + * @since 4.3.12 + * @see #forType(Type) + */ + public static ResolvableType forType(ParameterizedTypeReference typeReference) { + return forType(typeReference.getType(), null, null); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by a given + * {@link VariableResolver}. + * @param type the source type or {@code null} + * @param variableResolver the variable resolver or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} + */ + static ResolvableType forType(Type type, VariableResolver variableResolver) { + return forType(type, null, variableResolver); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by a given + * {@link VariableResolver}. + * @param type the source type or {@code null} + * @param typeProvider the type provider or {@code null} + * @param variableResolver the variable resolver or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} + */ + static ResolvableType forType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + if (type == null && typeProvider != null) { + type = SerializableTypeWrapper.forTypeProvider(typeProvider); + } + if (type == null) { + return NONE; + } + + // For simple Class references, build the wrapper right away - + // no expensive resolution necessary, so not worth caching... + if (type instanceof Class) { + return new ResolvableType(type, typeProvider, variableResolver, (ResolvableType) null); + } + + // Purge empty entries on access since we don't have a clean-up thread or the like. + cache.purgeUnreferencedEntries(); + + // Check the cache - we may have a ResolvableType which has been resolved before... + ResolvableType key = new ResolvableType(type, typeProvider, variableResolver); + ResolvableType resolvableType = cache.get(key); + if (resolvableType == null) { + resolvableType = new ResolvableType(type, typeProvider, variableResolver, key.hash); + cache.put(resolvableType, resolvableType); + } + return resolvableType; + } + + /** + * Clear the internal {@code ResolvableType}/{@code SerializableTypeWrapper} cache. + * @since 4.2 + */ + public static void clearCache() { + cache.clear(); + SerializableTypeWrapper.cache.clear(); + } + + + /** + * Strategy interface used to resolve {@link TypeVariable}s. + */ + interface VariableResolver extends Serializable { + + /** + * Return the source of the resolver (used for hashCode and equals). + */ + Object getSource(); + + /** + * Resolve the specified variable. + * @param variable the variable to resolve + * @return the resolved variable, or {@code null} if not found + */ + ResolvableType resolveVariable(TypeVariable variable); + } + + + @SuppressWarnings("serial") + private class DefaultVariableResolver implements VariableResolver { + + @Override + public ResolvableType resolveVariable(TypeVariable variable) { + return ResolvableType.this.resolveVariable(variable); + } + + @Override + public Object getSource() { + return ResolvableType.this; + } + } + + + @SuppressWarnings("serial") + private static class TypeVariablesVariableResolver implements VariableResolver { + + private final TypeVariable[] variables; + + private final ResolvableType[] generics; + + public TypeVariablesVariableResolver(TypeVariable[] variables, ResolvableType[] generics) { + this.variables = variables; + this.generics = generics; + } + + @Override + public ResolvableType resolveVariable(TypeVariable variable) { + for (int i = 0; i < this.variables.length; i++) { + TypeVariable v1 = SerializableTypeWrapper.unwrap(this.variables[i]); + TypeVariable v2 = SerializableTypeWrapper.unwrap(variable); + if (ObjectUtil.nullSafeEquals(v1, v2)) { + return this.generics[i]; + } + } + return null; + } + + @Override + public Object getSource() { + return this.generics; + } + } + + + private static final class SyntheticParameterizedType implements ParameterizedType, Serializable { + + private final Type rawType; + + private final Type[] typeArguments; + + public SyntheticParameterizedType(Type rawType, Type[] typeArguments) { + this.rawType = rawType; + this.typeArguments = typeArguments; + } + + @Override // on Java 8 + public String getTypeName() { + StringBuilder result = new StringBuilder(this.rawType.getTypeName()); + if (this.typeArguments.length > 0) { + result.append('<'); + for (int i = 0; i < this.typeArguments.length; i++) { + if (i > 0) { + result.append(", "); + } + result.append(this.typeArguments[i].getTypeName()); + } + result.append('>'); + } + return result.toString(); + } + + @Override + public Type getOwnerType() { + return null; + } + + @Override + public Type getRawType() { + return this.rawType; + } + + @Override + public Type[] getActualTypeArguments() { + return this.typeArguments; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ParameterizedType)) { + return false; + } + ParameterizedType otherType = (ParameterizedType) other; + return (otherType.getOwnerType() == null && this.rawType.equals(otherType.getRawType()) && + Arrays.equals(this.typeArguments, otherType.getActualTypeArguments())); + } + + @Override + public int hashCode() { + return (this.rawType.hashCode() * 31 + Arrays.hashCode(this.typeArguments)); + } + } + + + /** + * Internal helper to handle bounds from {@link WildcardType}s. + */ + private static class WildcardBounds { + + private final Kind kind; + + private final ResolvableType[] bounds; + + /** + * Internal constructor to create a new {@link WildcardBounds} instance. + * @param kind the kind of bounds + * @param bounds the bounds + * @see #get(ResolvableType) + */ + public WildcardBounds(Kind kind, ResolvableType[] bounds) { + this.kind = kind; + this.bounds = bounds; + } + + /** + * Return {@code true} if this bounds is the same kind as the specified bounds. + */ + public boolean isSameKind(WildcardBounds bounds) { + return this.kind == bounds.kind; + } + + /** + * Return {@code true} if this bounds is assignable to all the specified types. + * @param types the types to test against + * @return {@code true} if this bounds is assignable to all types + */ + public boolean isAssignableFrom(ResolvableType... types) { + for (ResolvableType bound : this.bounds) { + for (ResolvableType type : types) { + if (!isAssignable(bound, type)) { + return false; + } + } + } + return true; + } + + private boolean isAssignable(ResolvableType source, ResolvableType from) { + return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source)); + } + + /** + * Return the underlying bounds. + */ + public ResolvableType[] getBounds() { + return this.bounds; + } + + /** + * Get a {@link WildcardBounds} instance for the specified type, returning + * {@code null} if the specified type cannot be resolved to a {@link WildcardType}. + * @param type the source type + * @return a {@link WildcardBounds} instance or {@code null} + */ + public static WildcardBounds get(ResolvableType type) { + ResolvableType resolveToWildcard = type; + while (!(resolveToWildcard.getType() instanceof WildcardType)) { + if (resolveToWildcard == NONE) { + return null; + } + resolveToWildcard = resolveToWildcard.resolveType(); + } + WildcardType wildcardType = (WildcardType) resolveToWildcard.type; + Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER); + Type[] bounds = (boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds()); + ResolvableType[] resolvableBounds = new ResolvableType[bounds.length]; + for (int i = 0; i < bounds.length; i++) { + resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver); + } + return new WildcardBounds(boundsType, resolvableBounds); + } + + /** + * The various kinds of bounds. + */ + enum Kind {UPPER, LOWER} + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java new file mode 100644 index 00000000..db566cf2 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/ResolvableTypeProvider.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public interface ResolvableTypeProvider { + + /** + * Return the {@link ResolvableType} describing this instance + * (or {@code null} if some sort of default should be applied instead). + */ + ResolvableType getResolvableType(); + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java new file mode 100644 index 00000000..dc67e08d --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/type/SerializableTypeWrapper.java @@ -0,0 +1,399 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.base.type; + +import fun.asgc.neutrino.core.util.ConcurrentReferenceHashMap; +import fun.asgc.neutrino.core.util.ReflectUtil; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.lang.reflect.*; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +abstract class SerializableTypeWrapper { + + private static final Class[] SUPPORTED_SERIALIZABLE_TYPES = { + GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class}; + + static final ConcurrentReferenceHashMap cache = new ConcurrentReferenceHashMap(256); + + + /** + * Return a {@link Serializable} variant of {@link Field#getGenericType()}. + */ + public static Type forField(Field field) { + return forTypeProvider(new FieldTypeProvider(field)); + } + + /** + * Return a {@link Serializable} variant of + * {@link MethodParameter#getGenericParameterType()}. + */ + public static Type forMethodParameter(MethodParameter methodParameter) { + return forTypeProvider(new MethodParameterTypeProvider(methodParameter)); + } + + /** + * Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}. + */ + @SuppressWarnings("serial") + public static Type forGenericSuperclass(final Class type) { + return forTypeProvider(new SimpleTypeProvider() { + @Override + public Type getType() { + return type.getGenericSuperclass(); + } + }); + } + + /** + * Return a {@link Serializable} variant of {@link Class#getGenericInterfaces()}. + */ + @SuppressWarnings("serial") + public static Type[] forGenericInterfaces(final Class type) { + Type[] result = new Type[type.getGenericInterfaces().length]; + for (int i = 0; i < result.length; i++) { + final int index = i; + result[i] = forTypeProvider(new SimpleTypeProvider() { + @Override + public Type getType() { + return type.getGenericInterfaces()[index]; + } + }); + } + return result; + } + + /** + * Return a {@link Serializable} variant of {@link Class#getTypeParameters()}. + */ + @SuppressWarnings("serial") + public static Type[] forTypeParameters(final Class type) { + Type[] result = new Type[type.getTypeParameters().length]; + for (int i = 0; i < result.length; i++) { + final int index = i; + result[i] = forTypeProvider(new SimpleTypeProvider() { + @Override + public Type getType() { + return type.getTypeParameters()[index]; + } + }); + } + return result; + } + + /** + * Unwrap the given type, effectively returning the original non-serializable type. + * @param type the type to unwrap + * @return the original non-serializable type + */ + @SuppressWarnings("unchecked") + public static T unwrap(T type) { + Type unwrapped = type; + while (unwrapped instanceof SerializableTypeProxy) { + unwrapped = ((SerializableTypeProxy) type).getTypeProvider().getType(); + } + return (T) unwrapped; + } + + /** + * Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} . + */ + static Type forTypeProvider(TypeProvider provider) { + Type providedType = provider.getType(); + if (providedType == null || providedType instanceof Serializable) { + // No serializable type wrapping necessary (e.g. for java.lang.Class) + return providedType; + } + + // Obtain a serializable type proxy for the given provider... + Type cached = cache.get(providedType); + if (cached != null) { + return cached; + } + for (Class type : SUPPORTED_SERIALIZABLE_TYPES) { + if (type.isInstance(providedType)) { + ClassLoader classLoader = provider.getClass().getClassLoader(); + Class[] interfaces = new Class[] {type, SerializableTypeProxy.class, Serializable.class}; + InvocationHandler handler = new TypeProxyInvocationHandler(provider); + cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler); + cache.put(providedType, cached); + return cached; + } + } + throw new IllegalArgumentException("Unsupported Type class: " + providedType.getClass().getName()); + } + + + /** + * Additional interface implemented by the type proxy. + */ + interface SerializableTypeProxy { + + /** + * Return the underlying type provider. + */ + TypeProvider getTypeProvider(); + } + + + /** + * A {@link Serializable} interface providing access to a {@link Type}. + */ + interface TypeProvider extends Serializable { + + /** + * Return the (possibly non {@link Serializable}) {@link Type}. + */ + Type getType(); + + /** + * Return the source of the type or {@code null}. + */ + Object getSource(); + } + + + /** + * Base implementation of {@link TypeProvider} with a {@code null} source. + */ + @SuppressWarnings("serial") + private static abstract class SimpleTypeProvider implements TypeProvider { + + @Override + public Object getSource() { + return null; + } + } + + + /** + * {@link Serializable} {@link InvocationHandler} used by the proxied {@link Type}. + * Provides serialization support and enhances any methods that return {@code Type} + * or {@code Type[]}. + */ + @SuppressWarnings("serial") + private static class TypeProxyInvocationHandler implements InvocationHandler, Serializable { + + private final TypeProvider provider; + + public TypeProxyInvocationHandler(TypeProvider provider) { + this.provider = provider; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals("equals")) { + Object other = args[0]; + // Unwrap proxies for speed + if (other instanceof Type) { + other = unwrap((Type) other); + } + return this.provider.getType().equals(other); + } + else if (method.getName().equals("hashCode")) { + return this.provider.getType().hashCode(); + } + else if (method.getName().equals("getTypeProvider")) { + return this.provider; + } + + if (Type.class == method.getReturnType() && args == null) { + return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1)); + } + else if (Type[].class == method.getReturnType() && args == null) { + Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length]; + for (int i = 0; i < result.length; i++) { + result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i)); + } + return result; + } + + try { + return method.invoke(this.provider.getType(), args); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained from a {@link Field}. + */ + @SuppressWarnings("serial") + static class FieldTypeProvider implements TypeProvider { + + private final String fieldName; + + private final Class declaringClass; + + private transient Field field; + + public FieldTypeProvider(Field field) { + this.fieldName = field.getName(); + this.declaringClass = field.getDeclaringClass(); + this.field = field; + } + + @Override + public Type getType() { + return this.field.getGenericType(); + } + + @Override + public Object getSource() { + return this.field; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + try { + this.field = this.declaringClass.getDeclaredField(this.fieldName); + } + catch (Throwable ex) { + throw new IllegalStateException("Could not find original class structure", ex); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}. + */ + @SuppressWarnings("serial") + static class MethodParameterTypeProvider implements TypeProvider { + + private final String methodName; + + private final Class[] parameterTypes; + + private final Class declaringClass; + + private final int parameterIndex; + + private transient MethodParameter methodParameter; + + public MethodParameterTypeProvider(MethodParameter methodParameter) { + if (methodParameter.getMethod() != null) { + this.methodName = methodParameter.getMethod().getName(); + this.parameterTypes = methodParameter.getMethod().getParameterTypes(); + } + else { + this.methodName = null; + this.parameterTypes = methodParameter.getConstructor().getParameterTypes(); + } + this.declaringClass = methodParameter.getDeclaringClass(); + this.parameterIndex = methodParameter.getParameterIndex(); + this.methodParameter = methodParameter; + } + + + @Override + public Type getType() { + return this.methodParameter.getGenericParameterType(); + } + + @Override + public Object getSource() { + return this.methodParameter; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + try { + if (this.methodName != null) { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex); + } + else { + this.methodParameter = new MethodParameter( + this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex); + } + } + catch (Throwable ex) { + throw new IllegalStateException("Could not find original class structure", ex); + } + } + } + + + /** + * {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method. + */ + @SuppressWarnings("serial") + static class MethodInvokeTypeProvider implements TypeProvider { + + private final TypeProvider provider; + + private final String methodName; + + private final Class declaringClass; + + private final int index; + + private transient Method method; + + private transient volatile Object result; + + public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) { + this.provider = provider; + this.methodName = method.getName(); + this.declaringClass = method.getDeclaringClass(); + this.index = index; + this.method = method; + } + + @Override + public Type getType() { + Object result = this.result; + if (result == null) { + // Lazy invocation of the target method on the provided type + result = ReflectUtil.invokeMethod(this.method, this.provider.getType()); + // Cache the result for further calls to getType() + this.result = result; + } + return (result instanceof Type[] ? ((Type[]) result)[this.index] : (Type) result); + } + + @Override + public Object getSource() { + return null; + } + + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { + inputStream.defaultReadObject(); + this.method = ReflectUtil.findMethod(this.declaringClass, this.methodName); + if (this.method.getReturnType() != Type.class && this.method.getReturnType() != Type[].class) { + throw new IllegalStateException( + "Invalid return type on deserialized method - needs to be Type or Type[]: " + this.method); + } + } + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java new file mode 100644 index 00000000..fc14aa9d --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ConcurrentReferenceHashMap.java @@ -0,0 +1,1017 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.util; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Array; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author: aoshiguchen + * @date: 2022/9/24 + */ +public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { + + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; + + private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; + + private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; + + + /** + * Array of segments indexed using the high order bits from the hash. + */ + private final Segment[] segments; + + /** + * When the average number of references per table exceeds this value resize will be attempted. + */ + private final float loadFactor; + + /** + * The reference type: SOFT or WEAK. + */ + private final ReferenceType referenceType; + + /** + * The shift value used to calculate the size of the segments array and an index from the hash. + */ + private final int shift; + + /** + * Late binding entry set. + */ + private volatile Set> entrySet; + + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + */ + public ConcurrentReferenceHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + */ + public ConcurrentReferenceHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per table + * exceeds this value resize will be attempted + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + * @param referenceType the reference type used for entries (soft or weak) + */ + public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + */ + public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { + this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); + } + + /** + * Create a new {@code ConcurrentReferenceHashMap} instance. + * @param initialCapacity the initial capacity of the map + * @param loadFactor the load factor. When the average number of references per + * table exceeds this value, resize will be attempted. + * @param concurrencyLevel the expected number of threads that will concurrently + * write to the map + * @param referenceType the reference type used for entries (soft or weak) + */ + @SuppressWarnings("unchecked") + public ConcurrentReferenceHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { + + Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); + Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); + Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); + Assert.notNull(referenceType, "Reference type must not be null"); + this.loadFactor = loadFactor; + this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); + int size = 1 << this.shift; + this.referenceType = referenceType; + int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); + this.segments = (Segment[]) Array.newInstance(Segment.class, size); + for (int i = 0; i < this.segments.length; i++) { + this.segments[i] = new Segment(roundedUpSegmentCapacity); + } + } + + + protected final float getLoadFactor() { + return this.loadFactor; + } + + protected final int getSegmentsSize() { + return this.segments.length; + } + + protected final Segment getSegment(int index) { + return this.segments[index]; + } + + /** + * Factory method that returns the {@link ReferenceManager}. + * This method will be called once for each {@link Segment}. + * @return a new reference manager + */ + protected ReferenceManager createReferenceManager() { + return new ReferenceManager(); + } + + /** + * Get the hash for a given object, apply an additional hash function to reduce + * collisions. This implementation uses the same Wang/Jenkins algorithm as + * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. + * @param o the object to hash (may be null) + * @return the resulting hash code + */ + protected int getHash(Object o) { + int hash = (o != null ? o.hashCode() : 0); + hash += (hash << 15) ^ 0xffffcd7d; + hash ^= (hash >>> 10); + hash += (hash << 3); + hash ^= (hash >>> 6); + hash += (hash << 2) + (hash << 14); + hash ^= (hash >>> 16); + return hash; + } + + @Override + public V get(Object key) { + Entry entry = getEntryIfAvailable(key); + return (entry != null ? entry.getValue() : null); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + Entry entry = getEntryIfAvailable(key); + return (entry != null ? entry.getValue() : defaultValue); + } + + @Override + public boolean containsKey(Object key) { + Entry entry = getEntryIfAvailable(key); + return (entry != null && ObjectUtil.nullSafeEquals(entry.getKey(), key)); + } + + private Entry getEntryIfAvailable(Object key) { + Reference ref = getReference(key, Restructure.WHEN_NECESSARY); + return (ref != null ? ref.get() : null); + } + + /** + * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, + * or {@code null} if not found. + * @param key the key (can be {@code null}) + * @param restructure types of restructure allowed during this call + * @return the reference, or {@code null} if not found + */ + protected final Reference getReference(Object key, Restructure restructure) { + int hash = getHash(key); + return getSegmentForHash(hash).getReference(key, hash, restructure); + } + + @Override + public V put(K key, V value) { + return put(key, value, true); + } + + @Override + public V putIfAbsent(K key, V value) { + return put(key, value, false); + } + + private V put(final K key, final V value, final boolean overwriteExisting) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { + @Override + protected V execute(Reference ref, Entry entry, Entries entries) { + if (entry != null) { + V oldValue = entry.getValue(); + if (overwriteExisting) { + entry.setValue(value); + } + return oldValue; + } + entries.add(value); + return null; + } + }); + } + + @Override + public V remove(Object key) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected V execute(Reference ref, Entry entry) { + if (entry != null) { + ref.release(); + return entry.value; + } + return null; + } + }); + } + + @Override + public boolean remove(Object key, final Object value) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(Reference ref, Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), value)) { + ref.release(); + return true; + } + return false; + } + }); + } + + @Override + public boolean replace(K key, final V oldValue, final V newValue) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected Boolean execute(Reference ref, Entry entry) { + if (entry != null && ObjectUtil.nullSafeEquals(entry.getValue(), oldValue)) { + entry.setValue(newValue); + return true; + } + return false; + } + }); + } + + @Override + public V replace(K key, final V value) { + return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { + @Override + protected V execute(Reference ref, Entry entry) { + if (entry != null) { + V oldValue = entry.getValue(); + entry.setValue(value); + return oldValue; + } + return null; + } + }); + } + + @Override + public void clear() { + for (Segment segment : this.segments) { + segment.clear(); + } + } + + /** + * Remove any entries that have been garbage collected and are no longer referenced. + * Under normal circumstances garbage collected entries are automatically purged as + * items are added or removed from the Map. This method can be used to force a purge, + * and is useful when the Map is read frequently but updated less often. + */ + public void purgeUnreferencedEntries() { + for (Segment segment : this.segments) { + segment.restructureIfNecessary(false); + } + } + + + @Override + public int size() { + int size = 0; + for (Segment segment : this.segments) { + size += segment.getCount(); + } + return size; + } + + @Override + public boolean isEmpty() { + for (Segment segment : this.segments) { + if (segment.getCount() > 0) { + return false; + } + } + return true; + } + + @Override + public Set> entrySet() { + Set> entrySet = this.entrySet; + if (entrySet == null) { + entrySet = new EntrySet(); + this.entrySet = entrySet; + } + return entrySet; + } + + private T doTask(Object key, Task task) { + int hash = getHash(key); + return getSegmentForHash(hash).doTask(hash, key, task); + } + + private Segment getSegmentForHash(int hash) { + return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; + } + + /** + * Calculate a shift value that can be used to create a power-of-two value between + * the specified maximum and minimum values. + * @param minimumValue the minimum value + * @param maximumValue the maximum value + * @return the calculated shift (use {@code 1 << shift} to obtain a value) + */ + protected static int calculateShift(int minimumValue, int maximumValue) { + int shift = 0; + int value = 1; + while (value < minimumValue && value < maximumValue) { + value <<= 1; + shift++; + } + return shift; + } + + + /** + * Various reference types supported by this map. + */ + public enum ReferenceType { + + /** Use {@link SoftReference}s */ + SOFT, + + /** Use {@link WeakReference}s */ + WEAK + } + + + /** + * A single segment used to divide the map to allow better concurrent performance. + */ + @SuppressWarnings("serial") + protected final class Segment extends ReentrantLock { + + private final ReferenceManager referenceManager; + + private final int initialSize; + + /** + * Array of references indexed using the low order bits from the hash. + * This property should only be set along with {@code resizeThreshold}. + */ + private volatile Reference[] references; + + /** + * The total number of references contained in this segment. This includes chained + * references and references that have been garbage collected but not purged. + */ + private volatile int count = 0; + + /** + * The threshold when resizing of the references should occur. When {@code count} + * exceeds this value references will be resized. + */ + private int resizeThreshold; + + public Segment(int initialCapacity) { + this.referenceManager = createReferenceManager(); + this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE); + setReferences(createReferenceArray(this.initialSize)); + } + + public Reference getReference(Object key, int hash, Restructure restructure) { + if (restructure == Restructure.WHEN_NECESSARY) { + restructureIfNecessary(false); + } + if (this.count == 0) { + return null; + } + // Use a local copy to protect against other threads writing + Reference[] references = this.references; + int index = getIndex(hash, references); + Reference head = references[index]; + return findInChain(head, key, hash); + } + + /** + * Apply an update operation to this segment. + * The segment will be locked during the update. + * @param hash the hash of the key + * @param key the key + * @param task the update operation + * @return the result of the operation + */ + public T doTask(final int hash, final Object key, final Task task) { + boolean resize = task.hasOption(TaskOption.RESIZE); + if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { + restructureIfNecessary(resize); + } + if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) { + return task.execute(null, null, null); + } + lock(); + try { + final int index = getIndex(hash, this.references); + final Reference head = this.references[index]; + Reference ref = findInChain(head, key, hash); + Entry entry = (ref != null ? ref.get() : null); + Entries entries = new Entries() { + @Override + public void add(V value) { + @SuppressWarnings("unchecked") + Entry newEntry = new Entry((K) key, value); + Reference newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); + Segment.this.references[index] = newReference; + Segment.this.count++; + } + }; + return task.execute(ref, entry, entries); + } + finally { + unlock(); + if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { + restructureIfNecessary(resize); + } + } + } + + /** + * Clear all items from this segment. + */ + public void clear() { + if (this.count == 0) { + return; + } + lock(); + try { + setReferences(createReferenceArray(this.initialSize)); + this.count = 0; + } + finally { + unlock(); + } + } + + /** + * Restructure the underlying data structure when it becomes necessary. This + * method can increase the size of the references table as well as purge any + * references that have been garbage collected. + * @param allowResize if resizing is permitted + */ + protected final void restructureIfNecessary(boolean allowResize) { + boolean needsResize = (this.count > 0 && this.count >= this.resizeThreshold); + Reference ref = this.referenceManager.pollForPurge(); + if (ref != null || (needsResize && allowResize)) { + lock(); + try { + int countAfterRestructure = this.count; + Set> toPurge = Collections.emptySet(); + if (ref != null) { + toPurge = new HashSet>(); + while (ref != null) { + toPurge.add(ref); + ref = this.referenceManager.pollForPurge(); + } + } + countAfterRestructure -= toPurge.size(); + + // Recalculate taking into account count inside lock and items that + // will be purged + needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold); + boolean resizing = false; + int restructureSize = this.references.length; + if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { + restructureSize <<= 1; + resizing = true; + } + + // Either create a new table or reuse the existing one + Reference[] restructured = + (resizing ? createReferenceArray(restructureSize) : this.references); + + // Restructure + for (int i = 0; i < this.references.length; i++) { + ref = this.references[i]; + if (!resizing) { + restructured[i] = null; + } + while (ref != null) { + if (!toPurge.contains(ref) && (ref.get() != null)) { + int index = getIndex(ref.getHash(), restructured); + restructured[index] = this.referenceManager.createReference( + ref.get(), ref.getHash(), restructured[index]); + } + ref = ref.getNext(); + } + } + + // Replace volatile members + if (resizing) { + setReferences(restructured); + } + this.count = Math.max(countAfterRestructure, 0); + } + finally { + unlock(); + } + } + } + + private Reference findInChain(Reference ref, Object key, int hash) { + Reference currRef = ref; + while (currRef != null) { + if (currRef.getHash() == hash) { + Entry entry = currRef.get(); + if (entry != null) { + K entryKey = entry.getKey(); + if (ObjectUtil.nullSafeEquals(entryKey, key)) { + return currRef; + } + } + } + currRef = currRef.getNext(); + } + return null; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private Reference[] createReferenceArray(int size) { + return new Reference[size]; + } + + private int getIndex(int hash, Reference[] references) { + return (hash & (references.length - 1)); + } + + /** + * Replace the references with a new value, recalculating the resizeThreshold. + * @param references the new references + */ + private void setReferences(Reference[] references) { + this.references = references; + this.resizeThreshold = (int) (references.length * getLoadFactor()); + } + + /** + * Return the size of the current references array. + */ + public final int getSize() { + return this.references.length; + } + + /** + * Return the total number of references in this segment. + */ + public final int getCount() { + return this.count; + } + } + + + /** + * A reference to an {@link Entry} contained in the map. Implementations are usually + * wrappers around specific Java reference implementations (e.g., {@link SoftReference}). + */ + protected interface Reference { + + /** + * Return the referenced entry, or {@code null} if the entry is no longer available. + */ + Entry get(); + + /** + * Return the hash for the reference. + */ + int getHash(); + + /** + * Return the next reference in the chain, or {@code null} if none. + */ + Reference getNext(); + + /** + * Release this entry and ensure that it will be returned from + * {@code ReferenceManager#pollForPurge()}. + */ + void release(); + } + + + /** + * A single map entry. + */ + protected static final class Entry implements Map.Entry { + + private final K key; + + private volatile V value; + + public Entry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public V getValue() { + return this.value; + } + + @Override + public V setValue(V value) { + V previous = this.value; + this.value = value; + return previous; + } + + @Override + public String toString() { + return (this.key + "=" + this.value); + } + + @Override + @SuppressWarnings("rawtypes") + public final boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Map.Entry)) { + return false; + } + Map.Entry otherEntry = (Map.Entry) other; + return (ObjectUtil.nullSafeEquals(getKey(), otherEntry.getKey()) && + ObjectUtil.nullSafeEquals(getValue(), otherEntry.getValue())); + } + + @Override + public final int hashCode() { + return (ObjectUtil.nullSafeHashCode(this.key) ^ ObjectUtil.nullSafeHashCode(this.value)); + } + } + + + /** + * A task that can be {@link Segment#doTask run} against a {@link Segment}. + */ + private abstract class Task { + + private final EnumSet options; + + public Task(TaskOption... options) { + this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); + } + + public boolean hasOption(TaskOption option) { + return this.options.contains(option); + } + + /** + * Execute the task. + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @param entries access to the underlying entries + * @return the result of the task + * @see #execute(Reference, Entry) + */ + protected T execute(Reference ref, Entry entry, Entries entries) { + return execute(ref, entry); + } + + /** + * Convenience method that can be used for tasks that do not need access to {@link Entries}. + * @param ref the found reference (or {@code null}) + * @param entry the found entry (or {@code null}) + * @return the result of the task + * @see #execute(Reference, Entry, Entries) + */ + protected T execute(Reference ref, Entry entry) { + return null; + } + } + + + /** + * Various options supported by a {@code Task}. + */ + private enum TaskOption { + + RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE + } + + + /** + * Allows a task access to {@link Segment} entries. + */ + private abstract class Entries { + + /** + * Add a new entry with the specified value. + * @param value the value to add + */ + public abstract void add(V value); + } + + + /** + * Internal entry-set implementation. + */ + private class EntrySet extends AbstractSet> { + + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); + Entry otherEntry = (ref != null ? ref.get() : null); + if (otherEntry != null) { + return ObjectUtil.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); + } + return false; + } + + @Override + public int size() { + return ConcurrentReferenceHashMap.this.size(); + } + + @Override + public void clear() { + ConcurrentReferenceHashMap.this.clear(); + } + } + + + /** + * Internal entry iterator implementation. + */ + private class EntryIterator implements Iterator> { + + private int segmentIndex; + + private int referenceIndex; + + private Reference[] references; + + private Reference reference; + + private Entry next; + + private Entry last; + + public EntryIterator() { + moveToNextSegment(); + } + + @Override + public boolean hasNext() { + getNextIfNecessary(); + return (this.next != null); + } + + @Override + public Entry next() { + getNextIfNecessary(); + if (this.next == null) { + throw new NoSuchElementException(); + } + this.last = this.next; + this.next = null; + return this.last; + } + + private void getNextIfNecessary() { + while (this.next == null) { + moveToNextReference(); + if (this.reference == null) { + return; + } + this.next = this.reference.get(); + } + } + + private void moveToNextReference() { + if (this.reference != null) { + this.reference = this.reference.getNext(); + } + while (this.reference == null && this.references != null) { + if (this.referenceIndex >= this.references.length) { + moveToNextSegment(); + this.referenceIndex = 0; + } + else { + this.reference = this.references[this.referenceIndex]; + this.referenceIndex++; + } + } + } + + private void moveToNextSegment() { + this.reference = null; + this.references = null; + if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { + this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; + this.segmentIndex++; + } + } + + @Override + public void remove() { + Assert.state(this.last != null, "No element to remove"); + ConcurrentReferenceHashMap.this.remove(this.last.getKey()); + } + } + + + /** + * The types of restructuring that can be performed. + */ + protected enum Restructure { + + WHEN_NECESSARY, NEVER + } + + + /** + * Strategy class used to manage {@link Reference}s. This class can be overridden if + * alternative reference types need to be supported. + */ + protected class ReferenceManager { + + private final ReferenceQueue> queue = new ReferenceQueue>(); + + /** + * Factory method used to create a new {@link Reference}. + * @param entry the entry contained in the reference + * @param hash the hash + * @param next the next reference in the chain, or {@code null} if none + * @return a new {@link Reference} + */ + public Reference createReference(Entry entry, int hash, Reference next) { + if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { + return new WeakEntryReference(entry, hash, next, this.queue); + } + return new SoftEntryReference(entry, hash, next, this.queue); + } + + /** + * Return any reference that has been garbage collected and can be purged from the + * underlying structure or {@code null} if no references need purging. This + * method must be thread safe and ideally should not block when returning + * {@code null}. References should be returned once and only once. + * @return a reference to purge or {@code null} + */ + @SuppressWarnings("unchecked") + public Reference pollForPurge() { + return (Reference) this.queue.poll(); + } + } + + + /** + * Internal {@link Reference} implementation for {@link SoftReference}s. + */ + private static final class SoftEntryReference extends SoftReference> implements Reference { + + private final int hash; + + private final Reference nextReference; + + public SoftEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + clear(); + } + } + + + /** + * Internal {@link Reference} implementation for {@link WeakReference}s. + */ + private static final class WeakEntryReference extends WeakReference> implements Reference { + + private final int hash; + + private final Reference nextReference; + + public WeakEntryReference(Entry entry, int hash, Reference next, ReferenceQueue> queue) { + super(entry, queue); + this.hash = hash; + this.nextReference = next; + } + + @Override + public int getHash() { + return this.hash; + } + + @Override + public Reference getNext() { + return this.nextReference; + } + + @Override + public void release() { + enqueue(); + clear(); + } + } + +} + diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java index d07aed51..726fdb93 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/ReflectUtil.java @@ -29,7 +29,9 @@ import fun.asgc.neutrino.core.cache.MemoryCache; import fun.asgc.neutrino.core.type.TypeMatchLevel; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -464,4 +466,133 @@ public class ReflectUtil { } } } + + /** + * Invoke the specified {@link Method} against the supplied target object with no arguments. + * The target object can be {@code null} when invoking a static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @return the invocation result, if any + * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) + */ + public static Object invokeMethod(Method method, Object target) { + return invokeMethod(method, target, new Object[0]); + } + + /** + * Invoke the specified {@link Method} against the supplied target object with the + * supplied arguments. The target object can be {@code null} when invoking a + * static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @param args the invocation arguments (may be {@code null}) + * @return the invocation result, if any + */ + public static Object invokeMethod(Method method, Object target, Object... args) { + try { + return method.invoke(target, args); + } + catch (Exception ex) { + handleReflectionException(ex); + } + throw new IllegalStateException("Should never get here"); + } + + /** + * Handle the given reflection exception. Should only be called if no + * checked exception is expected to be thrown by the target method. + *

Throws the underlying RuntimeException or Error in case of an + * InvocationTargetException with such a root cause. Throws an + * IllegalStateException with an appropriate message or + * UndeclaredThrowableException otherwise. + * @param ex the reflection exception to handle + */ + public static void handleReflectionException(Exception ex) { + if (ex instanceof NoSuchMethodException) { + throw new IllegalStateException("Method not found: " + ex.getMessage()); + } + if (ex instanceof IllegalAccessException) { + throw new IllegalStateException("Could not access method: " + ex.getMessage()); + } + if (ex instanceof InvocationTargetException) { + handleInvocationTargetException((InvocationTargetException) ex); + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Handle the given invocation target exception. Should only be called if no + * checked exception is expected to be thrown by the target method. + *

Throws the underlying RuntimeException or Error in case of such a root + * cause. Throws an UndeclaredThrowableException otherwise. + * @param ex the invocation target exception to handle + */ + public static void handleInvocationTargetException(InvocationTargetException ex) { + rethrowRuntimeException(ex.getTargetException()); + } + + /** + * Rethrow the given {@link Throwable exception}, which is presumably the + * target exception of an {@link InvocationTargetException}. + * Should only be called if no checked exception is expected to be thrown + * by the target method. + *

Rethrows the underlying exception cast to a {@link RuntimeException} or + * {@link Error} if appropriate; otherwise, throws an + * {@link UndeclaredThrowableException}. + * @param ex the exception to rethrow + * @throws RuntimeException the rethrown exception + */ + public static void rethrowRuntimeException(Throwable ex) { + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and no parameters. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @return the Method object, or {@code null} if none found + */ + public static Method findMethod(Class clazz, String name) { + return findMethod(clazz, name, new Class[0]); + } + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and parameter types. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) + * @return the Method object, or {@code null} if none found + */ + public static Method findMethod(Class clazz, String name, Class... paramTypes) { + Assert.notNull(clazz, "Class must not be null"); + Assert.notNull(name, "Method name must not be null"); + Class searchType = clazz; + while (searchType != null) { + Set methods = (searchType.isInterface() ? Sets.newHashSet(searchType.getMethods()) : getDeclaredMethods(searchType)); + for (Method method : methods) { + if (name.equals(method.getName()) && + (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { + return method; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java new file mode 100644 index 00000000..45762356 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test1.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.type; + +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; + +/** + * @author: aoshiguchen + * @date: 2022/9/24 + */ +public class Test1 { + @Test + public void test() throws NoSuchFieldException { + Field param = GenericClazz.class.getDeclaredField("param"); + Type genericType = param.getGenericType(); + ParameterizedType type = (ParameterizedType) genericType; + Type[] typeArguments = type.getActualTypeArguments(); + System.out.println("从 HashMap> 中获取 String:" + typeArguments[0]); + System.out.println("从 HashMap> 中获取 List :" + typeArguments[1]); + System.out.println( + "从 HashMap> 中获取 List :" + ((ParameterizedType) typeArguments[1]).getRawType()); + System.out.println("从 HashMap> 中获取 Integer:" + ((ParameterizedType) typeArguments[1]) + .getActualTypeArguments()[0]); + System.out.println("从 HashMap> 中获取父类型:"+param.getType().getGenericSuperclass()); + } + + public static class GenericClazz { + private HashMap> param; + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java new file mode 100644 index 00000000..a1f29b16 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/type/Test2.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.type; + +import fun.asgc.neutrino.core.base.type.ResolvableType; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public class Test2 { + + @Test + public void test1() throws NoSuchFieldException { + ResolvableType param = ResolvableType.forField(GenericClazz.class.getDeclaredField("param")); + System.out.println("从 HashMap> 中获取 String:" + param.getGeneric(0).resolve()); + System.out.println("从 HashMap> 中获取 List :" + param.getGeneric(1)); + System.out.println( + "从 HashMap> 中获取 List :" + param.getGeneric(1).resolve()); + System.out.println("从 HashMap> 中获取 Integer:" + param.getGeneric(1,0)); + System.out.println("从 HashMap> 中获取父类型:" +param.getSuperType()); + } + + public static class GenericClazz { + private HashMap> param; + } +} From ed7e4ae849b0077902415cfa8241ee816bce269f Mon Sep 17 00:00:00 2001 From: aoshiguchen <1052045476@qq.com> Date: Sun, 25 Sep 2022 21:46:00 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0SecurityManager?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/security/SecurityManagerTest.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 neutrino-core/src/test/java/fun/asgc/neutrino/core/security/SecurityManagerTest.java diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/security/SecurityManagerTest.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/security/SecurityManagerTest.java new file mode 100644 index 00000000..a288942d --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/security/SecurityManagerTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.core.security; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.stream.Collectors; + +/** + * @author: aoshiguchen + * @date: 2022/9/25 + */ +public class SecurityManagerTest { + + public static void main(String[] args) { + // -Djava.security.manager -Djava.security.policy=/work/tmp/policy1.policy + System.out.println("SecurityManager: " + System.getSecurityManager()); + + try (BufferedReader br = new BufferedReader(new FileReader("/work/tmp/test.txt"))){ + System.out.println("content:\n" + br.lines().collect(Collectors.joining())); + } catch (IOException e) { + throw new RuntimeException(e); + } + + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + System.out.println(System.getProperty("file.encoding")); + return null; + } + }); +// System.out.println(System.getProperty("file.encoding")); + } + +} From a83f06339bdd8366aac10ea7c69eb845fd8e1729 Mon Sep 17 00:00:00 2001 From: zCans <1224895921@qq.com> Date: Sun, 25 Sep 2022 23:51:34 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- neutrino-proxy-admin/src/api/jobLog.js | 2 +- neutrino-proxy-admin/src/utils/request.js | 4 +- .../src/views/system/jobLog.vue | 28 +++++--- .../server/constant/AlarmStatusEnum.java | 41 ++++++++++++ .../server/controller/JobLogController.java | 54 +++++++++++++++ .../server/controller/req/JobLogListReq.java | 34 ++++++++++ .../server/controller/res/JobLogListRes.java | 65 +++++++++++++++++++ .../proxy/server/dal/JobLogMapper.java | 17 +++++ .../proxy/server/service/JobLogService.java | 17 +++++ 9 files changed, 250 insertions(+), 12 deletions(-) create mode 100644 neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/constant/AlarmStatusEnum.java create mode 100644 neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobLogController.java create mode 100644 neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/req/JobLogListReq.java create mode 100644 neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/res/JobLogListRes.java diff --git a/neutrino-proxy-admin/src/api/jobLog.js b/neutrino-proxy-admin/src/api/jobLog.js index 016785a9..4297c968 100644 --- a/neutrino-proxy-admin/src/api/jobLog.js +++ b/neutrino-proxy-admin/src/api/jobLog.js @@ -2,7 +2,7 @@ import request from '@/utils/request' export function fetchList(query) { return request({ - url: '/job-Log/page', + url: '/job-log/page', method: 'get', params: query }) diff --git a/neutrino-proxy-admin/src/utils/request.js b/neutrino-proxy-admin/src/utils/request.js index abaab545..2bc8a5f7 100644 --- a/neutrino-proxy-admin/src/utils/request.js +++ b/neutrino-proxy-admin/src/utils/request.js @@ -25,8 +25,8 @@ service.interceptors.request.use(config => { // respone interceptor service.interceptors.response.use( response => { - console.log('response', response) - console.log('router', this.router) + // console.log('response', response) + // console.log('router', this.router) const res = response.data if (res.code !== 0) { Message({ diff --git a/neutrino-proxy-admin/src/views/system/jobLog.vue b/neutrino-proxy-admin/src/views/system/jobLog.vue index a5a427aa..b1803f23 100644 --- a/neutrino-proxy-admin/src/views/system/jobLog.vue +++ b/neutrino-proxy-admin/src/views/system/jobLog.vue @@ -20,9 +20,9 @@ {{scope.row.param}} - + @@ -30,12 +30,12 @@ {{scope.row.msg}} - + - + @@ -77,20 +77,30 @@ export default { } }, filters: { + statusName(status) { + const statusMap = { + 0: '成功', + 1: '失败' + } + return statusMap[status] + }, statusFilter(status) { const statusMap = { - 1: 'success', - 2: 'danger' + 0: 'success', + 1: 'danger' } return statusMap[status] } }, created() { - // this.getList() + this.getList() }, activated() { - this.listQuery.jobId = this.$route.query.jobId - // this.getList() + if (this.$route.query.jobId) { + this.listQuery.jobId = this.$route.query.jobId + console.log(this.listQuery.jobId, this.$route.query.jobId) + this.getList() + } }, methods: { getList() { diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/constant/AlarmStatusEnum.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/constant/AlarmStatusEnum.java new file mode 100644 index 00000000..05d6aa62 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/constant/AlarmStatusEnum.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.proxy.server.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 启用状态枚举 + * @author: zCans + * @date: 2022/9/25 + */ +@Getter +@AllArgsConstructor +public enum AlarmStatusEnum { + WAIT(1, "待发送"), + SUCCESS(2, "发送成功"), + ERROR(3, "发送失败"); + + private Integer status; + private String desc; +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobLogController.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobLogController.java new file mode 100644 index 00000000..16f80d38 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobLogController.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.proxy.server.controller; + +import fun.asgc.neutrino.core.annotation.Autowired; +import fun.asgc.neutrino.core.annotation.NonIntercept; +import fun.asgc.neutrino.core.db.page.Page; +import fun.asgc.neutrino.core.db.page.PageQuery; +import fun.asgc.neutrino.core.web.annotation.*; +import fun.asgc.neutrino.proxy.server.controller.req.*; +import fun.asgc.neutrino.proxy.server.controller.res.*; +import fun.asgc.neutrino.proxy.server.service.JobLogService; +import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author: zCans + * @date: 2022/9/25 + */ +@Slf4j +@NonIntercept +@RequestMapping("job-log") +@RestController +public class JobLogController { + @Autowired + private JobLogService jobLogService; + + @GetMapping("page") + public Page page(PageQuery pageQuery, JobLogListReq req) { + ParamCheckUtil.checkNotNull(pageQuery, "pageQuery"); + return jobLogService.page(pageQuery, req); + } + +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/req/JobLogListReq.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/req/JobLogListReq.java new file mode 100644 index 00000000..274b44e2 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/req/JobLogListReq.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.proxy.server.controller.req; + +import lombok.Data; + +/** + * 端口映射列表请求 + * @author: zCans + * @date: 2022/9/12 + */ +@Data +public class JobLogListReq { + public Integer jobId; +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/res/JobLogListRes.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/res/JobLogListRes.java new file mode 100644 index 00000000..d350112f --- /dev/null +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/res/JobLogListRes.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2022 aoshiguchen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package fun.asgc.neutrino.proxy.server.controller.res; + +import lombok.Data; + +import java.util.Date; + +/** + * 调度日志列表响应 + * @author: zCans + * @date: 2022/9/12 + */ +@Data +public class JobLogListRes { + private Integer id; + /** + * job_id + */ + private Integer jobId; + /** + * 处理器 + */ + private String handler; + /** + * 任务参数 + */ + private String param; + /** + * code + */ + private Integer code; + /** + * msg + */ + private String msg; + /** + * 报警状态 + * {@link fun.asgc.neutrino.proxy.server.constant.AlarmStatusEnum} + */ + private Integer alarmStatus; + /** + * 创建时间 + */ + private Date createTime; +} diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobLogMapper.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobLogMapper.java index 2d9e6d5c..c661ccee 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobLogMapper.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/dal/JobLogMapper.java @@ -22,9 +22,17 @@ package fun.asgc.neutrino.proxy.server.dal; import fun.asgc.neutrino.core.annotation.Component; +import fun.asgc.neutrino.core.annotation.Param; import fun.asgc.neutrino.core.aop.Intercept; import fun.asgc.neutrino.core.db.annotation.Insert; +import fun.asgc.neutrino.core.db.annotation.ResultType; +import fun.asgc.neutrino.core.db.annotation.Select; import fun.asgc.neutrino.core.db.mapper.SqlMapper; +import fun.asgc.neutrino.core.db.page.Page; +import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq; +import fun.asgc.neutrino.proxy.server.controller.req.JobLogListReq; +import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes; +import fun.asgc.neutrino.proxy.server.controller.res.JobLogListRes; import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO; import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO; @@ -39,4 +47,13 @@ public interface JobLogMapper extends SqlMapper { @Insert("insert into job_log(`job_id`,`handler`,`param`,`code`,`msg`,`alarm_status`,`create_time`) values(:jobId,:handler,:param,:code,:msg,:alarmStatus,:createTime)") void add(JobLogDO jobLog); + + @ResultType(JobLogListRes.class) + @Select("select * from job_log order by create_time desc") + void page(Page page, JobLogListReq req); + + @ResultType(JobLogListRes.class) + @Select("select * from job_log where job_id = :jobId order by create_time desc") + void pageByJobId(Page page, JobLogListReq req); + } diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java index 3c9c536c..63d14c9b 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobLogService.java @@ -24,8 +24,14 @@ package fun.asgc.neutrino.proxy.server.service; import fun.asgc.neutrino.core.annotation.Autowired; import fun.asgc.neutrino.core.annotation.Component; import fun.asgc.neutrino.core.annotation.NonIntercept; +import fun.asgc.neutrino.core.db.page.Page; +import fun.asgc.neutrino.core.db.page.PageQuery; import fun.asgc.neutrino.core.quartz.IJobCallback; import fun.asgc.neutrino.core.quartz.JobInfo; +import fun.asgc.neutrino.proxy.server.controller.req.JobInfoListReq; +import fun.asgc.neutrino.proxy.server.controller.req.JobLogListReq; +import fun.asgc.neutrino.proxy.server.controller.res.JobInfoListRes; +import fun.asgc.neutrino.proxy.server.controller.res.JobLogListRes; import fun.asgc.neutrino.proxy.server.dal.JobLogMapper; import fun.asgc.neutrino.proxy.server.dal.entity.JobLogDO; import lombok.extern.slf4j.Slf4j; @@ -68,4 +74,15 @@ public class JobLogService implements IJobCallback { ); } + public Page page(PageQuery pageQuery, JobLogListReq req) { + Page page = Page.create(pageQuery); + + if(req.getJobId() != null){ + jobLogMapper.pageByJobId(page, req); + } else { + jobLogMapper.page(page, req); + } + return page; + } + } From 2c7f5522dbc37a592b013da62943b902e72a6549 Mon Sep 17 00:00:00 2001 From: zCans <1224895921@qq.com> Date: Mon, 26 Sep 2022 22:09:36 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E8=B0=83=E5=BA=A6=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B0=83=E5=BA=A6=E4=B8=8B=E6=8B=89=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../proxy/server/controller/JobInfoController.java | 8 ++++++++ .../neutrino/proxy/server/service/JobInfoService.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobInfoController.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobInfoController.java index fc219ffe..1ed25b21 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobInfoController.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/controller/JobInfoController.java @@ -29,10 +29,13 @@ import fun.asgc.neutrino.core.web.annotation.*; import fun.asgc.neutrino.proxy.server.base.rest.annotation.OnlyAdmin; import fun.asgc.neutrino.proxy.server.controller.req.*; import fun.asgc.neutrino.proxy.server.controller.res.*; +import fun.asgc.neutrino.proxy.server.dal.entity.JobInfoDO; import fun.asgc.neutrino.proxy.server.service.JobInfoService; import fun.asgc.neutrino.proxy.server.util.ParamCheckUtil; import lombok.extern.slf4j.Slf4j; +import java.util.List; + /** * * @author: aoshiguchen @@ -52,6 +55,11 @@ public class JobInfoController { return jobInfoService.page(pageQuery, req); } + @GetMapping("findList") + public List findList() { + return jobInfoService.findList(); + } + @OnlyAdmin @PostMapping("update/enable-status") public JobInfoUpdateEnableStatusRes updateEnableStatus(@RequestBody JobInfoUpdateEnableStatusReq req) { diff --git a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java index da1691b7..e2a0067f 100644 --- a/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java +++ b/neutrino-proxy-server/src/main/java/fun/asgc/neutrino/proxy/server/service/JobInfoService.java @@ -63,6 +63,11 @@ public class JobInfoService implements IJobSource { return page; } + public List findList() { + List jobInfoDOList = jobInfoMapper.findList(); + return jobInfoDOList; + } + public JobInfoUpdateEnableStatusRes updateEnableStatus(JobInfoUpdateEnableStatusReq req) { JobInfoDO jobInfoDO = jobInfoMapper.findById(req.getId()); ParamCheckUtil.checkNotNull(jobInfoDO, ExceptionConstant.JOB_INFO_NOT_EXIST); From 89204d23f3ffd0a0f33d0eac548ee9c8a64a3446 Mon Sep 17 00:00:00 2001 From: zCans <1224895921@qq.com> Date: Wed, 28 Sep 2022 23:10:06 +0800 Subject: [PATCH 12/28] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0=E6=8A=A5?= =?UTF-8?q?=E8=AD=A6=E7=8A=B6=E6=80=81=202=E3=80=81=E8=B0=83=E5=BA=A6?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=A2=9E=E5=8A=A0=E8=B0=83=E5=BA=A6=E7=AD=9B?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- neutrino-proxy-admin/src/api/jobInfo.js | 7 +++ .../src/views/system/jobLog.vue | 56 ++++++++++++++----- .../src/views/system/jobManager.vue | 6 +- .../server/constant/AlarmStatusEnum.java | 1 + .../proxy/server/service/JobLogService.java | 2 +- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/neutrino-proxy-admin/src/api/jobInfo.js b/neutrino-proxy-admin/src/api/jobInfo.js index 961935b9..570a0a31 100644 --- a/neutrino-proxy-admin/src/api/jobInfo.js +++ b/neutrino-proxy-admin/src/api/jobInfo.js @@ -34,3 +34,10 @@ export function updateJobInfo(data) { data: data }) } + +export function jobList() { + return request({ + url: '/job-info/findList', + method: 'get' + }) +} diff --git a/neutrino-proxy-admin/src/views/system/jobLog.vue b/neutrino-proxy-admin/src/views/system/jobLog.vue index b1803f23..94340f77 100644 --- a/neutrino-proxy-admin/src/views/system/jobLog.vue +++ b/neutrino-proxy-admin/src/views/system/jobLog.vue @@ -1,50 +1,49 @@