AspectJ is not intended to be used to patch up bad application design. I could easily tell you how to write some cheap aspect code using reflection in order to call the right getter for the current language, but
- it is ugly,
- it is slow,
- having one property + getter per language and encoding the language ID in the method name does not scale. If you want to add another language, you will have to add fields to dozens or hundreds of entities.
Maybe you should consider using a standard means like resource bundles for your property names. This way you can change text constants or even add new languages without recompiling the code. Because internationalisation is a cross-cutting concern, you can then still use AspectJ in order to declare access methods for your translations via ITD (inter-type definition) or by some other means, if you want to keep them out of the core code. That way your core code could be totally language-agnostic.
Update:
Anyway, if you want it so much, here is a sample showing you what you can do with AOP, namely with AspectJ. The solution proposed by user gknicker is similar, but it only works for one class. Mine keeps the code separate in an aspect and can apply it to many classes at once.
The plan is to manually annotate each entity class containing multi-language field captions with a marker annotation. I made up one called @Entity
. Alternatively, you could also determine the target classes by their superclass or by a class or package name pattern, AspectJ is very powerful in this regard. As I said, it is just an example.
In the next step we will define an aspect which does the following:
- Define an interface
LocalisedCaption
.
- Define a few sample default methods using reflection magic in order to
- get the localised caption for one field,
- get all localised captions for all defined entity fields,
- get a map of localised captions and field values for an entity instance.
- Use ITD (inter-type declaration) in order to make all
@Entity
classes implement that interface and thus inherit its methods.
Last, but not least, we will use the new methods in from sample application.
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity {}
package de.scrum_master.app;
@Entity
public class Person {
public static final String firstNameEN = "first name";
public static final String firstNameFR = "prénom";
public static final String firstNameRU = "и́мя";
public static final String lastNameEN = "last name";
public static final String lastNameFR = "nom de famille";
public static final String lastNameRU = "фами́лия";
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
package de.scrum_master.aspect;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityCaptionLocaliser {
public interface LocalisedCaption {
String getCaption(String attributeName);
}
declare parents :
@Entity * implements LocalisedCaption;
public String LocalisedCaption.getCaption(String attributeName)
throws ReflectiveOperationException
{
String fieldName = attributeName + Application.locale;
Field field = getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (String) field.get(this);
}
public Map<String, String> LocalisedCaption.getAllCaptions()
throws ReflectiveOperationException
{
Map<String, String> captions = new HashMap<>();
for (Field field : getClass().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()))
continue;
String attributeName = field.getName();
captions.put(attributeName, getCaption(attributeName));
}
return captions;
}
public Map<String, Object> LocalisedCaption.getCaptionValuePairs()
throws ReflectiveOperationException
{
Map<String, Object> captions = new HashMap<>();
for (Field field : getClass().getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
String attributeName = field.getName();
captions.put(getCaption(attributeName), field.get(this));
}
return captions;
}
}
package de.scrum_master.app;
public class Application {
public static String locale = "EN";
public static void main(String[] args) throws Exception {
Person albert = new Person("Albert", "Einstein");
System.out.println("Showing localised captions for " + albert + ":");
locale = "EN";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
locale = "FR";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
locale = "RU";
System.out.println(albert.getAllCaptions());
System.out.println(albert.getCaptionValuePairs());
}
}
Console output for Application.main
:
Showing localised captions for Person [firstName=Albert, lastName=Einstein]:
{lastName=last name, firstName=first name}
{first name=Albert, last name=Einstein}
{lastName=nom de famille, firstName=prénom}
{nom de famille=Einstein, prénom=Albert}
{lastName=фами́лия, firstName=и́мя}
{фами́лия=Einstein, и́мя=Albert}