diff --git a/.gitignore b/.gitignore index f4ffc6f..46db13e 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +dependency-reduced-pom.xml \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/BenthPAPIManager.java b/src/main/java/com/bentahsin/benthpapimanager/BenthPAPIManager.java index 58b5782..0ef9d2c 100644 --- a/src/main/java/com/bentahsin/benthpapimanager/BenthPAPIManager.java +++ b/src/main/java/com/bentahsin/benthpapimanager/BenthPAPIManager.java @@ -1,9 +1,7 @@ package com.bentahsin.benthpapimanager; -import com.bentahsin.benthpapimanager.annotations.Inject; -import com.bentahsin.benthpapimanager.annotations.Placeholder; -import com.bentahsin.benthpapimanager.annotations.PlaceholderIdentifier; -import com.bentahsin.benthpapimanager.annotations.RelationalPlaceholder; +import com.bentahsin.benthpapimanager.annotations.*; +import com.bentahsin.benthpapimanager.middleware.PlaceholderMiddleware; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.Relational; import org.bukkit.Bukkit; @@ -12,19 +10,16 @@ import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -/** - * PlaceholderAPI için anotasyon tabanlı, doğrudan sınıf kaydını destekleyen, - * mevcut PAPI sürümüyle %100 uyumlu, kararlı bir placeholder yönetim kütüphanesi. - */ public final class BenthPAPIManager { private final JavaPlugin plugin; @@ -37,53 +32,25 @@ private BenthPAPIManager(JavaPlugin plugin) { this.plugin = plugin; } - /** - * BenthPAPIManager için bir builder başlatır. - * @param plugin Ana plugin sınıfınız. - * @return Yeni bir BenthPAPIManager örneği. - */ public static BenthPAPIManager create(JavaPlugin plugin) { return new BenthPAPIManager(plugin); } - /** - * Placeholder sınıflarına enjekte edilebilecek bir nesne kaydeder. - * @param type Nesnenin sınıf tipi (örn: DatabaseManager.class). - * @param instance Nesnenin kendisi. - * @return Zincirleme kullanım için kendisini döndürür. - */ public BenthPAPIManager withInjectable(Class type, Object instance) { this.injectables.put(type, instance); return this; } - /** - * Tüm placeholder'lar için varsayılan bir hata metni belirler. - * Anotasyonda belirtilen 'onError' bu değeri ezer. - * @param errorText Varsayılan hata metni. - * @return Zincirleme kullanım için kendisini döndürür. - */ public BenthPAPIManager withDefaultErrorText(String errorText) { this.globalErrorText = errorText; return this; } - /** - * Kütüphane için debug modunu etkinleştirir. - * Etkinleştirildiğinde, konsola daha ayrıntılı hata ayıklama mesajları basılır. - * @return Zincirleme kullanım için kendisini döndürür. - */ public BenthPAPIManager withDebugMode() { this.debugMode = true; return this; } - /** - * Belirtilen placeholder sınıflarını kaydeder. - * Bu metot, yapılandırma zincirinin son adımı olmalıdır. - * @param placeholderClasses Kaydedilecek placeholder sınıfları. - * @return Yapılandırmanın devam etmesi için kendisini döndürür. - */ public BenthPAPIManager register(Class... placeholderClasses) { if (plugin.getServer().getPluginManager().getPlugin("PlaceholderAPI") == null) { plugin.getLogger().warning("PlaceholderAPI bulunamadı, BenthPAPIManager placeholder'ları kaydedemedi."); @@ -129,6 +96,66 @@ public BenthPAPIManager register(Class... placeholderClasses) { return this; } + @SuppressWarnings("unused") + public void generateDocs(String fileName) { + if (!plugin.getDataFolder().exists()) { + boolean ignored = plugin.getDataFolder().mkdirs(); + } + File file = new File(plugin.getDataFolder(), fileName); + try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { + writer.println("=== " + plugin.getName() + " Placeholder Listesi ==="); + writer.println("Oluşturulma Tarihi:" + java.time.LocalDateTime.now()); + writer.println("==================================================\n"); + + for (PlaceholderExpansion expansion : registeredExpansions) { + if (expansion instanceof DynamicExpansion) { + DynamicExpansion dyn = (DynamicExpansion) expansion; + writer.println("GRUP: %" + dyn.getIdentifier() + "_...%"); + writer.println("Yazar: " + dyn.getAuthor() + " | Versiyon: " + dyn.getVersion()); + writer.println("--------------------------------------------------"); + + for (Map.Entry entry : dyn.standardMethods.entrySet()) { + PlaceholderMethod pm = entry.getValue(); + String fullPapi = "%" + dyn.getIdentifier() + "_" + entry.getKey() + "%"; + + writer.println("• " + fullPapi); + + if (!pm.annotation.description().isEmpty()) { + writer.println(" Açıklama: " + pm.annotation.description()); + } + if (!pm.annotation.example().isEmpty()) { + writer.println(" Örnek: " + pm.annotation.example()); + } + if (pm.permissionInfo != null) { + writer.println(" Gerekli Yetki: " + pm.permissionInfo.value()); + } + if (pm.cacheInfo != null) { + writer.println(" Önbellek: " + pm.cacheInfo.duration() + " " + pm.cacheInfo.unit().toString().toLowerCase()); + } + writer.println(); + } + + if (!dyn.relationalMethods.isEmpty()) { + writer.println(" [İlişkisel Placeholderlar]"); + for (Map.Entry entry : dyn.relationalMethods.entrySet()) { + PlaceholderMethod pm = entry.getValue(); + String fullPapi = "%rel_" + dyn.getIdentifier() + "_" + entry.getKey() + "%"; + writer.println("• " + fullPapi); + if (!pm.relAnnotation.description().isEmpty()) { + writer.println(" Açıklama: " + pm.relAnnotation.description()); + } + writer.println(); + } + } + writer.println("==================================================\n"); + } + } + plugin.getLogger().info("Placeholder dokümantasyonu oluşturuldu: " + file.getPath()); + } catch (IOException e) { + plugin.getLogger().log(Level.SEVERE, "Dokümantasyon oluşturulurken hata meydana geldi.", e); + } + } + public void unregisterAll() { if (!registeredExpansions.isEmpty()) { plugin.getLogger().info(registeredExpansions.size() + " adet placeholder grubu kaldırılıyor..."); @@ -204,24 +231,34 @@ private static final class PlaceholderMethod { final Object instance; final PlaceholderIdentifier annotation; final RelationalPlaceholder relAnnotation; + final Cache cacheInfo; + final Middleware middlewareInfo; + final RequirePermission permissionInfo; PlaceholderMethod(Method method, Object instance) { this.method = method; this.instance = instance; this.annotation = method.getAnnotation(PlaceholderIdentifier.class); this.relAnnotation = method.getAnnotation(RelationalPlaceholder.class); + this.cacheInfo = method.getAnnotation(Cache.class); + this.middlewareInfo = method.getAnnotation(Middleware.class); + this.permissionInfo = method.getAnnotation(RequirePermission.class); } } private static class DynamicExpansion extends PlaceholderExpansion implements Relational { + private static final long DEFAULT_ASYNC_CACHE_MS = 2000L; + private final JavaPlugin plugin; private final Placeholder placeholderInfo; - private final Map standardMethods; - private final Map relationalMethods; + final Map standardMethods; + final Map relationalMethods; private final String defaultErrorText; private final boolean debug; + private final Map, PlaceholderMiddleware> middlewareInstances = new ConcurrentHashMap<>(); private final Map cache = new ConcurrentHashMap<>(); + private final Set pendingTasks = ConcurrentHashMap.newKeySet(); DynamicExpansion(JavaPlugin plugin, Placeholder info, Map standardMethods, Map relationalMethods, String defaultErrorText, boolean debug) { this.plugin = plugin; @@ -294,43 +331,130 @@ public String onRequest(OfflinePlayer player, @NotNull String params) { } private String handleStandard(OfflinePlayer viewer, PlaceholderMethod pMethod, String arg, String fullParams) { - if (pMethod.annotation.async()) { - String cacheKey = "std:" + (viewer != null ? viewer.getUniqueId() : "null") + ":" + fullParams; - CachedResult cached = cache.get(cacheKey); + if (pMethod.permissionInfo != null) { + if (viewer == null || !viewer.isOnline()) { + return pMethod.permissionInfo.onDeny(); + } + if (!viewer.getPlayer().hasPermission(pMethod.permissionInfo.value())) { + return pMethod.permissionInfo.onDeny(); + } + } + + String cacheKey = "std:" + (viewer != null ? viewer.getUniqueId() : "null") + ":" + fullParams; + + CachedResult cached = cache.get(cacheKey); + long cacheDuration = pMethod.cacheInfo != null + ? pMethod.cacheInfo.unit().toMillis(pMethod.cacheInfo.duration()) + : DEFAULT_ASYNC_CACHE_MS; - if (cached != null && !cached.isExpired()) { - return cached.value; + if (cached != null && !cached.isExpired(cacheDuration)) { + return cached.value; + } + + if (pMethod.annotation.async()) { + if (pendingTasks.contains(cacheKey)) { + return cached != null ? cached.value : pMethod.annotation.onLoading(); } + pendingTasks.add(cacheKey); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - String result = executeStandard(viewer, pMethod, arg); - cache.put(cacheKey, new CachedResult(result)); + try { + String rawResult = executeStandard(viewer, pMethod, arg); + String finalResult = applyMiddleware(rawResult, pMethod); + cache.put(cacheKey, new CachedResult(finalResult)); + } finally { + pendingTasks.remove(cacheKey); + } }); return cached != null ? cached.value : pMethod.annotation.onLoading(); } - return executeStandard(viewer, pMethod, arg); + String rawResult = executeStandard(viewer, pMethod, arg); + String finalResult = applyMiddleware(rawResult, pMethod); + + if (pMethod.cacheInfo != null) { + cache.put(cacheKey, new CachedResult(finalResult)); + } + + return finalResult; } private String handleRelational(Player one, Player two, PlaceholderMethod rMethod, String arg, String fullParams) { - if (rMethod.relAnnotation.async()) { - String cacheKey = "rel:" + one.getUniqueId() + ":" + two.getUniqueId() + ":" + fullParams; - CachedResult cached = cache.get(cacheKey); + if (rMethod.permissionInfo != null) { + if (!one.hasPermission(rMethod.permissionInfo.value())) { + return rMethod.permissionInfo.onDeny(); + } + } + + String cacheKey = "rel:" + one.getUniqueId() + ":" + two.getUniqueId() + ":" + fullParams; - if (cached != null && !cached.isExpired()) { - return cached.value; + CachedResult cached = cache.get(cacheKey); + long cacheDuration = rMethod.cacheInfo != null + ? rMethod.cacheInfo.unit().toMillis(rMethod.cacheInfo.duration()) + : DEFAULT_ASYNC_CACHE_MS; + + if (cached != null && !cached.isExpired(cacheDuration)) { + return cached.value; + } + + if (rMethod.relAnnotation.async()) { + if (pendingTasks.contains(cacheKey)) { + return cached != null ? cached.value : rMethod.relAnnotation.onLoading(); } + pendingTasks.add(cacheKey); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { - String result = executeRelational(one, two, rMethod, arg); - cache.put(cacheKey, new CachedResult(result)); + try { + String rawResult = executeRelational(one, two, rMethod, arg); + String finalResult = applyMiddleware(rawResult, rMethod); + cache.put(cacheKey, new CachedResult(finalResult)); + } finally { + pendingTasks.remove(cacheKey); + } }); return cached != null ? cached.value : rMethod.relAnnotation.onLoading(); } - return executeRelational(one, two, rMethod, arg); + String rawResult = executeRelational(one, two, rMethod, arg); + String finalResult = applyMiddleware(rawResult, rMethod); + + if (rMethod.cacheInfo != null) { + cache.put(cacheKey, new CachedResult(finalResult)); + } + + return finalResult; + } + + private String applyMiddleware(String rawResult, PlaceholderMethod pMethod) { + if (pMethod.middlewareInfo == null || rawResult == null) { + return rawResult == null ? "" : rawResult; + } + + Object currentResult = rawResult; + try { + for (Class middlewareClass : pMethod.middlewareInfo.value()) { + PlaceholderMiddleware middleware = middlewareInstances.computeIfAbsent(middlewareClass, clazz -> { + try { + return (PlaceholderMiddleware) clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Middleware sınıfı başlatılamadı: " + clazz.getName(), e); + return null; + } + }); + + if (middleware != null) { + currentResult = middleware.process(currentResult); + if (currentResult == null) return ""; + } + } + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "Middleware uygulanırken hata oluştu (" + pMethod.method.getName() + ")", e); + return getErrorText(pMethod); + } + + return String.valueOf(currentResult); } private String executeStandard(OfflinePlayer viewer, PlaceholderMethod pMethod, String argument) { @@ -405,8 +529,8 @@ private static class CachedResult { this.timestamp = System.currentTimeMillis(); } - boolean isExpired() { - return System.currentTimeMillis() - timestamp > 2000; + boolean isExpired(long durationMillis) { + return System.currentTimeMillis() - timestamp > durationMillis; } } } \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/annotations/Cache.java b/src/main/java/com/bentahsin/benthpapimanager/annotations/Cache.java new file mode 100644 index 0000000..0d5985b --- /dev/null +++ b/src/main/java/com/bentahsin/benthpapimanager/annotations/Cache.java @@ -0,0 +1,29 @@ +package com.bentahsin.benthpapimanager.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.TimeUnit; + +/** + * Bir placeholder metodunun sonucunun ne kadar süreyle önbelleğe alınacağını belirtir. + * Sık değişmeyen veriler için performansı artırır. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Cache { + + /** + * Önbelleğin geçerlilik süresi. + * @return Süre değeri. + */ + long duration(); + + /** + * Sürenin zaman birimi (saniye, dakika vb.). + * Varsayılan olarak saniyedir. + * @return Zaman birimi. + */ + TimeUnit unit() default TimeUnit.SECONDS; +} \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/annotations/Middleware.java b/src/main/java/com/bentahsin/benthpapimanager/annotations/Middleware.java new file mode 100644 index 0000000..511e306 --- /dev/null +++ b/src/main/java/com/bentahsin/benthpapimanager/annotations/Middleware.java @@ -0,0 +1,24 @@ +package com.bentahsin.benthpapimanager.annotations; + +import com.bentahsin.benthpapimanager.middleware.PlaceholderMiddleware; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Bir placeholder metodunun sonucunu, PAPI'ye döndürmeden önce + * işleyecek olan bir veya daha fazla ara katman sınıfını belirtir. + * Bu, sonuçları formatlamak veya değiştirmek için kullanılır. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Middleware { + /** + * Sonucu işleyecek olan PlaceholderMiddleware arayüzünü uygulayan sınıflar. + * Birden fazla middleware belirtilirse, sırayla uygulanırlar. + * @return Middleware sınıfları. + */ + Class[] value(); +} \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/annotations/PlaceholderIdentifier.java b/src/main/java/com/bentahsin/benthpapimanager/annotations/PlaceholderIdentifier.java index 38d8c1c..41a0a91 100644 --- a/src/main/java/com/bentahsin/benthpapimanager/annotations/PlaceholderIdentifier.java +++ b/src/main/java/com/bentahsin/benthpapimanager/annotations/PlaceholderIdentifier.java @@ -38,4 +38,16 @@ * @return Yükleniyor metni. */ String onLoading() default "§eHesaplanıyor...§r"; + + /** + * Placeholder açıklaması için opsiyonel alan + * @return Açıklama + */ + String description() default ""; + + /** + * Örnek kullanım için opsiyonel alan + * @return Örnek + */ + String example() default ""; } \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/annotations/RelationalPlaceholder.java b/src/main/java/com/bentahsin/benthpapimanager/annotations/RelationalPlaceholder.java index c2cceaa..088f080 100644 --- a/src/main/java/com/bentahsin/benthpapimanager/annotations/RelationalPlaceholder.java +++ b/src/main/java/com/bentahsin/benthpapimanager/annotations/RelationalPlaceholder.java @@ -37,4 +37,16 @@ * @return Yükleniyor metni. */ String onLoading() default "§eHesaplanıyor...§r"; + + /** + * Placeholder açıklaması için opsiyonel alan + * @return Açıklama + */ + String description() default ""; + + /** + * Örnek kullanım için opsiyonel alan + * @return Örnek + */ + String example() default ""; } \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/annotations/RequirePermission.java b/src/main/java/com/bentahsin/benthpapimanager/annotations/RequirePermission.java new file mode 100644 index 0000000..06b16cf --- /dev/null +++ b/src/main/java/com/bentahsin/benthpapimanager/annotations/RequirePermission.java @@ -0,0 +1,24 @@ +package com.bentahsin.benthpapimanager.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Bir placeholder'ı kullanmak için oyuncunun sahip olması gereken yetkiyi belirtir. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface RequirePermission { + /** + * Gerekli yetki düğümü (örn: "admin.see.ram"). + */ + String value(); + + /** + * Oyuncunun yetkisi yoksa döndürülecek mesaj. + * Boş bırakılırsa hiçbir şey döndürmez (boş string). + */ + String onDeny() default ""; +} \ No newline at end of file diff --git a/src/main/java/com/bentahsin/benthpapimanager/middleware/PlaceholderMiddleware.java b/src/main/java/com/bentahsin/benthpapimanager/middleware/PlaceholderMiddleware.java new file mode 100644 index 0000000..8a3e16c --- /dev/null +++ b/src/main/java/com/bentahsin/benthpapimanager/middleware/PlaceholderMiddleware.java @@ -0,0 +1,15 @@ +package com.bentahsin.benthpapimanager.middleware; + +/** + * Placeholder'dan dönen ham veriyi işlemek için bir arayüz. + * Bu arayüzü uygulayan sınıflar, @Middleware anotasyonu ile kullanılabilir. + */ +@FunctionalInterface +public interface PlaceholderMiddleware { + /** + * Placeholder metodundan gelen ham sonucu işler ve formatlanmış bir String döndürür. + * @param input Placeholder metodunun orijinal dönüş değeri (örn: Integer, Double, String). + * @return Formatlanmış veya işlenmiş sonuç. + */ + String process(Object input); +} \ No newline at end of file