@@ -151,7 +151,7 @@ public void interact(Player player, SignShim sign, SideShim side) {
151151
152152 try {
153153 openSignEditor (player , signImpl , side );
154- } catch (Exception e ) {
154+ } catch (NoSuchFieldException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e ) {
155155 formatSignForSave (player , signImpl , side );
156156 throw new SignEditorInvocationException (e );
157157 }
@@ -163,15 +163,19 @@ public void interact(Player player, SignShim sign, SideShim side) {
163163 * @param player The player that wants to open the sign editor
164164 * @param sign The sign that should load into the player's sign editor
165165 * @param side The side of the sign to open (ignored in Bukkit 1.19.4 and older)
166- * @throws Exception if anything goes wrong while trying to open the sign editor
166+ * @throws NoSuchFieldException if something goes wrong while trying to open the sign editor
167+ * @throws InvocationTargetException if something goes wrong while trying to open the sign editor
168+ * @throws NoSuchMethodException if something goes wrong while trying to open the sign editor
169+ * @throws IllegalAccessException if something goes wrong while trying to open the sign editor
167170 */
168- private void openSignEditor (Player player , Sign sign , SideShim side ) throws Exception {
171+ private void openSignEditor (Player player , Sign sign , SideShim side )
172+ throws NoSuchFieldException , InvocationTargetException , NoSuchMethodException , IllegalAccessException {
169173 try {
170174 if (openSignEditorBukkit1_20 (player , sign , side )) return ;
171175
172176 openSignEditorBukkit1_18 (player , sign );
173- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored ) {
174- openSignEditorWithReflection (player , sign );
177+ } catch (NoSuchMethodException ignored ) {
178+ openSignEditorWithReflection (player , sign , side );
175179 }
176180 }
177181
@@ -184,7 +188,6 @@ private void openSignEditor(Player player, Sign sign, SideShim side) throws Exce
184188 * @throws IllegalAccessException if the "openSign" method cannot be accessed due to Java reflection restrictions
185189 * @throws InvocationTargetException if an exception occurs while invoking the "openSign" method
186190 */
187- @ SuppressWarnings ("JavaReflectionMemberAccess" )
188191 private static void openSignEditorBukkit1_18 (Player player , Sign sign )
189192 throws NoSuchMethodException , IllegalAccessException , InvocationTargetException {
190193 Method method = Player .class .getMethod ("openSign" , Sign .class );
@@ -198,18 +201,19 @@ private static void openSignEditorBukkit1_18(Player player, Sign sign)
198201 * @param sign The sign that should load into the player's sign editor
199202 * @param side The player's viewing side for the sign
200203 * @return true if the sign editor was successfully opened, false otherwise
201- * @throws NoSuchMethodException if the "openSign" method cannot be found in the Player class
204+ * @throws NoSuchMethodException if the "openSign" method does not match the expected signature
202205 * @throws IllegalAccessException if the "openSign" method cannot be accessed due to Java reflection restrictions
203206 * @throws InvocationTargetException if an exception occurs while invoking the "openSign" method
204207 */
205208 private static boolean openSignEditorBukkit1_20 (Player player , Sign sign , SideShim side )
206209 throws NoSuchMethodException , IllegalAccessException , InvocationTargetException {
207210 try {
208211 setPlayerWhoMayEditSign (player , sign );
209- } catch (Exception ignored ) {
212+ } catch (NoSuchFieldException ignored ) {
213+ // Ignore PaperMC implementation detail
210214 }
211215
212- Optional <Method > optionalMethod = Arrays .stream (Player . class . getDeclaredMethods ())
216+ Optional <Method > optionalMethod = Arrays .stream (player . getClass (). getMethods ())
213217 .filter (method -> method .getName ().equals ("openSign" ) && method .getParameterCount () == 2 )
214218 .filter (method -> {
215219 Class <?>[] parameterTypes = method .getParameterTypes ();
@@ -236,7 +240,8 @@ private static boolean openSignEditorBukkit1_20(Player player, Sign sign, SideSh
236240 * @param player The player that wants to open the sign editor
237241 * @param sign The sign that should load into the player's sign editor
238242 */
239- private static void setPlayerWhoMayEditSign (Player player , Sign sign ) throws Exception {
243+ private static void setPlayerWhoMayEditSign (Player player , Sign sign )
244+ throws InvocationTargetException , NoSuchMethodException , IllegalAccessException , NoSuchFieldException {
240245 Object tileEntitySign = toRawTileEntity (sign );
241246
242247 Field playerWhoMayEdit = getFirstFieldOfType (tileEntitySign , UUID .class , Modifier .PUBLIC );
@@ -245,41 +250,65 @@ private static void setPlayerWhoMayEditSign(Player player, Sign sign) throws Exc
245250 }
246251
247252 /**
248- * Take a reflection-based guess to open the sign editor for common CraftBukkit implementations prior to Bukkit 1.18
253+ * Take a reflection-based guess to open the sign editor for common CraftBukkit implementations that are missing a
254+ * Bukkit stable API method to open the sign editor, i.e., no <code>Player#openSign(Sign, Side)</code> method.
249255 * <p>
250256 * Prior to Bukkit 1.18, there was no stable API to open the sign editor.
251- * Instead, there was a method called <code>EntityHuman#openSign</code> available since CraftBukkit 1.8, which took
252- * a <code>TileEntitySign</code> as its argument.
253- * This method tries to call that unstable API method.
257+ * Instead, there was a method called <code>EntityHuman#openSign(TileEntitySign)</code> available since CraftBukkit
258+ * 1.8.
259+ * <p>
260+ * CraftBukkit 1.20 replaced that method with <code>EntityHuman#openSign(TileEntitySign, boolean)</code> where the
261+ * boolean parameter indicates the side of the sign to open: <code>true</code> for the front and <code>false</code>
262+ * for the back.
263+ * <p>
264+ * This method tries to call one of those unstable API methods.
254265 *
255266 * @param player The player that wants to open the sign editor
256267 * @param sign The sign that should load into the player's sign editor
257- * @throws Exception if anything goes wrong while trying to open the sign editor
268+ * @param side The side of the sign to open (unused in Bukkit 1.19.4 and older)
269+ * @throws NoSuchFieldException if something goes wrong while trying to open the sign editor
270+ * @throws InvocationTargetException if something goes wrong while trying to open the sign editor
271+ * @throws NoSuchMethodException if something goes wrong while trying to open the sign editor
272+ * @throws IllegalAccessException if something goes wrong while trying to open the sign editor
258273 */
259- private void openSignEditorWithReflection (Player player , Sign sign ) throws Exception {
274+ private void openSignEditorWithReflection (Player player , Sign sign , SideShim side )
275+ throws InvocationTargetException , NoSuchMethodException , IllegalAccessException , NoSuchFieldException {
260276 Object tileEntitySign = toRawTileEntity (sign );
261277 Object entityPlayer = toRawEntity (player );
262278
263- makeTileEntitySignEditable (tileEntitySign );
279+ try {
280+ makeTileEntitySignEditable (tileEntitySign );
281+ } catch (NoSuchFieldException e ) {
282+ setPlayerWhoMayEditSign (player , sign );
283+ }
264284
265- Method openSignMethod = findMethodByParameterTypes (
266- entityPlayer .getClass (), tileEntitySign .getClass ()
267- );
268- openSignMethod .invoke (entityPlayer , tileEntitySign );
285+ try {
286+ Method openSignMethod = findMethodByParameterTypes (
287+ entityPlayer .getClass (), tileEntitySign .getClass ()
288+ );
289+ openSignMethod .invoke (entityPlayer , tileEntitySign );
290+ } catch (NoSuchMethodException e ) {
291+ Method openSignMethod = findMethodByParameterTypes (
292+ entityPlayer .getClass (), tileEntitySign .getClass (), boolean .class
293+ );
294+ openSignMethod .invoke (entityPlayer , tileEntitySign , SideShim .FRONT .equals (side ));
295+ }
269296 }
270297
271- private static Object toRawEntity (Entity entity ) throws Exception {
298+ private static Object toRawEntity (Entity entity )
299+ throws NoSuchMethodException , InvocationTargetException , IllegalAccessException {
272300 return getDeclaredMethodRecursive (entity .getClass (), "getHandle" ).invoke (entity );
273301 }
274302
275- private static Object toRawTileEntity (BlockState blockState ) throws Exception {
303+ private static Object toRawTileEntity (BlockState blockState )
304+ throws NoSuchMethodException , InvocationTargetException , IllegalAccessException {
276305 return getDeclaredMethodRecursive (blockState .getClass (), "getTileEntity" ).invoke (blockState );
277306 }
278307
279308 /**
280309 * FIXME: Find a more reliable way than looking for the first public boolean to mark the TileEntitySign as editable
281310 */
282- private void makeTileEntitySignEditable (Object tileEntitySign ) throws Exception {
311+ private void makeTileEntitySignEditable (Object tileEntitySign ) throws NoSuchFieldException , IllegalAccessException {
283312 Field signIsEditable = getFirstFieldOfType (tileEntitySign , boolean .class , Modifier .PUBLIC );
284313 signIsEditable .setAccessible (true );
285314 signIsEditable .set (tileEntitySign , true );
@@ -305,9 +334,10 @@ private void formatSignForSave(Player player, Sign sign, SideShim side) {
305334
306335 private static void sendSignUpdate (Player player , Sign sign , ISignSide signSide ) {
307336 try {
308- for (Method method : Player . class . getDeclaredMethods ()) {
337+ for (Method method : player . getClass (). getMethods ()) {
309338 if (method .getName ().equals ("sendBlockUpdate" ) && method .getParameterCount () == 2 ) {
310339 method .invoke (player , sign .getLocation (), sign );
340+ return ;
311341 }
312342 }
313343 throw new NoSuchMethodException ();
0 commit comments