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}