diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/annotation/Subscribe.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/annotation/Subscribe.java new file mode 100644 index 00000000..eb29afbb --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/annotation/Subscribe.java @@ -0,0 +1,18 @@ +package fun.asgc.neutrino.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author: aoshiguchen + * @date: 2022/10/10 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Subscribe { + boolean enable() default true; + String topic() default ""; + String[] tags() default {}; +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Channel.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Channel.java new file mode 100644 index 00000000..82d67ae2 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Channel.java @@ -0,0 +1,47 @@ +/** + * 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; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public interface Channel { + + /** + * 注册接收者 + * @param receiver 接收者 + */ + void registerReceiver(R receiver); + + /** + * 注销接收者 + * @param receiver 接收者 + */ + void unRegisterReceiver(R receiver); + + /** + * 发布消息 + * @param msg 消息 + */ + void publish(D msg); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/ChannelConnector.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/ChannelConnector.java new file mode 100644 index 00000000..e1068166 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/ChannelConnector.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.core.base; + +/** + * 该接口用于将多个channel连接,实现消息广播 + * 应用场景: + * 项目初期,为了快速实现功能,不考虑多节点,可能不会接入太多的第三方依赖,如:redis、rocketMQ等。 + * 但是要保证后期需要的时候能够快速接入,而不需要对现有逻辑做太大的改动。 + * + * 那么,前期你可以使用ApplicationEventChannel,来做业务解藕。 + * 当多次迭代后需要引入RocketMQ,则直接让ApplicationEventChannel连接RocketMQChannel,从而大大减少开发工作。 + * + * 该方案适用于异步、解耦、削峰,不适用与事务消息、延时消息. + * + * 需要注意的是,该连接是单向连接。 + * 如:A连接B,则经过A的消息会广播给B,而经过B的消息不会广播给A + * 如果需要,B也需要实现该接口,并且连接A + * + * @author: aoshiguchen + * @date: 2022/10/7 + */ +public interface ChannelConnector { + /** + * 连接channel + * @param channel channel + */ + void connectChannel(C channel); + + /** + * 断开连接channel + * @param channel channel + */ + void disconnectChannel(C channel); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Publisher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Publisher.java new file mode 100644 index 00000000..19eea0d1 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Publisher.java @@ -0,0 +1,46 @@ +/** + * 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; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public interface Publisher { + /** + * 发布消息 + * @param msg 消息 + */ + void publish(D msg); + + /** + * 绑定渠道 + * @param channel 渠道 + */ + void bindChannel(Ch channel); + + /** + * 解绑渠道 + * @param channel 渠道 + */ + void unbindChannel(Ch channel); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Receiver.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Receiver.java new file mode 100644 index 00000000..ff12f524 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/Receiver.java @@ -0,0 +1,35 @@ +/** + * 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; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public interface Receiver { + + /** + * 接收消息 + * @param msg + */ + void receive(D msg); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEvent.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEvent.java new file mode 100644 index 00000000..44a773de --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEvent.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.base.event; + +/** + * @author: aoshiguchen + * @date: 2022/9/28 + */ +public class ApplicationEvent implements Event { + + private D data; + private ApplicationEventContext context; + + public ApplicationEvent() { + this.context = new ApplicationEventContext(); + } + + @Override + public D data() { + return this.data; + } + + @Override + public ApplicationEventContext context() { + return this.context; + } + + public void setData(D data) { + this.data = data; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventChannel.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventChannel.java new file mode 100644 index 00000000..6506fb06 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventChannel.java @@ -0,0 +1,151 @@ +/** + * 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.event; + +import fun.asgc.neutrino.core.base.ChannelConnector; +import fun.asgc.neutrino.core.base.CustomThreadFactory; +import fun.asgc.neutrino.core.base.Dispatcher; +import fun.asgc.neutrino.core.util.Assert; +import fun.asgc.neutrino.core.util.CollectionUtil; +import fun.asgc.neutrino.core.util.StringUtil; +import fun.asgc.neutrino.core.web.AntPathMatcher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public class ApplicationEventChannel implements EventChannel,ApplicationEventReceiver,Dispatcher>>, ChannelConnector> { + private List> receiverList; + private ThreadPoolExecutor threadPoolExecutor; + private static final AntPathMatcher antPathMatcher = new AntPathMatcher(); + private List> channelList; + + public ApplicationEventChannel() { + this.receiverList = new ArrayList<>(); + this.channelList = new ArrayList<>(); + this.threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new CustomThreadFactory("ApplicationEventChannel")); + } + + @Override + public void registerReceiver(ApplicationEventReceiver receiver) { + Assert.notNull(receiver, "receiver不能为空!"); + if (this.receiverList.contains(receiver)) { + return; + } + this.receiverList.add(receiver); + } + + @Override + public void unRegisterReceiver(ApplicationEventReceiver receiver) { + Assert.notNull(receiver, "receiver不能为空!"); + if (!this.receiverList.contains(receiver)) { + return; + } + this.receiverList.remove(receiver); + } + + @Override + public void publish(ApplicationEvent msg) { + if (msg.context().channelList().contains(this)) { + return; + } + msg.context().channelList().add(this); + // 将消息推送给关注该channel的接受者 + this.receiverList.forEach(receiver -> { + threadPoolExecutor.submit(() -> { + if (match(msg, receiver)) { + receiver.receive(msg); + } + }); + }); + // 将消息广播到所有关联的channel中去 + this.channelList.forEach(channel -> channel.publish(msg)); + } + + @Override + public void connectChannel(ApplicationEventChannel channel) { + if (null == channel || this.channelList.contains(channel) || this == channel) { + return; + } + this.channelList.add(channel); + } + + @Override + public void disconnectChannel(ApplicationEventChannel channel) { + if (null == channel) { + return; + } + this.channelList.remove(channel); + } + + /** + * 判断指定消息和指定接受者是否匹配 + * @param msg 消息 + * @param receiver 接受者 + * @return 是否匹配 + */ + private boolean match(ApplicationEvent msg, ApplicationEventReceiver receiver) { + if (null == msg || null == receiver) { + return false; + } + return topicMatch(msg.context().topic(), receiver.getTopic()) && tagMatch(msg.context().tags(), receiver.getTags()); + } + + /** + * topic匹配起 + * @param eventTopic 事件主题 + * @param subscriptionTopic 订阅的主题 + * @return 是否匹配 + */ + private boolean topicMatch(String eventTopic, String subscriptionTopic) { + if (StringUtil.isEmpty(subscriptionTopic)) { + return true; + } + return antPathMatcher.match(subscriptionTopic, eventTopic == null ? "" : eventTopic); + } + + /** + * 标签匹配 + * @param eventTags 事件标签 + * @param subscriptionTags 关注的标签 + * @return 是否匹配 + */ + private boolean tagMatch(Set eventTags, Set subscriptionTags) { + if (CollectionUtil.isEmpty(subscriptionTags)) { + return true; + } + for (String tag : eventTags) { + if (subscriptionTags.contains(tag)) { + return true; + } + } + return false; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventContext.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventContext.java new file mode 100644 index 00000000..617a5206 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventContext.java @@ -0,0 +1,99 @@ +/** + * 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.event; + +import fun.asgc.neutrino.core.base.Channel; +import fun.asgc.neutrino.core.util.StringUtil; + +import java.util.*; + +/** + * @author: aoshiguchen + * @date: 2022/9/28 + */ +public class ApplicationEventContext implements EventContext { + + private String id; + private String topic; + private Set tags; + private Object source; + private Date happenTime; + private Map attachData = new HashMap<>(); + private List channelList; + + public ApplicationEventContext() { + this.id = StringUtil.genUUID(); + this.happenTime = new Date(); + this.channelList = new ArrayList<>(); + } + + @Override + public Map attachData() { + return attachData; + } + + @Override + public String id() { + return id; + } + + @Override + public Date happenTime() { + return happenTime; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public S source() { + return (S)source; + } + + @Override + public String topic() { + return topic; + } + + @Override + public Set tags() { + return tags; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public void setTags(Set tags) { + this.tags = tags; + } + + public void setSource(S source) { + this.source = source; + } + + @Override + public List channelList() { + return this.channelList; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventPublisher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventPublisher.java new file mode 100644 index 00000000..eed18869 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventPublisher.java @@ -0,0 +1,59 @@ +/** + * 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.event; + + +import fun.asgc.neutrino.core.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public class ApplicationEventPublisher implements EventPublisher,ApplicationEventChannel> { + private List> channelList; + + public ApplicationEventPublisher() { + this.channelList = new ArrayList<>(); + } + + @Override + public void publish(ApplicationEvent msg) { + this.channelList.forEach(channel -> channel.publish(msg)); + } + + @Override + public void bindChannel(ApplicationEventChannel channel) { + Assert.notNull(channel, "channel不能为空!"); + if (!this.channelList.contains(channel)) { + this.channelList.add(channel); + } + } + + @Override + public void unbindChannel(ApplicationEventChannel channel) { + Assert.notNull(channel, "channel不能为空!"); + this.channelList.remove(channel); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventReceiver.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventReceiver.java new file mode 100644 index 00000000..c0b93134 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/ApplicationEventReceiver.java @@ -0,0 +1,71 @@ +/** + * 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.event; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; + +import java.util.Set; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +@Slf4j +public class ApplicationEventReceiver implements EventReceiver> { + private String topic; + private Set tags; + + public ApplicationEventReceiver() { + + } + + public ApplicationEventReceiver(String topic) { + this.topic = topic; + } + + public ApplicationEventReceiver(String topic, Set tags) { + this.topic = topic; + this.tags = tags; + } + + @Override + public void receive(ApplicationEvent msg) { + log.debug("ApplicationEventReceiver receive {}", JSONObject.toJSONString(msg.data())); + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public void setTags(Set tags) { + this.tags = tags; + } + + public String getTopic() { + return topic; + } + + public Set getTags() { + return tags; + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/Event.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/Event.java new file mode 100644 index 00000000..75209edb --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/Event.java @@ -0,0 +1,40 @@ +/** + * 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.event; + +/** + * @author: aoshiguchen + * @date: 2022/9/28 + */ +public interface Event { + /** + * 获取数据 + * @return 数据 + */ + D data(); + + /** + * 获取事件上下文 + * @return 上下文 + */ + C context(); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventChannel.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventChannel.java new file mode 100644 index 00000000..cf73246c --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventChannel.java @@ -0,0 +1,33 @@ +/** + * 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.event; + +import fun.asgc.neutrino.core.base.Channel; +import fun.asgc.neutrino.core.base.Dispatcher; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public interface EventChannel, R extends EventReceiver, Dis extends Dispatcher> extends Channel { + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventContext.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventContext.java new file mode 100644 index 00000000..09cefb44 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventContext.java @@ -0,0 +1,79 @@ +/** + * 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.event; + +import fun.asgc.neutrino.core.base.Channel; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author: wen.y + * @date: 2022/9/28 + */ +public interface EventContext { + /** + * 获取事件源 + * @return 事件源 + */ + T source(); + /** + * 事件主题 + * 用于订阅一级过滤 + * @return 主题 + */ + String topic(); + + /** + * 事件标签 + * 用于订阅二级过滤 + * @return 标签 + */ + Set tags(); + /** + * 附加数据 + * @return 附加数据 + */ + Map attachData(); + + /** + * 事件ID + * @return 事件ID + */ + String id(); + + /** + * 事件发生的时间 + * @return 事件发生的时间 + */ + Date happenTime(); + + /** + * channel列表 + * 1、如果为空,说明该事件未经过channel + * 2、该list代表事件在channel中的广播顺序 + * @return channel列表 + */ + List channelList(); +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventPublisher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventPublisher.java new file mode 100644 index 00000000..595f16a6 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventPublisher.java @@ -0,0 +1,32 @@ +/** + * 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.event; + +import fun.asgc.neutrino.core.base.Publisher; + +/** + * @author: aoshiguchen + * @date: 2022/9/28 + */ +public interface EventPublisher,Ch extends EventChannel> extends Publisher { + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventReceiver.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventReceiver.java new file mode 100644 index 00000000..1db4b7d4 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/EventReceiver.java @@ -0,0 +1,33 @@ +/** + * 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.event; + + +import fun.asgc.neutrino.core.base.Receiver; + +/** + * @author: aoshiguchen + * @date: 2022/9/29 + */ +public interface EventReceiver> extends Receiver { + +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/SimpleApplicationEventManager.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/SimpleApplicationEventManager.java new file mode 100644 index 00000000..02689313 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/base/event/SimpleApplicationEventManager.java @@ -0,0 +1,81 @@ +/** + * 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.event; + +import java.util.Set; + +/** + * 简单的应用事件管理器 + * @author: wen.u + * @date: 2022/10/10 + */ +public class SimpleApplicationEventManager { + private ApplicationEventChannel channel; + private ApplicationEventPublisher publisher; + private Object source; + + public SimpleApplicationEventManager() { + this.channel = new ApplicationEventChannel<>(); + this.publisher = new ApplicationEventPublisher<>(); + this.publisher.bindChannel(channel); + } + + public SimpleApplicationEventManager(Object source) { + this.channel = new ApplicationEventChannel<>(); + this.publisher = new ApplicationEventPublisher<>(); + this.publisher.bindChannel(channel); + this.source = source; + } + + public void publish(D data) { + this.publish(null, null, data); + } + + public void publish(String topic, D data) { + this.publish(topic, null, data); + } + + public void publish(String topic, Set tags, D data) { + ApplicationEvent event = new ApplicationEvent<>(); + event.setData(data); + event.context().setSource(source); + event.context().setTopic(topic); + event.context().setTags(tags); + this.publisher.publish(event); + } + + public void registerReceiver(ApplicationEventReceiver receiver) { + this.channel.registerReceiver(receiver); + } + + public ApplicationEventChannel getChannel() { + return channel; + } + + public ApplicationEventPublisher getPublisher() { + return publisher; + } + + public Object getSource() { + return source; + } +} 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/bean/BeanWrapper.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/BeanWrapper.java index a7db4748..1163377f 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/BeanWrapper.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/BeanWrapper.java @@ -25,7 +25,10 @@ package fun.asgc.neutrino.core.bean; import fun.asgc.neutrino.core.annotation.*; import fun.asgc.neutrino.core.context.ApplicationRunner; import fun.asgc.neutrino.core.context.LifeCycle; -import fun.asgc.neutrino.core.util.*; +import fun.asgc.neutrino.core.util.ClassUtil; +import fun.asgc.neutrino.core.util.CollectionUtil; +import fun.asgc.neutrino.core.util.LockUtil; +import fun.asgc.neutrino.core.util.ReflectUtil; import lombok.Data; import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/AbstractBeanFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/AbstractBeanFactory.java index 29f10d19..857b5676 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/AbstractBeanFactory.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/AbstractBeanFactory.java @@ -21,7 +21,10 @@ */ package fun.asgc.neutrino.core.bean.factory; +import fun.asgc.neutrino.core.annotation.Subscribe; import fun.asgc.neutrino.core.base.CustomThreadFactory; +import fun.asgc.neutrino.core.base.event.ApplicationEventReceiver; +import fun.asgc.neutrino.core.base.event.SimpleApplicationEventManager; import fun.asgc.neutrino.core.bean.*; import fun.asgc.neutrino.core.context.Environment; import fun.asgc.neutrino.core.context.LifeCycle; @@ -31,12 +34,13 @@ import fun.asgc.neutrino.core.exception.BeanException; import fun.asgc.neutrino.core.util.*; import lombok.extern.slf4j.Slf4j; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 抽象的bean工厂 @@ -371,7 +375,32 @@ public abstract class AbstractBeanFactory implements BeanFactory, BeanRegistry, parent.init(); } if (CollectionUtil.notEmpty(beanCache)) { - beanCache.values().stream().filter(b -> BeanStatus.INJECT == b.getStatus()).forEach(bean -> bean.init()); + beanCache.values().stream().forEach(bean -> { + SimpleApplicationEventManager defaultApplicationEventManager = getEnvironment().getDefaultApplicationEventManager(); + if (null != defaultApplicationEventManager && ApplicationEventReceiver.class.isAssignableFrom(bean.getType())) { + boolean enable = true; + String topic = null; + Set tags = null; + Subscribe subscribe = bean.getType().getAnnotation(Subscribe.class); + if (null != subscribe) { + enable = subscribe.enable(); + topic = subscribe.topic(); + if (ArrayUtil.notEmpty(subscribe.tags())) { + tags = Stream.of(subscribe.tags()).collect(Collectors.toSet()); + } + } + if (enable) { + ApplicationEventReceiver receiver = (ApplicationEventReceiver) bean.getInstance(); + receiver.setTopic(topic); + receiver.setTags(tags); + defaultApplicationEventManager.registerReceiver(receiver); + } + } + + if (BeanStatus.INJECT == bean.getStatus()) { + bean.init(); + } + }); } log.info("bean工厂[{}]初始化.", getName()); scheduledExecutor.scheduleWithFixedDelay(this::run, 0, 1, TimeUnit.SECONDS); diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/SimpleBeanFactory.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/SimpleBeanFactory.java index 17f4b009..8d241285 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/SimpleBeanFactory.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/bean/factory/SimpleBeanFactory.java @@ -28,6 +28,7 @@ import fun.asgc.neutrino.core.aop.interceptor.ExceptionHandler; import fun.asgc.neutrino.core.aop.interceptor.Filter; import fun.asgc.neutrino.core.aop.interceptor.Interceptor; import fun.asgc.neutrino.core.aop.interceptor.ResultAdvice; +import fun.asgc.neutrino.core.base.event.ApplicationEventReceiver; import fun.asgc.neutrino.core.bean.*; import fun.asgc.neutrino.core.exception.BeanException; import fun.asgc.neutrino.core.context.ApplicationRunner; @@ -374,6 +375,7 @@ public class SimpleBeanFactory extends AbstractBeanFactory { || Filter.class.isAssignableFrom(item) || ExceptionHandler.class.isAssignableFrom(item) || ResultAdvice.class.isAssignableFrom(item) + || ApplicationEventReceiver.class.isAssignableFrom(item) ) .forEach(clazz -> { String beanName = TypeUtil.getDefaultVariableName(clazz); diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/AppLifeCycleStatusEnum.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/AppLifeCycleStatusEnum.java new file mode 100644 index 00000000..cae4d241 --- /dev/null +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/AppLifeCycleStatusEnum.java @@ -0,0 +1,60 @@ +/** + * 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.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 应用生命周期状态枚举 + * @author: aoshiguchen + * @date: 2022/10/10 + */ +@AllArgsConstructor +@Getter +public enum AppLifeCycleStatusEnum { + // 应用已创建 + APP_CREATE(100, "已创建"), + // 应用已初始化 + APP_INIT(200, "配置初始化"), + // 容器已初始化 + CONTAINER_INIT(300, "容器初始化"), + // 应用已启动完成 + APP_STARTUP(400, "应用启动"), + // 应用准备销毁 + APP_PRE_DESTROY(500, "应用准备销毁"), + // 应用已销毁 + APP_DESTROY(600, "应用已销毁"); + + private Integer status; + private String desc; + private static Map CACHE = Stream.of(AppLifeCycleStatusEnum.values()).collect(Collectors.toMap(AppLifeCycleStatusEnum::getStatus, Function.identity())); + + public static AppLifeCycleStatusEnum of(Integer status) { + return CACHE.get(status); + } +} diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/MetaDataConstant.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/MetaDataConstant.java index 948bab82..f61c7b27 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/MetaDataConstant.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/constant/MetaDataConstant.java @@ -80,4 +80,8 @@ public interface MetaDataConstant { * 服务版本 */ String SERVER_VS = "Neutrino-1.0"; + /** + * app生命周期主题 + */ + String TOPIC_APP_LIFE_CYCLE = "TP_APP_LIFE_CYCLE"; } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/Environment.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/Environment.java index 81dd8e65..8776c264 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/Environment.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/Environment.java @@ -22,6 +22,7 @@ package fun.asgc.neutrino.core.context; +import fun.asgc.neutrino.core.base.event.SimpleApplicationEventManager; import fun.asgc.neutrino.core.util.SystemUtil; import lombok.Data; import lombok.experimental.Accessors; @@ -64,4 +65,8 @@ public class Environment { * 运行上下文 */ private SystemUtil.RunContext runContext; + /** + * 默认的应用事件管理器 + */ + private SimpleApplicationEventManager defaultApplicationEventManager; } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/NeutrinoLauncher.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/NeutrinoLauncher.java index 0a37e1dc..ba35e6bb 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/NeutrinoLauncher.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/context/NeutrinoLauncher.java @@ -25,6 +25,8 @@ package fun.asgc.neutrino.core.context; import com.google.common.collect.Lists; import fun.asgc.neutrino.core.annotation.EnableJob; import fun.asgc.neutrino.core.annotation.NeutrinoApplication; +import fun.asgc.neutrino.core.base.event.SimpleApplicationEventManager; +import fun.asgc.neutrino.core.constant.AppLifeCycleStatusEnum; import fun.asgc.neutrino.core.constant.MetaDataConstant; import fun.asgc.neutrino.core.util.*; import lombok.extern.slf4j.Slf4j; @@ -52,26 +54,44 @@ public class NeutrinoLauncher { private NeutrinoLauncher(Class clazz, String[] args) { this.environment = new Environment() .setMainClass(clazz) - .setMainArgs(args); + .setMainArgs(args) + .setDefaultApplicationEventManager(new SimpleApplicationEventManager(this)) + ; } private SystemUtil.RunContext launch() { + // 已创建 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.APP_CREATE); + StopWatch stopWatch = new StopWatch(); stopWatch.start(); - environmentInit(); + + // 应用已初始化 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.APP_INIT); + ApplicationContext context = new ApplicationContext(environment); SystemUtil.RunContext runContext = SystemUtil.waitProcessDestroy(() -> { + // 应用准备销毁 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.APP_PRE_DESTROY); context.destroy(); log.info("Application already stop."); + // 应用已销毁 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.APP_DESTROY); }); environment.setRunContext(runContext); context.run(); + // 容器已初始化 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.CONTAINER_INIT); + stopWatch.stop(); printLog(environment, stopWatch); + // 应用已启动完成 + publishAppLifeCycleEvent(AppLifeCycleStatusEnum.APP_STARTUP); + return runContext; } @@ -133,4 +153,12 @@ public class NeutrinoLauncher { } log.info(environment.getBanner()); } + + /** + * 发布应用生命周期事件 + * @param appLifeCycleStatusEnum 应用生命周期事件 + */ + private void publishAppLifeCycleEvent(AppLifeCycleStatusEnum appLifeCycleStatusEnum) { + this.environment.getDefaultApplicationEventManager().publish(MetaDataConstant.TOPIC_APP_LIFE_CYCLE, appLifeCycleStatusEnum); + } } 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..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)) { @@ -112,7 +119,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 +135,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 +199,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/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/main/java/fun/asgc/neutrino/core/util/StringUtil.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/StringUtil.java index 16da2728..d2dd409b 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/StringUtil.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/util/StringUtil.java @@ -1107,4 +1107,11 @@ public class StringUtil { return arrayToDelimitedString(arr, ","); } + /** + * 生成UUID + * @return + */ + public static String genUUID() { + return UUID.randomUUID().toString().replace("-", "").toUpperCase(); + } } diff --git a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/router/DefaultHttpRouter.java b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/router/DefaultHttpRouter.java index 95f14886..7d63ce47 100644 --- a/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/router/DefaultHttpRouter.java +++ b/neutrino-core/src/main/java/fun/asgc/neutrino/core/web/router/DefaultHttpRouter.java @@ -234,6 +234,7 @@ public class DefaultHttpRouter implements HttpRouter { if (null != staticResource && CollectionUtil.notEmpty(staticResource.getLocations())) { for (String location : staticResource.getLocations()) { String path = location + httpRouteParam.getUrl(); + path = path.replaceAll("//", "/"); if (path.endsWith("/")) { path += "index.html"; } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test1.java new file mode 100644 index 00000000..248951f1 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test1.java @@ -0,0 +1,81 @@ +/** + * 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.event; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Sets; +import fun.asgc.neutrino.core.util.SystemUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +/** + * 应用事件测试 + * 1、异步执行 + * 2、业务解耦 + * 3、topic订阅 + * 4、支持多种模式无缝切换(本地模式、redis模式、rocketMQ模式、MQTT模式等) + * 5、不支持事务消息 + * @author: aoshiguchen + * @date: 2022/10/3 + */ +@Slf4j +public class Test1 { + private ApplicationEventChannel channel = new ApplicationEventChannel<>(); + private ApplicationEventPublisher publisher = new ApplicationEventPublisher<>(); + + { + publisher.bindChannel(channel); + } + + @Test + public void test1() { + ApplicationEventReceiver receiver = new ApplicationEventReceiver() { + @Override + public void receive(ApplicationEvent msg) { + log.info("data:{}", JSONObject.toJSONString(msg.data())); + } + }; +// receiver.setTopic("/**"); +// receiver.setTags(Sets.newHashSet("create")); + channel.registerReceiver(receiver); + + ApplicationEvent event = new ApplicationEvent<>(); +// event.context().setId("123"); + event.context().setTopic("/student/create"); + event.context().setTags(Sets.newHashSet("create")); + event.setData(new Student().setId("1").setName("张三").setAge(28).setSex("男")); + publisher.publish(event); + + SystemUtil.waitProcessDestroy().sync(); + } + + @Accessors(chain = true) + @Data + public static class Student { + private String id; + private String name; + private Integer age; + private String sex; + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test2.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test2.java new file mode 100644 index 00000000..d93e8d68 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test2.java @@ -0,0 +1,94 @@ +/** + * 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.event; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Sets; +import fun.asgc.neutrino.core.util.SystemUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +/** + * 应用事件测试 + * 1、异步执行 + * 2、业务解耦 + * 3、topic订阅 + * 4、支持多种模式无缝切换(本地模式、redis模式、rocketMQ模式、MQTT模式等) + * 5、不支持事务消息 + * @author: aoshiguchen + * @date: 2022/10/3 + */ +@Slf4j +public class Test2 { + private ApplicationEventChannel channel = new ApplicationEventChannel<>(); + private MyTestChannel myTestChannel = new MyTestChannel(); + private ApplicationEventPublisher publisher = new ApplicationEventPublisher<>(); + + { + channel.connectChannel(myTestChannel); + publisher.bindChannel(channel); + } + + @Test + public void test1() { + ApplicationEventReceiver receiver1 = new ApplicationEventReceiver() { + @Override + public void receive(ApplicationEvent msg) { + log.info("receiver1 data:{}", JSONObject.toJSONString(msg.data())); + } + }; + ApplicationEventReceiver receiver2 = new ApplicationEventReceiver() { + @Override + public void receive(ApplicationEvent msg) { + log.info("receiver2 data:{}", JSONObject.toJSONString(msg.data())); + } + }; +// receiver.setTopic("/**"); +// receiver.setTags(Sets.newHashSet("create")); + channel.registerReceiver(receiver1); + myTestChannel.registerReceiver(receiver2); + + ApplicationEvent event = new ApplicationEvent<>(); +// event.context().setId("123"); + event.context().setTopic("/student/create"); + event.context().setTags(Sets.newHashSet("create")); + event.setData(new Student().setId("1").setName("张三").setAge(28).setSex("男")); + publisher.publish(event); + + SystemUtil.waitProcessDestroy().sync(); + } + + @Accessors(chain = true) + @Data + public static class Student { + private String id; + private String name; + private Integer age; + private String sex; + } + + public static class MyTestChannel extends ApplicationEventChannel { + + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test3.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test3.java new file mode 100644 index 00000000..7ca0dd9b --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/event/Test3.java @@ -0,0 +1,83 @@ +/** + * 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.event; + +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Sets; +import fun.asgc.neutrino.core.util.SystemUtil; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +/** + * 应用事件测试 + * 1、异步执行 + * 2、业务解耦 + * 3、topic订阅 + * 4、支持多种模式无缝切换(本地模式、redis模式、rocketMQ模式、MQTT模式等) + * 5、不支持事务消息 + * @author: aoshiguchen + * @date: 2022/10/3 + */ +@Slf4j +public class Test3 { + private SimpleApplicationEventManager simpleApplicationEventManager = new SimpleApplicationEventManager<>(this); + + @Test + public void test1() { + ApplicationEventReceiver receiver1 = new ApplicationEventReceiver() { + @Override + public void receive(ApplicationEvent msg) { + log.info("receiver1 data:{}", JSONObject.toJSONString(msg.data())); + } + }; + receiver1.setTopic("/*"); + receiver1.setTags(Sets.newHashSet("tag1")); + ApplicationEventReceiver receiver2 = new ApplicationEventReceiver() { + @Override + public void receive(ApplicationEvent msg) { + log.info("receiver2 data:{}", JSONObject.toJSONString(msg.data())); + } + }; + receiver2.setTags(Sets.newHashSet("tag2")); + simpleApplicationEventManager.registerReceiver(receiver1); + simpleApplicationEventManager.registerReceiver(receiver2); + + simpleApplicationEventManager.publish("/aaa", Sets.newHashSet("tag1"), new Student().setId("1").setName("张三").setAge(28).setSex("男")); + + SystemUtil.waitProcessDestroy().sync(); + } + + @Accessors(chain = true) + @Data + public static class Student { + private String id; + private String name; + private Integer age; + private String sex; + } + + public static class MyTestChannel extends ApplicationEventChannel { + + } +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/type/Test1.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/type/Test1.java new file mode 100644 index 00000000..f3c7b9da --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/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.base.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/base/type/Test2.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/type/Test2.java new file mode 100644 index 00000000..27a58f51 --- /dev/null +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/base/type/Test2.java @@ -0,0 +1,49 @@ +/** + * 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 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; + } +} 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; } } } 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-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")); + } + +} diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/util/ClassUtilTest.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/util/ClassUtilTest.java index ce0a5b92..bc088ff9 100644 --- a/neutrino-core/src/test/java/fun/asgc/neutrino/core/util/ClassUtilTest.java +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/util/ClassUtilTest.java @@ -80,5 +80,4 @@ public class ClassUtilTest { Set> c = ClassUtil.scan("fun.asgc.neutrino.proxy.server", url); System.out.println(c); } - } diff --git a/neutrino-core/src/test/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistryTest.java b/neutrino-core/src/test/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistryTest.java index 3c701d66..86e14f31 100644 --- a/neutrino-core/src/test/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistryTest.java +++ b/neutrino-core/src/test/java/fun/asgc/neutrino/core/web/interceptor/InterceptorRegistryTest.java @@ -49,8 +49,6 @@ public class InterceptorRegistryTest { System.out.println(antPathMatcher.match("**/**.html", "/a/11/22/33/a.html")); System.out.println(antPathMatcher.match("/**/*.html", "/a/11/22/33/a.html")); System.out.println(antPathMatcher.match("/**/*.html", "/a.html")); - - } } 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/api/jobLog.js b/neutrino-proxy-admin/src/api/jobLog.js new file mode 100644 index 00000000..4297c968 --- /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/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 new file mode 100644 index 00000000..94340f77 --- /dev/null +++ b/neutrino-proxy-admin/src/views/system/jobLog.vue @@ -0,0 +1,157 @@ + + + diff --git a/neutrino-proxy-admin/src/views/system/jobManager.vue b/neutrino-proxy-admin/src/views/system/jobManager.vue index 94a4fb53..1521f03d 100644 --- a/neutrino-proxy-admin/src/views/system/jobManager.vue +++ b/neutrino-proxy-admin/src/views/system/jobManager.vue @@ -5,11 +5,7 @@ - - - + + + +