Push down field
Introduction
Although it was planned to use a field universally for all classes, in reality the field is used only in some subclasses. This situation can occur when planned features fail to pan out, for example. because of this, we push down the field from the superclass into its related subclass.
Pre and Post Conditions
Pre Conditions:
-
There should exist a corresponding child and parent in the project.
-
The field that should be pushed down must be valid.
-
The user must enter the package's name, class's name and the fields that need to be added.
Post Conditions:
-
The changed field's usages and callings will also change respectively.
-
There will be children and parents having their desired fields added or removed.
pushdown_field(source_filenames, package_name, superclass_name, field_name, class_names=[], filename_mapping=<function <lambda> at 0x0000015D74856280>)
The main function that does the process of pull up field refactoring. Adds the necessary fields to the subclasses and removes them from the superclass.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
source_filenames |
list |
A list of file names to be processed |
required |
package_name |
str |
The name of the package in which the refactoring has to be done (contains the superclass) |
required |
superclass_name |
str |
The name of the needed superclass |
required |
class_names |
list |
Name of the classes in which the refactoring has to be done (the classes to push down field from) |
[] |
field_name |
str |
Name of the field that has to be refactored |
required |
filename_mapping |
str |
Mapping the file's name to the correct format so that it can be processed |
<function <lambda> at 0x0000015D74856280> |
Returns:
Type | Description |
---|---|
bool |
No returns |
Source code in refactorings\pushdown_field.py
def pushdown_field(source_filenames: list,
package_name: str,
superclass_name: str,
field_name: str,
class_names: list = [],
filename_mapping=lambda x: (x[:-5] if x.endswith(".java") else x) + ".java") -> bool:
"""The main function that does the process of pull up field refactoring.
Adds the necessary fields to the subclasses and removes them from the superclass.
Args:
source_filenames (list): A list of file names to be processed
package_name (str): The name of the package in which the refactoring has to be done (contains the superclass)
superclass_name (str): The name of the needed superclass
class_names (str): Name of the classes in which the refactoring has to be done (the classes to push down field from)
field_name (str): Name of the field that has to be refactored
filename_mapping (str): Mapping the file's name to the correct format so that it can be processed
Returns:
No returns
"""
program = utils2.get_program(source_filenames, print_status=True)
if package_name not in program.packages \
or superclass_name not in program.packages[package_name].classes \
or field_name not in program.packages[package_name].classes[superclass_name].fields:
return False
superclass: utils_listener_fast.Class = program.packages[package_name].classes[superclass_name]
for mk in superclass.methods:
m: utils_listener_fast.Method = superclass.methods[mk]
for item in m.body_local_vars_and_expr_names:
if isinstance(item, utils_listener_fast.ExpressionName):
if ((len(item.dot_separated_identifiers) == 1
and item.dot_separated_identifiers[0] == field_name)
or (len(item.dot_separated_identifiers) == 2
and item.dot_separated_identifiers[0] == "this"
and item.dot_separated_identifiers[1] == field_name)):
return False
# all_derived_classes = [] # Not needed
other_derived_classes = []
classes_to_add_to = []
for pn in program.packages:
p: utils_listener_fast.Package = program.packages[pn]
for cn in p.classes:
c: utils_listener_fast.Class = p.classes[cn]
if ((c.superclass_name == superclass_name and c.file_info.has_imported_class(package_name, superclass_name)) \
or (package_name is not None and c.superclass_name == package_name + '.' + superclass_name)):
# all_derived_classes.append(c)
if len(class_names) == 0 or cn in class_names:
if field_name in c.fields:
return False
else:
classes_to_add_to.append(c)
else:
other_derived_classes.append(c)
# Check if the field is used from the superclass or other derived classes
for pn in program.packages:
p: utils_listener_fast.Package = program.packages[pn]
for cn in p.classes:
c: utils_listener_fast.Class = p.classes[cn]
has_imported_superclass = c.file_info.has_imported_class(package_name, superclass_name)
fields_of_superclass_type_or_others = []
for fn in c.fields:
f: utils_listener_fast.Field = c.fields[fn]
if (f.datatype == superclass_name and has_imported_superclass) \
or (package_name is not None and f.datatype == (package_name + '.' + superclass_name)):
fields_of_superclass_type_or_others.append(f.name)
if any((c.file_info.has_imported_class(o.package_name, o.name) and f.datatype == o.name)
or f.datatype == (o.package_name + '.' + o.name) for o in other_derived_classes):
fields_of_superclass_type_or_others.append(f.name)
for mk in c.methods:
m: utils_listener_fast.Method = c.methods[mk]
local_vars_of_superclass_type_or_others = []
for item in m.body_local_vars_and_expr_names:
if isinstance(item, utils_listener_fast.LocalVariable):
if (item.datatype == superclass_name and has_imported_superclass) \
or item.datatype == (package_name + '.' + superclass_name):
local_vars_of_superclass_type_or_others.append(item.identifier)
if any((c.file_info.has_imported_class(o.package_name, o.name) and item.datatype == o.name)
or item.datatype == (o.package_name + '.' + o.name) for o in other_derived_classes):
local_vars_of_superclass_type_or_others.append(item.identifier)
elif isinstance(item, utils_listener_fast.ExpressionName):
if item.dot_separated_identifiers[-1] == field_name \
and (
(len(item.dot_separated_identifiers) == 2)
or (len(item.dot_separated_identifiers) == 3 and item.dot_separated_identifiers[
0] == "this")
) and (
(item.dot_separated_identifiers[-2] in local_vars_of_superclass_type_or_others and len(
item.dot_separated_identifiers) == 2)
or item.dot_separated_identifiers[-2] in fields_of_superclass_type_or_others
):
return False
rewriter = utils2.Rewriter(program, filename_mapping)
field = superclass.fields[field_name]
if len(field.neighbor_names) == 0:
rewriter.replace(field.get_tokens_info(), "")
# Have to remove the modifiers too, because of the new grammar.
for mod_ctx in field.modifiers_parser_contexts:
rewriter.replace(utils_listener_fast.TokensInfo(mod_ctx), "")
else:
i = field.index_in_variable_declarators
var_ctxs = field.all_variable_declarator_contexts
if i == 0:
to_remove = utils_listener_fast.TokensInfo(var_ctxs[i])
to_remove.stop = utils_listener_fast.TokensInfo(var_ctxs[i + 1]).start - 1 # Include the ',' after it
rewriter.replace(to_remove, "")
else:
to_remove = utils_listener_fast.TokensInfo(var_ctxs[i])
to_remove.start = utils_listener_fast.TokensInfo(var_ctxs[i - 1]).stop + 1 # Include the ',' before it
rewriter.replace(to_remove, "")
is_public = "public" in field.modifiers
is_protected = "protected" in field.modifiers
modifier = ("public " if is_public else ("protected " if is_protected else ""))
for c in classes_to_add_to:
c_body_start = utils_listener_fast.TokensInfo(c.parser_context.classBody())
c_body_start.stop = c_body_start.start # Start and stop both point to the '{'
rewriter.insert_after(c_body_start, "\n " + modifier + field.datatype + " " + field_name \
+ ((" = " + field.initializer) if field.initializer is not None else "")
+ ";")
rewriter.apply()
return True
test_ant()
target_files = [ "tests/apache-ant/main/org/apache/tools/ant/types/ArchiveFileSet.java", "tests/apache-ant/main/org/apache/tools/ant/types/TarFileSet.java", "tests/apache-ant/main/org/apache/tools/ant/types/ZipFileSet.java" ]
Source code in refactorings\pushdown_field.py
def test_ant():
"""
target_files = [
"tests/apache-ant/main/org/apache/tools/ant/types/ArchiveFileSet.java",
"tests/apache-ant/main/org/apache/tools/ant/types/TarFileSet.java",
"tests/apache-ant/main/org/apache/tools/ant/types/ZipFileSet.java"
]
"""
ant_dir = "/home/ali/Desktop/code/TestProject/"
print("Success!" if pushdown_field(
utils2.get_filenames_in_dir(ant_dir),
"test_package",
"App",
"push_down",
[],
# lambda x: "tests/pushdown_field_ant/" + x[len(ant_dir):]
) else "Cannot refactor.")