001/**
002 * Copyright (C) 2007 - 2016, 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 */
019package org.dllearner.core;
020
021import com.google.common.collect.Sets;
022import org.apache.commons.collections4.BidiMap;
023import org.apache.commons.collections4.bidimap.DualHashBidiMap;
024import org.apache.log4j.Level;
025import org.dllearner.core.config.ConfigOption;
026import org.dllearner.accuracymethods.AccMethod;
027import org.dllearner.refinementoperators.RefinementOperator;
028import org.reflections.Reflections;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import java.lang.reflect.Field;
033import java.lang.reflect.InvocationTargetException;
034import java.lang.reflect.Method;
035import java.lang.reflect.Modifier;
036import java.util.*;
037import java.util.stream.Collectors;
038
039/**
040 * Component manager for the new (as of 2011) annotation based configuration
041 * system.
042 *
043 * In the future, this may replace the previous implementation of component
044 * manager.
045 *
046 * @author Jens Lehmann
047 *
048 */
049public class AnnComponentManager {
050        private static Logger logger = LoggerFactory.getLogger(AnnComponentManager.class);
051
052    // the list of annotation based components (note that we save them as string here
053    // instead of class objects in order not to have dependencies on the implementation
054    // of components, which are not required to be in the core module - the class
055    // objects are only created on invocation of the component manager);
056    // components must be listed here if they should be supported in interfaces
057    // (CLI, GUI, Web Service) and scripts (HTML documentation generator)
058    private static List<String> componentClassNames;
059//    ;= new ArrayList<String>  ( Arrays.asList(new String[]{
060//            "org.dllearner.algorithms.NaiveALLearner",
061//            "org.dllearner.algorithms.celoe.CELOE",
062////            "org.dllearner.algorithms.celoe.PCELOE",
063//            "org.dllearner.algorithms.el.ELLearningAlgorithm",
064//            "org.dllearner.algorithms.el.ELLearningAlgorithmDisjunctive",
065////            "org.dllearner.algorithms.fuzzydll.FuzzyCELOE",
066////            "org.dllearner.algorithms.BruteForceLearner",
067////            "org.dllearner.algorithms.RandomGuesser",
068//            "org.dllearner.algorithms.properties.DisjointObjectPropertyAxiomLearner",
069//            "org.dllearner.algorithms.properties.EquivalentObjectPropertyAxiomLearner",
070//            "org.dllearner.algorithms.properties.FunctionalObjectPropertyAxiomLearner",
071//            "org.dllearner.algorithms.properties.InverseFunctionalObjectPropertyAxiomLearner",
072//            "org.dllearner.algorithms.properties.ObjectPropertyDomainAxiomLearner",
073//            "org.dllearner.algorithms.properties.ObjectPropertyRangeAxiomLearner",
074//            "org.dllearner.algorithms.properties.SubObjectPropertyOfAxiomLearner",
075//            "org.dllearner.algorithms.properties.SymmetricObjectPropertyAxiomLearner",
076//            "org.dllearner.algorithms.properties.TransitiveObjectPropertyAxiomLearner",
077//            "org.dllearner.algorithms.properties.DisjointDataPropertyAxiomLearner",
078//            "org.dllearner.algorithms.properties.EquivalentDataPropertyAxiomLearner",
079//            "org.dllearner.algorithms.properties.FunctionalDataPropertyAxiomLearner",
080//            "org.dllearner.algorithms.properties.DataPropertyDomainAxiomLearner",
081//            "org.dllearner.algorithms.properties.DataPropertyRangeAxiomLearner",
082//            "org.dllearner.algorithms.properties.SubDataPropertyOfAxiomLearner",
083//            "org.dllearner.algorithms.DisjointClassesLearner",
084//            "org.dllearner.algorithms.SimpleSubclassLearner",
085//            "org.dllearner.algorithms.qtl.QTL2Disjunctive",
086//            "org.dllearner.kb.KBFile",
087//            "org.dllearner.kb.OWLFile",
088//            "org.dllearner.kb.SparqlEndpointKS",
089//            "org.dllearner.kb.LocalModelBasedSparqlEndpointKS",
090//            "org.dllearner.kb.sparql.SparqlKnowledgeSource",
091//            "org.dllearner.kb.sparql.simple.SparqlSimpleExtractor",
092//            "org.dllearner.learningproblems.PosNegLPStandard",
093////            "org.dllearner.learningproblems.PosNegLPStrict",
094////            "org.dllearner.learningproblems.FuzzyPosNegLPStandard",
095//            "org.dllearner.learningproblems.PosOnlyLP",
096//            "org.dllearner.learningproblems.ClassLearningProblem",
097//            "org.dllearner.learningproblems.PropertyAxiomLearningProblem",
098//            "org.dllearner.reasoning.ClosedWorldReasoner",
099//            "org.dllearner.reasoning.OWLAPIReasoner",
100//            "org.dllearner.reasoning.SPARQLReasoner",
101////            "org.dllearner.reasoning.fuzzydll.FuzzyOWLAPIReasoner",
102//            "org.dllearner.algorithms.ocel.OCEL",
103//            "org.dllearner.algorithms.ocel.MultiHeuristic",
104//            "org.dllearner.algorithms.celoe.OEHeuristicRuntime",
105//            "org.dllearner.algorithms.isle.NLPHeuristic",
106//            "org.dllearner.algorithms.qtl.heuristics.QueryTreeHeuristicComplex",
107//            "org.dllearner.algorithms.qtl.heuristics.QueryTreeHeuristicSimple",
108//            "org.dllearner.algorithms.el.DisjunctiveHeuristic",
109//            "org.dllearner.algorithms.isle.metrics.RelevanceWeightedStableHeuristic",
110//            "org.dllearner.algorithms.el.StableHeuristic",
111//            "org.dllearner.refinementoperators.RhoDRDown",
112////            "org.dllearner.refinementoperators.SynchronizedRhoDRDown",
113//            // just for testing
114//            // "org.dllearner.refinementoperators.ExampleOperator",
115//            "org.dllearner.utilities.semkernel.SemKernelWorkflow",
116//            "org.dllearner.utilities.semkernel.MPSemKernelWorkflow",
117//    } ));
118    private static Collection<Class<? extends Component>> components;
119    private static BidiMap<Class<? extends Component>, String> componentNames;
120    private static BidiMap<Class<? extends Component>, String> componentNamesShort;
121
122        private static AnnComponentManager cm = null;
123        private static Reflections reflectionScanner = null;
124
125        private AnnComponentManager() {
126                if (componentClassNames == null) {
127                        componentClassNames = new ArrayList<>();
128                        if (reflectionScanner == null) {
129                                org.apache.log4j.Logger.getLogger(Reflections.class).setLevel(Level.OFF);
130                                reflectionScanner = new Reflections("org.dllearner");
131                        }
132                        Set<Class<? extends Component>> componentClasses = reflectionScanner.getSubTypesOf(Component.class);
133                        Set<Class<?>> componentAnnClasses = reflectionScanner.getTypesAnnotatedWith(ComponentAnn.class, true);
134                        for (Class<?> clazz
135                                        : Sets.intersection(
136                                                        componentClasses,
137                                                        componentAnnClasses
138                                        )
139                                ) {
140                                if (!Modifier.isAbstract( clazz.getModifiers() ))
141                                        componentClassNames.add(clazz.getCanonicalName());
142                        }
143                        for (Class<?> clazz
144                                        : Sets.difference(componentClasses, componentAnnClasses)
145                                        ) {
146                                if (!Modifier.isAbstract( clazz.getModifiers() ))
147                                        logger.debug("Warning: " + clazz.getCanonicalName() + " implements Component but is not annotated, ignored");
148                        }
149                }
150                // conversion of class strings to objects
151                components = new TreeSet<>((Comparator<Class<? extends Component>>) (o1, o2) -> {
152                        return o1.getName().compareTo(o2.getName());
153                });
154                componentNames = new DualHashBidiMap<>();
155                componentNamesShort = new DualHashBidiMap<>();
156                for (String componentClassName : componentClassNames) {
157                        try {
158                                Class<? extends Component> component = Class.forName(componentClassName).asSubclass(Component.class);
159                                components.add(component);
160                                componentNames.put(component, getName(component));
161                                componentNamesShort.put(component, getShortName(component));
162                        } catch (ClassNotFoundException e) {
163                                e.printStackTrace();
164                        }
165                }
166        }
167
168        /**
169         * Explicitly sets the list of components to use. This will (re-)initialise the
170         * component manager the next time the singleton instance is retrieved.
171         */
172        public static void setComponentClassNames(List<String> componentClassNames) {
173                AnnComponentManager.componentClassNames = componentClassNames;
174                cm = null;
175        }
176        
177        public static void setReflectionScanner(Reflections ref) {
178                AnnComponentManager.reflectionScanner = ref;
179                setComponentClassNames(null);
180        }
181
182        /**
183         * Gets the singleton instance of <code>ComponentManager</code>.
184         * @return The singleton <code>ComponentManager</code> instance.
185         */
186        public static AnnComponentManager getInstance() {
187                if(cm == null) {
188                        cm = new AnnComponentManager();
189                }
190                return cm;
191        }
192
193        /**
194         * Returns a list of all available components in this instance
195         * of <code>ComponentManager</code>.
196         * @return the components A list of component classes available in this
197         * instance of <code>ComponentManager</code>.
198         */
199        public Collection<Class<? extends Component>> getComponents() {
200                return components;
201        }
202        
203        /**
204         * Returns a list of all available components in this instance
205         * of <code>ComponentManager</code>.
206         * @return the components A list of component classes available in this
207         * instance of <code>ComponentManager</code>.
208         */
209        public SortedSet<String> getComponentStrings() {
210                SortedSet<String> result = getComponents().stream()
211                                .map(AnnComponentManager::getShortName)
212                                .collect(Collectors.toCollection(TreeSet::new));
213                return result;
214        }
215
216    /**
217     * Get registered components which are of the specified type.
218     *
219     * @param type The super type.
220     * @return All sub classes of type.
221     */
222    public SortedSet<String> getComponentStringsOfType(Class type) {
223
224        SortedSet<String> result = getComponentsOfType(type).stream()
225                            .map(AnnComponentManager::getShortName)
226                            .collect(Collectors.toCollection(TreeSet::new));
227
228            return result;
229    }
230    
231    /**
232     * Get the corresponding component class given the long or short name.
233     *
234     * @param componentName The long or short name of the component.
235     * @return The class of the component.
236     */
237    public Class<? extends Component> getComponentClass(String componentName) {
238        // lookup by long name
239        Class<? extends Component> componentClass = componentNames.getKey(componentName);
240        
241        // lookup by short name
242        if(componentClass == null) {
243                componentClass = componentNamesShort.getKey(componentName);
244        }
245
246        return componentClass;
247    }
248    
249    /**
250     * Get registered components which are of the specified type.
251     *
252     * @param type The super type.
253     * @return All sub classes of type.
254     */
255    public Collection<Class<? extends Component>> getComponentsOfType(Class type) {
256
257        Collection<Class<? extends Component>> result = components.stream()
258                        .filter(component -> type.isAssignableFrom(component))
259                        .collect(Collectors.toCollection(ArrayList::new));
260
261            return result;
262    }
263
264        /**
265         * Convenience method, which returns a list of components along with
266         * their name.
267         *
268         * @return A map where the key is the class of the component and the
269         * value is its name.
270         */
271        public BidiMap<Class<? extends Component>, String> getComponentsNamed() {
272                return componentNames;
273        }
274
275        /**
276         * Convenience method, which returns a list of components along with
277         * their name.
278         *
279         * @return A map where the key is the class of the component and the
280         * value is its name.
281         */
282        public BidiMap<Class<? extends Component>, String> getComponentsNamedShort() {
283                return componentNamesShort;
284        }
285
286        /**
287         * Applies a config entry to a component. If the entry is not valid, the method
288         * prints an exception and returns false.
289         * @param <T> Type of the config option.
290         * @param component A component object.
291         * @param optionName the option name
292         * @param value the value to set
293         * @return True if the config entry could be applied succesfully, otherwise false.
294         */
295        @Deprecated
296        public static <T> boolean applyConfigEntry(AbstractComponent component, String optionName, T value) {
297                List<AbstractComponent> childComponents = new LinkedList<>();
298                for (Method m : component.getClass().getMethods()) {
299                        if (m.getName().equals("set" + optionName.substring(0, 1).toUpperCase() + optionName.substring(1))) {
300                                try {
301                                        m.invoke(component, value);
302                                } catch (IllegalAccessException | IllegalArgumentException
303                                                | InvocationTargetException e) {
304                                        logger.debug("Error setting " + optionName + " to " + value + " on " + component + ": ", e);
305                                        return false;
306                                }
307                                return true;
308                        } else if (m.getName().startsWith("get")
309                                        && AbstractComponent.class.isAssignableFrom(m.getReturnType())) {
310                                Object cc;
311                                try {
312                                        cc = m.invoke(component);
313                                        childComponents.add((AbstractComponent) cc);
314                                } catch (IllegalAccessException | IllegalArgumentException
315                                                | InvocationTargetException e) {
316                                        logger.trace("Error querying " + m.getName() + " for subcomponent in " + component, e);
317                                }
318                        }
319                }
320                for (AbstractComponent cc : childComponents) {
321                        if (cc != null) {
322                                boolean try_inv = applyConfigEntry(cc, optionName, value);
323                                if (try_inv) {
324                                        return true;
325                                }
326                        }
327                }
328                return false;
329        }
330
331        public final static Class[] coreComponentClasses = {
332                KnowledgeSource.class,
333                LearningAlgorithm.class,
334                AxiomLearningAlgorithm.class,
335                ClassExpressionLearningAlgorithm.class,
336                LearningProblem.class,
337                ReasonerComponent.class,
338                RefinementOperator.class,
339                Heuristic.class,
340                AccMethod.class
341        };
342
343        /**
344         * Convenience method to retrieve core types of a component. The main use case for this
345         * is for automatic documentation generation.
346         *
347         * @param component A component.
348         * @return The list of core interfaces the component implemnets.
349         */
350        public static List<Class<? extends Component>> getCoreComponentTypes(Class<? extends Component> component) {
351                List<Class<? extends Component>> types = new LinkedList<>();
352                for(Class c : coreComponentClasses) {
353                        if(c.isAssignableFrom(component)) {
354                                types.add(c);
355                        }
356                }
357                return types;
358        }
359
360        /**
361         * Returns the name of a DL-Learner component.
362         * @param component the component
363         * @return Name of the component.
364         */
365        public static String getName(Class<? extends Component> component){
366                ComponentAnn ann = component.getAnnotation(ComponentAnn.class);
367                if(ann == null) {
368                        throw new Error("Component " + component + " does not use component annotation.");
369                }
370                return ann.name();
371        }
372
373        /**
374         * Returns the name of a DL-Learner component.
375         * @param component the component
376         * @return Name of the component.
377         */
378        public static String getName(Component component){
379                return getName(component.getClass());
380        }
381
382        /**
383         * Returns the name of a DL-Learner component.
384         * @param component the component
385         * @return Name of the component.
386         */
387        public static String getShortName(Class<? extends Component> component){
388                ComponentAnn ann = component.getAnnotation(ComponentAnn.class);
389                if(ann == null) {
390                        throw new Error("Component " + component + " does not use component annotation.");
391                }
392                return ann.shortName();
393        }
394
395        /**
396         * Returns the short name of a DL-Learner component.
397         * @param component the component
398         * @return Short name of the component.
399         */
400        public static String getShortName(Component component){
401                return getShortName(component.getClass());
402        }
403
404        /**
405         * Returns the name of a DL-Learner component.
406         * @param component the component
407         * @return Name of the component.
408         */
409        public static String getDescription(Class<? extends Component> component){
410                ComponentAnn ann = component.getAnnotation(ComponentAnn.class);
411                return ann.description();
412        }
413
414        /**
415         * Returns the description of a DL-Learner component.
416         * @param component the component
417         * @return OWLClassExpression of the component.
418         */
419        public static String getDescription(Component component){
420                return getDescription(component.getClass());
421        }
422        
423        /**
424         * Returns the config options of a DL-Learner component.
425         * @param component the component
426         * @return OWLClassExpression of the component.
427         */
428        public static Set<Field> getConfigOptions(Class<? extends Component> component){
429                Set<Field> set = new HashSet<>();
430            Class<?> c = component;
431            while (c != null) {
432                for (Field field : c.getDeclaredFields()) {
433                    if (field.isAnnotationPresent(ConfigOption.class)) {
434                        set.add(field);
435                    }
436                }
437                c = c.getSuperclass();
438            }
439            return set;
440        }
441
442        /**
443         * Returns the version of a DL-Learner component.
444         * @param component the component
445         * @return Version of the component.
446         */
447        public static double getVersion(Class<? extends Component> component){
448                ComponentAnn ann = component.getAnnotation(ComponentAnn.class);
449                return ann.version();
450        }
451
452        /**
453         * Returns the version of a DL-Learner component.
454         * @param component the component
455         * @return Version of the component.
456         */
457        public static double getVersion(Component component){
458                return getVersion(component.getClass());
459        }
460
461        public static boolean addComponentClassName(String e) {
462                return componentClassNames.add(e);
463        }
464
465        /**
466         * Returns the name of a config option
467         * @param f the Reflection field of the option
468         * @return name of the option
469         */
470        public static String getName(Field f) {
471                f.getAnnotation(ConfigOption.class);
472                return f.getName();
473        }
474}