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 static eu.cqse.check.framework.scanner.ETokenType.INTERFACE;
020import static eu.cqse.check.framework.scanner.ETokenType.PRIVATE;
021import static eu.cqse.check.framework.scanner.ETokenType.PUBLIC;
022import static eu.cqse.check.framework.shallowparser.framework.EShallowEntityType.TYPE;
023
024import java.util.EnumSet;
025import java.util.List;
026import java.util.function.Predicate;
027import java.util.regex.Pattern;
028
029import org.conqat.lib.commons.collections.CollectionUtils;
030import org.conqat.lib.commons.collections.UnmodifiableList;
031import org.conqat.lib.commons.enums.EnumUtils;
032import org.conqat.lib.commons.string.StringUtils;
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 * Defines valid primitive expressions that can be used in the
045 * {@link EntitySelectionExpressionParser}. Each expression is defined by a
046 * public static method of same name. Methods found here must be parameterless
047 * or accept a single string parameter and must return an {@link Predicate} over
048 * {@link ShallowEntity}. To avoid clashes with Java keywords, an optional
049 * prefix "select" may be used.
050 * 
051 * Note that the comments for the public methods describe not exactly the
052 * function, but rather the returned predicate.
053 */
054public class EntitySelectionPredicates {
055
056        /** The visibility modifiers in C++. */
057        private static final EnumSet<ETokenType> CPP_VISIBILITY_MODIFIERS = EnumSet.of(ETokenType.PUBLIC, PRIVATE,
058                        ETokenType.PROTECTED);
059
060        /**
061         * Selects entities in files with the given language. If the given language name
062         * is invalid, no entities are matched.
063         */
064        public static Predicate<ShallowEntity> language(String languageName) {
065                ELanguage language = EnumUtils.valueOfIgnoreCase(ELanguage.class, languageName);
066                if (language == null) {
067                        return entity -> false;
068                }
069                return entity -> getLanguage(entity) == language;
070        }
071
072        /** Selects all modules/namespaces. */
073        public static Predicate<ShallowEntity> module() {
074                return typePredicate(EShallowEntityType.MODULE);
075        }
076
077        /** Selects all types/classes. */
078        public static Predicate<ShallowEntity> type() {
079                return typePredicate(EShallowEntityType.TYPE).and(entity -> !entity.getSubtype().equals("typedef"));
080        }
081
082        /** Selects all methods/functions. */
083        public static Predicate<ShallowEntity> method() {
084                return entity -> entity.getType() == EShallowEntityType.METHOD
085                                && (entity.getParent() == null || entity.getParent().getType() != EShallowEntityType.ATTRIBUTE);
086        }
087
088        /** Selects all shallow entities whose subtype is a declaration. */
089        public static Predicate<ShallowEntity> declaration() {
090                return entity -> {
091                        if (entity.getSubtype().contains(SubTypeNames.DECLARATION)) {
092                                return true;
093                        }
094
095                        // also consider inline methods in classes
096                        if (ELanguage.CPP.equals(getLanguage(entity))) {
097                                ShallowEntity parent = entity.getParent();
098                                return parent != null && parent.getType() == EShallowEntityType.TYPE;
099                        }
100                        return false;
101                };
102        }
103
104        /** Selects all shallow entities that are internal (C# keyword). */
105        public static Predicate<ShallowEntity> internal() {
106                return entity -> {
107                        if (TokenStreamUtils.containsAny(entity.ownStartTokens(), ETokenType.INTERNAL)) {
108                                return true;
109                        }
110                        ShallowEntity parent = entity.getParent();
111                        return parent != null && TokenStreamUtils.containsAny(parent.ownStartTokens(), ETokenType.INTERNAL);
112                };
113        }
114
115        /** Selects all attributes. */
116        public static Predicate<ShallowEntity> attribute() {
117                return typePredicate(EShallowEntityType.ATTRIBUTE);
118        }
119
120        /**
121         * Selects all properties, defined attributes that have only method children.
122         * More specific: ATTRIBUTE entities that have children and each child is a
123         * METHOD entity. E.g., C# properties.
124         */
125        public static Predicate<ShallowEntity> property() {
126                return entity -> {
127                        if (entity.getType() != EShallowEntityType.ATTRIBUTE) {
128                                return false;
129                        }
130
131                        for (ShallowEntity child : entity.getChildren()) {
132                                if (child.getType() != EShallowEntityType.METHOD) {
133                                        return false;
134                                }
135                        }
136                        return !entity.getChildren().isEmpty();
137                };
138        }
139
140        /** Selects all statements. */
141        public static Predicate<ShallowEntity> statement() {
142                return typePredicate(EShallowEntityType.STATEMENT);
143        }
144
145        /** Selects all meta information (defines, annotations, etc.). */
146        public static Predicate<ShallowEntity> meta() {
147                return typePredicate(EShallowEntityType.META);
148        }
149
150        /** Creates a predicate for checking the type of an entity. */
151        private static Predicate<ShallowEntity> typePredicate(EShallowEntityType type) {
152                return entity -> entity.getType() == type;
153        }
154
155        /**
156         * Matches all public entities as well as methods inside interfaces, which are
157         * considered public in most languages.
158         */
159        public static Predicate<ShallowEntity> selectPublic() {
160                return (modifierPredicate(PUBLIC).or(EntitySelectionPredicates::isInterfaceMethod)
161                                .or(EntitySelectionPredicates::isJavaScriptInterface))
162                                                .and(EntitySelectionPredicates::isNotTypeScriptConstructor);
163        }
164
165        /**
166         * Returns <code>true</code> if the given entity is is a JavaScript interface.
167         * JavaScript has no modifiers, so all interfaces are public.
168         */
169        private static boolean isJavaScriptInterface(ShallowEntity entity) {
170                return getLanguage(entity) == ELanguage.JAVASCRIPT && entity.getType() == EShallowEntityType.TYPE
171                                && modifierPredicate(INTERFACE).test(entity);
172        }
173
174        /**
175         * Returns <code>true</code> if the given entity is a method in an interface.
176         */
177        private static boolean isInterfaceMethod(ShallowEntity entity) {
178                ShallowEntity parent = entity.getParent();
179                return entity.getType() == EShallowEntityType.METHOD && parent != null && parent.getType() == TYPE
180                                && modifierPredicate(INTERFACE).test(parent);
181        }
182
183        public static Predicate<ShallowEntity> isAbstract() {
184                return modifierPredicate(ETokenType.ABSTRACT).or(EntitySelectionPredicates::isInterfaceMethod);
185        }
186
187        /** Matches all protected entities. */
188        public static Predicate<ShallowEntity> selectProtected() {
189                return modifierPredicate(ETokenType.PROTECTED).and(EntitySelectionPredicates::isNotTypeScriptConstructor);
190        }
191
192        /** Matches all entities marked with override. */
193        public static Predicate<ShallowEntity> selectOverride() {
194                return modifierPredicate(ETokenType.OVERRIDE);
195        }
196
197        /**
198         * Matches all private entities (except the TypeScript constructor parameters).
199         */
200        public static Predicate<ShallowEntity> selectPrivate() {
201                return modifierPredicate(PRIVATE).and(EntitySelectionPredicates::isNotTypeScriptConstructor);
202        }
203
204        /**
205         * Checks that the entity is not a TypeScript constructor.
206         */
207        private static boolean isNotTypeScriptConstructor(ShallowEntity entity) {
208                if (getLanguage(entity) == ELanguage.JAVASCRIPT) {
209                        return !SubTypeNames.CONSTRUCTOR.equals(entity.getSubtype());
210                }
211                return true;
212        }
213
214        /** Matches all entities with default visibility. */
215        public static Predicate<ShallowEntity> selectDefault() {
216                return element -> {
217                        if (getLanguage(element) == ELanguage.JAVASCRIPT) {
218                                return isTypeScriptDefault(element);
219                        }
220                        return hasNoModifiers(element);
221                };
222        }
223
224        /**
225         * Checks that the given entity has no modifiers. The method checks for the
226         * following modifiers: PUBLIC, PRIVATE and PROTECTED.
227         */
228        private static boolean hasNoModifiers(ShallowEntity element) {
229                List<IToken> tokens = element.ownStartTokens();
230                return !TokenStreamUtils.containsAny(tokens, 0, tokens.size(), ETokenType.PUBLIC, ETokenType.PROTECTED,
231                                PRIVATE);
232        }
233
234        /**
235         * Returns <code>true</code> if the given entity is a TypeScript attribute or
236         * method that could be selected as 'DEFAULT' and has no explicit modifier (i.e
237         * default visibility). These include:
238         * <ol>
239         * <li>Types</li>
240         * <li>Constructors</li>
241         * <li>All methods except lambda methods (since they will already be covered by
242         * the check on their parent attributes)</li>
243         * <li>Attributes</li>
244         * </ol>
245         */
246        private static boolean isTypeScriptDefault(ShallowEntity entity) {
247                if (getLanguage(entity) != ELanguage.JAVASCRIPT) {
248                        return false;
249                }
250
251                // Special handling of constructors needed to support public attributes
252                // / members, e.g.
253                // constructor(public pos: Vector, lookAt: Vector) { ... }
254                boolean isConstructor = entity.getType() == EShallowEntityType.METHOD
255                                && SubTypeNames.CONSTRUCTOR.equals(entity.getSubtype());
256                if (isConstructor) {
257                        UnmodifiableList<IToken> tokens = entity.ownStartTokens();
258                        if (!tokens.isEmpty() && tokens.get(0).getText().equals(SubTypeNames.CONSTRUCTOR)) {
259                                return true;
260                        }
261                }
262
263                if (!hasNoModifiers(entity)) {
264                        return false;
265                }
266
267                // ignore lambdas
268                if (entity.getType() == EShallowEntityType.METHOD && SubTypeNames.LAMBDA.equals(entity.getSubtype())) {
269                        return false;
270                }
271
272                return entity.getType() == EShallowEntityType.TYPE || entity.getType() == EShallowEntityType.METHOD
273                                || entity.getType() == EShallowEntityType.ATTRIBUTE;
274        }
275
276        /** Matches all final entities. */
277        public static Predicate<ShallowEntity> selectFinal() {
278                return modifierPredicate(ETokenType.FINAL);
279        }
280
281        /** Matches all static entities. */
282        public static Predicate<ShallowEntity> selectStatic() {
283                return modifierPredicate(ETokenType.STATIC);
284        }
285
286        /** Matches all export entities. */
287        public static Predicate<ShallowEntity> export() {
288                return modifierPredicate(ETokenType.EXPORT);
289        }
290
291        /** Returns a predicate that checks for existence of the given modifier. */
292        private static Predicate<ShallowEntity> modifierPredicate(final ETokenType modifier) {
293                return element -> {
294                        if (TokenStreamUtils.firstTokenOfType(element.ownStartTokens(), modifier) != TokenStreamUtils.NOT_FOUND) {
295                                return true;
296                        }
297
298                        if (ELanguage.CPP == getLanguage(element) && CPP_VISIBILITY_MODIFIERS.contains(modifier)) {
299                                return hasCppVisibilityModifer(element, modifier);
300                        }
301                        return false;
302                };
303        }
304
305        /**
306         * Determines the language of the given entity or <code>null</code> if it cannot
307         * be determined
308         */
309        private static ELanguage getLanguage(ShallowEntity entity) {
310                return ShallowParsingUtils.getLanguage(entity);
311        }
312
313        /**
314         * Determines the origin, i.e. the uniformPath, of the given entity or
315         * <code>null</code> if it cannot be determined.
316         */
317        private static String getUniformPath(ShallowEntity entity) {
318                UnmodifiableList<IToken> tokens = entity.includedTokens();
319                if (tokens.isEmpty()) {
320                        return null;
321                }
322                return CollectionUtils.getAny(tokens).getOriginId();
323        }
324
325        /**
326         * Checks whether the given shallow entity is affected by the specified C++
327         * visibility modifier by backwards searching the sibling shallow entities. The
328         * given modifier must be one of public, protected, or private.
329         */
330        private static boolean hasCppVisibilityModifer(ShallowEntity entity, ETokenType modifier) {
331                ShallowEntity parent = entity.getParent();
332                if (parent == null) {
333                        return false;
334                }
335
336                UnmodifiableList<ShallowEntity> siblings = parent.getChildren();
337                for (int i = siblings.indexOf(entity) - 1; i >= 0; i--) {
338                        ShallowEntity sibling = siblings.get(i);
339
340                        if (isCppVisibilityModifier(sibling)) {
341                                return TokenStreamUtils.containsAny(sibling.ownStartTokens(), modifier);
342                        }
343                }
344
345                // no explicit modifier found -> assume default
346                if (parent.getType() == EShallowEntityType.TYPE) {
347                        if (SubTypeNames.STRUCT.equals(parent.getSubtype())) {
348                                return ETokenType.PUBLIC.equals(modifier);
349                        }
350                        return PRIVATE.equals(modifier);
351                }
352
353                return false;
354        }
355
356        /** Returns whether the given entity is a C++ visibility modifier. */
357        private static boolean isCppVisibilityModifier(ShallowEntity entity) {
358                return entity.getType() == EShallowEntityType.META
359                                && TokenStreamUtils.containsAny(entity.ownStartTokens(), CPP_VISIBILITY_MODIFIERS);
360        }
361
362        /** Matches all primitive entities (i.e. those without children). */
363        public static Predicate<ShallowEntity> primitive() {
364                return entity -> entity.getChildren().isEmpty();
365        }
366
367        /** Matches entities by their subtype. */
368        public static Predicate<ShallowEntity> subtype(final String subtype) {
369                return entity -> subtype.equals(entity.getSubtype());
370        }
371
372        /**
373         * Matches entities by their parent type's subtype (e.g. to select all members
374         * of interfaces).
375         */
376        public static Predicate<ShallowEntity> typeSubtype(String subtype) {
377                return entity -> {
378                        ShallowEntity parent = entity.getParent();
379                        return parent != null && type().test(parent) && parent.getSubtype().equals(subtype);
380                };
381        }
382
383        /**
384         * Matches entities whose parent is an exported type.
385         */
386        public static Predicate<ShallowEntity> typeExported() {
387                return entity -> {
388                        ShallowEntity parent = entity.getParent();
389                        return parent != null && type().test(parent) && export().test(parent);
390                };
391        }
392
393        /** Matches entities by their name. */
394        public static Predicate<ShallowEntity> name(String name) {
395                return entity -> name.equals(entity.getName());
396        }
397
398        /** Matches entities by their name using regular expression. */
399        public static Predicate<ShallowEntity> nameRegex(String regex) {
400                Pattern pattern = Pattern.compile(regex);
401                // name is null for e.g. lambdas and anonymous types
402                return entity -> entity.getName() != null && pattern.matcher(entity.getName()).matches();
403        }
404
405        /** Matches entities by their file name */
406        public static Predicate<ShallowEntity> fileRegex(String regex) {
407                Pattern pattern = Pattern.compile(regex);
408                return entity -> {
409                        String uniformPath = getUniformPath(entity);
410                        if (uniformPath == null) {
411                                return false;
412                        }
413                        return pattern.matcher(uniformPath).matches();
414                };
415        }
416
417        /**
418         * Matches all entities that are annotated with an annotation of given name
419         * (excluding the '@' sign).
420         */
421        public static Predicate<ShallowEntity> annotated(String annotationName) {
422                final String normalizedAnnotationName = StringUtils.stripPrefix(annotationName, "@");
423                return entity -> {
424                        ShallowEntity parent = entity.getParent();
425                        if (parent == null) {
426                                return false;
427                        }
428
429                        // Special handling needed for JavaScript, since annotations are
430                        // detected as decorators.
431                        if (getLanguage(entity) == ELanguage.JAVASCRIPT) {
432                                return hasAnnotation(annotationName, entity, parent.getChildren(), SubTypeNames.DECORATOR);
433                        }
434
435                        return hasAnnotation(normalizedAnnotationName, entity, parent.getChildren(), SubTypeNames.ANNOTATION);
436                };
437        }
438
439        /**
440         * Checks if the entity is annotated with the specified annotation. Traverses
441         * the siblings before the entity in the children list, as more than one
442         * annotation might exist.
443         */
444        private static boolean hasAnnotation(final String annotationName, ShallowEntity entity,
445                        List<ShallowEntity> children, String subTypeNameToCheck) {
446
447                int index = children.indexOf(entity);
448                index -= 1;
449                while (index >= 0 && children.get(index).getType() == EShallowEntityType.META) {
450                        ShallowEntity metaEntity = children.get(index);
451                        if (subTypeNameToCheck.equals(metaEntity.getSubtype())
452                                        && annotationName.equalsIgnoreCase(metaEntity.getName())) {
453                                return true;
454                        }
455                        index -= 1;
456                }
457                return false;
458        }
459
460        /**
461         * Matches simple getters, i.e. methods starting with "get" or "is" and with at
462         * most one contained statement.
463         */
464        public static Predicate<ShallowEntity> simpleGetter() {
465                return (simpleMethod("get").or(simpleMethod("is"))).and(isAbstract().negate());
466        }
467
468        /**
469         * Matches simple setters, i.e. methods starting with "set" and with at most one
470         * contained statement.
471         */
472        public static Predicate<ShallowEntity> simpleSetter() {
473                return simpleMethod("set").and(isAbstract().negate());
474        }
475
476        /**
477         * Matches simple setters, i.e. methods starting with "set" (case insensitive)
478         * and with at most one contained statement.
479         */
480        public static Predicate<ShallowEntity> simpleSetterCaseInsensitive() {
481                return simpleMethodCaseInsensitive("set").and(isAbstract().negate());
482        }
483
484        /**
485         * Matches simple getters, i.e. methods starting with "get" or "is" (case
486         * insensitive) and with at most one contained statement.
487         */
488        public static Predicate<ShallowEntity> simpleGetterCaseInsensitive() {
489                return (simpleMethodCaseInsensitive("get").or(simpleMethodCaseInsensitive("is"))).and(isAbstract().negate());
490        }
491
492        /**
493         * Matches getter in CSharp generated by the automatic property generation
494         * feature. These are recognized by the entity subtype '(empty) get'
495         */
496        public static Predicate<ShallowEntity> simpleCSharpGetter() {
497                return entity -> {
498                        boolean isSimpleMethod = entity.getType() == EShallowEntityType.METHOD && getStatementCount(entity) <= 1;
499                        return isSimpleMethod && StringUtils.equalsOneOf(entity.getSubtype().toLowerCase(), SubTypeNames.GET,
500                                        SubTypeNames.EMPTY_GET);
501                };
502        }
503
504        /**
505         * Matches setter in CSharp generated by the automatic property generation
506         * feature. These are recognized by the entity subtype '(empty) set'
507         */
508        public static Predicate<ShallowEntity> simpleCSharpSetter() {
509                return entity -> {
510                        boolean isSimpleMethod = entity.getType() == EShallowEntityType.METHOD && getStatementCount(entity) <= 1;
511                        return isSimpleMethod && StringUtils.equalsOneOf(entity.getSubtype().toLowerCase(), SubTypeNames.SET,
512                                        SubTypeNames.EMPTY_SET);
513                };
514        }
515
516        /**
517         * Matches simple methods with the given prefix. A method is simple, if it
518         * contains as most one statement.
519         */
520        public static Predicate<ShallowEntity> simpleMethod(String namePrefix) {
521                return entity -> {
522                        boolean isSimpleMethod = entity.getType() == EShallowEntityType.METHOD && getStatementCount(entity) <= 1;
523                        return isSimpleMethod && (entity.getName() != null) && entity.getName().startsWith(namePrefix);
524                };
525        }
526
527        /**
528         * Matches simple methods with the given prefix (case insensitive). A method is
529         * simple, if it contains as most one statement.
530         */
531        public static Predicate<ShallowEntity> simpleMethodCaseInsensitive(String namePrefix) {
532                return entity -> {
533                        boolean isSimpleMethod = entity.getType() == EShallowEntityType.METHOD && getStatementCount(entity) <= 1;
534                        return isSimpleMethod && (entity.getName() != null)
535                                        && entity.getName().toLowerCase().startsWith(namePrefix.toLowerCase());
536                };
537        }
538
539        /**
540         * Returns the number of statements contained as children of the entity.
541         */
542        private static int getStatementCount(ShallowEntity entity) {
543                return ShallowEntityTraversalUtils.listEntitiesOfType(entity.getChildren(), EShallowEntityType.STATEMENT)
544                                .size();
545        }
546}