6

I wonder if there's a simple way to to find all methods accessing a field directly. More precisely:

I'd like to assure that there's exactly one method writing a field and exactly one method reading it. All other accesses should use these two.

Background: When a field gets written, I need to record the fact somewhere I can do this easily using a generated setter, but I'd like to assure that I don't circumvent it somewhere.

It's for mobile rather than server, so I don't want / can't use interfaces or run-time bytecode rewriting...

I know, there's ASM, but AFAIK using it means more work that I'd like to spend. I hope, there's a better way.

Update

I didn't think of it, but have to state that code changes are allowed, but memory is tight. So encapsulating fields (e.g., Java FX style) or making a backup is too bad. There are quite a few fields, so actually anything requiring to touch them all is not good.

I could imagine parsing the sources, which is either complicated or prone to false positives as the same identifier has different meanings depending on the context. It may be even shadowed (e.g., in a nested class declaring an equally-named variable), but then I'd gladly change the code to avoid the problem.

Getting a structured information from the class file would surely be better.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • 1
    Do you want to be able to check this manually (while minimising the risk of human error), or are you looking for an automated way of checking it? Do you want to check this for all fields, or just some specific fields? – kaya3 Nov 24 '19 at 20:54
  • @kaya3 A list of method-field pairs (method writing the field directly) would be best, but ATM I'd be happy with knowing that there's exactly one write access per field in the whole file. However, I guess that obtaining this information is about as hard as getting the full list. – maaartinus Nov 24 '19 at 21:12
  • Are you strictly looking for a way to verify this property of existing code, or are you allowed to change the code in order that Java's own static checker will do the job for you? – kaya3 Nov 24 '19 at 21:15
  • @kaya3 It's my own code, so I can do everything, though I can't imagine what changes could help. – maaartinus Nov 24 '19 at 21:41
  • Well, since you want to encapsulate the behaviour of a field, but Java's unit of encapsulation is an object, there is a possible solution using separate objects for each field you want the behaviour to apply to. I'll write an answer so you can see if it meets your needs. – kaya3 Nov 24 '19 at 21:44
  • @kaya3 I'm afraid, this won't work for me. I really need to place some code into the class itself, so I can use the objects properly (there are no anemic data containers) and a solution including abstract methods increases the JAR size (relevant on mobile) and possibly lowers the speed (again, on mobile). – maaartinus Nov 24 '19 at 21:57
  • @maaartinus Can you move the fields to a new base class, where the fields are `private` and provide `protected` setter and getter to access them like you want? – Progman Nov 24 '19 at 22:30
  • You mention mobile. Will this be running in an Android device (which does not run an actual JVM), or will it be running on an actual JVM? This matters for post-compilation but non-runtime approaches. – tucuxi Nov 26 '19 at 15:44

3 Answers3

6

Encapsulation in Java is at the object or class level; the strictest access control modifier is private, but even then, every method within the same class can access the private fields. So, if you want to encapsulate behaviour of fields, this can be achieved by representing the fields as objects.

Here's a class representing a mutable field:

public class MyField<T> {
    private T value;

    MyField(T initialValue) {
        value = initialValue;
    }

    public T get() {
        return value;
    }

    public void set(T newValue) {
        // any logging goes here
        value = newValue;
    }
}

Then if you want this logging behaviour to apply to a field name, declare name as type MyField<String> instead of String:

public class Person {
    private final MyField<String> name;
    private final MyField<Integer> age;

    public Person(String name, int age) {
        this.name = new MyField<>(name);
        this.age = new MyField<>(age);
    }

    public String getName() {
        return name.get();
    }

    public int getAge() {
        return age.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }

    public void setAge(int age) {
        this.age.set(age);
    }
}

Advantages:

  • Since MyField.value is private, it's easy to verify across all instances that its value is never set without the logging behaviour being triggered.
  • The Java compiler's static checks are sufficient for checking that the field's value is only accessed via its get and set methods; there is no need for a separate verification stage.
  • You can apply this to just the fields you want the logging behaviour on.

Disadvantages:

  • Each of those MyField objects has some overhead in memory use.
  • The extra method calls to get and set will have some overhead in running time.
  • The extra code to call .get() is not much, but it could harm readability in more complex expressions.
kaya3
  • 47,440
  • 4
  • 68
  • 97
  • 1
    That's something like FX properties.... and it'd be a good solution, but unfortunately, I'll be for sure having memory problems and I definitely can't afford some 32+ bytes per field. :( – maaartinus Nov 24 '19 at 22:17
  • Sorry, I missed the bounty award. I'm not sure, if I'd award it to you or to you or to Pavlus for the answer mentioning checkstyle, which is probably the way I'll go. – maaartinus Dec 03 '19 at 07:52
3

I could imagine parsing the sources, which is either complicated or prone to false positives as the same identifier has different meanings depending on the context. It may be even shadowed (e.g., in a nested class declaring an equally-named variable), but then I'd gladly change the code to avoid the problem.

That's why modern IDEs exist -- they can analyze your code based on context instead of just grepping sources.

Take IntelliJ IDEA, for example: enter image description here

Usages of field, method class, or other things are one shortcut away: enter image description here Not only in your project, but in dependencies and other linked projects as well!

This is also available in Eclipse-based IDEs and NetBeans, probably others too.

If you don't use any IDE, you can still use linters like Checkstyle. Just mark the field deprecated and ignore warnings in methods where you know usage is permitted, more about ignoring Checkstyle warnings in this answer: https://stackoverflow.com/a/1706844

Pavlus
  • 1,651
  • 1
  • 13
  • 24
  • For sure, I use an IDE (Eclipse) and I know about this feature.... however, there are maybe some 10 such classes with maybe 20 fields each and I *really* don't want to check them all again and again. But thank you for the Checkstyle hint, I could indeed use the deprecation. And maybe I could write a plugin for it working without any deprecation. – maaartinus Nov 29 '19 at 20:30
  • @maaartinus In IntelliJ there is also *Structural Search* feature, that can help with it, like "search for `field access` prepended by `0 to 1` `instance` with field type `String`" (this is also will include access on instance returned form method, for example) – Pavlus Nov 29 '19 at 20:44
0

If i had to check if a property is or not modified by other method than the right setter, I would do like this :

public class Target {

    private int property1;
    private int property1Contoller ;

    public Target() {
    }

    public int getProperty1() {
        return property1;
    }

    public void setProperty1(int property1) {
        this.property1 = property1;
        this.setProperty1Contoller(property1);
    }

    private int getProperty1Contoller() {
        return property1Contoller;
    }

    private void setProperty1Contoller(int property1Contoller) {
        this.property1Contoller = property1Contoller;
    }

    public boolean checkProperty1() {
        return (this.getProperty1() == this.getProperty1Contoller());
    }
}

The idea : insert a shadow field with private setter, and this private setter of the shadow field is only called in the setter of the regular field. Hence, if the regular field is modified outside its setter, then regular field and shadow field will be different.

kevin ternet
  • 4,514
  • 2
  • 19
  • 27