Loading Java Builder Object from Yaml file - java

I have created a Bean Class using Builder Pattern and having issues creating an object from a yaml file.
Here is a sample class (Actual class is quite big, this is just an excerpt incase if you wanted to answer with an example):
public class ClientBuilder {
private final String firstName;
private final String lastName;
private final String displayName;
private ClientBuilder(Builder builder) {
firstName = builder.firstName;
lastName = builder.lastName;
displayName = builder.displayName;
}
public static class Builder {
private final String displayName; // Mandatory Attribute
public Builder( String displayName ) {
this.displayName = displayName;
}
private String firstName;
private String lastName;
public Builder setFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder setLastName(String lastName) {
this.lastName = lastName;
return this;
}
public ClientBuilder build() {
return new ClientBuilder(this);
}
}
#Override
public String toString() {
StringBuffer sbf = new StringBuffer();
sbf.append("New Company Object: \n");
sbf.append("firstName : " + this.firstName + "\n");
sbf.append("lastName : " + this.lastName + "\n");
sbf.append("displayName : " + this.displayName + "\n");
return sbf.toString();
}
}
I am using snakeyaml to load the file but any yaml api would work. Since the displayName is a mandatory param, I want to pass that value while creating the instance. The other params can be passed while creating the object but I would like the option to load them through yaml file.
I am able to load the yaml file if I use java bean. Is there a way to instantiate builder objects?
I tried:
InputStream input = new FileInputStream(new File("src/main/resources/client.yaml"));
Yaml yaml = new Yaml();
Builder builder = new Builder("Display Name");
builder = (Builder) yaml.loadAs(input, ClientBuilder.Builder.class);
ClientBuilder client = builder.build();
System.out.println(client.toString());
but I get following error:
Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:com.xxx.xxx.xxx.ClientBuilder$Builder; exception=java.lang.NoSuchMethodException: com.xxx.xxx.xxx.ClientBuilder$Builder.<init>()
in 'reader', line 2, column 1:
firstName: "Jaypal"

SnakeYaml is a very powerful library & it provides support for creating instance based on constructor injection.
/**
* create JavaBean
*/
public void testGetBeanAssumeClass() {
String data = "--- !!org.yaml.snakeyaml.constructor.Person\nfirstName: Andrey\nage: 99";
Object obj = construct(data);
assertNotNull(obj);
assertTrue("Unexpected: " + obj.getClass().toString(), obj instanceof Person);
Person person = (Person) obj;
assertEquals("Andrey", person.getFirstName());
assertNull(person.getLastName());
assertEquals(99, person.getAge().intValue());
}
/**
* create instance using constructor arguments
*/
public void testGetConstructorBean() {
String data = "--- !!org.yaml.snakeyaml.constructor.Person [ Andrey, Somov, 99 ]";
Object obj = construct(data);
assertNotNull(obj);
assertTrue(obj.getClass().toString(), obj instanceof Person);
Person person = (Person) obj;
assertEquals("Andrey", person.getFirstName());
assertEquals("Somov", person.getLastName());
assertEquals(99, person.getAge().intValue());
}
Junit code sample can be viewed here.
So your code still holds good. You may need to change yaml content with proper format. Once done, you are all set.

The exception is about there not being a no-arg constructor in Builder, as you probably figured out.
What you could do is to allow no-arg constructor for Builder and add corresponding setter and getter for displayName to it. Then simply throw an exception in build() if displayName is not set (or provide a default value for it). The exception can be a runtime one, or you could make it clear and add an explicit throws.
While it is not the prettiest solution, it should work just fine. The fact that the Builder is created without a mandatory argument should not matter, as it is the ClientBuilder that needs to be constructed properly (as the factory/builder is used to ensure that each instance of whatever it is building is correct).
I have no way to access any yaml parsing tools for Java currently, but if there is any way I could improve my answer, let me know - I will be happy to do so.

Related

Java nested POJO update based on dot annotation

I have a nested POJO structure defined something like this,
public class Employee {
private String id;
private Personal personal;
private Official official;
}
public class Personal {
private String fName;
private String lName;
private String address;
}
public class Official {
private boolean active;
private Salary salary;
}
public class Salary {
private double hourly;
private double monthly;
private double yearly;
}
I get updates from a service with dot annotaion on what value changed, for ex,
id change --> id=100
address change --> personal.address=123 Main Street
hourly salary change --> official.salary.hourly=100
This POJO structure could be 3-4 level deeps. I need to look for this incoming change value and update the corresponding value in POJO. What's the best way of doing it?
If you would like to create Java objects that allows you to edit fields. You can specify your object fields with the public/default/protected access modifiers. This will enable you to get and set fields such as personal.address or official.salary.hours
This approach is typically frowned upon as the object is no longer encapsulated and any calling methods are welcome to manipulate the object. If these fields are not encapsulated with getters and setters, your object is no longer a POJO.
public provides access from any anywhere.
default provides access from any package
protected provides access from package or subclass.
public class Employee {
public String id;
public Personal personal;
public Official official;
}
public class Personal {
public String fName;
public String lName;
public String address;
}
Here's a quick approach using reflection to set fields dynamically. It surely isn't and can't be clean. If I were you, I would use a scripting engine for that (assuming it's safe to do so).
private static void setValueAt(Object target, String path, String value)
throws Exception {
String[] fields = path.split("\\.");
if (fields.length > 1) {
setValueAt(readField(target, fields[0]),
path.substring(path.indexOf('.') + 1), value);
return;
}
Field f = target.getClass()
.getDeclaredField(path);
f.setAccessible(true);
f.set(target, parse(value, f.getType())); // cast or convert value first
}
//Example code for converting strings to primitives
private static Object parse(String value, Class<?> type) {
if (String.class.equals(type)) {
return value;
} else if (double.class.equals(type) || Double.class.equals(type)) {
return Long.parseLong(value);
} else if (boolean.class.equals(type) || Boolean.class.equals(type)) {
return Boolean.valueOf(value);
}
return value;// ?
}
private static Object readField(Object from, String field) throws Exception {
Field f = from.getClass()
.getDeclaredField(field);
f.setAccessible(true);
return f.get(from);
}
Just be aware that there's a lot to improve in this code (exception handling, null checks, etc.), although it seems to achieve what you're looking for (split your input on = to call setValueAt()):
Employee e = new Employee();
e.setOfficial(new Official());
e.setPersonal(new Personal());
e.getOfficial().setSalary(new Salary());
ObjectMapper mapper = new ObjectMapper();
setValueAt(e, "id", "123");
// {"id":"123","personal":{},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "personal.address", "123 Main Street");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":0.0,"monthly":0.0,"yearly":0.0}}}
setValueAt(e, "official.salary.hourly", "100");
// {"id":"123","personal":{"address":"123 Main Street"},"official":{"active":false,"salary":{"hourly":100.0,"monthly":0.0,"yearly":0.0}}}

JSON with spaces in variable names [duplicate]

This question already has answers here:
How to parse with GSON when identifier has space in name
(2 answers)
Closed 6 years ago.
I have a JSON with spaces in the variable names something like the following:
{
"First Name":"John",
"Last Name":"Smith"
}
Since Java doesn't allow spaces in variable names I'm trying to see if Gson has a provision to parse this into First_Name or FirstName or something of that sort so I can use First_Name or FirstName as the variable name in my Java class to represent this data.
Is there a way to do it or will I need to make a local copy of the JSON and run a String parser through the JSON file to rename the variables and then pass it on to Gson to do the rest?
Any ideas?
Note: This JSON is being sent by a third-party API I am using, not my own creation. So while I wish I could tell them about proper naming conventions, that isn't where I would like to spend my time :)
Have you tried using field naming support. My first guess is it sshould work with spaces in the name (https://sites.google.com/site/gson/gson-user-guide#TOC-JSON-Field-Naming-Support). Something like below should work.
Tried the below , it works (I don't agree with the naming , but it works)
import com.google.gson.Gson;
public class SOMain {
public static void main(String[] args) throws Exception{
Gson gson = new Gson();
String json = "{\"First Name\":\"John\",\"Last Name\":\"Smith\"}";
Employee employee = gson.fromJson(json, Employee.class);
System.out.println(employee);
}
}
import com.google.gson.annotations.SerializedName;
public class Employee {
#SerializedName("Last Name")
public String lastName;
#SerializedName("First Name")
public String firstName;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Override
public String toString() {
// TODO Auto-generated method stub
return "Employee with first name " + firstName + " and last name " + lastName ;
}
}
First of all, name your variable firstName in your java code, see Variable naming conventions in Java?
Second, to change field names with Gson: here is a great tutorial: http://www.javacreed.com/gson-annotations-example/

How to send multiple objects with same state to a method with single Object parameter using Java?

Java Version : 1.6
I have multiple beans with similar structure
ClassBean1 {
String firstName;
String middleName;
String lastName;
}
UtilClass {
public static void concatName(ClassBean1 object) {
String fullName = object.firstName + object.middleName + object.lastName;
System.out.println(fullName);
}
}
Mainclass {
UtilClass.concatName(newClassBean1)
}
Now I get one more bean class with same structure as of ClassBean1; Say ClassBean2
ClassBean2 {
String firstName;
String middleName;
String lastName;
}
I want to modify UtilClass.concatName such that it works on all object with firstName, middleName & lastName. I have done following changes:
UtilClass {
public static void concatName(Object object) {
String fullName = object.firstName + object.middleName + object.lastName;
System.out.println(fullName);
}
}
But it gives compile time errors. Could anyone please help in resolving!!!
I could not touch existing bean i.e ClassBean1. So the new code i can take care off creating base class and derived fundaa; but refactor it so that existing code should work
You need to create a base class with the members and method that you want to derive all your other classes from, in your base class define the name members and the concatName method, then every object derived from this will inherit the members and function and it will work.
Create a parent class with those three variables, inherit other classes which will use those 3 from that parent class than:
public static void concatName(Parent object) {
String fullName = object.firstName + object.middleName + object.lastName;
System.out.println(fullName);
}
And if you REALLY want to do it without createing the base class, the only way I can come up with is reflection. That awy, you can have an Object as the method param. Then, in your method, you can use reflection to see if all the required methods are there and call them to get the data.
But this is very ugly and should not be attempted. The best way would be either a base class or a common interface, as suggested before.
You can check using instanceof
public static void concatName(Object object) {
if(object instanceof ClassBean1){
ClassBean1 b1 = (ClassBean1) object;
String fullName = b1.firstName +b1.middleName + b1.lastName;
System.out.println(fullName);
} else if(object instanceof ClassBean2){
ClassBean2 b2 = (ClassBean2) object;
String fullName = b2.firstName +b2.middleName + b2.lastName;
System.out.println(fullName);
}
}
You can use reflection to get the class fields/variables.
Pass the bean of any type having common state i.e the common variables you want to use in the UtilClass function and use the very base class of each object that is Object class as a parameter for the same method. Now extract the class object from that variable and used reflection methods to get the variables values.
public static void concatName(Object object) {
//
Class clazz = object.getClass();
String fullName = null;
try {
fullName = clazz.getDeclaredField("firstName").get(object) +" "+ clazz.getDeclaredField("middleName").get(object) + " " +clazz.getDeclaredField("lastName").get(object);
} catch (IllegalArgumentException | IllegalAccessException
| NoSuchFieldException | SecurityException e) {
}
System.out.println(fullName);
}
With this approach you can get the required result without polymorphism/inheritance or altering the bean classes.
You can have a method in Util class, which will take three parameter and call that method from main class.
UtilClass {
public static void concatName(String firstName, String middleName, String lastName) {
String fullName = firstName + middleName + lastName;
System.out.println(fullName);
}
}
Mainclass {
UtilClass.concatName(newClassBean1.getFirstName() + newClassBean1.getMiddleName() + newClassBean1.getLastName())
//for second class
UtilClass.concatName(newClassBean2.getFirstName() + newClassBean2.getMiddleName() + newClassBean2.getLastName())
}

Final print statement returning null (java)

I have a basic name application that is taking in user data from the main class, splits the data in the parser class and then tries to assign everything in the final class and print it out in the toString method. I know the main class and the parser are working fine. I have verified in the parser class that the data DOES split properly and also sends the data through the object I made to the final class to assign it all. However, my final code is returning null..
MAIN CLASS
import java.util.Scanner;
public class MainClass {
public static void main (String[]args)
{
Scanner input = new Scanner(System.in); //create scanner object to gather name information
String fullName = null; //set the predefined value for the users name to null
nameParse splitInformation = new nameParse(); //method build to split the name into different sections
SecondClass access = new SecondClass(); //class built to output the different name data
System.out.println("What is your name?");
fullName = input.nextLine(); //store the users name and pass it into the data parser
splitInformation.parseNameInformation(fullName); //name parsing parameters built
System.out.println(access.toString());
}
}
Data Parser Class
public class nameParse {
private String firstName;
private String middleName;
private String lastName;
public nameParse()
{
firstName = "initial";
middleName = "initial";
lastName = "initial";
}
public void parseNameInformation(String inputInfo)
{
//Create an array to store the data and split it into multiple sectors
String nameInformation[] = inputInfo.split("\\s");
firstName = nameInformation[0];
middleName = nameInformation[1];
lastName = nameInformation[2];
//System.out.println(firstName + " " + middleName + " " + lastName);
SecondClass sendData = new SecondClass();
sendData.setFirstName(firstName);
sendData.setMiddleName(middleName);
sendData.setLastName(lastName);
}
}
Final Class
__
public class SecondClass {
private String firstName;
private String middleName;
private String lastName;
/*public String GFN()
{
return firstName;
}
public String GMN()
{
return middleName;
}
public String GLN()
{
return lastName;
}*/
public String setFirstName(String yourFirstName)
{
firstName = yourFirstName;
return this.firstName;
}
public String setMiddleName(String yourMiddleName)
{
middleName = yourMiddleName;
return this.middleName;
}
public String setLastName(String yourLastName)
{
lastName = yourLastName;
return this.lastName;
}
public String getFN()
{
return firstName;
}
public String toString()
{
String printNameInfo = "\nYour First Name:\t" + getFN();
return printNameInfo;
}
}
You never set any of your SecondClass object's (called "access") fields, so of course they'll all be null.
So in short, your code creates a nameParse object, gets information from the user, but does nothing with that information. You create a SecondClass object called access, put no data into it, and so should expect no valid data in it when you try to print it out. Solution: put information into your SecondClass object first. Call its setter methods:
// be sure to call the setter methods before trying to print anything out:
access.setSomething(something);
access.setSomethingElse(somethingElse);
Edit
You state:
I thought I set the data using the sendData.setFirstname(...) etc?
In the parseNameInformation method you create a new SecondClass object and you do set the fields of this object, but this object is completely distinct from the one in your main method whose fields are still null. To solve this, give parseNameInformation a method parameter and pass in your main method's SecondClass object into it and set its methods. You'll have to create the SecondClass object before calling the method of course.
i.e.,
public void parseNameInformation(String inputInfo, SecondClass sendData)
{
//Create an array to store the data and split it into multiple sectors
String nameInformation[] = inputInfo.split("\\s");
firstName = nameInformation[0];
middleName = nameInformation[1];
lastName = nameInformation[2];
//System.out.println(firstName + " " + middleName + " " + lastName);
// SecondClass sendData = new SecondClass(); // !!! get rid of this
sendData.setFirstName(firstName);
sendData.setMiddleName(middleName);
sendData.setLastName(lastName);
}

how to deserialize object?

I have a class called Flight
The Flight class when instantiated, instantiates another class called SeatingChart, and SeatingChart also instantiates another class and so on and so forth.
public class Flight implements Serializable
{
SeatingChart sc = new SeatingChart(); seating
//WaitingList wl = new WaitingList();
}
public class SeatingChart extends ListAndChart implements PassengerList, Serializable
{
public static final int NOT_FOUND = 42;
Passenger [] pass = new Passenger[40];
}
public class Passenger implements Serializable
{
private String firstName, lastName, fullName;
public String getName()
{
fullName = firstName + " " + lastName;
return fullName;
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
}
I have another method in another class that deserializes the object saved in the disk
public void actionPerformed(ActionEvent evt)
{
Serialization.deserialize(sw101); <--- sw101 is a Flight object
.
.
.
}
//code for deserialization
public static void deserialize(Flight sw101)
{
String filename = "serialized.ser";
sw101 = null;
FileInputStream fis = null;
ObjectInputStream in = null;
try
{
fis = new FileInputStream(filename);
in = new ObjectInputStream(fis);
sw101 = (Flight)in.readObject();
System.out.println("sw101" + sw101.toString());
in.close();
}
catch(IOException ex)
{
ex.printStackTrace();
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace();
}
}
my question is when I assign sw101 the serialized object, everything sw101 instantiated in the beginning like the SeatingChart sc object also get whatever is saved in the file without me doing anything as long as those objects all implement the Serializable interface? If so why is my code not working? what am I doing wrong?
It looks like you're trying to return through a reference parameter (C/C++ background?)
That never works in Java. All parameters (including references) are passed by value. Once you do
sw101=null;
you lose the reference to the object that was passed in.
Your deserialize function should return the flight object instead.
(Technically there is a way to simulate returning through the arguments in Java by using an array, but it leads to unnecessarily complicated code)
In java, all parameters are passed as values.. so the answer to your last question is no. sw101 is a reference to a copy.
As said, you have to return your deserialized object to make it work.
check this article: parameter passing in java

Categories

Resources