I have immutable objects as follows.
#Getter
#Builder
class MainDetail {
// 5 other String fields
private Data data;
}
#Getter
#Builder
class ImageUrl {
private final String dataOne; // looking to change these 2 values
private final String dataTwo; // if rest call returns null for these.
}
Information to fill these up is fetched from a rest call, working fine as follows.
List<MainDetail> featureBenefits = // value from a rest response
I wish to switch out the dataOne and dataTwo values in here if it is null for each MainDetail Object.
I can't just use a set method to do this cos it is immutable.
I end up with the following verbose way of doing it where I need to do multiple variations of the check to swap values.
I can't just check one at a time and switch cos Object becomes immutable. Can't add another if the second one is null too after that.
Is there a way to do this more elegantly, possibly via streams? Appreciate any help. Thanks.
List<MainDetail> mainDetails = new ArrayList<>();
for (MainDetail mainDetail : featureBenefits) {
if (mainDetail.getImageUrl().getDataOne() == null && mainDetail.getImageUrl().getdataTwo() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataOne("default1")
.dataTwo("default12")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
else if (mainDetail.getImageUrl().getdataOne() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataOne("default1")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
else if (mainDetail.getImageUrl().getDataTwo() == null) {
ImageUrl imageUrl = ImageUrl.builder()
.dataTwo("default2")
.build();
MainDetail detail = MainDetail.builder()
.imageUrl(imageUrl)
.build();
mainDetails.add(detail);
}
}
What about this one:
List<MainDetail> featureBenefits = Collections.emptyList();
List<MainDetail> mainDetails = new ArrayList<>();
for (MainDetail mainDetail : featureBenefits) {
ImageUrl imageUrl = mainDetail.getImageUrl();
mainDetails.add(MainDetail.builder()
.imageUrl(ImageUrl.builder()
.dataOne(Optional.ofNullable(imageUrl.getDataOne()).orElse("default1"))
.dataTwo(Optional.ofNullable(imageUrl.getDataTwo()).orElse("default2"))
.build())
.build());
}
If you are not limited to sticking with standard builders then you could add your own methods for providing default values:
class ImageUrl {
private final String dataOne; // looking to change these 2 values
private final String dataTwo; // if rest call returns null for these.
public ImageUrl withDefaultDataOne(String value) {
return dataOne == null ? new ImageUrl(value, dataTwo) : this;
}
public ImageUrl withDefaultDataTwo(String value) {
return dataTwo == null ? new ImageUrl(dataOne, value) : this;
}
}
Then your translation code becomes:
for (MainDetail mainDetail : featureBenefits) {
ImageUrl imageUrl = mainDetail.getImageUrl()
.withDefaultDataOne("default1")
.withDefaultDataTwo("default2");
mainDetails.add(MainDetail.builder().imageUrl(imageUrl).build());
}
Related
Set<String> response = null;
Set<String> success = null;
for (String country : countrys) {
response = service.method(country);
if (response != null) {
success = response;
}
}
Here service.method returns a Set<String>. I want to add the response of each loop to the success Set.
Now, this code is just storing the response of the last loop in success.
Can someone please help with this at the earliest?
You could use the addAll(Collection<? extends E> c) method (see spec):
Set<String> response = null;
Set<String> success = new HashSet<>();
for (String country : countrys) {
response = service.method(country);
if (response != null) {
success.addAll(response);
}
}
Keep in mind that you will want to initialize success as an empty set (e.g. a HashSet) first. Otherwise you will run into a NullPointerException.
I have ProductInfo object which looks like this
ProductInfo.java
public class ProductInfo
{
private List<String> servicetagInfo;
}
I have Order object like this which has list of Products info
OrderDetail.java
public class OrderDetail
{
private String orderNum;
private List<ProductInfo> productInfo;
}
And then I have a Response object which basically has List of Order objects
Response.java
public class Response
{
private List<OrderDetail> orderInfo;
}
I am getting response as expected.But right now in this format
orderInfo:
0: {orderNum: "162293591",...}
productInfo:
0: {servicetag_info: ["7LSMW33", "49SMW33"]}
1: {servicetag_info: ["JF6XN33", "CQ5XN33"]}
2: {servicetag_info: ["5VRR523", "13LR523"]}
Here I am trying to merge productInfo List to be like this
productInfo:
0: {servicetag_info: ["7LSMW33", "49SMW33","JF6XN33", "CQ5XN33","5VRR523", "13LR523"]}
Just add all strings into one main property.
Here is my code
List<String> serviceTagList = new ArrayList<>();
for (OrderDetail orderDetail : arInvoiceOrderResponseBody.getOrders()) { //Here i am getting orders from external service
if (orderDetail != null) {
if (orderDetail.getProductInfo() != null && orderDetail.getProductInfo().size() > 0) {
for (ProductInfo productInfoDetail : orderDetail.getProductInfo()) {
if (productInfoDetail != null) {
if (productInfoDetail.getServicetagInfo() != null) {
for (String serviceTag : productInfoDetail.getServicetagInfo()) {
serviceTagList.add(serviceTag);
}
}
}
}
}
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(serviceTagList);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
Can anyone suggest how can i achieve same using streams in java so that it will be readable.
Try this:
Set<String> tags = order.stream()
.flatMap(order -> order.getProductInfo().stream())
.map(ProductInfo::getServicetagInfo)
.collect(Collectors.toSet());
Full implementation:
for (OrderDetail orderDetail : arInvoiceOrderResponseBody.getOrders()) {
if (orderDetail != null && orderDetail.getProductInfo() != null) {
orderDetail.getProductInfo().removeAll(null); // Remove any null elems
Set<String> tags = orderDetail.getProductInfo().stream()
.flatMap(product -> (product.getServicetagInfo() == null) ? null : product.getServicetagInfo().stream())
.collect(Collectors.toSet());
tags.remove(null); // Remove null if exists
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(tags);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
With streams your code could be like this:
arInvoiceOrderResponseBody.getOrders().stream()
.filter(Objects::nonNull)
.forEach(YourClassName::mergeProductInfo);
The method mergeProductInfo would be:
private static void mergeProductInfo(OrderDetail orderDetail) {
List<String> serviceTagList = new ArrayList<>();
if (orderDetail.getProductInfo() != null) {
serviceTagList = orderDetail.getProductInfo().stream()
.filter(Objects::nonNull)
.map(ProductInfo::getServicetagInfo)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
ProductInfo productInfo = new ProductInfo();
productInfo.setServicetagInfo(serviceTagList);
orderDetail.setProductInfo(Arrays.asList(productInfo));
}
It could be simplified if you could be sure that you are not going to receive null lists or elements.
I know that in Java a method can return only one return type... But if there is any possiblity to this, kindly let me know. From the below method I am trying to return a list if condition satisfies else i am trying to return an error message.
Here is my code:
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return "Incorrect loginId..Please enter valid loginId";
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return companyList;
} else {
return "You are not Authorized";
}
} else {
return "Incorrect Password";
}
Yes its possible, create a custom Exception say 'MyAppException' and throw that exception with the error message you want.
Write your logic in a try{}catch block and throw the exception in catch so that the response has the error message
public List<CompanyMaster> getCompanies(#RequestBody UserDetails user) throws MyAppppException
{
try
{
//your logic which throws error
return companyList;
}
catch( final MyAppException we )
{
throw new MyAppException("User not found", HttpStatus.NOT_FOUND);
}
}
Refer this link
https://www.codejava.net/java-core/exception/how-to-create-custom-exceptions-in-java
You can achieve this by creating a new presenter Class which contains List and status of type String and change the return type of getCompanies method to presenter class like
public CompaniesPresenter getCompanies()
And your CompaniesPresenter class should look like
public class CompaniesPresenter {
private List<CompanyMaster> companyMaster;
private string status;
//default constructor
public CompaniesPresenter(){
}
//parameterized constructor to return only string in exception case
public CompaniesPresenter(Stirng status){
this.status = status;
}
//parametirized constructor to return success case
public CompaniesPresenter(List<CompanyMaster> companyMaster, Stirng status){
this.companyMaster = companyMaster;
this.status = status;
}
//getters and setters
}
This is how your updated method lokks like
#RequestMapping(value = "/getcompanies", method = RequestMethod.POST)
public CompaniesPresenter getCompanies(#RequestBody UserDetails user) {
String OrgLoginId = user.getOrgLoginId();
String password = user.getuPassword();
String checkLoginId = null;
String uPassword = null;
String encPassword = null;
String loginId = null;
String checkAuthorized = null;
// String loginId=userService.getLoginId(OrgLoginId);
List<Object[]> CheckIdPassword = userService.checkLoginId(OrgLoginId);
List<Object[]> results = CheckIdPassword;
for (Object[] obj : results) {
checkLoginId = obj[0].toString();
if (null == obj[1]) {
uPassword = "";
} else {
uPassword = obj[1].toString();
}
loginId = obj[2].toString();
}
checkAuthorized = loginId.substring(0, 3);
if (null != password) {
MD5 md5 = new MD5();
encPassword = md5.getPassword(password);
}
if (checkLoginId == null) {
return new CompaniesPresenter("Incorrect loginId..Please enter valid loginId");
} else if (encPassword.equals(uPassword)) {
if (checkAuthorized.equals("STE")) {
List<CompanyMaster> companyList = userService.getCompanyList(OrgLoginId);
return new CompaniesPresenter(companyList,"success");
} else {
return new CompaniesPresenter("You are not Authorized");
}
} else {
return new CompaniesPresenter("Incorrect Password");
}
This is not tested please make sure for any compilation errors
vavr's Either class would be a good choice.
The usage of custom exception is most reasonable solution. However, creating custom exception for just one case is not ideal always.
Another solution is to return empty List from your method, check if the List is empty in your servlet (or wherever you are invoking this method from), and show error message there.
It seems like you want to return multiple error messages for different cases. In this case, custom exception is recommended solution. If you don't like custom exceptions, you can return List<Object> and populate error message as the first element in the list. In the place where this List is obtained, check if the first element is instanceOf String or CompanyMaster. Based on what it is, you can perform your operations. This is a weird but possible solution (only if you don't like custom exceptions).
You need to understand the problem first. You are mixing two things here, first authorization, does the user has correct privileges to get company details, second giving the company details itself. Let's understand the first problem when a user tries to access "/getcompanies" endpoint will you let him in if does not have access, in REST world your security model should take care of it. I would use spring security to achieve this. My recommendation would be to explore on "interceptor" and solve the problem of invalid user. This will make your other problem easy as your "/getcompanies" endpoint can focus only on getting the details and return it (SRP).
I've build a cache that returns a value in list format when you enter the parameters. If that value is not in the cache, it goes to the database and retrieves it, putting it in the cache for future reference:
private ProfileDAO profileDAO;
private String[] temp;
private LoadingCache<String, List<Profile>> loadingCache = CacheBuilder.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(
new CacheLoader<String, List<Profile>>() {
#Override
public List<Profile> load(String key) throws Exception {
logger.info("Running method to retrieve from database");
temp = key.split("\\|");
String instance = temp[0];
String name = temp[1];
List<Profile> profiles= profileDAO.getProfileByFields(id, name);
if (profiles.isEmpty()) {
List<Profile> nullValue = new ArrayList<Profile>();
logger.info("Unable to find a value.");
return nullValue;
}
logger.info("Found a value");
return profileDAO.getProfileByFields(id, name);
}
}
);
public List<Profile> getProfileByFields(String id, String name) throws Exception {
String key = id.toLowerCase() + "|" + name.toLowerCase()
return loadingCache.get(key);
}
This seems to work fine, but it does not take into account null values. If I look for an entry that does not exist, I get an exception for :
com.google.common.cache.CacheLoader$InvalidCacheLoadException: CacheLoader returned null for key A01|Peter
I'd like to simply return an empty List(Profile) if there is no match in the database, but my if statement has failed. Is there any way around this error for this particular use case?
Though this feels a bit hacky, I think it's a more complete solution (Suresh's answer only really applies to collections).
Define a singleton object that will represent null, and insert that value into the cache instead of null (converting to null at retrieval time):
class MyDAO
{
static final Object NULL = new Object();
LoadingCache<String,Object> cache = CacheBuilder.newBuilder()
.build( new CacheLoader<>()
{
public Object load( String key )
{
Object value = database.get( key );
if( value == null )
return NULL;
return value;
}
});
Object get( String key )
{
Object value = cache.get( key );
if( value == NULL ) // use '==' to compare object references
return null;
return value;
}
}
I believe this approach is preferable, in terms of efficiency, to any involving the use of exceptions.
Using Optional class Optional<Object> as the cache value is the easiest and cleanest way to do it.
Make changes in your code to check first profiles is null or not as(using profiles == null ...) :
private ProfileDAO profileDAO;
private String[] temp;
private LoadingCache<String, List<Profile>> loadingCache = CacheBuilder.newBuilder()
.refreshAfterWrite(5, TimeUnit.MINUTES)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(
new CacheLoader<String, List<Profile>>() {
#Override
public List<Profile> load(String key) throws Exception {
logger.info("Running method to retrieve from database");
temp = key.split("\\|");
String instance = temp[0];
String name = temp[1];
List<Profile> profiles= profileDAO.getProfileByFields(id, name);
if (profiles == null || profiles.isEmpty()) {
List<Profile> nullValue = new ArrayList<Profile>();
logger.info("Unable to find a value.");
return nullValue;
}
logger.info("Found a value");
return profileDAO.getProfileByFields(id, name);
}
}
);
public List<Profile> getProfileByFields(String id, String name) throws Exception {
String key = id.toLowerCase() + "|" + name.toLowerCase()
return loadingCache.get(key);
}
Please check this code is working for you null values or not..
Help me, guys!
Please, advise a simple solution to convert type boolean from server to int in Android :)
When I log in, i get respone from server like this :
{"status":{"error":0,"code":200,"message":"OK"},"response":{"profile":{"id":114,"username":"k#gmail.com","full_name":"k","phone":"9999999","verified":1,"admin":0,"allow_dev":false,"company":{"id":9,"name":"ООО \"Фингерз медиа\"","email":"info#fingers.by","sample":null,"logo":"http://storage.guardian-glass.fingersmedia.by/0cb56968b3cec1bba301db8d51d1015e.jpg"}},"access_token":"15629e234e04a54a5a44ef2aa4eccb1d"}}
Then I get undefined exception: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected NUMBER but was BOOLEAN
This happens because of JsonElement "allow_dev" is boolean from server, and in Android I have "allow_dev" like int.
This is login method :
private void login(String email, String pass) {
showProgress();
JsonObject params = new JsonObject();
params.addProperty("username", email);
params.addProperty("password", pass);
UserOperationsTask task = new UserOperationsTask(UserOperationsTask.TaskMode.MODE_LOGIN, params) {
#Override
public void onLoadFinished(Bundle res) {
hideProgress();
String errorMessage = res.getString(UserOperationsTask.RESULT_ERROR_STRING);
if (errorMessage != null) {
showMessage(getString(R.string.login_error), getString(R.string.server_request_error));
} else {
String json = res.getString(UserOperationsTask.RESULT_JSON_STRING);
if (json != null) {
JsonParser parser = new JsonParser();
JsonObject responseData = parser.parse(json).getAsJsonObject();
JsonObject companyObj = responseData.getAsJsonObject("profile").getAsJsonObject("company");
}
setRegisteredMode();
}
}
};
task.execute(this);
}
This method parse response and I tried to convert allow_dev type from boolean to int, but I dont understand whether I'm doing right?
private Bundle parseProfileResponse(Context context, JsonObject responseData) {
Log.d(TAG, "parseProfileResponse");
// I tried convert allow_dev type from boolean to int
String allow_dev_server = String.valueOf(responseData.get("allow_dev"));
boolean b = allow_dev_server.equals("true");
int allow_dev = b ? 1 : 0; // true == 1
Profile profile;
profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class);
profile.allow_dev = allow_dev;
Bundle res = new Bundle();
res.putParcelable(RESULT_OBJ, profile);
res.putString(RESULT_JSON_STRING, responseData.toString());
try {
Cache.saveToCache(context, profile);
} catch (RemoteException e) {
Log.d(TAG, "parseAuthResponse RemoteException: " + e.toString());
res.putString(RESULT_ERROR_STRING, context.getString(R.string.database_error));
} catch (OperationApplicationException e) {
Log.d(TAG, "parseAuthResponse OperationApplicationException: " + e.toString());
res.putString(RESULT_ERROR_STRING, context.getString(R.string.database_error));
}
return res;
}
I have to get "allow_dev" convert it in int and write to database.
If you can switch to mappings, you can use everything static typing can give you, comparing to weakly "typed" JsonElement and its subclasses. It has several advantages: compile-time checking, more robust code, IDE support, etc. The major disadvantage is that you have to write custom mappings, however you there tools (online as well) that can try to generate simple mapping classes based on the given sample JSON (for example, a very popular tool here: http://www.jsonschema2pojo.org/).
Now, let's create some mappings. The mappings like the ones below are used rarely: final fields (used for "server responses" that are no supposed to be modified programmatically; Gson can assign such fields anyway); null for non-primitives and some hacks for primitive type defaults values to cheat the compiler (like Integer.value(0) but not simply 0: otherwise, javac may inline constants, thus Gson cannot affect them); no getters/setters (data-transfer objects are just data bags, but yes, getters can work better). Anyway, you can use your style, and the followings mappings are used for demonstration purposes (the mappings code has even compact formatting: one property per line collapsing the annotations).
final class Response<T> {
final Status status = null;
final T response = null;
}
final class Status {
final int error = Integer.valueOf(0);
final int code = Integer.valueOf(0);
final String message = null;
}
final class ProfileAndAccessToken {
final Profile profile = null;
#SerializedName("access_token") final String accessToken = null;
}
final class Profile {
final int id = Integer.valueOf(0);
final String username = null;
#SerializedName("full_name") final String fullName = null;
final String phone = null;
final int verified = Integer.valueOf(0);
final int admin = Integer.valueOf(0);
#SerializedName("allow_dev") #JsonAdapter(BooleanToIntTypeAdapter.class) final int allowDev = Integer.valueOf(0);
final Company company = null;
}
final class Company {
final int id = Integer.valueOf(0);
final String name = null;
final String email = null;
final String sample = null;
final URL logo = null;
}
Note two annotations above:
#SerializedName -- this annotation can "rename" fields so you can use even special characters (however it's discouraged, and it's typically used to map incoming JSON property names to javaCamelNamingConventions).
#JsonAdapter -- this annotation can "attach" a special type adapter to a certain field, so that it could convert JSON properties to the given field and vice versa.
Now let's implement a type adapter that can convert incoming boolean values to local int values and vice versa. Note that type adapters work in streaming manner, so you have to read JSON tokens stream on fly during read, and, of course, generate JSON tokens stream during write.
final class BooleanToIntTypeAdapter
extends TypeAdapter<Integer> {
// Public constructors may be evil, and let expose as less as possible
// Gson can still instantiate this type adapter itself
private BooleanToIntTypeAdapter() {
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final Integer value)
throws IOException {
// If the given value is null, we must write the `null` token to the output JSON tokens stream anyway in order not to break JSON documents
if ( value == null ) {
out.nullValue();
return;
}
// Let's assume that we can accept either 0 or 1 that are mapped to false and true respectively
switch ( value ) {
case 0:
out.value(false);
break;
case 1:
out.value(true);
break;
default:
// Or throw an exception as fast as we can
throw new IllegalArgumentException("Cannot convert " + value + " to a boolean literal");
}
}
#Override
public Integer read(final JsonReader in)
throws IOException {
// Peek the next token type, and if it's null, then return null value too
if ( in.peek() == NULL ) {
return null;
}
// Otherwise parse the next token as boolean and map it either to 1 or 0
return in.nextBoolean() ? 1 : 0;
}
}
That's all you need in Gson. Now, for you entire JSON, since the response mapping class is generic, you have to tell Gson what the T is. Gson accepts java.lang.reflect.Type in the fromJson method, and this type can hold both raw and parameterized types, so Gson could (de)serialize more accurately.
private static final Type profileAndAccessTokenResponse = new TypeToken<Response<ProfileAndAccessToken>>() {
}.getType();
final Response<ProfileAndAccessToken> response = gson.fromJson(JSON, profileAndAccessTokenResponse);
System.out.println(response.response.profile.allowDev);
System.out.println(gson.toJson(response, profileAndAccessTokenResponse));
Output:
0
{"status":{"error":0,"code":200,"message":"OK"},"response":{"profile":{"id":114,"username":"k#gmail.com","full_name":"k","phone":"9999999","verified":1,"admin":0,"allow_dev":false,"company":{"id":9,"name":"ООО \"Фингерз медиа\"","email":"info#fingers.by","logo":"http://storage.guardian-glass.fingersmedia.by/0cb56968b3cec1bba301db8d51d1015e.jpg"}},"access_token":"15629e234e04a54a5a44ef2aa4eccb1d"}}
Note that the first line is 0: this what is generated with BooleanToIntTypeAdapter. Backing to your code:
String allow_dev_server = String.valueOf(responseData.get("allow_dev"));
boolean b = allow_dev_server.equals("true");
int allow_dev = b ? 1 : 0; // true == 1
Profile profile;
profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class);
profile.allow_dev = allow_dev;
Can be replaced with simple:
final Profile profile = GsonHolder.getGSONInstance().fromJson(responseData.getAsJsonObject("profile"), Profile.class)
// now `Profile.allowDev` is 0 or 1 automatically
Note that responseData can be replace with a particular mapping, so you could even not parse at that line: probably you might simply pass the whole response object as a class mapping rather that JsonObject in your parseProfileResponse -- it would be more robust.