Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions DemoMod/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id 'java'
}

group = 'cc.abro'
version = '4.3.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation project(path: ':OrchEngine')
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

test {
useJUnitPlatform()
}
12 changes: 12 additions & 0 deletions DemoMod/src/main/java/cc/abro/demomod/DemoMod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cc.abro.demomod;

import cc.abro.orchengine.util.Mod;


public class DemoMod implements Mod {

@Override
public void run() {
System.out.println("Running Demo Mod");
}
}
82 changes: 82 additions & 0 deletions OrchEngine/src/main/java/cc/abro/orchengine/util/ClassCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cc.abro.orchengine.util;

import java.util.*;

public class ClassCache {
private static final Set<String> loadedJarPaths = new HashSet<>();
private static final HashMap<String, List<Class<?>>> loadedJars = new HashMap<>();
private static final Set<String> knownClasses = new HashSet<>();


/**
* @return список зарегистрированных .jar файлов.
*/
public static Set<String> getLoadedJarPaths(){
return Collections.unmodifiableSet(loadedJarPaths);
}

/**
*
* @param path - путь к .jar файлу.
* @return список классов, загруженных из .jar файла по указанному пути.
*/
public static List<Class<?>> getLoadedJarClasses(String path){
return Collections.unmodifiableList(loadedJars.get(path));
}

/**
* @return список всех загруженных Loader'ом классов.
*/
public static List<Class<?>> getAllLoadedClasses(){
return loadedJars.values().stream()
.flatMap(List::stream).toList();
}

/**
* @param clazz - базовый класс.
* @return список всех закэшированных классов, унаследованных от указанного класса.
*/
public static List<Class<?>> getAssignableTo(Class<?> clazz) {
return getAllLoadedClasses().stream().filter(clazz::isAssignableFrom).toList();
}

/**
* @param path - путь к .jar файлу.
* @return true, если .jar файл был загружен в кэш.
*/
public static boolean isLoadedJar(String path) {
return loadedJarPaths.contains(path);
}

/**
* Добавляет класс в кэш.
* @param jar - .jar файл, в котором находится класс.
* @param clazz - сам класс.
*/
public static void addClass(String jar, Class<?> clazz) {
var classname = clazz.getCanonicalName();
var loadedClasses = getModifiableLoadedJarClasses(jar);
loadedClasses.add(clazz);
knownClasses.add(classname);
}

/**
* @param name - полное имя класса.
* @return true, если такой класс имеется в кэше.
*/
public static boolean isClassKnown(String name) {
return knownClasses.contains(name);
}

/**
* Добавляет .jar файл в список зарегистированных. Зарегистрированные .jar файлы будут игрнорироваться при попытке загрузки в {@link Loader#load(String)}.
* @param path - путь к .jar файлу.
*/
public static void registerLoadedJar(String path) {
loadedJarPaths.add(path);
}

private static List<Class<?>> getModifiableLoadedJarClasses(String path) {
return loadedJars.computeIfAbsent(path, k -> new ArrayList<>());
}
}
69 changes: 69 additions & 0 deletions OrchEngine/src/main/java/cc/abro/orchengine/util/Loader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package cc.abro.orchengine.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;

public class Loader {


/**
* Загружает классы в указанном .jar файле в {@link cc.abro.orchengine.util.ClassCache}, если он еще не был загружен.
* @param jarPath - путь к .jar файлу.
* @return Список классов загруженнх в ClassCache из этого .jar файла.
*/
public static List<Class<?>> load(String jarPath) throws IOException {
return load(jarPath, false);
}

/**
* Загружает классы в указанном .jar файле в {@link cc.abro.orchengine.util.ClassCache}
* @param jarPath - путь к .jar файлу.
* @param forceLoad - попытается загрузить .jar файл, даже если он уже был загружен.
* @return Список классов загруженнх в ClassCache из этого .jar файла.
*/
public static List<Class<?>> load(String jarPath, boolean forceLoad) throws IOException {
// Если .jar уже загружен, вернуть закэшированные классы.
if (ClassCache.isLoadedJar(jarPath) && !forceLoad) {
return ClassCache.getLoadedJarClasses(jarPath);
}

File jarFile = new File(jarPath);
URL jarUrl = jarFile.toURI().toURL();
ArrayList<String> classNames = new ArrayList<>();
ArrayList<Class<?>> classes = new ArrayList<>();

// Находим все классы в джарнике для использования их в URLClassLoader
try (JarFile jar = new JarFile(jarFile)) {
jar.stream().forEach(jarEntry -> {
if (jarEntry.getName().endsWith(".class")){
classNames.add(jarEntry.getName());
}
});
}

// Пытаемся загрузить классы из джарника
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarUrl}, ClassLoader.getSystemClassLoader())) {
classNames.forEach(classPath -> {
try {
String className = classPath.replaceAll("/",".").replace(".class","");
if (ClassCache.isClassKnown(className)) {
return; // не нужно грузить уже загруженный класс.
}
Class<?> cls = urlClassLoader.loadClass(className);
ClassCache.addClass(jarPath, cls);
} catch (Exception e) {
System.out.println(e);
}
});
// Регистрируем .jar файл, если удалось его открыть.
ClassCache.registerLoadedJar(jarPath);
}

return ClassCache.getLoadedJarClasses(jarPath);
}
}
5 changes: 5 additions & 0 deletions OrchEngine/src/main/java/cc/abro/orchengine/util/Mod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package cc.abro.orchengine.util;

public interface Mod {
void run();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -49,4 +50,14 @@ public Set<Object> createInstances(Set<Class<?>> types){

return instances;
}


/**
* @param classes
* @param clazz
* @return все классы из списка, унаследованные от указанного класса
*/
public static List<Class<?>> getAssignableTo(List<Class<?>> classes, Class<?> clazz) {
return classes.stream().filter(clazz::isAssignableFrom).toList();
}
}
3 changes: 2 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
*/

rootProject.name = 'TOW'
include 'OrchEngine'
include 'OrchEngine'
include 'DemoMod'
27 changes: 24 additions & 3 deletions src/main/java/cc/abro/tow/GameStart.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
package cc.abro.tow;

import cc.abro.orchengine.OrchEngine;
import cc.abro.orchengine.util.Loader;
import cc.abro.orchengine.util.Mod;
import cc.abro.orchengine.util.ReflectionUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class GameStart {

public static void main(String[] args) {
Set<String> activeProfiles = Arrays.stream(args).collect(Collectors.toSet());
Set<String> packagesForScan = Set.of(GameStart.class.getPackage().getName());
OrchEngine.start(activeProfiles, packagesForScan);
// Нах не надо, загружать моды можно и без самой игры
//Set<String> activeProfiles = Arrays.stream(args).collect(Collectors.toSet());
//Set<String> packagesForScan = Set.of(GameStart.class.getPackage().getName());
//OrchEngine.start(activeProfiles, packagesForScan);

// Сюда нужно передать путь к джарнику с модом. Хз как собирать его, помогите!
List<Class<?>> classes = null;
try {
classes = Loader.load("./mods/DemoMod.jar");
} catch (IOException e) {
throw new RuntimeException(e);
}

// Попытка запустить все моды
ReflectionUtils.getAssignableTo(classes, Mod.class).forEach((c) -> {
var instance = ReflectionUtils.createInstance(c);
((Mod)instance).run();
});

System.exit(0);
}
}