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.util;
018
019import java.util.ArrayList;
020import java.util.EnumSet;
021import java.util.List;
022
023import eu.cqse.check.framework.scanner.ELanguage;
024import eu.cqse.check.framework.scanner.ETokenType;
025import eu.cqse.check.framework.scanner.IToken;
026import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
027import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
028import eu.cqse.check.framework.shallowparser.util.ShallowParsingUtils;
029
030/**
031 * Concerned with checks for ternary operator for c-like languages and Python
032 */
033public class TernaryOperatorCheckUtil {
034
035        /**
036         * Returns a list of the index occurrences of ternary operators. For c-like
037         * languages, its a list of indices for the '?'. For Python, its the 'if' word.
038         * 
039         * @param entity
040         *            A statement which may contains ternary operators.
041         */
042        public static List<Integer> findOccurrenes(ShallowEntity entity) {
043                List<Integer> finalIndices = new ArrayList<>();
044
045                List<IToken> tokens = entity.ownStartTokens();
046                if (TokenStreamUtils.containsAll(tokens, ETokenType.QUESTION, ETokenType.COLON)) {
047                        List<Integer> questionMarkIndices = TokenStreamUtils.findAll(tokens, EnumSet.of(ETokenType.QUESTION));
048                        for (int i : questionMarkIndices) {
049                                checkSurroundingOfQuestionMark(finalIndices, tokens, i);
050                        }
051                } else if (ShallowParsingUtils.getLanguage(entity) == ELanguage.PYTHON
052                                && TokenStreamUtils.containsAll(tokens, ETokenType.IF, ETokenType.ELSE)) {
053                        List<Integer> elseIndices = TokenStreamUtils.findAll(tokens, EnumSet.of(ETokenType.ELSE));
054                        for (int i : elseIndices) {
055                                checkElseSuccessorReportIfIndex(finalIndices, tokens, i);
056                        }
057                }
058
059                return finalIndices;
060        }
061
062        /**
063         * Given the index of an 'else', check if an 'else' keyword is preceded by a
064         * colon and report the index of its corresponding 'if'.
065         * 
066         * @param indices
067         *            List of final set of 'if' indices
068         * @param tokens
069         *            List of {@link IToken}s
070         * @param indexElse
071         *            The index of one 'else' token.
072         */
073        private static void checkElseSuccessorReportIfIndex(List<Integer> indices, List<IToken> tokens, int indexElse) {
074                int successorIndex = indexElse + 1;
075                if (successorIndex >= tokens.size()) {
076                        return;
077                }
078
079                if (tokens.get(successorIndex).getType() == ETokenType.COLON) {
080                        return;
081                }
082
083                int correspondingIfIndex = TokenStreamUtils.findMatchingOpeningToken(tokens, indexElse - 1, ETokenType.IF,
084                                ETokenType.ELSE);
085                if (correspondingIfIndex > TokenStreamUtils.NOT_FOUND) {
086                        indices.add(indexElse);
087                }
088        }
089
090        /**
091         * Checks the surrounding of the question mark to exclude all cases which are
092         * not ternary operators.
093         */
094        private static void checkSurroundingOfQuestionMark(List<Integer> indices, List<IToken> tokens,
095                        int indexQuestionMark) {
096                if (indexQuestionMark < 1) {
097                        return;
098                }
099
100                // Avoid Java generic-type arguments.
101                if (isJavaGenericTypeArgument(tokens, indexQuestionMark)) {
102                        return;
103                }
104
105                // What precedes or succeeds the ? character now could be (, [ or an
106                // identifier like in a C# Null conditional
107                // https://msdn.microsoft.com/en-us/library/dn986595(v=vs.140).aspx
108                if (TokenStreamUtils.findMatchingClosingToken(tokens, indexQuestionMark + 1, ETokenType.QUESTION,
109                                ETokenType.COLON) > TokenStreamUtils.NOT_FOUND) {
110                        indices.add(indexQuestionMark);
111                }
112        }
113
114        /**
115         * Checks if question mark is generic type argument
116         */
117        private static boolean isJavaGenericTypeArgument(List<IToken> tokens, int indexQuestionMark) {
118                if (indexQuestionMark < 1) {
119                        return false;
120                }
121
122                ETokenType predecessorTokenType = tokens.get(indexQuestionMark - 1).getType();
123                if (predecessorTokenType == ETokenType.LT) {
124                        return true;
125                }
126
127                if (indexQuestionMark + 1 >= tokens.size()) {
128                        return false;
129                }
130
131                ETokenType successorTokenType = tokens.get(indexQuestionMark + 1).getType();
132                return predecessorTokenType == ETokenType.COMMA
133                                && (successorTokenType == ETokenType.GT || successorTokenType == ETokenType.COMMA
134                                                || successorTokenType == ETokenType.EXTENDS || successorTokenType == ETokenType.SUPER);
135        }
136}