Skip to content

Extract method

Introduction

An Extraction method refactoring class for using compiler listeners

Description about the code:
- statements are each line of code showing an act for example a = 5; is a statement.

- exact each method of each class.

Pre and post-conditions

Pre-conditions:

No specific pre-condition

Post-conditions:

No specific Post-condition

Conf object help

  • Lines are calculated from beginning the beginning of file starting from 1 .

limitations

  • Extracted lines must be a part of a method or a class constructor any other format simply would not work. (though we don't know java even supports any other format)

ExtractMethodRefactoring (JavaParserLabeledListener)

Extract method factoring class extending javaParserLabeledListener

Source code in codart\refactorings\extract_method.py
class ExtractMethodRefactoring(JavaParserLabeledListener):
    """

    Extract method factoring class extending javaParserLabeledListener

    """

    def __init__(self, lines: list):
        """
        Arges:

            Lines (list<int>): A list of statements line numbers to be extracted form the method body.

        Returns:

              object (ExtractMethodRefactoring): An instance of ExtractMethodRefactoring

        """

        # checks Target method and lines to be valid
        if lines is None or len(lines) == 0:
            raise Exception('target lines are not specified.')

        # setting variables
        self.lines = np.array(lines)
        self.lines.reshape((len(lines), 1))
        self.last_line = self.lines.max()
        self.first_line = self.lines.min()
        self.post_variables = {}
        self.pre_variables = {}
        self.mid_variables = {}
        self.is_in_target_method = False
        self.is_target_method_static = False
        self.is_result_valid = False
        self.exception_thrown_in_target_method = None
        self.assigning_value_pre = False
        self.assigning_value_mid = False
        self.assigning_value_post = False
        self.method_stop_line = 0
        self.return_variable = None
        self.return_variable_type = None
        self.methods_name = []

    ######################################
    # Overriding required methods to satisfy our extraction requirements
    ######################################

    def enterMethodDeclaration(self, ctx: JavaParserLabeled.MethodDeclarationContext):
        self.methods_name.append(ctx.IDENTIFIER().getText())
        # checks if this is the method containing target lines
        if ctx.start.line <= self.first_line and ctx.stop.line >= self.last_line:
            print("Found method containing target lines.")
            self.is_in_target_method = True
            self.is_result_valid = True

            # checks if method is static
            for modifier in ctx.parentCtx.parentCtx.modifier():
                if modifier.getText() == 'static':
                    self.is_target_method_static = True
                    print("Target Method is static.")
                    break

            # checks if method throws any exception
            if ctx.qualifiedNameList():
                self.exception_thrown_in_target_method = ctx.qualifiedNameList().getText()
                print("Target Method throws exception.")
                # TODO : check extracted lines for exception occurrence instead ,
                #  as they may not throw exception even though their parent method does

            # save method's last line number
            self.method_stop_line = ctx.stop.line

    def exitMethodDeclaration(self, ctx: JavaParserLabeled.MethodDeclarationContext):
        self.is_in_target_method = False

    def enterConstructorDeclaration(self, ctx: JavaParserLabeled.ConstructorDeclarationContext):

        # checks if this Constructor contains target lines
        if ctx.start.line <= self.first_line and ctx.stop.line >= self.last_line:
            print("Found Constructor containing target lines.")
            self.is_in_target_method = True
            self.is_result_valid = True

            # checks if Constructor is static
            for modifier in ctx.parentCtx.parentCtx.modifier():
                if modifier.getText() == 'static':
                    self.is_target_method_static = True
                    print("Target Method is static.")
                    break

            # checks if Constructor throws any exception
            if ctx.qualifiedNameList():
                self.exception_thrown_in_target_method = ctx.qualifiedNameList().getText()
                print("Target Method throws exception.")
                # TODO : check extracted lines for exception occurrence instead ,
                #  as they may not throw exception even though their parent method does

            # save method's last line number
            self.method_stop_line = ctx.stop.line

    def exitConstructorDeclaration(self, ctx: JavaParserLabeled.ConstructorDeclarationContext):
        self.is_in_target_method = False

    def enterLocalVariableDeclaration(self, ctx: JavaParserLabeled.LocalVariableDeclarationContext):

        # checks if we are in target method
        if self.is_in_target_method:

            # checks if this statement is not in extracting lines
            if not set(range(ctx.start.line, ctx.stop.line + 1)).issubset(set(self.lines)):

                # checks if this statement is before extracting lines
                if ctx.start.line < self.last_line:

                    # adding all created variables
                    for var in ctx.variableDeclarators().variableDeclarator():
                        self.pre_variables[var.variableDeclaratorId().getText()] = \
                            {
                                'type': ctx.typeType().getText(),
                                'write': str(var.getText()).__contains__('=')
                            }

                # this statement is after extracting lines
                else:
                    pass

            # this statement is inside extracting lines
            else:

                # adding all created variables
                for var in ctx.variableDeclarators().variableDeclarator():
                    self.mid_variables[
                        var.variableDeclaratorId().getText()] = \
                        {
                            'type': ctx.typeType().getText(),
                            'write': str(var.getText()).__contains__('=')
                        }

    def enterEnhancedForControl(self, ctx: JavaParserLabeled.EnhancedForControlContext):

        # checks if we are in target method
        if self.is_in_target_method:

            # checks if this statement is not in extracting lines
            if not set(range(ctx.start.line, ctx.stop.line + 1)).issubset(set(self.lines)):

                # checks if this statement is before extracting lines
                if ctx.start.line < self.last_line:

                    # adding created variables
                    var = ctx.variableDeclaratorId()
                    self.pre_variables[var.getText()] = \
                        {
                            'type': ctx.typeType().getText(),
                            'write': True
                        }

                # this statement is after extracting lines
                else:
                    pass

            # this statement is inside extracting lines
            else:

                # adding created variables
                var = ctx.variableDeclaratorId()
                self.mid_variables[var.getText()] = \
                    {
                        'type': ctx.typeType().getText(),
                        'write': True
                    }

    # adding target method parameters to pre_variables
    def enterFormalParameter(self, ctx: JavaParserLabeled.FormalParameterContext):

        # checks if we are in target method
        if self.is_in_target_method:
            # adding all created variables
            self.pre_variables[ctx.variableDeclaratorId().getText()] = \
                {
                    'type': ctx.typeType().getText(),
                    'write': True
                }

    # detecting writing value to variables
    def enterExpression21(self, ctx: JavaParserLabeled.Expression21Context):

        # checks if we are in target method
        if self.is_in_target_method:

            # checks if this statement is not in extracting lines
            if not set(range(ctx.start.line, ctx.stop.line + 1)).issubset(set(self.lines)):

                # checks if this statement is before extracting lines
                if ctx.start.line < self.last_line:
                    self.assigning_value_pre = True

                # this statement is after extracting lines
                elif ctx.start.line > self.last_line:
                    self.assigning_value_post = True

                # any other case is useless
                else:
                    pass

            # this statement is inside extracting lines
            else:
                self.assigning_value_mid = True

    def enterEveryRule(self, ctx: ParserRuleContext):

        # checks if we are in target method
        if self.is_in_target_method:

            # checks if every statements are is either completely inside or outside of extracting lines
            if set(range(ctx.start.line, ctx.stop.line + 1)) & set(self.lines):
                if not set(self.lines).issubset(set(range(ctx.start.line, ctx.stop.line + 1))) and \
                        not set(range(ctx.start.line, ctx.stop.line + 1)).issubset(self.lines):
                    self.is_result_valid = False

    def enterPrimary4(self, ctx: JavaParserLabeled.Primary4Context):

        # checks if we are in target method
        if self.is_in_target_method:

            # print('entering:',ctx.getText())
            # print(self.assigning_value_pre)
            # print(self.assigning_value_mid)
            # print(self.assigning_value_post)

            # writing value to a variable in mid
            if self.assigning_value_mid:

                # adding variable
                action = 'write'
                if (isinstance(ctx.parentCtx.parentCtx,
                               JavaParserLabeled.Expression1Context) and ctx.parentCtx.parentCtx.DOT()) or \
                        (isinstance(ctx.parentCtx.parentCtx,
                                    JavaParserLabeled.Expression2Context) and ctx.parentCtx.parentCtx.LBRACK()):
                    # print("exiting:", ctx.getText())
                    action = 'read'
                if self.mid_variables.keys().__contains__(str(ctx.IDENTIFIER())):
                    self.mid_variables[str(ctx.IDENTIFIER())][action] = True
                else:
                    self.mid_variables[str(ctx.IDENTIFIER())] = {action: True}
                self.assigning_value_mid = False

            # writing value to a variable in pre
            elif self.assigning_value_pre:

                # adding variable
                action = 'write'
                if (isinstance(ctx.parentCtx.parentCtx,
                               JavaParserLabeled.Expression1Context) and ctx.parentCtx.parentCtx.DOT()) or \
                        (isinstance(ctx.parentCtx.parentCtx,
                                    JavaParserLabeled.Expression2Context) and ctx.parentCtx.parentCtx.LBRACK()):
                    # print("exiting:", ctx.getText())
                    action = 'read'
                if self.pre_variables.keys().__contains__(str(ctx.IDENTIFIER())):
                    self.pre_variables[str(ctx.IDENTIFIER())][action] = True
                else:
                    self.pre_variables[str(ctx.IDENTIFIER())] = {action: True}
                self.assigning_value_pre = False

            # writing value to a variable in post
            elif self.assigning_value_post:

                # adding variable
                action = 'write'
                if (isinstance(ctx.parentCtx.parentCtx,
                               JavaParserLabeled.Expression1Context) and ctx.parentCtx.parentCtx.DOT()) or \
                        (isinstance(ctx.parentCtx.parentCtx,
                                    JavaParserLabeled.Expression2Context) and ctx.parentCtx.parentCtx.LBRACK()):
                    # print("exiting:", ctx.getText())
                    action = 'read'
                if self.post_variables.keys().__contains__(str(ctx.IDENTIFIER())):
                    self.post_variables[str(ctx.IDENTIFIER())][action] = True
                else:
                    self.post_variables[str(ctx.IDENTIFIER())] = {action: True}
                self.assigning_value_post = False

            # reading a variable value not in extracting lines
            elif not set(range(ctx.start.line, ctx.stop.line + 1)).issubset(set(self.lines)):

                # checks if this statement is after extracting lines
                if ctx.start.line > self.last_line:

                    # adding variable to post_variables
                    if self.post_variables.keys().__contains__(str(ctx.IDENTIFIER())):
                        self.post_variables[str(ctx.IDENTIFIER())]['read'] = True
                    else:
                        self.post_variables[str(ctx.IDENTIFIER())] = {'read': True}

                # this statement is before extracting lines
                else:
                    pass

            # this statement is inside extracting lines
            else:
                if self.mid_variables.keys().__contains__(str(ctx.IDENTIFIER())):
                    self.mid_variables[str(ctx.IDENTIFIER())]['read'] = True
                else:
                    self.mid_variables[str(ctx.IDENTIFIER())] = {'read': True}

    # helper functions
    # get method arguments for function call
    def get_args(self, include_type: bool):
        print(self.pre_variables)
        result = '('
        first = True
        for key in self.mid_variables.keys():
            if self.mid_variables[key].keys().__contains__('type'):
                continue
            if self.pre_variables.keys().__contains__(key) and \
                    self.pre_variables[key].keys().__contains__('write') and \
                    self.pre_variables[key]['write'] and \
                    self.pre_variables[key].keys().__contains__('type'):
                if not first:
                    result += ', '
                else:
                    first = False
                if include_type:
                    result += self.pre_variables[key]['type'] + ' ' + key
                else:
                    result += key
        result += ')' + ("" if include_type else ";")
        return result

    def get_write_variable(self):
        result = None
        for key in self.post_variables.keys():
            if self.mid_variables.__contains__(key) and self.mid_variables[key].__contains__('type') and \
                    self.mid_variables[key]['type']:
                if result is None:
                    self.return_variable = key
                    self.return_variable_type = self.mid_variables[key]['type']
                    result = self.mid_variables[key]['type'] + ' ' + key + ' = '
                else:
                    print('assignments on :', self.return_variable, ",", key)
                    self.return_variable = None
                    raise Exception('only one assignment in extracting lines is acceptable!')

            elif self.pre_variables.__contains__(key) and self.pre_variables[key].__contains__('type') and \
                    self.pre_variables[key]['type'] and self.mid_variables.__contains__(key) and self.mid_variables[
                key].__contains__('write') and self.mid_variables[key]['write']:
                if result is None:
                    result = key + ' = '
                    self.return_variable = key
                    self.return_variable_type = self.pre_variables[key]['type']
                else:
                    print('assignments on :', self.return_variable, ",", key)
                    self.return_variable = None
                    raise Exception('only one assignment in extracting lines is acceptable!')
        return '' if result is None else result

__init__(self, lines) special

Arges:

Lines (list<int>): A list of statements line numbers to be extracted form the method body.

Returns:

Type Description
object (ExtractMethodRefactoring)

An instance of ExtractMethodRefactoring

Source code in codart\refactorings\extract_method.py
def __init__(self, lines: list):
    """
    Arges:

        Lines (list<int>): A list of statements line numbers to be extracted form the method body.

    Returns:

          object (ExtractMethodRefactoring): An instance of ExtractMethodRefactoring

    """

    # checks Target method and lines to be valid
    if lines is None or len(lines) == 0:
        raise Exception('target lines are not specified.')

    # setting variables
    self.lines = np.array(lines)
    self.lines.reshape((len(lines), 1))
    self.last_line = self.lines.max()
    self.first_line = self.lines.min()
    self.post_variables = {}
    self.pre_variables = {}
    self.mid_variables = {}
    self.is_in_target_method = False
    self.is_target_method_static = False
    self.is_result_valid = False
    self.exception_thrown_in_target_method = None
    self.assigning_value_pre = False
    self.assigning_value_mid = False
    self.assigning_value_post = False
    self.method_stop_line = 0
    self.return_variable = None
    self.return_variable_type = None
    self.methods_name = []

main(file_path, lines)

The main API for Extract Method refactoring operation

Source code in codart\refactorings\extract_method.py
def main(file_path, lines: dict):
    """

       The main API for Extract Method refactoring operation

    """

    print("Started Extract Method")
    _conf = {
        'target_file': file_path,
        'output_file': file_path,
        'lines': lines,
        'new_method_name': 'newMethodByCodArt',
    }
    extract_method(_conf)

    print("Finished Extract Method")