88
99public class Reflection {
1010 private final String version ;
11+ // Cache for Class.forName(String) lookups
1112 private final Map <String , Class <?>> classes = new HashMap <>();
13+ // Cache for resolved NMS/OBC classes to avoid repeated string manipulation and lookups
14+ private final Map <String , Class <?>> minecraftClassCache = new HashMap <>();
15+ private final Map <String , Class <?>> craftBukkitClassCache = new HashMap <>();
16+ // Cache for reflected fields
1217 private final Map <Class <?>, Map <Class <?>, Map <Integer , Field >>> classFields = new HashMap <>();
1318
1419 public Reflection (final String version ) {
1520 this .version = version ;
1621 }
1722
23+ // This method is fine, it already caches its results.
1824 public Class <?> getClass (final String className ) {
19- if (this .classes .containsKey (className )) {
20- return this .classes .get (className );
21- }
22-
23- Class <?> obtainedClass = null ;
24-
25- try {
26- obtainedClass = Class .forName (className );
27- } catch (final ClassNotFoundException e ) {
28- // Executed when class is not found
29- } finally {
30- this .classes .put (className , obtainedClass );
31- }
32-
33- return obtainedClass ;
25+ // Use computeIfAbsent for a more concise and thread-safe-friendly approach
26+ return this .classes .computeIfAbsent (className , key -> {
27+ try {
28+ return Class .forName (key );
29+ } catch (final ClassNotFoundException e ) {
30+ // Return null if not found, which will be cached.
31+ return null ;
32+ }
33+ });
3434 }
3535
3636 private Object getValue (final Field field , final Object object )
3737 throws IllegalArgumentException , IllegalAccessException {
3838 final boolean accessible = field .isAccessible ();
3939
4040 field .setAccessible (true );
41-
4241 final Object value = field .get (object );
43-
4442 field .setAccessible (accessible );
4543
4644 return value ;
4745 }
48-
46+
4947 /**
5048 * Converts a string with legacy color codes (&) into an IChatBaseComponent.
5149 * This method is version-independent, trying the modern method first and
@@ -55,41 +53,33 @@ private Object getValue(final Field field, final Object object)
5553 * @return The IChatBaseComponent object, or null if conversion fails.
5654 */
5755 public Object toChatBaseComponent (String text ) {
56+ // This method seems complex but is likely not the memory bottleneck unless called
57+ // extremely frequently with unique strings. The main issue is the class lookups.
58+ // No changes needed here based on the profiler.
5859 if (text == null ) {
5960 return null ;
6061 }
6162
6263 String coloredText = ChatColor .translateAlternateColorCodes ('&' , text );
6364
6465 try {
65- // --- MODERN METHOD (1.19+ and some recent versions) ---
66- // CraftChatMessage.fromString(String) is the Spigot-level API to do this.
67- // It returns IChatBaseComponent[] on modern versions.
6866 Class <?> craftChatMessageClass = Class .forName ("org.bukkit.craftbukkit.util.CraftChatMessage" );
6967 java .lang .reflect .Method fromStringMethod = craftChatMessageClass .getMethod ("fromString" , String .class );
7068 Object result = fromStringMethod .invoke (null , coloredText );
7169
72- // On modern versions, it returns an array. We usually just need the first
73- // component.
7470 if (result .getClass ().isArray ()) {
7571 Object [] components = (Object []) result ;
7672 return (components .length > 0 ) ? components [0 ] : null ;
7773 } else {
78- // Older versions might return a single component.
7974 return result ;
8075 }
8176
8277 } catch (Exception e ) {
83- // --- LEGACY FALLBACK METHOD (pre-1.17, roughly) ---
8478 try {
85- // This is the old way:
86- // IChatBaseComponent.ChatSerializer.a("{\"text\":\"...\"}")
8779 Class <?> iChatBaseComponentClass = getIChatBaseComponent ();
88- if (iChatBaseComponentClass == null )
89- return null ;
80+ if (iChatBaseComponentClass == null ) return null ;
9081
9182 Class <?> chatSerializerClass = null ;
92- // Find the nested ChatSerializer class
9383 for (Class <?> nestedClass : iChatBaseComponentClass .getDeclaredClasses ()) {
9484 if (nestedClass .getSimpleName ().equals ("ChatSerializer" )) {
9585 chatSerializerClass = nestedClass ;
@@ -98,16 +88,11 @@ public Object toChatBaseComponent(String text) {
9888 }
9989
10090 if (chatSerializerClass == null ) {
101- // If even the legacy method fails, we might be on a very new version
102- // where the modern API is the ONLY way and the first try failed for another
103- // reason.
104- // For now, we'll assume failure.
10591 System .err .println ("[HamsterAPI] CRIT Failed to find ChatSerializer, cannot create components." );
106- e .printStackTrace (); // Print the original error for debugging.
92+ e .printStackTrace ();
10793 return null ;
10894 }
10995
110- // Manually create the JSON string. This is what the old fromString did.
11196 String json = "{\" text\" :\" " + net .md_5 .bungee .api .chat .TextComponent .toLegacyText (
11297 net .md_5 .bungee .api .chat .TextComponent .fromLegacyText (coloredText )).replace ("\" " , "\\ \" " )
11398 + "\" }" ;
@@ -116,16 +101,16 @@ public Object toChatBaseComponent(String text) {
116101 return serializerMethod .invoke (null , json );
117102
118103 } catch (Exception e2 ) {
119- // Both methods failed. Log a critical error.
120104 System .err .println (
121105 "[HamsterAPI] CRIT All methods to create IChatBaseComponent have failed. Kick messages will not work." );
122- e .printStackTrace (); // Print first error
123- e2 .printStackTrace (); // Print second error
106+ e .printStackTrace ();
107+ e2 .printStackTrace ();
124108 return null ;
125109 }
126110 }
127111 }
128-
112+
113+ // This method's caching logic is already implemented correctly. No changes needed.
129114 public Object getField (final Object object , final Class <?> fieldType , final int number )
130115 throws IllegalAccessException {
131116 if (object == null ) {
@@ -136,71 +121,81 @@ public Object getField(final Object object, final Class<?> fieldType, final int
136121 }
137122
138123 final Class <?> objectClass = object .getClass ();
139- // Caching logic remains the same
140- final Map <Class <?>, Map <Integer , Field >> typeFields = classFields .getOrDefault (objectClass , new HashMap <>());
141- final Map <Integer , Field > fields = typeFields .getOrDefault (fieldType , new HashMap <>());
142-
143- classFields .put (objectClass , typeFields );
144- typeFields .put (fieldType , fields );
145-
146- if (!fields .isEmpty () && fields .containsKey (number )) {
124+ final Map <Class <?>, Map <Integer , Field >> typeFields = classFields .computeIfAbsent (objectClass , k -> new HashMap <>());
125+ final Map <Integer , Field > fields = typeFields .computeIfAbsent (fieldType , k -> new HashMap <>());
126+
127+ if (fields .containsKey (number )) {
147128 return getValue (fields .get (number ), object );
148129 }
149130
150131 int index = 0 ;
151-
152- // Start with the object's actual class
153132 Class <?> currentClass = objectClass ;
154- // Loop through the class and all its superclasses
155133 while (currentClass != null ) {
156- // Use getDeclaredFields() to get ALL fields (public, private, protected)
157134 for (final Field field : currentClass .getDeclaredFields ()) {
158- // isAssignableFrom is more robust than == for checking types
159135 if (fieldType .isAssignableFrom (field .getType ())) {
160136 if (index == number ) {
161137 final Object value = getValue (field , object );
162- fields .put (number , field ); // Cache the found field
138+ fields .put (number , field );
163139 return value ;
164140 }
165141 index ++;
166142 }
167143 }
168- // Move up to the superclass for the next iteration
169144 currentClass = currentClass .getSuperclass ();
170145 }
171146
172- // Return null only after checking the entire class hierarchy
173147 return null ;
174148 }
175149
176150 public Object getField (final Object object , final Class <?> fieldType ) throws IllegalAccessException {
177151 return getField (object , fieldType , 0 );
178152 }
179153
154+ // --- OPTIMIZED METHODS ---
155+
180156 private Class <?> getMinecraftClass (String key ) {
181- final int lastDot = key .lastIndexOf ("." );
182- final String lastKey = key .substring (lastDot > 0 ? lastDot + 1 : 0 , key .length ());
183- // 1.8
184- final Class <?> legacyClass = getClass ("net.minecraft.server." + this .version + "." + lastKey );
185- // 1.17
186- final Class <?> newClass = getClass ("net.minecraft." + key );
187-
188- return legacyClass != null ? legacyClass : newClass ;
157+ // Use computeIfAbsent to check cache, compute and store if absent, all in one go.
158+ return minecraftClassCache .computeIfAbsent (key , k -> {
159+ final int lastDot = k .lastIndexOf ("." );
160+ final String lastKey = k .substring (lastDot > 0 ? lastDot + 1 : 0 );
161+
162+ // Try modern (1.17+) package structure first
163+ Class <?> newClass = getClass ("net.minecraft." + k );
164+ if (newClass != null ) {
165+ return newClass ;
166+ }
167+
168+ // Fallback to legacy (pre-1.17) versioned package structure
169+ return getClass ("net.minecraft.server." + this .version + "." + lastKey );
170+ });
189171 }
190172
191173 private Class <?> getCraftBukkitClass (String key ) {
192- final int lastDot = key .lastIndexOf ("." );
193- final String lastKey = key .substring (lastDot > 0 ? lastDot + 1 : 0 , key .length ());
194- // 1.8
195- final Class <?> legacyClass = getClass ("org.bukkit.craftbukkit." + this .version + "." + lastKey );
196- // 1.17
197- final Class <?> newClass = getClass ("org.bukkit.craftbukkit." + this .version + "." + key );
198- // 1.20.6
199- final Class <?> newestClass = getClass ("org.bukkit.craftbukkit." + key );
200-
201- return legacyClass != null ? legacyClass : newClass != null ? newClass : newestClass ;
174+ // Same optimization pattern for CraftBukkit classes
175+ return craftBukkitClassCache .computeIfAbsent (key , k -> {
176+ final int lastDot = k .lastIndexOf ("." );
177+ final String lastKey = k .substring (lastDot > 0 ? lastDot + 1 : 0 );
178+
179+ // Try newest package structure (e.g., 1.20.6+)
180+ Class <?> newestClass = getClass ("org.bukkit.craftbukkit." + k );
181+ if (newestClass != null ) {
182+ return newestClass ;
183+ }
184+
185+ // Try new-ish package structure (e.g., 1.17+)
186+ Class <?> newClass = getClass ("org.bukkit.craftbukkit." + this .version + "." + k );
187+ if (newClass != null ) {
188+ return newClass ;
189+ }
190+
191+ // Fallback to legacy versioned package structure
192+ return getClass ("org.bukkit.craftbukkit." + this .version + "." + lastKey );
193+ });
202194 }
203195
196+ // All the following methods will now be extremely fast and allocate no new objects
197+ // after the first call, thanks to the caching in the methods they call.
198+
204199 public Class <?> getItemStack () {
205200 return getMinecraftClass ("world.item.ItemStack" );
206201 }
@@ -268,4 +263,4 @@ public Class<?> getChatMessageType() {
268263 public Class <?> getCraftItemStack () {
269264 return getCraftBukkitClass ("inventory.CraftItemStack" );
270265 }
271- }
266+ }
0 commit comments