Pull-up constructor
Introduction
When subclasses grow and get developed separately, your code may have constructors that perform similar work. Pull up constructor refactoring removes the repetitive method from subclasses and moves it to a superclass.
Pre and post-conditions
Pre-conditions:
-
The source package, class and constructor should exist.
-
The order of the params in the constructor should be equal in the child classes.
-
empty package name is addressable using "".
Post Conditions:
No specific post-condition
PullUpConstructorListener (JavaParserLabeledListener)
Source code in codart\refactorings\pullup_constructor.py
class PullUpConstructorListener(JavaParserLabeledListener):
"""
"""
def __init__(self, rewriter: TokenStreamRewriter, is_father: bool, class_name: str, has_father_con: bool,
common_sets: [], params: str):
"""
"""
self.rewriter = rewriter
self.is_father = is_father
self.has_father_con = has_father_con
self.class_name = class_name
self.common_sets = common_sets
self.params = params
self.in_con = False
self.delete = False
def exitClassDeclaration(self, ctx: JavaParserLabeled.ClassDeclarationContext):
if self.is_father:
code = ""
for var in self.common_sets:
code += f"this.{var} = {var};\n"
if self.has_father_con:
pass
else:
self.rewriter.insertBeforeToken(
token=ctx.stop,
text=f"public {self.class_name}({self.params})" + "{\n" + code + "}"
)
def enterConstructorDeclaration(self, ctx: JavaParserLabeled.ConstructorDeclarationContext):
if not self.is_father:
self.in_con = True
def exitConstructorDeclaration(self, ctx: JavaParserLabeled.ConstructorDeclarationContext):
is_valid = False
for i in self.common_sets:
if i in ctx.getText():
is_valid = True
break
if self.is_father and self.has_father_con and is_valid:
code = ""
for var in self.common_sets:
code += f"this.{var} = {var};\n"
self.rewriter.insertBeforeToken(
token=ctx.stop,
text=code
)
self.in_con = False
def enterExpression1(self, ctx: JavaParserLabeled.Expression1Context):
if self.in_con:
identifier = str(ctx.IDENTIFIER())
if identifier in self.common_sets:
self.delete = True
def exitExpression21(self, ctx: JavaParserLabeled.Expression21Context):
if self.delete:
self.rewriter.delete(
program_name=self.rewriter.DEFAULT_PROGRAM_NAME,
from_idx=ctx.start.tokenIndex,
to_idx=ctx.stop.tokenIndex + 1
)
self.delete = False
__init__(self, rewriter, is_father, class_name, has_father_con, common_sets, params)
special
Source code in codart\refactorings\pullup_constructor.py
def __init__(self, rewriter: TokenStreamRewriter, is_father: bool, class_name: str, has_father_con: bool,
common_sets: [], params: str):
"""
"""
self.rewriter = rewriter
self.is_father = is_father
self.has_father_con = has_father_con
self.class_name = class_name
self.common_sets = common_sets
self.params = params
self.in_con = False
self.delete = False
get_cons(program, packagename, superclassname, class_name)
A function to complete the Pull-up constructor refactoring by finding all the classes with similar constructors.
Source code in codart\refactorings\pullup_constructor.py
def get_cons(program: Program, packagename: str, superclassname: str, class_name: str):
"""
A function to complete the Pull-up constructor refactoring by finding all the classes with similar constructors.
"""
extendedclass = []
removemethods = {}
removemethods1 = []
removemethods3 = {}
mets = program.packages[packagename].classes[class_name].methods
met = []
methodkey = ""
for methodName, method in mets.items():
if method.is_constructor:
met = method
methodkey = methodName
break
body_text_method = met.body_text
parammethod = met.parameters
for package_name in program.packages:
package = program.packages[package_name]
for class_ in package.classes:
_class = package.classes[class_]
if _class.superclass_name == superclassname:
extendedclass.append(_class)
i = 0
for d in extendedclass:
class_ = extendedclass[i]
i = i + 1
for mk in class_.methods:
m_ = class_.methods[mk]
m = mk[:mk.find('(')]
if m_.body_text == body_text_method and m_.parameters == parammethod and m_.is_constructor:
if class_.name not in removemethods:
removemethods[class_.name] = [methodkey]
else:
removemethods[class_.name].append(methodkey)
elif m_.is_constructor:
listBody_text = body_text_method.replace("{", "").replace("}", "").split(";")
listm_body = m_.body_text.replace("{", "").replace("}", "").split(";")
s1 = set(listBody_text)
s2 = set(listm_body)
if s2.issubset(s1):
removemethods1.append(diff_lists(listBody_text, listm_body))
if class_.name not in removemethods:
removemethods[class_.name] = [mk]
else:
removemethods[class_.name].append(mk)
elif s1.issubset(s2):
removemethods1.append(diff_lists(listm_body, listBody_text))
if class_.name not in removemethods:
removemethods[class_.name] = [mk]
else:
removemethods[class_.name].append(mk)
else:
a = diff_lists(listBody_text, listm_body)
if class_.name not in removemethods3:
removemethods3[class_.name] = [a]
else:
removemethods3[class_.name].append(a)
if class_.name not in removemethods:
removemethods[class_.name] = [mk]
else:
removemethods[class_.name].append(mk)
removemethods[class_name] = [methodkey]
return removemethods, removemethods1
main(udb_path, source_package, target_class, class_names, *args, **kwargs)
Source code in codart\refactorings\pullup_constructor.py
def main(udb_path, source_package, target_class, class_names: list, *args, **kwargs):
"""
"""
if len(class_names) < 2:
logger.error("class_names is empty.")
return False
db = und.open(udb_path)
parent_cons = []
# Check children
parent = db.lookup(f"{target_class}", "Public Class")
if len(parent) != 1:
logger.error("Count of target class is not 1.")
db.close()
return False
parent = parent[0]
parent_file = db.lookup(f"{target_class}.java", "File")[0].longname()
for i in parent.ents("Define", "Constructor"):
parent_cons.append(i.parameters())
# Find constructor entities group by signature
constructors = {}
for child in class_names:
cons = db.lookup(f"{child}.{child}", "Constructor")
for con in cons:
if con.parent() is not None:
if source_package not in con.parent().longname():
logger.error("Source package does not match.")
db.close()
return False
parameters = con.parameters()
if parameters in constructors:
constructors[parameters].append(con)
else:
constructors[parameters] = [con]
# Find common statements
for k in constructors:
meta_data = {
parent_file: {'is_father': True, 'has_father_con': k in parent_cons, 'class_name': parent.simplename()},
}
con = constructors[k][0]
ents = []
for ref in con.refs("Set"):
data = {'is_father': False, 'has_father_con': k in parent_cons, 'class_name': con.parent().simplename()}
if ref.file().longname() not in meta_data.keys():
meta_data[ref.file().longname()] = data
if target_class in ref.ent().longname():
ents.append(ref.ent().simplename())
for i in range(1, len(constructors[k])):
con2 = constructors[k][i]
for ref in con2.refs("Set"):
data = {'is_father': False, 'has_father_con': k in parent_cons,
'class_name': con2.parent().simplename()
}
if ref.file().longname() not in meta_data.keys():
meta_data[ref.file().longname()] = data
if target_class in ref.ent().longname():
ents.append(ref.ent().simplename())
ents = [item for item, count in collections.Counter(ents).items() if count > 1]
if len(meta_data.keys()) > 1:
for file_name in meta_data:
data = meta_data[file_name]
parse_and_walk(
file_name,
PullUpConstructorListener,
has_write=True,
is_father=data['is_father'],
has_father_con=data['has_father_con'],
common_sets=ents,
class_name=data['class_name'],
params=k
)
db.close()
return True