001package eu.cqse.check.framework.core.registry;
002
003import java.lang.reflect.Field;
004import java.util.Arrays;
005import java.util.LinkedHashMap;
006
007import org.conqat.lib.commons.assertion.CCSMAssert;
008import org.conqat.lib.commons.collections.CollectionUtils;
009import org.conqat.lib.commons.reflect.ReflectionUtils;
010
011import eu.cqse.check.framework.core.Check;
012import eu.cqse.check.framework.core.CheckException;
013import eu.cqse.check.framework.core.CheckImplementationBase;
014import eu.cqse.check.framework.core.CheckInfo;
015import eu.cqse.check.framework.core.ECheckParameter;
016import eu.cqse.check.framework.core.option.CheckOption;
017import eu.cqse.check.framework.core.option.CheckOptionWrapper;
018import eu.cqse.check.framework.scanner.ELanguage;
019import eu.cqse.check.framework.shallowparser.ShallowParserFactory;
020import eu.cqse.check.framework.typetracker.TypeTrackerFactory;
021import eu.cqse.check.util.clang.ClangUtils;
022
023/**
024 * Class for constructing a {@link CheckInfo} Object from a Custom Check Class
025 * ({@link CheckImplementationBase}).
026 */
027public class CheckLoader {
028
029        /** The class to load a check from. */
030        private final Class<?> checkClass;
031
032        /** Constructor. */
033        private CheckLoader(Class<?> checkClass) {
034                CCSMAssert.isNotNull(checkClass);
035                this.checkClass = checkClass;
036        }
037
038        /**
039         * Tries to load a check info from the check class that was set in the
040         * constructor. Returns null, if the given check class is not annotated with
041         * {@link Check}.
042         *
043         * @throws CheckException
044         *             if loading fails
045         */
046        private CheckInfo load() throws CheckException {
047                Check checkAnnotation = checkClass.getAnnotation(Check.class);
048                if (checkAnnotation == null) {
049                        return null;
050                }
051
052                CheckImplementationBase dummyInstance = CheckImplementationBase.createInstance(checkClass);
053
054                if (!languagesSupportParameters(checkAnnotation.languages(), checkAnnotation.parameters())) {
055                        throw new CheckException("Unsupported parameters!");
056                }
057
058                return new CheckInfo(checkClass, checkAnnotation.name(), checkAnnotation.description(),
059                                checkAnnotation.groupName(), checkAnnotation.categoryName(), checkAnnotation.defaultEnablement(),
060                                checkAnnotation.languages(), checkAnnotation.parameters(), checkAnnotation.phases(),
061                                loadOptions(dummyInstance));
062        }
063
064        /** Returns whether the given parameters support the given languages. */
065        private static boolean languagesSupportParameters(ELanguage[] languages, ECheckParameter[] parameters) {
066                for (ECheckParameter parameter : parameters) {
067                        if (parameter == ECheckParameter.CLANG) {
068                                if (CollectionUtils.intersectionSet(ClangUtils.CLANG_ENABLED_LANGUAGES, Arrays.asList(languages))
069                                                .isEmpty()) {
070                                        return false;
071                                }
072
073                                continue;
074                        }
075
076                        for (ELanguage language : languages) {
077                                if (!languageSupportParameter(language, parameter)) {
078                                        return false;
079                                }
080                        }
081                }
082                return true;
083        }
084
085        /** Returns whether the given parameter supports the given language. */
086        private static boolean languageSupportParameter(ELanguage language, ECheckParameter parameter) {
087                if (parameter == ECheckParameter.ABSTRACT_SYNTAX_TREE) {
088                        return ShallowParserFactory.supportsLanguage(language);
089                } else if (parameter == ECheckParameter.TYPE_RESOLUTION) {
090                        return TypeTrackerFactory.supportsLanguage(language);
091                }
092                return false;
093        }
094
095        /**
096         * Load options for a check. The given dummy instance is used to retrieve
097         * default values.
098         *
099         * Returns a LinkedHashMap since it keeps options ordered by insertion order
100         * (i.e., the order of options in analysis profile is determined by the order of
101         * option fields in the custom checks).
102         */
103        private LinkedHashMap<String, CheckOptionWrapper<?>> loadOptions(CheckImplementationBase dummyInstance)
104                        throws CheckException {
105                LinkedHashMap<String, CheckOptionWrapper<?>> options = new LinkedHashMap<>();
106                for (Field attribute : ReflectionUtils.getAllFields(checkClass)) {
107                        CheckOptionWrapper<?> option = getOptionFromField(attribute, dummyInstance);
108
109                        if (option == null) {
110                                continue;
111                        }
112
113                        if (options.put(option.getName(), option) != null) {
114                                throw new CheckException(
115                                                "Check " + checkClass.getName() + " has duplicated option: " + option.getName());
116                        }
117                }
118                return options;
119        }
120
121        /**
122         * Returns a CheckOption for the given field. If the given field is not
123         * annotated with ACheckOption null is returned.
124         */
125        @SuppressWarnings({ "rawtypes", "unchecked" })
126        private CheckOptionWrapper<?> getOptionFromField(Field field, CheckImplementationBase dummyInstance)
127                        throws CheckException {
128                field.setAccessible(true);
129                CheckOption annotation = field.getAnnotation(CheckOption.class);
130                if (annotation == null) {
131                        return null;
132                }
133
134                try {
135                        Object defaultValue = field.get(dummyInstance);
136                        if (defaultValue == null) {
137                                throw new CheckException("Check " + checkClass.getName() + " must have default values for options.");
138                        }
139
140                        Class<?> type = field.getType();
141                        if (type.isPrimitive()) {
142                                // If the type is primitive we wrap it into a the
143                                // corresponding wrapper class, as the whole custom check
144                                // option framework works with non-primitive types only
145                                // Writing values back to the attribute works due to
146                                // auto-boxing.
147                                type = ReflectionUtils.resolvePrimitiveClass(type);
148                        }
149
150                        return new CheckOptionWrapper(annotation, field, defaultValue, type);
151                } catch (IllegalAccessException e) {
152                        CCSMAssert.fail("Could not access default value: " + e.getMessage());
153                }
154                return null;
155        }
156
157        /**
158         * Tries to load a check info from the given check class. Returns null, if the
159         * given check class is not annotated with {@link Check}.
160         *
161         * @throws CheckException
162         *             if loading fails
163         */
164        public static CheckInfo loadFromClass(Class<?> checkClass) throws CheckException {
165                return new CheckLoader(checkClass).load();
166        }
167}