Design Enum generic behavior - java

Well my question today is:
I have an enum with file headers.
I have a function which export theses headers as String
Running multiple project I want to make it quite generic to be used in our private lib.
Current Function:
private static String getHeaders() {
StringBuilder sb = new StringBuilder();
for(HeadersEnum header : HeadersEnum.values()){
sb.append(header.getExportLib());
}
return sb.toString();
}
Goal something like it:
private static String getHeaders(ExportableCSV<Enum<T>> data) {
StringBuilder sb = new StringBuilder();
for( ExportableCSV<Enum> header : data.values()){
sb.append(header.getExportLib());
}
return sb.toString();
}
I know we can't make inheritance with Enum so I created an Interface "ExportableCSV":
public interface ExportableCSV<T extends Enum<T>> {
public static final String exportLib = "";
public static String getExportLib() {
return exportLib;
}
}
It's quite basic, but in my mind , any Enum which implement this ExportableCSV should have access to my exportLib() function.
public enum HeadersEnum implements ExportableCSV<HeadersEnum>{
foo("foo;"),
bar("bar;");
private String exportLib;
of course my goal approach of generic function isn't compiling and I'm not really understanding what I can do and what I can't.

One simple option is to have all relevant Enums implement an export interfce
interface Exportable { String getExportLib(); }
Another option: If you want a static method that uses values you can do something like:
class EnumUtil {
public static <T extends Enum<T>> String getExportLib(T[] aValues){
StringBuilder sb = new StringBuilder();
for( T t : aValues){
//todo
}
return sb.toString();
}
}

Related

Trying to write a generic enum util class in groovy

Problem
We have multiple enum types that have some public static EnumType valueOfText(String text), for the purpose of mapping the contents of a data file cell to enum.
I'm trying to write a generic enum util that takes a comma-separated string and return multiple enum values. For example, we have the following enum:
public enum Frequency {
SEMI_ANNUAL("S"), MONTHLY("M"), QUARTERLY("Q"), ANNUAL("A")
public final String textValue;
public Frequency(String textValue) {
this.textValue = textValue;
}
public static Frequency valueOfText(String textValue) {
for (Frequency frequency : values()) {
if (frequency.textValue.equals(textValue))
return frequency;
}
return null;
}
}
and string "A,S" which we want to convert to [Frequency.ANNUAL, Frequency.SEMI_ANNUAL].
Attempted solution
I create some EnumUtils like so:
import java.util.stream.Collectors
public final class EnumUtils {
public static final String LIST_SEPARATOR = ",";
public static <E extends Enum<E>> List<E> CreateFromText(String text) {
List<String> textList = text.split(this.LIST_SEPARATOR)
return textList.stream()
.map { txt ->
E.valueOfText(txt)
}
.collect(Collectors.toList())
}
}
What happen after said solution
We go to use it, like this:
EnumUtils.CreateFromText<Frequency>(row[3])
and the IDE compain, immediately, about the <>.
How can we specify enum type in this?
In Groovy you can do it if you pass the actual Class instead of just using a type parameter.
enum Frequency {
SEMI_ANNUAL("S"), MONTHLY("M"), QUARTERLY("Q"), ANNUAL("A")
final String textValue;
Frequency(String textValue) {
this.textValue = textValue;
}
static Frequency valueOfText(String textValue) {
return values().find { it.textValue == textValue }
}
}
final class EnumUtils {
static <E extends Enum<E>> List<E> createFromText(Class<E> clazz, String text) {
return text.split(",").collect { clazz.valueOfText(it) }
}
}
EnumUtils.createFromText(Frequency, "S,M")
The same idea won't work in Java, since clazz won't have valueOfText at compile time.
Perhaps the Util class doesn't save you much typing, though:
"S,M".split(",").collect(Frequency.&valueOfText)

getting net.sf.json.JSONException: java.lang.reflect.InvocationTargetException

i have the following method
public static <E> APIGatewayProxyResponseEvent generateResponse(E request, E response, int statusCode){
JSONObject result = new JSONObject();
result.put(Constants.REQUEST, request);
result.put(Constants.RESPONSE, response);
return new APIGatewayProxyResponseEvent()
.withBody(result.toString())
.withStatusCode(statusCode)
.withHeaders(Constants.commonHeaders);
}
i am getting net.sf.json.JSONException: java.lang.reflect.InvocationTargetException when result.put(Constants.RESPONSE, response); is executed
response is
Also the corresponding class is:
public class PhysicalMediaURL extends MediaURL {
private static final String IDENTIFIER_PREFIX = "images/I/";
public PhysicalMediaURL(String physicalId, String extension, MediaHostnameProvider mediaHostnameProvider) {
super("images/I/" + physicalId, extension, mediaHostnameProvider);
}
}
public abstract class MediaURL implements URL {
private final String identifier;
private final String extension;
private final MediaHostnameProvider mediaHostnameProvider;
public MediaURL(String identifier, String extension, MediaHostnameProvider mediaHostnameProvider) {
this.identifier = identifier;
this.extension = extension;
this.mediaHostnameProvider = mediaHostnameProvider;
}
public String getIdentifier() {
return this.identifier;
}
public String getExtension() {
return this.extension;
}
public String getDomainName() {
return this.mediaHostnameProvider.getMediaHostname(this.getExtension());
}
public String getURL() {
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append("https://");
urlBuilder.append(this.getDomainName());
urlBuilder.append('/');
urlBuilder.append(this.getIdentifier());
urlBuilder.append('.');
urlBuilder.append(this.getExtension());
return urlBuilder.toString();
}
public List<String> getStyleTags() {
return null;
}
}
where PhysicalMediaURL is of type: URL and that is an interface
public interface URL {
String getIdentifier();
String getDomainName();
String getExtension();
List<String> getStyleTags();
String getURL();
}
I am a bit stuck in this.. need help.
First off it looks like you are using a JSON implementation that is not updated as regularly as the other ones(Your exception is from net.sf.json). I always recommend using the org.json implementation as it receives regular updates and bugfixes.
Most implementations of JSONObject, when used in this form, use bean based reflection to retrieve values from your object. This is not always what you want when your object is in an inheritance hierarchy because, depending on the object and the JSONObject impl, it will pull fields from the implementation that are not on your higher level type(URL in this case).
If you really want a generic serialization function use something like Jackson or Gson that will allow you to specify the type as a part of the serialization. Otherwise consider transforming your objects, before they are passed to your generateResponse function, into simpler objects such as a Map<String, String> that can serialize unambiguously.
As a final thought JSONObject's generic serialization works, but, its performance is likely to be worse than using a dedicated higher level serializer like Jackson. It's best used with the explicit put methods to generate simple objects.

relate toString with object creation

I have a fairly basic Java class with some class variables. I have overwridden toString() to provide me with a string output (which will eventually be output to a text file).
I am trying to elegantly create a way for me to use this string output to recreate the object with all of the variables set as before. The class looks something like this:
public class Report {
private String itemA;
private String itemB;
private String itemC;
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
this is how I can potentially tackle it using reflection:
public class Report {
private String itemA;
private String itemB;
private String itemC;
private final Map<String, String> MAPPING = new HashMap<>();
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
MAPPING.put("Item A is: ", "itemA");
MAPPING.put("Item B is: ", "itemB");
MAPPING.put("Item C is: ", "itemC");
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
MAPPING.entrySet().forEach(entry -> {
sb.append("\n").append(entry.getKey()).append(BeanUtils.getProperty(this, entry.getValue()));
});
return sb.toString();
}
public Report createReportFromString(String reportString) {
List<String> reportLines = Arrays.asList(reportString.split("\n"));
HashMap<String, String> stringObjectRelationship = new HashMap<>();
reportLines.forEach(reportLine -> {
Optional<String> matchingKey = MAPPING.keySet().stream().filter(reportLine::contains).findFirst();
matchingKey.ifPresent(key -> {stringObjectRelationship.put(MAPPING.get(key), reportLine.split(key)[1]);});
});
stringObjectRelationship.forEach((variableName, variableValue) -> BeanUtils.setProperty(this, variableName, variableValue));
return this;
}
}
I basically want to relate the key in the report ("Item A is: ") to the name of the corresponding variable ("itemA") and use this relationship in both the toString() method and the createReportFromString(String string) method. Now when doing this there are a lot of possible exceptions that can be thrown and need to either be handled or thrown - and it then looks a lot less elegant than I would like.
I don't know if this is possible to do without reflection - or perhaps I could rearrange this class to make this possible?
What I can`t change is the structure of the string output in the toString().
Reflection bears multiple features:
Automatic discovery of features of a program at runtime
Support for dealing with features unknown at compile-time
Provide an abstraction of program features (e.g. methods or fields)
Your approach suggests that you don’t want an automatic discovery, as you are specifying the three elements explicitly. This is a good thing, as it makes your program more robust regarding future changes, as dealing with automatically discovered, potentially unknown program elements will destroy any help from the compiler, as it can’t tell you when there are mismatches.
You only want the third point, an abstraction over the elements of your report. You can create such an abstraction yourself, tailored to your use case, without Reflection, which will be more robust and even more efficient:
public class Report {
static final class Element {
final String header;
final Function<Report,String> getter;
final BiConsumer<Report,String> setter;
final Pattern pattern;
Element(String header,
Function<Report, String> getter, BiConsumer<Report, String> setter) {
this.header = header;
this.getter = getter;
this.setter = setter;
pattern = Pattern.compile("^\\Q"+header+"\\E(.*?)$", Pattern.MULTILINE);
}
}
static final List<Element> ELEMENTS = List.of(
new Element("Item A is: ", Report::getItemA, Report::setItemA),
new Element("Item B is: ", Report::getItemB, Report::setItemB),
new Element("Item C is: ", Report::getItemC, Report::setItemC));
private String itemA, itemB, itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are:");
ELEMENTS.forEach(e ->
sb.append('\n').append(e.header).append(e.getter.apply(this)));
return sb.toString();
}
public static Report createReportFromString(String reportString) {
return new Report("", "", "").setValuesFromString(reportString);
}
public Report setValuesFromString(String reportString) {
Matcher m = null;
for(Element e: ELEMENTS) {
if(m == null) m = e.pattern.matcher(reportString);
else m.usePattern(e.pattern).reset();
if(!m.find())
throw new IllegalArgumentException("missing \""+e.header+'"');
e.setter.accept(this, m.group(1));
}
return this;
}
public String getItemA() {
return itemA;
}
public void setItemA(String itemA) {
this.itemA = itemA;
}
public String getItemB() {
return itemB;
}
public void setItemB(String itemB) {
this.itemB = itemB;
}
public String getItemC() {
return itemC;
}
public void setItemC(String itemC) {
this.itemC = itemC;
}
}
This works with Java’s out-of-the-box features, not requiring another library to simplify the operation.
Note that I changed the code pattern, as createReportFromString is a misleading name for a method modifying an already existing object. I used the name for a factory method truly creating a new object and added a another method for setting the values of the object (as a direct counter-part to toString).
If you are still using Java 8, you can replace List.of(…) with Arrays.asList(…) or better Collections.unmodifiableList(Arrays.asList(…)).
You can also remove the .reset() call in the setValuesFromString method. When you remove it, the elements in the input string are required to be in the same order as the toString() method produces. This makes it a bit less flexible, but also more efficient if you expand the code to have a lot more elements.
#JimboMcHiggins assuming I can change the toString output how exactly would you tie together serialization and deserialization with some common mapping?
I would leave the toString unchanged and move the responsibility of serialization to java.io.Serializable. Correct me if this is not an acceptable approach. The mapping would be defined by the class fields of your Report pojo. This would also allow you to change your toString without breaking deserialization of existing objects.
import java.io.Serializable;
public class Report implements Serializable {
private static final long serialVersionUID = 1L;
private String itemA;
private String itemB;
private String itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
Example Usage
public class Test1 {
public static void main(String[] args) {
Report report = new Report("W", "O", "W");
System.out.println(report);
String filename = "file.ser";
// Serialization
try
{
//Saving of report in a file
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Method for serialization of report
out.writeObject(report);
out.close();
file.close();
System.out.println("Report has been serialized");
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
Report report1 = null;
// Deserialization
try
{
// Reading the report from a file
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Method for deserialization of report
report1 = (Report)in.readObject();
in.close();
file.close();
System.out.println("Report has been deserialized ");
System.out.println(report1);
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
catch(ClassNotFoundException ex)
{
System.out.println("ClassNotFoundException is caught");
}
}
}
Output
Items are::
Item A is: W
Item B is: O
Item C is: W
Report has been serialized
Report has been deserialized
Items are::
Item A is: W
Item B is: O
Item C is: W

Method that returns a string of the concatenated elements of an array list

I have a method toString that is supposed to return a string of concatenated elements of an array list. The issue I am encountering is that an arraylist of type T (because its supposed to be generic) cannot be converted to string.
public class RandomStuffContainer<T extends Comparable<T>>
{
ArrayList<T> array = new ArrayList<T>();
public void main(String[] args){
ArrayList<T> array = new ArrayList<T>();
}
public String toString() {
StringBuilder sb = new StringBuilder();
ArrayList<String> data = (ArrayList<T>)array.clone();
for (String s : data)
{
sb.append(s);
sb.append("\t");
}
return data.toString();
}
The issue I am encountering is that an arraylist of type T (because its supposed to be generic) cannot be converted to string.
You don't need to convert it to a String. You just invoke the toString() method on the object when you want its String representation.
Also there is no need for the clone() since you are not changing any data in the ArrayList:
import java.util.*;
public class RandomStuffContainer<T extends Comparable<T>>
{
ArrayList<T> array = new ArrayList<T>();
public void main(String[] args)
{
ArrayList<T> array = new ArrayList<T>();
}
public String toString()
{
StringBuilder sb = new StringBuilder();
//ArrayList<String> data = (ArrayList<T>)array.clone(); // not needed
//for (String s : array) // no need to use String here
for (T s : array)
{
//sb.append(s);
sb.append(s.toString());
sb.append("\t");
}
return sb.toString();
}
}
Also, instead of using the StringBuilder class you should use the StringJoiner class. This class will append the delimiter for you automatically and won't add the delimiter to the last token.
The above changes will just get the code to compile. It is still not a useable class since you have no methods to actually add data to the ArrayList and you have to code to create an instance of your RandomStuffContainer class.
The code below is just a follow-up of #camickr answer giving an example of applying Java 8 Stream API to the required implementation of toString method:
import java.util.*;
import java.util.stream.*;
public class RandomStuffContainer<T extends Comparable<T>> {
List<T> array = new ArrayList<T>();
#Override
public String toString() {
return array.stream().map(Object::toString).collect(Collectors.joining("\t"));
}
// provide constructor, add/remove methods, etc. as needed
}

deserialize Json into POJO

I am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON

Categories

Resources