2014-09-20 2 views
-1

Minecraft 서버 구현 CraftBukkit 용 플러그인을 작성하고 있으며 리플렉션을 통해 발견 된 클래스로 캐스팅해야하는 문제가 발생했습니다.Java의 동적 Typecasting

여기에 있습니다. - v1_7_R3

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Random; 

import net.minecraft.server.v1_7_R3.EntityAnimal; 
import net.minecraft.server.v1_7_R3.EntityHuman; 
import org.bukkit.craftbukkit.v1_7_R3.entity.CraftAnimals; 
import org.bukkit.craftbukkit.v1_7_R3.entity.CrafteEntity; 

import org.bukkit.World; 
import org.bukkit.entity.Animals; 
import org.bukkit.entity.Entity; 
import org.bukkit.event.Listener; 
import org.bukkit.plugin.java.JavaPlugin; 
import org.bukkit.scheduler.BukkitRunnable; 

public class Task extends BukkitRunnable { 
    private static final int MATING_DISTANCE = 14; 
    private final JavaPlugin plugin; 
    private final Random randomizer; 
    private boolean mateMode; 
    private double chance; 

    public Task(JavaPlugin plugin, double chance, boolean mateMode) { 
     this.plugin = plugin; 
     this.randomizer = new Random(); 
     this.chance = chance; 
     this.mateMode = mateMode; 
     this.theTaskListener = listener; 
    } 

    public void run() { 
     List<World> worlds = plugin.getServer().getWorlds(); 
     Iterator<World> worldIterator = worlds.iterator(); 

     while (worldIterator.hasNext()) { 
      World world = worldIterator.next(); 
      Collection<Animals> animals = world.getEntitiesByClass(Animals.class); 

      Iterator<Animals> animalIterator = animals.iterator(); 
      while (animalIterator.hasNext()) { 
       Animals animal = (Animals) animalIterator.next(); 
       EntityAnimal entity = (EntityAnimal) ((CraftEntity) ((CraftAnimals) animal)).getHandle(); 
       EntityHuman feeder = null; 
       entity.f(feeder); 
      } 
     } 
    } 
} 

그러나 당신이 수입에서 볼 수 있듯이,이 코드는 마인 크래프트 서버 패키지의 한 버전의 클래스를 수입 : 관련이없는 부분을 제거하여 내가 쓴 원래의 코드는이처럼 보였다. 이제 문제는, 그 이상을위한 지원을 추가하고 싶습니다. 그리고 Minecraft의 각 버전에 대한 플러그인의 별도 버전을 만들지 않고도 그 일을 할 수 있기를 바랍니다. 패키지의 클래스 대부분은 동일하지만 적어도 패키지 이름은 다르므로 정적 가져 오기를 사용하여 수행 할 수 없습니다 (또는 적어도 그렇게 생각합니까?)

그래서, 내가 필요로하는 정확한 클래스를 얻기 위해 반사를 사용하기로 결정 (이 코드는 다른 클래스에) :

이제
private static final String[] requiredClasses = { 
    "net.minecraft.server.%s.EntityAnimal", 
    "net.minecraft.server.%s.EntityHuman", 
    "org.bukkit.craftbukkit.%s.entity.CraftAnimals", 
    "org.bukkit.craftbukkit.%s.entity.CraftEntity" 
}; 
public static final String[] supportedVersions = { 
    "v1_7_R3", 
    "v1_7_R4" 
}; 

public Class<?>[] initializeClasses() { 
    String correctVersion = null; 
    for (int i = 0; i < supportedVersions.length; i++) { 
     String version = supportedVersions[i]; 
     boolean hadIssues = false; 
     for (int j = 0; j < requiredClasses.length; j++) { 
      String className = requiredClasses[j]; 
      try { 
       Class.forName(String.format(className, version)); 
      } catch (ClassNotFoundException e) { 
       getLogger().log(Level.INFO, String.format("The correct version isn't %s.", version)); 
       hadIssues = true; 
       break; 
      } 
     } 

     if (!hadIssues) { 
      correctVersion = version; 
      break; 
     } 
    } 

    Class[] classes = new Class[requiredClasses.length]; 

    if (correctVersion != null) { 
     getLogger().log(Level.INFO, String.format("The correct version is %s.", correctVersion)); 
     for (int i = 0; i < requiredClasses.length; i++) { 
      String className = requiredClasses[i]; 
      try { 
       classes[i] = Class.forName(String.format(className, correctVersion)); 
      } catch (ClassNotFoundException e) {} 
     } 
    } else { 
     getLogger().log(Level.WARNING, "The version of Minecraft on this server is not supported."); 
     getLogger().log(Level.WARNING, "Due to this, the plugin will self-disable."); 
     getLogger().log(Level.WARNING, "To fix this issue, get build that supports your version."); 
     this.setEnabled(false); 
    } 

    return classes; 
} 

는이 방법이 성공적으로 두 버전에서 필요한 클래스는 현재 지원 검색 . 내가 인스턴스 변수 및 편집 생성자를 사용하여 다시 작성 작업 클래스에이를 통과, 나는 버전 별 수입 제거 : 이제

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Random; 
import org.bukkit.World; 
import org.bukkit.entity.Animals; 
import org.bukkit.entity.Entity; 
import org.bukkit.plugin.java.JavaPlugin; 
import org.bukkit.scheduler.BukkitRunnable; 

public class Task extends BukkitRunnable { 

    private static final int MATING_DISTANCE = 14; 
    private final JavaPlugin plugin; 
    private final Random randomizer; 
    private boolean mateMode; 
    private double chance; 

    private Class entityAnimal; 
    private Class entityHuman; 
    private Class craftAnimals; 
    private Class craftEntity; 

    public Task(JavaPlugin plugin, Class[] classes, double chance, boolean mateMode) { 
     this.plugin = plugin; 
     this.randomizer = new Random(); 
     this.chance = chance; 
     this.mateMode = mateMode; 

     this.entityAnimal = classes[0]; 
     this.entityHuman = classes[1]; 
     this.craftAnimals = classes[2]; 
     this.craftEntity = classes[3]; 
    } 

을, 나는 Task.run() 메소드를 다시 작성할 수 있습니다 어떻게 사용할 수 있도록 반사 수업? typecasting이 많이 포함되어 있으며 불행히도 Minecraft 코드에서 오버로드가 너무 많아서 필요합니다. 예를 들어, entity.f (EntityHuman human) 메소드는 다른 overloading entity.f (Object object) 메소드가 있기 때문에 entity.f (null)을 수행하여 간단히 호출 할 수 없습니다.

나는 여기서 막 다른 골목에 직면하고 있으므로 모든 제안에 대해 개방적입니다. 문제에 대한 더 나은 접근법이 있다면, 나는 또한 그것을 바꿀 수 있습니다.

감사합니다.

+0

-1 * Minecraft 서버 구현 Java의 [이름 지정 패키지] (http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html) 협약을 의도적으로 무시한 Bukkit *. 더 나쁜 것은 버전으로 오염시키는 것입니다. 그런 일을 한 이유가 무엇인지 알고 있습니까? 나도 알아, 특히 너에게 도움이되지 않아. 그것을 도덕적 인지지로 간주하십시오. (그리고 덧붙여 말하자면, 나는 이것을 말할 수있는 주안점이되고 싶었습니다. 그러나 더 유용한 답변을 찾을 수 있는지 알아 보겠습니다. –

+0

두 개의 분리 된 버전의 클래스가 서로 다른 경향이있어 플러그인이 서로 다른 버전에서 실패하는 경향이 있기 때문에 완료되었다고 생각합니다. 개발자는 모든 이유로 플러그인을 검토해야합니다. 그것은 확실히이 일을하는 최선의 방법은 아니지만. – funstein

답변

0

Gerold Broser의 응답을 읽은 후에 버전 특정 작업을 수행 할 수있는 일종의 핸들러 클래스를 만들기 위해 어떻게 든 내 접근 방식을 수정해야한다는 것을 깨달았습니다. 물론이 인터페이스는 버전마다 클래스별로 별도로 구현됩니다.

그러나 이것은 Maven이 동일한 groupid.artifactid 객체의 두 버전을 호출하지 못하게한다는 것을 깨달았을 때 문제가되었습니다.

신속하게 조사를 한 결과 mbaxter의 Multiple Versions Tutorial과이 방법을 완벽하게 구현 한 AbstractionExamplePlugin 구현을 발견했습니다.

이 접근법은 완벽하게 작동하며 모든 Bukkit 개발자가 사용해야하는 것입니다. Here 님의 필자는 필자의 필요에 따라 참조 할 수있는 완성 된 플러그인을 제공합니다.

0

브레인 스토밍 아이디어. 어떤 경우 :

  • 지원되는 모든 버전을 가져
  • 완전히 특정 런타임을 대상 년대 버전 확인 적절한 패키지의 유형
  • 를 참조

    import net.minecraft.server.v1_7_R3.*; 
    import net.minecraft.server.v1_7_R4.*; 
    
    enum Version { 
        V1_7_R3, 
        V1_7_R4 
    } 
    
    Version currentVersion; 
    
    net.minecraft.server.v1_7_R3.EntityAnimal animal3; 
    net.minecraft.server.v1_7_R4.EntityAnimal animal4; 
    
    // obtain currentVersion 
    
    switch (currentVersion) { 
        case V1_7_R3: 
         animal3.method(); 
         break; 
        case V1_7_R4: 
         animal4.method(); 
         break; 
        } 
    
  • (어떻게 든 얻을 수 가정)

이것은 다소 추한 것입니다. 물론 주어진 상황에서 내 마음 속으로 먼저 들어 왔을 가능성이 있습니다.

0

우리는 객체 지향 언어에서이 목적을 위해 개발 된 다양한 디자인 패턴에 액세스 할 수 있습니다. 우리는 특히 두 가지 패턴을 사용합니다.

Adapter Pattern은 여러 가지 다른 구현에 동일한 인터페이스를 제공하는 데 사용됩니다. 때로는 심이라고도합니다. 각 서버의 버전마다 하나의 클래스를 만들고 라이브러리를 각각 가져옵니다. 클래스는 공통된 인터페이스를 구현합니다.

Factory Pattern은 어댑터 클래스 중에서 선택하는 데 사용됩니다.보유하고있는 서버 버전을 확인하는 데 필요한 모든 방법을 사용하면 적절한 인터페이스를 구현하는 객체가 만들어집니다. 주요 코드는 동일하게 유지됩니다. 팩토리를 호출하여 서버를 처리하는 방법을 알고있는 객체를 가져옵니다.

이 접근법의 장점은 여러 가지입니다. 겹치는 라이브러리를 가져 와서 이름 공간을 오염시키지 마십시오. 새 서버 버전이 추가되면 주 코드가 변경되기가 훨씬 쉽습니다. 작성해야 할 유일한 코드는 새 서버 심과 생산할 어댑터를 결정하는 팩토리입니다.