Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A guaranteed way to get source-order of member fields at runtime?

I'm looking for a way to retrieve (at runtime) the fields of a class in source-order, so that I can perform my own "initialization processing" which is to be based on the order of declaration. I know that Javadoc for Class.getDeclaredFields() explicitly states that no order is guaranteed.

Some answers on SO point to Javassist but I can find no evidence that javassist has any such guarantee in the absence of line number information.

Yet this "source-order" is used by the Java compiler, as this code does not compile:

private int a = 10 * b;
private int b = 5;

Clearly, the value of b isn't known at the moment a is being declared.

This initialization order must also be present in the bytecode since at runtime the initialization must happen in the same order (granted, this is only a requirement for these edge cases :-( Yet this leads me to think that the natural thing would be to store the source order inside the .class file.

Questions:

  1. How does the JVM/Byte Code go about initializing member fields in declared order, and can this information perhaps be used to reconstruct source-order of fields?

  2. Is there any other guaranteed way of achieving the same. Third-party tools like Javassist are OK but it must be "guaranteed" or at least "guaranteed under specific conditions".

  3. Is there any specific Java implementation that does guarantee order on Class.getDeclaredFields() (perhaps under specific conditions (which ones))?

For your information, I need the source order to reconstruct behavior of a legacy language where order was important. I don't like adding order explicitly e.g. by adding arrays or annotations, as I want to keep the source as readable as possible.

-- edit -- An important note may be that the fields I need to "traverse" will all be annotated, e.g. @MyComplexType(len = 4). The parent class will need this meta-information to construct a kind of memory map. Yet I don't want to clutter this annotation with ordering information, as I find this hinders readability and maintainability.

like image 889
geert3 Avatar asked Sep 28 '22 12:09

geert3


3 Answers

Concerning your second and third question, it is only possible to retrieve the fields in order using a kind of dirty hack:

In the bytecode, the fields of the class file are not stored in order, and neither are the methods. I don't know why that is the case (even though I made my own JVM compiler), but I believe the Java compiler just decides to do that. Class.getDeclaredFields returns the fields in the order in which they were read from the bytecode, which is why it states that no order is guaranteed.

If you still want to get them in order, I would try the following: You use a bytecode parser library such as Javassist or ASM to read the class file, and skip everything but the constructors (and the static {} if you also want to sort static fields). As soon as you encounter a PUTFIELD or PUTSTATIC instruction whose owner is the class you are inspecting, you get the current line which is available through debug information stored in the bytecode, and use it to sort the fields. The problem with this technique is its inefficiency and the fact that it relies on Line Number attributes, which are not always present in class files. Furthermore, you will only find PUT* instructions for fields which are explicitly initialized, default ones such as

protected int modifiers;

are not initialized by the compiler, so no instruction and thus no line number information is available in the bytecode. In this case or when there is no LineNumber attributes in general, you are unfortunately out of luck. At that point, the only solution I could come up with is to read the actual source code of the class.

Depending on the class you are trying to inspect, you might have trouble getting the actual bytecode of the class, but that is a question in and on itself.

like image 133
Clashsoft Avatar answered Oct 18 '22 02:10

Clashsoft


This initialization order must also be present at runtime, for the same reason.

The order the fields are declared and the order they are initialized don't have to have anything to do with one another. There are exceptions as you mention but this is not a requirement.

So how does the JVM go about initializing member fields in declared order

The JVM only sets fields to their un-initialized 0, null, and false values. Nothing else.

The only reason values have anything other than these is because there is byte code which sets each field to the value you set it to. i.e. no magic happens.

can this information perhaps be used to reconstruct source-order of fields?

You could infer what the order might be, based on the order the fields are set in the constructor. However, this is an assumption. You have a better chance of assuming the order the fields appear in the class file is the order they appeared in the source.

If there is debug information, this can be used to get actual line numbers, however the JVM ignores this information. Note: this only tells you the lines where the fields were initialised, which might not be the order they are declared.

e.g.

class A {
   int a;
   int b;
   int c;
   int d;

   A() {
      d = 1;
      //c not initialised
      b = 2;
      a = 3;
  }

}

So you can see the order the fields are initialised in the constructor don't match the order of declaration. In fact c won't be initialised at all and be left with the default 0

like image 24
Peter Lawrey Avatar answered Oct 18 '22 02:10

Peter Lawrey


If you want to retrieve information at runtime about a field or method that the compiler is not storing for you then use custom annotations. For example,

Declare your annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldOrder {
    public int order() default 0;
}

Annotate your fields:

@FieldOrder{order=1}
int field1 = 5;

@FieldOrder{order=2}
long field2 = field1 * 12;

Use reflection to retrieve your Field objects and the annotations on them.

final Class<MyClass> obj = MyClass.class;
if (obj.isAnnotationPresent(FieldOrder.class)) {
  for (final Method method : obj.getDeclaredMethods()) {
    if (method.isAnnotationPresent(FieldOrder.class)) {
      final Annotation annotation = method.getAnnotation(FieldOrder.class);
      final FieldOrder fieldOrder = (FieldOrder) annotation;
      final int order = fieldOrder.order();
      // do something with the field; 
      // add to sorted collection using field order?
    }
  }
}
like image 1
Rick Ryker Avatar answered Oct 18 '22 03:10

Rick Ryker