I've made some custom beans that implement the Map interface for easy access to arbitrary data in my JSP.
Example:
${person["cellPhoneNumber"]}
This extra data may or may not be added on the back end so a Map seems like a nice flexible way to store this.
The problem arises when I try to use the getters on my bean. My Person class has a getName() method. When using the following in my JSP, the Map.get() method is called instead of my method.
${person.name}
Is there a way to get around this call to Map's get("name") and call getName() instead?
Here is my basic (stripped down) Java class:
class Person implements Map
{
private HashMap<String, Object> myMap;
private String name;
public Object get(Object key)
{
return myMap.get(key);
}
public String getName()
{
return this.name;
}
}
Using JSTL 1.1
Looks like servlet container treats every class implementing Map interface as a map and completely discards other methods, falling back to by key lookup. I see two solutions:
1) get(Object key) should be aware of normal properties:
class Person implements Map
{
private HashMap<String, Object> myMap;
private String name;
public Object get(Object key)
{
switch(key) {
case "name": return getName();
default: myMap.get(key)
}
}
public String getName()
{
return this.name;
}
}
This is a bit clumsy and not very scalable. Also it's probably easier to either use reflection and find all fields automatically or convert your bean to map (see: How to convert a Java object (bean) to key-value pairs (and vice versa)?). Even worse.
2) Expose your map as a special bean attribute:
class Person
{
private HashMap<String, Object> optional;
private String name;
public Map<String, Object> getOptional()
{
return optional;
}
public String getName()
{
return this.name;
}
}
Then your EL expression looks like this:
${person.optional['cellPhoneNumber']}
This is a much better approach since:
it's faster and more scalable
better represents your intent
it's a better design (as highlighted by duffymo in comments) - Person has optional attributes like cell phone number. Person is not a ``Map` of optional attributes.
Related
I want to define a DAO over a DynamoDB that has 20+ fields. In Java, I can use Lombok and do something like this to avoid a bunch of boilerplate code.
#Setter
#Getter
#DynamoDBTable("MyTable")
public class MyDAO {
//FIELD_1, FIELD_2, FIELD_3 defined as static final String elsewhere
#DynamoDBAttribute(attribute = FIELD_1)
private final String field1;
#DynamoDBAttribute(attribute = FIELD_2)
private final Long field2;
#DynamoDBAttribute(attribute = FIELD_3)
private final int field3;
...
}
The problem is if I had methods that did something for each field like the following, I would end up duplicating the code over and over again, because the setters in step 2 would be different and the field names in step 3 would be different (i.e. setField1 for the first and setField2 for the second).
public void addField1(String key, String field1Value) {
//Wrap some retry logic and error handling around the following
// 1. get DAO for key
// 2. set FIELD_1 to field1Value in DAO if not set
// 3. put DAO in DynamoDB using attribute name FIELD_1
}
public void addField2(String key, Long field2Value) {
//Wrap some retry logic and error handling around the following
// 1. get DAO for key
// 2. set FIELD_2 to field2Value in DAO if not set
// 3. put DAO in DynamoDB using attribute name FIELD_2
}
Ideally, I would like to have something like the addField method below, with all the retry logic so I don't have to duplicate everything for every field.
private void addField(String fieldName, String key, Object value);
public void addField1(String key, String field1Value) {
addField(FIELD_1, key, (Object) field1Value);
}
I've tried a map between field names and BiConsumers as such
Map<String, BiConsumer<MyDAO, Object>> setterMap =
new HashMap<String, BiConsumer<MyDAO, Object>>(){{
put(FIELD_1, MyDAO::setField1);
put(FIELD_2, MyDAO::setField2);
}};
private void addField(String fieldName, String key, Object value) {
...
// 2. Use setterMap.get(fieldName).accept(value);
...
}
The problem is I get an error saying that I cannot cast BiConsumer<MyDAO, String> to BiConsumer<MyDAO, Object>.
Is it the only way to do it - to create a separate map and method for each type? Or is there a more elegant way to do this?
Well, I don't think it's possible to do it using a Map if you want to preserve type safety. Instead, here's what I would do:
1) I'd create a special class like that:
#AllArgsConstructor
#Getter
final class FieldDefinition<T> {
private final String name;
private final BiConsumer<MyDAO, T> setter;
}
2) Then, I'd create constants in MyDAO (or, even better, in some helper object near MyDAO) like that:
static final FieldDefinition<String> FIELD_1_DEF = new FieldDefinition<>(FIELD_1, MyDAO::setField1);
3) Finally, I'd create the following type-safe addField method:
private <T> void addField(FieldDefinition<T> fieldDefinition, String key, T value) {
// ...
fieldDefinition.getSetter().accept(this, value);
// ...
}
which whould be called like that:
myDao.addField(FIELD_1_DEF, key, value);
Dynamic selection of methods is really not a good fit for functional interfaces. Parameterizing your code around method selection is better done with reflection, rather than with functional interfaces.
The main reason making it difficult to implement your logic using the BiConsumer interface is that you would technically still have to provide static implementations for it, for each field (whether using lambdas, method references, or classes...).
Here's an example reflection-based implementation:
private void addField(String fieldName, String key, Object value) {
MyDAO.class.getDeclaredField(fieldName).set(value, key);
}
So I'd just make setterMap a map of key to field name mapping, and use it like so:
private void addField(String key, Object value) {
String field = setterMap.get(key);
MyDAO.class.getDeclaredField(field).set(value, key);
}
I have the following Java enum:
public enum CertificateType {
EMAIL("Email"),
NATURAL_QUALIFIED("Qualified"),
PSEUDONYMOUS_QUALIFIED("Qualified");
public final String NAME;
private CertificateType( final String name ) {
this.NAME = name;
}
}
What I would like to do is turn that enum into a Map[enum as String, enum.NAME] to use in a Play Framework #select function. By enum as String, I mean "EMAIL", or "NATURAL_QUALIFIED"
For a list of objects, I'd use the .map function, like so:
Organization.all.map(org => (org.id.toString, org.name))(collection.breakOut)
But I don't think I can modify that to work with an enum.
How can I accomplish this?
EDIT: The annotations are nothing to do with Scala, but are ORM annotations for Play. The code above is a Java enum. By enum value, I mean each enum, as in EMAIL, NATURAL_QUALIFIED, etc.
An example key=>value pair would be "EMAIL"=>"Email"
EDIT 2: I have removed the annotations on the enums for clarity.
CertificateType.values.map(e => (e.name -> e.NAME) ).toMap
Stripping out the scala part that I'm not familiar with, could you not do something like this?
public enum CertificateType {
EMAIL("Email"),
NATURAL_QUALIFIED("Qualified"),
PSEUDONYMOUS_QUALIFIED("Qualified");
public final String NAME;
private static Map<CertificateType, String> map = new HashMap<CertificateType, String>();
private CertificateType( final String name ) {
this.NAME = name;
map.put(this, this.NAME);
}
public Map<CertificateType, String> getMap()
{ //Return a copy so that any modifications a
//user might make won't propagate back to the stored map
return new HashMap<CertificateType, String>(map);
}
}
I want to get a specific enum based on its field value.
Enum:
public enum CrimeCategory {
ASBO ("Anti Social Behaviour"),
BURG ("Burglary"),
CRIMDAM ("Criminal Damage And Arson"),
DRUGS ("Drugs"),
OTHTHEFT ("Other Theft"),
PUPDISOR ("Public Disorder And Weapons"),
ROBBERY ("Robbery"),
SHOPLIF ("Shoplifting"),
VEHICLE ("Vehicle Crime"),
VIOLENT ("Violent Crime"),
OTHER ("Other Crime");
private String category;
private CrimeCategory (String category) {
this.category = category;
}
public String returnString() {
return category;
}
}
Getting a new Enum:
aStringRecivedFromJson = "Anti Social Behaviour"
CrimeCategory crimeCategoryEnum;
crimeCategoryEnum = CrimeCategory.valueOf(aStringRecivedFromJson);
I have been trying to work out a way for the above bring back a an enum so it can be passed stored in a HashMap with other Crime information.
Expected Result: ASBO
For reference, here is an alternative solution with a HashMap:
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> map = new HashMap<>(values().length, 1);
static {
for (CrimeCategory c : values()) map.put(c.category, c);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static CrimeCategory of(String name) {
CrimeCategory result = map.get(name);
if (result == null) {
throw new IllegalArgumentException("Invalid category name: " + name);
}
return result;
}
}
Add a static method to the CrimeCategory enum:
public static CrimeCategory valueOf(String name) {
for (CrimeCategory category : values()) {
if (category.category.equals(name)) {
return category;
}
}
throw new IllegalArgumentException(name);
}
Static factory methods that return an enum constant based on the value of an instance field take on one of the two forms described in the other answers: a solution based on iterating the enum values, or a solution based on a HashMap.
For enums with a small number of constants, the iterative solution should be as performant as the HashMap solution (which requires calculation of the hash code, matching it to a bucket, and assuming that there will be no hash collisions).
For larger enums, the map-based solution will be more performant (but requires storage space in memory). However, if the factory method is invoked infrequently then the overall performance improvement by using a map could still be immeasurably small.
The overall decision to use an iterative lookup or a map-based lookup for the static factory method will ultimately depend on your requirements and the environment. It is never wrong to start with an iterative lookup and then change to a map-based implementation if profiling shows an actual performance problem.
Lastly, since Java 8, the Streams API enables a pipeline-based solution for mapping (that should have performance similar to the iterative solution). For example, say that you want to create an interface that you could use on any enum class to express your intent that it should be matchable by one of its instance fields. Let's call this interface Matchable. This interface defines a method which returns the instance field on which you want to match (eg. getField()). This interface can also define a static factory method to return a constant from any implementing enum class:
interface Matchable {
Object getField();
public static <E extends Enum<E> & Matchable> E forToken(Class<E> cls, Object token) {
return Stream.of(cls.getEnumConstants())
.filter(e -> e.getField().equals(token))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown token '" +
token + "' for enum " + cls.getName()));
}
}
Now, any enum class that you define that implements Matchable can use the Matchable.forToken() static factory method to find the enum constant whose instance field value matches the supplied parameter.
The generic type declaration E extends Enum<E> & Matchable assures that the type token passed to the method as a parameter will be for an enum class that implements Matchable (otherwise the code won't compile).
Assylias answer is great. Though I would return an Optional from the factory method to let the client deal with the situation when enum could not be found (of course throwing IllegalArgumentException might be better if you use this enum internally and you think that invoking this method with wrong argument will never happen - this is your choice).
And also I would wrap the Map into unmodifiable wrapper to not modify it somewhere by accident inside your enum (the Map is private but someone could modify it later when adding new functionalities - it will at least force to think about it) :
enum CrimeCategory {
ASBO("Anti Social Behaviour"),
BURG("Burglary"),
CRIMDAM("Criminal Damage And Arson"),
DRUGS("Drugs"),
OTHTHEFT("Other Theft"),
PUPDISOR("Public Disorder And Weapons"),
ROBBERY("Robbery"),
SHOPLIF("Shoplifting"),
VEHICLE("Vehicle Crime"),
VIOLENT("Violent Crime"),
OTHER("Other Crime");
private static final Map<String, CrimeCategory> MAP;
static {
Map<String, CrimeCategory> crimeCategoryMap = Arrays.stream(values())
.collect(toMap(cg -> cg.category, e -> e));
MAP = Collections.unmodifiableMap(crimeCategoryMap);
}
private final String category;
private CrimeCategory(String category) {
this.category = category;
}
public static Optional<CrimeCategory> of(final String name) {
return Optional.ofNullable(MAP.get(name));
}
}
Having Student Class.
Class Student{
String _name;
....
....
public Student(){
}
}
is there any possibility to add dynamic attributes to Student Object?
without extending the student class.
In short, yes it is possible to modify bytecode at runtime, but it can be extremely messy and it (most likely) isn't the approach you want. However, if you decide to take this approach, I recommend a byte code manipulation library such as ASM.
The better approach would be to use a Map<String, String> for "dynamic" getters and setters, and a Map<String, Callable<Object>> for anything that isn't a getter or setter. Yet, the best approach may be to reconsider why you need dynamic classes altogether.
public class Student {
private Map<String, String> properties = new HashMap<String, String>();
private Map<String, Callable<Object>> callables = new HashMap<String, Callable<Object>>();
....
....
public String getProperty(String key) {
return properties.get(key);
}
public void setProperty(String key, String value) {
properties.put(key, value);
}
public Object call(String key) {
Callable<Object> callable = callables.get(key);
if (callable != null) {
return callable.call();
}
return null;
}
public void define(String key, Callable<Object> callable) {
callables.put(key, callable);
}
}
As a note, you can define void methods with this concept by using Callable and returning null in it.
You could get into bytecode manipulation but that way madness lies (especially for the programmer who has to maintain the code).
Store attributes in a Map<String,String> instead.
Although you can do that with some tricky, and complex way that others have suggested..
But you can sure have your attributes in some data structure(An appropriate one will be a Map).. Since you can modify your existing attributes, so can be done with you Data Structure. You can add more attributes to them.. This will be a better approach..
This is my firs titme dealing with Type Maps and everytime i try to map the node to my Actual Type Object which has a custom property key as FooType with a Set<Integer> values. Here is how my Object looks like
public class Foo {
private String some;
Map<FooTypes,Set<Integer>> foos;
public Map<FooTypes, Set<Integer>> getFoos() {
return foos;
}
public void setFoos(Map<FooTypes, Set<Integer>> map) {
this.foos = map;
}
public String getSome() {
return some;
}
public void setSome(String some) {
this.some = some;
}
}
public class FooTypes {
private String name;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Now everytime i try to use the mapper to read the value :-
List <Foo> response = mapper.readValue("/home/foo/foo.json",List.class);
I get an error stating that :-
Can not find a (Map) Key deserializer for type [simple type, class cruft.FooTypes]
Can someone tell me on how can i fix this problem ? Thank you.
Json Output:-
{"foos":{"FooTypes [id=1, name=Test Foo]":[1,2,3]},"some":hello},{"foos":{"FooTypes [id=2, name=Another foo]":[5,6,7]}}
It's a bit hard to help you since we don't have the Json structure you want to deserialize, but the problem here is Jackson has no idea how to deserialize your class when it is used as a map key. All the information Jackson as is a simple String, and your class provide no way of creating it with only a string.
There's 3 way to achieve this:
Add a single string argument constructor to your FooType class
Add a single string argument factory method (static) and annotate it with #JsonCreator
Use a custom KeyDeserializer and annotate the foos field with #JsonDeserialize(keyUsing=YourDeserializer.class)
IMHO the static factory method is the cleaner way.
Since you have non-primitive type as a map key (FooTypes) you'll need to write your own custom deserializer as described in Jackson wiki, because Jackson can't simply convert string value "FooTypes [id=1, name=Test Foo]" (which seems to be a result of FooTypes.toString()) into FooTypes instance.
On my opinion, serializing map with non-string keys is not really a good practice. JSON notation doesn't support anything but strings for map keys (as specified here). A better approach, i think, would be to reorganize your data structure before serialization and after deserialization.