Move method
Introduction
The module implements Move Method refactoring operation
Pre and post-conditions
Pre-conditions:
Todo: Add pre-conditions
Post-conditions:
Todo: Add post-conditions
CutMethodListener (JavaParserLabeledListener)
Source code in codart\refactorings\move_method.py
class CutMethodListener(JavaParserLabeledListener):
"""
"""
def __init__(self, class_name: str, instance_name: str, method_name: str, is_static: bool, import_statement: str,
rewriter: TokenStreamRewriter):
"""
"""
self.class_name = class_name
self.method_name = method_name
self.is_static = is_static
self.rewriter = rewriter
self.import_statement = import_statement
self.instance_name = instance_name
self.is_member = False
self.do_delete = False
self.method_text = ""
self.imports = ""
def enterImportDeclaration(self, ctx: JavaParserLabeled.ImportDeclarationContext):
self.imports += self.rewriter.getText(
program_name=self.rewriter.DEFAULT_PROGRAM_NAME,
start=ctx.start.tokenIndex,
stop=ctx.stop.tokenIndex
) + "\n"
def exitPackageDeclaration(self, ctx: JavaParserLabeled.PackageDeclarationContext):
if self.import_statement:
self.rewriter.insertAfterToken(
token=ctx.stop,
text=self.import_statement,
program_name=self.rewriter.DEFAULT_PROGRAM_NAME
)
self.import_statement = None
def enterMemberDeclaration0(self, ctx: JavaParserLabeled.MemberDeclaration0Context):
self.is_member = True
def exitMemberDeclaration0(self, ctx: JavaParserLabeled.MemberDeclaration0Context):
self.is_member = False
def enterMethodDeclaration(self, ctx: JavaParserLabeled.MethodDeclarationContext):
if self.is_member and ctx.IDENTIFIER().getText() == self.method_name:
self.do_delete = True
def exitClassBodyDeclaration2(self, ctx: JavaParserLabeled.ClassBodyDeclaration2Context):
if self.do_delete:
self.method_text = self.rewriter.getText(
program_name=self.rewriter.DEFAULT_PROGRAM_NAME,
start=ctx.start.tokenIndex,
stop=ctx.stop.tokenIndex
)
if self.is_static:
replace_text = f"public static {self.class_name} {self.instance_name} = new {self.class_name}();"
else:
replace_text = f"public {self.class_name} {self.instance_name} = new {self.class_name}();"
self.rewriter.replace(
program_name=self.rewriter.DEFAULT_PROGRAM_NAME,
from_idx=ctx.start.tokenIndex,
to_idx=ctx.stop.tokenIndex,
text=replace_text
)
self.do_delete = False
__init__(self, class_name, instance_name, method_name, is_static, import_statement, rewriter)
special
Source code in codart\refactorings\move_method.py
def __init__(self, class_name: str, instance_name: str, method_name: str, is_static: bool, import_statement: str,
rewriter: TokenStreamRewriter):
"""
"""
self.class_name = class_name
self.method_name = method_name
self.is_static = is_static
self.rewriter = rewriter
self.import_statement = import_statement
self.instance_name = instance_name
self.is_member = False
self.do_delete = False
self.method_text = ""
self.imports = ""
main(source_class, source_package, target_class, target_package, method_name, udb_path, *args, **kwargs)
Source code in codart\refactorings\move_method.py
def main(source_class: str, source_package: str, target_class: str, target_package: str, method_name: str,
udb_path: str, *args, **kwargs):
"""
"""
import_statement = None
if source_package != target_package:
import_statement = f"\nimport {target_package}.{target_class};"
instance_name = target_class.lower() + "ByCodArt"
db = und.open(udb_path)
method_map, class_ent = get_source_class_map(db, source_class)
if class_ent is None:
logger.error("Class entity is None")
return False
# Strong overlay precondition
# if class_ent.refs("Extend ~Implicit, ExtendBy, Implement"):
# logger.error("Class is in inheritance or implements an interface.")
# db.close()
# return False
# Check if method is static
method_ent = db.lookup(f"{source_package}.{source_class}.{method_name}", "Method")
if len(method_ent) >= 1:
method_ent = method_ent[0]
else:
logger.error("Entity not found.")
db.close()
return False
if method_ent.simplename() != method_name:
logger.error("Can not move method duo to duplicated entities.")
logger.info(f"{method_ent}, {method_ent.kindname()}")
db.close()
return False
if source_package == target_package and source_class == target_class:
logger.error("Can not move to self.")
db.close()
return False
is_static = STATIC in method_ent.kindname()
# Find usages
usages = {}
for ref in method_ent.refs("Callby"):
file = ref.file().longname()
if file in usages:
usages[file].append(ref.line())
else:
usages[file] = [ref.line(), ]
try:
src_class_file = db.lookup(f"{source_package}.{source_class}.java", "File")[0].longname()
target_class_file = db.lookup(f"{target_package}.{target_class}.java", "File")[0].longname()
except IndexError:
logger.error("This is a nested method.")
logger.info(f"{source_package}.{source_class}.java")
logger.info(f"{target_package}.{target_class}.java")
db.close()
return False
db.close()
# Check if there is an cycle
listener = parse_and_walk(
file_path=target_class_file,
listener_class=CheckCycleListener,
class_name=source_class
)
if not listener.is_valid:
logger.error(f"Can not move method because there is a cycle between {source_class}, {target_class}")
# db.close()
return False
# Propagate Changes
for file in usages.keys():
public_class_name = os.path.basename(file).split(".")[0]
is_in_target_class = public_class_name == target_class
parse_and_walk(
file_path=file,
listener_class=PropagateListener,
has_write=True,
method_name=method_name,
new_name=f"{instance_name}.{method_name}",
lines=usages[file],
is_in_target_class=is_in_target_class,
method_map=method_map,
)
# exit(-1)
# Do the cut and paste!
# Cut
listener = parse_and_walk(
file_path=src_class_file,
listener_class=CutMethodListener,
has_write=True,
class_name=target_class,
instance_name=instance_name,
method_name=method_name,
is_static=is_static,
import_statement=import_statement,
)
method_text = listener.method_text
# Paste
listener = parse_and_walk(
file_path=target_class_file,
listener_class=PasteMethodListener,
has_write=True,
method_text=method_text,
source_class=source_class,
method_map=method_map,
imports=listener.imports,
)
# Post-Paste: Reference Injection
parse_and_walk(
file_path=target_class_file,
listener_class=ReferenceInjectorAndConstructorListener,
has_write=True,
method_text=method_text,
source_class=source_class,
method_map=method_map,
imports=None,
has_empty_cons=listener.has_empty_cons,
)
# db.close()
return True