QueryStringBindable for a custom enum - java

I've defined an enum type Format that implements QueryStringBindable. I think I've implemented it correctly, but in my routes file, I can't specify my type as a route parameter, because the compiler can't find it, and I have no idea how to import it into the routes file.
Here's the enum:
package web;
import java.util.Map;
import play.libs.F;
import play.mvc.QueryStringBindable;
public enum Format implements QueryStringBindable<Format> {
Html,
Pdf,
Csv;
private Format value;
#Override
public F.Option<Format> bind(String key, Map<String, String[]> data) {
String[] vs = data.get(key);
if (vs != null && vs.length > 0) {
String v = vs[0];
value = Enum.valueOf(Format.class, v);
return F.Option.Some(value);
}
return F.Option.None();
}
#Override
public String unbind(String key) {
return key + "=" + value;
}
#Override
public String javascriptUnbind() {
return value.toString();
}
}
And here's my route:
GET /deposits controllers.Deposits.index(selectedAccountKey: Long ?= 0, format: Format ?= Format.Html)
How can I tell the compiler about my enum? Thanks!
Edit
I've also tried adding the path to the type in Build.scala as has been recommended in other posts:
val main = PlayProject(appName, appVersion, appDependencies, mainLang = JAVA).settings(
routesImport += "web.Format",
resolvers += Resolver.url("My GitHub Play Repository", url("http://www.joergviola.de/releases/"))(Resolver.ivyStylePatterns)
)
I changed that and restarted my server, but it appears to make no difference whatsoever.

I had the same problem and I finally found out that it is not solvable as is.
By reading the documentation for PathBindable and QueryStringBindable I found that play framework requires the Bindable to provide a No Argument public constructor. Which by definition is no possible with enum in Java.
I'd like to offer you the same solution I gave another (more recent) question.
I just wrapped the enum into a small Wrapper class that implements QueryStringBindable or PathBindable.
play framework - bind enum in routes

Use qualified name in the routes file, i.e. web.Format

Related

Is there any alternate way to replace complex enum?

I have a complex enum class in my spring boot application which holds different status values for different systems.
package com.foo;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public enum Status {
FOO_STATUS("Status1" ,"status_1", "STATUS_1", "stat1"),
BAR_STATUS("Status2" ,"status_2", "STATUS_2", "stat2" ),
FOO1_STATUS("Status3" ,"status_3", "STATUS_3", "stat3" ),
BAR1_STATUS("Status4" ,"status_4", "STATUS_4", "stat4" ),
....
....
....
private final String system1Status;
private final String system2Status;
private final String system3Status;
private final String system4Status;
private static Map<String, String> statusMap;
Status(String system1Status, String system2Status, String system3Status, String system4Status) {
this.system1Status = system1Status;
this.system2Status = system2Status;
this.system3Status = system3Status;
this.system4Status = system4Status;
}
public String getSystem1Status() {
return system1Status;
}
public String getSystem2Status() {
return system2Status;
}
public String getSystem3Status() {
return system3Status;
}
public String getSystem4Status() {
return system4Status;
}
private static void initializeMapping() {
statusMap = new HashMap<>();
for (Status map : Status.values()) {
statusMap.put(map.getSystem1Status(), map.getSystem2Status());
}
}
public static String getSystem2StatusForSytem1Status(String status) {
if (statusMap == null) {
initializeMapping();
}
if (statusMap.containsKey(status)) {
return statusMap.get(status);
}
return null;
}
public static String getSystem3StatusForSytem1Status(String status) {
....
}
public static String getSystem4StatusForSytem2Status(String status) {
....
}
public static String getSystem3StatusForSytem2Status(String status) {
....
}
....
....
}
The enum holds status string mapping for various systems. It also has methods to get different system status by supplying the current system status.
Ex: We can get System1 status by sending the System 2 status value.
As the enum is getting more complex , is there any alternate way to hold this static data?
PS: I know this can be moved to a reference table in DB, But I am looking for any alternate within the code (like loading from yaml file).
The concern about the enum getting more and more complex is only valid if that complexity is accidental, not inherent. Otherwise, switching to a different approach would just move that complexity elsewhere (which kind of seems to be the case in your example). I think it makes sense to keep the enum (even if it grows complex) iif the following conditions are met:
There is no reasonable scenario in which you would want/need to account for new statuses or new mappings (or drop existing ones) without changing the code.
You rely on at least some enum features available out of the box, so you would have to reimplement those by hand. E.g. values() listed in a determinate order, valueOf() used with canonical String labels, ordinal() to infer position, compareTo(), name(), serialization, etc.
You use the enum constants polymorphically (and maybe you need to alter the behavior for some of them without a full-fledged class hierarchy) or you want to leverage the compiler check for exhaustive case branches in switch expressions (with newer java versions).

Enum Localization generic approach

Enum
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
}
In callers scattered all over the application whicg get and set the enum like below. Here are examples
Caller_1
if(employee.getStatus() == EmployeeStatus.STATUS.SUBMITTED) {
}
Caller_2
employee.setStatus(EmployeeStatus.STATUS.SUBMITTED)
Problem
I need to implement the internationalization so that end user sees the employee status as per locale. So when i set the status for french locale
it should set the value from right resource bundel. Is there a way i can achieve this without changing the caller code. Here is the solution
I can think of
My proposed solution :-
public enum EmployeeStatus {
ACTIVE, IN_ACTIVE
public static String toString() {
return I18n.getMessage("label." + this);
}
}
public final class I18n {
private I18n() {
}
private static ResourceBundle bundle;
public static String getMessage(String key) {
if(bundle == null) {
bundle = ResourceBundle.getBundle("path.to.i18n.messages");
}
return bundle.getString(LocaleContextHolder.getLocale());
}
}
With this approach I need to add toString method in every Enum without change in caller ? Is there a better generic approach spring provides ?
I am using spring 4. See if spring can help here .

Dealing with changed ENUM definitions - database

Introduction
The lead architect went and changed the ENUM definition in a spring boot project.
From:
public enum ProcessState{
C("COMPLETE"), P("PARTIAL");
}
To:
public enum ProcessState{
COMPLETE("COMPLETE"), PARTIAL("PARTIAL");
}
What is the proper way to deal with this? Some other Java Spring Boot applications are now breaking. Would there be a way to tell the jackson deserializer to perform some kind of conversion in these situations?
My Current Work-Around
What I did was to run two update statements on the oracle database:
UPDATE store set PAYLOAD = REPLACE(PAYLOAD, '"processState":"P"','"processState":"PARTIAL"') where PAYLOAD like '%"processState":"P"%';
UPDATE store set PAYLOAD = REPLACE(PAYLOAD, '"processState":"C"','"processState":"COMPLETE"') where PAYLOAD like '%"processState":"C"%';
Question
So are there other ways? Could I do it by adding some deserialization/conversion code somewhere for these specific cases? Is there a more elegant way than running a replace SQL statement?
Could I do some kind of hack on a specific java sub-package, and say "use this enum instead of that enum..." or use one of the two? But without affecting the rest of the code?
The error:
java.lang.IllegalArgumentException: No enum constant
Ideally we store value of emum rather than Enum.
So, you should save ENUM values like COMPLETE,PARTIAL
For JSON serialization and de-serialization, use #JsonValue
#JsonValue
public String toValue() {
return value;
}
One additional solution to the others posted:
#JsonCreator
public static ProcessState factory(String inputValue) {
if(inputValue.length() == 1){
for(ProcessState type : ProcessState.values()){
if(inputValue.equals(type.getValue().substring(0,inputValue.length()))){
return type;
}
}
}
return ProcessState .valueOf(inputValue);
}
Implement a JPA converter like this:
#Converter(autoApply = true)
public class ProcessStateConverter
implements AttributeConverter<ProcessState, String> {
private ImmutableBiMap<ProcessState, String> map = ImmutableBiMap.<ProcessState, String>builder()
.put(COMPLETE, "C")
.put(COMPRESSING, "P")
.build();
#Override
public String convertToDatabaseColumn(ProcessState attribute) {
return Optional.ofNullable(map.get(attribute))
.orElseThrow(() -> new RuntimeException("Unknown ProcessState: " + attribute));
}
#Override
public ProcessState convertToEntityAttribute(String dbData) {
return Optional.ofNullable(map.inverse().get(dbData))
.orElseThrow(() -> new RuntimeException("Unknown String: " + dbData));
}
}
Remember to treat your Enum like a simple column and not #Enumerated i.e.
#Entity
public class MyEntity {
#Column //no #Enumerated
private ProcessState processState;
//...
}
The drawback is that you need to maintain the converter each time something changes. So better create a unit test to check if everything is correctly mapped.

JavaParser AnnotationExpr get parameters

I have an AnnotationExpr, how do I get parameters and their values of the annotation (e.g. #UnityBridge(fullClassName = "test") - how do I obtain a value of fullClassName parameter). Does JavaParser support this?
Do I have to accept another visitor? Which one in this case?
Late answer, I came into the same problem, just cast the AnnotationExpr to one of following:
MarkerAnnotationExpr (for no parameter),
SingleMemberAnnotationExpr (for single parameter),
NormalAnnotationExpr (for multiple parameters).
You may need instanceof to determine current annotation type.
I prefer this approach without instanceof and searching children by type instead, although you still need a distinction for a single parameter without key to find the Parameter "value":
private static final String VALUE = "value";
public static Expression getValueParameter(AnnotationExpr annotationExpr){
Expression expression = getParamater(annotationExpr, VALUE);
if(expression == null){
List<Expression> children = annotationExpr.getChildNodesByType(Expression.class);
if(!children.isEmpty()){
expression = children.get(0);
}
}
return expression;
}
public static Expression getParamater(AnnotationExpr annotationExpr, String parameterName){
List<MemberValuePair>children = annotationExpr.getChildNodesByType(MemberValuePair.class);
for(MemberValuePair memberValuePair : children){
if(parameterName.equals(memberValuePair.getNameAsString())){
return memberValuePair.getValue();
}
}
return null;
}
The simplest solution is:
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.expr.AnnotationExpr
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.expr.MemberValuePair
// Annotation
public #interface AnnotationName {
String argumentName();
}
// Class with this annotation
#AnnotationName(argumentName = "yourValue")
public class ClassWithAnnotationName {}
// Parse class with annotation
CompilationUnit compilationUnit = StaticJavaParser.parse(sourceFile);
Optional<ClassOrInterfaceDeclaration> classInterfaceForParse = compilationUnit.getInterfaceByName("ClassWithAnnotationName");
// Get annotation by name
final AnnotationExpr messageQueueKeyAnnotation =
classInterfaceForParse.get().getAnnotationByName("AnnotationName").get();
// Get all parameters. It doesn't matter how many.
final NodeList<MemberValuePair> annotationParameters = messageQueueKeyAnnotation.toNormalAnnotationExpr().get().pairs;
// Read annotation parameter from the list of all parameters
final String argumentName = annotationParameters.get(0).value;

Unifying enum.values() across multiple human languages

My Android app uses an enum type to define certain API endpoints.
public static enum API_ENDPOINT{
MISSION, FEATURED_MEDIA
}
The enum type seems an appropriate argument for methods that are dependent on the API call type, but I'm unable to translate enums to consistent Strings (i.e for mapping to API endpoint urls) across devices configured with different languages.
In Turkish API_ENDPOINT.values() returns: mıssıon, featured_medıa
In English API_ENDPOINT.values() returns: mission, featured_media
An obvious solution is an additional data structure that maps API_ENDPOINT to hard-coded string endpoints, but I'm curious as to whether this behavior of enum.values() is intended and/or avoidable.
Solved: Thanks everyone for the insight. It turns out deeper in the logic to convert API_ENDPOINT to a URL string I used String.toLowerCase() without specifying a Locale, which resulted in the undesirable behavior. This has been replaced with String.toLowerCase(Locale.US)
You can hard-code the strings as part of the enum, without any additional data structure:
public static enum API_ENDPOINT{
MISSION("mission"), FEATURED_MEDIA("featured_media");
private final String value;
API_ENDPOINT(String value) { this.value = value; }
public String value() { return value; }
}
but it would be nice if there were just a way to control the representation that's automatically generated.
The JLS enum section doesn't speak directly to language differences like this, but strongly suggests that the output would exactly match the enum identifiers; I'm surprised that you'd even get lower-case strings with upper-case identifiers.
After further testing, this isn't reproducible, something else must be going on in your code.
This minimal program displays the enum identifiers exactly as typed regardless of locale:
public class MainActivity extends Activity {
public enum ENUM {
MISSION, FEATURED_MEDIA
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView textView = (TextView) findViewById(R.id.text);
String enums = "";
for (ENUM e : ENUM.values()) {
enums += e + " ";
}
textView.setText(enums);
}
}
You can define two property-files. One for English and one for Turkish.The Enum could then look like this:
public static enum API_ENDPOINT{
MISSION("path.to.property.mission"), FEATURED_MEDIA("path.to.property.featured_media");
private String propertyName;
API_ENDPOINT(String propertyName){
this.propertyName = propertyName;
}
// language could also be an enum which defines the language to be taken
// and should contain the path to the file.
public String getTranslatedText(Language language){
Properties prop = new Properties();
try {
//load a properties file from class path
prop.load(API_ENDPOINT.class.getClassLoader().getResourceAsStream(language.getPropertyFileName()));
//get the translated value and raturn it.
return prop.getProperty(propertyName);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
The Property-File will look like this (English):
path.to.property.mission=Mission
path.to.property.featured_media=Featured Media
Same goes for Turkish.
Hope that helps.
EDIT: Due to you are using Android, this might be the solution for your problem:
Is there a sensible way to refer to application resources (R.string...) in static initializers
Make Enum.toString() localized

Categories