1111import java .lang .invoke .MethodHandles ;
1212import java .lang .invoke .MethodType ;
1313import java .lang .reflect .Constructor ;
14+ import java .util .Arrays ;
1415import java .util .logging .Level ;
1516
1617/**
@@ -90,9 +91,15 @@ public static class NmsItemSession {
9091 private static final MethodHandle GET_TAG ;
9192 private static final MethodHandle SET_TAG ;
9293 private static final Constructor <?> nbtTagConstructor ;
93- private static final boolean REFLECTION_READY ;
94+ private static final MethodHandle GET_COMPOUND ;
95+ private static final MethodHandle GET_NESTED_COMPOUND ;
96+ private static final MethodHandle SET_NESTED_COMPOUND ;
9497
98+ private static final boolean REFLECTION_READY ;
9599 private final Object nmsItemCopy ;
100+ private final ItemStack bukkitItem ;
101+ private boolean finalize = false ;
102+ private CompoundState compoundState = CompoundState .NOT_CREATED ;
96103
97104 static {
98105 Constructor <?> tagConstructor = null ;
@@ -101,6 +108,9 @@ public static class NmsItemSession {
101108 MethodHandle hasNBTTag = null ;
102109 MethodHandle bukkitCopy = null ;
103110 MethodHandle nMSCopyItem = null ;
111+ MethodHandle getCompoundM = null ;
112+ MethodHandle getNestedCompound = null ;
113+ MethodHandle setNestedCompound = null ;
104114 boolean reflectionDone = false ;
105115
106116 try {
@@ -110,6 +120,8 @@ public static class NmsItemSession {
110120 final Class <?> craftItemStack = Class .forName (craftPath + ".inventory.CraftItemStack" );
111121 final Class <?> nmsItemStack = Class .forName (nmsPath + ".ItemStack" );
112122 final Class <?> nbtTagCompound = Class .forName (getNbtTagPath ());
123+ final Class <?> nbtTagBase = Class .forName (getNbtTagBasePath ());
124+
113125 final MethodHandles .Lookup lookup = MethodHandles .lookup ();
114126
115127 nMSCopyItem = lookup .findStatic (craftItemStack , "asNMSCopy" ,
@@ -124,24 +136,31 @@ public static class NmsItemSession {
124136 setNBTTag = lookup .findVirtual (nmsItemStack , "setTag" ,
125137 MethodType .methodType (void .class , nbtTagCompound ));
126138
139+ setNestedCompound = lookup .findVirtual (nbtTagCompound , "set" , MethodType .methodType (void .class , String .class , nbtTagBase ));
140+ getNestedCompound = lookup .findVirtual (nbtTagCompound , "get" , MethodType .methodType (nbtTagBase , String .class ));
141+ getCompoundM = lookup .findVirtual (nbtTagCompound , "getCompound" , MethodType .methodType (nbtTagCompound , String .class ));
142+
127143 tagConstructor = nbtTagCompound .getConstructor ();
128144 reflectionDone = true ;
129- } catch (ClassNotFoundException | NoSuchMethodException e ) {
130- logger .logError (e , () -> "Failed to initialize all NMS methods needed." );
131- } catch (IllegalAccessException e ) {
132- throw new RuntimeException (e );
145+ } catch (IllegalAccessException | ClassNotFoundException | NoSuchMethodException e ) {
146+ logger .logError (e , () -> "Failed to initialize all NMS methods needed for legacy minecraft." );
133147 }
134148 nbtTagConstructor = tagConstructor ;
135149 SET_TAG = setNBTTag ;
136150 GET_TAG = getNBTTag ;
137151 HAS_TAG = hasNBTTag ;
138152 AS_BUKKIT_ITEM_COPY = bukkitCopy ;
139153 NMS_ITEM_COPY = nMSCopyItem ;
154+ GET_COMPOUND = getCompoundM ;
155+ GET_NESTED_COMPOUND = getNestedCompound ;
156+ SET_NESTED_COMPOUND = setNestedCompound ;
140157 REFLECTION_READY = reflectionDone ;
158+
141159 }
142160
143161 private NmsItemSession (@ Nonnull ItemStack item ) {
144162 this .nmsItemCopy = toNmsItemStack (item );
163+ this .bukkitItem = item .clone ();
145164 }
146165
147166 /**
@@ -172,64 +191,130 @@ public boolean hasTag() {
172191 }
173192
174193 /**
175- * Returns the existing {@link CompoundTag} if one is present,
176- * otherwise creates a new one.
194+ * Checks whether this item contains an {@code NBTTagCompound} with the given name.
195+ * <p>
196+ * If the name is empty, the <strong>root compound</strong> is evaluated instead.
197+ * Both root and nested compounds are considered valid targets.
198+ *
199+ * @param name the custom key of the nested compound. To target the root compound,
200+ * use an empty string or {@link #hasTag()}.
201+ * @return {@code true} if the specified (or root) compound exists
202+ */
203+ public boolean hasTag (@ Nonnull final String name ) {
204+ try {
205+ if (!hasTag ()) return false ;
206+ Object root = GET_TAG .invoke (nmsItemCopy );
207+ if (name .isEmpty ()) return root != null ;
208+ Object nested = GET_NESTED_COMPOUND .invoke (root , name );
209+ return nested != null ;
210+ } catch (Throwable ignored ) {
211+ return false ;
212+ }
213+ }
214+
215+ /**
216+ * Returns the root {@link CompoundTag} of this item, creating one if it does not exist.
217+ * <p>
218+ * This method always operates on the root compound. If you want a nested compound,
219+ * use {@link #getOrCreateCompound(String)} with a specific name.
177220 *
178- * @return the existing {@link CompoundTag} or a new instance if none exists.
179- * Returns {@code null} only if reflection failed or the underlying
180- * NBTTagCompound could not be created.
221+ * @return the root {@link CompoundTag}, never {@code null} unless reflection failed.
181222 */
182223 @ Nullable
183224 public CompoundTag getOrCreateCompound () {
184- try {
185- Object tag ;
186- if (hasTag ()) {
187- tag = GET_TAG .invoke (nmsItemCopy );
188- } else {
189- tag = nbtTagConstructor .newInstance ();
190- SET_TAG .invoke (nmsItemCopy , tag );
191- }
192- return new CompoundTag (tag );
225+ return getCompoundTag ("" , false );
226+ }
193227
194- } catch (Throwable e ) {
195- logger .logError (e , () -> "Failed to initialize CompoundTag" );
196- }
197- return null ;
228+ /**
229+ * Returns a {@link CompoundTag} with the given name, creating it if it does not exist.
230+ * <p>
231+ * If {@code name} is empty (""), this will return the root compound, which is equivalent
232+ * to {@link #getOrCreateCompound()}.
233+ * <p>
234+ * Use a non-empty name if you want a nested compound separate from the root.
235+ *
236+ * @param name the name of the nested compound, or empty string for root.
237+ * @return the existing or newly created {@link CompoundTag}, or {@code null} if reflection failed.
238+ */
239+ @ Nullable
240+ public CompoundTag getOrCreateCompound (@ Nonnull final String name ) {
241+ return getCompoundTag (name , true );
198242 }
199243
244+
200245 /**
201- * Returns the existing {@link CompoundTag} if one is present.
246+ * Returns the root {@link CompoundTag} if present.
247+ * <p>
248+ * This method does not create a new compound. Use {@link #getOrCreateCompound()} to
249+ * create a root compound if it does not exist.
202250 *
203- * @return the existing {@link CompoundTag} or {@code null} if none exists .
251+ * @return the root {@link CompoundTag} if present, otherwise {@code null}.
204252 */
205253 @ Nullable
206254 public CompoundTag getCompound () {
207- try {
208- if (!hasTag ()) return null ;
209- return new CompoundTag (GET_TAG .invoke (this .nmsItemCopy ));
210- } catch (Throwable e ) {
211- logger .logError (e , () -> "Failed to initialize CompoundTag" );
212- }
213- return null ;
255+ return getCompound ("" , false );
214256 }
215257
216258 /**
217- * Applies the current CompoundTag to the ItemStack and returns
218- * a new Bukkit ItemStack instance.
259+ * Returns the {@link CompoundTag} with the given name if present.
260+ * <p>
261+ * If {@code name} is empty (""), this returns the root compound.
262+ * For a nested compound, pass a non-empty name.
263+ * <p>
264+ * This method does not create a compound; use {@link #getOrCreateCompound(String)}
265+ * to create one if it does not exist.
219266 *
220- * @param tag the {@link CompoundTag} instance that wraps NBTTagCompound .
221- * @return Returns the copy of your itemStack with the nbt set .
267+ * @param name the name of the nested compound, or empty string for root .
268+ * @return the existing {@link CompoundTag} if present, otherwise {@code null} .
222269 */
223270 @ Nullable
224- public ItemStack apply (@ Nonnull final CompoundTag tag ) {
225- if (!REFLECTION_READY || nmsItemCopy == null ) return null ;
271+ public CompoundTag getCompound (@ Nonnull final String name ) {
272+ return getCompound (name , true );
273+ }
274+
275+ /**
276+ * Applies the current NBT data of this item to the underlying {@link ItemStack} and
277+ * returns a new Bukkit {@link ItemStack} instance.
278+ * <p>
279+ * This method always applies the root {@link CompoundTag}, including any nested compounds
280+ * created via {@link #getOrCreateCompound(String)}. The returned item will contain the
281+ * full NBT structure currently set in this session.
282+ * <p>
283+ * The method checks the {@link CompoundState} before applying changes:
284+ * <ul>
285+ * <li>{@link CompoundState#CREATED}: Compound exists and will be applied.</li>
286+ * <li>{@link CompoundState#NULL}: No compound exists, nothing is applied.</li>
287+ * <li>{@link CompoundState#ERROR}: Reflection failed or compound initialization failed,
288+ * nothing is applied.</li>
289+ * <li>{@link CompoundState#NOT_CREATED}: No compound has been created yet.</li>
290+ * </ul>
291+ * <p>
292+ * Use {@link #getOrCreateCompound()} or {@link #getOrCreateCompound(String)} to ensure a
293+ * compound exists before calling this method.
294+ *
295+ * @return a new {@link ItemStack} containing the applied NBT, or the original {@link ItemStack}
296+ * if the compound was not created or an error occurred.
297+ */
298+ @ Nonnull
299+ public ItemStack finalizeChanges () {
300+ if (!REFLECTION_READY || nmsItemCopy == null ) return this .bukkitItem ;
301+ if (compoundState != CompoundState .CREATED ) {
302+ logger .log (() -> "FinalizeChanges: " + compoundState .getMessage ());
303+ return this .bukkitItem ;
304+ }
226305 try {
227- SET_TAG .invoke (nmsItemCopy , tag .getHandle ());
306+ Object compound = GET_TAG .invoke (nmsItemCopy );
307+ if (compound == null ) {
308+ logger .log (() -> "Failed to initialize the item creation, because the compound is not created yet." );
309+ return this .bukkitItem ;
310+ }
311+ SET_TAG .invoke (nmsItemCopy , compound );
312+ finalize = true ;
228313 return (ItemStack ) AS_BUKKIT_ITEM_COPY .invoke (nmsItemCopy );
229314 } catch (Throwable e ) {
230315 logger .logError (e , () -> "Failed to apply back to itemStack" );
231316 }
232- return null ;
317+ return this . bukkitItem ;
233318 }
234319
235320 /**
@@ -247,6 +332,79 @@ private Object toNmsItemStack(@Nonnull final ItemStack item) {
247332 return null ;
248333 }
249334
335+ /**
336+ * Internal helper to get or create a {@link CompoundTag}.
337+ * <p>
338+ * If {@code usingName} is true and the {@code name} parameter is empty,
339+ * a debug log is written suggesting to use {@link #getOrCreateCompound()} instead
340+ * for the root compound.
341+ *
342+ * @param name the name of the nested compound; empty string returns the root.
343+ * @param usingName whether this call is intended for a named compound.
344+ * @return the requested {@link CompoundTag}, or null if reflection fails.
345+ */
346+ private CompoundTag getCompoundTag (final String name , final boolean usingName ) {
347+ if (usingName && name .isEmpty ())
348+ logger .log (Level .FINE , () -> "Empty string passed to getOrCreateCompound(name). Use getOrCreateCompound() for root instead." );
349+ try {
350+ Object root ;
351+ Object nested = null ;
352+ if (hasTag ()) {
353+ root = GET_TAG .invoke (nmsItemCopy );
354+ } else {
355+ root = nbtTagConstructor .newInstance ();
356+ SET_TAG .invoke (nmsItemCopy , root );
357+ }
358+ if (!name .isEmpty ()) {
359+ nested = GET_NESTED_COMPOUND .invoke (root , name );
360+ if (nested == null ) {
361+ nested = nbtTagConstructor .newInstance ();
362+ SET_NESTED_COMPOUND .invoke (root , name , nested );
363+ }
364+ }
365+ this .compoundState = CompoundState .CREATED ;
366+ return new CompoundTag (nested != null ? nested : root );
367+ } catch (Throwable e ) {
368+ logger .logError (e , () -> "Failed to initialize CompoundTag" );
369+ this .compoundState = CompoundState .ERROR ;
370+ }
371+ return null ;
372+ }
373+
374+ /**
375+ * Internal helper to get a {@link CompoundTag} if present.
376+ * <p>
377+ * If {@code usingName} is true and the {@code name} parameter is empty,
378+ * a debug log is written suggesting to use {@link #getCompound()} instead
379+ * for the root compound.
380+ *
381+ * @param name the name of the nested compound; empty string returns the root.
382+ * @param usingName whether this call is intended for a named compound.
383+ * @return the requested {@link CompoundTag}, or null if not present or reflection fails.
384+ */
385+ private CompoundTag getCompound (final String name , final boolean usingName ) {
386+ if (usingName && name .isEmpty ())
387+ logger .log (Level .FINE , () -> "Empty string passed to getCompound(name). Use getCompound() for root instead." );
388+
389+ try {
390+ if (!hasTag ()) return null ;
391+ Object root = GET_TAG .invoke (nmsItemCopy );
392+ Object nested = null ;
393+ if (!name .isEmpty ()) {
394+ nested = GET_NESTED_COMPOUND .invoke (root , name );
395+ if (nested == null ) {
396+ logger .log (Level .CONFIG , () -> "Requested nested compound '" + name + "' not found. Returning root instead." );
397+ }
398+ }
399+ this .compoundState = CompoundState .CREATED ;
400+ return new CompoundTag (nested != null ? nested : root );
401+ } catch (Throwable e ) {
402+ logger .logError (e , () -> "Failed to initialize CompoundTag" );
403+ this .compoundState = CompoundState .ERROR ;
404+ }
405+ return null ;
406+ }
407+
250408 private static String getCraftBukkitPath () {
251409 return "org.bukkit.craftbukkit." + getPackageVersion ();
252410 }
@@ -279,6 +437,14 @@ public static class CompoundSession {
279437 final Class <?> nbtTag = Class .forName (getNbtTagPath ());
280438 final MethodHandles .Lookup lookup = MethodHandles .lookup ();
281439
440+ Arrays .stream (nbtTag .getMethods ()).forEach (method -> {
441+ System .out .println ("######################################" );
442+ System .out .println ("m " + method .getName ());
443+ System .out .println ("ParameterTypes " + Arrays .toString (method .getParameterTypes ()));
444+ System .out .println ("ReturnType " + method .getReturnType ());
445+ });
446+
447+
282448 hasTagKey = lookup .findVirtual (nbtTag , "hasKey" ,
283449 MethodType .methodType (boolean .class , String .class ));
284450 removeM = lookup .findVirtual (nbtTag , "remove" ,
@@ -400,6 +566,10 @@ private static String getNbtTagPath() {
400566 return getNmsPath () + ".NBTTagCompound" ;
401567 }
402568
569+ private static String getNbtTagBasePath () {
570+ return getNmsPath () + ".NBTBase" ;
571+ }
572+
403573 private static String getNmsPath () {
404574 return "net.minecraft.server." + getPackageVersion ();
405575 }
@@ -416,4 +586,19 @@ private static String getPackageVersion() {
416586 return Bukkit .getServer ().getClass ().getPackage ().getName ().split ("\\ ." )[3 ];
417587 }
418588
589+ public enum CompoundState {
590+ ERROR ("Failed to initialize compound" ),
591+ CREATED ("Compound created successfully" ),
592+ NULL ("Compound is null" ),
593+ NOT_CREATED ("Compound is not set yet, can be null" );
594+ private final String message ;
595+
596+ CompoundState (String message ) {
597+ this .message = message ;
598+ }
599+
600+ public String getMessage () {
601+ return message ;
602+ }
603+ }
419604}
0 commit comments