001/**
002 * Copyright (C) 2007-2011, Jens Lehmann
003 *
004 * This file is part of DL-Learner.
005 *
006 * DL-Learner is free software; you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation; either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * DL-Learner is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
018 */
019
020package org.dllearner.configuration.spring.editors;
021
022import com.google.common.collect.ObjectArrays;
023import org.dllearner.core.AnnComponentManager;
024import org.dllearner.core.Component;
025import org.dllearner.core.config.ConfigOption;
026import org.springframework.beans.BeanUtils;
027import org.springframework.beans.BeansException;
028
029import java.beans.PropertyDescriptor;
030import java.beans.PropertyEditor;
031import java.lang.reflect.Field;
032import java.lang.reflect.InvocationTargetException;
033import java.lang.reflect.Method;
034import java.util.*;
035
036public class ConfigHelper {
037        
038        public final static Map<Class<?>, Class<?>> map = new HashMap<>();
039        
040        static {
041                map.put(Boolean.class, boolean.class);
042                map.put(Byte.class, byte.class);
043                map.put(Short.class, short.class);
044                map.put(Character.class, char.class);
045                map.put(Integer.class, int.class);
046                map.put(Long.class, long.class);
047                map.put(Float.class, float.class);
048                map.put(Double.class, double.class);
049        }
050        
051        /**
052         * Configures the given component by setting the value for the appropriate config option.
053         * @param component the component to be configured
054         * @param configName the name of the config option
055         * @param configValue the value of the config option
056         */
057        @Deprecated
058        public static <T> void configure(Component component, String configName, T configValue){
059                List<Field> fields = getAllFields(component);
060        for(Field f : fields){
061                if(f.isAnnotationPresent(ConfigOption.class)){
062                        if(AnnComponentManager.getName(f).equals(configName)){
063                                try {
064                                                PropertyEditor editor = PropertyEditor.class.newInstance();
065                                                editor.setAsText(configValue.toString());
066                                                Method method = component.getClass().getMethod("set" + Character.toUpperCase(f.getName().charAt(0)) + f.getName().substring(1), getClassForObject(editor.getValue()));
067                                                method.invoke(component, editor.getValue());
068                                        } catch (IllegalArgumentException | NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
069                                                e.printStackTrace();
070                                        }
071                        }
072                        
073                }
074        }
075        }
076        
077        /**
078         * Returns all config options for the given component.
079         * @param component
080         * @return
081         */
082        public static List<ConfigOption> getConfigOptions(Component component){
083                return getConfigOptions(component.getClass());
084        }
085        
086
087        /**
088         * 
089         * @param component The component to analyse.
090         * @return All config options of the component with their respective value.
091         */
092        public static Map<Field,Object> getConfigOptionValues(Component component) {
093                Map<Field,Object> optionValues = new HashMap<>();
094                List<Field> fields = getAllFields(component);
095                for(Field field : fields) {
096                        if(field.isAnnotationPresent(ConfigOption.class)) {
097                                try {
098                                        // we invoke the public getter instead of accessing a private field (may cause problem with SecurityManagers)
099                                        // use Spring BeanUtils TODO: might be unnecessarily slow because we already have the field?
100                                        PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(
101                                                        component.getClass(),
102                                                        field.getName()
103                                        );
104                                        Method readMethod = propertyDescriptor.getReadMethod();
105                                        if(readMethod == null) {
106                                                throw new RuntimeException("Getter method is missing for field " + field + " in component " + component);
107                                        }
108                                        Object value = readMethod.invoke(component);
109
110                                        optionValues.put(field, value);
111                                } catch (IllegalArgumentException | InvocationTargetException | BeansException | IllegalAccessException e1) {
112                                        e1.printStackTrace();
113                                }
114                        }
115                }
116                return optionValues;
117        }
118        
119        /**
120         * Returns all config options for the given component.
121         * @param component
122         * @return
123         */
124        public static List<ConfigOption> getConfigOptions(Class<? extends Component> component){
125                List<ConfigOption> options = new ArrayList<>();
126                
127                Field[] fields = component.getDeclaredFields();
128                for(Field f : fields){
129                ConfigOption option = f.getAnnotation(ConfigOption.class);
130                if(option != null){
131                        options.add(option);
132                }
133        }
134                
135                return options;
136        }
137        
138        /**
139         * Returns all config options for the given component.
140         * @param component
141         * @return
142         */
143        public static Map<Field,Class<?>> getConfigOptionTypes(Class<?> component){
144                return getConfigOptionTypes(component, true);
145        }
146        
147        /**
148         * Returns all config options for the given component.
149         * @param component
150         * @return
151         */
152        public static Map<Field,Class<?>> getConfigOptionTypes(Class<?> component, boolean useSuperTypes){
153                Map<Field,Class<?>> optionTypes = new TreeMap<>((o1, o2) -> {
154                        return AnnComponentManager.getName(o1).compareTo(AnnComponentManager.getName(o2));
155                });
156                Field[] fields = component.getDeclaredFields();
157                if(useSuperTypes) {
158                        if(useSuperTypes) {
159                                fields = ObjectArrays.concat(fields, component.getSuperclass().getDeclaredFields(), Field.class);
160                        }
161                }
162                for(Field f : fields){
163                if(f.isAnnotationPresent(ConfigOption.class)) {
164                        optionTypes.put(f, f.getType());
165                }
166        }
167                return optionTypes;
168        }
169        
170        /*
171         * returns the declared fields for the class and its superclass.
172         */
173        private static List<Field> getAllFields(Component component){
174                List<Field> fields = new ArrayList<>();
175                fields.addAll(Arrays.asList(component.getClass().getDeclaredFields()));
176                //check also the fields of the super class if exists
177                if(component.getClass().getSuperclass() != null){
178                        fields.addAll(Arrays.asList(component.getClass().getSuperclass().getDeclaredFields()));
179                }
180                return fields;
181        }
182        
183        private static Class<?> getClassForObject(Object obj){
184                if(map.containsKey(obj.getClass())){
185                        return map.get(obj.getClass());
186                } else {
187                        return obj.getClass();
188                }
189        }
190
191}