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}