001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.coding; 021 022import java.util.ArrayDeque; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Deque; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.LinkedList; 029import java.util.Map; 030import java.util.Queue; 031import java.util.Set; 032import java.util.stream.Collectors; 033 034import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 035import com.puppycrawl.tools.checkstyle.api.DetailAST; 036import com.puppycrawl.tools.checkstyle.api.TokenTypes; 037import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 038import com.puppycrawl.tools.checkstyle.utils.ScopeUtils; 039import com.puppycrawl.tools.checkstyle.utils.TokenUtils; 040 041/** 042 * <p>Checks that code doesn't rely on the "this" default. 043 * That is references to instance variables and methods of the present 044 * object are explicitly of the form "this.varName" or 045 * "this.methodName(args)". 046 * </p> 047 * Check has the following options: 048 * <p><b>checkFields</b> - whether to check references to fields. Default value is <b>true</b>.</p> 049 * <p><b>checkMethods</b> - whether to check references to methods. 050 * Default value is <b>true</b>.</p> 051 * <p><b>validateOnlyOverlapping</b> - whether to check only overlapping by variables or 052 * arguments. Default value is <b>true</b>.</p> 053 * 054 * <p>Warning: the Check is very controversial if 'validateOnlyOverlapping' option is set to 'false' 055 * and not that actual nowadays.</p> 056 * 057 * <p>Examples of use: 058 * <pre> 059 * <module name="RequireThis"/> 060 * </pre> 061 * An example of how to configure to check {@code this} qualifier for 062 * methods only: 063 * <pre> 064 * <module name="RequireThis"> 065 * <property name="checkFields" value="false"/> 066 * <property name="checkMethods" value="true"/> 067 * </module> 068 * </pre> 069 * 070 * <p>Rationale:</p> 071 * <ol> 072 * <li> 073 * The same notation/habit for C++ and Java (C++ have global methods, so having 074 * "this." do make sense in it to distinguish call of method of class 075 * instead of global). 076 * </li> 077 * <li> 078 * Non-IDE development (ease of refactoring, some clearness to distinguish 079 * static and non-static methods). 080 * </li> 081 * </ol> 082 * 083 * <p>Limitations: Nothing is currently done about static variables 084 * or catch-blocks. Static methods invoked on a class name seem to be OK; 085 * both the class name and the method name have a DOT parent. 086 * Non-static methods invoked on either this or a variable name seem to be 087 * OK, likewise.</p> 088 * 089 * @author Stephen Bloch 090 * @author o_sukhodolsky 091 * @author Andrei Selkin 092 */ 093public class RequireThisCheck extends AbstractCheck { 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_METHOD = "require.this.method"; 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_VARIABLE = "require.this.variable"; 105 106 /** Set of all declaration tokens. */ 107 private static final Set<Integer> DECLARATION_TOKENS = Collections.unmodifiableSet( 108 Arrays.stream(new Integer[] { 109 TokenTypes.VARIABLE_DEF, 110 TokenTypes.CTOR_DEF, 111 TokenTypes.METHOD_DEF, 112 TokenTypes.CLASS_DEF, 113 TokenTypes.ENUM_DEF, 114 TokenTypes.INTERFACE_DEF, 115 TokenTypes.PARAMETER_DEF, 116 TokenTypes.TYPE_ARGUMENT, 117 }).collect(Collectors.toSet())); 118 /** Set of all assign tokens. */ 119 private static final Set<Integer> ASSIGN_TOKENS = Collections.unmodifiableSet( 120 Arrays.stream(new Integer[] { 121 TokenTypes.ASSIGN, 122 TokenTypes.PLUS_ASSIGN, 123 TokenTypes.STAR_ASSIGN, 124 TokenTypes.DIV_ASSIGN, 125 TokenTypes.MOD_ASSIGN, 126 TokenTypes.SR_ASSIGN, 127 TokenTypes.BSR_ASSIGN, 128 TokenTypes.SL_ASSIGN, 129 TokenTypes.BAND_ASSIGN, 130 TokenTypes.BXOR_ASSIGN, 131 }).collect(Collectors.toSet())); 132 /** Set of all compound assign tokens. */ 133 private static final Set<Integer> COMPOUND_ASSIGN_TOKENS = Collections.unmodifiableSet( 134 Arrays.stream(new Integer[] { 135 TokenTypes.PLUS_ASSIGN, 136 TokenTypes.STAR_ASSIGN, 137 TokenTypes.DIV_ASSIGN, 138 TokenTypes.MOD_ASSIGN, 139 TokenTypes.SR_ASSIGN, 140 TokenTypes.BSR_ASSIGN, 141 TokenTypes.SL_ASSIGN, 142 TokenTypes.BAND_ASSIGN, 143 TokenTypes.BXOR_ASSIGN, 144 }).collect(Collectors.toSet())); 145 146 /** Tree of all the parsed frames. */ 147 private Map<DetailAST, AbstractFrame> frames; 148 149 /** Frame for the currently processed AST. */ 150 private AbstractFrame current; 151 152 /** Whether we should check fields usage. */ 153 private boolean checkFields = true; 154 /** Whether we should check methods usage. */ 155 private boolean checkMethods = true; 156 /** Whether we should check only overlapping by variables or arguments. */ 157 private boolean validateOnlyOverlapping = true; 158 159 /** 160 * Setter for checkFields property. 161 * @param checkFields should we check fields usage or not. 162 */ 163 public void setCheckFields(boolean checkFields) { 164 this.checkFields = checkFields; 165 } 166 167 /** 168 * Setter for checkMethods property. 169 * @param checkMethods should we check methods usage or not. 170 */ 171 public void setCheckMethods(boolean checkMethods) { 172 this.checkMethods = checkMethods; 173 } 174 175 /** 176 * Setter for validateOnlyOverlapping property. 177 * @param validateOnlyOverlapping should we check only overlapping by variables or arguments. 178 */ 179 public void setValidateOnlyOverlapping(boolean validateOnlyOverlapping) { 180 this.validateOnlyOverlapping = validateOnlyOverlapping; 181 } 182 183 @Override 184 public int[] getDefaultTokens() { 185 return getAcceptableTokens(); 186 } 187 188 @Override 189 public int[] getRequiredTokens() { 190 return getAcceptableTokens(); 191 } 192 193 @Override 194 public int[] getAcceptableTokens() { 195 return new int[] { 196 TokenTypes.CLASS_DEF, 197 TokenTypes.INTERFACE_DEF, 198 TokenTypes.ENUM_DEF, 199 TokenTypes.CTOR_DEF, 200 TokenTypes.METHOD_DEF, 201 TokenTypes.SLIST, 202 TokenTypes.IDENT, 203 }; 204 } 205 206 @Override 207 public void beginTree(DetailAST rootAST) { 208 frames = new HashMap<>(); 209 current = null; 210 211 final Deque<AbstractFrame> frameStack = new LinkedList<>(); 212 DetailAST curNode = rootAST; 213 while (curNode != null) { 214 collectDeclarations(frameStack, curNode); 215 DetailAST toVisit = curNode.getFirstChild(); 216 while (curNode != null && toVisit == null) { 217 endCollectingDeclarations(frameStack, curNode); 218 toVisit = curNode.getNextSibling(); 219 if (toVisit == null) { 220 curNode = curNode.getParent(); 221 } 222 } 223 curNode = toVisit; 224 } 225 } 226 227 @Override 228 public void visitToken(DetailAST ast) { 229 switch (ast.getType()) { 230 case TokenTypes.IDENT : 231 processIdent(ast); 232 break; 233 case TokenTypes.CLASS_DEF : 234 case TokenTypes.INTERFACE_DEF : 235 case TokenTypes.ENUM_DEF : 236 case TokenTypes.ANNOTATION_DEF : 237 case TokenTypes.SLIST : 238 case TokenTypes.METHOD_DEF : 239 case TokenTypes.CTOR_DEF : 240 current = frames.get(ast); 241 break; 242 default : 243 // do nothing 244 } 245 } 246 247 /** 248 * Checks if a given IDENT is method call or field name which 249 * requires explicit {@code this} qualifier. 250 * @param ast IDENT to check. 251 */ 252 private void processIdent(DetailAST ast) { 253 final int parentType = ast.getParent().getType(); 254 switch (parentType) { 255 case TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR: 256 case TokenTypes.ANNOTATION: 257 case TokenTypes.ANNOTATION_FIELD_DEF: 258 // no need to check annotations content 259 break; 260 case TokenTypes.METHOD_CALL: 261 if (checkMethods) { 262 final AbstractFrame frame = getMethodWithoutThis(ast); 263 if (frame != null) { 264 logViolation(MSG_METHOD, ast, frame); 265 } 266 } 267 break; 268 default: 269 if (checkFields) { 270 final AbstractFrame frame = getFieldWithoutThis(ast, parentType); 271 if (frame != null) { 272 logViolation(MSG_VARIABLE, ast, frame); 273 } 274 } 275 break; 276 } 277 } 278 279 /** 280 * Helper method to log a LocalizedMessage. 281 * @param ast a node to get line id column numbers associated with the message. 282 * @param msgKey key to locale message format. 283 * @param frame the class frame where the violation is found. 284 */ 285 private void logViolation(String msgKey, DetailAST ast, AbstractFrame frame) { 286 if (frame.getFrameName().equals(getNearestClassFrameName())) { 287 log(ast, msgKey, ast.getText(), ""); 288 } 289 else if (!(frame instanceof AnonymousClassFrame)) { 290 log(ast, msgKey, ast.getText(), frame.getFrameName() + '.'); 291 } 292 } 293 294 /** 295 * Returns the frame where the field is declared, if the given field is used without 296 * 'this', and null otherwise. 297 * @param ast field definition ast token. 298 * @param parentType type of the parent. 299 * @return the frame where the field is declared, if the given field is used without 300 * 'this' and null otherwise. 301 */ 302 private AbstractFrame getFieldWithoutThis(DetailAST ast, int parentType) { 303 final boolean importOrPackage = ScopeUtils.getSurroundingScope(ast) == null; 304 final boolean methodNameInMethodCall = parentType == TokenTypes.DOT 305 && ast.getPreviousSibling() != null; 306 final boolean typeName = parentType == TokenTypes.TYPE 307 || parentType == TokenTypes.LITERAL_NEW; 308 AbstractFrame frame = null; 309 310 if (!importOrPackage 311 && !methodNameInMethodCall 312 && !typeName 313 && !isDeclarationToken(parentType) 314 && !isLambdaParameter(ast)) { 315 final AbstractFrame fieldFrame = findClassFrame(ast, false); 316 317 if (fieldFrame != null && ((ClassFrame) fieldFrame).hasInstanceMember(ast)) { 318 frame = getClassFrameWhereViolationIsFound(ast); 319 } 320 } 321 return frame; 322 } 323 324 /** 325 * Parses the next AST for declarations. 326 * @param frameStack stack containing the FrameTree being built. 327 * @param ast AST to parse. 328 */ 329 private static void collectDeclarations(Deque<AbstractFrame> frameStack, DetailAST ast) { 330 final AbstractFrame frame = frameStack.peek(); 331 switch (ast.getType()) { 332 case TokenTypes.VARIABLE_DEF : 333 collectVariableDeclarations(ast, frame); 334 break; 335 case TokenTypes.PARAMETER_DEF : 336 if (!CheckUtils.isReceiverParameter(ast) 337 && !isLambdaParameter(ast)) { 338 final DetailAST parameterIdent = ast.findFirstToken(TokenTypes.IDENT); 339 frame.addIdent(parameterIdent); 340 } 341 break; 342 case TokenTypes.CLASS_DEF : 343 case TokenTypes.INTERFACE_DEF : 344 case TokenTypes.ENUM_DEF : 345 case TokenTypes.ANNOTATION_DEF : 346 final DetailAST classFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 347 frameStack.addFirst(new ClassFrame(frame, classFrameNameIdent)); 348 break; 349 case TokenTypes.SLIST : 350 frameStack.addFirst(new BlockFrame(frame, ast)); 351 break; 352 case TokenTypes.METHOD_DEF : 353 final DetailAST methodFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 354 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 355 if (mods.branchContains(TokenTypes.LITERAL_STATIC)) { 356 ((ClassFrame) frame).addStaticMethod(methodFrameNameIdent); 357 } 358 else { 359 ((ClassFrame) frame).addInstanceMethod(methodFrameNameIdent); 360 } 361 frameStack.addFirst(new MethodFrame(frame, methodFrameNameIdent)); 362 break; 363 case TokenTypes.CTOR_DEF : 364 final DetailAST ctorFrameNameIdent = ast.findFirstToken(TokenTypes.IDENT); 365 frameStack.addFirst(new ConstructorFrame(frame, ctorFrameNameIdent)); 366 break; 367 case TokenTypes.LITERAL_NEW: 368 if (isAnonymousClassDef(ast)) { 369 frameStack.addFirst(new AnonymousClassFrame(frame, 370 ast.getFirstChild().toString())); 371 } 372 break; 373 default: 374 // do nothing 375 } 376 } 377 378 /** 379 * Collects variable declarations. 380 * @param ast variable token. 381 * @param frame current frame. 382 */ 383 private static void collectVariableDeclarations(DetailAST ast, AbstractFrame frame) { 384 final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT); 385 if (frame.getType() == FrameType.CLASS_FRAME) { 386 final DetailAST mods = 387 ast.findFirstToken(TokenTypes.MODIFIERS); 388 if (ScopeUtils.isInInterfaceBlock(ast) 389 || mods.branchContains(TokenTypes.LITERAL_STATIC)) { 390 ((ClassFrame) frame).addStaticMember(ident); 391 } 392 else { 393 ((ClassFrame) frame).addInstanceMember(ident); 394 } 395 } 396 else { 397 frame.addIdent(ident); 398 } 399 } 400 401 /** 402 * Ends parsing of the AST for declarations. 403 * @param frameStack Stack containing the FrameTree being built. 404 * @param ast AST that was parsed. 405 */ 406 private void endCollectingDeclarations(Queue<AbstractFrame> frameStack, DetailAST ast) { 407 switch (ast.getType()) { 408 case TokenTypes.CLASS_DEF : 409 case TokenTypes.INTERFACE_DEF : 410 case TokenTypes.ENUM_DEF : 411 case TokenTypes.ANNOTATION_DEF : 412 case TokenTypes.SLIST : 413 case TokenTypes.METHOD_DEF : 414 case TokenTypes.CTOR_DEF : 415 frames.put(ast, frameStack.poll()); 416 break; 417 case TokenTypes.LITERAL_NEW : 418 if (isAnonymousClassDef(ast)) { 419 frames.put(ast, frameStack.poll()); 420 } 421 break; 422 default : 423 // do nothing 424 } 425 } 426 427 /** 428 * Whether the AST is a definition of an anonymous class. 429 * @param ast the AST to process. 430 * @return true if the AST is a definition of an anonymous class. 431 */ 432 private static boolean isAnonymousClassDef(DetailAST ast) { 433 final DetailAST lastChild = ast.getLastChild(); 434 return lastChild != null 435 && lastChild.getType() == TokenTypes.OBJBLOCK; 436 } 437 438 /** 439 * Returns the class frame where violation is found (where the field is used without 'this') 440 * or null otherwise. 441 * @param ast IDENT ast to check. 442 * @return the class frame where violation is found or null otherwise. 443 * @noinspection IfStatementWithIdenticalBranches 444 */ 445 // -@cs[CyclomaticComplexity] Method already invokes too many methods that fully explain 446 // a logic, additional abstraction will not make logic/algorithm more readable. 447 private AbstractFrame getClassFrameWhereViolationIsFound(DetailAST ast) { 448 AbstractFrame frameWhereViolationIsFound = null; 449 final AbstractFrame variableDeclarationFrame = findFrame(ast, false); 450 final FrameType variableDeclarationFrameType = variableDeclarationFrame.getType(); 451 final DetailAST prevSibling = ast.getPreviousSibling(); 452 if (variableDeclarationFrameType == FrameType.CLASS_FRAME 453 && !validateOnlyOverlapping 454 && prevSibling == null 455 && canBeReferencedFromStaticContext(ast)) { 456 frameWhereViolationIsFound = variableDeclarationFrame; 457 } 458 else if (variableDeclarationFrameType == FrameType.METHOD_FRAME) { 459 if (isOverlappingByArgument(ast)) { 460 if (!isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 461 && !isReturnedVariable(variableDeclarationFrame, ast) 462 && canBeReferencedFromStaticContext(ast) 463 && canAssignValueToClassField(ast)) { 464 frameWhereViolationIsFound = findFrame(ast, true); 465 } 466 } 467 else if (!validateOnlyOverlapping 468 && prevSibling == null 469 && isAssignToken(ast.getParent().getType()) 470 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 471 && canBeReferencedFromStaticContext(ast) 472 && canAssignValueToClassField(ast)) { 473 frameWhereViolationIsFound = findFrame(ast, true); 474 475 } 476 } 477 else if (variableDeclarationFrameType == FrameType.CTOR_FRAME 478 && isOverlappingByArgument(ast) 479 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast)) { 480 frameWhereViolationIsFound = findFrame(ast, true); 481 } 482 else if (variableDeclarationFrameType == FrameType.BLOCK_FRAME 483 && isOverlappingByLocalVariable(ast) 484 && canAssignValueToClassField(ast) 485 && !isUserDefinedArrangementOfThis(variableDeclarationFrame, ast) 486 && !isReturnedVariable(variableDeclarationFrame, ast) 487 && canBeReferencedFromStaticContext(ast)) { 488 frameWhereViolationIsFound = findFrame(ast, true); 489 } 490 return frameWhereViolationIsFound; 491 } 492 493 /** 494 * Checks whether user arranges 'this' for variable in method, constructor, or block on his own. 495 * @param currentFrame current frame. 496 * @param ident ident token. 497 * @return true if user arranges 'this' for variable in method, constructor, 498 * or block on his own. 499 */ 500 private static boolean isUserDefinedArrangementOfThis(AbstractFrame currentFrame, 501 DetailAST ident) { 502 final DetailAST blockFrameNameIdent = currentFrame.getFrameNameIdent(); 503 final DetailAST definitionToken = blockFrameNameIdent.getParent(); 504 final DetailAST blockStartToken = definitionToken.findFirstToken(TokenTypes.SLIST); 505 final DetailAST blockEndToken = getBlockEndToken(blockFrameNameIdent, blockStartToken); 506 507 boolean userDefinedArrangementOfThis = false; 508 509 final Set<DetailAST> variableUsagesInsideBlock = 510 getAllTokensWhichAreEqualToCurrent(definitionToken, ident, 511 blockEndToken.getLineNo()); 512 513 for (DetailAST variableUsage : variableUsagesInsideBlock) { 514 final DetailAST prevSibling = variableUsage.getPreviousSibling(); 515 if (prevSibling != null 516 && prevSibling.getType() == TokenTypes.LITERAL_THIS) { 517 userDefinedArrangementOfThis = true; 518 break; 519 } 520 } 521 return userDefinedArrangementOfThis; 522 } 523 524 /** 525 * Returns the token which ends the code block. 526 * @param blockNameIdent block name identifier. 527 * @param blockStartToken token which starts the block. 528 * @return the token which ends the code block. 529 */ 530 private static DetailAST getBlockEndToken(DetailAST blockNameIdent, DetailAST blockStartToken) { 531 final Set<DetailAST> rcurlyTokens = getAllTokensOfType(blockNameIdent, TokenTypes.RCURLY); 532 DetailAST blockEndToken = null; 533 for (DetailAST currentRcurly : rcurlyTokens) { 534 final DetailAST parent = currentRcurly.getParent(); 535 if (blockStartToken.getLineNo() == parent.getLineNo()) { 536 blockEndToken = currentRcurly; 537 } 538 } 539 return blockEndToken; 540 } 541 542 /** 543 * Checks whether the current variable is returned from the method. 544 * @param currentFrame current frame. 545 * @param ident variable ident token. 546 * @return true if the current variable is returned from the method. 547 */ 548 private static boolean isReturnedVariable(AbstractFrame currentFrame, DetailAST ident) { 549 final DetailAST blockFrameNameIdent = currentFrame.getFrameNameIdent(); 550 final DetailAST definitionToken = blockFrameNameIdent.getParent(); 551 final DetailAST blockStartToken = definitionToken.findFirstToken(TokenTypes.SLIST); 552 final DetailAST blockEndToken = getBlockEndToken(blockFrameNameIdent, blockStartToken); 553 554 final Set<DetailAST> returnsInsideBlock = getAllTokensOfType(definitionToken, 555 TokenTypes.LITERAL_RETURN, blockEndToken.getLineNo()); 556 557 boolean returnedVariable = false; 558 for (DetailAST returnToken : returnsInsideBlock) { 559 returnedVariable = returnToken.findAll(ident).hasMoreNodes(); 560 if (returnedVariable) { 561 break; 562 } 563 } 564 return returnedVariable; 565 } 566 567 /** 568 * Checks whether a field can be referenced from a static context. 569 * @param ident ident token. 570 * @return true if field can be referenced from a static context. 571 */ 572 private boolean canBeReferencedFromStaticContext(DetailAST ident) { 573 AbstractFrame variableDeclarationFrame = findFrame(ident, false); 574 boolean staticInitializationBlock = false; 575 while (variableDeclarationFrame.getType() == FrameType.BLOCK_FRAME) { 576 final DetailAST blockFrameNameIdent = variableDeclarationFrame.getFrameNameIdent(); 577 final DetailAST definitionToken = blockFrameNameIdent.getParent(); 578 if (definitionToken.getType() == TokenTypes.STATIC_INIT) { 579 staticInitializationBlock = true; 580 break; 581 } 582 variableDeclarationFrame = variableDeclarationFrame.getParent(); 583 } 584 585 boolean staticContext = false; 586 if (staticInitializationBlock) { 587 staticContext = true; 588 } 589 else { 590 if (variableDeclarationFrame.getType() == FrameType.CLASS_FRAME) { 591 final DetailAST codeBlockDefinition = getCodeBlockDefinitionToken(ident); 592 if (codeBlockDefinition != null) { 593 final DetailAST modifiers = codeBlockDefinition.getFirstChild(); 594 staticContext = codeBlockDefinition.getType() == TokenTypes.STATIC_INIT 595 || modifiers.branchContains(TokenTypes.LITERAL_STATIC); 596 } 597 } 598 else { 599 final DetailAST frameNameIdent = variableDeclarationFrame.getFrameNameIdent(); 600 final DetailAST definitionToken = frameNameIdent.getParent(); 601 staticContext = definitionToken.branchContains(TokenTypes.LITERAL_STATIC); 602 } 603 } 604 return !staticContext; 605 } 606 607 /** 608 * Returns code block definition token for current identifier. 609 * @param ident ident token. 610 * @return code block definition token for current identifier or null if code block 611 * definition was not found. 612 */ 613 private static DetailAST getCodeBlockDefinitionToken(DetailAST ident) { 614 DetailAST parent = ident.getParent(); 615 while (parent != null 616 && parent.getType() != TokenTypes.METHOD_DEF 617 && parent.getType() != TokenTypes.CTOR_DEF 618 && parent.getType() != TokenTypes.STATIC_INIT) { 619 parent = parent.getParent(); 620 } 621 return parent; 622 } 623 624 /** 625 * Checks whether a value can be assigned to a field. 626 * A value can be assigned to a final field only in constructor block. If there is a method 627 * block, value assignment can be performed only to non final field. 628 * @param ast an identifier token. 629 * @return true if a value can be assigned to a field. 630 */ 631 private boolean canAssignValueToClassField(DetailAST ast) { 632 final AbstractFrame fieldUsageFrame = findFrame(ast, false); 633 final boolean fieldUsageInConstructor = isInsideConstructorFrame(fieldUsageFrame); 634 635 final AbstractFrame declarationFrame = findFrame(ast, true); 636 final boolean finalField = ((ClassFrame) declarationFrame).hasFinalField(ast); 637 638 return fieldUsageInConstructor || !finalField; 639 } 640 641 /** 642 * Checks whether a field usage frame is inside constructor frame. 643 * @param frame frame, where field is used. 644 * @return true if the field usage frame is inside constructor frame. 645 */ 646 private static boolean isInsideConstructorFrame(AbstractFrame frame) { 647 boolean assignmentInConstructor = false; 648 AbstractFrame fieldUsageFrame = frame; 649 if (fieldUsageFrame.getType() == FrameType.BLOCK_FRAME) { 650 while (fieldUsageFrame.getType() == FrameType.BLOCK_FRAME) { 651 fieldUsageFrame = fieldUsageFrame.getParent(); 652 } 653 if (fieldUsageFrame.getType() == FrameType.CTOR_FRAME) { 654 assignmentInConstructor = true; 655 } 656 } 657 return assignmentInConstructor; 658 } 659 660 /** 661 * Checks whether an overlapping by method or constructor argument takes place. 662 * @param ast an identifier. 663 * @return true if an overlapping by method or constructor argument takes place. 664 */ 665 private boolean isOverlappingByArgument(DetailAST ast) { 666 boolean overlapping = false; 667 final DetailAST parent = ast.getParent(); 668 final DetailAST sibling = ast.getNextSibling(); 669 if (sibling != null && isAssignToken(parent.getType())) { 670 if (isCompoundAssignToken(parent.getType())) { 671 overlapping = true; 672 } 673 else { 674 final ClassFrame classFrame = (ClassFrame) findFrame(ast, true); 675 final Set<DetailAST> exprIdents = getAllTokensOfType(sibling, TokenTypes.IDENT); 676 overlapping = classFrame.containsFieldOrVariableDef(exprIdents, ast); 677 } 678 } 679 return overlapping; 680 } 681 682 /** 683 * Checks whether an overlapping by local variable takes place. 684 * @param ast an identifier. 685 * @return true if an overlapping by local variable takes place. 686 */ 687 private boolean isOverlappingByLocalVariable(DetailAST ast) { 688 boolean overlapping = false; 689 final DetailAST parent = ast.getParent(); 690 final DetailAST sibling = ast.getNextSibling(); 691 if (sibling != null && isAssignToken(parent.getType())) { 692 final ClassFrame classFrame = (ClassFrame) findFrame(ast, true); 693 final Set<DetailAST> exprIdents = getAllTokensOfType(sibling, TokenTypes.IDENT); 694 overlapping = classFrame.containsFieldOrVariableDef(exprIdents, ast); 695 } 696 return overlapping; 697 } 698 699 /** 700 * Collects all tokens of specific type starting with the current ast node. 701 * @param ast ast node. 702 * @param tokenType token type. 703 * @return a set of all tokens of specific type starting with the current ast node. 704 */ 705 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) { 706 DetailAST vertex = ast; 707 final Set<DetailAST> result = new HashSet<>(); 708 final Deque<DetailAST> stack = new ArrayDeque<>(); 709 while (vertex != null || !stack.isEmpty()) { 710 if (!stack.isEmpty()) { 711 vertex = stack.pop(); 712 } 713 while (vertex != null) { 714 if (vertex.getType() == tokenType) { 715 result.add(vertex); 716 } 717 if (vertex.getNextSibling() != null) { 718 stack.push(vertex.getNextSibling()); 719 } 720 vertex = vertex.getFirstChild(); 721 } 722 } 723 return result; 724 } 725 726 /** 727 * Collects all tokens of specific type starting with the current ast node and which line 728 * number is lower or equal to the end line number. 729 * @param ast ast node. 730 * @param tokenType token type. 731 * @param endLineNumber end line number. 732 * @return a set of all tokens of specific type starting with the current ast node and which 733 * line number is lower or equal to the end line number. 734 */ 735 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType, 736 int endLineNumber) { 737 DetailAST vertex = ast; 738 final Set<DetailAST> result = new HashSet<>(); 739 final Deque<DetailAST> stack = new ArrayDeque<>(); 740 while (vertex != null || !stack.isEmpty()) { 741 if (!stack.isEmpty()) { 742 vertex = stack.pop(); 743 } 744 while (vertex != null) { 745 if (tokenType == vertex.getType() 746 && vertex.getLineNo() <= endLineNumber) { 747 result.add(vertex); 748 } 749 if (vertex.getNextSibling() != null) { 750 stack.push(vertex.getNextSibling()); 751 } 752 vertex = vertex.getFirstChild(); 753 } 754 } 755 return result; 756 } 757 758 /** 759 * Collects all tokens which are equal to current token starting with the current ast node and 760 * which line number is lower or equal to the end line number. 761 * @param ast ast node. 762 * @param token token. 763 * @param endLineNumber end line number. 764 * @return a set of tokens which are equal to current token starting with the current ast node 765 * and which line number is lower or equal to the end line number. 766 */ 767 private static Set<DetailAST> getAllTokensWhichAreEqualToCurrent(DetailAST ast, DetailAST token, 768 int endLineNumber) { 769 DetailAST vertex = ast; 770 final Set<DetailAST> result = new HashSet<>(); 771 final Deque<DetailAST> stack = new ArrayDeque<>(); 772 while (vertex != null || !stack.isEmpty()) { 773 if (!stack.isEmpty()) { 774 vertex = stack.pop(); 775 } 776 while (vertex != null) { 777 if (token.equals(vertex) 778 && vertex.getLineNo() <= endLineNumber) { 779 result.add(vertex); 780 } 781 if (vertex.getNextSibling() != null) { 782 stack.push(vertex.getNextSibling()); 783 } 784 vertex = vertex.getFirstChild(); 785 } 786 } 787 return result; 788 } 789 790 /** 791 * Returns the frame where the method is declared, if the given method is used without 792 * 'this' and null otherwise. 793 * @param ast the IDENT ast of the name to check. 794 * @return the frame where the method is declared, if the given method is used without 795 * 'this' and null otherwise. 796 */ 797 private AbstractFrame getMethodWithoutThis(DetailAST ast) { 798 AbstractFrame result = null; 799 final AbstractFrame frame = findFrame(ast, true); 800 if (!validateOnlyOverlapping 801 && ((ClassFrame) frame).hasInstanceMethod(ast) 802 && !((ClassFrame) frame).hasStaticMethod(ast)) { 803 result = frame; 804 } 805 return result; 806 } 807 808 /** 809 * Find the class frame containing declaration. 810 * @param name IDENT ast of the declaration to find. 811 * @param lookForMethod whether we are looking for a method name. 812 * @return AbstractFrame containing declaration or null. 813 */ 814 private AbstractFrame findClassFrame(DetailAST name, boolean lookForMethod) { 815 AbstractFrame frame = current; 816 817 while (true) { 818 frame = findFrame(frame, name, lookForMethod); 819 820 if (frame == null || frame instanceof ClassFrame) { 821 break; 822 } 823 824 frame = frame.getParent(); 825 } 826 827 return frame; 828 } 829 830 /** 831 * Find frame containing declaration. 832 * @param name IDENT ast of the declaration to find. 833 * @param lookForMethod whether we are looking for a method name. 834 * @return AbstractFrame containing declaration or null. 835 */ 836 private AbstractFrame findFrame(DetailAST name, boolean lookForMethod) { 837 return findFrame(current, name, lookForMethod); 838 } 839 840 /** 841 * Find frame containing declaration. 842 * @param frame The parent frame to searching in. 843 * @param name IDENT ast of the declaration to find. 844 * @param lookForMethod whether we are looking for a method name. 845 * @return AbstractFrame containing declaration or null. 846 */ 847 private static AbstractFrame findFrame(AbstractFrame frame, DetailAST name, 848 boolean lookForMethod) { 849 final AbstractFrame result; 850 if (frame == null) { 851 result = null; 852 } 853 else { 854 result = frame.getIfContains(name, lookForMethod); 855 } 856 return result; 857 } 858 859 /** 860 * Check that token is related to Definition tokens. 861 * @param parentType token Type. 862 * @return true if token is related to Definition Tokens. 863 */ 864 private static boolean isDeclarationToken(int parentType) { 865 return DECLARATION_TOKENS.contains(parentType); 866 } 867 868 /** 869 * Check that token is related to assign tokens. 870 * @param tokenType token type. 871 * @return true if token is related to assign tokens. 872 */ 873 private static boolean isAssignToken(int tokenType) { 874 return ASSIGN_TOKENS.contains(tokenType); 875 } 876 877 /** 878 * Check that token is related to compound assign tokens. 879 * @param tokenType token type. 880 * @return true if token is related to compound assign tokens. 881 */ 882 private static boolean isCompoundAssignToken(int tokenType) { 883 return COMPOUND_ASSIGN_TOKENS.contains(tokenType); 884 } 885 886 /** 887 * Gets the name of the nearest parent ClassFrame. 888 * @return the name of the nearest parent ClassFrame. 889 */ 890 private String getNearestClassFrameName() { 891 AbstractFrame frame = current; 892 while (frame.getType() != FrameType.CLASS_FRAME) { 893 frame = frame.getParent(); 894 } 895 return frame.getFrameName(); 896 } 897 898 /** 899 * Checks if the token is a Lambda parameter. 900 * @param ast the {@code DetailAST} value of the token to be checked 901 * @return true if the token is a Lambda parameter 902 */ 903 private static boolean isLambdaParameter(DetailAST ast) { 904 DetailAST parent; 905 for (parent = ast.getParent(); parent != null; parent = parent.getParent()) { 906 if (parent.getType() == TokenTypes.LAMBDA) { 907 break; 908 } 909 } 910 final boolean isLambdaParameter; 911 if (parent == null) { 912 isLambdaParameter = false; 913 } 914 else if (ast.getType() == TokenTypes.PARAMETER_DEF) { 915 isLambdaParameter = true; 916 } 917 else { 918 final DetailAST lambdaParameters = parent.findFirstToken(TokenTypes.PARAMETERS); 919 if (lambdaParameters == null) { 920 isLambdaParameter = parent.getFirstChild().getText().equals(ast.getText()); 921 } 922 else { 923 isLambdaParameter = TokenUtils.findFirstTokenByPredicate(lambdaParameters, 924 paramDef -> { 925 final DetailAST param = paramDef.findFirstToken(TokenTypes.IDENT); 926 return param != null && param.getText().equals(ast.getText()); 927 }).isPresent(); 928 } 929 } 930 return isLambdaParameter; 931 } 932 933 /** An AbstractFrame type. */ 934 private enum FrameType { 935 /** Class frame type. */ 936 CLASS_FRAME, 937 /** Constructor frame type. */ 938 CTOR_FRAME, 939 /** Method frame type. */ 940 METHOD_FRAME, 941 /** Block frame type. */ 942 BLOCK_FRAME, 943 } 944 945 /** 946 * A declaration frame. 947 * @author Stephen Bloch 948 * @author Andrei Selkin 949 */ 950 private abstract static class AbstractFrame { 951 /** Set of name of variables declared in this frame. */ 952 private final Set<DetailAST> varIdents; 953 954 /** Parent frame. */ 955 private final AbstractFrame parent; 956 957 /** Name identifier token. */ 958 private final DetailAST frameNameIdent; 959 960 /** 961 * Constructor -- invokable only via super() from subclasses. 962 * @param parent parent frame. 963 * @param ident frame name ident. 964 */ 965 protected AbstractFrame(AbstractFrame parent, DetailAST ident) { 966 this.parent = parent; 967 frameNameIdent = ident; 968 varIdents = new HashSet<>(); 969 } 970 971 /** 972 * Get the type of the frame. 973 * @return a FrameType. 974 */ 975 protected abstract FrameType getType(); 976 977 /** 978 * Add a name to the frame. 979 * @param identToAdd the name we're adding. 980 */ 981 private void addIdent(DetailAST identToAdd) { 982 varIdents.add(identToAdd); 983 } 984 985 protected AbstractFrame getParent() { 986 return parent; 987 } 988 989 protected String getFrameName() { 990 return frameNameIdent.getText(); 991 } 992 993 public DetailAST getFrameNameIdent() { 994 return frameNameIdent; 995 } 996 997 /** 998 * Check whether the frame contains a field or a variable with the given name. 999 * @param nameToFind the IDENT ast of the name we're looking for. 1000 * @return whether it was found. 1001 */ 1002 protected boolean containsFieldOrVariable(DetailAST nameToFind) { 1003 return containsFieldOrVariableDef(varIdents, nameToFind); 1004 } 1005 1006 /** 1007 * Check whether the frame contains a given name. 1008 * @param nameToFind IDENT ast of the name we're looking for. 1009 * @param lookForMethod whether we are looking for a method name. 1010 * @return whether it was found. 1011 */ 1012 protected AbstractFrame getIfContains(DetailAST nameToFind, boolean lookForMethod) { 1013 final AbstractFrame frame; 1014 1015 if (!lookForMethod 1016 && containsFieldOrVariable(nameToFind)) { 1017 frame = this; 1018 } 1019 else { 1020 frame = parent.getIfContains(nameToFind, lookForMethod); 1021 } 1022 return frame; 1023 } 1024 1025 /** 1026 * Whether the set contains a declaration with the text of the specified 1027 * IDENT ast and it is declared in a proper position. 1028 * @param set the set of declarations. 1029 * @param ident the specified IDENT ast. 1030 * @return true if the set contains a declaration with the text of the specified 1031 * IDENT ast and it is declared in a proper position. 1032 */ 1033 protected boolean containsFieldOrVariableDef(Set<DetailAST> set, DetailAST ident) { 1034 boolean result = false; 1035 for (DetailAST ast: set) { 1036 if (isProperDefinition(ident, ast)) { 1037 result = true; 1038 break; 1039 } 1040 } 1041 return result; 1042 } 1043 1044 /** 1045 * Whether the definition is correspondent to the IDENT. 1046 * @param ident the IDENT ast to check. 1047 * @param ast the IDENT ast of the definition to check. 1048 * @return true if ast is correspondent to ident. 1049 */ 1050 protected boolean isProperDefinition(DetailAST ident, DetailAST ast) { 1051 final String nameToFind = ident.getText(); 1052 return nameToFind.equals(ast.getText()) 1053 && checkPosition(ast, ident); 1054 } 1055 1056 /** 1057 * Whether the declaration is located before the checked ast. 1058 * @param ast1 the IDENT ast of the declaration. 1059 * @param ast2 the IDENT ast to check. 1060 * @return true, if the declaration is located before the checked ast. 1061 */ 1062 private static boolean checkPosition(DetailAST ast1, DetailAST ast2) { 1063 boolean result = false; 1064 if (ast1.getLineNo() < ast2.getLineNo() 1065 || ast1.getLineNo() == ast2.getLineNo() 1066 && ast1.getColumnNo() < ast2.getColumnNo()) { 1067 result = true; 1068 } 1069 return result; 1070 } 1071 } 1072 1073 /** 1074 * A frame initiated at method definition; holds a method definition token. 1075 * @author Stephen Bloch 1076 * @author Andrei Selkin 1077 */ 1078 private static class MethodFrame extends AbstractFrame { 1079 1080 /** 1081 * Creates method frame. 1082 * @param parent parent frame. 1083 * @param ident method name identifier token. 1084 */ 1085 protected MethodFrame(AbstractFrame parent, DetailAST ident) { 1086 super(parent, ident); 1087 } 1088 1089 @Override 1090 protected FrameType getType() { 1091 return FrameType.METHOD_FRAME; 1092 } 1093 } 1094 1095 /** 1096 * A frame initiated at constructor definition. 1097 * @author Andrei Selkin 1098 */ 1099 private static class ConstructorFrame extends AbstractFrame { 1100 1101 /** 1102 * Creates a constructor frame. 1103 * @param parent parent frame. 1104 * @param ident frame name ident. 1105 */ 1106 protected ConstructorFrame(AbstractFrame parent, DetailAST ident) { 1107 super(parent, ident); 1108 } 1109 1110 @Override 1111 protected FrameType getType() { 1112 return FrameType.CTOR_FRAME; 1113 } 1114 } 1115 1116 /** 1117 * A frame initiated at class, enum or interface definition; holds instance variable names. 1118 * @author Stephen Bloch 1119 * @author Andrei Selkin 1120 */ 1121 private static class ClassFrame extends AbstractFrame { 1122 /** Set of idents of instance members declared in this frame. */ 1123 private final Set<DetailAST> instanceMembers; 1124 /** Set of idents of instance methods declared in this frame. */ 1125 private final Set<DetailAST> instanceMethods; 1126 /** Set of idents of variables declared in this frame. */ 1127 private final Set<DetailAST> staticMembers; 1128 /** Set of idents of static methods declared in this frame. */ 1129 private final Set<DetailAST> staticMethods; 1130 1131 /** 1132 * Creates new instance of ClassFrame. 1133 * @param parent parent frame. 1134 * @param ident frame name ident. 1135 */ 1136 ClassFrame(AbstractFrame parent, DetailAST ident) { 1137 super(parent, ident); 1138 instanceMembers = new HashSet<>(); 1139 instanceMethods = new HashSet<>(); 1140 staticMembers = new HashSet<>(); 1141 staticMethods = new HashSet<>(); 1142 } 1143 1144 @Override 1145 protected FrameType getType() { 1146 return FrameType.CLASS_FRAME; 1147 } 1148 1149 /** 1150 * Adds static member's ident. 1151 * @param ident an ident of static member of the class. 1152 */ 1153 public void addStaticMember(final DetailAST ident) { 1154 staticMembers.add(ident); 1155 } 1156 1157 /** 1158 * Adds static method's name. 1159 * @param ident an ident of static method of the class. 1160 */ 1161 public void addStaticMethod(final DetailAST ident) { 1162 staticMethods.add(ident); 1163 } 1164 1165 /** 1166 * Adds instance member's ident. 1167 * @param ident an ident of instance member of the class. 1168 */ 1169 public void addInstanceMember(final DetailAST ident) { 1170 instanceMembers.add(ident); 1171 } 1172 1173 /** 1174 * Adds instance method's name. 1175 * @param ident an ident of instance method of the class. 1176 */ 1177 public void addInstanceMethod(final DetailAST ident) { 1178 instanceMethods.add(ident); 1179 } 1180 1181 /** 1182 * Checks if a given name is a known instance member of the class. 1183 * @param ident the IDENT ast of the name to check. 1184 * @return true is the given name is a name of a known 1185 * instance member of the class. 1186 */ 1187 public boolean hasInstanceMember(final DetailAST ident) { 1188 return containsFieldOrVariableDef(instanceMembers, ident); 1189 } 1190 1191 /** 1192 * Checks if a given name is a known instance method of the class. 1193 * @param ident the IDENT ast of the method call to check. 1194 * @return true if the given ast is correspondent to a known 1195 * instance method of the class. 1196 */ 1197 public boolean hasInstanceMethod(final DetailAST ident) { 1198 return containsMethodDef(instanceMethods, ident); 1199 } 1200 1201 /** 1202 * Checks if a given name is a known static method of the class. 1203 * @param ident the IDENT ast of the method call to check. 1204 * @return true is the given ast is correspondent to a known 1205 * instance method of the class. 1206 */ 1207 public boolean hasStaticMethod(final DetailAST ident) { 1208 return containsMethodDef(staticMethods, ident); 1209 } 1210 1211 /** 1212 * Checks whether given instance member has final modifier. 1213 * @param instanceMember an instance member of a class. 1214 * @return true if given instance member has final modifier. 1215 */ 1216 public boolean hasFinalField(final DetailAST instanceMember) { 1217 boolean result = false; 1218 for (DetailAST member : instanceMembers) { 1219 final DetailAST mods = member.getParent().findFirstToken(TokenTypes.MODIFIERS); 1220 final boolean finalMod = mods.branchContains(TokenTypes.FINAL); 1221 if (finalMod && member.equals(instanceMember)) { 1222 result = true; 1223 break; 1224 } 1225 } 1226 return result; 1227 } 1228 1229 @Override 1230 protected boolean containsFieldOrVariable(DetailAST nameToFind) { 1231 return containsFieldOrVariableDef(instanceMembers, nameToFind) 1232 || containsFieldOrVariableDef(staticMembers, nameToFind); 1233 } 1234 1235 @Override 1236 protected boolean isProperDefinition(DetailAST ident, DetailAST ast) { 1237 final String nameToFind = ident.getText(); 1238 return nameToFind.equals(ast.getText()); 1239 } 1240 1241 @Override 1242 protected AbstractFrame getIfContains(DetailAST nameToFind, boolean lookForMethod) { 1243 AbstractFrame frame = null; 1244 1245 if (lookForMethod && containsMethod(nameToFind) 1246 || containsFieldOrVariable(nameToFind)) { 1247 frame = this; 1248 } 1249 else if (getParent() != null) { 1250 frame = getParent().getIfContains(nameToFind, lookForMethod); 1251 } 1252 return frame; 1253 } 1254 1255 /** 1256 * Check whether the frame contains a given method. 1257 * @param methodToFind the AST of the method to find. 1258 * @return true, if a method with the same name and number of parameters is found. 1259 */ 1260 private boolean containsMethod(DetailAST methodToFind) { 1261 return containsMethodDef(instanceMethods, methodToFind) 1262 || containsMethodDef(staticMethods, methodToFind); 1263 } 1264 1265 /** 1266 * Whether the set contains a method definition with the 1267 * same name and number of parameters. 1268 * @param set the set of definitions. 1269 * @param ident the specified method call IDENT ast. 1270 * @return true if the set contains a definition with the 1271 * same name and number of parameters. 1272 */ 1273 private static boolean containsMethodDef(Set<DetailAST> set, DetailAST ident) { 1274 boolean result = false; 1275 for (DetailAST ast: set) { 1276 if (isSimilarSignature(ident, ast)) { 1277 result = true; 1278 break; 1279 } 1280 } 1281 return result; 1282 } 1283 1284 /** 1285 * Whether the method definition has the same name and number of parameters. 1286 * @param ident the specified method call IDENT ast. 1287 * @param ast the ast of a method definition to compare with. 1288 * @return true if a method definition has the same name and number of parameters 1289 * as the method call. 1290 */ 1291 private static boolean isSimilarSignature(DetailAST ident, DetailAST ast) { 1292 boolean result = false; 1293 final DetailAST elistToken = ident.getParent().findFirstToken(TokenTypes.ELIST); 1294 if (elistToken != null && ident.getText().equals(ast.getText())) { 1295 final int paramsNumber = 1296 ast.getParent().findFirstToken(TokenTypes.PARAMETERS).getChildCount(); 1297 final int argsNumber = elistToken.getChildCount(); 1298 result = paramsNumber == argsNumber; 1299 } 1300 return result; 1301 } 1302 } 1303 1304 /** 1305 * An anonymous class frame; holds instance variable names. 1306 */ 1307 private static class AnonymousClassFrame extends ClassFrame { 1308 1309 /** The name of the frame. */ 1310 private final String frameName; 1311 1312 /** 1313 * Creates anonymous class frame. 1314 * @param parent parent frame. 1315 * @param frameName name of the frame. 1316 */ 1317 protected AnonymousClassFrame(AbstractFrame parent, String frameName) { 1318 super(parent, null); 1319 this.frameName = frameName; 1320 } 1321 1322 @Override 1323 protected String getFrameName() { 1324 return frameName; 1325 } 1326 } 1327 1328 /** 1329 * A frame initiated on entering a statement list; holds local variable names. 1330 * @author Stephen Bloch 1331 */ 1332 private static class BlockFrame extends AbstractFrame { 1333 1334 /** 1335 * Creates block frame. 1336 * @param parent parent frame. 1337 * @param ident ident frame name ident. 1338 */ 1339 protected BlockFrame(AbstractFrame parent, DetailAST ident) { 1340 super(parent, ident); 1341 } 1342 1343 @Override 1344 protected FrameType getType() { 1345 return FrameType.BLOCK_FRAME; 1346 } 1347 } 1348}