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.util;
018
019import static eu.cqse.check.framework.scanner.ETokenType.BASE;
020import static eu.cqse.check.framework.scanner.ETokenType.BOOL;
021import static eu.cqse.check.framework.scanner.ETokenType.BYTE;
022import static eu.cqse.check.framework.scanner.ETokenType.CHAR;
023import static eu.cqse.check.framework.scanner.ETokenType.CONST;
024import static eu.cqse.check.framework.scanner.ETokenType.DECIMAL;
025import static eu.cqse.check.framework.scanner.ETokenType.DOT;
026import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE;
027import static eu.cqse.check.framework.scanner.ETokenType.EQ;
028import static eu.cqse.check.framework.scanner.ETokenType.FLOAT;
029import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER;
030import static eu.cqse.check.framework.scanner.ETokenType.IN;
031import static eu.cqse.check.framework.scanner.ETokenType.INT;
032import static eu.cqse.check.framework.scanner.ETokenType.INTERNAL;
033import static eu.cqse.check.framework.scanner.ETokenType.LONG;
034import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
035import static eu.cqse.check.framework.scanner.ETokenType.NEW;
036import static eu.cqse.check.framework.scanner.ETokenType.OBJECT;
037import static eu.cqse.check.framework.scanner.ETokenType.OUT;
038import static eu.cqse.check.framework.scanner.ETokenType.PARAMS;
039import static eu.cqse.check.framework.scanner.ETokenType.PRIVATE;
040import static eu.cqse.check.framework.scanner.ETokenType.PROTECTED;
041import static eu.cqse.check.framework.scanner.ETokenType.PUBLIC;
042import static eu.cqse.check.framework.scanner.ETokenType.READONLY;
043import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
044import static eu.cqse.check.framework.scanner.ETokenType.SBYTE;
045import static eu.cqse.check.framework.scanner.ETokenType.SHORT;
046import static eu.cqse.check.framework.scanner.ETokenType.STATIC;
047import static eu.cqse.check.framework.scanner.ETokenType.STRING;
048import static eu.cqse.check.framework.scanner.ETokenType.UINT;
049import static eu.cqse.check.framework.scanner.ETokenType.ULONG;
050import static eu.cqse.check.framework.scanner.ETokenType.USHORT;
051import static eu.cqse.check.framework.scanner.ETokenType.VAR;
052import static eu.cqse.check.framework.scanner.ETokenType.VOID;
053import static eu.cqse.check.framework.scanner.ETokenType.VOLATILE;
054
055import java.util.ArrayList;
056import java.util.EnumSet;
057import java.util.List;
058import java.util.Set;
059
060import org.conqat.lib.commons.assertion.CCSMAssert;
061import org.conqat.lib.commons.collections.CollectionUtils;
062
063import eu.cqse.check.framework.scanner.ELanguage;
064import eu.cqse.check.framework.scanner.ETokenType;
065import eu.cqse.check.framework.scanner.IToken;
066import eu.cqse.check.framework.shallowparser.SubTypeNames;
067import eu.cqse.check.framework.shallowparser.TokenStreamTextUtils;
068import eu.cqse.check.framework.shallowparser.TokenStreamUtils;
069import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
070import eu.cqse.check.framework.shallowparser.framework.ShallowEntity;
071import eu.cqse.check.framework.shallowparser.languages.cs.CsShallowParser;
072import eu.cqse.check.framework.util.variable.CSVariableUseExtractor;
073
074/**
075 * Language feature parser for Cs.
076 */
077public class CsLanguageFeatureParser extends CLikeLanguageFeatureParserBase {
078
079        /** IN and OUT keyword token types. */
080        private static final EnumSet<ETokenType> IN_OUT_KEYWORDS = EnumSet.of(IN, OUT);
081
082        /** All token types that can be used to specify a type. */
083        public static final EnumSet<ETokenType> ADDITIONAL_TYPE_TOKENS = EnumSet.of(IDENTIFIER, VAR, STRING, OBJECT);
084
085        /** All token types that can be used to specify a primitive type. */
086        public static final EnumSet<ETokenType> PRIMITIVE_TYPE_TOKENS = EnumSet.of(BOOL, CHAR, BYTE, SBYTE, SHORT, INT,
087                        LONG, USHORT, UINT, ULONG, FLOAT, DOUBLE, DECIMAL, VOID);
088
089        /** All token types that are modifiers for variables or attributes. */
090        public static final EnumSet<ETokenType> VARIABLE_DECLARATION_MODIFIERS = EnumSet.of(PUBLIC, PRIVATE, INTERNAL,
091                        PROTECTED, CONST, READONLY, STATIC, VOLATILE, NEW);
092
093        /** Suffix for CS-EventHandlers. */
094        public static final String EVENT_ARGS_SUFFIX = "EventArgs";
095
096        /** Constructor. */
097        public CsLanguageFeatureParser() {
098                super(ELanguage.CS, CsShallowParser.VALID_IDENTIFIERS, PRIMITIVE_TYPE_TOKENS, ADDITIONAL_TYPE_TOKENS, DOT, ".",
099                                new CSVariableUseExtractor(DOT, CsShallowParser.VALID_IDENTIFIERS));
100        }
101
102        /** Names of classes that count as generic exceptions. */
103        private static final Set<String> GENERIC_EXCEPTION_NAMES = CollectionUtils.asHashSet("Exception",
104                        "ApplicationException", "SystemException");
105
106        /**
107         * Returns the imported namespace name from a given "using" entity. If the
108         * "using" clause is an aliasing clause, null is returned.
109         */
110        @Override
111        public String getImportName(ShallowEntity entity) {
112                CCSMAssert.isTrue(isImport(entity), "entity.getType() must be equal to EShallowEntityType.META and "
113                                + "entity.getSubtype() must be equal to SubTypeNames.USING");
114
115                List<IToken> tokens = entity.ownStartTokens();
116
117                if (TokenStreamUtils.containsAll(tokens, EQ)) {
118                        // if this using directive is aliasing a namespace (thus it contains
119                        // a "="), we don't extract the namespace
120                        return null;
121                }
122
123                int lastIdentifierIndex = TokenStreamUtils.lastTokenOfType(tokens, CsShallowParser.VALID_IDENTIFIERS);
124                if (lastIdentifierIndex == TokenStreamUtils.NOT_FOUND) {
125                        return null;
126                }
127
128                return TokenStreamTextUtils.concatTokenTexts(tokens.subList(1, lastIdentifierIndex + 1));
129        }
130
131        /** Returns whether the given entity is a using directive. */
132        @Override
133        public boolean isImport(ShallowEntity entity) {
134                return entity.getType().equals(EShallowEntityType.META) && entity.getSubtype().equals(SubTypeNames.USING);
135        }
136
137        /** Checks whether the given method is a CS-EventHandler. */
138        public boolean isEventHandler(ShallowEntity method) {
139                CCSMAssert.isTrue(method.getType() == EShallowEntityType.METHOD, "method.getType() must be \"METHOD\"");
140                if (!hasVoidReturnType(method)) {
141                        return false;
142                }
143
144                List<List<IToken>> parameterTokens = getSplitParameterTokens(method);
145                if (parameterTokens.size() != 2) {
146                        return false;
147                }
148
149                List<IToken> firstParameter = parameterTokens.get(0);
150                List<IToken> secondParameter = parameterTokens.get(1);
151
152                if (!TokenStreamUtils.hasTypes(firstParameter, ETokenType.OBJECT, ETokenType.IDENTIFIER)) {
153                        return false;
154                }
155
156                String secondType = getModifiersAndTypeFromTokens(secondParameter).getSecond();
157                return secondType.endsWith(EVENT_ARGS_SUFFIX);
158        }
159
160        /** Returns whether the given entity is partial. */
161        public boolean isPartial(ShallowEntity entity) {
162                return TokenStreamUtils.firstTokenOfType(entity.ownStartTokens(),
163                                ETokenType.PARTIAL) != TokenStreamUtils.NOT_FOUND;
164        }
165
166        /** Returns all parameter tokens of a base call in a constructor. */
167        public List<IToken> getBaseCallParameterTokens(ShallowEntity constructor) {
168                CCSMAssert.isTrue(
169                                constructor.getType() == EShallowEntityType.METHOD
170                                                && SubTypeNames.CONSTRUCTOR.equals(constructor.getSubtype()),
171                                "The given entity must be a constructor");
172                List<IToken> headTokens = constructor.ownStartTokens();
173                int baseIndex = TokenStreamUtils.firstTokenOfTypeSequence(headTokens, 0, BASE, LPAREN);
174                if (baseIndex < 0) {
175                        return CollectionUtils.emptyList();
176                }
177                return TokenStreamUtils.tokensBetweenWithNesting(headTokens, baseIndex, LPAREN, RPAREN);
178        }
179
180        @Override
181        public boolean hasVariableLengthArgumentList(List<IToken> tokens) {
182                return TokenStreamUtils.containsAll(tokens, PARAMS);
183        }
184
185        /**
186         * Filters IN and OUT tokens from generics.
187         * 
188         * {@inheritDoc}
189         */
190        @Override
191        protected List<IToken> filterGenericTokens(List<IToken> genericTokens) {
192                return CollectionUtils.filter(genericTokens, token -> !IN_OUT_KEYWORDS.contains(token.getType()));
193        }
194
195        /** Returns a list of all class names the given entity inherits from. */
196        public List<String> getParentNames(ShallowEntity type) {
197                CCSMAssert.isTrue(type.getType() == EShallowEntityType.TYPE, "type.getType() must be \"TYPE\"");
198                List<IToken> inheritanceTokens = TokenStreamUtils.tokensBetween(type.ownStartTokens(), ETokenType.COLON,
199                                ETokenType.LBRACE);
200                if (inheritanceTokens.isEmpty()) {
201                        return CollectionUtils.emptyList();
202                }
203
204                List<List<IToken>> splitInheritanceTokens = TokenStreamUtils.splitWithNesting(inheritanceTokens,
205                                ETokenType.COMMA, ETokenType.LT, ETokenType.GT);
206                return TokenStreamTextUtils.concatAllTokenTexts(splitInheritanceTokens);
207        }
208
209        /**
210         * Returns all variable names from a variable declaration within a for
211         * statement.
212         */
213        public List<IToken> getVariableNamesFromForLikeTokens(List<IToken> forLikeTokens, ETokenType endToken) {
214                List<IToken> variableDeclarationTokens = getVariableTokensFromForLikeTokens(forLikeTokens, endToken);
215                return getVariableDeclarationNamesFromTokens(variableDeclarationTokens);
216        }
217
218        /**
219         * Returns all variable names from the given variable declaration tokens. Names
220         * are only returned if they are newly introduced, that means, that e. g. the
221         * variable "t" from "using(t) {" will not be returned. This method does not
222         * support generic types at the moment.
223         */
224        public List<IToken> getVariableDeclarationNamesFromTokens(List<IToken> variableDeclarationTokens) {
225                List<IToken> variableNames = new ArrayList<>();
226
227                int offset = 0;
228                while (variableDeclarationTokens.size() > 0
229                                && VARIABLE_DECLARATION_MODIFIERS.contains(variableDeclarationTokens.get(offset).getType())) {
230                        offset += 1;
231                }
232
233                if (variableDeclarationTokens.size() >= 2 + offset) {
234                        IToken token1 = variableDeclarationTokens.get(offset);
235                        IToken token2 = variableDeclarationTokens.get(1 + offset);
236                        if (isVariableDeclaration(token1, token2)) {
237                                variableNames.add(token2);
238                                addAdditionalVariableNameTokens(variableDeclarationTokens, offset, variableNames);
239                        }
240
241                }
242
243                return variableNames;
244        }
245
246        /** Adds additional variable name tokens. */
247        private static void addAdditionalVariableNameTokens(List<IToken> variableDeclarationTokens, int offset,
248                        List<IToken> variableNames) {
249                int parenthesisNesting = 0;
250                for (int i = 2 + offset; i < variableDeclarationTokens.size(); ++i) {
251                        IToken token = variableDeclarationTokens.get(i);
252                        ETokenType tokenType = token.getType();
253                        if (tokenType == ETokenType.LPAREN) {
254                                parenthesisNesting += 1;
255                        } else if (tokenType == ETokenType.RPAREN) {
256                                parenthesisNesting -= 1;
257                        } else if (tokenType == ETokenType.IDENTIFIER && parenthesisNesting == 0
258                                        && variableDeclarationTokens.get(i - 1).getType() == ETokenType.COMMA) {
259                                variableNames.add(token);
260                        }
261                }
262        }
263
264        /** Returns whether the two given tokens indicate a variable declaration. */
265        private boolean isVariableDeclaration(IToken token1, IToken token2) {
266                return (typeTokens.contains(token1.getType()) && validIdentifiers.contains(token2.getType()));
267        }
268
269        /**
270         * Determines whether the given class name represents a generic exception.
271         */
272        public boolean isGenericExceptionClass(String className) {
273                return GENERIC_EXCEPTION_NAMES.contains(className);
274        }
275}