Skip to content

Move class

Introduction

The module implements Move Class refactoring operation

Pre and post-conditions

Pre-conditions:

Todo: Add pre-conditions

Post-conditions:

Todo: Add post-conditions

MoveClassAPI

Source code in codart\refactorings\move_class.py
class MoveClassAPI:
    """


    """

    def __init__(self, udb_path: str, source_package: str, target_package: str, class_name: str):
        """


        """

        self.udb_path = udb_path
        self.source_package = source_package
        self.target_package = target_package
        self.class_name = class_name

        self.source_package_dir = None
        self.target_package_dir = None
        self._class_current_path = None
        self.class_content = None
        self.usages = None
        self._class_new_path = None

    def check_preconditions(self) -> bool:
        if self.source_package == self.target_package:
            config.logger.error("Source and target packages are same.")
            return False

        if self.source_package == ROOT_PACKAGE or self.target_package == ROOT_PACKAGE:
            config.logger.error("Can not move package to/from root package.")
            return False

        # Get package directories
        source_package_dir, target_package_dir = self.get_package_directories()
        if source_package_dir is None or target_package_dir is None:
            config.logger.error("Package entity does not exists.")
            return False

        if config.PROJECT_PATH not in target_package_dir:
            config.logger.error(f"Target package address {target_package_dir} cannot be resolved.")
            return False

        if config.PROJECT_PATH not in source_package_dir:
            config.logger.error(f"Source package address {source_package_dir} cannot be resolved.")
            return False

        class_full_path = os.path.join(source_package_dir, f"{self.class_name}.java")
        if not os.path.exists(class_full_path):
            config.logger.error(f'Class "{self.class_name}" does not exists in source package "{source_package_dir}" ')
            return False

        # Get class directory
        class_path, class_content, usages = self.get_class_info()
        if class_path is None or class_content is None:
            config.logger.error("Class entity does not exists.")
            return False

        class_new_path = os.path.join(target_package_dir, f"{self.class_name}.java")
        if os.path.exists(class_new_path):
            config.logger.error("Class already exists in target package.")
            return False

        self.source_package_dir = source_package_dir
        self.target_package_dir = target_package_dir
        self._class_current_path = class_path
        self.class_content = class_content
        self.usages = usages
        self._class_new_path = class_new_path

        return True

    def get_package_directories(self):
        db = und.open(self.udb_path)
        source_package_path = None
        target_package_path = None
        packages = db.ents("Java Package")
        for ent_ in packages:
            if ent_.longname() == self.source_package:
                if ent_.parent() is not None:
                    name_ = ent_.parent().longname()
                    if os.path.exists(name_):
                        source_package_path = os.path.dirname(name_)
                        break

        for ent_ in packages:
            if ent_.longname() == self.target_package:
                if ent_.parent() is not None:
                    name_ = ent_.parent().longname()
                    if os.path.exists(name_):
                        target_package_path = os.path.dirname(name_)
                        break

        db.close()
        return source_package_path, target_package_path

    def get_class_info(self):
        db = und.open(self.udb_path)
        class_path = None
        class_contents = None
        usages = set()

        classes = db.ents("Class ~Unresolved ~Unknown ~Anonymous")
        for ent_ in classes:
            simple_name = ent_.simplename()
            if simple_name == self.class_name and class_path is None and ent_.parent() is not None:
                class_contents = ent_.contents()
                class_path = ent_.parent().longname()

                for ref_ in ent_.refs():
                    if ref_.file().simplename() != f"{simple_name}.java":
                        usages.add(ref_.file().longname())
                break
        db.close()
        return class_path, class_contents, usages

    def do_refactor(self):
        if not self.check_preconditions():
            config.logger.error("Pre conditions failed.")
            return False

        # Update usages
        for file_path in self.usages:
            parse_and_walk(
                file_path=file_path,
                listener_class=UpdateImportsListener,
                has_write=True,
                source_package=self.source_package,
                target_package=self.target_package,
                class_name=self.class_name
            )

        # Delete source class
        # config.logger.debug(f'Current class path to be removed: {self._class_current_path}')
        os.remove(self._class_current_path)

        # Write the new class
        package = ""
        if self.target_package != ROOT_PACKAGE:
            package = f"package {self.target_package};\n"
        imports = ""
        if self.source_package != ROOT_PACKAGE:
            imports = f"import {self.source_package}.*;\n"

        # logger.debug(f'New class path to be added: {self._class_new_path}')
        with open(self._class_new_path, mode='w', encoding='utf8', errors='ignore') as f:
            f.write(package + imports + self.class_content)

        return True

__init__(self, udb_path, source_package, target_package, class_name) special

Source code in codart\refactorings\move_class.py
def __init__(self, udb_path: str, source_package: str, target_package: str, class_name: str):
    """


    """

    self.udb_path = udb_path
    self.source_package = source_package
    self.target_package = target_package
    self.class_name = class_name

    self.source_package_dir = None
    self.target_package_dir = None
    self._class_current_path = None
    self.class_content = None
    self.usages = None
    self._class_new_path = None

UpdateImportsListener (JavaParserLabeledListener)

Source code in codart\refactorings\move_class.py
class UpdateImportsListener(JavaParserLabeledListener):
    """


    """

    def __init__(self, rewriter: TokenStreamRewriter, source_package: str, target_package: str, class_name: str):
        """


        """

        self.rewriter = rewriter
        self.source_package = source_package
        self.target_package = target_package
        self.class_name = class_name
        self.current_package = None

        self.imported = False
        self.import_loc = None

    def enterPackageDeclaration(self, ctx: JavaParserLabeled.PackageDeclarationContext):
        self.current_package = ctx.qualifiedName().getText()

    def exitPackageDeclaration(self, ctx: JavaParserLabeled.PackageDeclarationContext):
        self.import_loc = ctx.stop

    def enterImportDeclaration(self, ctx: JavaParserLabeled.ImportDeclarationContext):
        # import source_package.Sample;
        if self.target_package in ctx.getText():
            self.imported = True
            if self.class_name in ctx.getText():
                if self.target_package == self.current_package:
                    replace_text = ""
                else:
                    replace_text = f"\nimport {self.target_package}.{self.class_name};\n"

                self.rewriter.replaceRangeTokens(
                    from_token=ctx.start,
                    to_token=ctx.stop,
                    text=replace_text,
                    program_name=self.rewriter.DEFAULT_PROGRAM_NAME
                )
        elif f"{self.source_package}.{self.class_name}" in ctx.getText():
            self.rewriter.delete(
                program_name=self.rewriter.DEFAULT_PROGRAM_NAME,
                from_idx=ctx.start.tokenIndex,
                to_idx=ctx.stop.tokenIndex
            )

    def exitCompilationUnit(self, ctx: JavaParserLabeled.CompilationUnitContext):
        if not self.imported and self.current_package != self.target_package:
            self.rewriter.insertAfterToken(
                token=self.import_loc,
                text=f"\nimport {self.target_package}.{self.class_name};\n",
                program_name=self.rewriter.DEFAULT_PROGRAM_NAME
            )

__init__(self, rewriter, source_package, target_package, class_name) special

Source code in codart\refactorings\move_class.py
def __init__(self, rewriter: TokenStreamRewriter, source_package: str, target_package: str, class_name: str):
    """


    """

    self.rewriter = rewriter
    self.source_package = source_package
    self.target_package = target_package
    self.class_name = class_name
    self.current_package = None

    self.imported = False
    self.import_loc = None

main(udb_path, source_package, target_package, class_name, *args, **kwargs)

The main API for Move Class refactoring

Source code in codart\refactorings\move_class.py
def main(udb_path: str, source_package: str, target_package: str, class_name: str, *args, **kwargs):
    """

    The main API for Move Class refactoring

    """
    move_class = MoveClassAPI(udb_path, source_package, target_package, class_name)
    res = move_class.do_refactor()
    return res