/*******************************************************************************
* 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.util.traitutil;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import de.tobiyas.racesandclasses.RacesAndClasses;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.RemoveSuperConfigField;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationField;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.annotations.configuration.TraitConfigurationNeeded;
import de.tobiyas.racesandclasses.traitcontainer.interfaces.markerinterfaces.Trait;
import de.tobiyas.util.config.YAMLConfigExtended;
public class TraitConfigParser {
@SuppressWarnings("deprecation")
public static void configureTraitFromYAML(YamlConfiguration config, String traitPath, Trait trait) throws TraitConfigurationFailedException{
TraitConfiguration configurationMap = new TraitConfiguration(config);
String traitHolderName = "UNKNOWN";
if(!trait.getTraitHolders().isEmpty()) traitHolderName = trait.getTraitHolders().iterator().next().getDisplayName();
try{
ConfigurationSection traitConfig = config.getConfigurationSection(traitPath);
for(String pathEntry : traitConfig.getKeys(true)){
Object value = traitConfig.get(pathEntry);
//Only use the non null values.
if(value != null){
configurationMap.put(pathEntry, value);
}
}
List<TraitConfigurationField> annotationList = getAllTraitConfigFieldsOfTrait(trait);
List<RemoveSuperConfigField> removedFields = getAllTraitRemovedFieldsOfTrait(trait);
for(TraitConfigurationField field : annotationList){
boolean optional = field.optional();
boolean isPresent = configurationMap.containsKey(field.fieldName());
boolean isRemovedField = false;
for(RemoveSuperConfigField removedField : removedFields){
if(removedField.name().equalsIgnoreCase(field.fieldName())){
isRemovedField = true;
break;
}
}
if(optional && !isPresent){
continue;
}
//We don't check any removed fields.
if(isRemovedField){
continue;
}
if(!optional && !isPresent){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName());
}
Class<?> classToExpect = field.classToExpect();
Object toCheck = configurationMap.get(field.fieldName());
if(toCheck == null){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Wanted a " + classToExpect.getCanonicalName() + " But found NOTHING.");
}
if(classToExpect == Integer.class){
try{
if(toCheck instanceof Integer){
continue;
}
int value = Integer.parseInt(toCheck.toString());
configurationMap.put(field.fieldName(), value);
continue;
}catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
if(classToExpect == String.class){
try{
String value = YAMLConfigExtended.replaceUmlauts(toCheck.toString());
configurationMap.put(field.fieldName(), value);
continue;
}catch(Exception exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
if(classToExpect == Double.class){
try{
if(toCheck instanceof Double){
continue;
}
if(toCheck instanceof Integer){
configurationMap.put(field.fieldName(), (double) ((Integer)toCheck));
continue;
}
double value = Double.MIN_VALUE;
try{
value = Double.parseDouble(toCheck.toString());
}catch(NumberFormatException exp){
value = Integer.parseInt(toCheck.toString());
}
configurationMap.put(field.fieldName(), value);
continue;
}catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
if(classToExpect == Boolean.class){
try{
if(toCheck instanceof Boolean){
continue;
}
boolean value = Boolean.parseBoolean(toCheck.toString());
configurationMap.put(field.fieldName(), value);
continue;
}catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
if(classToExpect == List.class){
try{
if(toCheck instanceof List){
continue;
}
List<String> stringList = Arrays.asList(toCheck.toString().replaceAll(" ", "").split(","));
for(int i = 0; i < stringList.size(); i++) stringList.set(i, YAMLConfigExtended.replaceUmlauts(stringList.get(i)));
configurationMap.put(field.fieldName(), stringList);
continue;
}catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
if(classToExpect == Material.class){
try{
if(toCheck instanceof Material){
continue;
}
Material mat = null;
if(toCheck instanceof Integer){
int value = (Integer) toCheck;
mat = Material.getMaterial(value); // We still want to support IDs.
}else{
String value = toCheck.toString();
try{
int matID = Integer.parseInt(value);
mat = Material.getMaterial(matID);
}catch(NumberFormatException exp){}
if(mat == null){
try{
mat = Material.valueOf(value);
}catch(IllegalArgumentException exp){
throw new NumberFormatException();
}
}
}
if(mat == null){
throw new NumberFormatException(); //early out.
}
configurationMap.put(field.fieldName(), mat);
continue;
}catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
}
//rest... Try it at least.
if(classToExpect.isAssignableFrom(toCheck.getClass())){
configurationMap.put(field.fieldName(), classToExpect.cast(toCheck));
continue;
}
throw new TraitConfigurationFailedException("Field: '" + traitPath + "." + field.fieldName() +
"' not found in Config for Trait: " + trait.getName() + " in Holder: " + traitHolderName +
". Found a " + toCheck.getClass().getCanonicalName() + " but wanted a " + classToExpect.getCanonicalName());
}
try{
trait.setConfiguration(configurationMap);
}catch(TraitConfigurationFailedException exp){
//we have an error here.
throw exp;
}catch(Exception exp){
if(RacesAndClasses.getPlugin().getConfigManager().getGeneralConfig().isConfig_enableDebugOutputs()){
RacesAndClasses.getPlugin().logStackTrace("Configuring Trait failed: " + trait.getDisplayName() + ". Error in logs.", exp);
}
throw new TraitConfigurationFailedException("Configuration of: " + trait.getDisplayName()
+ " of: " + traitHolderName + " Failed. Check your Config! There seems to be a wrong / unset value! Check the Documentation for Value references.");
}
}catch(TraitConfigurationFailedException exp){
throw exp;
}catch(NullPointerException exp){
RacesAndClasses.getPlugin().getDebugLogger().logStackTrace(exp);
throw new TraitConfigurationFailedException("Got a Nullpointer! Please report. Writing error to Error.log. Error in: " + trait.getName());
} catch (SecurityException e) {
throw new TraitConfigurationFailedException("No Annotation found in Trait: " + trait.getName());
} catch(NumberFormatException exp){
throw new TraitConfigurationFailedException("A number could not be read correct at: " + trait.getName());
}catch(Exception exp){
throw new TraitConfigurationFailedException("An unknown Exception has occured at Trait: " + trait.getName()
+ ". Exception: " + exp.getLocalizedMessage());
}
}
/**
* Returns all ConfigFiels from a Trait.
*
* @param trait to search through
*
* @return list of all {@link TraitConfigurationField}s.
*/
public static List<TraitConfigurationField> getAllTraitConfigFieldsOfTrait(Class<? extends Trait> traitClass){
List<TraitConfigurationField> annotationList = new LinkedList<TraitConfigurationField>();
Class<? extends Object> classTocheck = traitClass;
while(classTocheck != null && classTocheck != Trait.class){
try{
Method method = classTocheck.getMethod("setConfiguration", TraitConfiguration.class);
if(method == null || !method.isAnnotationPresent(TraitConfigurationNeeded.class)){
throw new NoSuchMethodException();
}
TraitConfigurationNeeded neededConfig = method
.getAnnotation(TraitConfigurationNeeded.class);
if(neededConfig != null){
Collections.addAll(annotationList, neededConfig.fields());
}
}catch(NoSuchMethodException exp){
continue;
}finally{
classTocheck = classTocheck.getSuperclass();
}
}
return annotationList;
}
/**
* Returns all Removed Fields from a Trait.
*
* @param trait to search through
*
* @return list of all {@link RemoveSuperConfigField}s.
*/
public static List<RemoveSuperConfigField> getAllTraitRemovedFieldsOfTrait(Class<? extends Trait> traitClass){
List<RemoveSuperConfigField> annotationList = new LinkedList<RemoveSuperConfigField>();
Class<? extends Object> classTocheck = traitClass;
while(classTocheck != null && classTocheck != Trait.class){
try{
Method method = classTocheck.getMethod("setConfiguration", TraitConfiguration.class);
if(method == null || !method.isAnnotationPresent(TraitConfigurationNeeded.class)){
throw new NoSuchMethodException();
}
TraitConfigurationNeeded neededConfig = method
.getAnnotation(TraitConfigurationNeeded.class);
if(neededConfig != null){
Collections.addAll(annotationList, neededConfig.removedFields());
}
}catch(NoSuchMethodException exp){
continue;
}finally{
classTocheck = classTocheck.getSuperclass();
}
}
return annotationList;
}
/**
* Returns all ConfigFiels from a Trait.
*
* @param trait to search through
*
* @return list of all {@link TraitConfigurationField}s.
*/
public static List<TraitConfigurationField> getAllTraitConfigFieldsOfTrait(Trait trait){
return getAllTraitConfigFieldsOfTrait(trait.getClass());
}
/**
* Returns all Removed Fields from a Trait.
*
* @param trait to search through
*
* @return list of all {@link RemoveSuperConfigField}s.
*/
public static List<RemoveSuperConfigField> getAllTraitRemovedFieldsOfTrait(Trait trait){
return getAllTraitRemovedFieldsOfTrait(trait.getClass());
}
}