Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a synthetic or bridge method be used to smooth an int -> double API change?

Java has special markers on methods called synthetic and bridge.

JLS 13.1.7, "Any constructs introduced by a Java compiler that do not have a corresponding construct in the source code must be marked as synthetic ..."

So synthetic methods are anything generated by the compiler and not represented in the source code, and although it's not mentioned in that specification PDF very well, bridge methods are used to typecheck generics. (E.g. Animal.interactWith(Creature c) gets a bridge method interactWith(Object c), which casts to Creature and calls the other method.)


We have this API called Bukkit, which provides stable access to change how a Minecraft server works. One facet of the underlying implementation of the API (aka vanilla Minecraft), which we have little control over, was recently forced in version 1.6.1 to change from integer values to float values. And in the interest of avoiding the difficulty of another change, we chose to change all our API methods to doubles.

So, for example:

public int getHealth();
public void setHealth(int health);
// Must now be
public double getHealth();
public void setHealth(double health);

However, as always, we would like plugins compiled with the previous version, 1.5.2, to still work as much as possible - that's the whole point of the API.

The setHealth is a solved problem, just introduce an overload. And currently, we have a method named _INVALID_getHealth(V)I which is being renamed at implementation compile (not API compile) to getHealth(V)I, and this lets the old plugins continue to run.

However, when someone tries to extend our implementation of these renamed methods, they get compile errors from the doubly-named methods and overriding.

Is there a way to provide both the int and double returns using a manually / tool-inserted synthetic or bridge method, that won't cause compilation errors for those who try to change parts of our API implementation?

like image 753
Riking Avatar asked Jul 02 '13 23:07

Riking


1 Answers

The answer is yes - if you mark a method with different return type but the same parameters as both synthetic and bridge, you can compile subclasses.

In this specific case, a new Maven utility, "Overmapper", was created to perform this task, though you could do it in any automated tool that can edit Java bytecode. Here's the configuration file:

members:
  "org/bukkit/entity/Damageable _INVALID_damage (I)V": damage
  "org/bukkit/entity/Damageable _INVALID_damage (ILorg/bukkit/entity/Entity;)V": damage
  "org/bukkit/entity/Damageable _INVALID_getHealth ()I": getHealth
  "org/bukkit/entity/Damageable _INVALID_setHealth (I)V": setHealth
  "org/bukkit/entity/Damageable _INVALID_getMaxHealth ()I": getMaxHealth
  "org/bukkit/entity/Damageable _INVALID_setMaxHealth (I)V": setMaxHealth
  "org/bukkit/entity/LivingEntity _INVALID_getLastDamage ()I": getLastDamage
  "org/bukkit/entity/LivingEntity _INVALID_setLastDamage (I)V": setLastDamage
  "org/bukkit/event/entity/EntityDamageEvent _INVALID_getDamage ()I": getDamage
  "org/bukkit/event/entity/EntityDamageEvent _INVALID_setDamage (I)V": setDamage
  "org/bukkit/event/vehicle/VehicleDamageEvent _INVALID_getDamage ()I": getDamage
  "org/bukkit/event/vehicle/VehicleDamageEvent _INVALID_setDamage (I)V": setDamage
  "org/bukkit/event/entity/EntityRegainHealthEvent _INVALID_getAmount ()I": getAmount
  "org/bukkit/event/entity/EntityRegainHealthEvent _INVALID_setAmount (I)V": setAmount
  "org/bukkit/entity/Minecart _INVALID_getDamage ()I": getDamage
  "org/bukkit/entity/Minecart _INVALID_setDamage (I)V": setDamage
  "org/bukkit/entity/Projectile _INVALID_getShooter ()Lorg/bukkit/entity/LivingEntity;": getShooter
  "org/bukkit/entity/Projectile _INVALID_setShooter (Lorg/bukkit/entity/LivingEntity;)V": setShooter
  "org/bukkit/Bukkit _INVALID_getOnlinePlayers ()[Lorg/bukkit/entity/Player;": getOnlinePlayers
  "org/bukkit/Server _INVALID_getOnlinePlayers ()[Lorg/bukkit/entity/Player;": getOnlinePlayers
flags:
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity getHealth ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart getHealth ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity getMaxHealth ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart getMaxHealth ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity setHealth (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart setHealth (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity setMaxHealth (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart setMaxHealth (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity damage (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart damage (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity damage (ILorg/bukkit/entity/Entity;)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftEnderDragonPart damage (ILorg/bukkit/entity/Entity;)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity getLastDamage ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftLivingEntity setLastDamage (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftMinecart setDamage (I)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftMinecart getDamage ()I": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftProjectile getShooter ()Lorg/bukkit/entity/LivingEntity;": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftProjectile setShooter (Lorg/bukkit/entity/LivingEntity;)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftArrow getShooter ()Lorg/bukkit/entity/LivingEntity;": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftArrow setShooter (Lorg/bukkit/entity/LivingEntity;)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftFireball getShooter ()Lorg/bukkit/entity/LivingEntity;": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftFireball setShooter (Lorg/bukkit/entity/LivingEntity;)V": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftFish getShooter ()Lorg/bukkit/entity/LivingEntity;": 0x1001
  "org/bukkit/craftbukkit/v${minecraft_version}/entity/CraftFish setShooter (Lorg/bukkit/entity/LivingEntity;)V": 0x1001

You can see that all the int-returning methods are being changed to be flagged as bridge and synthetic.

like image 170
Riking Avatar answered Oct 17 '22 14:10

Riking