scopes;
46 |
47 | /**
48 | * 依赖重定向
49 | *
50 | *
51 | * relocate = ["!taboolib.", "!taboolib610."] // 同 test 参数
52 | *
53 | */
54 | private final List relocate;
55 |
56 | /**
57 | * 是否外部库(不会被扫到)
58 | */
59 | private final boolean external;
60 |
61 | public ParsedDependency(String value, String test, String repository, boolean transitive, boolean ignoreOptional, boolean ignoreException, List scopes, List relocate, boolean external) {
62 | this.value = value;
63 | this.test = test;
64 | this.repository = repository;
65 | this.transitive = transitive;
66 | this.ignoreOptional = ignoreOptional;
67 | this.ignoreException = ignoreException;
68 | this.scopes = scopes;
69 | this.relocate = relocate;
70 | this.external = external;
71 | }
72 |
73 | public String value() {
74 | return value;
75 | }
76 |
77 | public String test() {
78 | return test;
79 | }
80 |
81 | public String repository() {
82 | return repository;
83 | }
84 |
85 | public boolean transitive() {
86 | return transitive;
87 | }
88 |
89 | public boolean ignoreOptional() {
90 | return ignoreOptional;
91 | }
92 |
93 | public boolean ignoreException() {
94 | return ignoreException;
95 | }
96 |
97 | public List scopes() {
98 | return scopes;
99 | }
100 |
101 | public List relocate() {
102 | return relocate;
103 | }
104 |
105 | public boolean external() {
106 | return external;
107 | }
108 |
109 | @Override
110 | public String toString() {
111 | return "ParsedDependency{" +
112 | "value='" + value + '\'' +
113 | ", test='" + test + '\'' +
114 | ", repository='" + repository + '\'' +
115 | ", transitive=" + transitive +
116 | ", ignoreOptional=" + ignoreOptional +
117 | ", ignoreException=" + ignoreException +
118 | ", scopes=" + scopes +
119 | ", relocate=" + relocate +
120 | ", external=" + external +
121 | '}';
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeDependencies.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.TYPE)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface RuntimeDependencies {
11 |
12 | RuntimeDependency[] value();
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeDependency.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies;
2 |
3 | import java.lang.annotation.*;
4 |
5 | @Target(ElementType.TYPE)
6 | @Retention(RetentionPolicy.RUNTIME)
7 | @Repeatable(RuntimeDependencies.class)
8 | public @interface RuntimeDependency {
9 |
10 | /**
11 | * 依赖地址,格式为:
12 | * :[:[:]]:
13 | */
14 | String value();
15 |
16 | /**
17 | * 测试类
18 | *
19 | *
20 | * test = "!org.bukkit.Bukkit" // 前面带个感叹号避免在编译时重定向
21 | *
22 | */
23 | String test() default "";
24 |
25 | /**
26 | * 仓库地址,留空默认使用 阿里云中央仓库
27 | */
28 | String repository() default "";
29 |
30 | /**
31 | * 是否进行依赖传递
32 | */
33 | boolean transitive() default true;
34 |
35 | /**
36 | * 忽略可选依赖
37 | */
38 | boolean ignoreOptional() default true;
39 |
40 | /**
41 | * 忽略加载异常
42 | */
43 | boolean ignoreException() default false;
44 |
45 | /**
46 | * 依赖范围
47 | */
48 | DependencyScope[] scopes() default {DependencyScope.RUNTIME, DependencyScope.COMPILE};
49 |
50 | /**
51 | * 依赖重定向
52 | *
53 | *
54 | * relocate = ["!taboolib.", "!taboolib610."] // 同 test 参数
55 | *
56 | */
57 | String[] relocate() default {};
58 |
59 | /**
60 | * 是否外部库(不会被扫到)
61 | */
62 | boolean external() default true;
63 | }
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeEnv.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.io.File;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import static me.zhenxin.zmusic.ZMusicConstants.KOTLIN_VERSION;
10 |
11 | /**
12 | * TabooLib
13 | * taboolib.common.env.RuntimeEnv
14 | *
15 | * @author sky
16 | * @since 2021/6/15 6:23 下午
17 | */
18 | public class RuntimeEnv {
19 | public static final RuntimeEnv ENV = new RuntimeEnv();
20 | public static final RuntimeEnvDependency ENV_DEPENDENCY = new RuntimeEnvDependency();
21 |
22 | public void runtimeInit(String dataFolder) {
23 | // 数据目录是否存在
24 | File dataFolderFile = new File(dataFolder);
25 | if (!dataFolderFile.exists()) {
26 | //noinspection ResultOfMethodCallIgnored
27 | dataFolderFile.mkdirs();
28 | }
29 | // 设置默认库路径
30 | String defaultLibrary = ENV_DEPENDENCY.getDefaultLibrary();
31 | ENV_DEPENDENCY.setDefaultLibrary(dataFolder + "/" + defaultLibrary);
32 |
33 | // 加载 Kotlin 环境
34 | try {
35 | List relocations = new ArrayList<>();
36 | // Kotlin Relocation
37 | String kotlinId = "!kotlin".substring(1);
38 | String kotlinRelocationId = "!me.zhenxin.zmusic.library.kotlin".substring(1);
39 | relocations.add(new JarRelocation(kotlinId + ".", kotlinRelocationId + "."));
40 | // Kotlin Dependency
41 | String kotlinStdlib = "org.jetbrains.kotlin:kotlin-stdlib:" + KOTLIN_VERSION;
42 | String kotlinStdlibJdk8 = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" + KOTLIN_VERSION;
43 | ENV_DEPENDENCY.loadDependency(kotlinStdlib, false, relocations);
44 | ENV_DEPENDENCY.loadDependency(kotlinStdlibJdk8, false, relocations);
45 | } catch (Throwable e) {
46 | throw new RuntimeException(e);
47 | }
48 | }
49 |
50 | public void inject(@NotNull Class> clazz) throws Throwable {
51 | ENV_DEPENDENCY.loadDependency(clazz);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/RuntimeEnvDependency.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies;
2 |
3 | import me.zhenxin.zmusic.dependencies.aether.AetherResolver;
4 | import me.zhenxin.zmusic.dependencies.common.ClassAppender;
5 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO;
6 | import me.zhenxin.zmusic.dependencies.legacy.Artifact;
7 | import me.zhenxin.zmusic.dependencies.legacy.Dependency;
8 | import me.zhenxin.zmusic.dependencies.legacy.DependencyDownloader;
9 | import me.zhenxin.zmusic.dependencies.legacy.Repository;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.io.File;
14 | import java.net.URL;
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.Collections;
18 | import java.util.List;
19 |
20 |
21 | public class RuntimeEnvDependency {
22 |
23 | private static boolean isAetherFound;
24 |
25 | static {
26 | // 当服务端版本在 1.17+ 时,可借助服务端自带的 Aether 库完成依赖下载,兼容性更高。
27 | // 同时停止对 Legacy 的支持。
28 | try {
29 | Class.forName("org.eclipse.aether.graph.Dependency");
30 | isAetherFound = true;
31 | } catch (ClassNotFoundException e) {
32 | isAetherFound = false;
33 | }
34 | // Mohist 直接不用 Aether
35 | try {
36 | Class.forName("com.mohistmc.MohistMC");
37 | isAetherFound = false;
38 | } catch (ClassNotFoundException ignored) {
39 | }
40 | }
41 |
42 | private String defaultLibrary = "libraries";
43 |
44 | public String getDefaultLibrary() {
45 | return defaultLibrary;
46 | }
47 |
48 | public void setDefaultLibrary(String library) {
49 | this.defaultLibrary = library;
50 | }
51 |
52 | public ParsedDependency parseDependency(RuntimeDependency dependency) {
53 | String value = dependency.value();
54 | String test = dependency.test();
55 | String repository = dependency.repository();
56 | boolean transitive = dependency.transitive();
57 | boolean ignoreOptional = dependency.ignoreOptional();
58 | boolean ignoreException = dependency.ignoreException();
59 | boolean external = dependency.external();
60 | List scopes = new ArrayList<>(Arrays.asList(dependency.scopes()));
61 | List relocate = new ArrayList<>(Arrays.asList(dependency.relocate()));
62 | return new ParsedDependency(value, test, repository, transitive, ignoreOptional, ignoreException, scopes, relocate, external);
63 | }
64 |
65 | public List getDependency(@NotNull Class> clazz) {
66 | List dependencyList = new ArrayList<>();
67 | RuntimeDependency[] dependencies = null;
68 | if (clazz.isAnnotationPresent(RuntimeDependency.class)) {
69 | dependencies = clazz.getAnnotationsByType(RuntimeDependency.class);
70 | } else {
71 | RuntimeDependencies annotation = clazz.getAnnotation(RuntimeDependencies.class);
72 | if (annotation != null) {
73 | dependencies = annotation.value();
74 | }
75 | }
76 | if (dependencies != null) {
77 | for (RuntimeDependency dependency : dependencies) {
78 | ParsedDependency parsedDependency = parseDependency(dependency);
79 | if (parsedDependency != null) {
80 | dependencyList.add(parsedDependency);
81 | }
82 | }
83 | return dependencyList;
84 | } else {
85 | return Collections.emptyList();
86 | }
87 | }
88 |
89 | public void loadDependency(@NotNull Class> clazz) throws Throwable {
90 | List dependencies = getDependency(clazz);
91 | if (dependencies != null) {
92 | File baseFile = new File(defaultLibrary);
93 | for (ParsedDependency dep : dependencies) {
94 | String allTest = dep.test();
95 | List tests = new ArrayList<>();
96 | if (allTest.contains(",")) {
97 | tests.addAll(Arrays.asList(allTest.split(",")));
98 | } else {
99 | tests.add(allTest);
100 | }
101 | if (!tests.isEmpty() && tests.stream().allMatch(this::test)) {
102 | continue;
103 | }
104 | List relocation = new ArrayList<>();
105 | List relocate = dep.relocate();
106 | if (relocate.size() % 2 != 0) {
107 | throw new IllegalStateException("invalid relocate format");
108 | }
109 | for (int i = 0; i + 1 < relocate.size(); i += 2) {
110 | String from = relocate.get(i);
111 | String to = relocate.get(i + 1);
112 | // 移除前缀
113 | if (from.startsWith("!")) {
114 | from = from.substring(1);
115 | }
116 | if (to.startsWith("!")) {
117 | to = to.substring(1);
118 | }
119 | relocation.add(new JarRelocation(from, to));
120 | }
121 | String url = dep.value().startsWith("!") ? dep.value().substring(1) : dep.value();
122 | loadDependency(url, baseFile, relocation, dep.repository(), dep.ignoreOptional(), dep.ignoreException(), dep.transitive(), dep.scopes(), dep.external());
123 | }
124 | }
125 | }
126 |
127 | public void loadDependency(@NotNull String url) throws Throwable {
128 | loadDependency(url, new File(defaultLibrary));
129 | }
130 |
131 | public void loadDependency(@NotNull String url, @Nullable String repository) throws Throwable {
132 | loadDependency(url, new File(defaultLibrary), repository);
133 | }
134 |
135 | public void loadDependency(@NotNull String url, @NotNull List relocation) throws Throwable {
136 | loadDependency(url, new File(defaultLibrary), relocation, null, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE));
137 | }
138 |
139 | public void loadDependency(@NotNull String url, @NotNull List relocation, @Nullable String repository) throws Throwable {
140 | loadDependency(url, new File(defaultLibrary), relocation, repository, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE));
141 | }
142 |
143 | public void loadDependency(@NotNull String url, boolean transitive, @NotNull List relocation) throws Throwable {
144 | loadDependency(url, new File(defaultLibrary), relocation, null, true, false, transitive, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE));
145 | }
146 |
147 | public void loadDependency(@NotNull String url, boolean transitive, @NotNull List relocation, @Nullable String repository) throws Throwable {
148 | loadDependency(url, new File(defaultLibrary), relocation, repository, true, false, transitive, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE));
149 | }
150 |
151 | public void loadDependency(@NotNull String url, @NotNull File baseDir) throws Throwable {
152 | loadDependency(url, baseDir, null);
153 | }
154 |
155 | public void loadDependency(@NotNull String url, @NotNull File baseDir, @Nullable String repository) throws Throwable {
156 | loadDependency(url, baseDir, new ArrayList<>(), repository, true, false, true, Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE));
157 | }
158 |
159 | public void loadDependency(
160 | @NotNull String url,
161 | @NotNull File baseDir,
162 | @NotNull List relocation,
163 | @Nullable String repository,
164 | boolean ignoreOptional,
165 | boolean ignoreException,
166 | boolean transitive,
167 | @NotNull List scope
168 | ) throws Throwable {
169 | loadDependency(url, baseDir, relocation, repository, ignoreOptional, ignoreException, transitive, scope, true);
170 | }
171 |
172 | public void loadDependency(
173 | @NotNull String url,
174 | @NotNull File baseDir,
175 | @NotNull List relocation,
176 | @Nullable String repository,
177 | boolean ignoreOptional,
178 | boolean ignoreException,
179 | boolean transitive,
180 | @NotNull List scope,
181 | boolean external
182 | ) throws Throwable {
183 | if (repository == null || repository.isEmpty()) {
184 | repository = "https://maven.aliyun.com/repository/central";
185 | }
186 | // 使用 Aether 处理依赖
187 | if (isAetherFound) {
188 | AetherResolver.of(repository).resolve(url, scope, transitive, ignoreOptional).forEach(file -> {
189 | try {
190 | AetherResolver.inject(file, relocation, external);
191 | } catch (Throwable ex) {
192 | if (!ignoreException) {
193 | //noinspection CallToPrintStackTrace
194 | ex.printStackTrace();
195 | }
196 | }
197 | });
198 | } else {
199 | loadDependencyLegacy(url, baseDir, relocation, repository, ignoreOptional, ignoreException, transitive, scope, external);
200 | }
201 | }
202 |
203 | void loadDependencyLegacy(
204 | @NotNull String url,
205 | @NotNull File baseDir,
206 | @NotNull List relocation,
207 | String repository,
208 | boolean ignoreOptional,
209 | boolean ignoreException,
210 | boolean transitive,
211 | @NotNull List scope,
212 | boolean external
213 | ) throws Throwable {
214 | Artifact artifact = new Artifact(url);
215 | DependencyDownloader downloader = new DependencyDownloader(baseDir, relocation);
216 | downloader.addRepository(new Repository(repository));
217 | downloader.setIgnoreOptional(ignoreOptional);
218 | downloader.setIgnoreException(ignoreException);
219 | downloader.setDependencyScopes(scope);
220 | downloader.setTransitive(transitive);
221 | // 解析依赖
222 | String pomPath = String.format(
223 | "%s/%s/%s/%s-%s.pom",
224 | artifact.getGroupId().replace('.', '/'),
225 | artifact.getArtifactId(),
226 | artifact.getVersion(),
227 | artifact.getArtifactId(),
228 | artifact.getVersion()
229 | );
230 | File pomFile = new File(baseDir, pomPath);
231 | File pomFile1 = new File(pomFile.getPath() + ".sha1");
232 | // 验证文件完整性
233 | if (PrimitiveIO.validation(pomFile, pomFile1)) {
234 | downloader.loadDependencyFromInputStream(pomFile.toPath().toUri().toURL().openStream());
235 | } else {
236 | downloader.loadDependencyFromInputStream(new URL(repository + "/" + pomPath).openStream());
237 | }
238 | // 加载自身
239 | Dependency dep = new Dependency(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(), DependencyScope.RUNTIME);
240 | dep.setType(artifact.getExtension());
241 | dep.setExternal(external);
242 | if (transitive) {
243 | downloader.injectClasspath(downloader.loadDependency(downloader.getRepositories(), dep));
244 | } else {
245 | downloader.injectClasspath(Collections.singleton(dep));
246 | }
247 | }
248 |
249 | boolean test(String path) {
250 | String test = path.startsWith("!") ? path.substring(1) : path;
251 | return !test.isEmpty() && ClassAppender.isExists(test);
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/aether/AetherResolver.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.aether;
2 |
3 | import com.google.common.collect.Maps;
4 | import com.google.common.collect.Sets;
5 | import me.lucko.jarrelocator.JarRelocator;
6 | import me.lucko.jarrelocator.Relocation;
7 | import me.zhenxin.zmusic.dependencies.DependencyScope;
8 | import me.zhenxin.zmusic.dependencies.JarRelocation;
9 | import me.zhenxin.zmusic.dependencies.common.ClassAppender;
10 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO;
11 | import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
12 | import org.eclipse.aether.DefaultRepositorySystemSession;
13 | import org.eclipse.aether.RepositorySystem;
14 | import org.eclipse.aether.artifact.DefaultArtifact;
15 | import org.eclipse.aether.collection.CollectRequest;
16 | import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
17 | import org.eclipse.aether.graph.Dependency;
18 | import org.eclipse.aether.graph.DependencyFilter;
19 | import org.eclipse.aether.graph.DependencyNode;
20 | import org.eclipse.aether.impl.DefaultServiceLocator;
21 | import org.eclipse.aether.repository.LocalRepository;
22 | import org.eclipse.aether.repository.RemoteRepository;
23 | import org.eclipse.aether.resolution.DependencyRequest;
24 | import org.eclipse.aether.resolution.DependencyResolutionException;
25 | import org.eclipse.aether.resolution.DependencyResult;
26 | import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
27 | import org.eclipse.aether.spi.connector.transport.TransporterFactory;
28 | import org.eclipse.aether.transport.http.HttpTransporterFactory;
29 | import org.jetbrains.annotations.NotNull;
30 | import org.jetbrains.annotations.Nullable;
31 |
32 | import java.io.File;
33 | import java.io.IOException;
34 | import java.util.Collections;
35 | import java.util.List;
36 | import java.util.Map;
37 | import java.util.Set;
38 | import java.util.stream.Collectors;
39 |
40 | /**
41 | * @author md_5, sky
42 | * @since 2024/7/20 20:31
43 | */
44 | @SuppressWarnings("deprecation")
45 |
46 | public class AetherResolver {
47 |
48 | private static final Map RESOLVER_MAP = Maps.newConcurrentMap();
49 | private static final Set INJECTED_DEPENDENCIES = Sets.newConcurrentHashSet();
50 |
51 | private final RepositorySystem repository;
52 | private final DefaultRepositorySystemSession session;
53 | private final List repositories;
54 |
55 | public AetherResolver(String repo) {
56 | DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
57 | locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
58 | locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
59 | this.repository = locator.getService(RepositorySystem.class);
60 | this.session = MavenRepositorySystemUtils.newSession();
61 | this.session.setChecksumPolicy("fail");
62 | this.session.setLocalRepositoryManager(this.repository.newLocalRepositoryManager(this.session, new LocalRepository("libraries")));
63 | this.session.setReadOnly();
64 | this.repositories = this.repository.newResolutionRepositories(this.session, Collections.singletonList(
65 | new RemoteRepository.Builder("central", "default", repo).build()
66 | ));
67 | }
68 |
69 | public static AetherResolver of(@NotNull String repository) {
70 | return RESOLVER_MAP.computeIfAbsent(repository, AetherResolver::new);
71 | }
72 |
73 | @SuppressWarnings("DuplicatedCode")
74 | public static void inject(@NotNull File file, @Nullable List relocation, boolean isExternal) throws Throwable {
75 | // 避免重复加载多个依赖
76 | if (INJECTED_DEPENDENCIES.contains(file.getPath())) {
77 | return;
78 | } else {
79 | INJECTED_DEPENDENCIES.add(file.getParent());
80 | }
81 | // 如果没有重定向规则,直接注入
82 | if (relocation == null || relocation.isEmpty()) {
83 | ClassAppender.addPath(file.toPath(), isExternal);
84 | } else {
85 | // 获取重定向后的文件
86 | String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
87 | File rel = new File(file.getParentFile(), name + "_r2_" + Math.abs(relocation.hashCode()) + ".jar");
88 | // 如果文件不存在或者文件大小为 0,就执行重定向逻辑
89 | if (!rel.exists() || rel.length() == 0) {
90 | try {
91 | // 获取重定向规则
92 | List rules = relocation.stream().map(JarRelocation::toRelocation).collect(Collectors.toList());
93 | // 获取临时文件
94 | File tempSourceFile = PrimitiveIO.copyFile(file, File.createTempFile(file.getName(), ".jar"));
95 | // 运行
96 | new JarRelocator(tempSourceFile, rel, rules).run();
97 | } catch (IOException e) {
98 | throw new IllegalStateException(String.format("Unable to relocate %s%n", file), e);
99 | }
100 | }
101 | // 注入重定向后的文件
102 | ClassAppender.addPath(rel.toPath(), isExternal);
103 | }
104 | }
105 |
106 | public List resolve(@NotNull String library, List scope, boolean isTransitive, boolean ignoreOptional) throws DependencyResolutionException {
107 | Dependency dependency = new Dependency(new DefaultArtifact(library), null);
108 | DependencyResult result;
109 | DependencyRequest dependencyRequest = getDependencyRequest(dependency, scope, isTransitive, ignoreOptional);
110 | result = this.repository.resolveDependencies(this.session, dependencyRequest);
111 | return result.getArtifactResults().stream().map(it -> it.getArtifact().getFile()).collect(Collectors.toList());
112 | }
113 |
114 | private @NotNull DependencyRequest getDependencyRequest(Dependency dependency, List scope, boolean isTransitive, boolean ignoreOptional) {
115 | return new DependencyRequest(new CollectRequest(dependency, null, repositories), new DependencyFilter() {
116 | boolean self = true;
117 |
118 | @Override
119 | public boolean accept(DependencyNode node, List parents) {
120 | // 忽略可选
121 | if (ignoreOptional && node.getDependency().isOptional()) {
122 | return false;
123 | }
124 | // 依赖传递
125 | if (isTransitive) {
126 | return true;
127 | }
128 | if (self) {
129 | self = false;
130 | return true;
131 | }
132 | return false;
133 | }
134 | });
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/ClassAppender.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.common;
2 |
3 | import me.zhenxin.zmusic.ZMusicRuntime;
4 | import sun.misc.Unsafe;
5 |
6 | import java.io.File;
7 | import java.lang.invoke.MethodHandle;
8 | import java.lang.invoke.MethodHandles;
9 | import java.lang.invoke.MethodType;
10 | import java.lang.reflect.Field;
11 | import java.net.URL;
12 | import java.net.URLClassLoader;
13 | import java.nio.file.Path;
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import static me.zhenxin.zmusic.dependencies.common.PrimitiveIO.t;
18 |
19 | /**
20 | * @author sky
21 | * @since 2020-04-12 22:39
22 | */
23 | public class ClassAppender {
24 |
25 | final static List CALLBACKS = new ArrayList<>();
26 | static MethodHandles.Lookup lookup;
27 | static Unsafe unsafe;
28 |
29 | static {
30 | try {
31 | Field field = Unsafe.class.getDeclaredField("theUnsafe");
32 | field.setAccessible(true);
33 | unsafe = (Unsafe) field.get(null);
34 | Field lookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
35 | Object lookupBase = unsafe.staticFieldBase(lookupField);
36 | long lookupOffset = unsafe.staticFieldOffset(lookupField);
37 | lookup = (MethodHandles.Lookup) unsafe.getObject(lookupBase, lookupOffset);
38 | // 如果第二个 IMPL_LOOKUP 没有找到,提示无法加载
39 | if (lookup == null) {
40 | RuntimeLogger.warning(t(
41 | "未能找到 Unsafe lookup,ZMusic 将无法正常工作。",
42 | "Unsafe lookup not found, ZMusic will not work properly."
43 | ));
44 | }
45 | } catch (Throwable ignored) {
46 | }
47 | }
48 |
49 | /**
50 | * 加载一个文件到 ClassLoader
51 | *
52 | * @param path 路径
53 | * @param isExternal 是否外部库(不加入 loadedClasses)
54 | */
55 | public static ClassLoader addPath(Path path, boolean isExternal) throws Throwable {
56 | File file = new File(path.toUri().getPath());
57 | ClassLoader loader = ZMusicRuntime.class.getClassLoader();
58 | // Application
59 | if ("AppClassLoader".equals(loader.getClass().getSimpleName())) {
60 | addUrl(loader, ucp(loader.getClass()), file, isExternal);
61 | }
62 | // Hybrid
63 | else if ("net.minecraft.launchwrapper.LaunchClassLoader".equals(loader.getClass().getName())) {
64 | MethodHandle methodHandle = lookup.findVirtual(URLClassLoader.class, "addURL", MethodType.methodType(void.class, java.net.URL.class));
65 | methodHandle.invoke(loader, file.toURI().toURL());
66 | }
67 | // Bukkit
68 | else {
69 | addUrl(loader, ucp(loader), file, isExternal);
70 | }
71 | return loader;
72 | }
73 |
74 | /**
75 | * 获取 addPath 函数所使用的 ClassLoader(原函数为:judgeAddPathClassLoader)
76 | */
77 | public static ClassLoader getClassLoader() {
78 | return ZMusicRuntime.class.getClassLoader();
79 | }
80 |
81 | /**
82 | * 判断类是否粗在
83 | */
84 | public static boolean isExists(String path) {
85 | try {
86 | Class.forName(path, false, getClassLoader());
87 | return true;
88 | } catch (ClassNotFoundException ignored) {
89 | return false;
90 | }
91 | }
92 |
93 | private static void addUrl(ClassLoader loader, Field ucpField, File file, boolean isExternal) throws Throwable {
94 | if (ucpField == null) {
95 | throw new IllegalStateException("ucp field not found");
96 | }
97 | if (lookup == null) {
98 | throw new IllegalStateException("lookup not found");
99 | }
100 | Object ucp = unsafe.getObject(loader, unsafe.objectFieldOffset(ucpField));
101 | try {
102 | MethodHandle methodHandle = lookup.findVirtual(ucp.getClass(), "addURL", MethodType.methodType(void.class, URL.class));
103 | methodHandle.invoke(ucp, file.toURI().toURL());
104 | for (Callback i : CALLBACKS) {
105 | i.add(loader, file, isExternal);
106 | }
107 | } catch (NoSuchMethodError e) {
108 | throw new IllegalStateException("Unsupported (classloader: " + loader.getClass().getName() + ", ucp: " + ucp.getClass().getName() + ")", e);
109 | }
110 | }
111 |
112 | private static Field ucp(ClassLoader loader) {
113 | try {
114 | return URLClassLoader.class.getDeclaredField("ucp");
115 | } catch (NoSuchFieldError | NoSuchFieldException ignored) {
116 | return ucp(loader.getClass());
117 | }
118 | }
119 |
120 | private static Field ucp(Class> loader) {
121 | try {
122 | return loader.getDeclaredField("ucp");
123 | } catch (NoSuchFieldError | NoSuchFieldException e2) {
124 | Class> superclass = loader.getSuperclass();
125 | if (superclass == Object.class) {
126 | return null;
127 | }
128 | return ucp(superclass);
129 | }
130 | }
131 |
132 | public static void registerCallback(Callback callback) {
133 | CALLBACKS.add(callback);
134 | }
135 |
136 | public interface Callback {
137 |
138 | void add(ClassLoader loader, File file, boolean isExternal);
139 | }
140 | }
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/PrimitiveIO.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.common;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 |
5 | import java.io.*;
6 | import java.net.URL;
7 | import java.nio.channels.FileChannel;
8 | import java.nio.charset.Charset;
9 | import java.nio.charset.StandardCharsets;
10 | import java.nio.file.Files;
11 | import java.security.MessageDigest;
12 | import java.security.NoSuchAlgorithmException;
13 | import java.util.Locale;
14 | import java.util.UUID;
15 |
16 | /**
17 | * TabooLib
18 | * taboolib.common.env.IO
19 | *
20 | * @author 坏黑
21 | * @since 2023/3/31 14:59
22 | */
23 | @SuppressWarnings("CallToPrintStackTrace")
24 | public class PrimitiveIO {
25 | private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
26 | private static final int BUFFER_SIZE = 8192;
27 | private static final ThreadLocal DIGEST_THREAD_LOCAL = ThreadLocal.withInitial(() -> {
28 | try {
29 | return MessageDigest.getInstance("SHA-1");
30 | } catch (NoSuchAlgorithmException e) {
31 | throw new RuntimeException(e);
32 | }
33 | });
34 |
35 | /**
36 | * 是否为中文环境
37 | * 如果在获取的时候发生异常,默认视为中文环境
38 | */
39 | private static boolean isChineseEnvironment = true;
40 |
41 | static {
42 | // 获取语言环境
43 | try {
44 | isChineseEnvironment = Locale.getDefault().toLanguageTag().startsWith("zh");
45 | } catch (Throwable ignored) {
46 | }
47 | }
48 |
49 | /**
50 | * 验证文件完整性
51 | *
52 | * @param file 文件
53 | * @param hashFile 哈希文件
54 | */
55 | public static boolean validation(File file, File hashFile) {
56 | return file.exists() && hashFile.exists() && PrimitiveIO.readFile(hashFile).startsWith(PrimitiveIO.getHash(file));
57 | }
58 |
59 | /**
60 | * 获取文件哈希,使用 sha-1 算法
61 | */
62 | @NotNull
63 | public static String getHash(File file) {
64 | MessageDigest digest = DIGEST_THREAD_LOCAL.get();
65 | digest.reset(); // Ensure the MessageDigest is reset before each use
66 | try (InputStream inputStream = Files.newInputStream(file.toPath())) {
67 | byte[] buffer = new byte[BUFFER_SIZE];
68 | int total;
69 | while ((total = inputStream.read(buffer)) != -1) {
70 | digest.update(buffer, 0, total);
71 | }
72 | byte[] hashBytes = digest.digest();
73 | return bytesToHex(hashBytes);
74 | } catch (IOException ex) {
75 | ex.printStackTrace();
76 | }
77 | return "null (" + UUID.randomUUID() + ")";
78 | }
79 |
80 | public static String bytesToHex(byte[] bytes) {
81 | char[] hexChars = new char[bytes.length * 2];
82 | for (int j = 0; j < bytes.length; j++) {
83 | int v = bytes[j] & 0xFF;
84 | hexChars[j * 2] = HEX_ARRAY[v >>> 4];
85 | hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
86 | }
87 | return new String(hexChars);
88 | }
89 |
90 | /**
91 | * 读取文件内容
92 | */
93 | @NotNull
94 | public static String readFile(File file) {
95 | try (FileInputStream fileInputStream = new FileInputStream(file)) {
96 | return readFully(fileInputStream, StandardCharsets.UTF_8);
97 | } catch (IOException e) {
98 | e.printStackTrace();
99 | }
100 | return "null (" + UUID.randomUUID() + ")";
101 | }
102 |
103 | /**
104 | * 从 InputStream 读取全部内容
105 | *
106 | * @param inputStream 输入流
107 | * @param charset 编码
108 | */
109 | @NotNull
110 | public static String readFully(InputStream inputStream, Charset charset) throws IOException {
111 | return new String(readFully(inputStream), charset);
112 | }
113 |
114 | /**
115 | * 从 InputStream 读取全部内容
116 | *
117 | * @param inputStream 输入流
118 | */
119 | public static byte[] readFully(InputStream inputStream) throws IOException {
120 | ByteArrayOutputStream stream = new ByteArrayOutputStream();
121 | byte[] buf = new byte[BUFFER_SIZE];
122 | int len;
123 | while ((len = inputStream.read(buf)) > 0) {
124 | stream.write(buf, 0, len);
125 | }
126 | return stream.toByteArray();
127 | }
128 |
129 | /**
130 | * 通过 FileChannel 复制文件
131 | */
132 | @NotNull
133 | public static File copyFile(File from, File to) {
134 | try (FileInputStream fileIn = new FileInputStream(from); FileOutputStream fileOut = new FileOutputStream(to); FileChannel channelIn = fileIn.getChannel(); FileChannel channelOut = fileOut.getChannel()) {
135 | channelIn.transferTo(0, channelIn.size(), channelOut);
136 | } catch (IOException t) {
137 | t.printStackTrace();
138 | }
139 | return to;
140 | }
141 |
142 | /**
143 | * 下载文件
144 | *
145 | * @param url 地址
146 | * @param out 目标文件
147 | */
148 | @SuppressWarnings("StatementWithEmptyBody")
149 | public static void downloadFile(URL url, File out) throws IOException {
150 | //noinspection ResultOfMethodCallIgnored
151 | out.getParentFile().mkdirs();
152 | InputStream ins = url.openStream();
153 | OutputStream outs = Files.newOutputStream(out.toPath());
154 | byte[] buffer = new byte[BUFFER_SIZE];
155 | for (int len; (len = ins.read(buffer)) > 0; outs.write(buffer, 0, len)) {
156 |
157 | }
158 | outs.close();
159 | ins.close();
160 | }
161 |
162 | /**
163 | * 针对中文环境进行特殊适配,以支持在中文环境中输出本土化的提示信息。
164 | * 其他语言环境均输出英文。
165 | */
166 | public static String t(String zh, String en) {
167 | if (isChineseEnvironment) {
168 | return zh;
169 | } else {
170 | return en;
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/common/RuntimeLogger.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.common;
2 |
3 | import java.util.logging.Level;
4 |
5 | /**
6 | * @author zhenxin
7 | */
8 | public class RuntimeLogger {
9 |
10 | /**
11 | * 日志对象
12 | */
13 | private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger("ZMusic");
14 |
15 | /**
16 | * 输出 Info 日志
17 | *
18 | * @param message 日志内容
19 | */
20 | public static void info(String message, Object... args) {
21 | LOGGER.log(Level.INFO, message, args);
22 | }
23 |
24 | /**
25 | * 输出 Warning 日志
26 | *
27 | * @param message 日志内容
28 | */
29 | public static void warning(String message, Object... args) {
30 | LOGGER.log(Level.WARNING, message, args);
31 | }
32 |
33 | /**
34 | * 输出 Error 日志
35 | *
36 | * @param message 日志内容
37 | */
38 | public static void error(String message, Object... args) {
39 | LOGGER.log(Level.SEVERE, message, args);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/AbstractXmlParser.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.w3c.dom.Element;
5 | import org.w3c.dom.Node;
6 | import org.w3c.dom.NodeList;
7 |
8 | import java.text.ParseException;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | /**
13 | * Base class for any class that needs to do XML parsing
14 | *
15 | * @author Zach Deibert, sky
16 | * @since 1.0.0
17 | */
18 | public abstract class AbstractXmlParser {
19 |
20 | /**
21 | * The pattern to use to detect when a variable should be substituted in the
22 | * pom
23 | *
24 | * @since 1.0.0
25 | */
26 | private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]+)}");
27 |
28 | /**
29 | * Gets the replacement value for a substitution variable
30 | *
31 | * @param key The key of the variable
32 | * @param pom The pom document
33 | * @return The value that it should be replaced with
34 | * @throws ParseException If the variable could not be resolved
35 | * @since 1.0.0
36 | */
37 | @NotNull
38 | private static String getReplacement(String key, Element pom) throws ParseException {
39 | if (key.startsWith("project.")) {
40 | return find(key.substring("project.".length()), pom);
41 | } else if (key.startsWith("pom.")) {
42 | return find(key.substring("pom.".length()), pom);
43 | } else {
44 | throw new ParseException(String.format("Unknown variable '%s'", key), -1);
45 | }
46 | }
47 |
48 | /**
49 | * Replaces all the variables in a string of text
50 | *
51 | * @param text The text to replace the variables in
52 | * @param pom The pom document
53 | * @return The text with all the variables replaced
54 | * @throws ParseException If the variable could not be resolved
55 | * @since 1.0.0
56 | */
57 | @NotNull
58 | private static String replaceVariables(String text, Element pom) throws ParseException {
59 | Matcher matcher = SUBSTITUTION_PATTERN.matcher(text);
60 | while (matcher.find()) {
61 | text = matcher.replaceFirst(getReplacement(matcher.group(1), pom));
62 | }
63 | return text;
64 | }
65 |
66 |
67 | @NotNull
68 | protected static String find(String name, Element node) throws ParseException {
69 | return find(name, node, null);
70 | }
71 |
72 |
73 | /**
74 | * Searches for a node and returns the text inside of it
75 | *
76 | * @param name The name of the node to search for
77 | * @param node The node to search inside of
78 | * @param def The default value, or null
if the value is required
79 | * @return The text content of the node it found, or def
if the node is not found
80 | * @throws ParseException If the node cannot be found and there is no default value
81 | * @since 1.0.0
82 | */
83 | @NotNull
84 | protected static String find(String name, Element node, String def) throws ParseException {
85 | NodeList list = node.getChildNodes();
86 | for (int i = 0; i < list.getLength(); ++i) {
87 | Node n = list.item(i);
88 | if (n.getNodeName().equals(name)) {
89 | try {
90 | return replaceVariables(n.getTextContent(), node.getOwnerDocument().getDocumentElement());
91 | } catch (ParseException ex) {
92 | if (def == null) {
93 | throw ex;
94 | } else {
95 | return def;
96 | }
97 | }
98 | }
99 | }
100 | list = node.getElementsByTagName(name);
101 | if (list.getLength() > 0) {
102 | try {
103 | return replaceVariables(list.item(0).getTextContent(), node.getOwnerDocument().getDocumentElement());
104 | } catch (ParseException ex) {
105 | if (def == null) {
106 | throw ex;
107 | } else {
108 | return def;
109 | }
110 | }
111 | }
112 | if (def == null) {
113 | throw new ParseException(String.format("Unable to find required tag '%s' in node", name), -1);
114 | } else {
115 | return def;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Artifact.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | /**
9 | * TabooLib
10 | * taboolib.common.env.legacy.Artifact
11 | *
12 | * @author 坏黑
13 | * @since 2024/7/20 22:24
14 | */
15 | public class Artifact {
16 |
17 | private static final Pattern COORDINATE_PATTERN = Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
18 | private final String groupId;
19 | private final String artifactId;
20 | private final String version;
21 | private final String classifier;
22 | private final String extension;
23 |
24 | public Artifact(String coords) {
25 | this(coords, Collections.emptyMap());
26 | }
27 |
28 | public Artifact(String coords, Map properties) {
29 | Matcher m = COORDINATE_PATTERN.matcher(coords);
30 | if (!m.matches()) {
31 | throw new IllegalArgumentException("Bad artifact coordinates " + coords + ", expected format is :[:[:]]:");
32 | } else {
33 | this.groupId = m.group(1);
34 | this.artifactId = m.group(2);
35 | this.extension = get(m.group(4), "jar");
36 | this.classifier = get(m.group(6), "");
37 | this.version = m.group(7);
38 | }
39 | }
40 |
41 | static String get(String value, String defaultValue) {
42 | return value != null && !value.isEmpty() ? value : defaultValue;
43 | }
44 |
45 | public String getGroupId() {
46 | return groupId;
47 | }
48 |
49 | public String getArtifactId() {
50 | return artifactId;
51 | }
52 |
53 | public String getVersion() {
54 | return version;
55 | }
56 |
57 | public String getClassifier() {
58 | return classifier;
59 | }
60 |
61 | public String getExtension() {
62 | return extension;
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | StringBuilder buffer = new StringBuilder(128);
68 | buffer.append(this.getGroupId());
69 | buffer.append(':').append(this.getArtifactId());
70 | buffer.append(':').append(this.getExtension());
71 | if (!this.getClassifier().isEmpty()) {
72 | buffer.append(':').append(this.getClassifier());
73 | }
74 | buffer.append(':').append(this.getVersion());
75 | return buffer.toString();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Dependency.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import me.zhenxin.zmusic.dependencies.DependencyScope;
4 | import org.jetbrains.annotations.Nullable;
5 | import org.w3c.dom.Element;
6 |
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.net.MalformedURLException;
10 | import java.net.URL;
11 | import java.text.ParseException;
12 | import java.util.Collection;
13 | import java.util.Objects;
14 |
15 | /**
16 | * Represents a dependency that needs to be downloaded and injected into the
17 | * classpath
18 | *
19 | * @author Zach Deibert, sky
20 | * @since 1.0.0
21 | */
22 | public class Dependency extends AbstractXmlParser {
23 |
24 | /**
25 | * 当版本尚未指定时的占位符字符串
26 | */
27 | private static final String LATEST_VERSION = "latest";
28 |
29 | /**
30 | * 此依赖项的组 ID
31 | */
32 | private final String groupId;
33 |
34 | /**
35 | * 此依赖项的工件 ID
36 | */
37 | private final String artifactId;
38 |
39 | /**
40 | * 依赖项的范围
41 | */
42 | private final DependencyScope scope;
43 |
44 | /**
45 | * 要下载的版本,或者如果在 pom 中没有指定依赖项的最新版本,则设置依赖项的最新版本
46 | */
47 | private String version;
48 |
49 | /**
50 | * 类型(extension)
51 | * 例如:jar, pom... 只有
52 | */
53 | private String type = "jar";
54 |
55 | /**
56 | * 是否外部库(不加入 loadedClasses)
57 | */
58 | private boolean isExternal;
59 |
60 | public Dependency(String groupId, String artifactId, String version, DependencyScope scope) {
61 | this.groupId = groupId;
62 | this.artifactId = artifactId;
63 | this.version = version.contains("$") || version.contains("[") || version.contains("(") ? LATEST_VERSION : version;
64 | this.scope = scope;
65 | }
66 |
67 | public Dependency(Element node) throws ParseException {
68 | this(find("groupId", node), find("artifactId", node), find("version", node, LATEST_VERSION), DependencyScope.valueOf(find("scope", node, "runtime").toUpperCase()));
69 | }
70 |
71 | /**
72 | * 获取依赖的下载地址
73 | */
74 | public URL getURL(Repository repo, String ext) throws MalformedURLException {
75 | String name = String.format("%s-%s.%s", getArtifactId(), getVersion(), ext);
76 | return new URL(String.format("%s/%s/%s/%s/%s", repo.getUrl(), getGroupId().replace('.', '/'), getArtifactId(), getVersion(), name));
77 | }
78 |
79 | /**
80 | * 检查依赖项的版本
81 | * 如果版本尚未指定,则尝试从仓库中获取最新版本
82 | */
83 | public void checkVersion(Collection repositories, File baseDir) throws IOException {
84 | if (getVersion() == null) {
85 | // 获取本地最新版本
86 | DependencyVersion installedLatestVersion = getInstalledLatestVersion(baseDir);
87 | // 是否检查更新
88 | boolean checkUpdate = false;
89 | // 本地版本不存在
90 | if (installedLatestVersion == null) {
91 | checkUpdate = true;
92 | }
93 | // 2022/3/31
94 | // HikariCP 引用的 slf4j 为 latest 版本,因此每次开服都会尝试从仓库中获取最新版本
95 | else if (VersionChecker.isOutdated()) {
96 | checkUpdate = true;
97 | VersionChecker.updateCheckTime();
98 | }
99 | IOException e = null;
100 | if (checkUpdate) {
101 | // 尝试从仓库中获取最新版本
102 | for (Repository repo : repositories) {
103 | try {
104 | repo.getLatestVersion(this);
105 | e = null;
106 | break;
107 | } catch (IOException ex) {
108 | e = new IOException(String.format("Unable to find latest version of %s", this), ex);
109 | }
110 | }
111 | if (e != null) {
112 | throw e;
113 | }
114 | } else {
115 | setVersion(installedLatestVersion.toString());
116 | }
117 | }
118 | }
119 |
120 | /**
121 | * Get the latest version of this artifact that are currently
122 | * downloaded on this computer
123 | */
124 | @Nullable
125 | public DependencyVersion getInstalledLatestVersion(File baseDir) {
126 | DependencyVersion max = null;
127 | for (DependencyVersion ver : getInstalledVersions(baseDir)) {
128 | if (max == null || ver.compareTo(max) > 0) {
129 | max = ver;
130 | }
131 | }
132 | return max;
133 | }
134 |
135 | /**
136 | * Gets a list of all the versions of this artifact that are currently
137 | * downloaded on this computer
138 | *
139 | * @return An array of the versions that are already downloaded
140 | */
141 | public DependencyVersion[] getInstalledVersions(File dir) {
142 | for (String part : getGroupId().split("\\.")) {
143 | dir = new File(dir, part);
144 | }
145 | dir = new File(dir, getArtifactId());
146 | String[] list = dir.list();
147 | if (list == null) {
148 | return new DependencyVersion[0];
149 | }
150 | DependencyVersion[] versions = new DependencyVersion[list.length];
151 | for (int i = 0; i < list.length; ++i) {
152 | versions[i] = new DependencyVersion(list[i]);
153 | }
154 | return versions;
155 | }
156 |
157 | /**
158 | * Gets the file that the downloaded artifact should be stored in
159 | *
160 | * @param dir The directory to store downloaded artifacts in
161 | * @param ext The file extension to download (should be either "jar"
or "pom"
)
162 | * @return The file to download into
163 | */
164 | public File findFile(File dir, String ext) {
165 | if (getVersion() == null) {
166 | throw new IllegalStateException("Version is not resolved: " + this);
167 | }
168 | for (String part : getGroupId().split("\\.")) {
169 | dir = new File(dir, part);
170 | }
171 | dir = new File(dir, getArtifactId());
172 | dir = new File(dir, getVersion());
173 | dir = new File(dir, String.format("%s-%s.%s", getArtifactId(), getVersion(), ext));
174 | return dir;
175 | }
176 |
177 | public String getGroupId() {
178 | return groupId;
179 | }
180 |
181 | public String getArtifactId() {
182 | return artifactId;
183 | }
184 |
185 | public String getVersion() {
186 | return version.equals(LATEST_VERSION) ? null : version;
187 | }
188 |
189 | /**
190 | * Sets the version of this dependency
191 | */
192 | public void setVersion(String version) {
193 | if (!this.version.equals(LATEST_VERSION)) {
194 | throw new IllegalStateException("Version is already resolved");
195 | } else if (version.equals(LATEST_VERSION)) {
196 | throw new IllegalArgumentException("Cannot set version to the latest");
197 | } else {
198 | this.version = version;
199 | }
200 | }
201 |
202 | public String getType() {
203 | return type;
204 | }
205 |
206 | public void setType(String type) {
207 | this.type = type;
208 | }
209 |
210 | public boolean isExternal() {
211 | return isExternal;
212 | }
213 |
214 | public void setExternal(boolean external) {
215 | isExternal = external;
216 | }
217 |
218 | public DependencyScope getScope() {
219 | return scope;
220 | }
221 |
222 | @Override
223 | public String toString() {
224 | return String.format("%s:%s:%s", groupId, artifactId, version);
225 | }
226 |
227 | @Override
228 | public boolean equals(Object o) {
229 | if (this == o) {
230 | return true;
231 | }
232 | if (!(o instanceof Dependency)) {
233 | return false;
234 | }
235 | Dependency that = (Dependency) o;
236 | return Objects.equals(getGroupId(), that.getGroupId()) && Objects.equals(getArtifactId(), that.getArtifactId()) && Objects.equals(getVersion(), that.getVersion());
237 | }
238 |
239 | @Override
240 | public int hashCode() {
241 | return Objects.hash(getGroupId(), getArtifactId());
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/DependencyDownloader.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import me.lucko.jarrelocator.JarRelocator;
4 | import me.lucko.jarrelocator.Relocation;
5 | import me.zhenxin.zmusic.dependencies.DependencyScope;
6 | import me.zhenxin.zmusic.dependencies.JarRelocation;
7 | import me.zhenxin.zmusic.dependencies.common.ClassAppender;
8 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO;
9 | import org.jetbrains.annotations.Nullable;
10 | import org.w3c.dom.Document;
11 | import org.w3c.dom.Element;
12 | import org.w3c.dom.Node;
13 | import org.w3c.dom.NodeList;
14 | import org.xml.sax.SAXException;
15 |
16 | import javax.xml.parsers.DocumentBuilder;
17 | import javax.xml.parsers.DocumentBuilderFactory;
18 | import javax.xml.parsers.ParserConfigurationException;
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.io.InputStream;
22 | import java.text.ParseException;
23 | import java.util.*;
24 | import java.util.concurrent.ConcurrentHashMap;
25 | import java.util.concurrent.CopyOnWriteArraySet;
26 | import java.util.stream.Collectors;
27 |
28 |
29 | /**
30 | * 包含所有需要下载和注入依赖项到类路径的方法的类。
31 | *
32 | * @author Zach Deibert, sky
33 | * @since 1.0.0
34 | */
35 | @SuppressWarnings({"UnusedReturnValue", "ResultOfMethodCallIgnored"})
36 | public class DependencyDownloader extends AbstractXmlParser {
37 |
38 | /**
39 | * 已注入的依赖
40 | */
41 | private static final Map> injectedDependencies = new ConcurrentHashMap<>();
42 |
43 | /**
44 | * 已下载的依赖
45 | */
46 | private static final Set downloadedDependencies = new CopyOnWriteArraySet<>();
47 |
48 | /**
49 | * 仓库
50 | */
51 | private final Set repositories = new CopyOnWriteArraySet<>();
52 |
53 | /**
54 | * 重定向规则
55 | */
56 | private final Set relocation = new CopyOnWriteArraySet<>();
57 |
58 | /**
59 | * 本地依赖目录
60 | */
61 | private final File baseDir;
62 |
63 | /**
64 | * 依赖范围
65 | */
66 | private List dependencyScopes = Arrays.asList(DependencyScope.RUNTIME, DependencyScope.COMPILE);
67 |
68 | /**
69 | * 忽略可选依赖
70 | */
71 | private boolean ignoreOptional = true;
72 |
73 | /**
74 | * 忽略异常
75 | */
76 | private boolean ignoreException = false;
77 |
78 | /**
79 | * 是否传递依赖
80 | */
81 | private boolean isTransitive = true;
82 |
83 | public DependencyDownloader(@Nullable File baseDir) {
84 | this.baseDir = baseDir;
85 | }
86 |
87 | public DependencyDownloader(@Nullable File baseDir, @Nullable List relocation) {
88 | this.baseDir = baseDir;
89 | if (relocation != null) {
90 | for (JarRelocation rel : relocation) {
91 | if (rel != null) {
92 | this.relocation.add(rel);
93 | }
94 | }
95 | }
96 | }
97 |
98 | /**
99 | * 确保 {@link DependencyDownloader#baseDir} 存在
100 | */
101 | private void createBaseDir() {
102 | baseDir.mkdirs();
103 | }
104 |
105 | /**
106 | * 将一组依赖项注入到类路径中
107 | */
108 | @SuppressWarnings("DuplicatedCode")
109 | public void injectClasspath(Set dependencies) throws Throwable {
110 | for (Dependency dep : dependencies) {
111 | // 如果已经注入过了,就跳过
112 | Set injectedDependencyClassLoaders = injectedDependencies.get(dep);
113 | if (injectedDependencyClassLoaders != null && injectedDependencyClassLoaders.contains(ClassAppender.getClassLoader())) {
114 | continue;
115 | }
116 | // 获取依赖项的文件
117 | File file = dep.findFile(baseDir, "jar");
118 | // 如果文件存在
119 | if (file.exists()) {
120 | // 如果没有重定向规则,直接注入
121 | if (relocation.isEmpty()) {
122 | ClassLoader loader = ClassAppender.addPath(file.toPath(), dep.isExternal());
123 | injectedDependencies.computeIfAbsent(dep, dependency -> new HashSet<>()).add(loader);
124 | } else {
125 | // 获取重定向后的文件
126 | String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
127 | File rel = new File(file.getParentFile(), name + "_r2_" + Math.abs(relocation.hashCode()) + ".jar");
128 | // 如果文件不存在或者文件大小为 0,就执行重定向逻辑
129 | if (!rel.exists() || rel.length() == 0) {
130 | try {
131 | // 获取重定向规则
132 | List rules = relocation.stream().map(JarRelocation::toRelocation).collect(Collectors.toList());
133 | // 获取临时文件
134 | File tempSourceFile = PrimitiveIO.copyFile(file, File.createTempFile(file.getName(), ".jar"));
135 | // 运行
136 | new JarRelocator(tempSourceFile, rel, rules).run();
137 | } catch (IOException e) {
138 | throw new IllegalStateException(String.format("Unable to relocate %s%n", dep), e);
139 | }
140 | }
141 | // 注入重定向后的文件
142 | ClassLoader loader = ClassAppender.addPath(rel.toPath(), dep.isExternal());
143 | injectedDependencies.computeIfAbsent(dep, dependency -> new HashSet<>()).add(loader);
144 | }
145 | } else {
146 | try {
147 | // 下载依赖项
148 | loadDependency(repositories, dep);
149 | // 重新注入
150 | injectClasspath(Collections.singleton(dep));
151 | } catch (IOException e) {
152 | // TODO: Disable Plugin
153 | throw new IllegalStateException("Unable to load dependency: " + dep, e);
154 | }
155 | }
156 | }
157 | }
158 |
159 | /**
160 | * 下载一个依赖项以及它的所有依赖项,并将它们存储在 {@link DependencyDownloader#baseDir} 中。
161 | */
162 | public Set loadDependency(Collection repositories, Dependency dependency) throws IOException {
163 | // 未指定仓库
164 | if (repositories.isEmpty()) {
165 | throw new IllegalArgumentException("No repositories specified");
166 | }
167 | // 检查依赖版本
168 | dependency.checkVersion(repositories, baseDir);
169 | // 如果已经下载过了,就直接返回
170 | if (downloadedDependencies.contains(dependency)) {
171 | Set singleton = new HashSet<>();
172 | singleton.add(dependency);
173 | return singleton;
174 | }
175 | // 获取依赖项的 pom 文件和 jar 文件
176 | File pom = dependency.findFile(baseDir, "pom");
177 | File pom1 = new File(pom.getPath() + ".sha1");
178 | File jar = dependency.findFile(baseDir, "jar");
179 | File jar1 = new File(jar.getPath() + ".sha1");
180 | Set downloaded = new HashSet<>();
181 | // 如果类型为 Type 才会下载自己
182 | if ("jar".equals(dependency.getType())) {
183 | downloaded.add(dependency);
184 | }
185 | // 检查文件的完整性
186 | if (PrimitiveIO.validation(pom, pom1) && PrimitiveIO.validation(jar, jar1)) {
187 | // 加载依赖项
188 | downloadedDependencies.add(dependency);
189 | if (pom.exists()) {
190 | downloaded.addAll(loadDependencyFromInputStream(pom.toURI().toURL().openStream()));
191 | }
192 | return downloaded;
193 | }
194 | // 创建所在目录
195 | pom.getParentFile().mkdirs();
196 | // 下载文件
197 | IOException e = null;
198 | for (Repository repo : repositories) {
199 | try {
200 | repo.downloadFile(dependency, pom);
201 | repo.downloadFile(dependency, jar);
202 | e = null;
203 | break;
204 | } catch (Exception ex) {
205 | e = new IOException(String.format("Unable to find download for %s (%s)", dependency, repo.getUrl()), ex);
206 | }
207 | }
208 | // 如果存在异常,则抛出
209 | if (e != null) {
210 | throw e;
211 | }
212 | return downloaded;
213 | }
214 |
215 | /**
216 | * 下载一个依赖项列表以及它们的所有依赖项,并将它们存储在 {@link DependencyDownloader#baseDir} 中。
217 | */
218 | public Set loadDependency(List repositories, List dependencies) throws IOException {
219 | createBaseDir();
220 | Set downloaded = new HashSet<>();
221 | for (Dependency dep : dependencies) {
222 | downloaded.addAll(loadDependency(repositories, dep));
223 | }
224 | return downloaded;
225 | }
226 |
227 | /**
228 | * 下载 pom 中指定的所有依赖项
229 | */
230 | public Set loadDependencyFromPom(Document pom, List scopes) throws IOException {
231 | List dependencies = new ArrayList<>();
232 | Set scopeSet = new HashSet<>(scopes);
233 | NodeList nodes = pom.getDocumentElement().getChildNodes();
234 | List repos = new ArrayList<>(repositories);
235 | if (repos.isEmpty()) {
236 | repos.add(new Repository());
237 | }
238 | try {
239 | for (int i = 0; i < nodes.getLength(); ++i) {
240 | Node node = nodes.item(i);
241 | if ("repositories".equals(node.getNodeName())) {
242 | nodes = ((Element) node).getElementsByTagName("repository");
243 | for (i = 0; i < nodes.getLength(); ++i) {
244 | Element e = (Element) nodes.item(i);
245 | repos.add(new Repository(e));
246 | }
247 | break;
248 | }
249 | }
250 | } catch (ParseException ex) {
251 | throw new IOException("Unable to parse repositories", ex);
252 | }
253 | if (isTransitive) {
254 | nodes = pom.getElementsByTagName("dependency");
255 | try {
256 | for (int i = 0; i < nodes.getLength(); ++i) {
257 | // ignore optional
258 | if (ignoreOptional && "true".equals(find("optional", (Element) nodes.item(i), "false"))) {
259 | continue;
260 | }
261 | Dependency dep = new Dependency((Element) nodes.item(i));
262 | if (scopeSet.contains(dep.getScope())) {
263 | dependencies.add(dep);
264 | }
265 | }
266 | } catch (ParseException ex) {
267 | if (!ignoreException) {
268 | throw new IOException("Unable to parse dependencies", ex);
269 | }
270 | }
271 | }
272 | return loadDependency(repos, dependencies);
273 | }
274 |
275 | /**
276 | * 下载 pom 中指定的所有依赖项
277 | */
278 | public Set loadDependencyFromInputStream(InputStream pom) throws IOException {
279 | return loadDependencyFromInputStream(pom, dependencyScopes);
280 | }
281 |
282 | /**
283 | * 下载 pom 中指定的所有依赖项
284 | */
285 | @SuppressWarnings("HttpUrlsUsage")
286 | public Set loadDependencyFromInputStream(InputStream pom, List scopes) throws IOException {
287 | try {
288 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
289 | factory.setFeature("http://xml.org/sax/features/validation", false);
290 | factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
291 | factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
292 | DocumentBuilder builder = factory.newDocumentBuilder();
293 | Document xml = builder.parse(pom);
294 | return loadDependencyFromPom(xml, scopes);
295 | } catch (ParserConfigurationException ex) {
296 | throw new IOException("Unable to load pom.xml parser", ex);
297 | } catch (SAXException ex) {
298 | throw new IOException("Unable to parse pom.xml", ex);
299 | }
300 | }
301 |
302 | public void addRepository(Repository repository) {
303 | repositories.add(repository);
304 | }
305 |
306 | public File getBaseDir() {
307 | return baseDir;
308 | }
309 |
310 | public List getDependencyScopes() {
311 | return dependencyScopes;
312 | }
313 |
314 | public DependencyDownloader setDependencyScopes(List dependencyScopes) {
315 | this.dependencyScopes = dependencyScopes;
316 | return this;
317 | }
318 |
319 | public Map> getInjectedDependencies() {
320 | return injectedDependencies;
321 | }
322 |
323 | public Set getRepositories() {
324 | return repositories;
325 | }
326 |
327 | public boolean isIgnoreOptional() {
328 | return ignoreOptional;
329 | }
330 |
331 | public DependencyDownloader setIgnoreOptional(boolean ignoreOptional) {
332 | this.ignoreOptional = ignoreOptional;
333 | return this;
334 | }
335 |
336 | public DependencyDownloader setIgnoreException(boolean ignoreException) {
337 | this.ignoreException = ignoreException;
338 | return this;
339 | }
340 |
341 | public Set getRelocation() {
342 | return relocation;
343 | }
344 |
345 | public boolean isTransitive() {
346 | return isTransitive;
347 | }
348 |
349 | public void setTransitive(boolean transitive) {
350 | isTransitive = transitive;
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/DependencyVersion.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Iterator;
5 | import java.util.List;
6 |
7 | /**
8 | * Represents a version parsed into its components
9 | *
10 | * @author Zach Deibert
11 | * @since 1.0.0
12 | */
13 | public class DependencyVersion implements Comparable {
14 |
15 | /**
16 | * 版本的组件列表
17 | */
18 | private final List parts;
19 |
20 | /**
21 | * 作为字符串的版本
22 | */
23 | private final String version;
24 |
25 | public DependencyVersion(String version) {
26 | parts = new ArrayList<>();
27 | for (String part : version.split("[^0-9]")) {
28 | if (!part.isEmpty()) {
29 | parts.add(Integer.parseInt(part));
30 | }
31 | }
32 | this.version = version;
33 | }
34 |
35 | @Override
36 | public int compareTo(DependencyVersion o) {
37 | Iterator us = parts.iterator();
38 | Iterator them = o.parts.iterator();
39 | while (us.hasNext() && them.hasNext()) {
40 | int diff = us.next().compareTo(them.next());
41 | if (diff != 0) {
42 | return diff;
43 | }
44 | }
45 | return us.hasNext() ? 1 : them.hasNext() ? -1 : 0;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return version;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/Repository.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import me.zhenxin.zmusic.dependencies.common.PrimitiveIO;
4 | import org.w3c.dom.Document;
5 | import org.w3c.dom.Element;
6 |
7 | import javax.xml.parsers.DocumentBuilder;
8 | import javax.xml.parsers.DocumentBuilderFactory;
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.io.InputStream;
12 | import java.net.URL;
13 | import java.text.ParseException;
14 | import java.util.Objects;
15 |
16 | /**
17 | * Represents a maven repository that artifacts can be downloaded from
18 | *
19 | * @author Zach Deibert, sky
20 | * @since 1.0.0
21 | */
22 | public class Repository extends AbstractXmlParser {
23 |
24 | /**
25 | * 仓库地址
26 | */
27 | private final String url;
28 |
29 | public Repository(String url) {
30 | this.url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
31 | }
32 |
33 | public Repository(Element node) throws ParseException {
34 | this(find("url", node, null));
35 | }
36 |
37 | public Repository() {
38 | this("https://maven.aliyun.com/repository/central");
39 | }
40 |
41 | /**
42 | * 从仓库下载依赖文件,以及它的 sha1 文件
43 | */
44 | public void downloadFile(Dependency dep, File out) throws IOException {
45 | // 获取文件扩展名
46 | String ext = out.getName().substring(out.getName().lastIndexOf('.') + 1);
47 | // 构建 URL
48 | URL url = dep.getURL(this, ext);
49 | // 下载文件
50 | PrimitiveIO.downloadFile(url, out);
51 | PrimitiveIO.downloadFile(dep.getURL(this, ext + ".sha1"), new File(out.getPath() + ".sha1"));
52 | }
53 |
54 | /**
55 | * 如果在 pom 中没有指定依赖项的最新版本,则设置依赖项的最新版本
56 | */
57 | public void getLatestVersion(Dependency dep) throws IOException {
58 | URL url = new URL(String.format("%s/%s/%s/maven-metadata.xml", getUrl(), dep.getGroupId().replace('.', '/'), dep.getArtifactId()));
59 | try {
60 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
61 | DocumentBuilder builder = factory.newDocumentBuilder();
62 | InputStream ins = url.openStream();
63 | Document doc = builder.parse(ins);
64 | dep.setVersion(find("release", doc.getDocumentElement(), find("version", doc.getDocumentElement(), null)));
65 | } catch (IOException | RuntimeException ex) {
66 | throw ex;
67 | } catch (Exception ex) {
68 | throw new IOException(ex);
69 | }
70 | }
71 |
72 | public String getUrl() {
73 | return url;
74 | }
75 |
76 | @Override
77 | public boolean equals(Object o) {
78 | if (this == o) {
79 | return true;
80 | }
81 | if (!(o instanceof Repository)) {
82 | return false;
83 | }
84 | Repository that = (Repository) o;
85 | return Objects.equals(getUrl(), that.getUrl());
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return Objects.hash(getUrl());
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return "Repository{" + "url='" + url + '\'' + '}';
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/zmusic-runtime/src/main/java/me/zhenxin/zmusic/dependencies/legacy/VersionChecker.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.dependencies.legacy;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | /**
8 | * TabooLib
9 | * taboolib.common.env.VersionChecker
10 | *
11 | * @author 坏黑
12 | * @since 2023/3/31 16:37
13 | */
14 | @SuppressWarnings("ResultOfMethodCallIgnored")
15 | public class VersionChecker {
16 |
17 | private static final File checkFile = new File("version.lock");
18 |
19 | /**
20 | * 是否需要检查更新
21 | * 距离上次版本检查的时间是否超过 7 天
22 | */
23 | public static boolean isOutdated() {
24 | return System.currentTimeMillis() - getLatestCheckTime() > TimeUnit.DAYS.toMillis(7);
25 | }
26 |
27 | /**
28 | * 获取最后一次检查的时间
29 | */
30 | public static long getLatestCheckTime() {
31 | return checkFile.lastModified();
32 | }
33 |
34 | /**
35 | * 更新最后一次检查的时间
36 | */
37 | public static void updateCheckTime() {
38 | if (checkFile.exists()) {
39 | checkFile.setLastModified(System.currentTimeMillis());
40 | } else {
41 | try {
42 | checkFile.createNewFile();
43 | } catch (IOException e) {
44 | throw new RuntimeException(e);
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/zmusic-velocity/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3 |
4 | dependencies {
5 | api(project(":zmusic-core"))
6 | compileOnly(libs.velocity)
7 | implementation(libs.bstats.velocity)
8 | }
9 |
10 | java {
11 | sourceCompatibility = JavaVersion.VERSION_21
12 | targetCompatibility = JavaVersion.VERSION_21
13 | }
14 |
15 | tasks.withType {
16 | compilerOptions {
17 | jvmTarget.set(JvmTarget.JVM_21)
18 | }
19 | }
20 |
21 | tasks.processResources {
22 | inputs.property("version", version)
23 |
24 | filesMatching("velocity-plugin.json") {
25 | expand(mapOf("version" to version))
26 | }
27 | }
--------------------------------------------------------------------------------
/zmusic-velocity/src/main/java/me/zhenxin/zmusic/VelocityPlugin.java:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic;
2 |
3 | import com.google.inject.Inject;
4 | import com.velocitypowered.api.event.Subscribe;
5 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
6 | import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
7 | import com.velocitypowered.api.plugin.Plugin;
8 | import com.velocitypowered.api.plugin.annotation.DataDirectory;
9 | import com.velocitypowered.api.proxy.ProxyServer;
10 | import me.zhenxin.zmusic.dependencies.RuntimeDependency;
11 | import me.zhenxin.zmusic.enums.Platform;
12 | import me.zhenxin.zmusic.platform.VelocityLoggerImpl;
13 | import org.bstats.velocity.Metrics;
14 |
15 | import java.nio.file.Path;
16 | import java.util.logging.Logger;
17 |
18 | /**
19 | * Velocity 入口类
20 | *
21 | * @author 真心
22 | * @since 2023/8/28 12:22
23 | */
24 | @SuppressWarnings({"SpellCheckingInspection", "unused"})
25 | @RuntimeDependency(
26 | value = "!org.bstats:bstats-velocity:" + ZMusicConstants.BSTATS_VERSION,
27 | test = "!me.zhenxin.zmusic.library.bstats.velocity.Metrics",
28 | relocate = {"!org.bstats.", "!me.zhenxin.zmusic.library.bstats."}
29 | )
30 | @Plugin(id = "zmusic")
31 | public class VelocityPlugin {
32 | private final ProxyServer server;
33 | private final Path dataDirectory;
34 | private final Metrics.Factory metricsFactory;
35 |
36 | @Inject
37 | public VelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory, Metrics.Factory metricsFactory) {
38 | this.server = server;
39 | this.dataDirectory = dataDirectory;
40 | this.metricsFactory = metricsFactory;
41 |
42 | ZMusicRuntime.setup(dataDirectory.toFile().getAbsolutePath(), VelocityPlugin.class);
43 | }
44 |
45 | @Subscribe
46 | public void onProxyInitialization(ProxyInitializeEvent event) {
47 | ZMusicKt.setLogger(new VelocityLoggerImpl(server.getConsoleCommandSource()));
48 | ZMusicKt.setDataFolder(dataDirectory.toFile());
49 | ZMusicKt.setCurrentPlatform(Platform.VELOCITY);
50 | metricsFactory.make(this, 12426);
51 | ZMusic.INSTANCE.onEnable();
52 | }
53 |
54 | @Subscribe
55 | public void onProxyShutdown(ProxyShutdownEvent event) {
56 | ZMusic.INSTANCE.onDisable();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/zmusic-velocity/src/main/kotlin/me/zhenxin/zmusic/platform/VelocityLoggerImpl.kt:
--------------------------------------------------------------------------------
1 | package me.zhenxin.zmusic.platform
2 |
3 | import com.velocitypowered.api.command.CommandSource
4 | import me.zhenxin.zmusic.config.Config
5 | import me.zhenxin.zmusic.utils.uncolored
6 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
7 |
8 | /**
9 | * Velocity 日志实现
10 | *
11 | * @author 真心
12 | * @since 2023/7/24 11:01
13 | */
14 | class VelocityLoggerImpl(private val sender: CommandSource) : PlatformLogger {
15 | override fun log(msg: String) = sender.sendMessage(
16 | LegacyComponentSerializer.legacyAmpersand().deserialize("${Config.prefix}$msg".uncolored())
17 | )
18 | }
--------------------------------------------------------------------------------
/zmusic-velocity/src/main/resources/velocity-plugin.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "zmusic",
3 | "name": "ZMusic",
4 | "main": "me.zhenxin.zmusic.VelocityPlugin",
5 | "version": "${version}",
6 | "authors": [
7 | "ZhenXin"
8 | ]
9 | }
--------------------------------------------------------------------------------