/*******************************************************************************
* Copyright 2014 Tobias Welther
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package de.tobiyas.racesandclasses.traitcontainer;
import java.io.File;
import java.lang.annotation.AnnotationFormatError;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import de.tobiyas.racesandclasses.RacesAndClasses;
import de.tobiyas.racesandclasses.datacontainer.traitholdercontainer.AbstractTraitHolder;
import de.tobiyas.racesandclasses.eventprocessing.TraitEventManager;
import de.tobiyas.racesandclasses.traitcontainer.container.TraitsList;
import de.tobiyas.racesandclasses.traitcontainer.exceptions.TraitNotFoundException;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.NeedMC1_6;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.NeedMC1_7;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.NeedMC1_8;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.bypasses.NeedsOtherPlugins;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitEventsUsed;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitInfos;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait;
import de.tobiyas.racesandclasses.util.bukkit.versioning.CertainVersionChecker;
import de.tobiyas.racesandclasses.util.traitutil.TraitConfigurationFailedException;
public class TraitStore {
/**
* All Classloaders loaded.
*/
private static Set<ClassLoader> classLoaders = new HashSet<ClassLoader>();
/**
* Clears all Classloaders linked
*/
public static void destroyClassLoaders(){
for(ClassLoader loader : classLoaders){
loader.clearAssertionStatus(); //TODO remove classloader somehow
}
classLoaders.clear();
}
/**
* Constructs a trait for the passed name.
* It is important, that traitName and holders are NEVER null!
*
* If something gone Wrong while initialization, null is returned.
*
* @param traitName of the trait
* @param holders of Trait
* @return the constructed Trait or null
*/
public static Trait buildTraitByName(String traitName, AbstractTraitHolder holder){
if(traitName == null || holder == null){
return null;
}
try{
Trait trait = buildTrait(traitName, holder);
registerTrait(trait);
return trait;
}catch(TraitNotFoundException e){
RacesAndClasses.getPlugin().log("Trait not found: " + e.getLocalizedMessage());
}catch(AnnotationFormatError e){
RacesAndClasses.getPlugin().log("Could not find Annotation for: " + traitName +
" Error was: " + e.getLocalizedMessage());
}catch(TraitConfigurationFailedException exp){
RacesAndClasses.getPlugin().log("Coild not Construct trait: " + traitName + ". Problem was: "
+ exp.getLocalizedMessage());
}catch(Exception e){
RacesAndClasses.getPlugin().log("Could not Construct trait: " + traitName);
RacesAndClasses.getPlugin().getDebugLogger().logStackTrace(e);
}
return null;
}
/**
* Builds a Trait and sets his holders to the one wanted.
*
* @param traitName
* @param holders
* @return
* @throws Exception
*/
private static Trait buildTrait(String traitName, AbstractTraitHolder holder) throws Exception{
Class<? extends Trait> clazz = TraitsList.getClassOfTrait(traitName);
if(clazz == null){
throw new TraitNotFoundException(traitName);
}
Trait trait = (Trait) clazz.getConstructor().newInstance();
if(trait == null){
throw new TraitNotFoundException(traitName);
}
trait.addTraitHolder(holder);
return trait;
}
/**
* Registers a trait for Uplink and event recieving
*
* @param trait to register
* @return if worked or not
*
* @throws AnnotationFormatError
*/
private static boolean registerTrait(Trait trait) throws AnnotationFormatError{
try{
Set<Class<? extends Event>> wantedEvents = new HashSet<Class<? extends Event>>();
int traitPriority = -1;
Class<? extends Object> toInspect = trait.getClass();
while(toInspect != null && toInspect != Object.class){
try{
Method method = toInspect.getMethod("generalInit");
TraitEventsUsed annotation = method.getAnnotation(TraitEventsUsed.class);
if(annotation != null){
if(annotation.traitPriority() > traitPriority){
traitPriority = annotation.traitPriority();
}
Collections.addAll(wantedEvents, annotation.registerdClasses());
Collections.addAll(wantedEvents, annotation.bypassClasses());
}
}catch(Exception exp){
continue;
}finally{
toInspect = toInspect.getSuperclass();
}
}
//No events wanted? So don't bother them.
if(!wantedEvents.isEmpty()) TraitEventManager.registerTrait(trait, wantedEvents, traitPriority);
return true;
}catch(AnnotationFormatError e){
throw e;
}catch(Exception e){
RacesAndClasses.getPlugin().getDebugLogger().logStackTrace(e);
return false;
}
}
/**
* Imports all jar files from the traits dire.
*/
public static void importFromFileSystem(){
RacesAndClasses plugin = RacesAndClasses.getPlugin();
File traitDir = new File(plugin.getDataFolder() + File.separator + "ExternalTraits" + File.separator);
if(!traitDir.exists()){
traitDir.mkdirs();
return;
}
List<File> possibleTraits = getAllTraitsOfDir(traitDir);
for(File file : possibleTraits){
try{
loadExternalTrait(file);
}catch(Throwable exception){
RacesAndClasses.getPlugin().log("Could not load file: " + file.toString());
continue;
}
}
}
/**
* Finds recursively all '.jar' files in the Trait directory.
*
* @param dir
* @return
*/
private static List<File> getAllTraitsOfDir(File dir){
List<File> traitFileList = new LinkedList<File>();
for(File file : dir.listFiles()){
if(file.isDirectory()){
//directory -> research in this one
traitFileList.addAll(getAllTraitsOfDir(file));
}else{
//file -> check if .jar -> add to list
if(file.getAbsolutePath().endsWith(".jar")){
traitFileList.add(file);
}
}
}
return traitFileList;
}
@SuppressWarnings("unchecked")
private static void loadExternalTrait(File file){
try{
//old but working: //TODO replace by new Classloader with no leaking of memory
URLClassLoader clazzLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()}, RacesAndClasses.getPlugin().getClass().getClassLoader());
classLoaders.add(clazzLoader);
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
Set<Class<Trait>> clazzArray = new HashSet<Class<Trait>>();
while (entries.hasMoreElements()) {
JarEntry element = entries.nextElement();
if (element.getName().endsWith(".class")) {
try {
Class<?> clazz = clazzLoader.loadClass(element.getName().replaceAll(".class", "").replaceAll("/", "."));
if(clazz != null){
if(Trait.class.isAssignableFrom(clazz)){
if(clazz.isAnnotationPresent(NeedMC1_6.class)){
if(!CertainVersionChecker.isAbove1_6()){
//We need MC > 1.6 But do not have it.
continue;
}
}
if(clazz.isAnnotationPresent(NeedMC1_7.class)){
if(!CertainVersionChecker.isAbove1_7()){
//We need MC > 1.7 But do not have it.
continue;
}
}
if(clazz.isAnnotationPresent(NeedMC1_8.class)){
if(!CertainVersionChecker.isAbove1_8()){
//We need MC > 1.8 But do not have it.
continue;
}
}
if(clazz.isAnnotationPresent(NeedsOtherPlugins.class)){
boolean doBreak = false;
for(String pluginName : clazz.getAnnotation(NeedsOtherPlugins.class).neededPlugins()){
if(Bukkit.getPluginManager().getPlugin(pluginName) == null) {
doBreak = true;
break;
}
}
//some depends not found.
if(doBreak) continue;
}
clazzArray.add((Class<Trait>) clazz);
}
}
} catch (Throwable e) {
RacesAndClasses.getPlugin().getDebugLogger().logError("Could not load Java Class: "
+ element.getName() + ". In: " + jarFile.getName());
continue;
}
}
}
boolean hasImportInfos = false;
for(Class<Trait> clazz : clazzArray){
try{
if (clazz != null) {
Trait trait = clazz.newInstance();
boolean isPresent = trait.getClass().getMethod("importTrait").isAnnotationPresent(TraitInfos.class);
if(isPresent){
TraitInfos annotation = trait.getClass().getMethod("importTrait").getAnnotation(TraitInfos.class);
TraitsList.addTraitToList(annotation.traitName(), clazz, annotation.category(), annotation.visible());
trait.importTrait();
hasImportInfos = true;
}else{
throw new AnnotationFormatError("Annotation: Import could not be found for class: " + clazz);
}
}
}catch(AnnotationFormatError e){
RacesAndClasses.getPlugin().log(e.getLocalizedMessage());
}
}
jarFile.close();
if(!hasImportInfos){
throw new AnnotationFormatError("Annotation: Import could not be found for file: " + file.getName());
}
}catch (NoClassDefFoundError e) {
String message = "Unable to load " + file.getName() + ". Probably it was written for a previous Races version!";
RacesAndClasses.getPlugin().log(message);
RacesAndClasses.getPlugin().logStackTrace(message, e);
return;
} catch (AnnotationFormatError e){
//Not interesting since the Plugin needed is not loaded!
//RacesAndClasses.getPlugin().log(e.getLocalizedMessage());
} catch (Throwable e) {
String message = "The trait " + file.getName() + " failed to load for an unknown reason.";
RacesAndClasses.getPlugin().log(message);
RacesAndClasses.getPlugin().logStackTrace(message, e);
}
}
/**
* Builds a static trait with NO holders.
*
* @param string
*/
public static Trait buildTraitWithoutHolderByName(String traitName) {
try{
Trait trait = buildTrait(traitName, null);
registerTrait(trait);
return trait;
}catch(TraitNotFoundException e){
RacesAndClasses.getPlugin().log(e.getLocalizedMessage());
}catch(AnnotationFormatError e){
RacesAndClasses.getPlugin().log("Could not find Annotation for: " + traitName);
}catch(Exception e){
RacesAndClasses.getPlugin().log("Could not Construct trait: " + traitName);
}
return null;
}
}