UPDATE: There is an RFE (Request For Enhancement) in the Java Bug DB that you can vote for: RFE-6570766.
Please make this part of JDK 8!
The original need
I came around this issue in the end of 2004 when trying to parse Rinex files (GPS satellites data). Using JDK 5 I used enum to defined the file format. It was very clean, readable, and made the reading and writing of the parser quite easy. This system of using enum for static grammar declaration is actually used for the javac parser itself also.
The issue is that I needed to declared multiple enum for each type of lines and all enum entry contained the exact same data. So I ended up with something like:
public enum HeaderFormat {
info( "RINEX VERSION / TYPE","F9.2,11X,A1,19X" ,VERSION,TYPE),
creator( "PGM / RUN BY / DATE ","A20,A20,A20" ,PROGRAM,RUN_BY,DATE),
comment( "COMMENT ","A60" ,COMMENT),
endOfHeader("END OF HEADER ","60X" ,"");
private final RinexLineFormat _lineFormat;
HeaderFormat(String label, String format, String... fields) {
this._lineFormat = new RinexLineFormatImpl(label,format,fields);
}
public RinexLineFormat getLineFormat() {
return _lineFormat;
}
}
I needed to delegate all my enum static data to an external class, an I needed to delegate (copy/paste) this for all my XXXFormat enum.
I had one that look like:
I was not too happy about this but it was OK.I had one that look like:
public enum NavigationBodyFormat {
info(
"I2,1X,I2.2,1X,I2,1X,I2,1X,I2,1X,I2,F5.1,3D19.12",
SATELLITE_NUMBER,YEAR,MONTH,DAY,HOUR,MINUTE,SECOND,SV_CLOCK_BIAS,SV_CLOCK_DRIFT,SV_CLOCK_DRIFT_RATE),
brodacastOrbit1(
"3X,4D19.12",
IODE,CRS,DELTA_N,M0),
brodacastOrbit2(
"3X,4D19.12",
CUC,ECCENTRICITY,CUS,SQRT_A),
brodacastOrbit3(
"3X,4D19.12",
TOE,CIC,OMEGA_BIG,CIS);
private final RinexLineFormat _lineFormat;
NavigationBodyFormat(RinexLineFormat lineFormat) {
_lineFormat = lineFormat;
}
NavigationBodyFormat(String format, String... fields) {
this._lineFormat = new RinexLineFormatImpl(null,format,fields);
}
public RinexLineFormat getLineFormat() {
return _lineFormat;
}
public void read(String line, Map<String, Object> container) {
getLineFormat().read(line,container);
}
}
But, then we started to do a lot of JPA migrations and the need for property declaration and/or Method and Field references, and their usage in annotations was raised again.
When trying to solve this problem, I wrote jfrog reflect xref and found out that I needed to extends enum again.
Solution
I try to read all around this extends limitation Self types (aka type of this) from Peter Ahe, and for me it resumes to one point: ''You cannot extends a class that is self-referencing in its generics.'' I may be totally wrong here, but anyway I'll stick to that.
So, that's why
java.lang.Enum
cannot be extended. But if you allow only one extension that will replace the class Enum, it should be quite easy. So I will really like to see "abstract enum
"
appears in JDK 7. The Rinex parser code will then look like:public abstract enum EnumFormat {
private final String _label;
private final String _format;
private final String[] _fields;
private final RinexLineReader _lineReader;
EnumFormat(String label, String format, String fields) {
this._label = label;
this._format = format;
StringTokenizer fieldsStringTokenizer = new StringTokenizer(fields, ",");
List fieldsList = new ArrayList();
while (fieldsStringTokenizer.hasMoreTokens()) {
fieldsList.add(fieldsStringTokenizer.nextToken());
}
this._fields = fieldsList.toArray(new String[fieldsList.size()]);
_lineReader = new RinexLineReaderImpl(this);
}
EnumFormat(String label, String format, String... fields) {
this._label = label;
this._format = format;
this._fields = fields;
_lineReader = new RinexLineReaderImpl(this);
}
public String getLabel() {return _label;}
public String getFormat() {return _format;}
public String[] getFields() {return _fields;}
public RinexLineReader getLineReader() {return _lineReader;}
public void read(String line, Map<String, Object> container) {
getLineReader().read(line,container);
}
}
public enum HeaderFormat extends EnumFormat {and
info( "RINEX VERSION / TYPE","F9.2,11X,A1,19X" ,VERSION,TYPE),
creator( "PGM / RUN BY / DATE ","A20,A20,A20" ,PROGRAM,RUN_BY,DATE),
comment( "COMMENT ","A60" ,COMMENT),
endOfHeader("END OF HEADER ","60X" ,"");
}
public enum NavigationBodyFormat extends EnumFormat {
info(
"I2,1X,I2.2,1X,I2,1X,I2,1X,I2,1X,I2,F5.1,3D19.12",
SATELLITE_NUMBER,YEAR,MONTH,DAY,HOUR,MINUTE,SECOND,SV_CLOCK_BIAS,SV_CLOCK_DRIFT,SV_CLOCK_DRIFT_RATE),
brodacastOrbit1(
"3X,4D19.12",
IODE,CRS,DELTA_N,M0),
brodacastOrbit2(
"3X,4D19.12",
CUC,ECCENTRICITY,CUS,SQRT_A),
brodacastOrbit3(
"3X,4D19.12",
TOE,CIC,OMEGA_BIG,CIS);
}
Using it for property
So, how
"abstract enum
"
can solve some issues for Java 7 like:- Property in java: Pure Danger Tech >> Java 7 on property and especially Remy Forax Blog
- Beans Binding: Pure Danger Tech >> Java 7 on JSR 295
- Method and Field references, and their usage
- Method and Field references in annotations
First, I have to say that I really don't want to see "->" comes to Java. I used to be a C++ developer and removing from by brain the significance of "->" was a huge relief. So, please no more '''->'''
To target the above issues, I'm using Typesafe Reflections, which will provide me with 3 kind of
"abstract enum
"
: FieldDefinition, MethodDefinition, and PropertyDefinition.The
abstract enum java.lang.reflect.PropertyDefinition
will look like:public abstract enum PropertyDefinition {
private final Class modelClass;
private final String fieldName;
private final String getterMethodName;
private final String setterMethodName;
private Field reflectField;
private Method getterMethod;
private Method setterMethod;
PropertyDefinition() {
modelClass = getDeclaringClass().getEnclosingClass();
// some string mapping that can be driven by annotations for prefixes and so on
this.fieldName = name();
this.getterMethodName = "get"+StringUtils.capFirst(name());
this.setterMethodName = "set"+ StringUtils.capFirst(name());
}
public Field getField() {
...some field resolution
return reflectField;
}
public Method getSetterMethod() {
...some method resolution
return setterMethod;
}
public Method getGetterMethod() {
//...some method resolution
return getterMethod;
}
public Class getModelClass() {return modelClass;}
public String getFieldName() {return fieldName;}
// And then a nice list of helper and delegate methods like,
// They can aggregate annotations from fields and getters/setters methods
public Type getGenericType() {...}
public <t> T getAnnotation(Class<t> annotationClass) {...}
public Annotation[] getAnnotations() {...}
public boolean isAnnotationPresent(Class annotationClass) {...}
public Annotation[] getDeclaredAnnotations() {...}
// Here there is a need to type this
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException {...}
public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException {...}
}
Then inside my classes I can explicitly, implicit by annotation, or fully implicitly if Java allows it, have:
public class MyModel {
private String firstName;
private String lastName;
private int age;
// No mandatory getters or setters, just the one I want
public String getFirstName() {return firstName;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public static enum properties extends PropertyDefinition {
firstName, lastName, age;
}
}
With this ability, java property and binding declaration are possible, readable, don't add any keywords or ugly "->" and becomes very clean and concise. The only draw back is the ''implicit'' or compile time verification between the enum entries name and the actual fields. I keep thinking about this issue and here are my views:
I don't want all my fields or methods to be accessible via these XXXDefinition enum, I want to control the fields and methods that children class will expose for binding, The compiler can always do a sanity check.
Using property in Annotations
So now, to solve more binding problems:
Associations in JPA. How to you link to the property on the other side in a type safe way? Interceptor Binding. Passing a method reference in an annotation like @PostInsert(). And many more...
Since
"abstract enum"
public @interface OneToMany {
FieldDefinition inverseField() default BaseObject.fields.id;
}
And so now linking properties will be type safe:
public class Person {
@OneToMany(inverseField = Dog.fields.owner)
private Set<Dog> dogs;
}
public class Dog {
@ManyToOne(inverseField = Person.fields.dogs)
private Owner owner;
}
I like it...