001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.shallowparser.util;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.EnumSet;
022import java.util.List;
023import java.util.Optional;
024import java.util.Set;
025import java.util.function.Predicate;
026
027import org.conqat.lib.commons.collections.CollectionUtils;
028import org.conqat.lib.commons.collections.UnmodifiableList;
029import org.conqat.lib.commons.region.LineBasedRegion;
030import org.conqat.lib.commons.region.OffsetBasedRegion;
031
032import com.google.common.collect.ImmutableSet;
033
034import eu.cqse.check.framework.scanner.ELanguage;
035import eu.cqse.check.framework.scanner.ETokenType;
036import eu.cqse.check.framework.scanner.IToken;
037import eu.cqse.check.framework.shallowparser.SubTypeNames;
038import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
039import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
040import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
041import eu.cqse.check.framework.shallowparser.framework.ShallowEntityTraversalUtils;
042
043/**
044 * Utility methods used for dealing with {@link ShallowEntity}s.
045 */
046public class ShallowParsingUtils {
047
048        /**
049         * Entity subtypes that are part of a branching statement but not the first
050         * option of the branches. Those statements must not be separated from the first
051         * part of the branching statement when we consider refactoring suggestions. DO
052         * is part of this set because a last entity of a refactoring suggestion
053         * covering a do-while loop (where while is the branching statement) can be of
054         * type DO.
055         */
056        private static final Set<String> SUBPART_BRANCHING_STATEMENTS = ImmutableSet.of(SubTypeNames.ELSE_IF,
057                        SubTypeNames.ELSE, SubTypeNames.CATCH, SubTypeNames.FINALLY, SubTypeNames.CASE, SubTypeNames.DEFAULT,
058                        SubTypeNames.USING, SubTypeNames.DO);
059
060        /**
061         * A set containing C# contextual keywords. A contextual keyword is used to
062         * provide a specific meaning in the code, but it is not a reserved word in C#.
063         * Thus, one could name a local variable or method using a contextual keyword.
064         */
065        private static final Set<ETokenType> CONTEXTUAL_KEYWORDS = ImmutableSet.of(ETokenType.ADD, ETokenType.ASYNC,
066                        ETokenType.AWAIT, ETokenType.DYNAMIC, ETokenType.GET, ETokenType.GLOBAL, ETokenType.PARTIAL,
067                        ETokenType.REMOVE, ETokenType.SET, ETokenType.VALUE, ETokenType.VAR, ETokenType.WHEN, ETokenType.WHERE,
068                        ETokenType.YIELD, ETokenType.GROUP);
069
070        /**
071         * EnumSet containing every language in which it is possible that the type info
072         * of a variable or parameter might be after a colon.
073         * <p>
074         * E.g. test(param1 : String)
075         */
076        private static final EnumSet<ELanguage> LANGUAGES_TYPE_INFO_AFTER_COLON = EnumSet.of(ELanguage.JAVASCRIPT,
077                        ELanguage.GOSU);
078
079        /**
080         * Entity subtypes that represent looping statements
081         */
082        private static final Set<String> LOOP_SUBTYPES = ImmutableSet.of(SubTypeNames.FOR, SubTypeNames.FOREACH,
083                        SubTypeNames.WHILE);
084
085        /** Lists all primitive statements (i.e. statements without children). */
086        public static List<ShallowEntity> listPrimitiveStatements(Collection<ShallowEntity> entities) {
087                return new ShallowEntityTraversalUtils.CollectingVisitorBase() {
088                        @Override
089                        protected boolean collect(ShallowEntity entity) {
090                                return entity.getType() == EShallowEntityType.STATEMENT && entity.getChildren().isEmpty();
091                        }
092                }.apply(entities);
093        }
094
095        /** Lists all nested statements (i.e. statements with children). */
096        public static List<ShallowEntity> listNestedStatements(Collection<ShallowEntity> entities) {
097                return new ShallowEntityTraversalUtils.CollectingVisitorBase() {
098                        @Override
099                        protected boolean collect(ShallowEntity entity) {
100                                UnmodifiableList<ShallowEntity> children = entity.getChildren();
101                                return entity.getType() == EShallowEntityType.STATEMENT && !children.isEmpty()
102                                // only include this, if nested is also code and not an
103                                // anonymous function, class, etc.
104                                                && children.get(0).getType() == EShallowEntityType.STATEMENT;
105                        }
106                }.apply(entities);
107        }
108
109        /**
110         * Returns the tokens of a method entity that correspond to parameter names.
111         */
112        public static List<IToken> extractParameterNameTokens(ShallowEntity entity) {
113                return extractVariableNameTokens(
114                                TokenStreamUtils.tokensBetween(entity.ownStartTokens(), ETokenType.LPAREN, ETokenType.RPAREN), true);
115        }
116
117        /** Returns the tokens corresponding to variable names. */
118        public static List<IToken> extractVariableNameTokens(List<IToken> tokens) {
119                return extractVariableNameTokens(tokens, false);
120        }
121
122        /**
123         * Returns the tokens corresponding to variable names. Heuristic is to look at
124         * identifier tokens outside of parenthesis/braces and directly before a comma,
125         * equals sign, or closing parenthesis. The equals sign is needed to deal with
126         * C++ default parameters and assignments in local variables. Additionally, we
127         * drop brackets, as they can obscure the type/name separation.
128         *
129         * @param ignoreParameterTypes
130         *            if this is true, identifiers at the beginning, or after a comma or
131         *            a double-colon are ignored, as they are likely type names in a
132         *            parameter list (C++ allows to skip the parameter name in some
133         *            cases).
134         */
135        private static List<IToken> extractVariableNameTokens(List<IToken> tokens, boolean ignoreParameterTypes) {
136                List<IToken> result = new ArrayList<>();
137                int parenthesisNesting = 0;
138                boolean waitForComma = false;
139                IToken previousToken = null;
140                ETokenType beforePreviousType = null;
141                for (IToken token : tokens) {
142                        switch (token.getType()) {
143                        case LBRACK:
144                        case LT:
145                        case LPAREN:
146                        case LBRACE:
147                                parenthesisNesting += 1;
148                                break;
149
150                        case RBRACK:
151                        case GT:
152                        case RPAREN:
153                        case RBRACE:
154                                parenthesisNesting -= 1;
155                                // do not update previousToken
156                                continue;
157
158                        case COMMA:
159                        case ARRAY_SEPARATOR:
160                                // the commas in matlab methods are parsed as array separators
161                                if (parenthesisNesting == 0) {
162                                        if (!waitForComma && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) {
163                                                result.add(previousToken);
164                                        }
165                                        waitForComma = false;
166                                }
167                                break;
168
169                        case EQ:
170                        case EOL:
171                        case EQUAL:
172                        case SEMICOLON:
173                                if (parenthesisNesting == 0 && !waitForComma
174                                                && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) {
175                                        result.add(previousToken);
176                                        waitForComma = true;
177                                }
178                                break;
179
180                        case COLON:
181                                // In TypeScript you can add type information after colon
182                                if (LANGUAGES_TYPE_INFO_AFTER_COLON.contains(token.getLanguage()) && parenthesisNesting == 0
183                                                && !waitForComma && isParameterName(previousToken, beforePreviousType, false)) {
184                                        result.add(previousToken);
185                                        waitForComma = true;
186                                }
187                                break;
188
189                        case QUESTION:
190                                // skip question mark (optional parameter token) between parameter name and
191                                // colon (TypeScript)
192                                token = previousToken;
193                                break;
194                        default:
195                                break;
196                        }
197
198                        if (parenthesisNesting == 0) {
199                                if (previousToken != null) {
200                                        beforePreviousType = previousToken.getType();
201                                }
202                                previousToken = token;
203                        }
204                }
205
206                if (parenthesisNesting == 0 && !waitForComma
207                                && isParameterName(previousToken, beforePreviousType, ignoreParameterTypes)) {
208                        result.add(previousToken);
209                }
210
211                return result;
212        }
213
214        /** Returns whether the previous token could be a parameter name. */
215        private static boolean isParameterName(IToken previousToken, ETokenType beforePreviousType,
216                        boolean ignoreParameterTypes) {
217                if (previousToken == null) {
218                        return false;
219                }
220
221                if (previousToken.getLanguage() == ELanguage.JAVASCRIPT) {
222                        return previousToken.getType() == ETokenType.IDENTIFIER && (!ignoreParameterTypes
223                                        || (beforePreviousType == ETokenType.COMMA || beforePreviousType == null));
224
225                }
226
227                return (previousToken.getType() == ETokenType.IDENTIFIER || isCsContextualKeyword(previousToken))
228                                && (!ignoreParameterTypes || (beforePreviousType != ETokenType.COMMA && beforePreviousType != null
229                                                && beforePreviousType != ETokenType.SCOPE));
230        }
231
232        private static boolean isCsContextualKeyword(IToken token) {
233                return token.getLanguage() == ELanguage.CS && CONTEXTUAL_KEYWORDS.contains(token.getType());
234        }
235
236        /**
237         * Returns the list of tokens corresponding to the names of variables newly
238         * declared within a for loop.
239         */
240        public static List<IToken> extractVariablesDeclaredInFor(ShallowEntity entity) {
241                List<IToken> forLoopInitTokens = TokenStreamUtils.tokensBetween(entity.ownStartTokens(), ETokenType.LPAREN,
242                                ETokenType.SEMICOLON);
243                List<IToken> variableNameTokens = ShallowParsingUtils.extractVariableNameTokens(forLoopInitTokens);
244
245                // handle the case where only existing variables are initialized
246                if (!variableNameTokens.isEmpty() && variableNameTokens.get(0) == forLoopInitTokens.get(0)) {
247                        variableNameTokens.clear();
248                }
249
250                return variableNameTokens;
251        }
252
253        /** Returns whether the entity is a local variable. */
254        public static boolean isLocalVariable(ShallowEntity entity) {
255                return entity.getType() == EShallowEntityType.STATEMENT
256                                && SubTypeNames.LOCAL_VARIABLE.equals(entity.getSubtype());
257        }
258
259        /** Returns whether the entity is a global variable. */
260        public static boolean isGlobalVariable(ShallowEntity entity) {
261                return entity.getType() == EShallowEntityType.ATTRIBUTE
262                                && (entity.getParent() == null || entity.getParent().getType() == EShallowEntityType.MODULE);
263        }
264
265        /**
266         * Checks if <code>continue</code> or <code>break</code> are contained in a
267         * <code>for</code> or <code>while</code> loop in the given block. If there is
268         * <code>continue</code>, <code>break</code> or <code>return;</code> in a code
269         * segment, it can not be extracted. (Note: <code>return sth;</code> may be
270         * extractable.)
271         *
272         * @param block
273         *            the {@link ShallowEntity ShallowEntities} that will be checked.
274         * @return <code>true</code> if there is <code>continue</code> or
275         *         <code>break</code>
276         */
277        public static boolean containsLoopReferenceWithoutLoop(List<ShallowEntity> block) {
278                for (ShallowEntity statement : block) {
279                        if (statement.getSubtype().equals(SubTypeNames.FOR) || statement.getSubtype().equals(SubTypeNames.WHILE)) {
280                                continue;
281                        }
282                        List<IToken> startTokens = statement.ownStartTokens();
283                        IToken firstToken = startTokens.get(0);
284                        if (firstToken.getType() == ETokenType.CONTINUE || firstToken.getType() == ETokenType.BREAK) {
285                                return true;
286                        } else if (firstToken.getType() == ETokenType.RETURN) {
287                                // return statements are not extractable (for us), even if
288                                // return obj;
289                                // may be extractable if it is at the very end of the original
290                                // method
291                                return true;
292                        }
293                }
294                for (ShallowEntity statement : block) {
295                        // when the statement contains a whole anonymous class or lambda, loops don't
296                        // affect the behavior and can be ignored.
297                        if (containsAnonymousClassOrLambda(statement)) {
298                                continue;
299                        }
300                        if (containsLoopReferenceWithoutLoop(statement.getChildren())) {
301                                return true;
302                        }
303                }
304                return false;
305        }
306
307        /**
308         * Checks if the child of the statement is an anonymous class or lambda
309         * expression
310         *
311         * @param statement
312         *            the {@link ShallowEntity} that will be checked.
313         * @return <code>true</code> if there is an anonymous class or lambda.
314         */
315        private static boolean containsAnonymousClassOrLambda(ShallowEntity statement) {
316                return !statement.getChildren().isEmpty()
317                                && (statement.getChildren().get(0).getSubtype().equals(SubTypeNames.ANONYMOUS_CLASS)
318                                                || statement.getChildren().get(0).getSubtype().equals(SubTypeNames.LAMBDA));
319        }
320
321        /**
322         * Returns for a given entity that contains an <code>if</code> or
323         * <code>try</code> block a list of {@link ShallowEntity ShallowEntities} that
324         * represents the corresponding <code>else if</code> or <code>catch</code> and
325         * <code>else</code> or <code>finally</code> block. That means that the complete
326         * statement is determined and returned.
327         *
328         * @param shallowEntity
329         *            the entity that contains an <code>if</code> or <code>try</code>
330         *            block
331         * @return The list of {@link ShallowEntity ShallowEntities} that contains the
332         *         corresponding block with all <code>else if</code> or
333         *         <code>catch</code> and <code>else</code> or <code>finally</code>
334         *         entities belonging to the given shallowEntity
335         */
336        public static List<ShallowEntity> getCompleteStatement(ShallowEntity shallowEntity) {
337
338                String secondKeyword = "";
339                String thirdKeyword = "";
340
341                if (shallowEntity.getSubtype().equals(SubTypeNames.IF)) {
342                        secondKeyword = SubTypeNames.ELSE_IF;
343                        thirdKeyword = SubTypeNames.ELSE;
344                } else if (shallowEntity.getSubtype().equals(SubTypeNames.TRY)) {
345                        secondKeyword = SubTypeNames.CATCH;
346                        thirdKeyword = SubTypeNames.FINALLY;
347                } else if (shallowEntity.getSubtype().equals(SubTypeNames.SWITCH)) {
348                        secondKeyword = SubTypeNames.CASE;
349                        thirdKeyword = SubTypeNames.DEFAULT;
350                } else {
351                        return CollectionUtils.emptyList();
352                }
353
354                return resembleCompleteStatement(shallowEntity, secondKeyword, thirdKeyword);
355        }
356
357        /**
358         * For a given {@link ShallowEntity} of subtype <code>if</code>,
359         * <code>try</code> or <code>switch</code> and the corresponding keywords the
360         * complete statement is resembled
361         *
362         * @param shallowEntity
363         *            A {@link ShallowEntity} of subtype <code>if</code>,
364         *            <code>try</code> or <code>switch</code>.
365         * @param secondKeyword
366         *            The corresponding second keyword, for <code>if</code> that is
367         *            <code>else if</code>.
368         * @param thirdKeyword
369         *            The corresponding third keyword, for <code>if</code> that is
370         *            <code>else</code>.
371         * @return the complete statement
372         */
373        private static List<ShallowEntity> resembleCompleteStatement(ShallowEntity shallowEntity, String secondKeyword,
374                        String thirdKeyword) {
375                List<ShallowEntity> completeStatement = new ArrayList<>();
376                completeStatement.add(shallowEntity);
377                List<ShallowEntity> children = shallowEntity.getParent().getChildren();
378
379                boolean firstKeywordFound = false;
380                // Construct the complete statement: First if/try, then as many else
381                // if/catch as you need and finally (optionally) one else/finally
382                // statement.
383                for (ShallowEntity child : children) {
384                        if (!firstKeywordFound) {
385                                if (child.equals(shallowEntity)) {
386                                        firstKeywordFound = true;
387                                }
388                        } else if (child.getSubtype().equals(secondKeyword)) {
389                                completeStatement.add(child);
390
391                        } else if (child.getSubtype().equals(thirdKeyword)) {
392                                completeStatement.add(child);
393                                return completeStatement;
394                        } else {
395                                break;
396                        }
397                }
398
399                return completeStatement;
400        }
401
402        /**
403         * Returns the entity which is directly after the given {@link ShallowEntity}.
404         *
405         * @param candidate
406         *            the last entity of the candidate
407         * @return The first entity which begins after the the given
408         *         {@link ShallowEntity}. If there is no such entity, <code>null</code>
409         *         will be returned.
410         */
411        public static ShallowEntity getFirstEntityAfter(ShallowEntity candidate) {
412                List<ShallowEntity> childrenOfParent = candidate.getParent().getChildrenOfType(EShallowEntityType.STATEMENT);
413
414                int indexOfNextCandidate = childrenOfParent.indexOf(candidate) + 1;
415                if (indexOfNextCandidate < childrenOfParent.size()) {
416                        return childrenOfParent.get(indexOfNextCandidate);
417                }
418                return null;
419        }
420
421        /**
422         * Returns the entity which is directly before the given {@link ShallowEntity}.
423         *
424         * @param candidate
425         *            the first entity of the candidate
426         * @return The last entity which ends before the the given
427         *         {@link ShallowEntity}. If there is no such entity, <code>null</code>
428         *         will be returned.
429         */
430        public static ShallowEntity getLastEntityBefore(ShallowEntity candidate) {
431                List<ShallowEntity> childrenOfParent = candidate.getParent().getChildrenOfType(EShallowEntityType.STATEMENT);
432
433                int indexOfPreviousCandidate = childrenOfParent.indexOf(candidate) - 1;
434                if (indexOfPreviousCandidate >= 0) {
435                        return childrenOfParent.get(indexOfPreviousCandidate);
436                }
437                return null;
438        }
439
440        /**
441         * Returns the nesting area of a list of {@link ShallowEntity ShallowEntities}.
442         * The nesting area is the sum of the nesting depth of all {@link ShallowEntity
443         * ShallowEntities}.
444         *
445         * @param shallowEntities
446         *            the {@link ShallowEntity ShallowEntities}.
447         * @return The nesting area of the given entities.
448         */
449        public static int getNestingArea(List<ShallowEntity> shallowEntities) {
450                int currentDepth = 0;
451                int nestingArea = 0;
452                for (ShallowEntity shallowEntity : shallowEntities) {
453                        nestingArea += getNestingArea(shallowEntity, currentDepth);
454                }
455                return nestingArea;
456        }
457
458        /**
459         * Returns the nesting area of a {@link ShallowEntity}. The nesting area is the
460         * sum of the nesting depth of all {@link ShallowEntity ShallowEntities} .
461         *
462         * @param shallowEntity
463         *            the {@link ShallowEntity}.
464         * @return The nesting area of the given entities.
465         */
466        private static int getNestingArea(ShallowEntity shallowEntity, int currentDepth) {
467                int nestingArea = currentDepth;
468                for (ShallowEntity child : shallowEntity.getChildrenOfType(EShallowEntityType.STATEMENT)) {
469                        nestingArea += getNestingArea(child, currentDepth + 1);
470                }
471                return nestingArea;
472        }
473
474        /**
475         * Returns the maximal nesting depth of the given block.
476         *
477         * @param block
478         *            the {@link ShallowEntity ShallowEntities} that are taken into
479         *            account for the maximal nesting depth.
480         * @return The maximal nesting depth
481         */
482        public static int getNestingDepth(List<ShallowEntity> block) {
483                int maxNestingDepth = 0;
484                for (ShallowEntity shallowEntity : block) {
485                        int entityNestingDepth = getNestingDepth(shallowEntity);
486
487                        if (entityNestingDepth > maxNestingDepth) {
488                                maxNestingDepth = entityNestingDepth;
489                        }
490                }
491                return maxNestingDepth;
492        }
493
494        /**
495         * Returns the maximal nesting depth of the given entity.
496         *
497         * @param shallowEntity
498         *            the {@link ShallowEntity}.
499         * @return The maximal nesting depth
500         */
501        public static int getNestingDepth(ShallowEntity shallowEntity) {
502                int maxNestingDepth = 0;
503                for (ShallowEntity child : shallowEntity.getChildrenOfType(EShallowEntityType.STATEMENT)) {
504                        int childNestingDepth = getNestingDepth(child);
505
506                        if (maxNestingDepth <= childNestingDepth) {
507                                maxNestingDepth = childNestingDepth + 1;
508                        }
509                }
510                return maxNestingDepth;
511        }
512
513        /**
514         * Returns the number of blank or commented lines after the given
515         * {@link ShallowEntity}.
516         *
517         * @param candidate
518         *            if a refactoring candidate should be considered, take its last
519         *            {@link ShallowEntity}.
520         * @return The number of blank or commented lines
521         */
522        public static int getNumberOfBlankLinesOrCommentsAfter(ShallowEntity candidate) {
523                int lineDifference = 1;
524                ShallowEntity firstEntityAfter = getFirstEntityAfter(candidate);
525                if (firstEntityAfter == null) {
526                        if (candidate.getParent().getEndLine() - candidate.getEndLine() > 1) {
527                                lineDifference = candidate.getParent().getEndLine() - candidate.getEndLine();
528                        }
529                        return lineDifference - 1;
530                }
531                lineDifference = firstEntityAfter.getStartLine() - candidate.getEndLine();
532                return lineDifference - 1;
533        }
534
535        /**
536         * Returns the number of blank or commented lines before the given
537         * {@link ShallowEntity}.
538         *
539         * @param candidate
540         *            if a refactoring candidate should be considered, take its first
541         *            {@link ShallowEntity}.
542         * @return The number of blank or commented lines
543         */
544        public static int getNumberOfBlankLinesOrCommentsBefore(ShallowEntity candidate) {
545                int lineDifference = 1;
546                ShallowEntity lastEntityBefore = getLastEntityBefore(candidate);
547                if (lastEntityBefore == null) {
548                        if (candidate.getStartLine() - candidate.getParent().getStartLine() > 1) {
549                                lineDifference = candidate.getStartLine() - candidate.getParent().getStartLine();
550                        }
551                        return lineDifference - 1;
552                }
553                lineDifference = candidate.getStartLine() - lastEntityBefore.getEndLine();
554                return lineDifference - 1;
555        }
556
557        /**
558         * Returns a list of variable names (identifiers) that must be returned from the
559         * specified block if it is extracted into a new method
560         *
561         * @param block
562         *            {@link ShallowEntity ShallowEntities} that might be extracted
563         * @return List of identifiers
564         */
565        public static List<String> getReturnParameters(List<ShallowEntity> block) {
566                List<String> returnParameters = new ArrayList<>();
567                List<ShallowEntity> allEntities = ShallowEntityTraversalUtils.listEntitiesOfType(block,
568                                EShallowEntityType.STATEMENT);
569                for (ShallowEntity statement : allEntities) {
570                        if (statement.includedTokens().get(0).getType() == ETokenType.RETURN
571                                        // if the return statement is located in an anonymous class or lambda, it can be
572                                        // ignored because its not a return parameter of the block.
573                                        && !locatedInAnonymousClassOrLambda(statement)) {
574                                returnParameters.add(statement.getName());
575                        }
576                }
577                return returnParameters;
578        }
579
580        /**
581         * Recursive computation if the statement is located in an anonymous class or
582         * lambda.
583         *
584         * @param statement
585         *            {@link ShallowEntity} that represents the statement
586         * @return whether the statement is located in an anonymous class or lambda
587         */
588        private static boolean locatedInAnonymousClassOrLambda(ShallowEntity statement) {
589                ShallowEntity parent = statement.getParent();
590                if (parent == null) {
591                        // exit condition: the outer statement is reached, so the statement can't be in
592                        // an anonymous class
593                        return false;
594                } else if (parent.getSubtype().equals(SubTypeNames.ANONYMOUS_CLASS)
595                                || parent.getSubtype().equals(SubTypeNames.LAMBDA)) {
596                        return true;
597                }
598                return locatedInAnonymousClassOrLambda(parent);
599        }
600
601        /** Returns all statements in the given entity */
602        public static List<ShallowEntity> getStatements(ShallowEntity entity) {
603                List<ShallowEntity> list = new ArrayList<>();
604                list.add(entity);
605                return ShallowEntityTraversalUtils.listEntitiesOfType(list, EShallowEntityType.STATEMENT);
606        }
607
608        /**
609         * Obtain all classes (including anonymous classes) that contain directly or
610         * indirectly the method corresponding to the given line numbers.
611         *
612         * @param shallowEntities
613         *            the {@link ShallowEntity ShallowEntities} that represent the file
614         *            in which the finding was found
615         * @param startLine
616         *            the start line of the code area that must be covered by the
617         *            classes
618         * @param endLine
619         *            the end line of the code area that must be covered by the classes
620         * @return A list that contains at least one {@link ShallowEntity} with type
621         *         TYPE.
622         */
623        public static List<ShallowEntity> getClassEntities(List<ShallowEntity> shallowEntities, int startLine,
624                        int endLine) {
625
626                List<ShallowEntity> classEntities = ShallowEntityTraversalUtils.listEntitiesOfType(shallowEntities,
627                                EShallowEntityType.TYPE);
628
629                List<ShallowEntity> classEntitiesCoveringCodeArea = new ArrayList<>();
630                for (ShallowEntity classEntity : classEntities) {
631                        if (classEntity.getStartLine() <= startLine && classEntity.getEndLine() >= endLine) {
632                                classEntitiesCoveringCodeArea.add(classEntity);
633                        }
634                }
635                return classEntitiesCoveringCodeArea;
636        }
637
638        /**
639         * Obtain all methods.
640         *
641         * @param shallowEntities
642         *            the {@link ShallowEntity ShallowEntities} that represent the file
643         *            in which the finding was found
644         * @return all entities of type {@link EShallowEntityType#METHOD}
645         */
646        public static List<ShallowEntity> getMethods(List<ShallowEntity> shallowEntities) {
647                return ShallowEntityTraversalUtils.listEntitiesOfType(shallowEntities, EShallowEntityType.METHOD);
648        }
649
650        /**
651         * Get a method by its region. The region must match exactly.
652         *
653         * @param shallowEntities
654         *            the {@link ShallowEntity ShallowEntities} that represent the file
655         *            in which the finding was found
656         * @param region
657         *            of the method
658         */
659        public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities, OffsetBasedRegion region) {
660                Predicate<ShallowEntity> filter = (methodEntity) -> methodEntity.getStartOffset() == region.getStart()
661                                && methodEntity.getEndOffset() == region.getEnd();
662                return getMethod(shallowEntities, filter);
663        }
664
665        /**
666         * Get a method by its region. The region must match exactly.
667         *
668         * @param shallowEntities
669         *            the {@link ShallowEntity ShallowEntities} that represent the file
670         *            in which the finding was found
671         * @param region
672         *            of the method
673         */
674        public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities, LineBasedRegion region) {
675                Predicate<ShallowEntity> filter = (methodEntity) -> methodEntity.getStartLine() == region.getStart()
676                                && methodEntity.getEndLine() == region.getEnd();
677                return getMethod(shallowEntities, filter);
678        }
679
680        /**
681         * Get a method using a predicate.
682         *
683         * @param shallowEntities
684         *            the {@link ShallowEntity ShallowEntities} that represent the file
685         *            in which the finding was found
686         * @param filter
687         *            to filter a method
688         */
689        public static Optional<ShallowEntity> getMethod(List<ShallowEntity> shallowEntities,
690                        Predicate<ShallowEntity> filter) {
691                return getMethods(shallowEntities).stream().filter(filter).findFirst();
692        }
693
694        /**
695         * Extracts from a given token (that represents a file) all the attributes
696         * (variables) that are declared directly in a type (class) and not within a
697         * method etc.
698         *
699         * @param classEntities
700         *            the {@link ShallowEntity} of the class that contains the method
701         *            for which suggestions are to be generated
702         * @return A list containing all the names (identifiers) of the global variables
703         */
704        public static List<String> getClassAttributes(List<ShallowEntity> classEntities) {
705
706                List<String> globalVariables = new ArrayList<>();
707
708                for (ShallowEntity classEntity : classEntities) {
709
710                        List<ShallowEntity> attributes = classEntity.getChildrenOfType(EShallowEntityType.ATTRIBUTE);
711
712                        for (ShallowEntity attribute : attributes) {
713                                globalVariables.add(attribute.getName());
714                        }
715                }
716
717                return globalVariables;
718        }
719
720        /** Checks if the entity is a loop (for, foreach, while). */
721        public static boolean isLoop(ShallowEntity entity) {
722                return LOOP_SUBTYPES.contains(entity.getSubtype());
723        }
724
725        /**
726         * Checks if the given entity is part of a branching statement but not the first
727         * option of the branches. Those statements must not be separated from the first
728         * part of the branching statement when we consider refactoring suggestions.
729         *
730         * @return For entities of {@link SubTypeNames subtype} ELSE IF, ELSE, CATCH,
731         *         FINALLY, CASE, DEFAULT, USING, DO will return <code>true</code> but
732         *         for other subtypes (including IF, SWITCH, TRY) return
733         *         <code>false</code>.
734         */
735        public static boolean isSubpartOfBranchingStatement(ShallowEntity entity) {
736                return SUBPART_BRANCHING_STATEMENTS.contains(entity.getSubtype());
737        }
738
739        /**
740         * Checks whether the given method is a C++ constructor.
741         */
742        public static boolean isCppConstructor(ELanguage language, ShallowEntity entity) {
743                return language.equals(ELanguage.CPP) && entity.getType().equals(EShallowEntityType.METHOD)
744                                && SubTypeNames.CONSTRUCTOR.equals(entity.getSubtype());
745
746        }
747
748        /**
749         * @return whether the given entity is a lambda method.
750         */
751        public static boolean isLambdaMethod(ShallowEntity entity) {
752                return entity.getType() == EShallowEntityType.METHOD && entity.getSubtype() == SubTypeNames.LAMBDA;
753        }
754
755        /**
756         * Determines the language of the given entity or <code>null</code> if it cannot
757         * be determined
758         */
759        public static ELanguage getLanguage(ShallowEntity entity) {
760                UnmodifiableList<IToken> tokens = entity.includedTokens();
761                if (tokens.isEmpty()) {
762                        return null;
763                }
764                return CollectionUtils.getAny(tokens).getLanguage();
765        }
766}