001package eu.cqse.check.framework.core;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006
007import org.conqat.lib.commons.assertion.CCSMAssert;
008import org.conqat.lib.commons.collections.CollectionUtils;
009import org.conqat.lib.commons.collections.PairList;
010import org.conqat.lib.commons.region.OffsetBasedRegion;
011import org.jaxen.BaseXPath;
012import org.jaxen.JaxenException;
013
014import eu.cqse.check.framework.core.phase.ECodeViewOption;
015import eu.cqse.check.framework.core.phase.ECodeViewOption.ETextViewOption;
016import eu.cqse.check.framework.core.xpath.CheckXPathUtils;
017import eu.cqse.check.framework.scanner.IToken;
018import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
019import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
020import eu.cqse.check.framework.util.CppLanguageFeatureParser;
021
022/**
023 * Abstract base class for custom checks. A check is instantiated
024 * ({@Link CheckInstance}) and initialized with a check context when its
025 * executing processor is set up (CheckProcessor.initializeChecks()) based on
026 * {@link CheckInfo} Objects. It is destroyed, when the executing processor has
027 * finished execution.
028 */
029public abstract class CheckImplementationBase {
030
031        /**
032         * The check context that is used to access custom check specific functionality.
033         */
034        protected ICheckContext context = null;
035
036        /**
037         * Sets the context. Must not be called if the context has already been set.
038         */
039        public void setContext(ICheckContext context) {
040                CCSMAssert.isTrue(this.context == null, "The context has already been set.");
041                this.context = context;
042        }
043
044        /**
045         * Initializes the custom check. This is called after the parameters have been
046         * set, but before the check is executed and before the context is set.
047         * 
048         * This method is called only once for a batch of files that need to be
049         * analyzed. Since the context is file-specific it can't be accessed here. This
050         * method is intended for preprocessing or parsing of the parameters.
051         *
052         * @throws CheckException
053         *             can be thrown by subclasses
054         */
055        public void initialize() throws CheckException {
056                // empty dummy implementation
057        }
058
059        /** Executes the custom check with the given context. */
060        public abstract void execute() throws CheckException;
061
062        /**
063         * Can be used to specifiy the code representation. See {@link ECodeViewOption}
064         * for details, the default is {@link ECodeViewOption#FILTERED}. See the
065         * {@linkplain <a href=
066         * "https://docs.google.com/presentation/d/1xNfWJSG057R9VCwEN3RGiV7C5u2uduLDlbQHy-ma7IY">
067         * Teamscale Code Processing Pipeline</a>}
068         */
069        protected ECodeViewOption getCodeViewOption() {
070                return ECodeViewOption.FILTERED;
071        }
072
073        /**
074         * Selects all shallow entities from the abstract syntax tree, that match the
075         * given xpath-expression. Set CodeViewOption via overwriting
076         * {@link #getCodeViewOption()}. This method can be used, if the executing check
077         * requests the {@link ECheckParameter#ABSTRACT_SYNTAX_TREE} parameter.
078         */
079        protected List<ShallowEntity> select(String xPathExpression) throws CheckException {
080                return select(context.getRootEntity(getCodeViewOption()), xPathExpression);
081        }
082
083        /**
084         * Selects all shallow entities from the abstract syntax tree, that match the
085         * given xpath-expression. Code representation is
086         * {@link ECodeViewOption#UNFILTERED_PREPROCESSED}.
087         */
088        protected List<ShallowEntity> selectUnfilteredPreProcessed(String xPathExpression) throws CheckException {
089                return select(context.getRootEntity(ECodeViewOption.UNFILTERED_PREPROCESSED), xPathExpression);
090        }
091
092        /**
093         * Creates a xpath-expression to select entities with the given type and the
094         * optional subtype alternatives eg. "//METHOD[subtype('function') |
095         * subtype('function declaration')]".
096         */
097        protected static String createSelectionPattern(EShallowEntityType entityType, String... subtypeAlternatives) {
098                String typeSelector = "//" + entityType.name();
099                if (subtypeAlternatives.length == 0) {
100                        return typeSelector;
101                }
102                return typeSelector + "[" + Arrays.stream(subtypeAlternatives).map(subtype -> "subtype('" + subtype + "')")
103                                .reduce((first, second) -> first + " | " + second).get() + "]";
104        }
105
106        /**
107         * Selects all shallow entities starting from the given entity, that match the
108         * given xpath-expression. This method is still selecting entities from the
109         * whole AST as long as the xPath expression does not limit the selection to a
110         * special axis. For more information have a look at
111         * http://www.w3schools.com/xsl/xpath_syntax.asp
112         */
113        @SuppressWarnings("unchecked")
114        public static List<ShallowEntity> select(ShallowEntity entity, String xPathExpression) throws CheckException {
115                BaseXPath xPath = CheckXPathUtils.getXPath(xPathExpression);
116                try {
117                        Object result = xPath.evaluate(entity);
118                        if (result instanceof List<?>) {
119                                return (List<ShallowEntity>) result;
120                        }
121                        throw new CheckException(
122                                        "Evaluating the xPath expression '" + xPathExpression + "' returned unexpected result: " + result);
123                } catch (JaxenException e) {
124                        throw new CheckException(e);
125                }
126        }
127
128        /**
129         * Creates a finding with the given message for the whole analyzed token
130         * element.
131         */
132        protected void createFinding(String message) throws CheckException {
133                context.createFindingForElement(message);
134        }
135
136        /**
137         * Creates a finding with the given message at the given token.
138         *
139         * Use this method, if the token is determined from
140         * {@link ICheckContext#getTextContent()} or another method that is based on
141         * <b>filtered</b> content.
142         */
143        protected void createFinding(String message, IToken token) throws CheckException {
144                createFinding(message, token, token);
145        }
146
147        /**
148         * Creates a finding with the given message beginning from startToken to
149         * endToken.
150         *
151         * Use this method, if the tokens are determined from
152         * {@link ICheckContext#getTextContent()} or another method that is based on
153         * <b>filtered</b> content.
154         */
155        protected void createFinding(String message, IToken startToken, IToken endToken) throws CheckException {
156                context.createFindingForOffsets(message, startToken.getOffset(), endToken.getEndOffset(),
157                                ETextViewOption.FILTERED_CONTENT);
158        }
159
160        /**
161         * Creates a finding with the given message beginning from first token to last
162         * token.
163         *
164         * Use this method, if the tokens are determined from
165         * {@link ICheckContext#getTextContent()} or another method that is based on
166         * <b>filtered</b> content.
167         */
168        protected void createFinding(String message, List<IToken> tokens) throws CheckException {
169                createFinding(message, tokens.get(0), CollectionUtils.getLast(tokens));
170        }
171
172        /**
173         * Creates a finding with the given message beginning from startToken to
174         * endToken.
175         *
176         * Use this method, if the tokens are determined from
177         * {@link ICheckContext#getUnfilteredTextContent()} or another method that is
178         * based on <b>unfiltered</b> content.
179         */
180        protected void createFindingOnUnfilteredCode(String message, IToken startToken, IToken endToken)
181                        throws CheckException {
182                context.createFindingForOffsets(message, startToken.getOffset(), endToken.getEndOffset(),
183                                ETextViewOption.UNFILTERED_CONTENT);
184        }
185
186        /**
187         * Creates a finding with the given message for the given entity.
188         *
189         * Use this method, if the shallow entity is determined from
190         * {@link ICheckContext#getTextContent()} or another method that is based on
191         * <b>filtered</b> content.
192         */
193        protected void createFinding(String message, ShallowEntity entity) throws CheckException {
194                context.createFindingForOffsets(message, entity.getStartOffset(), entity.getEndOffset(),
195                                ETextViewOption.FILTERED_CONTENT);
196        }
197
198        /**
199         * Creates a finding with the given message on the first line of the given
200         * entity.
201         *
202         * Use this method, if the shallow entity is determined from
203         * {@link ICheckContext#getTextContent()} or another method that is based on
204         * <b>filtered</b> content.
205         */
206        protected void createFindingOnFirstLine(String message, ShallowEntity entity) throws CheckException {
207                createFinding(message, entity.getStartLine());
208        }
209
210        /**
211         * Creates a finding with the given message at the given token.
212         *
213         * Use this method, if the token is determined from
214         * {@link ICheckContext#getUnfilteredTextContent()} or another method that is
215         * based on <b>unfiltered</b> content.
216         */
217        protected void createFindingOnUnfilteredCode(String message, IToken token) throws CheckException {
218                createFindingOnUnfilteredCode(message, token, token);
219        }
220
221        /**
222         * Creates a finding with the given message for the given entity.
223         *
224         * Use this method, if the shallow entity is determined from
225         * {@link ICheckContext#getUnfilteredTextContent()} or another method that is
226         * based on <b>unfiltered</b> content.
227         */
228        protected void createFindingOnUnfilteredCode(String message, ShallowEntity entity) throws CheckException {
229                context.createFindingForOffsets(message, entity.getStartOffset(), entity.getEndOffset(),
230                                ETextViewOption.UNFILTERED_CONTENT);
231        }
232
233        /**
234         * Creates a finding with the given message at the given line. Use this method,
235         * if the line is determined from {@link ICheckContext#getTextContent()} or
236         * another method that is based on <b>filtered</b> content.
237         */
238        protected void createFinding(String message, int line) throws CheckException {
239                context.createFindingForLines(message, line, line, ETextViewOption.FILTERED_CONTENT);
240        }
241
242        /**
243         * Creates findings with the given message for the given pair lists of start-
244         * and end tokens.
245         *
246         * @param message
247         *            the message describing the findings
248         * @param startAndEndTokens
249         *            A list of corresponding pairs of start and end tokens.
250         * @throws CheckException
251         */
252        protected void createFindingForSiblings(String message, PairList<IToken, IToken> startAndEndTokens)
253                        throws CheckException {
254                List<OffsetBasedRegion> regions = new ArrayList<>();
255                startAndEndTokens.forEach((startToken, endToken) -> {
256                        OffsetBasedRegion region = new OffsetBasedRegion(startToken.getOffset(), endToken.getOffset());
257                        regions.add(region);
258                });
259
260                context.createFindingForSiblingsInCurrentFile(message, regions);
261        }
262
263        /**
264         * Creates findings for entities which are associated in some way. The entities
265         * must stem from the same file.
266         */
267        protected void createFindingForSiblingEntities(String message, List<ShallowEntity> associatedEntities)
268                        throws CheckException {
269                List<OffsetBasedRegion> regions = new ArrayList<>();
270                associatedEntities.forEach(ent -> {
271                        OffsetBasedRegion region = new OffsetBasedRegion(ent.getStartOffset(), ent.getEndOffset());
272                        regions.add(region);
273                });
274                context.createFindingForSiblingsInCurrentFile(message, regions);
275        }
276
277        /**
278         * Creates a finding with the given message from the given start line
279         * (inclusive) to the given end line (inclusive). Use this method, if the lines
280         * are determined from {@link ICheckContext#getTextContent()} or another method
281         * that is based on <b>filtered</b> content.
282         */
283        protected void createFinding(String message, int startLine, int endLine) throws CheckException {
284                context.createFindingForLines(message, startLine, endLine, ETextViewOption.FILTERED_CONTENT);
285        }
286
287        /** Creates an instance of the given check class. */
288        public static CheckImplementationBase createInstance(Class<?> checkClass) throws CheckException {
289                CheckImplementationBase implementation = null;
290                try {
291                        implementation = (CheckImplementationBase) checkClass.newInstance();
292                } catch (InstantiationException e) {
293                        throw new CheckException("Check class " + checkClass.getName() + " does not provide a default constructor.",
294                                        e);
295                } catch (IllegalAccessException e) {
296                        throw new CheckException("Check class " + checkClass.getName() + "'s default constructor is not visible.",
297                                        e);
298                } catch (ClassCastException e) {
299                        throw new CheckException(
300                                        "Check class " + checkClass.getName() + " does not implement CheckImplementationBase.", e);
301                }
302                return implementation;
303        }
304
305        /**
306         * Determines if the the check is currently processing source code that is
307         * written in C++ based on the file extension and the C++ specific tokens.
308         * Especially distinguishes C++ from C source code.
309         *
310         * @return true if the context is C++, false else
311         */
312        public boolean isCpp() throws CheckException {
313                return CppLanguageFeatureParser.isCpp(context.getUniformPath(),
314                                context.getTokens(ECodeViewOption.FILTERED_PREPROCESSED));
315        }
316}