javabytecodebcellcom

Determining the LCOM4 (Lack of Cohesion in Methods) by parsing the Java Bytecode using BCEL


I have built a program, which takes in a provided ".class" file and parses it using the BCEL, but I'm a bit lost when it comes to using the resulting object to determine the LCOM4 value. I've scoured the whole web, trying to find a proper tutorial about it, but I've been unable so far (I've read the whole javadoc regarding the BCEL as well). So I would like some help with this issue, as in some detailed tutorials or code snippets that would help me understand on how to do it.


Solution

  • OK, let's define a class to represent a group of fields and methods:

    public class Group {
        private final Set<String> fields = new HashSet<>();
        private final Set<String> methods = new HashSet<>();
    
        public Group addFields(String...fields) {
            for (String field: fields) {
                this.fields.add(field);
            }
            return this;
        }
    
        public Group addMethods(String... methods) {
            for (String method: methods) {
                this.methods.add(method);
            }
            return this;
        }
    
        public int fields() {
            return fields.size();
        }
    
        public int methods() {
            return methods.size();
        }
    
        public boolean intersects(Group other) {
            for (String field: other.fields) {
                if (fields.contains(field)) {
                    return true;
                }
            }
            for (String method: other.methods) {
                if (methods.contains(method)) {
                    return true;
                }
            }
            return false;
        }
    
        public void merge(Group other) {
            fields.addAll(other.fields);
            methods.addAll(other.methods);
        }
    
        @Override
        public String toString() {
            return "Group{" + "fields=" + fields + ", methods=" + methods + '}';
        }
    }
    

    We start the process by filling the list of groups with a group for each field defined in the class, then, for each method we build a group with the fields and the methods referenced in the code, then with reduce the list of group by merging and removing each group that intersects the group of the method.

    Here's the java code to load the groups of the class. LCOM4 is groups.size():

    private List<Group> loadGroups(File file) throws IOException {
        try (InputStream in = new FileInputStream(file)) {
            ClassParser parser = new ClassParser(in, file.getName());
            JavaClass clazz = parser.parse();
            String className = clazz.getClassName();
            ConstantPoolGen cp = new ConstantPoolGen(clazz.getConstantPool());
            List<Group> groups = new ArrayList<Group>();
            for (Field field: clazz.getFields()) {
                groups.add(new Group().addFields(field.getName()));
            }
            for (Method method: clazz.getMethods()) {
                Group group = new Group().addMethods(method.getName());
                Code code = method.getCode();
                InstructionList instrs = new InstructionList(code.getCode());
                for (InstructionHandle ih: instrs) {
                    Instruction instr = ih.getInstruction();
                    if (instr instanceof FieldInstruction) {
                        FieldInstruction fld = (FieldInstruction)instr;
                        if (fld.getClassName(cp).equals(className)) {
                            group.addFields(fld.getFieldName(cp));
                        }
                    } else if (instr instanceof InvokeInstruction) {
                        InvokeInstruction inv = (InvokeInstruction)instr;
                        if (inv.getClassName(cp).equals(className)) {
                            group.addMethods(inv.getMethodName(cp));
                        }
                    }
                }
                if (group.fields() > 0 || group.methods() > 1) {
                    int i = groups.size();
                    while (i > 0) {
                        --i;
                        Group g = groups.get(i);
                        if (g.intersects(group)) {
                            group.merge(g);
                            groups.remove(i);
                        }
                    }
                    groups.add(group);
                }
            }
            return groups;
        }
    }